v3.4.1 de8f68e

service_test.go RawHistoryFolder

1package domain
2
3import (
4	"testing"
5	"time"
6
7	"github.com/stretchr/testify/require"
8)
9
10func TestStandaloneServiceValidateRequiresNameAndImageWhenEnabled(t *testing.T) {
11	svc := StandaloneService{Enabled: true, Image: "redis:7"}
12	require.ErrorContains(t, svc.Validate(), "service name")
13
14	svc = StandaloneService{Enabled: true, Name: "cache"}
15	require.ErrorContains(t, svc.Validate(), "image")
16}
17
18func TestStandaloneServiceValidateRejectsPaddedNames(t *testing.T) {
19	svc := StandaloneService{Name: " cache ", Image: "redis:7", Enabled: true}
20	require.ErrorContains(t, svc.Validate(), "leading or trailing whitespace")
21
22	svc = StandaloneService{
23		Name:    "cache",
24		Image:   "redis:7",
25		Enabled: true,
26		Ports:   []StandaloneServicePort{{Name: " admin ", Container: 6379, Protocol: NetworkProtocolTCP}},
27	}
28	require.ErrorContains(t, svc.Validate(), "leading or trailing whitespace")
29}
30
31func TestStandaloneServiceValidateAllowsDisabledServiceWithoutImage(t *testing.T) {
32	svc := StandaloneService{Name: "cache", Enabled: false}
33	require.NoError(t, svc.Validate())
34}
35
36func TestStandaloneServiceValidatePorts(t *testing.T) {
37	valid := StandaloneService{
38		Name:    "game",
39		Image:   "game:latest",
40		Enabled: true,
41		Ports: []StandaloneServicePort{
42			{Name: "game", Container: 28015, Protocol: NetworkProtocolUDP, Publish: "127.0.0.1:38015"},
43			{Name: "rcon", Container: 28016, Protocol: NetworkProtocolTCP, Publish: "127.0.0.1:38016", Private: true},
44		},
45	}
46	require.NoError(t, valid.Validate())
47
48	duplicate := valid
49	duplicate.Ports = append(duplicate.Ports, StandaloneServicePort{Name: "game", Container: 28017, Protocol: NetworkProtocolUDP})
50	require.ErrorContains(t, duplicate.Validate(), "duplicate port")
51
52	badProtocol := valid
53	badProtocol.Ports = []StandaloneServicePort{{Name: "game", Container: 28015, Protocol: NetworkProtocol("sctp")}}
54	require.ErrorContains(t, badProtocol.Validate(), "protocol")
55
56	badPublish := valid
57	badPublish.Ports = []StandaloneServicePort{{Name: "game", Container: 28015, Protocol: NetworkProtocolUDP, Publish: "127.0.0.1:not-a-port"}}
58	require.ErrorContains(t, badPublish.Validate(), "valid port")
59
60	conflictingVisibility := valid
61	conflictingVisibility.Ports = []StandaloneServicePort{{Name: "rcon", Container: 28016, Protocol: NetworkProtocolTCP, Private: true, Public: true}}
62	require.ErrorContains(t, conflictingVisibility.Validate(), "cannot be both private and public")
63
64	privateNonLoopback := valid
65	privateNonLoopback.Ports = []StandaloneServicePort{{Name: "rcon", Container: 28016, Protocol: NetworkProtocolTCP, Private: true, Publish: "0.0.0.0:28016"}}
66	require.ErrorContains(t, privateNonLoopback.Validate(), "must be loopback")
67}
68
69func TestStandaloneServiceValidateVolumesAndEnvFile(t *testing.T) {
70	svc := StandaloneService{Name: "cache", Image: "redis:7", Enabled: true}
71	require.NoError(t, svc.Validate(), "volumes may be omitted")
72
73	svc.Volumes = []StandaloneServiceVolume{{Source: "cache-data", Target: "var/lib/redis"}}
74	require.ErrorContains(t, svc.Validate(), "absolute")
75
76	svc.Volumes = []StandaloneServiceVolume{{Source: "cache-data", Target: `C:\\data`}}
77	require.ErrorContains(t, svc.Validate(), "absolute")
78
79	svc.Volumes = nil
80	svc.EnvFile = "   "
81	require.ErrorContains(t, svc.Validate(), "env_file")
82}
83
84func TestStandaloneServiceValidateReadiness(t *testing.T) {
85	svc := StandaloneService{Name: "game", Image: "game:latest", Enabled: true}
86	for _, readinessType := range []string{"", "none", "tcp", "log"} {
87		t.Run(readinessType, func(t *testing.T) {
88			candidate := svc
89			candidate.Readiness = StandaloneServiceReadiness{Type: readinessType, Path: "/tmp/server.log", Contains: "ready", Timeout: time.Minute}
90			require.NoError(t, candidate.Validate())
91		})
92	}
93
94	svc.Readiness = StandaloneServiceReadiness{Type: "udp"}
95	require.ErrorContains(t, svc.Validate(), "readiness")
96
97	for _, timeout := range []time.Duration{0, -time.Second} {
98		svc.Readiness = StandaloneServiceReadiness{Type: "tcp", Timeout: timeout, TimeoutSet: true}
99		require.ErrorContains(t, svc.Validate(), "readiness timeout")
100	}
101
102	svc.Readiness = StandaloneServiceReadiness{Type: "log", Path: "/tmp/server.log"}
103	require.ErrorContains(t, svc.Validate(), "contains")
104
105	svc.Readiness = StandaloneServiceReadiness{Type: "log", Contains: "ready"}
106	require.ErrorContains(t, svc.Validate(), "path")
107}
108
109func TestStandaloneServiceWithDefaultsCleanup(t *testing.T) {
110	svc := StandaloneService{Name: "game", Image: "game:latest", Enabled: true}.WithDefaults()
111	require.True(t, svc.Cleanup.PreserveVolumes)
112	require.True(t, svc.Cleanup.RemoveContainer)
113	require.NoError(t, svc.Validate())
114
115	svc = StandaloneService{Name: "game", Image: "game:latest", Enabled: true, Cleanup: StandaloneServiceCleanup{RemoveContainer: true}}.WithDefaults()
116	require.False(t, svc.Cleanup.PreserveVolumes)
117	require.True(t, svc.Cleanup.RemoveContainer)
118
119	svc = StandaloneService{Name: "game", Image: "game:latest", Enabled: true, Cleanup: StandaloneServiceCleanup{PreserveVolumes: true}}.WithDefaults()
120	require.True(t, svc.Cleanup.PreserveVolumes)
121	require.False(t, svc.Cleanup.RemoveContainer)
122}
123
124func TestStandaloneServiceValidateRejectsDuplicateVolumeTargets(t *testing.T) {
125	svc := StandaloneService{
126		Name:    "cache",
127		Image:   "redis:7",
128		Enabled: true,
129		Volumes: []StandaloneServiceVolume{
130			{Source: "cache-data", Target: "/data"},
131			{Source: "cache-other", Target: "/data"},
132		},
133	}
134	require.ErrorContains(t, svc.Validate(), "duplicate volume target")
135}

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