1// SPDX-FileCopyrightText: Amolith <amolith@secluded.site>
2//
3// SPDX-License-Identifier: LicenseRef-MutuaL-1.2
4
5package appconfig
6
7import (
8 "context"
9 "os"
10 "path/filepath"
11 "strings"
12 "testing"
13
14 "git.secluded.site/cooked-mcp/internal/configvalue"
15)
16
17func TestLoadACIDAuthenticationCredentials21ResolvesTOMLConfigValues(t *testing.T) {
18 t.Setenv("COOKED_USERNAME_FROM_MY_SHELL", "shell-user")
19
20 configPath := filepath.Join(t.TempDir(), "config.toml")
21 writeConfig(t, configPath, `[user]
22name = "$COOKED_USERNAME_FROM_MY_SHELL"
23password = "!printf shell-password"
24`)
25
26 got, err := Load(context.Background(), configPath, configvalue.NewResolver())
27 if err != nil {
28 t.Fatalf("Load() error = %v", err)
29 }
30
31 if got.Username != "shell-user" {
32 t.Fatalf("Username = %q, want shell-user", got.Username)
33 }
34 if got.Password != "shell-password" {
35 t.Fatalf("Password = %q, want shell-password", got.Password)
36 }
37}
38
39func TestLoadACIDAuthenticationCredentials11EnvironmentOverridesTOML(t *testing.T) {
40 t.Setenv("COOKED_LOGIN_USERNAME", "override-user")
41 t.Setenv("COOKED_LOGIN_PASSWORD", "override-password")
42
43 configPath := filepath.Join(t.TempDir(), "config.toml")
44 writeConfig(t, configPath, `[user]
45name = "toml-user"
46password = "toml-password"
47`)
48
49 got, err := Load(context.Background(), configPath, configvalue.NewResolver())
50 if err != nil {
51 t.Fatalf("Load() error = %v", err)
52 }
53
54 if got.Username != "override-user" {
55 t.Fatalf("Username = %q, want override-user", got.Username)
56 }
57 if got.Password != "override-password" {
58 t.Fatalf("Password = %q, want override-password", got.Password)
59 }
60}
61
62func TestLoadDefaultsHTTPAddr(t *testing.T) {
63 configPath := filepath.Join(t.TempDir(), "config.toml")
64 writeConfig(t, configPath, `[user]
65name = "toml-user"
66password = "toml-password"
67`)
68
69 got, err := Load(context.Background(), configPath, configvalue.NewResolver())
70 if err != nil {
71 t.Fatalf("Load() error = %v", err)
72 }
73
74 if got.HTTPAddr != "127.0.0.1:8123" {
75 t.Fatalf("HTTPAddr = %q, want default loopback address", got.HTTPAddr)
76 }
77}
78
79// server.CONFIG.8-2
80func TestLoadACIDServerConfig8_2LoadsTOMLHTTPAddr(t *testing.T) {
81 configPath := filepath.Join(t.TempDir(), "config.toml")
82 writeConfig(t, configPath, `[user]
83name = "toml-user"
84password = "toml-password"
85
86[mcp]
87http_addr = "127.0.0.1:9000"
88`)
89
90 got, err := Load(context.Background(), configPath, configvalue.NewResolver())
91 if err != nil {
92 t.Fatalf("Load() error = %v", err)
93 }
94
95 if got.HTTPAddr != "127.0.0.1:9000" {
96 t.Fatalf("HTTPAddr = %q, want TOML address", got.HTTPAddr)
97 }
98}
99
100// server.CONFIG.3 server.CONFIG.8-1
101func TestLoadACIDServerConfig3And8_1EnvironmentOverridesTOMLHTTPAddr(t *testing.T) {
102 t.Setenv("COOKED_MCP_HTTP_ADDR", "127.0.0.1:9001")
103
104 configPath := filepath.Join(t.TempDir(), "config.toml")
105 writeConfig(t, configPath, `[user]
106name = "toml-user"
107password = "toml-password"
108
109[mcp]
110http_addr = "127.0.0.1:9000"
111`)
112
113 got, err := Load(context.Background(), configPath, configvalue.NewResolver())
114 if err != nil {
115 t.Fatalf("Load() error = %v", err)
116 }
117
118 if got.HTTPAddr != "127.0.0.1:9001" {
119 t.Fatalf("HTTPAddr = %q, want environment address", got.HTTPAddr)
120 }
121}
122
123func TestLoadRejectsMissingExplicitConfigFile(t *testing.T) {
124 _, err := Load(context.Background(), filepath.Join(t.TempDir(), "missing.toml"), configvalue.NewResolver())
125 if err == nil {
126 t.Fatal("Load() error = nil, want missing config error")
127 }
128}
129
130func TestLoadACIDServerConfig72RejectsMissingDefaultConfigHome(t *testing.T) {
131 t.Setenv("XDG_CONFIG_HOME", "")
132
133 originalUserHomeDir := userHomeDir
134 userHomeDir = func() (string, error) {
135 return "", os.ErrNotExist
136 }
137 t.Cleanup(func() {
138 userHomeDir = originalUserHomeDir
139 })
140
141 _, err := Load(context.Background(), "", configvalue.NewResolver())
142 if err == nil {
143 t.Fatal("Load() error = nil, want default config path error")
144 }
145 if !strings.Contains(err.Error(), "resolve default config path") {
146 t.Fatalf("Load() error = %v, want default config path error", err)
147 }
148}
149
150func TestLoadRejectsNonLocalHTTPBaseURL(t *testing.T) {
151 configPath := filepath.Join(t.TempDir(), "config.toml")
152 writeConfig(t, configPath, `[user]
153name = "toml-user"
154password = "toml-password"
155
156[cooked]
157base_url = "http://example.com"
158`)
159
160 _, err := Load(context.Background(), configPath, configvalue.NewResolver())
161 if err == nil {
162 t.Fatal("Load() error = nil, want non-local HTTP base URL error")
163 }
164 if !strings.Contains(err.Error(), "must use https") {
165 t.Fatalf("Load() error = %v, want https error", err)
166 }
167}
168
169func writeConfig(t *testing.T, path, content string) {
170 t.Helper()
171
172 if err := os.WriteFile(path, []byte(content), 0o600); err != nil {
173 t.Fatalf("write config: %v", err)
174 }
175}