package region import ( "testing" "ai-operator/internal/dialogue/state" ) func TestCatalogCountsAndNames(t *testing.T) { r := NewDefaultResolver() if got := len(r.ListEnabled()); got != 20 { t.Fatalf("enabled=%d", got) } if got := len(r.ListDisabled()); got != 1 { t.Fatalf("disabled=%d", got) } city, oblast := 0, 0 seen := map[string]bool{} for _, reg := range r.ListEnabled() { if reg.Code == "" || seen[reg.Code] { t.Fatalf("bad code %q", reg.Code) } seen[reg.Code] = true if reg.DisplayNameRU == "" || reg.DisplayNameKK == "" { t.Fatalf("missing display names for %s", reg.Code) } switch reg.Type { case RegionTypeRepublicCity: city++ case RegionTypeOblast: oblast++ } } if city != 3 || oblast != 17 { t.Fatalf("city=%d oblast=%d", city, oblast) } } func TestNormalizer(t *testing.T) { cases := map[string]string{ " Г. Алматы! ": "город алматы", "Алматинская обл.": "алматинская обл", "Нұр-Сұлтан": "нұр сұлтан", "Восточно-Казахстанская": "восточно казахстанская", "Қазақша, Астана": "қазақша астана", } for input, want := range cases { if got := Normalize(input); got != want { t.Fatalf("Normalize(%q)=%q want %q", input, got, want) } } } func TestRepublicCityDetection(t *testing.T) { r := NewDefaultResolver() cases := map[string]string{ "астана": "astana_city", "город астана": "astana_city", "астана қаласы": "astana_city", "astana": "astana_city", "нурсултан": "astana_city", "нұр-сұлтан": "astana_city", "город алматы": "almaty_city", "алматы қаласы": "almaty_city", "шымкент": "shymkent_city", "шимкент": "shymkent_city", "shymkent": "shymkent_city", } for input, want := range cases { got := r.Resolve(input, SourceCLI) if got.RegionCode != want || got.Confidence < MediumConfidence { t.Fatalf("%q => %+v want %s", input, got, want) } } } func TestOblastDetection(t *testing.T) { r := NewDefaultResolver() cases := map[string]string{ "Акмолинская область": "akmola_region", "Ақмола облысы": "akmola_region", "Актюбинская область": "aktobe_region", "Ақтөбе облысы": "aktobe_region", "Алматинская область": "almaty_region", "Алматы облысы": "almaty_region", "Атырауская область": "atyrau_region", "Атырау облысы": "atyrau_region", "Восточно-Казахстанская область": "east_kazakhstan_region", "Шығыс Қазақстан облысы": "east_kazakhstan_region", "Жамбылская область": "zhambyl_region", "Жамбыл облысы": "zhambyl_region", "Западно-Казахстанская область": "west_kazakhstan_region", "Батыс Қазақстан облысы": "west_kazakhstan_region", "Карагандинская область": "karaganda_region", "Қарағанды облысы": "karaganda_region", "Костанайская область": "kostanay_region", "Қостанай облысы": "kostanay_region", "Кызылординская область": "kyzylorda_region", "Қызылорда облысы": "kyzylorda_region", "Мангистауская область": "mangystau_region", "Маңғыстау облысы": "mangystau_region", "Павлодарская область": "pavlodar_region", "Павлодар облысы": "pavlodar_region", "Северо-Казахстанская область": "north_kazakhstan_region", "Солтүстік Қазақстан облысы": "north_kazakhstan_region", "Туркестанская область": "turkistan_region", "Түркістан облысы": "turkistan_region", "область Абай": "abai_region", "Абай облысы": "abai_region", "область Жетісу": "jetisu_region", "Жетісу облысы": "jetisu_region", "область Ұлытау": "ulytau_region", "Ұлытау облысы": "ulytau_region", } for input, want := range cases { got := r.Resolve(input, SourceCLI) if got.RegionCode != want || got.Confidence < MediumConfidence { t.Fatalf("%q => %+v want %s", input, got, want) } } } func TestAbbreviations(t *testing.T) { r := NewDefaultResolver() cases := map[string]string{"ВКО": "east_kazakhstan_region", "ЗКО": "west_kazakhstan_region", "СКО": "north_kazakhstan_region", "vko": "east_kazakhstan_region", "zko": "west_kazakhstan_region", "sko": "north_kazakhstan_region"} for input, want := range cases { if got := r.Resolve(input, SourceCLI); got.RegionCode != want || got.ReasonCode != "abbreviation" { t.Fatalf("%q => %+v want %s", input, got, want) } } } func TestAlmatyAmbiguityAndClarification(t *testing.T) { r := NewDefaultResolver() for _, input := range []string{"Алматы", "almaty", "Алматы тарифы"} { got := r.Resolve(input, SourceCLI) if !got.NeedsClarification || got.ReasonCode != "ambiguous_almaty" || len(got.Candidates) != 2 { t.Fatalf("%q => %+v", input, got) } } pending := []string{"almaty_city", "almaty_region"} if got := r.ResolvePending(pending, "город", SourceCLI); got.RegionCode != "almaty_city" { t.Fatalf("city clarification: %+v", got) } if got := r.ResolvePending(pending, "қала", SourceCLI); got.RegionCode != "almaty_city" { t.Fatalf("kk city clarification: %+v", got) } if got := r.ResolvePending(pending, "область", SourceCLI); got.RegionCode != "almaty_region" { t.Fatalf("region clarification: %+v", got) } if got := r.ResolvePending(pending, "облыс", SourceCLI); got.RegionCode != "almaty_region" { t.Fatalf("kk region clarification: %+v", got) } } func TestFalsePositivesAndDisabled(t *testing.T) { r := NewDefaultResolver() for _, input := range []string{"Казахтелеком", "Казахстан", "область", "город", "мой тариф", "астана балет", "карагандинский уголь", "у меня вопрос по шымкентскому номеру", "алматинский район"} { got := r.Resolve(input, SourceCLI) if got.RegionCode != "" { t.Fatalf("false positive %q => %+v", input, got) } } got := r.Resolve("Байконур", SourceCLI) if got.Intent != IntentUnsupported || got.ReasonCode != "disabled_region" { t.Fatalf("disabled: %+v", got) } } func TestSelectionPolicy(t *testing.T) { r := NewDefaultResolver() astana := r.Resolve("Астана", SourceCLI) if d := ShouldApplyRegionResolution(SelectionContext{CurrentState: state.StateLanguageSelection, CurrentLanguage: state.LanguageUnknown}, astana); d.Apply || d.ReasonCode != "language_required" { t.Fatalf("language selection applied region: %+v", d) } if d := ShouldApplyRegionResolution(SelectionContext{CurrentState: state.StateRegionSelection, CurrentLanguage: state.LanguageRU}, astana); !d.Apply || d.RegionCode != "astana_city" { t.Fatalf("region selection did not apply: %+v", d) } amb := r.Resolve("Алматы", SourceCLI) if d := ShouldApplyRegionResolution(SelectionContext{CurrentState: state.StateRegionSelection, CurrentLanguage: state.LanguageRU}, amb); !d.NeedsClarification { t.Fatalf("ambiguous not clarified: %+v", d) } if d := ShouldApplyRegionResolution(SelectionContext{CurrentState: state.StateReadyToHelp, CurrentLanguage: state.LanguageRU, CurrentRegionCode: "astana_city", AllowChange: true}, astana); d.Apply { t.Fatalf("random mention switched: %+v", d) } astana.Intent = IntentRegionChange if d := ShouldApplyRegionResolution(SelectionContext{CurrentState: state.StateReadyToHelp, CurrentLanguage: state.LanguageRU, CurrentRegionCode: "shymkent_city", AllowChange: true}, astana); !d.Apply { t.Fatalf("explicit change not applied: %+v", d) } if d := ShouldApplyRegionResolution(SelectionContext{CurrentState: state.StateEnded, CurrentLanguage: state.LanguageRU}, astana); d.Apply { t.Fatalf("ended applied: %+v", d) } }