v2.0.0 ae3e752

manager_network_test.go RawHistoryFolder

1package container
2
3import (
4	"errors"
5	"testing"
6
7	"github.com/stretchr/testify/assert"
8	"github.com/stretchr/testify/require"
9	"go.uber.org/mock/gomock"
10
11	"gordon/internal/config"
12	"gordon/internal/testutils"
13	"gordon/internal/testutils/mocks"
14	"gordon/pkg/runtime"
15)
16
17func TestManager_GetNetworkForApp_WithGroups(t *testing.T) {
18	manager := &Manager{
19		config: &config.Config{
20			NetworkIsolation: config.NetworkIsolationConfig{
21				Enabled:       true,
22				NetworkPrefix: "gordon",
23			},
24			NetworkGroups: map[string][]string{
25				"backend": {"api.example.com", "worker.example.com"},
26				"frontend": {"web.example.com", "cdn.example.com"},
27			},
28		},
29	}
30
31	// Test app in backend group
32	network := manager.GetNetworkForApp("api.example.com")
33	assert.Equal(t, "gordon-backend", network)
34
35	// Test app in frontend group
36	network = manager.GetNetworkForApp("web.example.com")
37	assert.Equal(t, "gordon-frontend", network)
38
39	// Test standalone app
40	network = manager.GetNetworkForApp("standalone.example.com")
41	assert.Equal(t, "gordon-standalone-example-com", network)
42
43	// Test disabled network isolation
44	manager.config.NetworkIsolation.Enabled = false
45	network = manager.GetNetworkForApp("test.example.com")
46	assert.Equal(t, "bridge", network)
47}
48
49func TestManager_generateNetworkName(t *testing.T) {
50	manager := &Manager{
51		config: &config.Config{
52			NetworkIsolation: config.NetworkIsolationConfig{
53				NetworkPrefix: "gordon",
54			},
55		},
56	}
57
58	networkName := manager.generateNetworkName("test.example.com")
59	assert.Equal(t, "gordon-test-example-com", networkName)
60
61	networkName = manager.generateNetworkName("backend")
62	assert.Equal(t, "gordon-backend", networkName)
63
64	networkName = manager.generateNetworkName("complex-app.subdomain.example.com")
65	assert.Equal(t, "gordon-complex-app-subdomain-example-com", networkName)
66}
67
68func TestManager_GetAppsForNetwork(t *testing.T) {
69	manager := &Manager{
70		config: &config.Config{
71			NetworkGroups: map[string][]string{
72				"backend": {"api.example.com", "worker.example.com"},
73				"frontend": {"web.example.com"},
74			},
75		},
76	}
77
78	// Test network group
79	apps := manager.GetAppsForNetwork("backend")
80	assert.Equal(t, []string{"api.example.com", "worker.example.com"}, apps)
81
82	// Test single app group
83	apps = manager.GetAppsForNetwork("frontend")
84	assert.Equal(t, []string{"web.example.com"}, apps)
85
86	// Test single app (not in any group)
87	apps = manager.GetAppsForNetwork("standalone.example.com")
88	assert.Equal(t, []string{"standalone.example.com"}, apps)
89}
90
91func TestManager_CreateNetworkIfNeeded(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	// Test bridge network (should not try to create)
103	err = manager.CreateNetworkIfNeeded(ctx, "bridge")
104	assert.NoError(t, err)
105
106	// Test default network (should not try to create)
107	err = manager.CreateNetworkIfNeeded(ctx, "default")
108	assert.NoError(t, err)
109
110	// Test custom network that doesn't exist
111	mockRuntime.EXPECT().NetworkExists(ctx, "gordon-test").Return(false, nil)
112	mockRuntime.EXPECT().CreateNetwork(ctx, "gordon-test", map[string]string{"driver": "bridge"}).Return(nil)
113
114	err = manager.CreateNetworkIfNeeded(ctx, "gordon-test")
115	assert.NoError(t, err)
116
117	// Test custom network that already exists
118	mockRuntime.EXPECT().NetworkExists(ctx, "gordon-existing").Return(true, nil)
119
120	err = manager.CreateNetworkIfNeeded(ctx, "gordon-existing")
121	assert.NoError(t, err)
122}
123
124func TestManager_CreateNetworkIfNeeded_Errors(t *testing.T) {
125	ctrl := gomock.NewController(t)
126	defer ctrl.Finish()
127
128	ctx := testutils.TestContext(t)
129	mockRuntime := mocks.NewMockRuntime(ctrl)
130
131	cfg := &config.Config{}
132	manager, err := NewManagerWithRuntime(cfg, mockRuntime)
133	require.NoError(t, err)
134
135	// Test error checking if network exists
136	mockRuntime.EXPECT().NetworkExists(ctx, "gordon-test").Return(false, errors.New("check failed"))
137
138	err = manager.CreateNetworkIfNeeded(ctx, "gordon-test")
139	assert.Error(t, err)
140	assert.Contains(t, err.Error(), "failed to check if network exists")
141
142	// Test error creating network
143	mockRuntime.EXPECT().NetworkExists(ctx, "gordon-test").Return(false, nil)
144	mockRuntime.EXPECT().CreateNetwork(ctx, "gordon-test", map[string]string{"driver": "bridge"}).Return(errors.New("create failed"))
145
146	err = manager.CreateNetworkIfNeeded(ctx, "gordon-test")
147	assert.Error(t, err)
148	assert.Contains(t, err.Error(), "failed to create network")
149}
150
151func TestManager_cleanupNetworkIfEmpty(t *testing.T) {
152	ctrl := gomock.NewController(t)
153	defer ctrl.Finish()
154
155	ctx := testutils.TestContext(t)
156	mockRuntime := mocks.NewMockRuntime(ctrl)
157
158	cfg := &config.Config{}
159	manager, err := NewManagerWithRuntime(cfg, mockRuntime)
160	require.NoError(t, err)
161
162	// Test cleanup of empty network
163	networks := []*runtime.NetworkInfo{
164		{
165			Name:       "gordon-test",
166			Containers: []string{}, // Empty network
167		},
168		{
169			Name:       "gordon-used",
170			Containers: []string{"container1"}, // Has containers
171		},
172	}
173
174	mockRuntime.EXPECT().ListNetworks(ctx).Return(networks, nil)
175	mockRuntime.EXPECT().RemoveNetwork(ctx, "gordon-test").Return(nil)
176
177	err = manager.cleanupNetworkIfEmpty(ctx, "gordon-test")
178	assert.NoError(t, err)
179
180	// Test network with containers (should not be removed)
181	mockRuntime.EXPECT().ListNetworks(ctx).Return(networks, nil)
182
183	err = manager.cleanupNetworkIfEmpty(ctx, "gordon-used")
184	assert.NoError(t, err)
185
186	// Test bridge network (should not try to remove)
187	err = manager.cleanupNetworkIfEmpty(ctx, "bridge")
188	assert.NoError(t, err)
189
190	// Test default network (should not try to remove)
191	err = manager.cleanupNetworkIfEmpty(ctx, "default")
192	assert.NoError(t, err)
193}
194
195func TestManager_cleanupNetworkIfEmpty_Errors(t *testing.T) {
196	ctrl := gomock.NewController(t)
197	defer ctrl.Finish()
198
199	ctx := testutils.TestContext(t)
200	mockRuntime := mocks.NewMockRuntime(ctrl)
201
202	cfg := &config.Config{}
203	manager, err := NewManagerWithRuntime(cfg, mockRuntime)
204	require.NoError(t, err)
205
206	// Test error listing networks
207	mockRuntime.EXPECT().ListNetworks(ctx).Return(nil, errors.New("list failed"))
208
209	err = manager.cleanupNetworkIfEmpty(ctx, "gordon-test")
210	assert.Error(t, err)
211	assert.Contains(t, err.Error(), "failed to list networks")
212
213	// Test error removing network
214	networks := []*runtime.NetworkInfo{
215		{
216			Name:       "gordon-test",
217			Containers: []string{},
218		},
219	}
220
221	mockRuntime.EXPECT().ListNetworks(ctx).Return(networks, nil)
222	mockRuntime.EXPECT().RemoveNetwork(ctx, "gordon-test").Return(errors.New("remove failed"))
223
224	err = manager.cleanupNetworkIfEmpty(ctx, "gordon-test")
225	assert.Error(t, err)
226}
227
228func TestManager_DeployAttachedService_SharedService(t *testing.T) {
229	ctrl := gomock.NewController(t)
230	defer ctrl.Finish()
231
232	ctx := testutils.TestContext(t)
233	mockRuntime := mocks.NewMockRuntime(ctrl)
234
235	cfg := &config.Config{
236		Volumes: config.VolumeConfig{
237			AutoCreate: false,
238		},
239		NetworkIsolation: config.NetworkIsolationConfig{
240			NetworkPrefix: "gordon",
241		},
242		NetworkGroups: map[string][]string{
243			"backend": {"api.example.com", "worker.example.com"},
244		},
245	}
246
247	manager, err := NewManagerWithRuntime(cfg, mockRuntime)
248	require.NoError(t, err)
249
250	// Test deploying shared service for network group
251	mockRuntime.EXPECT().ListContainers(ctx, false).Return([]*runtime.Container{}, nil)
252	mockRuntime.EXPECT().GetImageExposedPorts(ctx, "postgres:latest").Return([]int{5432}, nil)
253	mockRuntime.EXPECT().InspectImageEnv(ctx, "postgres:latest").Return([]string{"POSTGRES_VERSION=15"}, nil)
254
255	expectedContainer := &runtime.Container{
256		ID:   "postgres-service",
257		Name: "gordon-shared-postgres",
258	}
259
260	mockRuntime.EXPECT().CreateContainer(ctx, gomock.Any()).Return(expectedContainer, nil)
261	mockRuntime.EXPECT().StartContainer(ctx, "postgres-service").Return(nil)
262
263	err = manager.DeployAttachedService(ctx, "backend", "postgres:latest")
264
265	assert.NoError(t, err)
266}
267
268func TestManager_DeployAttachedService_AppSpecific(t *testing.T) {
269	ctrl := gomock.NewController(t)
270	defer ctrl.Finish()
271
272	ctx := testutils.TestContext(t)
273	mockRuntime := mocks.NewMockRuntime(ctrl)
274
275	cfg := &config.Config{
276		Volumes: config.VolumeConfig{
277			AutoCreate: false,
278		},
279		NetworkIsolation: config.NetworkIsolationConfig{
280			NetworkPrefix: "gordon",
281		},
282	}
283
284	manager, err := NewManagerWithRuntime(cfg, mockRuntime)
285	require.NoError(t, err)
286
287	// Test deploying app-specific service
288	mockRuntime.EXPECT().ListContainers(ctx, false).Return([]*runtime.Container{}, nil)
289	mockRuntime.EXPECT().GetImageExposedPorts(ctx, "redis:latest").Return([]int{6379}, nil)
290	mockRuntime.EXPECT().InspectImageEnv(ctx, "redis:latest").Return([]string{}, nil)
291
292	expectedContainer := &runtime.Container{
293		ID:   "redis-service",
294		Name: "api-example-com-redis",
295	}
296
297	mockRuntime.EXPECT().CreateContainer(ctx, gomock.Any()).Return(expectedContainer, nil)
298	mockRuntime.EXPECT().StartContainer(ctx, "redis-service").Return(nil)
299
300	err = manager.DeployAttachedService(ctx, "api.example.com", "redis:latest")
301
302	assert.NoError(t, err)
303}
304
305func TestManager_DeployAttachedService_AlreadyRunning(t *testing.T) {
306	ctrl := gomock.NewController(t)
307	defer ctrl.Finish()
308
309	ctx := testutils.TestContext(t)
310	mockRuntime := mocks.NewMockRuntime(ctrl)
311
312	cfg := &config.Config{
313		NetworkIsolation: config.NetworkIsolationConfig{
314			NetworkPrefix: "gordon",
315		},
316	}
317
318	manager, err := NewManagerWithRuntime(cfg, mockRuntime)
319	require.NoError(t, err)
320
321	// Mock existing container with same name
322	existingContainers := []*runtime.Container{
323		{
324			ID:   "existing-redis",
325			Name: "api-example-com-redis",
326		},
327	}
328
329	mockRuntime.EXPECT().ListContainers(ctx, false).Return(existingContainers, nil)
330
331	err = manager.DeployAttachedService(ctx, "api.example.com", "redis:latest")
332
333	assert.NoError(t, err)
334}
335
336func TestManager_DeployAttachedService_WithVolumes(t *testing.T) {
337	ctrl := gomock.NewController(t)
338	defer ctrl.Finish()
339
340	ctx := testutils.TestContext(t)
341	mockRuntime := mocks.NewMockRuntime(ctrl)
342
343	cfg := &config.Config{
344		Volumes: config.VolumeConfig{
345			AutoCreate: true,
346			Prefix:     "gordon",
347		},
348		NetworkIsolation: config.NetworkIsolationConfig{
349			NetworkPrefix: "gordon",
350		},
351	}
352
353	manager, err := NewManagerWithRuntime(cfg, mockRuntime)
354	require.NoError(t, err)
355
356	// Test service with volumes
357	mockRuntime.EXPECT().ListContainers(ctx, false).Return([]*runtime.Container{}, nil)
358	mockRuntime.EXPECT().GetImageExposedPorts(ctx, "postgres:latest").Return([]int{5432}, nil)
359	mockRuntime.EXPECT().InspectImageEnv(ctx, "postgres:latest").Return([]string{}, nil)
360	mockRuntime.EXPECT().InspectImageVolumes(ctx, "postgres:latest").Return([]string{"/var/lib/postgresql/data"}, nil)
361
362	// Volume creation expectations
363	mockRuntime.EXPECT().VolumeExists(ctx, "gordon-api-example-com-var-lib-postgresql-data").Return(false, nil)
364	mockRuntime.EXPECT().CreateVolume(ctx, "gordon-api-example-com-var-lib-postgresql-data").Return(nil)
365
366	expectedContainer := &runtime.Container{
367		ID:   "postgres-service",
368		Name: "api-example-com-postgres",
369	}
370
371	mockRuntime.EXPECT().CreateContainer(ctx, gomock.Any()).Return(expectedContainer, nil)
372	mockRuntime.EXPECT().StartContainer(ctx, "postgres-service").Return(nil)
373
374	err = manager.DeployAttachedService(ctx, "api.example.com", "postgres:latest")
375
376	assert.NoError(t, err)
377}
378
379func TestManager_DeployAttachedService_Errors(t *testing.T) {
380	ctrl := gomock.NewController(t)
381	defer ctrl.Finish()
382
383	ctx := testutils.TestContext(t)
384	mockRuntime := mocks.NewMockRuntime(ctrl)
385
386	cfg := &config.Config{
387		Volumes: config.VolumeConfig{
388			AutoCreate: false,
389		},
390		NetworkIsolation: config.NetworkIsolationConfig{
391			NetworkPrefix: "gordon",
392		},
393	}
394
395	manager, err := NewManagerWithRuntime(cfg, mockRuntime)
396	require.NoError(t, err)
397
398	// Test create container error
399	mockRuntime.EXPECT().ListContainers(ctx, false).Return([]*runtime.Container{}, nil)
400	mockRuntime.EXPECT().GetImageExposedPorts(ctx, "redis:latest").Return([]int{6379}, nil)
401	mockRuntime.EXPECT().InspectImageEnv(ctx, "redis:latest").Return([]string{}, nil)
402	mockRuntime.EXPECT().CreateContainer(ctx, gomock.Any()).Return(nil, errors.New("create failed"))
403
404	err = manager.DeployAttachedService(ctx, "api.example.com", "redis:latest")
405	assert.Error(t, err)
406	assert.Contains(t, err.Error(), "failed to create service container")
407
408	// Test start container error
409	mockRuntime.EXPECT().ListContainers(ctx, false).Return([]*runtime.Container{}, nil)
410	mockRuntime.EXPECT().GetImageExposedPorts(ctx, "redis:latest").Return([]int{6379}, nil)
411	mockRuntime.EXPECT().InspectImageEnv(ctx, "redis:latest").Return([]string{}, nil)
412
413	container := &runtime.Container{ID: "service123", Name: "api-example-com-redis"}
414	mockRuntime.EXPECT().CreateContainer(ctx, gomock.Any()).Return(container, nil)
415	mockRuntime.EXPECT().StartContainer(ctx, "service123").Return(errors.New("start failed"))
416	mockRuntime.EXPECT().RemoveContainer(ctx, "service123", true).Return(nil)
417
418	err = manager.DeployAttachedService(ctx, "api.example.com", "redis:latest")
419	assert.Error(t, err)
420	assert.Contains(t, err.Error(), "failed to start service container")
421}

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