1package container
2
3import (
4 "context"
5 "os"
6 "path/filepath"
7 "strings"
8 "testing"
9
10 "github.com/stretchr/testify/assert"
11 "github.com/stretchr/testify/require"
12
13 "gordon/internal/config"
14)
15
16func TestCreateRuntime(t *testing.T) {
17 ctx := context.Background()
18
19 tests := []struct {
20 name string
21 config *config.Config
22 expectError bool
23 expectedError string
24 }{
25 {
26 name: "docker runtime",
27 config: &config.Config{
28 Server: config.ServerConfig{
29 Runtime: "docker",
30 SocketPath: "/var/run/docker.sock",
31 },
32 },
33 expectError: true, // Will fail because Docker isn't running in test
34 },
35 {
36 name: "podman runtime",
37 config: &config.Config{
38 Server: config.ServerConfig{
39 Runtime: "podman",
40 SocketPath: "/run/user/1000/podman/podman.sock",
41 },
42 },
43 expectError: true, // Will fail because Podman isn't running in test
44 },
45 {
46 name: "auto runtime",
47 config: &config.Config{
48 Server: config.ServerConfig{
49 Runtime: "auto",
50 },
51 },
52 expectError: false, // May succeed if runtime is available
53 },
54 {
55 name: "invalid runtime",
56 config: &config.Config{
57 Server: config.ServerConfig{
58 Runtime: "invalid",
59 },
60 },
61 expectError: true,
62 expectedError: "unsupported runtime",
63 },
64 }
65
66 for _, tt := range tests {
67 t.Run(tt.name, func(t *testing.T) {
68 runtime, err := CreateRuntime(ctx, tt.config)
69
70 if tt.name == "auto runtime" {
71 // Auto runtime may succeed or fail depending on environment
72 if err != nil {
73 assert.Nil(t, runtime)
74 } else {
75 assert.NotNil(t, runtime)
76 }
77 } else if tt.expectError {
78 assert.Error(t, err)
79 if tt.expectedError != "" {
80 assert.Contains(t, err.Error(), tt.expectedError)
81 }
82 assert.Nil(t, runtime)
83 } else {
84 assert.NoError(t, err)
85 assert.NotNil(t, runtime)
86 }
87 })
88 }
89}
90
91func TestGetSocketPath(t *testing.T) {
92 tests := []struct {
93 name string
94 configPath string
95 setupEnv func()
96 cleanupEnv func()
97 expected string
98 }{
99 {
100 name: "with provided config path",
101 configPath: "/custom/docker.sock",
102 setupEnv: func() {},
103 cleanupEnv: func() {},
104 expected: "/custom/docker.sock",
105 },
106 {
107 name: "with CONTAINER_HOST env var",
108 configPath: "",
109 setupEnv: func() {
110 os.Setenv("CONTAINER_HOST", "/env/container.sock")
111 },
112 cleanupEnv: func() {
113 os.Unsetenv("CONTAINER_HOST")
114 },
115 expected: "/env/container.sock",
116 },
117 {
118 name: "no config or env - returns empty",
119 configPath: "",
120 setupEnv: func() {
121 os.Unsetenv("CONTAINER_HOST")
122 },
123 cleanupEnv: func() {},
124 expected: "",
125 },
126 }
127
128 for _, tt := range tests {
129 t.Run(tt.name, func(t *testing.T) {
130 tt.setupEnv()
131 defer tt.cleanupEnv()
132
133 result := getSocketPath(tt.configPath)
134 assert.Equal(t, tt.expected, result)
135 })
136 }
137}
138
139func TestCreateAutoRuntime(t *testing.T) {
140 ctx := context.Background()
141
142 // Test that createAutoRuntime tries different runtimes
143 runtime, err := createAutoRuntime(ctx, "")
144 // In test environment, this may succeed or fail depending on available runtimes
145 if err != nil {
146 // If it fails, should be because no runtime is available
147 assert.Nil(t, runtime)
148 assert.Contains(t, err.Error(), "no container runtime available")
149 } else {
150 // If it succeeds, should return a runtime
151 assert.NotNil(t, runtime)
152 }
153}
154
155func TestCreateDockerRuntime(t *testing.T) {
156 ctx := context.Background()
157
158 tests := []struct {
159 name string
160 socketPath string
161 expectError bool
162 }{
163 {
164 name: "default docker socket",
165 socketPath: "/var/run/docker.sock",
166 expectError: true, // Will fail because Docker isn't running
167 },
168 {
169 name: "custom socket path",
170 socketPath: "/custom/docker.sock",
171 expectError: true, // Will fail because socket doesn't exist
172 },
173 }
174
175 for _, tt := range tests {
176 t.Run(tt.name, func(t *testing.T) {
177 runtime, err := createDockerRuntime(ctx, tt.socketPath)
178
179 if tt.expectError {
180 assert.Error(t, err)
181 assert.Nil(t, runtime)
182 } else {
183 assert.NoError(t, err)
184 assert.NotNil(t, runtime)
185 }
186 })
187 }
188}
189
190func TestCreatePodmanRuntime(t *testing.T) {
191 ctx := context.Background()
192
193 tests := []struct {
194 name string
195 socketPath string
196 rootless bool
197 expectError bool
198 }{
199 {
200 name: "custom podman socket",
201 socketPath: "/custom/podman.sock",
202 rootless: false,
203 expectError: true, // Will fail because socket doesn't exist
204 },
205 {
206 name: "rootless podman socket",
207 socketPath: "/custom/podman.sock",
208 rootless: true,
209 expectError: true, // Will fail because socket doesn't exist
210 },
211 }
212
213 for _, tt := range tests {
214 t.Run(tt.name, func(t *testing.T) {
215 runtime, err := createPodmanRuntime(ctx, tt.socketPath, tt.rootless)
216
217 if tt.expectError {
218 assert.Error(t, err)
219 assert.Nil(t, runtime)
220 } else {
221 assert.NoError(t, err)
222 assert.NotNil(t, runtime)
223 }
224 })
225 }
226}
227
228func TestDetectRuntimeType(t *testing.T) {
229 tests := []struct {
230 name string
231 socketPath string
232 expected RuntimeType
233 }{
234 {
235 name: "docker socket",
236 socketPath: "/var/run/docker.sock",
237 expected: RuntimeDocker,
238 },
239 {
240 name: "docker unix socket",
241 socketPath: "unix:///var/run/docker.sock",
242 expected: RuntimeDocker,
243 },
244 {
245 name: "podman socket",
246 socketPath: "/run/podman/podman.sock",
247 expected: RuntimePodman,
248 },
249 {
250 name: "rootless podman socket",
251 socketPath: "/run/user/1000/podman/podman.sock",
252 expected: RuntimePodmanRootless,
253 },
254 {
255 name: "unknown socket defaults to docker",
256 socketPath: "/unknown/socket.sock",
257 expected: RuntimeDocker,
258 },
259 }
260
261 for _, tt := range tests {
262 t.Run(tt.name, func(t *testing.T) {
263 result := detectRuntimeType(tt.socketPath)
264 assert.Equal(t, tt.expected, result)
265 })
266 }
267}
268
269func TestGetDefaultSocketPaths(t *testing.T) {
270 paths := getDefaultSocketPaths()
271
272 // Should return common socket paths
273 assert.True(t, len(paths) > 0)
274
275 // Should contain at least one Docker socket path
276 found := false
277 for _, path := range paths {
278 if strings.Contains(path, "docker") {
279 found = true
280 break
281 }
282 }
283 assert.True(t, found, "Should contain a Docker socket path")
284}
285
286func TestGetDefaultPodmanSocket(t *testing.T) {
287 socket := getDefaultPodmanSocket(false) // Regular mode
288
289 // Should return a valid path
290 assert.NotEmpty(t, socket)
291
292 // Should contain "podman" in the path
293 assert.Contains(t, socket, "podman")
294
295 // Test rootless mode
296 rootlessSocket := getDefaultPodmanSocket(true)
297 assert.NotEmpty(t, rootlessSocket)
298 assert.Contains(t, rootlessSocket, "podman")
299}
300
301func TestGetAvailableRuntimes(t *testing.T) {
302 runtimes := GetAvailableRuntimes()
303
304 // Should return a slice of runtime info
305 assert.IsType(t, []RuntimeInfo{}, runtimes)
306
307 // Should not be empty
308 assert.True(t, len(runtimes) > 0)
309
310 // Each runtime should have basic info
311 for _, rt := range runtimes {
312 assert.NotEmpty(t, rt.Type)
313 assert.NotEmpty(t, rt.SocketPath)
314 // Version may be empty if runtime not actually available
315 }
316}
317
318// Test socket existence checking (private function behavior via public interface)
319func TestSocketExistenceChecking(t *testing.T) {
320 ctx := context.Background()
321
322 // Create a temporary socket file
323 tmpDir := t.TempDir()
324 socketPath := filepath.Join(tmpDir, "test.sock")
325
326 // Create an empty file to simulate a socket
327 file, err := os.Create(socketPath)
328 require.NoError(t, err)
329 file.Close()
330
331 // Test with existing file (won't be a real socket, but tests file existence)
332 _, err = createDockerRuntime(ctx, socketPath)
333 // This will still fail because it's not a real Docker socket, but for a different reason
334 assert.Error(t, err)
335 // Error should not be about file not existing
336 assert.NotContains(t, err.Error(), "no such file")
337}