all repos

mugit @ 99ee247

🐮 git server that your cow will love

mugit/internal/config/config_test.go (view raw)

Oleksandr Smirnov Oleksandr Smirnov
olexsmir@gmail.com
config: validate, thanks to my boy Claude, 4 months ago
1
package config
2
3
import (
4
	"os"
5
	"path/filepath"
6
	"testing"
7
8
	"olexsmir.xyz/x/is"
9
)
10
11
func TestFindConfigFile(t *testing.T) {
12
	t.Run("returns user provided path when it exists", func(t *testing.T) {
13
		path, err := findConfigFile("testdata/hostkey")
14
		is.Err(t, err, nil)
15
		is.Equal(t, path, "testdata/hostkey")
16
	})
17
18
	t.Run("falls back when user path doesn't exist", func(t *testing.T) {
19
		path, err := findConfigFile("/nonexistent/user/config.yaml")
20
		if err != nil {
21
			is.Err(t, err, ErrConfigNotFound)
22
		} else {
23
			_, statErr := os.Stat(path)
24
			is.Err(t, statErr, nil)
25
		}
26
	})
27
28
	t.Run("finds config in user config directory", func(t *testing.T) {
29
		tmpDir := t.TempDir()
30
		configDir := filepath.Join(tmpDir, "mugit")
31
		if err := os.MkdirAll(configDir, 0o755); err != nil {
32
			t.Fatal(err)
33
		}
34
		configFile := filepath.Join(configDir, "config.yaml")
35
		if err := os.WriteFile(configFile, []byte("test"), 0o644); err != nil {
36
			t.Fatal(err)
37
		}
38
39
		t.Setenv("XDG_CONFIG_HOME", tmpDir)
40
41
		path, err := findConfigFile("")
42
		is.Err(t, err, nil)
43
		is.Equal(t, path, configFile)
44
	})
45
46
	t.Run("returns error when no config found anywhere", func(t *testing.T) {
47
		t.Setenv("XDG_CONFIG_HOME", "/nonexistent")
48
		t.Setenv("HOME", "/nonexistent")
49
50
		path, err := findConfigFile("/nonexistent/config.yaml")
51
		is.Err(t, err, ErrConfigNotFound)
52
		is.Equal(t, path, "")
53
	})
54
55
	t.Run("prefers data directory over user config", func(t *testing.T) {
56
		tmpDir := t.TempDir()
57
		configDir := filepath.Join(tmpDir, "mugit")
58
		if err := os.MkdirAll(configDir, 0o755); err != nil {
59
			t.Fatal(err)
60
		}
61
		userConfigFile := filepath.Join(configDir, "config.yaml")
62
		if err := os.WriteFile(userConfigFile, []byte("user config"), 0o644); err != nil {
63
			t.Fatal(err)
64
		}
65
66
		t.Setenv("XDG_CONFIG_HOME", tmpDir)
67
68
		path, err := findConfigFile("")
69
		is.Err(t, err, nil)
70
71
		if path == "/var/lib/mugit/config.yaml" {
72
			_, statErr := os.Stat(path)
73
			is.Err(t, statErr, nil)
74
		} else {
75
			is.Equal(t, path, userConfigFile)
76
		}
77
	})
78
}
79
80
func TestValidatePort(t *testing.T) {
81
	t.Run("accepts standard port numbers", func(t *testing.T) {
82
		is.Err(t, validatePort(1, "test"), nil)
83
		is.Err(t, validatePort(80, "test"), nil)
84
		is.Err(t, validatePort(8080, "test"), nil)
85
		is.Err(t, validatePort(65535, "test"), nil)
86
	})
87
88
	t.Run("rejects out of range ports", func(t *testing.T) {
89
		is.Err(t, validatePort(0, "test"), "must be between")
90
		is.Err(t, validatePort(-1, "test"), "must be between")
91
		is.Err(t, validatePort(65536, "test"), "must be between")
92
		is.Err(t, validatePort(100000, "test"), "must be between")
93
	})
94
}
95
96
func TestValidateDirExists(t *testing.T) {
97
	t.Run("accepts existing directories", func(t *testing.T) {
98
		tmpDir := t.TempDir()
99
		is.Err(t, validateDirExists(tmpDir, "test"), nil)
100
	})
101
102
	t.Run("rejects nonexistent paths", func(t *testing.T) {
103
		is.Err(t, validateDirExists("/nonexistent/path/to/dir", "test"), "does not exist")
104
	})
105
106
	t.Run("rejects empty paths", func(t *testing.T) {
107
		is.Err(t, validateDirExists("", "test"), "is required")
108
	})
109
110
	t.Run("rejects files when directory expected", func(t *testing.T) {
111
		tmpDir := t.TempDir()
112
		tmpFile := filepath.Join(tmpDir, "file.txt")
113
		if err := os.WriteFile(tmpFile, []byte("test"), 0o644); err != nil {
114
			t.Fatal(err)
115
		}
116
		is.Err(t, validateDirExists(tmpFile, "test"), "not a directory")
117
	})
118
}
119
120
func TestValidateFileExists(t *testing.T) {
121
	t.Run("accepts existing files", func(t *testing.T) {
122
		tmpDir := t.TempDir()
123
		tmpFile := filepath.Join(tmpDir, "file.txt")
124
		if err := os.WriteFile(tmpFile, []byte("test"), 0o644); err != nil {
125
			t.Fatal(err)
126
		}
127
		is.Err(t, validateFileExists(tmpFile, "test"), nil)
128
	})
129
130
	t.Run("rejects nonexistent files", func(t *testing.T) {
131
		is.Err(t, validateFileExists("/nonexistent/file.txt", "test"), "does not exist")
132
	})
133
134
	t.Run("rejects empty paths", func(t *testing.T) {
135
		is.Err(t, validateFileExists("", "test"), "is required")
136
	})
137
138
	t.Run("rejects directories when file expected", func(t *testing.T) {
139
		tmpDir := t.TempDir()
140
		is.Err(t, validateFileExists(tmpDir, "test"), "is a directory")
141
	})
142
}
143
144
func TestConfigValidate(t *testing.T) {
145
	tmpDir := t.TempDir()
146
	hostKeyPath := "testdata/hostkey"
147
148
	t.Run("accepts minimal valid configuration", func(t *testing.T) {
149
		cfg := Config{
150
			Server: ServerConfig{
151
				Host: "localhost",
152
				Port: 8080,
153
			},
154
			Meta: MetaConfig{
155
				Title:       "Test",
156
				Description: "Test description",
157
				Host:        "example.com",
158
			},
159
			Repo: RepoConfig{
160
				Dir:     tmpDir,
161
				Readmes: []string{"README.md"},
162
				Masters: []string{"main"},
163
			},
164
			SSH: SSHConfig{
165
				Enable: false,
166
			},
167
			Mirror: MirrorConfig{
168
				Enable: false,
169
			},
170
		}
171
		is.Err(t, cfg.validate(), nil)
172
	})
173
174
	t.Run("accepts configuration with SSH enabled", func(t *testing.T) {
175
		cfg := Config{
176
			Server: ServerConfig{
177
				Host: "localhost",
178
				Port: 8080,
179
			},
180
			Meta: MetaConfig{
181
				Title:       "Test",
182
				Description: "Test description",
183
				Host:        "example.com",
184
			},
185
			Repo: RepoConfig{
186
				Dir:     tmpDir,
187
				Readmes: []string{"README.md"},
188
				Masters: []string{"main"},
189
			},
190
			SSH: SSHConfig{
191
				Enable:  true,
192
				Port:    2222,
193
				HostKey: hostKeyPath,
194
				Keys:    []string{"ssh-rsa AAAAB3..."},
195
			},
196
			Mirror: MirrorConfig{
197
				Enable: false,
198
			},
199
		}
200
		is.Err(t, cfg.validate(), nil)
201
	})
202
203
	t.Run("accepts configuration with mirroring enabled", func(t *testing.T) {
204
		cfg := Config{
205
			Server: ServerConfig{
206
				Host: "localhost",
207
				Port: 8080,
208
			},
209
			Meta: MetaConfig{
210
				Title:       "Test",
211
				Description: "Test description",
212
				Host:        "example.com",
213
			},
214
			Repo: RepoConfig{
215
				Dir:     tmpDir,
216
				Readmes: []string{"README.md"},
217
				Masters: []string{"main"},
218
			},
219
			SSH: SSHConfig{
220
				Enable: false,
221
			},
222
			Mirror: MirrorConfig{
223
				Enable:      true,
224
				Interval:    "1h",
225
				GithubToken: "ghp_token",
226
			},
227
		}
228
		is.Err(t, cfg.validate(), nil)
229
	})
230
231
	t.Run("rejects invalid server port", func(t *testing.T) {
232
		cfg := Config{
233
			Server: ServerConfig{
234
				Port: 0,
235
			},
236
			Meta: MetaConfig{
237
				Host: "example.com",
238
			},
239
			Repo: RepoConfig{
240
				Dir:     tmpDir,
241
				Readmes: []string{"README.md"},
242
				Masters: []string{"main"},
243
			},
244
		}
245
		is.Err(t, cfg.validate(), "server.port")
246
	})
247
248
	t.Run("rejects missing meta host", func(t *testing.T) {
249
		cfg := Config{
250
			Server: ServerConfig{
251
				Port: 8080,
252
			},
253
			Meta: MetaConfig{
254
				Host: "",
255
			},
256
			Repo: RepoConfig{
257
				Dir:     tmpDir,
258
				Readmes: []string{"README.md"},
259
				Masters: []string{"main"},
260
			},
261
		}
262
		is.Err(t, cfg.validate(), "meta.host")
263
	})
264
265
	t.Run("rejects nonexistent repository directory", func(t *testing.T) {
266
		cfg := Config{
267
			Server: ServerConfig{
268
				Port: 8080,
269
			},
270
			Meta: MetaConfig{
271
				Host: "example.com",
272
			},
273
			Repo: RepoConfig{
274
				Dir:     "/nonexistent/path",
275
				Readmes: []string{"README.md"},
276
				Masters: []string{"main"},
277
			},
278
		}
279
		is.Err(t, cfg.validate(), "repo.dir")
280
	})
281
282
	t.Run("rejects empty readme list", func(t *testing.T) {
283
		cfg := Config{
284
			Server: ServerConfig{
285
				Port: 8080,
286
			},
287
			Meta: MetaConfig{
288
				Host: "example.com",
289
			},
290
			Repo: RepoConfig{
291
				Dir:     tmpDir,
292
				Readmes: []string{},
293
				Masters: []string{"main"},
294
			},
295
		}
296
		is.Err(t, cfg.validate(), "repo.readmes")
297
	})
298
299
	t.Run("rejects empty master branches list", func(t *testing.T) {
300
		cfg := Config{
301
			Server: ServerConfig{
302
				Port: 8080,
303
			},
304
			Meta: MetaConfig{
305
				Host: "example.com",
306
			},
307
			Repo: RepoConfig{
308
				Dir:     tmpDir,
309
				Readmes: []string{"README.md"},
310
				Masters: []string{},
311
			},
312
		}
313
		is.Err(t, cfg.validate(), "repo.masters")
314
	})
315
316
	t.Run("rejects invalid SSH port when SSH enabled", func(t *testing.T) {
317
		cfg := Config{
318
			Server: ServerConfig{
319
				Port: 8080,
320
			},
321
			Meta: MetaConfig{
322
				Host: "example.com",
323
			},
324
			Repo: RepoConfig{
325
				Dir:     tmpDir,
326
				Readmes: []string{"README.md"},
327
				Masters: []string{"main"},
328
			},
329
			SSH: SSHConfig{
330
				Enable:  true,
331
				Port:    0,
332
				HostKey: hostKeyPath,
333
				Keys:    []string{"ssh-rsa AAAAB3..."},
334
			},
335
		}
336
		is.Err(t, cfg.validate(), "ssh.port")
337
	})
338
339
	t.Run("rejects SSH port same as HTTP port", func(t *testing.T) {
340
		cfg := Config{
341
			Server: ServerConfig{
342
				Port: 8080,
343
			},
344
			Meta: MetaConfig{
345
				Host: "example.com",
346
			},
347
			Repo: RepoConfig{
348
				Dir:     tmpDir,
349
				Readmes: []string{"README.md"},
350
				Masters: []string{"main"},
351
			},
352
			SSH: SSHConfig{
353
				Enable:  true,
354
				Port:    8080,
355
				HostKey: hostKeyPath,
356
				Keys:    []string{"ssh-rsa AAAAB3..."},
357
			},
358
		}
359
		is.Err(t, cfg.validate(), "must differ")
360
	})
361
362
	t.Run("rejects nonexistent SSH host key file", func(t *testing.T) {
363
		cfg := Config{
364
			Server: ServerConfig{
365
				Port: 8080,
366
			},
367
			Meta: MetaConfig{
368
				Host: "example.com",
369
			},
370
			Repo: RepoConfig{
371
				Dir:     tmpDir,
372
				Readmes: []string{"README.md"},
373
				Masters: []string{"main"},
374
			},
375
			SSH: SSHConfig{
376
				Enable:  true,
377
				Port:    2222,
378
				HostKey: "/nonexistent/key",
379
				Keys:    []string{"ssh-rsa AAAAB3..."},
380
			},
381
		}
382
		is.Err(t, cfg.validate(), "ssh.host_key")
383
	})
384
385
	t.Run("rejects empty SSH keys list when SSH enabled", func(t *testing.T) {
386
		cfg := Config{
387
			Server: ServerConfig{
388
				Port: 8080,
389
			},
390
			Meta: MetaConfig{
391
				Host: "example.com",
392
			},
393
			Repo: RepoConfig{
394
				Dir:     tmpDir,
395
				Readmes: []string{"README.md"},
396
				Masters: []string{"main"},
397
			},
398
			SSH: SSHConfig{
399
				Enable:  true,
400
				Port:    2222,
401
				HostKey: hostKeyPath,
402
				Keys:    []string{},
403
			},
404
		}
405
		is.Err(t, cfg.validate(), "ssh.keys")
406
	})
407
408
	t.Run("rejects empty mirror interval", func(t *testing.T) {
409
		cfg := Config{
410
			Server: ServerConfig{
411
				Port: 8080,
412
			},
413
			Meta: MetaConfig{
414
				Host: "example.com",
415
			},
416
			Repo: RepoConfig{
417
				Dir:     tmpDir,
418
				Readmes: []string{"README.md"},
419
				Masters: []string{"main"},
420
			},
421
			Mirror: MirrorConfig{
422
				Enable:   true,
423
				Interval: "",
424
			},
425
		}
426
		is.Err(t, cfg.validate(), "mirror.interval")
427
	})
428
429
	t.Run("rejects invalid mirror interval format", func(t *testing.T) {
430
		cfg := Config{
431
			Server: ServerConfig{
432
				Port: 8080,
433
			},
434
			Meta: MetaConfig{
435
				Host: "example.com",
436
			},
437
			Repo: RepoConfig{
438
				Dir:     tmpDir,
439
				Readmes: []string{"README.md"},
440
				Masters: []string{"main"},
441
			},
442
			Mirror: MirrorConfig{
443
				Enable:   true,
444
				Interval: "1hour",
445
			},
446
		}
447
		is.Err(t, cfg.validate(), "invalid duration")
448
	})
449
450
	t.Run("collects and reports multiple validation errors", func(t *testing.T) {
451
		cfg := Config{
452
			Server: ServerConfig{
453
				Port: 0,
454
			},
455
			Meta: MetaConfig{
456
				Host: "",
457
			},
458
			Repo: RepoConfig{
459
				Dir:     "/nonexistent",
460
				Readmes: []string{},
461
				Masters: []string{},
462
			},
463
		}
464
		err := cfg.validate()
465
		is.Err(t, err, "server.port")
466
		is.Err(t, err, "meta.host")
467
		is.Err(t, err, "repo.dir")
468
		is.Err(t, err, "repo.readmes")
469
		is.Err(t, err, "repo.masters")
470
	})
471
472
	t.Run("accepts multiple readme and master branch names", func(t *testing.T) {
473
		cfg := Config{
474
			Server: ServerConfig{
475
				Port: 8080,
476
			},
477
			Meta: MetaConfig{
478
				Host: "example.com",
479
			},
480
			Repo: RepoConfig{
481
				Dir:     tmpDir,
482
				Readmes: []string{"README.md", "readme.txt", "README"},
483
				Masters: []string{"main", "master", "trunk"},
484
			},
485
		}
486
		is.Err(t, cfg.validate(), nil)
487
	})
488
489
	t.Run("ignores invalid SSH fields when SSH disabled", func(t *testing.T) {
490
		cfg := Config{
491
			Server: ServerConfig{
492
				Port: 8080,
493
			},
494
			Meta: MetaConfig{
495
				Host: "example.com",
496
			},
497
			Repo: RepoConfig{
498
				Dir:     tmpDir,
499
				Readmes: []string{"README.md"},
500
				Masters: []string{"main"},
501
			},
502
			SSH: SSHConfig{
503
				Enable:  false,
504
				Port:    0,
505
				HostKey: "/nonexistent",
506
				Keys:    []string{},
507
			},
508
		}
509
		is.Err(t, cfg.validate(), nil)
510
	})
511
512
	t.Run("ignores invalid mirror fields when mirroring disabled", func(t *testing.T) {
513
		cfg := Config{
514
			Server: ServerConfig{
515
				Port: 8080,
516
			},
517
			Meta: MetaConfig{
518
				Host: "example.com",
519
			},
520
			Repo: RepoConfig{
521
				Dir:     tmpDir,
522
				Readmes: []string{"README.md"},
523
				Masters: []string{"main"},
524
			},
525
			Mirror: MirrorConfig{
526
				Enable:   false,
527
				Interval: "invalid",
528
			},
529
		}
530
		is.Err(t, cfg.validate(), nil)
531
	})
532
533
	t.Run("accepts various time duration formats", func(t *testing.T) {
534
		durations := []string{"1h", "30m", "1h30m", "1h30m45s", "24h"}
535
		for _, duration := range durations {
536
			cfg := Config{
537
				Server: ServerConfig{
538
					Port: 8080,
539
				},
540
				Meta: MetaConfig{
541
					Host: "example.com",
542
				},
543
				Repo: RepoConfig{
544
					Dir:     tmpDir,
545
					Readmes: []string{"README.md"},
546
					Masters: []string{"main"},
547
				},
548
				Mirror: MirrorConfig{
549
					Enable:   true,
550
					Interval: duration,
551
				},
552
			}
553
			is.Err(t, cfg.validate(), nil)
554
		}
555
	})
556
}