config_test.go 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135
  1. package config_test
  2. import (
  3. "os"
  4. "path/filepath"
  5. "testing"
  6. "workflow/config"
  7. )
  8. func TestLoad_Defaults(t *testing.T) {
  9. // No file, no env vars — should return built-in defaults
  10. cfg := config.Load("/nonexistent/path/that/does/not/exist")
  11. if cfg.LLM.URL != "http://localhost:4000" {
  12. t.Errorf("LLM.URL = %q, want default", cfg.LLM.URL)
  13. }
  14. if cfg.LLM.Model != "gpt-4o" {
  15. t.Errorf("LLM.Model = %q, want default", cfg.LLM.Model)
  16. }
  17. if cfg.Workspace.Root != "./workspace" {
  18. t.Errorf("Workspace.Root = %q, want default", cfg.Workspace.Root)
  19. }
  20. if cfg.Server.Port != "8080" {
  21. t.Errorf("Server.Port = %q, want default", cfg.Server.Port)
  22. }
  23. }
  24. func TestLoad_FromFile(t *testing.T) {
  25. content := `
  26. # comment
  27. LLM_URL=http://my-llm:5000
  28. LLM_KEY=sk-test-key
  29. LLM_MODEL=gpt-4-turbo
  30. WORKSPACE_ROOT=/tmp/ws
  31. PORT=9090
  32. `
  33. f := filepath.Join(t.TempDir(), "test.env")
  34. if err := os.WriteFile(f, []byte(content), 0600); err != nil {
  35. t.Fatal(err)
  36. }
  37. cfg := config.Load(f)
  38. if cfg.LLM.URL != "http://my-llm:5000" {
  39. t.Errorf("LLM.URL = %q", cfg.LLM.URL)
  40. }
  41. if cfg.LLM.Key != "sk-test-key" {
  42. t.Errorf("LLM.Key = %q", cfg.LLM.Key)
  43. }
  44. if cfg.LLM.Model != "gpt-4-turbo" {
  45. t.Errorf("LLM.Model = %q", cfg.LLM.Model)
  46. }
  47. if cfg.Workspace.Root != "/tmp/ws" {
  48. t.Errorf("Workspace.Root = %q", cfg.Workspace.Root)
  49. }
  50. if cfg.Server.Port != "9090" {
  51. t.Errorf("Server.Port = %q", cfg.Server.Port)
  52. }
  53. }
  54. func TestLoad_QuotedValues(t *testing.T) {
  55. content := `LLM_KEY="sk-quoted-key"` + "\n" + `LLM_MODEL='gpt-4o'` + "\n"
  56. f := filepath.Join(t.TempDir(), "test.env")
  57. os.WriteFile(f, []byte(content), 0600)
  58. cfg := config.Load(f)
  59. if cfg.LLM.Key != "sk-quoted-key" {
  60. t.Errorf("quoted double: LLM.Key = %q, want sk-quoted-key", cfg.LLM.Key)
  61. }
  62. if cfg.LLM.Model != "gpt-4o" {
  63. t.Errorf("quoted single: LLM.Model = %q, want gpt-4o", cfg.LLM.Model)
  64. }
  65. }
  66. func TestLoad_EnvVarOverridesFile(t *testing.T) {
  67. content := "LLM_KEY=from-file\nLLM_MODEL=from-file-model\n"
  68. f := filepath.Join(t.TempDir(), "test.env")
  69. os.WriteFile(f, []byte(content), 0600)
  70. // Set env var that should override file
  71. t.Setenv("LLM_KEY", "from-env")
  72. cfg := config.Load(f)
  73. if cfg.LLM.Key != "from-env" {
  74. t.Errorf("env var should win: LLM.Key = %q, want from-env", cfg.LLM.Key)
  75. }
  76. // Non-overridden field still comes from file
  77. if cfg.LLM.Model != "from-file-model" {
  78. t.Errorf("file value: LLM.Model = %q, want from-file-model", cfg.LLM.Model)
  79. }
  80. }
  81. func TestLoad_WorkflowConfigEnv(t *testing.T) {
  82. content := "LLM_KEY=from-workflow-config\n"
  83. f := filepath.Join(t.TempDir(), "custom.env")
  84. os.WriteFile(f, []byte(content), 0600)
  85. t.Setenv("WORKFLOW_CONFIG", f)
  86. cfg := config.Load("") // empty explicit path — should use WORKFLOW_CONFIG
  87. if cfg.LLM.Key != "from-workflow-config" {
  88. t.Errorf("WORKFLOW_CONFIG: LLM.Key = %q, want from-workflow-config", cfg.LLM.Key)
  89. }
  90. }
  91. func TestLoad_MissingFileUsesDefaults(t *testing.T) {
  92. cfg := config.Load("")
  93. // Should not panic; returns defaults
  94. if cfg.LLM.URL == "" {
  95. t.Error("LLM.URL should not be empty")
  96. }
  97. }
  98. func TestMaskKey(t *testing.T) {
  99. got := config.MaskKey("")
  100. if got != "(not set)" {
  101. t.Errorf("MaskKey(\"\") = %q, want \"(not set)\"", got)
  102. }
  103. got = config.MaskKey("sk-1234567890abcdef")
  104. if got == "sk-1234567890abcdef" {
  105. t.Error("MaskKey should not return plaintext key")
  106. }
  107. if len(got) == 0 {
  108. t.Error("MaskKey should not return empty string")
  109. }
  110. // Short key (≤ 8 chars) → all stars
  111. got = config.MaskKey("abc")
  112. for _, c := range got {
  113. if c != '*' {
  114. t.Errorf("MaskKey(short): expected all stars, got %q", got)
  115. break
  116. }
  117. }
  118. }