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
This commit is contained in:
@@ -1,7 +1,9 @@
|
||||
package registrystore
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"git.totmin.ru/en2zmax/addrkit/internal/domain/address"
|
||||
@@ -11,11 +13,13 @@ func TestSaveLoadRoundTrip(t *testing.T) {
|
||||
path := filepath.Join(t.TempDir(), "registry.db")
|
||||
|
||||
addr := "Станционная улица 1/1"
|
||||
p := address.Parse(addr)
|
||||
parser := address.NewParser(address.NewDictionary([]address.City{{Name: "Новосибирск"}}), "")
|
||||
p := parser.ParseInCity(addr, "Новосибирск")
|
||||
entries := []address.Entry{{
|
||||
ID: "a1", Address: addr, District: "Ленинский",
|
||||
City: p.City, CityKey: p.CityKey,
|
||||
Ordinal: p.Ordinal, NameKey: p.NameKey, StreetType: p.StreetType, StreetKey: p.StreetKey,
|
||||
House: p.House, HouseFrac: p.HouseFrac,
|
||||
House: p.House, HouseFrac: p.HouseFrac, Structure: "2",
|
||||
}}
|
||||
|
||||
if err := Save(path, "2026-10-09", entries); err != nil {
|
||||
@@ -35,6 +39,58 @@ func TestSaveLoadRoundTrip(t *testing.T) {
|
||||
if got[0].Address != addr || got[0].District != "Ленинский" || got[0].StreetKey != p.StreetKey {
|
||||
t.Errorf("round-tripped entry mismatch: %+v", got[0])
|
||||
}
|
||||
if got[0].City != "Новосибирск" || got[0].CityKey != "новосибирск" || got[0].Structure != "2" {
|
||||
t.Errorf("city/structure not round-tripped: %+v", got[0])
|
||||
}
|
||||
}
|
||||
|
||||
func TestSaveRejectsEmptyIndex(t *testing.T) {
|
||||
path := filepath.Join(t.TempDir(), "registry.db")
|
||||
if err := Save(path, "v1", nil); err == nil {
|
||||
t.Fatal("expected error for empty index")
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadRejectsIncompatibleSchema(t *testing.T) {
|
||||
path := filepath.Join(t.TempDir(), "old.db")
|
||||
uri, err := dsn(path, false)
|
||||
if err != nil {
|
||||
t.Fatalf("dsn: %v", err)
|
||||
}
|
||||
db, err := sql.Open("sqlite", uri)
|
||||
if err != nil {
|
||||
t.Fatalf("open: %v", err)
|
||||
}
|
||||
if _, err := db.Exec(`CREATE TABLE meta (key TEXT PRIMARY KEY, value TEXT NOT NULL)`); err != nil {
|
||||
t.Fatalf("create meta: %v", err)
|
||||
}
|
||||
if _, err := db.Exec(`INSERT INTO meta(key, value) VALUES ('schema_version', '1')`); err != nil {
|
||||
t.Fatalf("insert schema version: %v", err)
|
||||
}
|
||||
db.Close()
|
||||
|
||||
_, _, err = Load(path)
|
||||
if err == nil || !strings.Contains(err.Error(), "unsupported index schema") {
|
||||
t.Fatalf("Load error = %v, want unsupported schema", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestPathWithSpecialCharacters guards the SQLite URI construction: a naive
|
||||
// "file:" + path breaks on ?, # and % in the path.
|
||||
func TestPathWithSpecialCharacters(t *testing.T) {
|
||||
path := filepath.Join(t.TempDir(), "registry ? # 100%.db")
|
||||
entries := []address.Entry{{ID: "x", Address: "Первая улица 1"}}
|
||||
|
||||
if err := Save(path, "v1", entries); err != nil {
|
||||
t.Fatalf("Save: %v", err)
|
||||
}
|
||||
version, got, err := Load(path)
|
||||
if err != nil {
|
||||
t.Fatalf("Load: %v", err)
|
||||
}
|
||||
if version != "v1" || len(got) != 1 {
|
||||
t.Fatalf("version=%q entries=%+v", version, got)
|
||||
}
|
||||
}
|
||||
|
||||
// TestSaveIsAtomicOverwrite verifies that re-seeding replaces the previous data
|
||||
|
||||
Reference in New Issue
Block a user