1package config
2
3import (
4 "context"
5 "os"
6 "path/filepath"
7 "testing"
8
9 "github.com/bnema/zerowrap"
10 "github.com/spf13/viper"
11 "github.com/stretchr/testify/assert"
12 "github.com/stretchr/testify/require"
13
14 "gordon/internal/boundaries/out/mocks"
15 "gordon/internal/domain"
16)
17
18func testContext() context.Context {
19 return zerowrap.WithCtx(context.Background(), zerowrap.Default())
20}
21
22func TestService_Load(t *testing.T) {
23 v := viper.New()
24 v.Set("server.port", 8080)
25 v.Set("server.registry_port", 5000)
26 v.Set("server.registry_domain", "registry.example.com")
27 v.Set("server.data_dir", "/var/gordon")
28 v.Set("auto_route.enabled", true)
29 v.Set("network_isolation.enabled", true)
30 v.Set("network_isolation.network_prefix", "gordon")
31 v.Set("auth.enabled", true)
32 v.Set("auth.username", "admin")
33 v.Set("auth.password", "secret")
34 v.Set("volumes.auto_create", true)
35 v.Set("volumes.prefix", "gordon")
36 v.Set("volumes.preserve", false)
37 v.Set("routes", map[string]interface{}{
38 "app.example.com": "myapp:latest",
39 })
40
41 eventBus := mocks.NewMockEventPublisher(t)
42 svc := NewService(v, eventBus)
43 ctx := testContext()
44
45 err := svc.Load(ctx)
46
47 assert.NoError(t, err)
48 assert.Equal(t, 8080, svc.GetServerPort())
49 assert.Equal(t, 5000, svc.GetRegistryPort())
50 assert.Equal(t, "registry.example.com", svc.GetRegistryDomain())
51 assert.Equal(t, "/var/gordon", svc.GetDataDir())
52 assert.True(t, svc.IsAutoRouteEnabled())
53 assert.True(t, svc.IsNetworkIsolationEnabled())
54 assert.Equal(t, "gordon", svc.GetNetworkPrefix())
55
56 enabled, username, password := svc.GetRegistryAuthConfig()
57 assert.True(t, enabled)
58 assert.Equal(t, "admin", username)
59 assert.Equal(t, "secret", password)
60
61 autoCreate, prefix, preserve := svc.GetVolumeConfig()
62 assert.True(t, autoCreate)
63 assert.Equal(t, "gordon", prefix)
64 assert.False(t, preserve)
65}
66
67func TestService_GetRoutes(t *testing.T) {
68 v := viper.New()
69 v.Set("routes", map[string]interface{}{
70 "app1.example.com": "myapp:latest",
71 "app2.example.com": "otherapp:v2",
72 "http://insecure.example": "insecureapp:latest",
73 })
74
75 eventBus := mocks.NewMockEventPublisher(t)
76 svc := NewService(v, eventBus)
77 ctx := testContext()
78
79 _ = svc.Load(ctx)
80
81 routes := svc.GetRoutes(ctx)
82
83 assert.Len(t, routes, 3)
84
85 // Check routes (order is not guaranteed)
86 routeMap := make(map[string]domain.Route)
87 for _, r := range routes {
88 routeMap[r.Domain] = r
89 }
90
91 assert.Equal(t, "myapp:latest", routeMap["app1.example.com"].Image)
92 assert.True(t, routeMap["app1.example.com"].HTTPS)
93
94 assert.Equal(t, "otherapp:v2", routeMap["app2.example.com"].Image)
95 assert.True(t, routeMap["app2.example.com"].HTTPS)
96
97 assert.Equal(t, "insecureapp:latest", routeMap["insecure.example"].Image)
98 assert.False(t, routeMap["insecure.example"].HTTPS)
99}
100
101func TestService_GetRoute(t *testing.T) {
102 v := viper.New()
103 v.Set("routes", map[string]interface{}{
104 "app.example.com": "myapp:latest",
105 "http://insecure.example": "insecureapp:latest",
106 })
107
108 eventBus := mocks.NewMockEventPublisher(t)
109 svc := NewService(v, eventBus)
110 ctx := testContext()
111
112 _ = svc.Load(ctx)
113
114 t.Run("existing route", func(t *testing.T) {
115 route, err := svc.GetRoute(ctx, "app.example.com")
116 require.NoError(t, err)
117 assert.Equal(t, "app.example.com", route.Domain)
118 assert.Equal(t, "myapp:latest", route.Image)
119 assert.True(t, route.HTTPS)
120 })
121
122 t.Run("non-existent route", func(t *testing.T) {
123 route, err := svc.GetRoute(ctx, "notfound.example.com")
124 assert.ErrorIs(t, err, domain.ErrRouteNotFound)
125 assert.Nil(t, route)
126 })
127}
128
129func TestService_AddRoute(t *testing.T) {
130 t.Run("success", func(t *testing.T) {
131 // Create temp config file for Save() to work
132 tmpDir := t.TempDir()
133 configFile := filepath.Join(tmpDir, "gordon.toml")
134 err := os.WriteFile(configFile, []byte("[routes]\n"), 0600)
135 require.NoError(t, err)
136
137 v := viper.New()
138 v.SetConfigFile(configFile)
139 eventBus := mocks.NewMockEventPublisher(t)
140 svc := NewService(v, eventBus)
141 ctx := testContext()
142
143 _ = svc.Load(ctx)
144
145 route := domain.Route{
146 Domain: "new.example.com",
147 Image: "newapp:latest",
148 }
149
150 err = svc.AddRoute(ctx, route)
151
152 assert.NoError(t, err)
153
154 // Verify route was added
155 config := svc.GetConfig()
156 assert.Equal(t, "newapp:latest", config.Routes["new.example.com"])
157 })
158
159 t.Run("empty domain", func(t *testing.T) {
160 v := viper.New()
161 eventBus := mocks.NewMockEventPublisher(t)
162 svc := NewService(v, eventBus)
163 ctx := testContext()
164
165 route := domain.Route{
166 Domain: "",
167 Image: "myapp:latest",
168 }
169
170 err := svc.AddRoute(ctx, route)
171 assert.ErrorIs(t, err, domain.ErrRouteDomainEmpty)
172 })
173
174 t.Run("empty image", func(t *testing.T) {
175 v := viper.New()
176 eventBus := mocks.NewMockEventPublisher(t)
177 svc := NewService(v, eventBus)
178 ctx := testContext()
179
180 route := domain.Route{
181 Domain: "example.com",
182 Image: "",
183 }
184
185 err := svc.AddRoute(ctx, route)
186 assert.ErrorIs(t, err, domain.ErrRouteImageEmpty)
187 })
188}
189
190func TestService_UpdateRoute(t *testing.T) {
191 t.Run("success", func(t *testing.T) {
192 // Create temp config file for Save() to work
193 tmpDir := t.TempDir()
194 configFile := filepath.Join(tmpDir, "gordon.toml")
195 err := os.WriteFile(configFile, []byte("[routes]\n\"app.example.com\" = \"myapp:v1\"\n"), 0600)
196 require.NoError(t, err)
197
198 v := viper.New()
199 v.SetConfigFile(configFile)
200 v.Set("routes", map[string]any{
201 "app.example.com": "myapp:v1",
202 })
203
204 eventBus := mocks.NewMockEventPublisher(t)
205 svc := NewService(v, eventBus)
206 ctx := testContext()
207
208 _ = svc.Load(ctx)
209
210 route := domain.Route{
211 Domain: "app.example.com",
212 Image: "myapp:v2",
213 }
214
215 err = svc.UpdateRoute(ctx, route)
216
217 assert.NoError(t, err)
218
219 config := svc.GetConfig()
220 assert.Equal(t, "myapp:v2", config.Routes["app.example.com"])
221 })
222
223 t.Run("not found", func(t *testing.T) {
224 v := viper.New()
225 eventBus := mocks.NewMockEventPublisher(t)
226 svc := NewService(v, eventBus)
227 ctx := testContext()
228
229 _ = svc.Load(ctx)
230
231 route := domain.Route{
232 Domain: "nonexistent.example.com",
233 Image: "myapp:latest",
234 }
235
236 err := svc.UpdateRoute(ctx, route)
237 assert.ErrorIs(t, err, domain.ErrRouteNotFound)
238 })
239
240 t.Run("empty domain", func(t *testing.T) {
241 v := viper.New()
242 eventBus := mocks.NewMockEventPublisher(t)
243 svc := NewService(v, eventBus)
244 ctx := testContext()
245
246 route := domain.Route{
247 Domain: "",
248 Image: "myapp:latest",
249 }
250
251 err := svc.UpdateRoute(ctx, route)
252 assert.ErrorIs(t, err, domain.ErrRouteDomainEmpty)
253 })
254
255 t.Run("empty image", func(t *testing.T) {
256 v := viper.New()
257 eventBus := mocks.NewMockEventPublisher(t)
258 svc := NewService(v, eventBus)
259 ctx := testContext()
260
261 route := domain.Route{
262 Domain: "example.com",
263 Image: "",
264 }
265
266 err := svc.UpdateRoute(ctx, route)
267 assert.ErrorIs(t, err, domain.ErrRouteImageEmpty)
268 })
269}
270
271func TestService_RemoveRoute(t *testing.T) {
272 // Create temp config file for Save() to work
273 tmpDir := t.TempDir()
274 configFile := filepath.Join(tmpDir, "gordon.toml")
275 err := os.WriteFile(configFile, []byte("[routes]\n\"app.example.com\" = \"myapp:latest\"\n"), 0600)
276 require.NoError(t, err)
277
278 v := viper.New()
279 v.SetConfigFile(configFile)
280 v.Set("routes", map[string]interface{}{
281 "app.example.com": "myapp:latest",
282 })
283
284 eventBus := mocks.NewMockEventPublisher(t)
285 svc := NewService(v, eventBus)
286 ctx := testContext()
287
288 _ = svc.Load(ctx)
289
290 err = svc.RemoveRoute(ctx, "app.example.com")
291
292 assert.NoError(t, err)
293
294 config := svc.GetConfig()
295 _, exists := config.Routes["app.example.com"]
296 assert.False(t, exists)
297}
298
299func TestService_RemoveRoute_NotFound(t *testing.T) {
300 v := viper.New()
301 eventBus := mocks.NewMockEventPublisher(t)
302 svc := NewService(v, eventBus)
303 ctx := testContext()
304
305 _ = svc.Load(ctx)
306
307 err := svc.RemoveRoute(ctx, "nonexistent.example.com")
308
309 assert.ErrorIs(t, err, domain.ErrRouteNotFound)
310}
311
312func TestService_GetNetworkGroups(t *testing.T) {
313 v := viper.New()
314 v.Set("network_groups", map[string]interface{}{
315 "frontend": []interface{}{"app1.example.com", "app2.example.com"},
316 "backend": []interface{}{"api.example.com", "db.example.com"},
317 })
318
319 eventBus := mocks.NewMockEventPublisher(t)
320 svc := NewService(v, eventBus)
321 ctx := testContext()
322
323 _ = svc.Load(ctx)
324
325 groups := svc.GetNetworkGroups()
326
327 assert.Len(t, groups, 2)
328 assert.ElementsMatch(t, []string{"app1.example.com", "app2.example.com"}, groups["frontend"])
329 assert.ElementsMatch(t, []string{"api.example.com", "db.example.com"}, groups["backend"])
330}
331
332func TestService_GetAttachments(t *testing.T) {
333 v := viper.New()
334 v.Set("attachments", map[string]interface{}{
335 "app.example.com": []interface{}{"redis:latest", "postgres:18"},
336 })
337
338 eventBus := mocks.NewMockEventPublisher(t)
339 svc := NewService(v, eventBus)
340 ctx := testContext()
341
342 _ = svc.Load(ctx)
343
344 attachments := svc.GetAttachments()
345
346 assert.Len(t, attachments, 1)
347 assert.ElementsMatch(t, []string{"redis:latest", "postgres:18"}, attachments["app.example.com"])
348}
349
350func TestService_GetAllAttachments(t *testing.T) {
351 t.Run("returns all attachments", func(t *testing.T) {
352 v := viper.New()
353 v.Set("attachments", map[string]interface{}{
354 "app.example.com": []interface{}{"redis:latest", "postgres:18"},
355 "api.example.com": []interface{}{"rabbitmq:3"},
356 })
357
358 eventBus := mocks.NewMockEventPublisher(t)
359 svc := NewService(v, eventBus)
360 ctx := testContext()
361
362 _ = svc.Load(ctx)
363
364 attachments := svc.GetAllAttachments(ctx)
365
366 assert.Len(t, attachments, 2)
367 assert.ElementsMatch(t, []string{"redis:latest", "postgres:18"}, attachments["app.example.com"])
368 assert.ElementsMatch(t, []string{"rabbitmq:3"}, attachments["api.example.com"])
369 })
370
371 t.Run("returns empty map when no attachments", func(t *testing.T) {
372 v := viper.New()
373 eventBus := mocks.NewMockEventPublisher(t)
374 svc := NewService(v, eventBus)
375 ctx := testContext()
376
377 _ = svc.Load(ctx)
378
379 attachments := svc.GetAllAttachments(ctx)
380
381 assert.Empty(t, attachments)
382 })
383
384 t.Run("returns copy not reference", func(t *testing.T) {
385 v := viper.New()
386 v.Set("attachments", map[string]interface{}{
387 "app.example.com": []interface{}{"redis:latest"},
388 })
389
390 eventBus := mocks.NewMockEventPublisher(t)
391 svc := NewService(v, eventBus)
392 ctx := testContext()
393
394 _ = svc.Load(ctx)
395
396 attachments := svc.GetAllAttachments(ctx)
397 // Modify the returned map
398 attachments["app.example.com"] = append(attachments["app.example.com"], "postgres:18")
399
400 // Original should be unchanged
401 original := svc.GetAllAttachments(ctx)
402 assert.Len(t, original["app.example.com"], 1)
403 })
404}
405
406func TestService_GetAttachmentsFor(t *testing.T) {
407 t.Run("existing domain", func(t *testing.T) {
408 v := viper.New()
409 v.Set("attachments", map[string]interface{}{
410 "app.example.com": []interface{}{"redis:latest", "postgres:18"},
411 })
412
413 eventBus := mocks.NewMockEventPublisher(t)
414 svc := NewService(v, eventBus)
415 ctx := testContext()
416
417 _ = svc.Load(ctx)
418
419 images, err := svc.GetAttachmentsFor(ctx, "app.example.com")
420
421 require.NoError(t, err)
422 assert.ElementsMatch(t, []string{"redis:latest", "postgres:18"}, images)
423 })
424
425 t.Run("existing network group", func(t *testing.T) {
426 v := viper.New()
427 v.Set("attachments", map[string]interface{}{
428 "backend": []interface{}{"rabbitmq:3", "redis:latest"},
429 })
430
431 eventBus := mocks.NewMockEventPublisher(t)
432 svc := NewService(v, eventBus)
433 ctx := testContext()
434
435 _ = svc.Load(ctx)
436
437 images, err := svc.GetAttachmentsFor(ctx, "backend")
438
439 require.NoError(t, err)
440 assert.ElementsMatch(t, []string{"rabbitmq:3", "redis:latest"}, images)
441 })
442
443 t.Run("non-existent target", func(t *testing.T) {
444 v := viper.New()
445 eventBus := mocks.NewMockEventPublisher(t)
446 svc := NewService(v, eventBus)
447 ctx := testContext()
448
449 _ = svc.Load(ctx)
450
451 images, err := svc.GetAttachmentsFor(ctx, "notfound.example.com")
452
453 assert.ErrorIs(t, err, domain.ErrAttachmentNotFound)
454 assert.Nil(t, images)
455 })
456
457 t.Run("returns copy not reference", func(t *testing.T) {
458 v := viper.New()
459 v.Set("attachments", map[string]interface{}{
460 "app.example.com": []interface{}{"redis:latest"},
461 })
462
463 eventBus := mocks.NewMockEventPublisher(t)
464 svc := NewService(v, eventBus)
465 ctx := testContext()
466
467 _ = svc.Load(ctx)
468
469 images, err := svc.GetAttachmentsFor(ctx, "app.example.com")
470 require.NoError(t, err)
471
472 // Modify the returned slice (use _ to satisfy linter)
473 _ = append(images, "postgres:18")
474
475 // Original should be unchanged
476 original, _ := svc.GetAttachmentsFor(ctx, "app.example.com")
477 assert.Len(t, original, 1)
478 })
479}
480
481func TestService_AddAttachment(t *testing.T) {
482 t.Run("success - new target", func(t *testing.T) {
483 tmpDir := t.TempDir()
484 configFile := filepath.Join(tmpDir, "gordon.toml")
485 err := os.WriteFile(configFile, []byte("[attachments]\n"), 0600)
486 require.NoError(t, err)
487
488 v := viper.New()
489 v.SetConfigFile(configFile)
490 eventBus := mocks.NewMockEventPublisher(t)
491 svc := NewService(v, eventBus)
492 ctx := testContext()
493
494 _ = svc.Load(ctx)
495
496 err = svc.AddAttachment(ctx, "app.example.com", "postgres:18")
497
498 assert.NoError(t, err)
499
500 // Verify attachment was added
501 config := svc.GetConfig()
502 assert.Contains(t, config.Attachments["app.example.com"], "postgres:18")
503 })
504
505 t.Run("success - existing target", func(t *testing.T) {
506 tmpDir := t.TempDir()
507 configFile := filepath.Join(tmpDir, "gordon.toml")
508 err := os.WriteFile(configFile, []byte("[attachments]\n\"app.example.com\" = [\"redis:latest\"]\n"), 0600)
509 require.NoError(t, err)
510
511 v := viper.New()
512 v.SetConfigFile(configFile)
513 v.Set("attachments", map[string]interface{}{
514 "app.example.com": []interface{}{"redis:latest"},
515 })
516 eventBus := mocks.NewMockEventPublisher(t)
517 svc := NewService(v, eventBus)
518 ctx := testContext()
519
520 _ = svc.Load(ctx)
521
522 err = svc.AddAttachment(ctx, "app.example.com", "postgres:18")
523
524 assert.NoError(t, err)
525
526 // Verify attachment was added alongside existing
527 config := svc.GetConfig()
528 assert.ElementsMatch(t, []string{"redis:latest", "postgres:18"}, config.Attachments["app.example.com"])
529 })
530
531 t.Run("duplicate attachment", func(t *testing.T) {
532 v := viper.New()
533 v.Set("attachments", map[string]interface{}{
534 "app.example.com": []interface{}{"redis:latest"},
535 })
536 eventBus := mocks.NewMockEventPublisher(t)
537 svc := NewService(v, eventBus)
538 ctx := testContext()
539
540 _ = svc.Load(ctx)
541
542 err := svc.AddAttachment(ctx, "app.example.com", "redis:latest")
543
544 assert.ErrorIs(t, err, domain.ErrAttachmentExists)
545 })
546
547 t.Run("empty target", func(t *testing.T) {
548 v := viper.New()
549 eventBus := mocks.NewMockEventPublisher(t)
550 svc := NewService(v, eventBus)
551 ctx := testContext()
552
553 err := svc.AddAttachment(ctx, "", "postgres:18")
554
555 assert.ErrorIs(t, err, domain.ErrAttachmentTargetEmpty)
556 })
557
558 t.Run("empty image", func(t *testing.T) {
559 v := viper.New()
560 eventBus := mocks.NewMockEventPublisher(t)
561 svc := NewService(v, eventBus)
562 ctx := testContext()
563
564 err := svc.AddAttachment(ctx, "app.example.com", "")
565
566 assert.ErrorIs(t, err, domain.ErrAttachmentImageEmpty)
567 })
568
569 t.Run("network group target", func(t *testing.T) {
570 tmpDir := t.TempDir()
571 configFile := filepath.Join(tmpDir, "gordon.toml")
572 err := os.WriteFile(configFile, []byte("[attachments]\n"), 0600)
573 require.NoError(t, err)
574
575 v := viper.New()
576 v.SetConfigFile(configFile)
577 eventBus := mocks.NewMockEventPublisher(t)
578 svc := NewService(v, eventBus)
579 ctx := testContext()
580
581 _ = svc.Load(ctx)
582
583 err = svc.AddAttachment(ctx, "backend", "rabbitmq:3")
584
585 assert.NoError(t, err)
586
587 config := svc.GetConfig()
588 assert.Contains(t, config.Attachments["backend"], "rabbitmq:3")
589 })
590}
591
592func TestService_RemoveAttachment(t *testing.T) {
593 t.Run("success - single attachment", func(t *testing.T) {
594 tmpDir := t.TempDir()
595 configFile := filepath.Join(tmpDir, "gordon.toml")
596 err := os.WriteFile(configFile, []byte("[attachments]\n\"app.example.com\" = [\"redis:latest\"]\n"), 0600)
597 require.NoError(t, err)
598
599 v := viper.New()
600 v.SetConfigFile(configFile)
601 v.Set("attachments", map[string]interface{}{
602 "app.example.com": []interface{}{"redis:latest"},
603 })
604 eventBus := mocks.NewMockEventPublisher(t)
605 svc := NewService(v, eventBus)
606 ctx := testContext()
607
608 _ = svc.Load(ctx)
609
610 err = svc.RemoveAttachment(ctx, "app.example.com", "redis:latest")
611
612 assert.NoError(t, err)
613
614 // Verify target was removed (no attachments left)
615 config := svc.GetConfig()
616 _, exists := config.Attachments["app.example.com"]
617 assert.False(t, exists)
618 })
619
620 t.Run("success - multiple attachments", func(t *testing.T) {
621 tmpDir := t.TempDir()
622 configFile := filepath.Join(tmpDir, "gordon.toml")
623 err := os.WriteFile(configFile, []byte("[attachments]\n\"app.example.com\" = [\"redis:latest\", \"postgres:18\"]\n"), 0600)
624 require.NoError(t, err)
625
626 v := viper.New()
627 v.SetConfigFile(configFile)
628 v.Set("attachments", map[string]interface{}{
629 "app.example.com": []interface{}{"redis:latest", "postgres:18"},
630 })
631 eventBus := mocks.NewMockEventPublisher(t)
632 svc := NewService(v, eventBus)
633 ctx := testContext()
634
635 _ = svc.Load(ctx)
636
637 err = svc.RemoveAttachment(ctx, "app.example.com", "redis:latest")
638
639 assert.NoError(t, err)
640
641 // Verify only redis was removed
642 config := svc.GetConfig()
643 assert.Equal(t, []string{"postgres:18"}, config.Attachments["app.example.com"])
644 })
645
646 t.Run("target not found", func(t *testing.T) {
647 v := viper.New()
648 eventBus := mocks.NewMockEventPublisher(t)
649 svc := NewService(v, eventBus)
650 ctx := testContext()
651
652 _ = svc.Load(ctx)
653
654 err := svc.RemoveAttachment(ctx, "notfound.example.com", "redis:latest")
655
656 assert.ErrorIs(t, err, domain.ErrAttachmentNotFound)
657 })
658
659 t.Run("image not found in target", func(t *testing.T) {
660 v := viper.New()
661 v.Set("attachments", map[string]interface{}{
662 "app.example.com": []interface{}{"redis:latest"},
663 })
664 eventBus := mocks.NewMockEventPublisher(t)
665 svc := NewService(v, eventBus)
666 ctx := testContext()
667
668 _ = svc.Load(ctx)
669
670 err := svc.RemoveAttachment(ctx, "app.example.com", "postgres:18")
671
672 assert.ErrorIs(t, err, domain.ErrAttachmentNotFound)
673 })
674
675 t.Run("empty target", func(t *testing.T) {
676 v := viper.New()
677 eventBus := mocks.NewMockEventPublisher(t)
678 svc := NewService(v, eventBus)
679 ctx := testContext()
680
681 err := svc.RemoveAttachment(ctx, "", "redis:latest")
682
683 assert.ErrorIs(t, err, domain.ErrAttachmentTargetEmpty)
684 })
685
686 t.Run("empty image", func(t *testing.T) {
687 v := viper.New()
688 eventBus := mocks.NewMockEventPublisher(t)
689 svc := NewService(v, eventBus)
690 ctx := testContext()
691
692 err := svc.RemoveAttachment(ctx, "app.example.com", "")
693
694 assert.ErrorIs(t, err, domain.ErrAttachmentImageEmpty)
695 })
696
697 t.Run("network group target", func(t *testing.T) {
698 tmpDir := t.TempDir()
699 configFile := filepath.Join(tmpDir, "gordon.toml")
700 err := os.WriteFile(configFile, []byte("[attachments]\n\"backend\" = [\"rabbitmq:3\"]\n"), 0600)
701 require.NoError(t, err)
702
703 v := viper.New()
704 v.SetConfigFile(configFile)
705 v.Set("attachments", map[string]interface{}{
706 "backend": []interface{}{"rabbitmq:3"},
707 })
708 eventBus := mocks.NewMockEventPublisher(t)
709 svc := NewService(v, eventBus)
710 ctx := testContext()
711
712 _ = svc.Load(ctx)
713
714 err = svc.RemoveAttachment(ctx, "backend", "rabbitmq:3")
715
716 assert.NoError(t, err)
717
718 config := svc.GetConfig()
719 _, exists := config.Attachments["backend"]
720 assert.False(t, exists)
721 })
722}
723
724func TestService_GetExternalRoutes(t *testing.T) {
725 v := viper.New()
726 v.Set("external_routes", map[string]interface{}{
727 "reg.example.com": "localhost:5000",
728 "cache.example.com": "127.0.0.1:6379",
729 })
730
731 eventBus := mocks.NewMockEventPublisher(t)
732 svc := NewService(v, eventBus)
733 ctx := testContext()
734
735 _ = svc.Load(ctx)
736
737 routes := svc.GetExternalRoutes()
738
739 assert.Len(t, routes, 2)
740 assert.Equal(t, "localhost:5000", routes["reg.example.com"])
741 assert.Equal(t, "127.0.0.1:6379", routes["cache.example.com"])
742}
743
744func TestService_GetExternalRoutes_Empty(t *testing.T) {
745 v := viper.New()
746
747 eventBus := mocks.NewMockEventPublisher(t)
748 svc := NewService(v, eventBus)
749 ctx := testContext()
750
751 _ = svc.Load(ctx)
752
753 routes := svc.GetExternalRoutes()
754
755 assert.Empty(t, routes)
756}
757
758func TestExtractDomainFromImageName(t *testing.T) {
759 tests := []struct {
760 name string
761 imageName string
762 expectedDom string
763 expectedFound bool
764 }{
765 {
766 name: "domain like image",
767 imageName: "myapp.example.com:latest",
768 expectedDom: "myapp.example.com",
769 expectedFound: true,
770 },
771 {
772 name: "domain like image without tag",
773 imageName: "api.backend.io",
774 expectedDom: "api.backend.io",
775 expectedFound: true,
776 },
777 {
778 name: "simple image name",
779 imageName: "nginx:latest",
780 expectedDom: "",
781 expectedFound: false,
782 },
783 {
784 name: "registry path",
785 imageName: "gcr.io/project/image:tag",
786 expectedDom: "gcr.io/project/image",
787 expectedFound: true,
788 },
789 {
790 name: "leading dot",
791 imageName: ".invalid:latest",
792 expectedDom: "",
793 expectedFound: false,
794 },
795 {
796 name: "trailing dot",
797 imageName: "invalid.:latest",
798 expectedDom: "",
799 expectedFound: false,
800 },
801 }
802
803 for _, tt := range tests {
804 t.Run(tt.name, func(t *testing.T) {
805 domain, found := ExtractDomainFromImageName(tt.imageName)
806 assert.Equal(t, tt.expectedDom, domain)
807 assert.Equal(t, tt.expectedFound, found)
808 })
809 }
810}
811
812func TestLoadStringMap(t *testing.T) {
813 tests := []struct {
814 name string
815 input any
816 expected map[string]string
817 }{
818 {
819 name: "nil input",
820 input: nil,
821 expected: map[string]string{},
822 },
823 {
824 name: "valid map",
825 input: map[string]any{
826 "key1": "value1",
827 "key2": "value2",
828 },
829 expected: map[string]string{
830 "key1": "value1",
831 "key2": "value2",
832 },
833 },
834 {
835 name: "invalid type",
836 input: "not a map",
837 expected: map[string]string{},
838 },
839 {
840 name: "mixed values",
841 input: map[string]any{
842 "string": "value",
843 "int": 123,
844 },
845 expected: map[string]string{
846 "string": "value",
847 },
848 },
849 }
850
851 for _, tt := range tests {
852 t.Run(tt.name, func(t *testing.T) {
853 result := loadStringMap(tt.input)
854 assert.Equal(t, tt.expected, result)
855 })
856 }
857}
858
859func TestLoadStringArrayMap(t *testing.T) {
860 tests := []struct {
861 name string
862 input any
863 expected map[string][]string
864 }{
865 {
866 name: "nil input",
867 input: nil,
868 expected: map[string][]string{},
869 },
870 {
871 name: "valid map",
872 input: map[string]any{
873 "group1": []any{"a", "b"},
874 "group2": []any{"c", "d", "e"},
875 },
876 expected: map[string][]string{
877 "group1": {"a", "b"},
878 "group2": {"c", "d", "e"},
879 },
880 },
881 {
882 name: "invalid type",
883 input: "not a map",
884 expected: map[string][]string{},
885 },
886 {
887 name: "non-array value",
888 input: map[string]any{
889 "array": []any{"a", "b"},
890 "string": "not an array",
891 },
892 expected: map[string][]string{
893 "array": {"a", "b"},
894 },
895 },
896 }
897
898 for _, tt := range tests {
899 t.Run(tt.name, func(t *testing.T) {
900 result := loadStringArrayMap(tt.input)
901 assert.Equal(t, tt.expected, result)
902 })
903 }
904}