1package proxy
2
3import (
4 "context"
5 "testing"
6
7 "github.com/bnema/zerowrap"
8 "github.com/stretchr/testify/assert"
9 "github.com/stretchr/testify/mock"
10
11 inmocks "gordon/internal/boundaries/in/mocks"
12 outmocks "gordon/internal/boundaries/out/mocks"
13 "gordon/internal/domain"
14)
15
16func testContext() context.Context {
17 return zerowrap.WithCtx(context.Background(), zerowrap.Default())
18}
19
20func TestService_GetTarget_FromCache(t *testing.T) {
21 runtime := outmocks.NewMockContainerRuntime(t)
22 containerSvc := inmocks.NewMockContainerService(t)
23 configSvc := inmocks.NewMockConfigService(t)
24
25 config := Config{
26 RegistryDomain: "registry.example.com",
27 RegistryPort: 5000,
28 }
29 svc := NewService(runtime, containerSvc, configSvc, config)
30 ctx := testContext()
31
32 // Pre-populate cache
33 cachedTarget := &domain.ProxyTarget{
34 Host: "192.168.1.100",
35 Port: 8080,
36 ContainerID: "container-123",
37 Scheme: "http",
38 }
39 svc.targets["app.example.com"] = cachedTarget
40
41 // No mock calls expected - should return from cache
42
43 result, err := svc.GetTarget(ctx, "app.example.com")
44
45 assert.NoError(t, err)
46 assert.Equal(t, cachedTarget, result)
47}
48
49func TestService_GetTarget_ContainerNotFound(t *testing.T) {
50 runtime := outmocks.NewMockContainerRuntime(t)
51 containerSvc := inmocks.NewMockContainerService(t)
52 configSvc := inmocks.NewMockConfigService(t)
53
54 config := Config{}
55 svc := NewService(runtime, containerSvc, configSvc, config)
56 ctx := testContext()
57
58 containerSvc.EXPECT().Get(mock.Anything, "app.example.com").Return(nil, false)
59
60 result, err := svc.GetTarget(ctx, "app.example.com")
61
62 assert.ErrorIs(t, err, domain.ErrNoTargetAvailable)
63 assert.Nil(t, result)
64}
65
66func TestService_RegisterTarget(t *testing.T) {
67 runtime := outmocks.NewMockContainerRuntime(t)
68 containerSvc := inmocks.NewMockContainerService(t)
69 configSvc := inmocks.NewMockConfigService(t)
70
71 svc := NewService(runtime, containerSvc, configSvc, Config{})
72 ctx := testContext()
73
74 target := &domain.ProxyTarget{
75 Host: "192.168.1.100",
76 Port: 8080,
77 ContainerID: "container-123",
78 Scheme: "http",
79 }
80
81 err := svc.RegisterTarget(ctx, "app.example.com", target)
82
83 assert.NoError(t, err)
84
85 // Verify target is cached
86 svc.mu.RLock()
87 cached := svc.targets["app.example.com"]
88 svc.mu.RUnlock()
89
90 assert.Equal(t, target, cached)
91}
92
93func TestService_UnregisterTarget(t *testing.T) {
94 runtime := outmocks.NewMockContainerRuntime(t)
95 containerSvc := inmocks.NewMockContainerService(t)
96 configSvc := inmocks.NewMockConfigService(t)
97
98 svc := NewService(runtime, containerSvc, configSvc, Config{})
99 ctx := testContext()
100
101 // Pre-populate
102 svc.targets["app.example.com"] = &domain.ProxyTarget{
103 Host: "192.168.1.100",
104 Port: 8080,
105 }
106
107 err := svc.UnregisterTarget(ctx, "app.example.com")
108
109 assert.NoError(t, err)
110
111 // Verify target is removed
112 svc.mu.RLock()
113 _, exists := svc.targets["app.example.com"]
114 svc.mu.RUnlock()
115
116 assert.False(t, exists)
117}
118
119func TestService_RefreshTargets(t *testing.T) {
120 runtime := outmocks.NewMockContainerRuntime(t)
121 containerSvc := inmocks.NewMockContainerService(t)
122 configSvc := inmocks.NewMockConfigService(t)
123
124 svc := NewService(runtime, containerSvc, configSvc, Config{})
125 ctx := testContext()
126
127 // Pre-populate with some targets
128 svc.targets["app1.example.com"] = &domain.ProxyTarget{Host: "192.168.1.100"}
129 svc.targets["app2.example.com"] = &domain.ProxyTarget{Host: "192.168.1.101"}
130
131 err := svc.RefreshTargets(ctx)
132
133 assert.NoError(t, err)
134
135 // Verify all targets are cleared
136 svc.mu.RLock()
137 count := len(svc.targets)
138 svc.mu.RUnlock()
139
140 assert.Equal(t, 0, count)
141}
142
143func TestService_UpdateConfig(t *testing.T) {
144 runtime := outmocks.NewMockContainerRuntime(t)
145 containerSvc := inmocks.NewMockContainerService(t)
146 configSvc := inmocks.NewMockConfigService(t)
147
148 svc := NewService(runtime, containerSvc, configSvc, Config{
149 RegistryDomain: "old.registry.com",
150 RegistryPort: 5000,
151 })
152
153 newConfig := Config{
154 RegistryDomain: "new.registry.com",
155 RegistryPort: 5001,
156 }
157
158 svc.UpdateConfig(newConfig)
159
160 assert.Equal(t, "new.registry.com", svc.config.RegistryDomain)
161 assert.Equal(t, 5001, svc.config.RegistryPort)
162}
163
164func TestService_isRunningInContainer(t *testing.T) {
165 runtime := outmocks.NewMockContainerRuntime(t)
166 containerSvc := inmocks.NewMockContainerService(t)
167 configSvc := inmocks.NewMockConfigService(t)
168
169 svc := NewService(runtime, containerSvc, configSvc, Config{})
170
171 // This test just verifies the method doesn't panic
172 // The actual result depends on the environment
173 result := svc.isRunningInContainer()
174 assert.IsType(t, true, result) // Just verify it returns a bool
175}
176
177func TestService_InvalidateTarget(t *testing.T) {
178 runtime := outmocks.NewMockContainerRuntime(t)
179 containerSvc := inmocks.NewMockContainerService(t)
180 configSvc := inmocks.NewMockConfigService(t)
181
182 svc := NewService(runtime, containerSvc, configSvc, Config{})
183 ctx := testContext()
184
185 // Pre-populate cache with target
186 svc.targets["app.example.com"] = &domain.ProxyTarget{
187 Host: "192.168.1.100",
188 Port: 8080,
189 ContainerID: "old-container",
190 }
191
192 // Invalidate the target
193 svc.InvalidateTarget(ctx, "app.example.com")
194
195 // Verify target is removed from cache
196 svc.mu.RLock()
197 _, exists := svc.targets["app.example.com"]
198 svc.mu.RUnlock()
199
200 assert.False(t, exists, "target should be removed from cache after invalidation")
201}
202
203func TestContainerDeployedHandler_CanHandle(t *testing.T) {
204 handler := NewContainerDeployedHandler(testContext(), nil)
205
206 assert.True(t, handler.CanHandle(domain.EventContainerDeployed))
207 assert.False(t, handler.CanHandle(domain.EventImagePushed))
208 assert.False(t, handler.CanHandle(domain.EventConfigReload))
209}
210
211func TestContainerDeployedHandler_Handle_InvalidatesCache(t *testing.T) {
212 runtime := outmocks.NewMockContainerRuntime(t)
213 containerSvc := inmocks.NewMockContainerService(t)
214 configSvc := inmocks.NewMockConfigService(t)
215
216 svc := NewService(runtime, containerSvc, configSvc, Config{})
217 ctx := testContext()
218
219 // Pre-populate cache
220 svc.targets["app.example.com"] = &domain.ProxyTarget{
221 Host: "192.168.1.100",
222 Port: 8080,
223 ContainerID: "old-container",
224 }
225
226 // Create handler with service as invalidator
227 handler := NewContainerDeployedHandler(ctx, svc)
228
229 // Simulate container deployed event
230 event := domain.Event{
231 ID: "event-123",
232 Type: domain.EventContainerDeployed,
233 Route: "app.example.com",
234 Data: &domain.ContainerEventPayload{
235 ContainerID: "new-container",
236 Domain: "app.example.com",
237 },
238 }
239
240 err := handler.Handle(event)
241
242 assert.NoError(t, err)
243
244 // Verify target was invalidated
245 svc.mu.RLock()
246 _, exists := svc.targets["app.example.com"]
247 svc.mu.RUnlock()
248
249 assert.False(t, exists, "cache should be invalidated after container deployed event")
250}
251
252func TestContainerDeployedHandler_Handle_NoDomain(t *testing.T) {
253 runtime := outmocks.NewMockContainerRuntime(t)
254 containerSvc := inmocks.NewMockContainerService(t)
255 configSvc := inmocks.NewMockConfigService(t)
256
257 svc := NewService(runtime, containerSvc, configSvc, Config{})
258 ctx := testContext()
259
260 handler := NewContainerDeployedHandler(ctx, svc)
261
262 // Event with no domain
263 event := domain.Event{
264 ID: "event-123",
265 Type: domain.EventContainerDeployed,
266 Data: &domain.ContainerEventPayload{
267 ContainerID: "new-container",
268 },
269 }
270
271 // Should not error, just skip
272 err := handler.Handle(event)
273 assert.NoError(t, err)
274}