1package events
2
3import (
4 "context"
5 "fmt"
6 "os"
7 "testing"
8
9 "github.com/spf13/viper"
10 "github.com/stretchr/testify/assert"
11 "github.com/stretchr/testify/mock"
12 "github.com/stretchr/testify/require"
13
14 "gordon/internal/config"
15 "gordon/pkg/runtime"
16)
17
18// MockContainerManager is a mock implementation of container.ManagerInterface
19type MockContainerManager struct {
20 mock.Mock
21}
22
23func (m *MockContainerManager) DeployContainer(ctx context.Context, route config.Route) (*runtime.Container, error) {
24 args := m.Called(ctx, route)
25 if args.Get(0) == nil {
26 return nil, args.Error(1)
27 }
28 return args.Get(0).(*runtime.Container), args.Error(1)
29}
30
31func (m *MockContainerManager) StopContainer(ctx context.Context, containerID string) error {
32 args := m.Called(ctx, containerID)
33 return args.Error(0)
34}
35
36func (m *MockContainerManager) RemoveContainer(ctx context.Context, containerID string, force bool) error {
37 args := m.Called(ctx, containerID, force)
38 return args.Error(0)
39}
40
41func (m *MockContainerManager) ListContainers() map[string]*runtime.Container {
42 args := m.Called()
43 if args.Get(0) == nil {
44 return nil
45 }
46 return args.Get(0).(map[string]*runtime.Container)
47}
48
49func (m *MockContainerManager) GetContainer(domain string) (*runtime.Container, bool) {
50 args := m.Called(domain)
51 if args.Get(0) == nil {
52 return nil, args.Bool(1)
53 }
54 return args.Get(0).(*runtime.Container), args.Bool(1)
55}
56
57func (m *MockContainerManager) StopContainerByDomain(ctx context.Context, domain string) error {
58 args := m.Called(ctx, domain)
59 return args.Error(0)
60}
61
62func (m *MockContainerManager) Runtime() runtime.Runtime {
63 args := m.Called()
64 return args.Get(0).(runtime.Runtime)
65}
66
67func (m *MockContainerManager) HealthCheck(ctx context.Context) map[string]bool {
68 args := m.Called(ctx)
69 return args.Get(0).(map[string]bool)
70}
71
72func (m *MockContainerManager) SyncContainers(ctx context.Context) error {
73 args := m.Called(ctx)
74 return args.Error(0)
75}
76
77func (m *MockContainerManager) GetContainerPort(ctx context.Context, domain string, internalPort int) (int, error) {
78 args := m.Called(ctx, domain, internalPort)
79 return args.Int(0), args.Error(1)
80}
81
82func (m *MockContainerManager) GetNetworkForApp(domain string) string {
83 args := m.Called(domain)
84 return args.String(0)
85}
86
87func (m *MockContainerManager) AutoStartContainers(ctx context.Context) error {
88 args := m.Called(ctx)
89 return args.Error(0)
90}
91
92func (m *MockContainerManager) StopAllManagedContainers(ctx context.Context) error {
93 args := m.Called(ctx)
94 return args.Error(0)
95}
96
97func TestContainerEventHandler_CanHandle(t *testing.T) {
98 handler := &ContainerEventHandler{}
99
100 testCases := []struct {
101 eventType EventType
102 expected bool
103 }{
104 {ImagePushed, true},
105 {ConfigReload, true},
106 {ManualReload, true},
107 {ContainerStop, true},
108 {ContainerStart, true},
109 {ImageDeleted, false},
110 {ContainerHealthCheck, false},
111 }
112
113 for _, tc := range testCases {
114 t.Run(string(tc.eventType), func(t *testing.T) {
115 result := handler.CanHandle(tc.eventType)
116 assert.Equal(t, tc.expected, result)
117 })
118 }
119}
120
121func TestContainerEventHandler_Handle_ImagePushed(t *testing.T) {
122 mockManager := &MockContainerManager{}
123 mockConfig := &config.Config{
124 Routes: map[string]string{
125 "app.example.com": "nginx:latest",
126 },
127 }
128
129 handler := &ContainerEventHandler{
130 manager: mockManager,
131 config: mockConfig,
132 }
133
134 // Mock container listing (empty list initially)
135 mockManager.On("ListContainers").Return(map[string]*runtime.Container{})
136
137 // Mock successful deployment
138 expectedRoute := config.Route{
139 Domain: "app.example.com",
140 Image: "nginx:latest",
141 HTTPS: true,
142 }
143 mockContainer := &runtime.Container{
144 ID: "container123",
145 Name: "gordon-app-example-com",
146 }
147 mockManager.On("DeployContainer", mock.Anything, expectedRoute).Return(mockContainer, nil)
148
149 event := Event{
150 Type: ImagePushed,
151 ImageName: "nginx",
152 Tag: "latest",
153 }
154
155 err := handler.Handle(event)
156
157 assert.NoError(t, err)
158 mockManager.AssertExpectations(t)
159}
160
161func TestContainerEventHandler_Handle_ImagePushed_NoMatchingRoutes(t *testing.T) {
162 mockManager := &MockContainerManager{}
163 mockConfig := &config.Config{
164 Routes: map[string]string{
165 "app.example.com": "postgres:14", // Different image
166 },
167 }
168
169 handler := &ContainerEventHandler{
170 manager: mockManager,
171 config: mockConfig,
172 }
173
174 event := Event{
175 Type: ImagePushed,
176 ImageName: "nginx",
177 Tag: "latest",
178 }
179
180 err := handler.Handle(event)
181
182 // Should succeed but not deploy anything
183 assert.NoError(t, err)
184 mockManager.AssertExpectations(t) // No calls expected
185}
186
187func TestContainerEventHandler_Handle_ImagePushed_MultipleRoutes(t *testing.T) {
188 mockManager := &MockContainerManager{}
189 mockConfig := &config.Config{
190 Routes: map[string]string{
191 "app1.example.com": "nginx:latest",
192 "app2.example.com": "nginx:latest",
193 "app3.example.com": "postgres:14", // Different image
194 },
195 }
196
197 handler := &ContainerEventHandler{
198 manager: mockManager,
199 config: mockConfig,
200 }
201
202 // Mock successful deployments for matching routes
203 mockManager.On("ListContainers").Return(map[string]*runtime.Container{}).Times(2)
204
205 expectedRoute1 := config.Route{Domain: "app1.example.com", Image: "nginx:latest", HTTPS: true}
206 expectedRoute2 := config.Route{Domain: "app2.example.com", Image: "nginx:latest", HTTPS: true}
207
208 mockContainer1 := &runtime.Container{ID: "container1", Name: "gordon-app1-example-com"}
209 mockContainer2 := &runtime.Container{ID: "container2", Name: "gordon-app2-example-com"}
210
211 mockManager.On("DeployContainer", mock.Anything, expectedRoute1).Return(mockContainer1, nil)
212 mockManager.On("DeployContainer", mock.Anything, expectedRoute2).Return(mockContainer2, nil)
213
214 event := Event{
215 Type: ImagePushed,
216 ImageName: "nginx",
217 Tag: "latest",
218 }
219
220 err := handler.Handle(event)
221
222 assert.NoError(t, err)
223 mockManager.AssertExpectations(t)
224}
225
226func TestContainerEventHandler_Handle_ConfigReload(t *testing.T) {
227 mockManager := &MockContainerManager{}
228
229 // Mock existing containers
230 existingContainers := map[string]*runtime.Container{
231 "old.example.com": {
232 ID: "container1",
233 Name: "old-container",
234 Labels: map[string]string{
235 "gordon.route": "old.example.com",
236 "gordon.image": "old:image",
237 },
238 },
239 "app.example.com": {
240 ID: "container2",
241 Name: "app-container",
242 Labels: map[string]string{
243 "gordon.route": "app.example.com",
244 "gordon.image": "nginx:latest",
245 },
246 },
247 }
248
249 // New config with different routes
250 newConfig := &config.Config{
251 Routes: map[string]string{
252 "app.example.com": "nginx:latest", // Keep this one
253 "new.example.com": "postgres:14", // Add new one
254 // Remove old.example.com
255 },
256 }
257
258 handler := &ContainerEventHandler{
259 manager: mockManager,
260 config: newConfig,
261 }
262
263 // Mock manager calls
264 mockManager.On("ListContainers").Return(existingContainers).Once() // First call in handleConfigReload
265 mockManager.On("StopContainer", mock.Anything, "container1").Return(nil) // Stop removed container
266 mockManager.On("RemoveContainer", mock.Anything, "container1", true).Return(nil) // Remove removed container
267
268 // Mock deployment for new route
269 expectedRoute := config.Route{Domain: "new.example.com", Image: "postgres:14", HTTPS: true}
270 mockContainer := &runtime.Container{ID: "container3", Name: "gordon-new-example-com"}
271 mockManager.On("ListContainers").Return(map[string]*runtime.Container{}).Once() // Second call in deployContainerForRoute
272 mockManager.On("DeployContainer", mock.Anything, expectedRoute).Return(mockContainer, nil)
273
274 event := Event{
275 Type: ConfigReload,
276 }
277
278 err := handler.Handle(event)
279
280 assert.NoError(t, err)
281 mockManager.AssertExpectations(t)
282}
283
284func TestContainerEventHandler_Handle_ManualReload(t *testing.T) {
285 mockManager := &MockContainerManager{}
286 mockConfig := &config.Config{
287 Routes: map[string]string{
288 "app.example.com": "nginx:latest",
289 },
290 }
291
292 handler := &ContainerEventHandler{
293 manager: mockManager,
294 config: mockConfig,
295 }
296
297 // Mock GetContainer to return true (container exists)
298 mockContainer := &runtime.Container{ID: "container1", Name: "app-container"}
299 mockManager.On("GetContainer", "app.example.com").Return(mockContainer, true)
300
301 // Mock deployment
302 expectedRoute := config.Route{Domain: "app.example.com", Image: "nginx:latest", HTTPS: true}
303 mockManager.On("DeployContainer", mock.Anything, expectedRoute).Return(mockContainer, nil)
304
305 event := Event{
306 Type: ManualReload,
307 }
308
309 err := handler.Handle(event)
310
311 assert.NoError(t, err)
312 mockManager.AssertExpectations(t)
313}
314
315func TestContainerEventHandler_Handle_ContainerStop(t *testing.T) {
316 mockManager := &MockContainerManager{}
317
318 handler := &ContainerEventHandler{
319 manager: mockManager,
320 }
321
322 // Mock container stop
323 mockManager.On("StopContainer", mock.Anything, "container123").Return(nil)
324
325 event := Event{
326 Type: ContainerStop,
327 ContainerID: "container123",
328 }
329
330 err := handler.Handle(event)
331
332 assert.NoError(t, err)
333 mockManager.AssertExpectations(t)
334}
335
336func TestContainerEventHandler_Handle_ContainerStart(t *testing.T) {
337 handler := &ContainerEventHandler{}
338
339 event := Event{
340 Type: ContainerStart,
341 ContainerID: "container123",
342 }
343
344 err := handler.Handle(event)
345
346 // Should return error as start is not implemented
347 assert.Error(t, err)
348 assert.Contains(t, err.Error(), "container start not implemented")
349}
350
351func TestContainerEventHandler_Handle_UnsupportedEvent(t *testing.T) {
352 handler := &ContainerEventHandler{}
353
354 event := Event{
355 Type: ImageDeleted, // Unsupported event type
356 }
357
358 err := handler.Handle(event)
359
360 assert.Error(t, err)
361 assert.Contains(t, err.Error(), "unsupported event type")
362}
363
364func TestAutoRouteHandler_CanHandle(t *testing.T) {
365 handler := &AutoRouteHandler{}
366
367 testCases := []struct {
368 eventType EventType
369 expected bool
370 }{
371 {ImagePushed, true},
372 {ImageDeleted, false},
373 {ConfigReload, false},
374 {ManualReload, false},
375 {ContainerStop, false},
376 {ContainerStart, false},
377 {ContainerHealthCheck, false},
378 }
379
380 for _, tc := range testCases {
381 t.Run(string(tc.eventType), func(t *testing.T) {
382 result := handler.CanHandle(tc.eventType)
383 assert.Equal(t, tc.expected, result)
384 })
385 }
386}
387
388func TestAutoRouteHandler_Handle_AutoRouteEnabled(t *testing.T) {
389 mockManager := &MockContainerManager{}
390
391 // Create a temporary config file for testing
392 tempConfigFile := "/tmp/test-gordon-config.toml"
393 configContent := `[server]
394registry_domain = "registry.example.com"
395
396[auto_route]
397enabled = true
398
399[routes]
400`
401 err := os.WriteFile(tempConfigFile, []byte(configContent), 0644)
402 require.NoError(t, err)
403 defer os.Remove(tempConfigFile)
404
405 // Load config from temp file to set viper config file path
406 viper.SetConfigFile(tempConfigFile)
407 err = viper.ReadInConfig()
408 require.NoError(t, err)
409
410 mockConfig := &config.Config{
411 AutoRoute: config.AutoRouteConfig{
412 Enabled: true,
413 },
414 Routes: make(map[string]string),
415 Server: config.ServerConfig{
416 RegistryDomain: "registry.example.com",
417 },
418 }
419
420 handler := &AutoRouteHandler{
421 config: mockConfig,
422 manager: mockManager,
423 }
424
425 payload := ImagePushedPayload{
426 Name: "myapp.example.com",
427 Reference: "latest",
428 }
429
430 event := Event{
431 Type: ImagePushed,
432 Data: payload,
433 }
434
435 err = handler.Handle(event)
436
437 assert.NoError(t, err)
438 // Check that route was added to config
439 assert.Equal(t, "registry.example.com/myapp.example.com:latest", mockConfig.Routes["myapp.example.com"])
440 mockManager.AssertExpectations(t)
441}
442
443func TestAutoRouteHandler_Handle_AutoRouteDisabled(t *testing.T) {
444 mockManager := &MockContainerManager{}
445 mockConfig := &config.Config{
446 AutoRoute: config.AutoRouteConfig{
447 Enabled: false,
448 },
449 Routes: make(map[string]string),
450 }
451
452 handler := &AutoRouteHandler{
453 config: mockConfig,
454 manager: mockManager,
455 }
456
457 payload := ImagePushedPayload{
458 Name: "myapp.example.com",
459 Reference: "latest",
460 }
461
462 event := Event{
463 Type: ImagePushed,
464 Data: payload,
465 }
466
467 err := handler.Handle(event)
468
469 // Should succeed but not create route
470 assert.NoError(t, err)
471 assert.Empty(t, mockConfig.Routes)
472 mockManager.AssertExpectations(t) // No calls expected
473}
474
475func TestAutoRouteHandler_Handle_RouteAlreadyExists(t *testing.T) {
476 mockManager := &MockContainerManager{}
477 mockConfig := &config.Config{
478 AutoRoute: config.AutoRouteConfig{
479 Enabled: true,
480 },
481 Routes: map[string]string{
482 "myapp.example.com": "existing:image",
483 },
484 }
485
486 handler := &AutoRouteHandler{
487 config: mockConfig,
488 manager: mockManager,
489 }
490
491 payload := ImagePushedPayload{
492 Name: "myapp.example.com",
493 Reference: "latest",
494 }
495
496 event := Event{
497 Type: ImagePushed,
498 Data: payload,
499 }
500
501 err := handler.Handle(event)
502
503 // Should succeed but not modify existing route
504 assert.NoError(t, err)
505 assert.Equal(t, "existing:image", mockConfig.Routes["myapp.example.com"])
506 mockManager.AssertExpectations(t) // No calls expected
507}
508
509func TestAutoRouteHandler_Handle_InvalidImageName(t *testing.T) {
510 mockManager := &MockContainerManager{}
511 mockConfig := &config.Config{
512 AutoRoute: config.AutoRouteConfig{
513 Enabled: true,
514 },
515 Routes: make(map[string]string),
516 }
517
518 handler := &AutoRouteHandler{
519 config: mockConfig,
520 manager: mockManager,
521 }
522
523 payload := ImagePushedPayload{
524 Name: "nginx", // No domain extractable
525 Reference: "latest",
526 }
527
528 event := Event{
529 Type: ImagePushed,
530 Data: payload,
531 }
532
533 err := handler.Handle(event)
534
535 // Should succeed but not create route
536 assert.NoError(t, err)
537 assert.Empty(t, mockConfig.Routes)
538 mockManager.AssertExpectations(t) // No calls expected
539}
540
541func TestNewContainerEventHandler(t *testing.T) {
542 mockManager := &MockContainerManager{}
543 mockConfig := &config.Config{}
544
545 handler := NewContainerEventHandler(mockManager, mockConfig)
546
547 require.NotNil(t, handler)
548 assert.Equal(t, mockManager, handler.manager)
549 assert.Equal(t, mockConfig, handler.config)
550}
551
552func TestNewAutoRouteHandler(t *testing.T) {
553 mockManager := &MockContainerManager{}
554 mockConfig := &config.Config{}
555
556 handler := NewAutoRouteHandler(mockConfig, mockManager)
557
558 require.NotNil(t, handler)
559 assert.Equal(t, mockManager, handler.manager)
560 assert.Equal(t, mockConfig, handler.config)
561}
562
563func TestVersionHandler_CanHandle(t *testing.T) {
564 handler := &VersionHandler{}
565
566 testCases := []struct {
567 eventType EventType
568 expected bool
569 }{
570 {ImagePushed, true},
571 {ImageDeleted, false},
572 {ConfigReload, false},
573 {ManualReload, false},
574 {ContainerStop, false},
575 {ContainerStart, false},
576 {ContainerHealthCheck, false},
577 }
578
579 for _, tc := range testCases {
580 t.Run(string(tc.eventType), func(t *testing.T) {
581 result := handler.CanHandle(tc.eventType)
582 assert.Equal(t, tc.expected, result)
583 })
584 }
585}
586
587func TestVersionHandler_Handle_VersionedDeployment(t *testing.T) {
588 // Create a temporary config file for testing
589 tempConfigFile := "/tmp/test-gordon-version-config.toml"
590 configContent := `[server]
591registry_domain = "registry.example.com"
592
593[routes]
594"app.example.com" = "myapp:v1.0"
595`
596 err := os.WriteFile(tempConfigFile, []byte(configContent), 0644)
597 require.NoError(t, err)
598 defer os.Remove(tempConfigFile)
599
600 // Load config from temp file to set viper config file path
601 viper.SetConfigFile(tempConfigFile)
602 err = viper.ReadInConfig()
603 require.NoError(t, err)
604
605 mockManager := &MockContainerManager{}
606 mockConfig := &config.Config{
607 Routes: map[string]string{
608 "app.example.com": "myapp:v2.0", // Match the payload reference so it finds routes
609 },
610 }
611
612 handler := &VersionHandler{
613 manager: mockManager,
614 config: mockConfig,
615 }
616
617 // Mock deployment
618 expectedRoute := config.Route{Domain: "app.example.com", Image: "myapp:v2.0", HTTPS: true}
619 mockContainer := &runtime.Container{ID: "container1", Name: "app-container"}
620 mockManager.On("DeployContainer", mock.Anything, expectedRoute).Return(mockContainer, nil)
621
622 payload := ImagePushedPayload{
623 Name: "myapp",
624 Reference: "v2.0",
625 Annotations: map[string]string{
626 "version": "v2.0",
627 },
628 }
629
630 event := Event{
631 Type: ImagePushed,
632 Data: payload,
633 }
634
635 err = handler.Handle(event)
636
637 assert.NoError(t, err)
638 // Check that route was updated to new version
639 assert.Equal(t, "myapp:v2.0", mockConfig.Routes["app.example.com"])
640 mockManager.AssertExpectations(t)
641}
642
643func TestVersionHandler_Handle_NonVersionedDeployment(t *testing.T) {
644 mockManager := &MockContainerManager{}
645 mockConfig := &config.Config{
646 Routes: make(map[string]string),
647 }
648
649 handler := &VersionHandler{
650 manager: mockManager,
651 config: mockConfig,
652 }
653
654 payload := ImagePushedPayload{
655 Name: "myapp",
656 Reference: "latest",
657 Annotations: map[string]string{}, // No version annotations
658 }
659
660 event := Event{
661 Type: ImagePushed,
662 Data: payload,
663 }
664
665 err := handler.Handle(event)
666
667 // Should succeed but not process anything
668 assert.NoError(t, err)
669 assert.Empty(t, mockConfig.Routes)
670 mockManager.AssertExpectations(t) // No calls expected
671}
672
673func TestVersionHandler_Handle_NoMatchingRoutes(t *testing.T) {
674 mockManager := &MockContainerManager{}
675 mockConfig := &config.Config{
676 Routes: map[string]string{
677 "app.example.com": "nginx:latest", // Different image
678 },
679 }
680
681 handler := &VersionHandler{
682 manager: mockManager,
683 config: mockConfig,
684 }
685
686 payload := ImagePushedPayload{
687 Name: "myapp",
688 Reference: "v2.0",
689 Annotations: map[string]string{
690 "version": "v2.0",
691 },
692 }
693
694 event := Event{
695 Type: ImagePushed,
696 Data: payload,
697 }
698
699 err := handler.Handle(event)
700
701 // Should succeed but not deploy anything
702 assert.NoError(t, err)
703 assert.Equal(t, "nginx:latest", mockConfig.Routes["app.example.com"])
704 mockManager.AssertExpectations(t) // No calls expected
705}
706
707func TestVersionHandler_Handle_InvalidPayload(t *testing.T) {
708 handler := &VersionHandler{}
709
710 event := Event{
711 Type: ImagePushed,
712 Data: "invalid payload",
713 }
714
715 err := handler.Handle(event)
716
717 assert.Error(t, err)
718 assert.Contains(t, err.Error(), "invalid payload type")
719}
720
721func TestVersionHandler_Handle_WrongEventType(t *testing.T) {
722 handler := &VersionHandler{}
723
724 event := Event{
725 Type: ConfigReload,
726 }
727
728 err := handler.Handle(event)
729
730 assert.NoError(t, err) // Should succeed but do nothing
731}
732
733func TestVersionHandler_Handle_DeploymentFailure(t *testing.T) {
734 // Create a temporary config file for testing
735 tempConfigFile := "/tmp/test-gordon-version-fail-config.toml"
736 configContent := `[server]
737registry_domain = "registry.example.com"
738
739[routes]
740"app.example.com" = "myapp:v1.0"
741`
742 err := os.WriteFile(tempConfigFile, []byte(configContent), 0644)
743 require.NoError(t, err)
744 defer os.Remove(tempConfigFile)
745
746 // Load config from temp file to set viper config file path
747 viper.SetConfigFile(tempConfigFile)
748 err = viper.ReadInConfig()
749 require.NoError(t, err)
750
751 mockManager := &MockContainerManager{}
752 mockConfig := &config.Config{
753 Routes: map[string]string{
754 "app.example.com": "myapp:v2.0", // Match the payload reference so it finds routes
755 },
756 }
757
758 handler := &VersionHandler{
759 manager: mockManager,
760 config: mockConfig,
761 }
762
763 // Mock deployment failure
764 expectedRoute := config.Route{Domain: "app.example.com", Image: "myapp:v2.0", HTTPS: true}
765 mockManager.On("DeployContainer", mock.Anything, expectedRoute).Return(nil, fmt.Errorf("deployment failed"))
766
767 payload := ImagePushedPayload{
768 Name: "myapp",
769 Reference: "v2.0",
770 Annotations: map[string]string{
771 "version": "v2.0",
772 },
773 }
774
775 event := Event{
776 Type: ImagePushed,
777 Data: payload,
778 }
779
780 err = handler.Handle(event)
781
782 assert.Error(t, err)
783 assert.Contains(t, err.Error(), "version deployment completed with")
784 mockManager.AssertExpectations(t)
785}
786
787func TestVersionHandler_Handle_FullImageReference(t *testing.T) {
788 // Create a temporary config file for testing
789 tempConfigFile := "/tmp/test-gordon-version-full-config.toml"
790 configContent := `[server]
791registry_domain = "registry.example.com"
792
793[routes]
794"app.example.com" = "myapp:v1.0"
795`
796 err := os.WriteFile(tempConfigFile, []byte(configContent), 0644)
797 require.NoError(t, err)
798 defer os.Remove(tempConfigFile)
799
800 // Load config from temp file to set viper config file path
801 viper.SetConfigFile(tempConfigFile)
802 err = viper.ReadInConfig()
803 require.NoError(t, err)
804
805 mockManager := &MockContainerManager{}
806 mockConfig := &config.Config{
807 Routes: map[string]string{
808 "app.example.com": "myapp:v2.0", // Match the payload reference so it finds routes
809 },
810 }
811
812 handler := &VersionHandler{
813 manager: mockManager,
814 config: mockConfig,
815 }
816
817 // Mock deployment with full image reference
818 expectedRoute := config.Route{Domain: "app.example.com", Image: "registry.example.com/myapp:v2.0", HTTPS: true}
819 mockContainer := &runtime.Container{ID: "container1", Name: "app-container"}
820 mockManager.On("DeployContainer", mock.Anything, expectedRoute).Return(mockContainer, nil)
821
822 payload := ImagePushedPayload{
823 Name: "myapp",
824 Reference: "v2.0",
825 Annotations: map[string]string{
826 "version": "registry.example.com/myapp:v2.0", // Full image reference
827 },
828 }
829
830 event := Event{
831 Type: ImagePushed,
832 Data: payload,
833 }
834
835 err = handler.Handle(event)
836
837 assert.NoError(t, err)
838 // Check that route was updated to full image reference
839 assert.Equal(t, "registry.example.com/myapp:v2.0", mockConfig.Routes["app.example.com"])
840 mockManager.AssertExpectations(t)
841}
842
843func TestNewVersionHandler(t *testing.T) {
844 mockManager := &MockContainerManager{}
845 mockConfig := &config.Config{}
846
847 handler := NewVersionHandler(mockManager, mockConfig)
848
849 require.NotNil(t, handler)
850 assert.Equal(t, mockManager, handler.manager)
851 assert.Equal(t, mockConfig, handler.config)
852}