1package services
2
3import (
4 "testing"
5
6 "github.com/stretchr/testify/assert"
7 "github.com/stretchr/testify/require"
8
9 "github.com/bnema/gordon/internal/domain"
10)
11
12func TestToDomainRejectsDuplicateServiceNames(t *testing.T) {
13 configs := []Config{
14 {Name: "cache", Image: "redis:7", Enabled: true},
15 {Name: "cache", Image: "redis:7", Enabled: true},
16 }
17
18 _, err := ToDomain(configs)
19
20 require.ErrorContains(t, err, "duplicate service name")
21}
22
23func TestToDomainRejectsServiceNamesWithDuplicateRuntimeIdentifier(t *testing.T) {
24 configs := []Config{
25 {Name: "cache.api", Image: "redis:7", Enabled: true},
26 {Name: "cache_api", Image: "redis:7", Enabled: true},
27 }
28
29 _, err := ToDomain(configs)
30
31 require.ErrorContains(t, err, "runtime identifier")
32}
33
34func TestConfigToDomainRejectsNonPositiveReadinessTimeout(t *testing.T) {
35 for _, timeout := range []string{"0s", "-1s"} {
36 t.Run(timeout, func(t *testing.T) {
37 cfg := Config{
38 Name: "cache",
39 Image: "redis:7",
40 Enabled: true,
41 Readiness: ReadinessConfig{
42 Type: domain.StandaloneServiceReadinessTCP,
43 Timeout: timeout,
44 },
45 }
46
47 _, err := cfg.ToDomain()
48
49 require.ErrorContains(t, err, "readiness timeout")
50 require.ErrorContains(t, err, "positive")
51 })
52 }
53}
54
55func TestConfigToDomainRejectsExplicitNoCleanupAction(t *testing.T) {
56 preserveVolumes := false
57 removeContainer := false
58 cfg := Config{
59 Name: "cache",
60 Image: "redis:7",
61 Enabled: true,
62 Cleanup: CleanupConfig{PreserveVolumes: &preserveVolumes, RemoveContainer: &removeContainer},
63 }
64
65 _, err := cfg.ToDomain()
66
67 require.ErrorContains(t, err, "preserve_volumes=false")
68 require.ErrorContains(t, err, "remove_container=false")
69}
70
71func TestConfigToDomainMapsExplicitPublicPorts(t *testing.T) {
72 cfg := Config{
73 Name: "game",
74 Image: "game:latest",
75 Enabled: true,
76 Ports: []PortConfig{{
77 Name: "rcon",
78 Container: 28016,
79 Protocol: domain.NetworkProtocolTCP,
80 Publish: "127.0.0.1:38016",
81 Public: true,
82 }},
83 }
84
85 svc, err := cfg.ToDomain()
86
87 require.NoError(t, err)
88 require.Len(t, svc.Ports, 1)
89 assert.True(t, svc.Ports[0].Public)
90}
91
92func TestResolveVolumeMountsUsesExplicitVolumesAsIs(t *testing.T) {
93 explicit := []domain.StandaloneServiceVolume{
94 {Source: "rust-data", Target: "/steamcmd/rust", ReadOnly: false},
95 {Source: "/srv/config", Target: "/config", ReadOnly: true},
96 }
97
98 mounts := ResolveVolumeMounts("gordon", "rust.example.com", explicit, []string{"/ignored"})
99
100 assert.Equal(t, []ResolvedVolumeMount{
101 {Source: "rust-data", Target: "/steamcmd/rust", ReadOnly: false},
102 {Source: "/srv/config", Target: "/config", ReadOnly: true},
103 }, mounts)
104}
105
106func TestResolveVolumeMountsUsesImageDiscoveredVolumesWhenExplicitOmitted(t *testing.T) {
107 mounts := ResolveVolumeMounts("gordon", "rust.example.com", nil, []string{"/steamcmd/rust", "/var/lib/data"})
108
109 assert.Equal(t, []ResolvedVolumeMount{
110 {Source: "gordon-rust-example-com-steamcmd-rust", Target: "/steamcmd/rust", Managed: true},
111 {Source: "gordon-rust-example-com-var-lib-data", Target: "/var/lib/data", Managed: true},
112 }, mounts)
113}
114
115func TestResolveVolumeMountsReturnsEmptyForStatelessImage(t *testing.T) {
116 mounts := ResolveVolumeMounts("gordon", "stateless.example.com", nil, nil)
117
118 assert.Empty(t, mounts)
119}
120
121func TestManagedServiceVolumeNameIsDeterministicAndGordonCompatible(t *testing.T) {
122 assert.Equal(t, "gordon-rust-example-com-var-lib-data", ManagedServiceVolumeName("gordon", "rust.example.com", "/var/lib/data"))
123 assert.Equal(t, "gordon-rust-example-com-data", ManagedServiceVolumeName("gordon", "rust.example.com", "/data"))
124}