|
1 | 1 | package main |
2 | 2 |
|
3 | 3 | import ( |
4 | | - "os" |
5 | | - "path/filepath" |
| 4 | + "errors" |
6 | 5 | "strings" |
7 | 6 | "testing" |
8 | 7 |
|
9 | 8 | "github.com/eraser-dev/eraser/api/unversioned" |
| 9 | + |
10 | 10 | "github.com/stretchr/testify/assert" |
11 | | - "github.com/stretchr/testify/require" |
12 | 11 | ) |
13 | 12 |
|
14 | 13 | const ( |
@@ -173,189 +172,103 @@ func TestCLIArgs(t *testing.T) { |
173 | 172 | } |
174 | 173 | } |
175 | 174 |
|
176 | | -// TestEnsureTrivyExecutable tests the ensureTrivyExecutable function in isolation. |
177 | | -func TestEnsureTrivyExecutable(t *testing.T) { |
178 | | - // Save original PATH to restore after tests |
179 | | - originalPath := os.Getenv("PATH") |
180 | | - defer func() { os.Setenv("PATH", originalPath) }() |
| 175 | +// TestFindTrivyExecutable tests the findTrivyExecutable function in isolation. |
| 176 | +func TestFindTrivyExecutable(t *testing.T) { |
| 177 | + // Store original function to restore after tests |
| 178 | + originalLookPath := currentExecutingLookPath |
| 179 | + defer func() { currentExecutingLookPath = originalLookPath }() |
181 | 180 |
|
182 | 181 | testCases := []struct { |
183 | | - name string |
184 | | - setupFunc func(t *testing.T) (targetPath string, cleanup func()) |
185 | | - expectedError bool |
186 | | - expectedErrorMsg string |
187 | | - validateFunc func(t *testing.T, targetPath string) |
| 182 | + name string |
| 183 | + lookPathSetup func() |
| 184 | + expectedPath string |
| 185 | + expectedError bool |
| 186 | + expectedErrorMatch string |
188 | 187 | }{ |
189 | 188 | { |
190 | | - name: "Trivy already exists at target path", |
191 | | - setupFunc: func(t *testing.T) (string, func()) { |
192 | | - tempDir := t.TempDir() |
193 | | - targetPath := filepath.Join(tempDir, "trivy") |
194 | | - |
195 | | - // Create a trivy executable at the target path |
196 | | - file, err := os.Create(targetPath) |
197 | | - require.NoError(t, err) |
198 | | - file.Close() |
199 | | - err = os.Chmod(targetPath, 0o755) |
200 | | - require.NoError(t, err) |
201 | | - |
202 | | - return targetPath, func() {} |
203 | | - }, |
204 | | - expectedError: false, |
205 | | - validateFunc: func(t *testing.T, targetPath string) { |
206 | | - // Should not create a symlink, original file should still exist |
207 | | - info, err := os.Lstat(targetPath) |
208 | | - require.NoError(t, err) |
209 | | - assert.Equal(t, os.FileMode(0o755), info.Mode().Perm(), "Original file should be preserved") |
210 | | - |
211 | | - // Verify it's not a symlink |
212 | | - assert.Equal(t, 0, int(info.Mode()&os.ModeSymlink), "Should not be a symlink") |
213 | | - }, |
214 | | - }, |
215 | | - { |
216 | | - name: "Trivy found in PATH, symlink created successfully", |
217 | | - setupFunc: func(t *testing.T) (string, func()) { |
218 | | - tempDir := t.TempDir() |
219 | | - pathDir := filepath.Join(tempDir, "bin") |
220 | | - err := os.Mkdir(pathDir, 0o755) |
221 | | - require.NoError(t, err) |
222 | | - |
223 | | - // Create a trivy executable in the PATH directory |
224 | | - trivyInPath := filepath.Join(pathDir, "trivy") |
225 | | - file, err := os.Create(trivyInPath) |
226 | | - require.NoError(t, err) |
227 | | - file.Close() |
228 | | - err = os.Chmod(trivyInPath, 0o755) |
229 | | - require.NoError(t, err) |
230 | | - |
231 | | - // Set PATH to include our temp bin directory |
232 | | - os.Setenv("PATH", pathDir) |
233 | | - |
234 | | - // Target path where symlink should be created |
235 | | - targetPath := filepath.Join(tempDir, "target_trivy") |
236 | | - |
237 | | - return targetPath, func() {} |
| 189 | + name: "Trivy found in PATH only", |
| 190 | + lookPathSetup: func() { |
| 191 | + currentExecutingLookPath = func(file string) (string, error) { |
| 192 | + if file == trivyExecutableName { |
| 193 | + return trivyPathBin, nil |
| 194 | + } |
| 195 | + return "", errors.New("not found") |
| 196 | + } |
238 | 197 | }, |
| 198 | + expectedPath: trivyPathBin, |
239 | 199 | expectedError: false, |
240 | | - validateFunc: func(t *testing.T, targetPath string) { |
241 | | - // Should create a symlink at target path |
242 | | - info, err := os.Lstat(targetPath) |
243 | | - require.NoError(t, err) |
244 | | - |
245 | | - // Verify it's a symlink |
246 | | - assert.NotEqual(t, 0, int(info.Mode()&os.ModeSymlink), "Should be a symlink") |
247 | | - |
248 | | - // Verify symlink points to the correct location |
249 | | - linkTarget, err := os.Readlink(targetPath) |
250 | | - require.NoError(t, err) |
251 | | - assert.Contains(t, linkTarget, "trivy", "Symlink should point to trivy executable") |
252 | | - }, |
253 | 200 | }, |
254 | 201 | { |
255 | 202 | name: "Trivy not found anywhere", |
256 | | - setupFunc: func(t *testing.T) (string, func()) { |
257 | | - tempDir := t.TempDir() |
258 | | - emptyPathDir := filepath.Join(tempDir, "empty") |
259 | | - err := os.Mkdir(emptyPathDir, 0o755) |
260 | | - require.NoError(t, err) |
261 | | - |
262 | | - // Set PATH to empty directory without trivy |
263 | | - os.Setenv("PATH", emptyPathDir) |
264 | | - |
265 | | - targetPath := filepath.Join(tempDir, "target_trivy") |
266 | | - return targetPath, func() {} |
267 | | - }, |
268 | | - expectedError: true, |
269 | | - expectedErrorMsg: "trivy executable not found", |
270 | | - validateFunc: func(t *testing.T, targetPath string) { |
271 | | - // Should not create any file or symlink |
272 | | - _, err := os.Lstat(targetPath) |
273 | | - assert.True(t, os.IsNotExist(err), "Target path should not exist when trivy is not found") |
274 | | - }, |
275 | | - }, |
276 | | - { |
277 | | - name: "Symlink creation fails due to permission", |
278 | | - setupFunc: func(t *testing.T) (string, func()) { |
279 | | - if os.Getuid() == 0 { |
280 | | - t.Skip("Skipping permission test when running as root") |
281 | | - } |
282 | | - |
283 | | - tempDir := t.TempDir() |
284 | | - pathDir := filepath.Join(tempDir, "bin") |
285 | | - err := os.Mkdir(pathDir, 0o755) |
286 | | - require.NoError(t, err) |
287 | | - |
288 | | - // Create a trivy executable in PATH |
289 | | - trivyInPath := filepath.Join(pathDir, "trivy") |
290 | | - file, err := os.Create(trivyInPath) |
291 | | - require.NoError(t, err) |
292 | | - file.Close() |
293 | | - err = os.Chmod(trivyInPath, 0o755) |
294 | | - require.NoError(t, err) |
295 | | - |
296 | | - os.Setenv("PATH", pathDir) |
297 | | - |
298 | | - // Try to create symlink in root directory (should fail for non-root users) |
299 | | - targetPath := "/trivy_test_symlink" |
300 | | - |
301 | | - return targetPath, func() { |
302 | | - // Clean up any created symlink |
303 | | - os.Remove(targetPath) |
| 203 | + lookPathSetup: func() { |
| 204 | + currentExecutingLookPath = func(_ string) (string, error) { |
| 205 | + return "", errors.New("executable file not found in $PATH") |
304 | 206 | } |
305 | 207 | }, |
306 | | - expectedError: true, |
307 | | - expectedErrorMsg: "failed to create symlink", |
308 | | - validateFunc: func(t *testing.T, targetPath string) { |
309 | | - // Should not create symlink due to permission error |
310 | | - _, err := os.Lstat(targetPath) |
311 | | - assert.True(t, os.IsNotExist(err), "Target path should not exist when symlink creation fails") |
312 | | - }, |
| 208 | + expectedPath: "", |
| 209 | + expectedError: true, |
| 210 | + expectedErrorMatch: "trivy executable not found at /trivy and not found in PATH", |
313 | 211 | }, |
314 | 212 | } |
315 | 213 |
|
316 | 214 | for _, tc := range testCases { |
317 | 215 | t.Run(tc.name, func(t *testing.T) { |
318 | | - targetPath, cleanup := tc.setupFunc(t) |
319 | | - defer cleanup() |
| 216 | + tc.lookPathSetup() |
320 | 217 |
|
321 | | - // Test the ensureTrivyExecutable function |
322 | | - err := ensureTrivyExecutable(targetPath) |
| 218 | + path, err := findTrivyExecutable() |
323 | 219 |
|
324 | 220 | if tc.expectedError { |
325 | 221 | assert.Error(t, err) |
326 | | - assert.Contains(t, err.Error(), tc.expectedErrorMsg) |
| 222 | + assert.Contains(t, err.Error(), tc.expectedErrorMatch) |
| 223 | + assert.Empty(t, path) |
327 | 224 | } else { |
328 | 225 | assert.NoError(t, err) |
329 | | - } |
330 | | - |
331 | | - if tc.validateFunc != nil { |
332 | | - tc.validateFunc(t, targetPath) |
| 226 | + assert.Equal(t, tc.expectedPath, path) |
333 | 227 | } |
334 | 228 | }) |
335 | 229 | } |
336 | 230 | } |
337 | 231 |
|
338 | | -// TestInitScanner_TrivySymlinkCreation tests the symlink creation logic in initScanner. |
339 | | -func TestInitScanner_TrivySymlinkCreation(t *testing.T) { |
340 | | - // This test verifies that initScanner properly calls ensureTrivyExecutable |
341 | | - // and handles errors appropriately. Since ensureTrivyExecutable is tested |
342 | | - // comprehensively above, this focuses on the integration aspect. |
| 232 | +// TestImageScanner_Scan_TrivyPathLookup tests the logic for using the trivy executable path. |
| 233 | +func TestImageScanner_Scan_TrivyPathLookup(t *testing.T) { |
| 234 | + // Base configuration for the scanner |
| 235 | + baseConfig := DefaultConfig() |
| 236 | + // Dummy image for testing |
| 237 | + img := unversioned.Image{ImageID: "test-image-id", Names: []string{"test-image:latest"}} |
343 | 238 |
|
344 | | - // Save original PATH to restore after test |
345 | | - originalPath := os.Getenv("PATH") |
346 | | - defer func() { os.Setenv("PATH", originalPath) }() |
| 239 | + testCases := []struct { |
| 240 | + name string |
| 241 | + trivyPath string |
| 242 | + expectedStatus ScanStatus |
| 243 | + }{ |
| 244 | + { |
| 245 | + name: "Trivy path set to hardcoded path /trivy", |
| 246 | + trivyPath: trivyCommandName, |
| 247 | + expectedStatus: StatusFailed, // Will fail during actual execution but not due to path issues |
| 248 | + }, |
| 249 | + { |
| 250 | + name: "Trivy path set to system PATH location", |
| 251 | + trivyPath: trivyPathBin, |
| 252 | + expectedStatus: StatusFailed, // Will fail during actual execution but not due to path issues |
| 253 | + }, |
| 254 | + } |
347 | 255 |
|
348 | | - t.Run("initScanner fails when trivy not found", func(t *testing.T) { |
349 | | - // Set PATH to empty directory |
350 | | - tempDir := t.TempDir() |
351 | | - os.Setenv("PATH", tempDir) |
| 256 | + for _, tc := range testCases { |
| 257 | + t.Run(tc.name, func(t *testing.T) { |
| 258 | + scanner := &ImageScanner{ |
| 259 | + config: *baseConfig, |
| 260 | + trivyPath: tc.trivyPath, |
| 261 | + } |
352 | 262 |
|
353 | | - config := DefaultConfig() |
354 | | - scanner, err := initScanner(config) |
| 263 | + status, err := scanner.Scan(img) |
355 | 264 |
|
356 | | - // Should fail because trivy is not found |
357 | | - assert.Error(t, err) |
358 | | - assert.Contains(t, err.Error(), "trivy executable not found") |
359 | | - assert.Nil(t, scanner) |
360 | | - }) |
| 265 | + // The scan will likely fail due to inability to run actual scan in test, |
| 266 | + // but it should not be a "trivy not found" error since the path is already set |
| 267 | + assert.Equal(t, tc.expectedStatus, status, "ScanStatus should be StatusFailed") |
| 268 | + if err != nil { |
| 269 | + assert.NotContains(t, err.Error(), "trivy executable not found", |
| 270 | + "Error should not be about trivy not being found since path is pre-set") |
| 271 | + } |
| 272 | + }) |
| 273 | + } |
361 | 274 | } |
0 commit comments