package address import "testing" func TestParseHouseBlock(t *testing.T) { tests := []struct { name string raw string wantOrdinal string wantName string wantType string wantHouse string wantFrac string wantLetter string wantBuild string wantBFrac string wantBLetter string wantStruct string wantHas bool }{ { name: "ordinal, fraction and structure", raw: "2-я Станционная улица 1/1 стр.", wantOrdinal: "2", wantName: "станционная", wantType: "улица", wantHouse: "1", wantFrac: "1", wantStruct: "", wantHas: true, }, { name: "building with fraction and structure", raw: "Станционная улица 30а к9/1 стр.", wantName: "станционная", wantType: "улица", wantHouse: "30", wantLetter: "а", wantBuild: "9", wantBFrac: "1", wantHas: true, }, { name: "letter glued to house", raw: "10-й Бронный переулок 33а", wantOrdinal: "10", wantName: "бронный", wantType: "переулок", wantHouse: "33", wantLetter: "а", wantHas: true, }, { name: "microdistrict, two-word name", raw: "Зеленый бор микрорайон 13", wantName: "бор зеленый", wantType: "микрорайон", wantHouse: "13", wantHas: true, }, { name: "spelled-out ordinal", raw: "вторая Станционная улица 1", wantOrdinal: "2", wantName: "станционная", wantType: "улица", wantHouse: "1", wantHas: true, }, { name: "en dash variant", raw: "2–я Станционная улица 1", wantOrdinal: "2", wantName: "станционная", wantType: "улица", wantHouse: "1", wantHas: true, }, { name: "no house", raw: "Станционная улица", wantName: "станционная", wantType: "улица", wantHas: false, }, { name: "structure with number only", raw: "Станционная улица 3 стр.", wantName: "станционная", wantType: "улица", wantHouse: "3", wantStruct: "", wantHas: true, }, { name: "letter before fraction", raw: "Выборная улица 45а/4", wantName: "выборная", wantType: "улица", wantHouse: "45", wantLetter: "а", wantFrac: "4", wantHas: true, }, { name: "corpus letter without digit", raw: "1-я Ельцовка улица 1 кД", wantOrdinal: "1", wantName: "ельцовка", wantType: "улица", wantHouse: "1", wantBLetter: "д", wantHas: true, }, { name: "two type words in a registry name", raw: "Гусинобродский Тракт улица 21", wantName: "гусинобродский", wantType: "тракт", wantHouse: "21", wantHas: true, }, { name: "name coincides with its type word", raw: "Набережная улица 5", wantName: "набережная", wantType: "набережная", wantHouse: "5", wantHas: true, }, { name: "abbreviations dom and flat", raw: "ул. Кошурникова, д. 11/1 кв. 5", wantName: "кошурникова", wantType: "улица", wantHouse: "11", wantFrac: "1", wantHas: true, }, { name: "portal district suffix in parentheses", raw: "Медкадры улица, 1 (Заельцовский район)", wantName: "медкадры", wantType: "улица", wantHouse: "1", wantHas: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got := Parse(tt.raw) if got.Ordinal != tt.wantOrdinal { t.Errorf("Ordinal = %q, want %q", got.Ordinal, tt.wantOrdinal) } if got.NameKey != tt.wantName { t.Errorf("NameKey = %q, want %q", got.NameKey, tt.wantName) } if got.StreetType != tt.wantType { t.Errorf("StreetType = %q, want %q", got.StreetType, tt.wantType) } if got.House != tt.wantHouse { t.Errorf("House = %q, want %q", got.House, tt.wantHouse) } if got.HouseFrac != tt.wantFrac { t.Errorf("HouseFrac = %q, want %q", got.HouseFrac, tt.wantFrac) } if got.HouseLetter != tt.wantLetter { t.Errorf("HouseLetter = %q, want %q", got.HouseLetter, tt.wantLetter) } if got.Building != tt.wantBuild { t.Errorf("Building = %q, want %q", got.Building, tt.wantBuild) } if got.BuildingFrac != tt.wantBFrac { t.Errorf("BuildingFrac = %q, want %q", got.BuildingFrac, tt.wantBFrac) } if got.BuildingLetter != tt.wantBLetter { t.Errorf("BuildingLetter = %q, want %q", got.BuildingLetter, tt.wantBLetter) } if got.Structure != tt.wantStruct { t.Errorf("Structure = %q, want %q", got.Structure, tt.wantStruct) } if got.HasHouse != tt.wantHas { t.Errorf("HasHouse = %v, want %v", got.HasHouse, tt.wantHas) } }) } } // TestParseDeterministic guards the core invariant: parsing the same input // always yields the same structured result and the same street key. func TestParseDeterministic(t *testing.T) { input := "2-я Станционная улица 1/1 стр." first := Parse(input) for i := 0; i < 100; i++ { if got := Parse(input); got.StreetKey != first.StreetKey || got.House != first.House { t.Fatalf("non-deterministic parse: %+v vs %+v", got, first) } } } func TestNormalizeIdempotent(t *testing.T) { inputs := []string{ " 2-Я Станционная\u00a0улица 1/1 стр. ", "ЁЛКИ д. 5, кв. 3", "СТАНИЦИОННАЯ — улица, 30а", "Медкадры улица, 1 (Заельцовский район)", "", } for _, in := range inputs { once := NormalizeText(in) twice := NormalizeText(once) if once != twice { t.Errorf("NormalizeText not idempotent for %q: %q != %q", in, once, twice) } } }