package config import ( "os" "path/filepath" "testing" "git.totmin.ru/en2zmax/addrkit/internal/domain/match" ) func writeFile(t *testing.T, dir, name, content string) string { t.Helper() path := filepath.Join(dir, name) if err := os.WriteFile(path, []byte(content), 0o600); err != nil { t.Fatalf("write %s: %v", name, err) } return path } func baseConfig(promptFields string) string { return `{ "addr": ":6171", "data_path": "registry.db", "llm": { "enabled": true, "url": "http://llm"` + promptFields + ` } }` } func TestLoadInlineSystemPrompt(t *testing.T) { dir := t.TempDir() path := writeFile(t, dir, "config.json", baseConfig(`, "system_prompt": "INLINE PROMPT"`)) cfg, err := Load(path) if err != nil { t.Fatalf("Load: %v", err) } if cfg.LLM.SystemPrompt != "INLINE PROMPT" { t.Errorf("SystemPrompt = %q", cfg.LLM.SystemPrompt) } } func TestLoadSystemPromptFromFile(t *testing.T) { dir := t.TempDir() promptFile := writeFile(t, dir, "prompt.txt", "PROMPT FROM FILE\n") path := writeFile(t, dir, "config.json", baseConfig(`, "system_prompt_file": "`+promptFile+`"`)) cfg, err := Load(path) if err != nil { t.Fatalf("Load: %v", err) } if cfg.LLM.SystemPrompt != "PROMPT FROM FILE\n" { t.Errorf("SystemPrompt = %q", cfg.LLM.SystemPrompt) } } func TestLoadMissingSystemPromptFileFailsClosed(t *testing.T) { dir := t.TempDir() path := writeFile(t, dir, "config.json", baseConfig(`, "system_prompt_file": "`+filepath.Join(dir, "nope.txt")+`"`)) if _, err := Load(path); err == nil { t.Fatal("expected error for missing system_prompt_file") } } func TestDefaultsMatchMatcher(t *testing.T) { dir := t.TempDir() path := writeFile(t, dir, "config.json", `{"addr": ":6171", "data_path": "x.db"}`) cfg, err := Load(path) if err != nil { t.Fatalf("Load: %v", err) } want := match.DefaultOptions() if cfg.Resolve.FuzzyThreshold != want.FuzzyThreshold || cfg.Resolve.AmbiguityMargin != want.AmbiguityMargin || cfg.Resolve.MaxCandidates != want.MaxCandidates { t.Fatalf("config defaults %+v do not match matcher defaults %+v", cfg.Resolve, want) } } func TestRejectsBadResolveRanges(t *testing.T) { dir := t.TempDir() cases := map[string]string{ "threshold": `{"addr":":6171","data_path":"x.db","resolve":{"fuzzy_threshold":0}}`, "margin": `{"addr":":6171","data_path":"x.db","resolve":{"ambiguity_margin":2}}`, "candidates": `{"addr":":6171","data_path":"x.db","resolve":{"max_candidates":0}}`, } for name, body := range cases { path := writeFile(t, dir, name+".json", body) if _, err := Load(path); err == nil { t.Errorf("%s: expected validation error", name) } } } func TestCitiesAndDefaultCity(t *testing.T) { dir := t.TempDir() path := writeFile(t, dir, "config.json", `{ "addr": ":6171", "data_path": "x.db", "cities": [ {"name": "Новосибирск", "aliases": ["нск", "новосибирская область"]}, {"name": "Бердск"} ], "default_city": "Новосибирск" }`) cfg, err := Load(path) if err != nil { t.Fatalf("Load: %v", err) } if len(cfg.Cities) != 2 || cfg.DefaultCity != "Новосибирск" { t.Fatalf("cities = %+v, default = %q", cfg.Cities, cfg.DefaultCity) } } func TestRejectsUnknownDefaultCity(t *testing.T) { dir := t.TempDir() path := writeFile(t, dir, "config.json", `{ "addr": ":6171", "data_path": "x.db", "cities": [{"name": "Бердск"}], "default_city": "Новосибирск" }`) if _, err := Load(path); err == nil { t.Fatal("expected error for default_city absent from cities") } } func TestLoadCitiesFile(t *testing.T) { dir := t.TempDir() path := writeFile(t, dir, "cities.json", `[ {"name": "Новосибирск", "aliases": ["нск"]}, {"name": "Бердск"} ]`) cities, err := LoadCitiesFile(path) if err != nil { t.Fatalf("LoadCitiesFile: %v", err) } if len(cities) != 2 || cities[0].Name != "Новосибирск" { t.Fatalf("cities = %+v", cities) } } func TestRejectsUnknownConfigFields(t *testing.T) { dir := t.TempDir() // "portal" was removed with the events module; silently ignoring it would // hide a stale deployment config. path := writeFile(t, dir, "config.json", `{"addr":":6171","data_path":"x.db","portal":{"url":"http://example"}}`) if _, err := Load(path); err == nil { t.Fatal("expected error for unknown config field") } }