package registrystore import ( "database/sql" "path/filepath" "strings" "testing" "git.totmin.ru/en2zmax/addrkit/internal/domain/address" ) func TestSaveLoadRoundTrip(t *testing.T) { path := filepath.Join(t.TempDir(), "registry.db") addr := "Станционная улица 1/1" 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, Structure: "2", }} if err := Save(path, "2026-10-09", entries); err != nil { t.Fatalf("Save: %v", err) } version, got, err := Load(path) if err != nil { t.Fatalf("Load: %v", err) } if version != "2026-10-09" { t.Errorf("version = %q", version) } if len(got) != 1 { t.Fatalf("entries = %d, want 1", len(got)) } 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 // rather than merging with it. func TestSaveIsAtomicOverwrite(t *testing.T) { path := filepath.Join(t.TempDir(), "registry.db") if err := Save(path, "v1", []address.Entry{{ID: "x", Address: "Первая улица 1"}}); err != nil { t.Fatalf("Save v1: %v", err) } if err := Save(path, "v2", []address.Entry{{ID: "y", Address: "Вторая улица 2"}}); err != nil { t.Fatalf("Save v2: %v", err) } version, got, err := Load(path) if err != nil { t.Fatalf("Load: %v", err) } if version != "v2" || len(got) != 1 || got[0].Address != "Вторая улица 2" { t.Fatalf("unexpected state after overwrite: version=%q entries=%+v", version, got) } }