package config import ( "os" "path/filepath" "testing" ) func writeConfig(t *testing.T, path, content string) { t.Helper() if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil { t.Fatal(err) } if err := os.WriteFile(path, []byte(content), 0o644); err != nil { t.Fatal(err) } } func TestLoad_LayerPrecedence_RepoOverridesHome(t *testing.T) { home := t.TempDir() repo := t.TempDir() t.Setenv("HOME", home) t.Setenv("XDG_CONFIG_HOME", "") // macOS UserHomeDir ignores this, aber sauber halten t.Setenv("NUB_MODEL", "") t.Setenv("NUB_BASE_URL", "") t.Setenv("NUB_API_KEY", "") t.Setenv("OPENAI_API_KEY", "") writeConfig(t, filepath.Join(home, ".config", "nub", "config.toml"), ` version = 1 [model] default = "from-home" endpoint = "openai" `) writeConfig(t, filepath.Join(repo, ".nub", "config.toml"), ` version = 1 [model] default = "from-repo" endpoint = "openai" `) cfg, err := Load(repo) if err != nil { t.Fatal(err) } if cfg.Model.Default != "from-repo" { t.Errorf("model.default = %q, want from-repo (repo layer must win over home)", cfg.Model.Default) } } func TestLoad_EnvOverridesFiles(t *testing.T) { home := t.TempDir() repo := t.TempDir() t.Setenv("HOME", home) t.Setenv("NUB_MODEL", "from-env") t.Setenv("NUB_BASE_URL", "") t.Setenv("NUB_API_KEY", "") t.Setenv("OPENAI_API_KEY", "") writeConfig(t, filepath.Join(repo, ".nub", "config.toml"), ` version = 1 [model] default = "from-repo" endpoint = "openai" `) cfg, err := Load(repo) if err != nil { t.Fatal(err) } if cfg.Model.Default != "from-env" { t.Errorf("model.default = %q, want from-env (env must win over repo file)", cfg.Model.Default) } } func TestLoad_DefaultsApplyWithoutAnyConfigFile(t *testing.T) { home := t.TempDir() repo := t.TempDir() t.Setenv("HOME", home) t.Setenv("NUB_MODEL", "") t.Setenv("NUB_BASE_URL", "") t.Setenv("NUB_API_KEY", "") t.Setenv("OPENAI_API_KEY", "") cfg, err := Load(repo) if err != nil { t.Fatal(err) } if cfg.Model.Default != "gpt-4o" { t.Errorf("model.default = %q, want default gpt-4o", cfg.Model.Default) } if len(cfg.Context.Files) != 2 { t.Errorf("context.files = %v, want 2 defaults", cfg.Context.Files) } } func TestLoad_RejectsUnsupportedVersion(t *testing.T) { home := t.TempDir() repo := t.TempDir() t.Setenv("HOME", home) writeConfig(t, filepath.Join(repo, ".nub", "config.toml"), `version = 2`) if _, err := Load(repo); err == nil { t.Fatal("expected error for unsupported config version") } } func TestResolve_EnvKeyIndirection(t *testing.T) { t.Setenv("MY_TEST_KEY", "sk-secret") cfg := Defaults() cfg.Endpoints[0].APIKey = "env:MY_TEST_KEY" resolved, err := Resolve(cfg) if err != nil { t.Fatal(err) } if resolved.APIKey != "sk-secret" { t.Errorf("api key = %q, want sk-secret", resolved.APIKey) } } func TestResolve_MissingKeyOnRemoteEndpointErrors(t *testing.T) { t.Setenv("OPENAI_API_KEY", "") cfg := Defaults() cfg.Endpoints[0].APIKey = "env:DOES_NOT_EXIST_ENV_VAR" if _, err := Resolve(cfg); err == nil { t.Fatal("expected error for missing API key on non-local endpoint") } } func TestResolve_LocalEndpointWithoutKeyIsFine(t *testing.T) { cfg := Defaults() cfg.Endpoints[0].BaseURL = "http://localhost:11434/v1" cfg.Endpoints[0].APIKey = "none" resolved, err := Resolve(cfg) if err != nil { t.Fatal(err) } if resolved.APIKey != "" { t.Errorf("api key = %q, want empty for local endpoint", resolved.APIKey) } } func TestResolve_UnknownEndpointErrors(t *testing.T) { cfg := Defaults() cfg.Model.Endpoint = "does-not-exist" if _, err := Resolve(cfg); err == nil { t.Fatal("expected error for unknown model.endpoint reference") } }