2.0.0 4803e67

manager_operations_test.go RawHistoryFolder

1package container
2
3import (
4	"errors"
5	"os"
6	"testing"
7
8	"github.com/stretchr/testify/assert"
9	"github.com/stretchr/testify/require"
10	"go.uber.org/mock/gomock"
11
12	"gordon/internal/config"
13	"gordon/internal/testutils"
14	"gordon/internal/testutils/mocks"
15	"gordon/pkg/runtime"
16)
17
18func TestManager_SyncContainers(t *testing.T) {
19	ctrl := gomock.NewController(t)
20	defer ctrl.Finish()
21
22	ctx := testutils.TestContext(t)
23	mockRuntime := mocks.NewMockRuntime(ctrl)
24
25	cfg := &config.Config{}
26	manager, err := NewManagerWithRuntime(cfg, mockRuntime)
27	require.NoError(t, err)
28
29	// Add some containers to internal state that might be stale
30	manager.containers["old.example.com"] = &runtime.Container{ID: "old-container"}
31
32	// Mock runtime returning current containers
33	currentContainers := []*runtime.Container{
34		{
35			ID:   "active-container-1",
36			Name: "gordon-app1.example.com",
37			Labels: map[string]string{
38				"gordon.managed": "true",
39				"gordon.domain":  "app1.example.com",
40			},
41		},
42		{
43			ID:   "active-container-2",
44			Name: "gordon-app2.example.com",
45			Labels: map[string]string{
46				"gordon.managed": "true",
47				"gordon.domain":  "app2.example.com",
48			},
49		},
50		{
51			ID:   "unmanaged-container",
52			Name: "some-other-container",
53			Labels: map[string]string{
54				"other.label": "value",
55			},
56		},
57	}
58
59	mockRuntime.EXPECT().ListContainers(ctx, false).Return(currentContainers, nil)
60
61	err = manager.SyncContainers(ctx)
62
63	assert.NoError(t, err)
64	assert.Len(t, manager.containers, 2)
65	assert.Contains(t, manager.containers, "app1.example.com")
66	assert.Contains(t, manager.containers, "app2.example.com")
67	assert.NotContains(t, manager.containers, "old.example.com")
68	assert.Equal(t, "active-container-1", manager.containers["app1.example.com"].ID)
69	assert.Equal(t, "active-container-2", manager.containers["app2.example.com"].ID)
70}
71
72func TestManager_SyncContainers_Error(t *testing.T) {
73	ctrl := gomock.NewController(t)
74	defer ctrl.Finish()
75
76	ctx := testutils.TestContext(t)
77	mockRuntime := mocks.NewMockRuntime(ctrl)
78
79	cfg := &config.Config{}
80	manager, err := NewManagerWithRuntime(cfg, mockRuntime)
81	require.NoError(t, err)
82
83	mockRuntime.EXPECT().ListContainers(ctx, false).Return(nil, errors.New("runtime error"))
84
85	err = manager.SyncContainers(ctx)
86
87	assert.Error(t, err)
88	assert.Contains(t, err.Error(), "failed to list containers")
89}
90
91func TestManager_StopContainer(t *testing.T) {
92	ctrl := gomock.NewController(t)
93	defer ctrl.Finish()
94
95	ctx := testutils.TestContext(t)
96	mockRuntime := mocks.NewMockRuntime(ctrl)
97
98	cfg := &config.Config{}
99	manager, err := NewManagerWithRuntime(cfg, mockRuntime)
100	require.NoError(t, err)
101
102	mockRuntime.EXPECT().StopContainer(ctx, "container123").Return(nil)
103
104	err = manager.StopContainer(ctx, "container123")
105
106	assert.NoError(t, err)
107}
108
109func TestManager_StopContainer_Error(t *testing.T) {
110	ctrl := gomock.NewController(t)
111	defer ctrl.Finish()
112
113	ctx := testutils.TestContext(t)
114	mockRuntime := mocks.NewMockRuntime(ctrl)
115
116	cfg := &config.Config{}
117	manager, err := NewManagerWithRuntime(cfg, mockRuntime)
118	require.NoError(t, err)
119
120	mockRuntime.EXPECT().StopContainer(ctx, "container123").Return(errors.New("stop failed"))
121
122	err = manager.StopContainer(ctx, "container123")
123
124	assert.Error(t, err)
125	assert.Contains(t, err.Error(), "failed to stop container")
126}
127
128func TestManager_StopContainerByDomain(t *testing.T) {
129	ctrl := gomock.NewController(t)
130	defer ctrl.Finish()
131
132	ctx := testutils.TestContext(t)
133	mockRuntime := mocks.NewMockRuntime(ctrl)
134
135	cfg := &config.Config{}
136	manager, err := NewManagerWithRuntime(cfg, mockRuntime)
137	require.NoError(t, err)
138
139	container := &runtime.Container{
140		ID: "container123",
141	}
142	manager.containers["test.example.com"] = container
143
144	mockRuntime.EXPECT().StopContainer(ctx, "container123").Return(nil)
145
146	err = manager.StopContainerByDomain(ctx, "test.example.com")
147
148	assert.NoError(t, err)
149}
150
151func TestManager_StopContainerByDomain_NotFound(t *testing.T) {
152	cfg := &config.Config{}
153	manager := &Manager{
154		config:     cfg,
155		containers: make(map[string]*runtime.Container),
156	}
157
158	ctx := testutils.TestContext(t)
159	err := manager.StopContainerByDomain(ctx, "nonexistent.example.com")
160
161	assert.Error(t, err)
162	assert.Contains(t, err.Error(), "no container found for domain")
163}
164
165func TestManager_RemoveContainer(t *testing.T) {
166	ctrl := gomock.NewController(t)
167	defer ctrl.Finish()
168
169	ctx := testutils.TestContext(t)
170	mockRuntime := mocks.NewMockRuntime(ctrl)
171
172	cfg := &config.Config{
173		Volumes: config.VolumeConfig{
174			Preserve: true, // Don't cleanup volumes
175		},
176		NetworkIsolation: config.NetworkIsolationConfig{
177			Enabled: false,
178		},
179	}
180
181	manager, err := NewManagerWithRuntime(cfg, mockRuntime)
182	require.NoError(t, err)
183
184	container := &runtime.Container{
185		ID: "container123",
186	}
187	manager.containers["test.example.com"] = container
188
189	mockRuntime.EXPECT().RemoveContainer(ctx, "container123", true).Return(nil)
190
191	err = manager.RemoveContainer(ctx, "container123", true)
192
193	assert.NoError(t, err)
194	assert.NotContains(t, manager.containers, "test.example.com")
195}
196
197func TestManager_RemoveContainer_WithVolumeCleanup(t *testing.T) {
198	ctrl := gomock.NewController(t)
199	defer ctrl.Finish()
200
201	ctx := testutils.TestContext(t)
202	mockRuntime := mocks.NewMockRuntime(ctrl)
203
204	cfg := &config.Config{
205		Volumes: config.VolumeConfig{
206			Preserve: false, // Should cleanup volumes
207			Prefix:   "gordon",
208		},
209		NetworkIsolation: config.NetworkIsolationConfig{
210			Enabled: false,
211		},
212		Routes: map[string]string{
213			"test.example.com": "nginx:latest",
214		},
215	}
216
217	manager, err := NewManagerWithRuntime(cfg, mockRuntime)
218	require.NoError(t, err)
219
220	container := &runtime.Container{
221		ID: "container123",
222	}
223	manager.containers["test.example.com"] = container
224
225	mockRuntime.EXPECT().RemoveContainer(ctx, "container123", true).Return(nil)
226
227	// Expect volume cleanup
228	mockRuntime.EXPECT().InspectImageVolumes(ctx, "nginx:latest").Return([]string{"/var/log/nginx"}, nil)
229	varLogNginxVolume := generateVolumeName("gordon", "test.example.com", "/var/log/nginx")
230	mockRuntime.EXPECT().VolumeExists(ctx, varLogNginxVolume).Return(true, nil)
231	mockRuntime.EXPECT().RemoveVolume(ctx, varLogNginxVolume, true).Return(nil)
232
233	err = manager.RemoveContainer(ctx, "container123", true)
234
235	assert.NoError(t, err)
236	assert.NotContains(t, manager.containers, "test.example.com")
237}
238
239func TestManager_RemoveContainer_WithNetworkCleanup(t *testing.T) {
240	ctrl := gomock.NewController(t)
241	defer ctrl.Finish()
242
243	ctx := testutils.TestContext(t)
244	mockRuntime := mocks.NewMockRuntime(ctrl)
245
246	cfg := &config.Config{
247		Volumes: config.VolumeConfig{
248			Preserve: true,
249		},
250		NetworkIsolation: config.NetworkIsolationConfig{
251			Enabled:       true,
252			NetworkPrefix: "gordon",
253		},
254	}
255
256	manager, err := NewManagerWithRuntime(cfg, mockRuntime)
257	require.NoError(t, err)
258
259	container := &runtime.Container{
260		ID: "container123",
261	}
262	manager.containers["test.example.com"] = container
263
264	mockRuntime.EXPECT().RemoveContainer(ctx, "container123", true).Return(nil)
265
266	// Expect network cleanup check
267	mockRuntime.EXPECT().ListNetworks(ctx).Return([]*runtime.NetworkInfo{
268		{
269			Name:       "gordon-test-example-com",
270			Containers: []string{}, // Empty network
271		},
272	}, nil)
273	mockRuntime.EXPECT().RemoveNetwork(ctx, "gordon-test-example-com").Return(nil)
274
275	err = manager.RemoveContainer(ctx, "container123", true)
276
277	assert.NoError(t, err)
278	assert.NotContains(t, manager.containers, "test.example.com")
279}
280
281func TestManager_RemoveContainerByDomain(t *testing.T) {
282	ctrl := gomock.NewController(t)
283	defer ctrl.Finish()
284
285	ctx := testutils.TestContext(t)
286	mockRuntime := mocks.NewMockRuntime(ctrl)
287
288	cfg := &config.Config{
289		Volumes: config.VolumeConfig{
290			Preserve: true,
291		},
292		NetworkIsolation: config.NetworkIsolationConfig{
293			Enabled: false,
294		},
295	}
296
297	manager, err := NewManagerWithRuntime(cfg, mockRuntime)
298	require.NoError(t, err)
299
300	container := &runtime.Container{
301		ID: "container123",
302	}
303	manager.containers["test.example.com"] = container
304
305	mockRuntime.EXPECT().RemoveContainer(ctx, "container123", false).Return(nil)
306
307	err = manager.RemoveContainerByDomain(ctx, "test.example.com", false)
308
309	assert.NoError(t, err)
310	assert.NotContains(t, manager.containers, "test.example.com")
311}
312
313func TestManager_RemoveContainerByDomain_NotFound(t *testing.T) {
314	cfg := &config.Config{}
315	manager := &Manager{
316		config:     cfg,
317		containers: make(map[string]*runtime.Container),
318	}
319
320	ctx := testutils.TestContext(t)
321	err := manager.RemoveContainerByDomain(ctx, "nonexistent.example.com", false)
322
323	assert.Error(t, err)
324	assert.Contains(t, err.Error(), "no container found for domain")
325}
326
327func TestManager_GetContainerPort(t *testing.T) {
328	ctrl := gomock.NewController(t)
329	defer ctrl.Finish()
330
331	ctx := testutils.TestContext(t)
332	mockRuntime := mocks.NewMockRuntime(ctrl)
333
334	cfg := &config.Config{}
335	manager, err := NewManagerWithRuntime(cfg, mockRuntime)
336	require.NoError(t, err)
337
338	container := &runtime.Container{
339		ID: "container123",
340	}
341	manager.containers["test.example.com"] = container
342
343	mockRuntime.EXPECT().GetContainerPort(ctx, "container123", 80).Return(8080, nil)
344
345	port, err := manager.GetContainerPort(ctx, "test.example.com", 80)
346
347	assert.NoError(t, err)
348	assert.Equal(t, 8080, port)
349}
350
351func TestManager_GetContainerPort_NotFound(t *testing.T) {
352	cfg := &config.Config{}
353	manager := &Manager{
354		config:     cfg,
355		containers: make(map[string]*runtime.Container),
356	}
357
358	ctx := testutils.TestContext(t)
359	_, err := manager.GetContainerPort(ctx, "nonexistent.example.com", 80)
360
361	assert.Error(t, err)
362	assert.Contains(t, err.Error(), "no container found for domain")
363}
364
365func TestManager_FindContainerByDomain(t *testing.T) {
366	manager := &Manager{
367		containers: map[string]*runtime.Container{
368			"test.example.com": {ID: "container123"},
369		},
370	}
371
372	id, exists := manager.FindContainerByDomain("test.example.com")
373	assert.True(t, exists)
374	assert.Equal(t, "container123", id)
375
376	id, exists = manager.FindContainerByDomain("nonexistent.example.com")
377	assert.False(t, exists)
378	assert.Empty(t, id)
379}
380
381func TestManager_FindDomainByContainerID(t *testing.T) {
382	manager := &Manager{
383		containers: map[string]*runtime.Container{
384			"test.example.com": {ID: "container123"},
385			"app.example.com":  {ID: "container456"},
386		},
387	}
388
389	domain, exists := manager.FindDomainByContainerID("container123")
390	assert.True(t, exists)
391	assert.Equal(t, "test.example.com", domain)
392
393	domain, exists = manager.FindDomainByContainerID("nonexistent")
394	assert.False(t, exists)
395	assert.Empty(t, domain)
396}
397
398func TestManager_StopAllManagedContainers(t *testing.T) {
399	ctrl := gomock.NewController(t)
400	defer ctrl.Finish()
401
402	ctx := testutils.TestContext(t)
403	mockRuntime := mocks.NewMockRuntime(ctrl)
404
405	cfg := &config.Config{}
406	manager, err := NewManagerWithRuntime(cfg, mockRuntime)
407	require.NoError(t, err)
408
409	manager.containers["app1.example.com"] = &runtime.Container{ID: "container1"}
410	manager.containers["app2.example.com"] = &runtime.Container{ID: "container2"}
411
412	mockRuntime.EXPECT().StopContainer(ctx, "container1").Return(nil)
413	mockRuntime.EXPECT().StopContainer(ctx, "container2").Return(nil)
414
415	err = manager.StopAllManagedContainers(ctx)
416
417	assert.NoError(t, err)
418	assert.Empty(t, manager.containers)
419}
420
421func TestManager_StopAllManagedContainers_WithErrors(t *testing.T) {
422	ctrl := gomock.NewController(t)
423	defer ctrl.Finish()
424
425	ctx := testutils.TestContext(t)
426	mockRuntime := mocks.NewMockRuntime(ctrl)
427
428	cfg := &config.Config{}
429	manager, err := NewManagerWithRuntime(cfg, mockRuntime)
430	require.NoError(t, err)
431
432	manager.containers["app1.example.com"] = &runtime.Container{ID: "container1"}
433	manager.containers["app2.example.com"] = &runtime.Container{ID: "container2"}
434
435	mockRuntime.EXPECT().StopContainer(ctx, "container1").Return(nil)
436	mockRuntime.EXPECT().StopContainer(ctx, "container2").Return(errors.New("stop failed"))
437
438	err = manager.StopAllManagedContainers(ctx)
439
440	assert.Error(t, err)
441	assert.Contains(t, err.Error(), "failed to stop 1 containers")
442}
443
444func TestManager_StopAllManagedContainers_Empty(t *testing.T) {
445	ctrl := gomock.NewController(t)
446	defer ctrl.Finish()
447
448	ctx := testutils.TestContext(t)
449	mockRuntime := mocks.NewMockRuntime(ctrl)
450
451	cfg := &config.Config{}
452	manager, err := NewManagerWithRuntime(cfg, mockRuntime)
453	require.NoError(t, err)
454
455	err = manager.StopAllManagedContainers(ctx)
456
457	assert.NoError(t, err)
458	assert.Empty(t, manager.containers)
459}
460
461func TestManager_UpdateConfig(t *testing.T) {
462	ctrl := gomock.NewController(t)
463	defer ctrl.Finish()
464
465	mockRuntime := mocks.NewMockRuntime(ctrl)
466
467	originalCfg := &config.Config{
468		Server: config.ServerConfig{
469			Runtime: "docker",
470		},
471	}
472
473	manager, err := NewManagerWithRuntime(originalCfg, mockRuntime)
474	require.NoError(t, err)
475
476	newCfg := &config.Config{
477		Server: config.ServerConfig{
478			Runtime: "podman",
479		},
480		Routes: map[string]string{
481			"new-app.example.com": "nginx:latest",
482		},
483	}
484
485	manager.UpdateConfig(newCfg)
486
487	// Cleanup the env file created by UpdateConfig
488	defer func() {
489		if err := os.Remove("new-app_example_com.env"); err != nil && !os.IsNotExist(err) {
490			t.Logf("Failed to cleanup env file: %v", err)
491		}
492	}()
493
494	assert.Equal(t, newCfg, manager.config)
495	assert.Equal(t, "podman", manager.config.Server.Runtime)
496}
497
498func TestManager_Shutdown(t *testing.T) {
499	ctrl := gomock.NewController(t)
500	defer ctrl.Finish()
501
502	ctx := testutils.TestContext(t)
503	mockRuntime := mocks.NewMockRuntime(ctrl)
504
505	cfg := &config.Config{}
506	manager, err := NewManagerWithRuntime(cfg, mockRuntime)
507	require.NoError(t, err)
508
509	manager.containers["app1.example.com"] = &runtime.Container{ID: "container1"}
510
511	mockRuntime.EXPECT().StopContainer(ctx, "container1").Return(nil)
512
513	err = manager.Shutdown(ctx)
514
515	assert.NoError(t, err)
516	assert.Empty(t, manager.containers)
517}

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