Files
Maksim Totmin 17ff5c591f refactor!: make addrkit a multi-city address normalizer
- remove the events/outages module, ArcGIS adapter, portal config and event statuses
- add a configurable city dictionary/parser and make City part of StreetKey
- seed: read city/city_name, namespace IDs by city, keep structures distinct
- fix reliability: schema v2 with fail-closed load, deterministic ordering,
  input limits, config validation, atomic DB publish, LLM response caps
- add read-only shadow mode comparing the old ES path with addrkit
- breaking: registry.db must be re-seeded; config has no portal section
2026-10-27 14:35:51 +07:00

87 lines
3.1 KiB
Go

package eval
import (
"math/rand"
"testing"
"unicode/utf8"
"git.totmin.ru/en2zmax/addrkit/internal/domain/address"
"git.totmin.ru/en2zmax/addrkit/internal/domain/match"
)
// TestMoveTypeFront guards against the slice-aliasing regression that made the
// type word disappear.
func TestMoveTypeFront(t *testing.T) {
got := moveTypeFront("станционная улица 1/1")
if got != "улица станционная 1/1" {
t.Fatalf("moveTypeFront = %q", got)
}
got = moveTypeFront("2-я станционная улица 1/1")
if got != "улица 2-я станционная 1/1" {
t.Fatalf("moveTypeFront with ordinal = %q", got)
}
}
// TestAddYoKeepsValidUTF8 guards against the byte-index regression.
func TestAddYoKeepsValidUTF8(t *testing.T) {
cases := []string{"переулок 5", "Северный проезд 7", "Зеленхозовская улица 48", "дом 1"}
for _, in := range cases {
out := addYo(in)
if out != "" && !utf8.ValidString(out) {
t.Fatalf("addYo(%q) produced invalid UTF-8: %q", in, out)
}
}
if got := addYo("переулок 5"); got != "пёреулок 5" {
t.Fatalf("addYo = %q", got)
}
}
// TestMutateOnlyNameTokens verifies typos never target the street type word,
// which would turn into a bogus name token.
func TestMutateOnlyNameTokens(t *testing.T) {
rng := rand.New(rand.NewSource(7))
for i := 0; i < 200; i++ {
got := mutateLongestToken("Станционная улица 1/1", rng)
if got == "" {
continue
}
if got == "Станционная улицб 1/1" || got == "Станционная улиц 1/1" {
t.Fatalf("type word was mutated: %q", got)
}
}
}
// TestRunDetectsWrongCity guards the multi-city safety net: canonical address
// text is identical across cities, so a found result must be checked against
// the expected city, not only against street/house text.
func TestRunDetectsWrongCity(t *testing.T) {
parser := address.NewParser(address.NewDictionary([]address.City{{Name: "Бердск"}}), "")
p := parser.ParseInCity("Ленина улица 5", "Бердск")
entries := []address.Entry{{
ID: "b1", Address: "Ленина улица 5",
City: p.City, CityKey: p.CityKey,
Ordinal: p.Ordinal, NameKey: p.NameKey, StreetKey: p.StreetKey, House: p.House,
}}
resolver := match.NewResolverWithOptions(
match.NewIndex("test", entries, match.DefaultOptions()),
nil,
match.ResolverOptions{Parser: parser},
)
cases := []Case{
{Query: "Ленина 5", Expect: "Ленина улица 5", ExpectCity: "Новосибирск", Category: "wrong_city", RequireExact: true},
{Query: "Ленина 5", Expect: "Ленина улица 5", ExpectCity: "Бердск", Category: "right_city", RequireExact: true},
}
rep := Run(resolver, cases)
if got := rep.Categories["wrong_city"]; got.WrongCity != 1 || got.Exact != 0 {
t.Fatalf("wrong_city = %+v, want WrongCity=1 Exact=0", got)
}
if got := rep.Categories["right_city"]; got.Exact != 1 || got.WrongCity != 0 {
t.Fatalf("right_city = %+v, want Exact=1 WrongCity=0", got)
}
if rep.Wrong != 1 {
t.Fatalf("report Wrong = %d, want 1", rep.Wrong)
}
}