package config import ( "os" "path/filepath" "strings" "testing" "time" ) func TestLoadExpandsEnvAndDefaults(t *testing.T) { dir := t.TempDir() t.Setenv("AGENTU_TEST_KEY", "sk-test") path := filepath.Join(dir, "agentu.yaml") if err := os.WriteFile(path, []byte(` providers: test: base_url: https://example.com api_key: ${AGENTU_TEST_KEY} models: - id: test-model agent: working_dir: . `), 0o644); err != nil { t.Fatal(err) } cfg, err := Load(path) if err != nil { t.Fatal(err) } provider := cfg.Providers[cfg.DefaultProviderName()] if provider.APIKey != "sk-test" { t.Fatalf("api key = %q", provider.APIKey) } if providerName := cfg.DefaultProviderName(); providerName != "test" { t.Fatalf("default provider = %q", providerName) } if cfg.Agent.ToolTimeout != 30*time.Second { t.Fatalf("tool timeout = %s", cfg.Agent.ToolTimeout) } if !filepath.IsAbs(cfg.Agent.WorkingDir) { t.Fatalf("working dir is not absolute: %s", cfg.Agent.WorkingDir) } } func TestLoadMinimalModelObjectConfig(t *testing.T) { dir := t.TempDir() t.Setenv("AGENTU_TEST_KEY", "sk-test") path := filepath.Join(dir, "agentu.yaml") if err := os.WriteFile(path, []byte(` providers: test: base_url: https://example.com api_key: ${AGENTU_TEST_KEY} models: - id: model-a thinking: none context: 256000 `), 0o644); err != nil { t.Fatal(err) } cfg, err := Load(path) if err != nil { t.Fatal(err) } provider := cfg.Providers["test"] if provider.Model != "model-a" { t.Fatalf("model = %q", provider.Model) } if len(provider.Models) != 1 || provider.Models[0] != "model-a" { t.Fatalf("models = %#v", provider.Models) } if provider.ContextTokens != 256000 { t.Fatalf("context tokens = %d", provider.ContextTokens) } if !provider.ThinkingConfigured || provider.Thinking != "none" || provider.ThinkingParam != "thinking" { t.Fatalf("thinking config = %#v", provider) } if cfg.Agent.ToolTimeout != 30*time.Second { t.Fatalf("tool timeout = %s", cfg.Agent.ToolTimeout) } if cfg.Agent.SystemPrompt == "" { t.Fatal("system prompt default was not applied") } } func TestLoadRejectsStringModelEntries(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, "agentu.yaml") if err := os.WriteFile(path, []byte(` providers: test: base_url: https://example.com api_key: sk-test models: - test-model `), 0o644); err != nil { t.Fatal(err) } _, err := Load(path) if err == nil || !strings.Contains(err.Error(), "cannot unmarshal") { t.Fatalf("err = %v", err) } } func TestLoadMaxContextTokens(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, "agentu.yaml") if err := os.WriteFile(path, []byte(` providers: test: base_url: https://example.com api_key: sk-test models: - id: test-model agent: max_context_tokens: 120000 `), 0o644); err != nil { t.Fatal(err) } cfg, err := Load(path) if err != nil { t.Fatal(err) } if cfg.Agent.MaxContextTokens != 120000 { t.Fatalf("max context tokens = %d", cfg.Agent.MaxContextTokens) } } func TestLoadRejectsNegativeMaxContextTokens(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, "agentu.yaml") if err := os.WriteFile(path, []byte(` providers: test: base_url: https://example.com api_key: sk-test models: - id: test-model agent: max_context_tokens: -1 `), 0o644); err != nil { t.Fatal(err) } _, err := Load(path) if err == nil || !strings.Contains(err.Error(), "agent.max_context_tokens") { t.Fatalf("err = %v", err) } } func TestLoadUsesDefaultConfigPath(t *testing.T) { dir := t.TempDir() t.Setenv("HOME", dir) t.Setenv("AGENTU_TEST_KEY", "sk-test") configDir := filepath.Join(dir, ".agentu") if err := os.MkdirAll(configDir, 0o755); err != nil { t.Fatal(err) } path := filepath.Join(configDir, "config.yaml") if err := os.WriteFile(path, []byte(` providers: test: base_url: https://example.com api_key: ${AGENTU_TEST_KEY} models: - id: test-model `), 0o644); err != nil { t.Fatal(err) } cfg, err := Load("") if err != nil { t.Fatal(err) } if got := cfg.DefaultProviderName(); got != "test" { t.Fatalf("default provider = %q", got) } } func TestResolvePathExpandsHome(t *testing.T) { dir := t.TempDir() t.Setenv("HOME", dir) got, err := ResolvePath("~/.agentu/config.yaml") if err != nil { t.Fatal(err) } want := filepath.Join(dir, ".agentu", "config.yaml") if got != want { t.Fatalf("path = %q, want %q", got, want) } } func TestLoadDefaultSystemPromptDiscouragesEagerTools(t *testing.T) { dir := t.TempDir() t.Setenv("AGENTU_TEST_KEY", "sk-test") path := filepath.Join(dir, "agentu.yaml") if err := os.WriteFile(path, []byte(` providers: test: base_url: https://example.com api_key: ${AGENTU_TEST_KEY} models: - id: test-model `), 0o644); err != nil { t.Fatal(err) } cfg, err := Load(path) if err != nil { t.Fatal(err) } for _, want := range []string{"Answer simple conversation directly", "Use tools only when"} { if !strings.Contains(cfg.Agent.SystemPrompt, want) { t.Fatalf("system prompt missing %q: %s", want, cfg.Agent.SystemPrompt) } } } func TestLoadMultiProviderConfig(t *testing.T) { dir := t.TempDir() t.Setenv("AGENTU_TEST_KEY", "sk-test") path := filepath.Join(dir, "agentu.yaml") if err := os.WriteFile(path, []byte(` providers: loveuer: base_url: https://example.com api_key: ${AGENTU_TEST_KEY} models: - id: model-a thinking: xhigh - id: model-b backup: base_url: https://backup.example.com api_key: ${AGENTU_TEST_KEY} models: - id: model-c `), 0o644); err != nil { t.Fatal(err) } cfg, err := Load(path) if err != nil { t.Fatal(err) } if got := cfg.DefaultProviderName(); got != "backup" { t.Fatalf("default provider = %q", got) } provider := cfg.Providers["loveuer"] if got := provider.Model; got != "model-a" { t.Fatalf("active model = %q", got) } if got := cfg.Providers["backup"].Model; got != "model-c" { t.Fatalf("backup default model = %q", got) } if !provider.ThinkingConfigured || provider.Thinking != "xhigh" || provider.ThinkingParam != "thinking" { t.Fatalf("thinking config = %#v", provider) } } func TestLoadDefaultProviderNameIsDeterministic(t *testing.T) { dir := t.TempDir() t.Setenv("AGENTU_TEST_KEY", "sk-test") path := filepath.Join(dir, "agentu.yaml") if err := os.WriteFile(path, []byte(` providers: two: base_url: https://two.example.com api_key: ${AGENTU_TEST_KEY} models: - id: model-b one: base_url: https://one.example.com api_key: ${AGENTU_TEST_KEY} models: - id: model-a `), 0o644); err != nil { t.Fatal(err) } cfg, err := Load(path) if err != nil { t.Fatal(err) } if got := cfg.DefaultProviderName(); got != "one" { t.Fatalf("default provider = %q", got) } } func TestLoadRejectsInvalidThinking(t *testing.T) { dir := t.TempDir() t.Setenv("AGENTU_TEST_KEY", "sk-test") path := filepath.Join(dir, "agentu.yaml") if err := os.WriteFile(path, []byte(` providers: test: base_url: https://example.com api_key: ${AGENTU_TEST_KEY} models: - id: test-model thinking: huge `), 0o644); err != nil { t.Fatal(err) } _, err := Load(path) if err == nil || !strings.Contains(err.Error(), "thinking") { t.Fatalf("err = %v", err) } } func TestLoadRequiresProviderFields(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, "agentu.yaml") if err := os.WriteFile(path, []byte(` providers: test: base_url: https://example.com `), 0o644); err != nil { t.Fatal(err) } _, err := Load(path) if err == nil { t.Fatal("expected error") } } func TestLoadRejectsLegacyProviderConfig(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, "agentu.yaml") if err := os.WriteFile(path, []byte(` provider: base_url: https://example.com api_key: sk-test models: - id: test-model `), 0o644); err != nil { t.Fatal(err) } _, err := Load(path) if err == nil || !strings.Contains(err.Error(), "field provider not found") { t.Fatalf("err = %v", err) } } func TestLoadRejectsActiveProviderConfig(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, "agentu.yaml") if err := os.WriteFile(path, []byte(` active_provider: loveuer providers: loveuer: base_url: https://example.com api_key: sk-test models: - id: test-model `), 0o644); err != nil { t.Fatal(err) } _, err := Load(path) if err == nil || !strings.Contains(err.Error(), "field active_provider not found") { t.Fatalf("err = %v", err) } }