internal/config/config_test.go
251 lines · 6498 bytes
1package config
2
3import (
4 "crypto/ecdsa"
5 "crypto/elliptic"
6 "crypto/rand"
7 "crypto/x509"
8 "encoding/pem"
9 "os"
10 "path/filepath"
11 "strings"
12 "testing"
13)
14
15func writeConfig(t *testing.T, body string) string {
16 t.Helper()
17 p := filepath.Join(t.TempDir(), "config.toml")
18 if err := os.WriteFile(p, []byte(body), 0o600); err != nil {
19 t.Fatal(err)
20 }
21 return p
22}
23
24const minimal = `
25[server]
26root = "/var/lib/gitbay"
27site_url = "https://gitbay.example"
28`
29
30func TestLoadMinimal(t *testing.T) {
31 cfg, err := Load(writeConfig(t, minimal))
32 if err != nil {
33 t.Fatal(err)
34 }
35 // Defaults applied.
36 if cfg.SSH.Mode != "embedded" || cfg.SSH.Port != 22 {
37 t.Errorf("ssh defaults wrong: %+v", cfg.SSH)
38 }
39 if cfg.Web.Mode != "view_only" {
40 t.Errorf("web default wrong: %+v", cfg.Web)
41 }
42 if cfg.Registration.Mode != "closed" {
43 t.Errorf("registration default wrong: %+v", cfg.Registration)
44 }
45}
46
47func TestContradictions(t *testing.T) {
48 cases := []struct {
49 name string
50 body string
51 wantErr string
52 }{
53 {
54 "registration open without smtp",
55 minimal + "\n[registration]\nmode = \"open\"\n",
56 "requires [mail] smtp_host",
57 },
58 {
59 "notify_admin without smtp",
60 minimal + "\n[registration]\nnotify_admin = true\n",
61 "notify_admin = true requires [mail] smtp_host",
62 },
63 {
64 "system ssh with open registration",
65 minimal + "\n[ssh]\nmode = \"system\"\n[registration]\nmode = \"open\"\n[mail]\nsmtp_host = \"mx.example\"\nfrom = \"gitbay@example\"\n",
66 "requires registration.mode = \"closed\"",
67 },
68 {
69 "password auth in view_only",
70 minimal + "\n[web]\nmode = \"view_only\"\npassword_auth = true\n",
71 "password_auth",
72 },
73 {
74 "password auth not implemented",
75 minimal + "\n[web]\nmode = \"accounts\"\npassword_auth = true\n",
76 "not implemented",
77 },
78 {
79 "bad ssh mode",
80 minimal + "\n[ssh]\nmode = \"tcp\"\n",
81 "ssh.mode",
82 },
83 {
84 "unknown key",
85 "[server]\nroot = \"/var/lib/gitbay\"\nsite_url = \"https://gitbay.example\"\nbogus = 1\n",
86 "unknown config key",
87 },
88 {
89 "missing site_url",
90 "[server]\nroot = \"/var/lib/gitbay\"\n",
91 "site_url",
92 },
93 {
94 "negative repo limit",
95 minimal + "\n[limits]\nmax_repos_per_user = -1\n",
96 "must not be negative",
97 },
98 {
99 "negative snippet limit",
100 minimal + "\n[limits]\nmax_snippets_per_user = -1\n",
101 "max_snippets_per_user",
102 },
103 }
104 for _, tc := range cases {
105 t.Run(tc.name, func(t *testing.T) {
106 _, err := Load(writeConfig(t, tc.body))
107 if err == nil {
108 t.Fatalf("expected error containing %q, got nil", tc.wantErr)
109 }
110 if !strings.Contains(err.Error(), tc.wantErr) {
111 t.Fatalf("error %q does not contain %q", err, tc.wantErr)
112 }
113 })
114 }
115}
116
117func TestValidCombinations(t *testing.T) {
118 cases := []struct {
119 name string
120 body string
121 }{
122 {
123 "invite with smtp",
124 minimal + "\n[registration]\nmode = \"invite\"\n[mail]\nsmtp_host = \"mx.example\"\nfrom = \"gitbay@example\"\n",
125 },
126 {
127 "system ssh closed registration",
128 minimal + "\n[ssh]\nmode = \"system\"\n",
129 },
130 {
131 "accounts web without password auth",
132 minimal + "\n[web]\nmode = \"accounts\"\n",
133 },
134 {
135 "closed registration, no smtp at all",
136 minimal,
137 },
138 {
139 "acme with public https host",
140 "[server]\nroot = \"/var/lib/gitbay\"\nsite_url = \"https://gitbay.org\"\n[http]\ntls = \"acme\"\nacme_email = \"noreply@gitbay.org\"\n",
141 },
142 }
143 for _, tc := range cases {
144 t.Run(tc.name, func(t *testing.T) {
145 if _, err := Load(writeConfig(t, tc.body)); err != nil {
146 t.Fatal(err)
147 }
148 })
149 }
150}
151
152// writeP8 writes a PEM-wrapped PKCS#8 P-256 key, the shape of Apple's
153// .p8 provider key, and returns its path.
154func writeP8(t *testing.T) string {
155 t.Helper()
156 key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
157 if err != nil {
158 t.Fatal(err)
159 }
160 der, err := x509.MarshalPKCS8PrivateKey(key)
161 if err != nil {
162 t.Fatal(err)
163 }
164 p := filepath.Join(t.TempDir(), "apns.p8")
165 f, err := os.Create(p)
166 if err != nil {
167 t.Fatal(err)
168 }
169 defer f.Close()
170 if err := pem.Encode(f, &pem.Block{Type: "PRIVATE KEY", Bytes: der}); err != nil {
171 t.Fatal(err)
172 }
173 return p
174}
175
176func TestPushConfigValidation(t *testing.T) {
177 keyPath := writeP8(t)
178 full := `
179[push]
180enabled = true
181key_file = "` + keyPath + `"
182key_id = "KEYID"
183team_id = "TEAMID"
184topic = "org.gitbay.gitbay"
185environment = "production"
186`
187 cases := []struct {
188 name string
189 body string
190 want string // substring of the expected error; "" means valid
191 }{
192 {"disabled needs nothing", "\n[push]\nenabled = false\n", ""},
193 {"complete is valid", full, ""},
194 {"key_id required", strings.Replace(full, `key_id = "KEYID"`, "", 1), "push.key_id"},
195 {"team_id required", strings.Replace(full, `team_id = "TEAMID"`, "", 1), "push.team_id"},
196 {"topic required", strings.Replace(full, `topic = "org.gitbay.gitbay"`, "", 1), "push.topic"},
197 {"environment must be a known name",
198 strings.Replace(full, `environment = "production"`, `environment = "staging"`, 1),
199 "push.environment"},
200 }
201 for _, tc := range cases {
202 t.Run(tc.name, func(t *testing.T) {
203 _, err := Load(writeConfig(t, minimal+tc.body))
204 if tc.want == "" {
205 if err != nil {
206 t.Fatalf("want valid, got %v", err)
207 }
208 return
209 }
210 if err == nil || !strings.Contains(err.Error(), tc.want) {
211 t.Fatalf("want an error mentioning %q, got %v", tc.want, err)
212 }
213 })
214 }
215}
216
217// A key_file that exists but is not a PKCS#8 EC key is refused at load,
218// not at the first notice: the failure mode otherwise is a queue that
219// fills and dead-letters with nobody watching.
220func TestPushConfigRejectsAnUnparseableKey(t *testing.T) {
221 p := filepath.Join(t.TempDir(), "junk.p8")
222 if err := os.WriteFile(p, []byte("not a key\n"), 0o600); err != nil {
223 t.Fatal(err)
224 }
225 body := `
226[push]
227enabled = true
228key_file = "` + p + `"
229key_id = "K"
230team_id = "T"
231topic = "org.gitbay.gitbay"
232environment = "production"
233`
234 _, err := Load(writeConfig(t, minimal+body))
235 if err == nil || !strings.Contains(err.Error(), "push.key_file") {
236 t.Fatalf("want a push.key_file error, got %v", err)
237 }
238}
239
240func TestPushHost(t *testing.T) {
241 if got := (Push{Environment: "production"}).Host(); got != "api.push.apple.com" {
242 t.Fatalf("production host = %q", got)
243 }
244 if got := (Push{Environment: "sandbox"}).Host(); got != "api.sandbox.push.apple.com" {
245 t.Fatalf("sandbox host = %q", got)
246 }
247 t.Setenv("GITBAY_APNS_HOST", "127.0.0.1:1234")
248 if got := (Push{Environment: "production"}).Host(); got != "127.0.0.1:1234" {
249 t.Fatalf("GITBAY_APNS_HOST ignored: %q", got)
250 }
251}