v2.4.1 7c3b292

service_test.go RawHistoryFolder

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	// Mock external routes (empty - no match)
59	configSvc.EXPECT().GetExternalRoutes().Return(map[string]string{})
60	containerSvc.EXPECT().Get(mock.Anything, "app.example.com").Return(nil, false)
61
62	result, err := svc.GetTarget(ctx, "app.example.com")
63
64	assert.ErrorIs(t, err, domain.ErrNoTargetAvailable)
65	assert.Nil(t, result)
66}
67
68func TestService_GetTarget_ExternalRoute(t *testing.T) {
69	runtime := outmocks.NewMockContainerRuntime(t)
70	containerSvc := inmocks.NewMockContainerService(t)
71	configSvc := inmocks.NewMockConfigService(t)
72
73	svc := NewService(runtime, containerSvc, configSvc, Config{})
74	ctx := testContext()
75
76	// Mock external routes
77	configSvc.EXPECT().GetExternalRoutes().Return(map[string]string{
78		"reg.example.com": "localhost:5000",
79	})
80
81	result, err := svc.GetTarget(ctx, "reg.example.com")
82
83	assert.NoError(t, err)
84	assert.Equal(t, "localhost", result.Host)
85	assert.Equal(t, 5000, result.Port)
86	assert.Equal(t, "", result.ContainerID)
87	assert.Equal(t, "http", result.Scheme)
88}
89
90func TestService_GetTarget_ExternalRoute_Cached(t *testing.T) {
91	runtime := outmocks.NewMockContainerRuntime(t)
92	containerSvc := inmocks.NewMockContainerService(t)
93	configSvc := inmocks.NewMockConfigService(t)
94
95	svc := NewService(runtime, containerSvc, configSvc, Config{})
96	ctx := testContext()
97
98	// First call - should resolve external route
99	configSvc.EXPECT().GetExternalRoutes().Return(map[string]string{
100		"reg.example.com": "localhost:5000",
101	}).Once()
102
103	result1, err := svc.GetTarget(ctx, "reg.example.com")
104	assert.NoError(t, err)
105	assert.Equal(t, "localhost", result1.Host)
106
107	// Second call - should return from cache (no mock call needed)
108	result2, err := svc.GetTarget(ctx, "reg.example.com")
109	assert.NoError(t, err)
110	assert.Equal(t, result1, result2)
111}
112
113func TestService_GetTarget_ExternalRoute_InvalidTarget(t *testing.T) {
114	runtime := outmocks.NewMockContainerRuntime(t)
115	containerSvc := inmocks.NewMockContainerService(t)
116	configSvc := inmocks.NewMockConfigService(t)
117
118	svc := NewService(runtime, containerSvc, configSvc, Config{})
119	ctx := testContext()
120
121	// Mock external routes with invalid format (missing port)
122	configSvc.EXPECT().GetExternalRoutes().Return(map[string]string{
123		"invalid.example.com": "not-valid-format",
124	})
125
126	result, err := svc.GetTarget(ctx, "invalid.example.com")
127
128	assert.Error(t, err)
129	assert.Nil(t, result)
130}
131
132func TestService_GetTarget_ExternalRoute_InvalidPort(t *testing.T) {
133	runtime := outmocks.NewMockContainerRuntime(t)
134	containerSvc := inmocks.NewMockContainerService(t)
135	configSvc := inmocks.NewMockConfigService(t)
136
137	svc := NewService(runtime, containerSvc, configSvc, Config{})
138	ctx := testContext()
139
140	// Mock external routes with invalid port
141	configSvc.EXPECT().GetExternalRoutes().Return(map[string]string{
142		"invalid.example.com": "localhost:abc",
143	})
144
145	result, err := svc.GetTarget(ctx, "invalid.example.com")
146
147	assert.Error(t, err)
148	assert.Nil(t, result)
149}
150
151func TestService_RegisterTarget(t *testing.T) {
152	runtime := outmocks.NewMockContainerRuntime(t)
153	containerSvc := inmocks.NewMockContainerService(t)
154	configSvc := inmocks.NewMockConfigService(t)
155
156	svc := NewService(runtime, containerSvc, configSvc, Config{})
157	ctx := testContext()
158
159	target := &domain.ProxyTarget{
160		Host:        "192.168.1.100",
161		Port:        8080,
162		ContainerID: "container-123",
163		Scheme:      "http",
164	}
165
166	err := svc.RegisterTarget(ctx, "app.example.com", target)
167
168	assert.NoError(t, err)
169
170	// Verify target is cached
171	svc.mu.RLock()
172	cached := svc.targets["app.example.com"]
173	svc.mu.RUnlock()
174
175	assert.Equal(t, target, cached)
176}
177
178func TestService_UnregisterTarget(t *testing.T) {
179	runtime := outmocks.NewMockContainerRuntime(t)
180	containerSvc := inmocks.NewMockContainerService(t)
181	configSvc := inmocks.NewMockConfigService(t)
182
183	svc := NewService(runtime, containerSvc, configSvc, Config{})
184	ctx := testContext()
185
186	// Pre-populate
187	svc.targets["app.example.com"] = &domain.ProxyTarget{
188		Host: "192.168.1.100",
189		Port: 8080,
190	}
191
192	err := svc.UnregisterTarget(ctx, "app.example.com")
193
194	assert.NoError(t, err)
195
196	// Verify target is removed
197	svc.mu.RLock()
198	_, exists := svc.targets["app.example.com"]
199	svc.mu.RUnlock()
200
201	assert.False(t, exists)
202}
203
204func TestService_RefreshTargets(t *testing.T) {
205	runtime := outmocks.NewMockContainerRuntime(t)
206	containerSvc := inmocks.NewMockContainerService(t)
207	configSvc := inmocks.NewMockConfigService(t)
208
209	svc := NewService(runtime, containerSvc, configSvc, Config{})
210	ctx := testContext()
211
212	// Pre-populate with some targets
213	svc.targets["app1.example.com"] = &domain.ProxyTarget{Host: "192.168.1.100"}
214	svc.targets["app2.example.com"] = &domain.ProxyTarget{Host: "192.168.1.101"}
215
216	err := svc.RefreshTargets(ctx)
217
218	assert.NoError(t, err)
219
220	// Verify all targets are cleared
221	svc.mu.RLock()
222	count := len(svc.targets)
223	svc.mu.RUnlock()
224
225	assert.Equal(t, 0, count)
226}
227
228func TestService_UpdateConfig(t *testing.T) {
229	runtime := outmocks.NewMockContainerRuntime(t)
230	containerSvc := inmocks.NewMockContainerService(t)
231	configSvc := inmocks.NewMockConfigService(t)
232
233	svc := NewService(runtime, containerSvc, configSvc, Config{
234		RegistryDomain: "old.registry.com",
235		RegistryPort:   5000,
236	})
237
238	newConfig := Config{
239		RegistryDomain: "new.registry.com",
240		RegistryPort:   5001,
241	}
242
243	svc.UpdateConfig(newConfig)
244
245	assert.Equal(t, "new.registry.com", svc.config.RegistryDomain)
246	assert.Equal(t, 5001, svc.config.RegistryPort)
247}
248
249func TestService_isRunningInContainer(t *testing.T) {
250	runtime := outmocks.NewMockContainerRuntime(t)
251	containerSvc := inmocks.NewMockContainerService(t)
252	configSvc := inmocks.NewMockConfigService(t)
253
254	svc := NewService(runtime, containerSvc, configSvc, Config{})
255
256	// This test just verifies the method doesn't panic
257	// The actual result depends on the environment
258	result := svc.isRunningInContainer()
259	assert.IsType(t, true, result) // Just verify it returns a bool
260}
261
262func TestService_InvalidateTarget(t *testing.T) {
263	runtime := outmocks.NewMockContainerRuntime(t)
264	containerSvc := inmocks.NewMockContainerService(t)
265	configSvc := inmocks.NewMockConfigService(t)
266
267	svc := NewService(runtime, containerSvc, configSvc, Config{})
268	ctx := testContext()
269
270	// Pre-populate cache with target
271	svc.targets["app.example.com"] = &domain.ProxyTarget{
272		Host:        "192.168.1.100",
273		Port:        8080,
274		ContainerID: "old-container",
275	}
276
277	// Invalidate the target
278	svc.InvalidateTarget(ctx, "app.example.com")
279
280	// Verify target is removed from cache
281	svc.mu.RLock()
282	_, exists := svc.targets["app.example.com"]
283	svc.mu.RUnlock()
284
285	assert.False(t, exists, "target should be removed from cache after invalidation")
286}
287
288func TestContainerDeployedHandler_CanHandle(t *testing.T) {
289	handler := NewContainerDeployedHandler(testContext(), nil)
290
291	assert.True(t, handler.CanHandle(domain.EventContainerDeployed))
292	assert.False(t, handler.CanHandle(domain.EventImagePushed))
293	assert.False(t, handler.CanHandle(domain.EventConfigReload))
294}
295
296func TestContainerDeployedHandler_Handle_InvalidatesCache(t *testing.T) {
297	runtime := outmocks.NewMockContainerRuntime(t)
298	containerSvc := inmocks.NewMockContainerService(t)
299	configSvc := inmocks.NewMockConfigService(t)
300
301	svc := NewService(runtime, containerSvc, configSvc, Config{})
302	ctx := testContext()
303
304	// Pre-populate cache
305	svc.targets["app.example.com"] = &domain.ProxyTarget{
306		Host:        "192.168.1.100",
307		Port:        8080,
308		ContainerID: "old-container",
309	}
310
311	// Create handler with service as invalidator
312	handler := NewContainerDeployedHandler(ctx, svc)
313
314	// Simulate container deployed event
315	event := domain.Event{
316		ID:    "event-123",
317		Type:  domain.EventContainerDeployed,
318		Route: "app.example.com",
319		Data: &domain.ContainerEventPayload{
320			ContainerID: "new-container",
321			Domain:      "app.example.com",
322		},
323	}
324
325	err := handler.Handle(event)
326
327	assert.NoError(t, err)
328
329	// Verify target was invalidated
330	svc.mu.RLock()
331	_, exists := svc.targets["app.example.com"]
332	svc.mu.RUnlock()
333
334	assert.False(t, exists, "cache should be invalidated after container deployed event")
335}
336
337func TestContainerDeployedHandler_Handle_NoDomain(t *testing.T) {
338	runtime := outmocks.NewMockContainerRuntime(t)
339	containerSvc := inmocks.NewMockContainerService(t)
340	configSvc := inmocks.NewMockConfigService(t)
341
342	svc := NewService(runtime, containerSvc, configSvc, Config{})
343	ctx := testContext()
344
345	handler := NewContainerDeployedHandler(ctx, svc)
346
347	// Event with no domain
348	event := domain.Event{
349		ID:   "event-123",
350		Type: domain.EventContainerDeployed,
351		Data: &domain.ContainerEventPayload{
352			ContainerID: "new-container",
353		},
354	}
355
356	// Should not error, just skip
357	err := handler.Handle(event)
358	assert.NoError(t, err)
359}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 7

Tags 106

↑↓ moveenter openesc close