package config import ( "bytes" "errors" "path/filepath" "strings" "testing" ) func TestLoad_ValidTwoAccounts(t *testing.T) { var warn bytes.Buffer cfg, err := Load(filepath.Join("testdata", "valid_two_accounts.yaml"), &warn) if err != nil { t.Fatalf("unexpected error: %v", err) } if len(cfg.Accounts) != 2 { t.Fatalf("want 2 accounts, got %d", len(cfg.Accounts)) } if cfg.Accounts[0].Name != "personal" || cfg.Accounts[1].Name != "work" { t.Fatalf("wrong order or names: %+v", cfg.Accounts) } if warn.Len() != 0 { t.Fatalf("expected no warnings, got %q", warn.String()) } } func TestLoad_DuplicateNames(t *testing.T) { _, err := Load(filepath.Join("testdata", "duplicate_names.yaml"), nil) if !errors.Is(err, ErrParse) { t.Fatalf("want ErrParse, got %v", err) } if !strings.Contains(err.Error(), "duplicate") { t.Fatalf("error message should mention duplicate, got %v", err) } } func TestLoad_MissingAPIKey(t *testing.T) { _, err := Load(filepath.Join("testdata", "missing_api_key.yaml"), nil) if !errors.Is(err, ErrParse) { t.Fatalf("want ErrParse, got %v", err) } if !strings.Contains(err.Error(), "api_key") { t.Fatalf("error message should mention api_key, got %v", err) } } func TestLoad_EmptyAccounts(t *testing.T) { _, err := Load(filepath.Join("testdata", "empty_accounts.yaml"), nil) if !errors.Is(err, ErrParse) { t.Fatalf("want ErrParse, got %v", err) } } func TestLoad_UnknownFieldsWarn(t *testing.T) { var warn bytes.Buffer cfg, err := Load(filepath.Join("testdata", "unknown_fields.yaml"), &warn) if err != nil { t.Fatalf("unexpected error: %v", err) } if len(cfg.Accounts) != 1 { t.Fatalf("want 1 account, got %d", len(cfg.Accounts)) } warnings := warn.String() if !strings.Contains(warnings, `"notifications"`) { t.Errorf("expected top-level warning for notifications, got %q", warnings) } if !strings.Contains(warnings, `"color"`) { t.Errorf("expected per-account warning for color, got %q", warnings) } } func TestLoad_Malformed(t *testing.T) { _, err := Load(filepath.Join("testdata", "malformed.yaml"), nil) if !errors.Is(err, ErrParse) { t.Fatalf("want ErrParse, got %v", err) } } func TestLoad_MissingFile(t *testing.T) { _, err := Load(filepath.Join("testdata", "does_not_exist.yaml"), nil) if err == nil { t.Fatal("expected error for missing file") } // Missing file is NOT an ErrParse — callers distinguish. if errors.Is(err, ErrParse) { t.Fatalf("missing file should not be ErrParse, got %v", err) } } func TestResolve_APIKeyBeatsConfig(t *testing.T) { cfg := &Config{Accounts: []Account{ {Name: "personal", APIKey: "sk-p"}, {Name: "work", APIKey: "sk-w"}, }} accs, err := Resolve(cfg, ResolveFlags{APIKey: "sk-ad-hoc"}) if err != nil { t.Fatalf("unexpected error: %v", err) } if len(accs) != 1 || accs[0].Name != "cli" || accs[0].APIKey != "sk-ad-hoc" { t.Fatalf("want single cli account with ad-hoc key, got %+v", accs) } } func TestResolve_AccountFilter(t *testing.T) { cfg := &Config{Accounts: []Account{ {Name: "personal", APIKey: "sk-p"}, {Name: "work", APIKey: "sk-w"}, }} accs, err := Resolve(cfg, ResolveFlags{AccountName: "work"}) if err != nil { t.Fatalf("unexpected error: %v", err) } if len(accs) != 1 || accs[0].Name != "work" { t.Fatalf("want work account, got %+v", accs) } } func TestResolve_AccountNotFound(t *testing.T) { cfg := &Config{Accounts: []Account{ {Name: "personal", APIKey: "sk-p"}, }} _, err := Resolve(cfg, ResolveFlags{AccountName: "missing"}) if !errors.Is(err, ErrAccountNotFound) { t.Fatalf("want ErrAccountNotFound, got %v", err) } if !strings.Contains(err.Error(), "personal") { t.Fatalf("error should list available accounts, got %v", err) } } func TestResolve_AllAccountsInOrder(t *testing.T) { cfg := &Config{Accounts: []Account{ {Name: "a", APIKey: "1"}, {Name: "b", APIKey: "2"}, {Name: "c", APIKey: "3"}, }} accs, err := Resolve(cfg, ResolveFlags{}) if err != nil { t.Fatalf("unexpected error: %v", err) } if len(accs) != 3 { t.Fatalf("want 3 accounts, got %d", len(accs)) } for i, want := range []string{"a", "b", "c"} { if accs[i].Name != want { t.Errorf("accs[%d].Name = %q, want %q", i, accs[i].Name, want) } } } func TestResolve_EnvFallback(t *testing.T) { accs, err := Resolve(nil, ResolveFlags{EnvAPIKey: "sk-env"}) if err != nil { t.Fatalf("unexpected error: %v", err) } if len(accs) != 1 || accs[0].Name != "env" || accs[0].APIKey != "sk-env" { t.Fatalf("want env account, got %+v", accs) } } func TestResolve_NoAccount(t *testing.T) { _, err := Resolve(nil, ResolveFlags{}) if !errors.Is(err, ErrNoAccount) { t.Fatalf("want ErrNoAccount, got %v", err) } } func TestDefaultPath_XDGOverride(t *testing.T) { t.Setenv("XDG_CONFIG_HOME", "/custom/xdg") got := DefaultPath() want := filepath.Join("/custom/xdg", "zenmux-usage", "config.yaml") if got != want { t.Errorf("DefaultPath() = %q, want %q", got, want) } } func TestDefaultPath_HomeFallback(t *testing.T) { t.Setenv("XDG_CONFIG_HOME", "") t.Setenv("HOME", "/fake/home") got := DefaultPath() want := filepath.Join("/fake/home", ".config", "zenmux-usage", "config.yaml") if got != want { t.Errorf("DefaultPath() = %q, want %q", got, want) } }