|
1 | 1 | package main |
2 | 2 |
|
3 | 3 | import ( |
4 | | - "errors" |
| 4 | + "os" |
| 5 | + "path/filepath" |
5 | 6 | "strings" |
6 | 7 | "testing" |
7 | 8 |
|
8 | 9 | "github.com/eraser-dev/eraser/api/unversioned" |
9 | | - |
10 | 10 | "github.com/stretchr/testify/assert" |
| 11 | + "github.com/stretchr/testify/require" |
11 | 12 | ) |
12 | 13 |
|
13 | 14 | const ( |
@@ -172,103 +173,189 @@ func TestCLIArgs(t *testing.T) { |
172 | 173 | } |
173 | 174 | } |
174 | 175 |
|
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 }() |
| 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) }() |
180 | 181 |
|
181 | 182 | testCases := []struct { |
182 | | - name string |
183 | | - lookPathSetup func() |
184 | | - expectedPath string |
185 | | - expectedError bool |
186 | | - expectedErrorMatch string |
| 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) |
187 | 188 | }{ |
188 | 189 | { |
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 | | - } |
| 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() {} |
197 | 203 | }, |
198 | | - expectedPath: trivyPathBin, |
199 | 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() {} |
| 238 | + }, |
| 239 | + 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 | + }, |
200 | 253 | }, |
201 | 254 | { |
202 | 255 | name: "Trivy not found anywhere", |
203 | | - lookPathSetup: func() { |
204 | | - currentExecutingLookPath = func(_ string) (string, error) { |
205 | | - return "", errors.New("executable file not found in $PATH") |
| 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) |
206 | 304 | } |
207 | 305 | }, |
208 | | - expectedPath: "", |
209 | | - expectedError: true, |
210 | | - expectedErrorMatch: "trivy executable not found at /trivy and not found in PATH", |
| 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 | + }, |
211 | 313 | }, |
212 | 314 | } |
213 | 315 |
|
214 | 316 | for _, tc := range testCases { |
215 | 317 | t.Run(tc.name, func(t *testing.T) { |
216 | | - tc.lookPathSetup() |
| 318 | + targetPath, cleanup := tc.setupFunc(t) |
| 319 | + defer cleanup() |
217 | 320 |
|
218 | | - path, err := findTrivyExecutable() |
| 321 | + // Test the ensureTrivyExecutable function |
| 322 | + err := ensureTrivyExecutable(targetPath) |
219 | 323 |
|
220 | 324 | if tc.expectedError { |
221 | 325 | assert.Error(t, err) |
222 | | - assert.Contains(t, err.Error(), tc.expectedErrorMatch) |
223 | | - assert.Empty(t, path) |
| 326 | + assert.Contains(t, err.Error(), tc.expectedErrorMsg) |
224 | 327 | } else { |
225 | 328 | assert.NoError(t, err) |
226 | | - assert.Equal(t, tc.expectedPath, path) |
| 329 | + } |
| 330 | + |
| 331 | + if tc.validateFunc != nil { |
| 332 | + tc.validateFunc(t, targetPath) |
227 | 333 | } |
228 | 334 | }) |
229 | 335 | } |
230 | 336 | } |
231 | 337 |
|
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"}} |
| 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. |
238 | 343 |
|
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 | | - } |
| 344 | + // Save original PATH to restore after test |
| 345 | + originalPath := os.Getenv("PATH") |
| 346 | + defer func() { os.Setenv("PATH", originalPath) }() |
255 | 347 |
|
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 | | - } |
| 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) |
262 | 352 |
|
263 | | - status, err := scanner.Scan(img) |
| 353 | + config := DefaultConfig() |
| 354 | + scanner, err := initScanner(config) |
264 | 355 |
|
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 | | - } |
| 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 | + }) |
274 | 361 | } |
0 commit comments