config_test.go 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  1. package config
  2. import (
  3. "bytes"
  4. "errors"
  5. "path/filepath"
  6. "strings"
  7. "testing"
  8. )
  9. func TestLoad_ValidTwoAccounts(t *testing.T) {
  10. var warn bytes.Buffer
  11. cfg, err := Load(filepath.Join("testdata", "valid_two_accounts.yaml"), &warn)
  12. if err != nil {
  13. t.Fatalf("unexpected error: %v", err)
  14. }
  15. if len(cfg.Accounts) != 2 {
  16. t.Fatalf("want 2 accounts, got %d", len(cfg.Accounts))
  17. }
  18. if cfg.Accounts[0].Name != "personal" || cfg.Accounts[1].Name != "work" {
  19. t.Fatalf("wrong order or names: %+v", cfg.Accounts)
  20. }
  21. if warn.Len() != 0 {
  22. t.Fatalf("expected no warnings, got %q", warn.String())
  23. }
  24. }
  25. func TestLoad_DuplicateNames(t *testing.T) {
  26. _, err := Load(filepath.Join("testdata", "duplicate_names.yaml"), nil)
  27. if !errors.Is(err, ErrParse) {
  28. t.Fatalf("want ErrParse, got %v", err)
  29. }
  30. if !strings.Contains(err.Error(), "duplicate") {
  31. t.Fatalf("error message should mention duplicate, got %v", err)
  32. }
  33. }
  34. func TestLoad_MissingAPIKey(t *testing.T) {
  35. _, err := Load(filepath.Join("testdata", "missing_api_key.yaml"), nil)
  36. if !errors.Is(err, ErrParse) {
  37. t.Fatalf("want ErrParse, got %v", err)
  38. }
  39. if !strings.Contains(err.Error(), "api_key") {
  40. t.Fatalf("error message should mention api_key, got %v", err)
  41. }
  42. }
  43. func TestLoad_EmptyAccounts(t *testing.T) {
  44. _, err := Load(filepath.Join("testdata", "empty_accounts.yaml"), nil)
  45. if !errors.Is(err, ErrParse) {
  46. t.Fatalf("want ErrParse, got %v", err)
  47. }
  48. }
  49. func TestLoad_UnknownFieldsWarn(t *testing.T) {
  50. var warn bytes.Buffer
  51. cfg, err := Load(filepath.Join("testdata", "unknown_fields.yaml"), &warn)
  52. if err != nil {
  53. t.Fatalf("unexpected error: %v", err)
  54. }
  55. if len(cfg.Accounts) != 1 {
  56. t.Fatalf("want 1 account, got %d", len(cfg.Accounts))
  57. }
  58. warnings := warn.String()
  59. if !strings.Contains(warnings, `"notifications"`) {
  60. t.Errorf("expected top-level warning for notifications, got %q", warnings)
  61. }
  62. if !strings.Contains(warnings, `"color"`) {
  63. t.Errorf("expected per-account warning for color, got %q", warnings)
  64. }
  65. }
  66. func TestLoad_Malformed(t *testing.T) {
  67. _, err := Load(filepath.Join("testdata", "malformed.yaml"), nil)
  68. if !errors.Is(err, ErrParse) {
  69. t.Fatalf("want ErrParse, got %v", err)
  70. }
  71. }
  72. func TestLoad_MissingFile(t *testing.T) {
  73. _, err := Load(filepath.Join("testdata", "does_not_exist.yaml"), nil)
  74. if err == nil {
  75. t.Fatal("expected error for missing file")
  76. }
  77. // Missing file is NOT an ErrParse — callers distinguish.
  78. if errors.Is(err, ErrParse) {
  79. t.Fatalf("missing file should not be ErrParse, got %v", err)
  80. }
  81. }
  82. func TestResolve_APIKeyBeatsConfig(t *testing.T) {
  83. cfg := &Config{Accounts: []Account{
  84. {Name: "personal", APIKey: "sk-p"},
  85. {Name: "work", APIKey: "sk-w"},
  86. }}
  87. accs, err := Resolve(cfg, ResolveFlags{APIKey: "sk-ad-hoc"})
  88. if err != nil {
  89. t.Fatalf("unexpected error: %v", err)
  90. }
  91. if len(accs) != 1 || accs[0].Name != "cli" || accs[0].APIKey != "sk-ad-hoc" {
  92. t.Fatalf("want single cli account with ad-hoc key, got %+v", accs)
  93. }
  94. }
  95. func TestResolve_AccountFilter(t *testing.T) {
  96. cfg := &Config{Accounts: []Account{
  97. {Name: "personal", APIKey: "sk-p"},
  98. {Name: "work", APIKey: "sk-w"},
  99. }}
  100. accs, err := Resolve(cfg, ResolveFlags{AccountName: "work"})
  101. if err != nil {
  102. t.Fatalf("unexpected error: %v", err)
  103. }
  104. if len(accs) != 1 || accs[0].Name != "work" {
  105. t.Fatalf("want work account, got %+v", accs)
  106. }
  107. }
  108. func TestResolve_AccountNotFound(t *testing.T) {
  109. cfg := &Config{Accounts: []Account{
  110. {Name: "personal", APIKey: "sk-p"},
  111. }}
  112. _, err := Resolve(cfg, ResolveFlags{AccountName: "missing"})
  113. if !errors.Is(err, ErrAccountNotFound) {
  114. t.Fatalf("want ErrAccountNotFound, got %v", err)
  115. }
  116. if !strings.Contains(err.Error(), "personal") {
  117. t.Fatalf("error should list available accounts, got %v", err)
  118. }
  119. }
  120. func TestResolve_AllAccountsInOrder(t *testing.T) {
  121. cfg := &Config{Accounts: []Account{
  122. {Name: "a", APIKey: "1"},
  123. {Name: "b", APIKey: "2"},
  124. {Name: "c", APIKey: "3"},
  125. }}
  126. accs, err := Resolve(cfg, ResolveFlags{})
  127. if err != nil {
  128. t.Fatalf("unexpected error: %v", err)
  129. }
  130. if len(accs) != 3 {
  131. t.Fatalf("want 3 accounts, got %d", len(accs))
  132. }
  133. for i, want := range []string{"a", "b", "c"} {
  134. if accs[i].Name != want {
  135. t.Errorf("accs[%d].Name = %q, want %q", i, accs[i].Name, want)
  136. }
  137. }
  138. }
  139. func TestResolve_EnvFallback(t *testing.T) {
  140. accs, err := Resolve(nil, ResolveFlags{EnvAPIKey: "sk-env"})
  141. if err != nil {
  142. t.Fatalf("unexpected error: %v", err)
  143. }
  144. if len(accs) != 1 || accs[0].Name != "env" || accs[0].APIKey != "sk-env" {
  145. t.Fatalf("want env account, got %+v", accs)
  146. }
  147. }
  148. func TestResolve_NoAccount(t *testing.T) {
  149. _, err := Resolve(nil, ResolveFlags{})
  150. if !errors.Is(err, ErrNoAccount) {
  151. t.Fatalf("want ErrNoAccount, got %v", err)
  152. }
  153. }
  154. func TestDefaultPath_XDGOverride(t *testing.T) {
  155. t.Setenv("XDG_CONFIG_HOME", "/custom/xdg")
  156. got := DefaultPath()
  157. want := filepath.Join("/custom/xdg", "zenmux-usage", "config.yaml")
  158. if got != want {
  159. t.Errorf("DefaultPath() = %q, want %q", got, want)
  160. }
  161. }
  162. func TestDefaultPath_HomeFallback(t *testing.T) {
  163. t.Setenv("XDG_CONFIG_HOME", "")
  164. t.Setenv("HOME", "/fake/home")
  165. got := DefaultPath()
  166. want := filepath.Join("/fake/home", ".config", "zenmux-usage", "config.yaml")
  167. if got != want {
  168. t.Errorf("DefaultPath() = %q, want %q", got, want)
  169. }
  170. }