config_test.go 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216
  1. package config
  2. import (
  3. "log/slog"
  4. "testing"
  5. "time"
  6. )
  7. func setRequired(t *testing.T) {
  8. t.Helper()
  9. t.Setenv("WIKIJS_URL", "https://wiki.example.com")
  10. t.Setenv("WIKIJS_API_KEY", "secret")
  11. }
  12. func TestLoadDefaults(t *testing.T) {
  13. setRequired(t)
  14. t.Setenv("WIKIJS_ENABLE_EDIT", "")
  15. t.Setenv("WIKIJS_TIMEOUT", "")
  16. t.Setenv("MCP_TRANSPORT", "")
  17. t.Setenv("MCP_ADDR", "")
  18. t.Setenv("MCP_PATH", "")
  19. t.Setenv("MCP_STATELESS", "")
  20. t.Setenv("MCP_AUTH_TOKEN", "")
  21. t.Setenv("LOG_LEVEL", "")
  22. cfg, err := Load()
  23. if err != nil {
  24. t.Fatalf("Load() error = %v", err)
  25. }
  26. if cfg.Transport != TransportStdio {
  27. t.Errorf("Transport = %q, want %q", cfg.Transport, TransportStdio)
  28. }
  29. if cfg.Addr != DefaultAddr {
  30. t.Errorf("Addr = %q, want %q", cfg.Addr, DefaultAddr)
  31. }
  32. if cfg.Path != DefaultPath {
  33. t.Errorf("Path = %q, want %q", cfg.Path, DefaultPath)
  34. }
  35. if cfg.Timeout != DefaultTimeout {
  36. t.Errorf("Timeout = %v, want %v", cfg.Timeout, DefaultTimeout)
  37. }
  38. if cfg.EnableEdit {
  39. t.Error("EnableEdit = true, want false")
  40. }
  41. if cfg.Stateless {
  42. t.Error("Stateless = true, want false")
  43. }
  44. if cfg.LogLevel != slog.LevelInfo {
  45. t.Errorf("LogLevel = %v, want info", cfg.LogLevel)
  46. }
  47. if err := cfg.Validate(); err != nil {
  48. t.Errorf("Validate() error = %v", err)
  49. }
  50. }
  51. func TestLoadCustomValues(t *testing.T) {
  52. setRequired(t)
  53. t.Setenv("WIKIJS_ENABLE_EDIT", "true")
  54. t.Setenv("WIKIJS_TIMEOUT", "5s")
  55. t.Setenv("MCP_TRANSPORT", "HTTP")
  56. t.Setenv("MCP_ADDR", "127.0.0.1:9000")
  57. t.Setenv("MCP_PATH", "/mcp/")
  58. t.Setenv("MCP_STATELESS", "1")
  59. t.Setenv("MCP_AUTH_TOKEN", "token")
  60. t.Setenv("LOG_LEVEL", "debug")
  61. cfg, err := Load()
  62. if err != nil {
  63. t.Fatalf("Load() error = %v", err)
  64. }
  65. if cfg.Transport != TransportHTTP {
  66. t.Errorf("Transport = %q, want %q", cfg.Transport, TransportHTTP)
  67. }
  68. if cfg.Addr != "127.0.0.1:9000" {
  69. t.Errorf("Addr = %q", cfg.Addr)
  70. }
  71. if cfg.Path != "/mcp/" {
  72. t.Errorf("Path = %q", cfg.Path)
  73. }
  74. if cfg.Timeout != 5*time.Second {
  75. t.Errorf("Timeout = %v", cfg.Timeout)
  76. }
  77. if !cfg.EnableEdit || !cfg.Stateless {
  78. t.Errorf("EnableEdit = %v, Stateless = %v, want both true", cfg.EnableEdit, cfg.Stateless)
  79. }
  80. if cfg.AuthToken != "token" {
  81. t.Errorf("AuthToken = %q", cfg.AuthToken)
  82. }
  83. if cfg.LogLevel != slog.LevelDebug {
  84. t.Errorf("LogLevel = %v, want debug", cfg.LogLevel)
  85. }
  86. }
  87. func TestLoadRequired(t *testing.T) {
  88. tests := []struct {
  89. name string
  90. url string
  91. apiKey string
  92. wantErr bool
  93. }{
  94. {name: "missing url", apiKey: "secret", wantErr: true},
  95. {name: "missing key", url: "https://wiki.example.com", wantErr: true},
  96. {name: "present", url: "https://wiki.example.com", apiKey: "secret"},
  97. }
  98. for _, tt := range tests {
  99. t.Run(tt.name, func(t *testing.T) {
  100. t.Setenv("WIKIJS_URL", tt.url)
  101. t.Setenv("WIKIJS_API_KEY", tt.apiKey)
  102. cfg, err := Load()
  103. if err != nil {
  104. t.Fatalf("Load() error = %v", err)
  105. }
  106. if gotErr := cfg.Validate() != nil; gotErr != tt.wantErr {
  107. t.Fatalf("Validate() error = %v, wantErr %v", cfg.Validate(), tt.wantErr)
  108. }
  109. })
  110. }
  111. }
  112. func TestLoadInvalidValues(t *testing.T) {
  113. tests := []struct {
  114. name string
  115. key string
  116. value string
  117. }{
  118. {name: "edit bool", key: "WIKIJS_ENABLE_EDIT", value: "maybe"},
  119. {name: "stateless bool", key: "MCP_STATELESS", value: "maybe"},
  120. {name: "timeout", key: "WIKIJS_TIMEOUT", value: "soon"},
  121. {name: "log level", key: "LOG_LEVEL", value: "verbose"},
  122. }
  123. for _, tt := range tests {
  124. t.Run(tt.name, func(t *testing.T) {
  125. setRequired(t)
  126. t.Setenv(tt.key, tt.value)
  127. if _, err := Load(); err == nil {
  128. t.Fatal("Load() error = nil, want error")
  129. }
  130. })
  131. }
  132. }
  133. func TestValidateHTTPAuth(t *testing.T) {
  134. tests := []struct {
  135. name string
  136. addr string
  137. token string
  138. wantErr bool
  139. }{
  140. {name: "all interfaces without token", addr: ":8080", wantErr: true},
  141. {name: "all interfaces with token", addr: ":8080", token: "secret"},
  142. {name: "ipv4 any without token", addr: "0.0.0.0:8080", wantErr: true},
  143. {name: "ipv4 loopback", addr: "127.0.0.1:8080"},
  144. {name: "ipv6 loopback", addr: "[::1]:8080"},
  145. {name: "localhost", addr: "localhost:8080"},
  146. {name: "remote hostname without token", addr: "wiki.internal:8080", wantErr: true},
  147. {name: "missing port", addr: "127.0.0.1", wantErr: true},
  148. }
  149. for _, tt := range tests {
  150. t.Run(tt.name, func(t *testing.T) {
  151. cfg := Config{
  152. WikiJSURL: "https://wiki.example.com",
  153. WikiJSAPIKey: "secret",
  154. Transport: TransportHTTP,
  155. Addr: tt.addr,
  156. Path: DefaultPath,
  157. Timeout: DefaultTimeout,
  158. AuthToken: tt.token,
  159. }
  160. if gotErr := cfg.Validate() != nil; gotErr != tt.wantErr {
  161. t.Fatalf("Validate() error = %v, wantErr %v", cfg.Validate(), tt.wantErr)
  162. }
  163. })
  164. }
  165. }
  166. func TestValidate(t *testing.T) {
  167. valid := func() Config {
  168. return Config{
  169. WikiJSURL: "https://wiki.example.com",
  170. WikiJSAPIKey: "secret",
  171. Transport: TransportStdio,
  172. Addr: DefaultAddr,
  173. Path: DefaultPath,
  174. Timeout: DefaultTimeout,
  175. }
  176. }
  177. tests := []struct {
  178. name string
  179. mutate func(*Config)
  180. wantErr bool
  181. }{
  182. {name: "valid"},
  183. {name: "missing url", mutate: func(c *Config) { c.WikiJSURL = "" }, wantErr: true},
  184. {name: "missing key", mutate: func(c *Config) { c.WikiJSAPIKey = "" }, wantErr: true},
  185. {name: "relative url", mutate: func(c *Config) { c.WikiJSURL = "wiki.example.com" }, wantErr: true},
  186. {name: "url with query", mutate: func(c *Config) { c.WikiJSURL = "https://wiki.example.com/?a=1" }, wantErr: true},
  187. {name: "ftp url", mutate: func(c *Config) { c.WikiJSURL = "ftp://wiki.example.com" }, wantErr: true},
  188. {name: "bad transport", mutate: func(c *Config) { c.Transport = "sse" }, wantErr: true},
  189. {name: "empty addr", mutate: func(c *Config) { c.Addr = "" }, wantErr: true},
  190. {name: "path without slash", mutate: func(c *Config) { c.Path = "mcp" }, wantErr: true},
  191. {name: "healthz path", mutate: func(c *Config) { c.Path = "/healthz" }, wantErr: true},
  192. {name: "zero timeout", mutate: func(c *Config) { c.Timeout = 0 }, wantErr: true},
  193. }
  194. for _, tt := range tests {
  195. t.Run(tt.name, func(t *testing.T) {
  196. cfg := valid()
  197. if tt.mutate != nil {
  198. tt.mutate(&cfg)
  199. }
  200. if gotErr := cfg.Validate() != nil; gotErr != tt.wantErr {
  201. t.Fatalf("Validate() error = %v, wantErr %v", cfg.Validate(), tt.wantErr)
  202. }
  203. })
  204. }
  205. }