github.com/adevinta/lava@v0.7.2/internal/engine/engine_test.go (about) 1 // Copyright 2023 Adevinta 2 3 package engine 4 5 import ( 6 "context" 7 "encoding/json" 8 "flag" 9 "fmt" 10 "log/slog" 11 "math/rand" 12 "net/http" 13 "net/http/httptest" 14 "os" 15 "strings" 16 "testing" 17 18 agentconfig "github.com/adevinta/vulcan-agent/config" 19 report "github.com/adevinta/vulcan-report" 20 types "github.com/adevinta/vulcan-types" 21 "github.com/docker/docker/api/types/image" 22 "github.com/jroimartin/clilog" 23 24 "github.com/adevinta/lava/internal/assettypes" 25 "github.com/adevinta/lava/internal/config" 26 "github.com/adevinta/lava/internal/containers" 27 ) 28 29 var testRuntime containers.Runtime 30 31 func TestMain(m *testing.M) { 32 flag.Parse() 33 34 level := slog.LevelError 35 if testing.Verbose() { 36 level = slog.LevelDebug 37 } 38 39 h := clilog.NewCLIHandler(os.Stderr, &clilog.HandlerOptions{Level: level}) 40 slog.SetDefault(slog.New(h)) 41 42 rt, err := containers.GetenvRuntime() 43 if err != nil { 44 fmt.Fprintf(os.Stderr, "error: get env runtime: %v", err) 45 os.Exit(2) 46 } 47 testRuntime = rt 48 49 os.Exit(m.Run()) 50 } 51 52 func TestEngine_Run(t *testing.T) { 53 cli, err := containers.NewDockerdClient(testRuntime) 54 if err != nil { 55 t.Fatalf("could not create dockerd client: %v", err) 56 } 57 defer cli.Close() 58 59 const imgRef = "lava-internal-engine-test:go-test" 60 61 if _, err := cli.ImageBuild(context.Background(), "testdata/engine/lava-engine-test", "Dockerfile", imgRef); err != nil { 62 t.Fatalf("could build Docker image: %v", err) 63 } 64 defer func() { 65 rmOpts := image.RemoveOptions{Force: true, PruneChildren: true} 66 if _, err := cli.ImageRemove(context.Background(), imgRef, rmOpts); err != nil { 67 t.Logf("could not delete test Docker image %q: %v", imgRef, err) 68 } 69 }() 70 71 wantDetails := fmt.Sprintf("lava engine test response %v", rand.Uint64()) 72 73 srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 74 fmt.Fprint(w, wantDetails) 75 })) 76 defer srv.Close() 77 78 t.Logf("test server listening at %v", srv.URL) 79 80 var ( 81 checktypeURLs = []string{"testdata/engine/checktypes_lava_engine_test.json"} 82 targets = []config.Target{ 83 { 84 Identifier: srv.URL, 85 AssetType: types.WebAddress, 86 }, 87 } 88 agentConfig = config.AgentConfig{ 89 PullPolicy: agentconfig.PullPolicyNever, 90 } 91 ) 92 93 eng, err := New(agentConfig, checktypeURLs) 94 if err != nil { 95 t.Fatalf("engine initialization error: %v", err) 96 } 97 defer eng.Close() 98 99 engineReport, err := eng.Run(targets) 100 if err != nil { 101 t.Fatalf("engine run error: %v", err) 102 } 103 104 checkReportTarget(t, engineReport, eng.cli.HostGatewayHostname()) 105 106 var checkReports []report.Report 107 for _, v := range engineReport { 108 checkReports = append(checkReports, v) 109 } 110 111 if len(checkReports) != 1 { 112 t.Fatalf("unexpected number of reports: %v", len(checkReports)) 113 } 114 115 gotReport := checkReports[0] 116 117 if gotReport.Status != "FINISHED" { 118 t.Errorf("unexpected status: %v", gotReport.Status) 119 } 120 121 if gotReport.Target != srv.URL { 122 t.Errorf("unexpected target: got: %v, want: %v", gotReport.Target, srv.URL) 123 } 124 125 if len(gotReport.Vulnerabilities) != 1 { 126 t.Fatalf("unexpected number of vulnerabilities: %v", len(gotReport.Vulnerabilities)) 127 } 128 129 gotDetails := gotReport.Vulnerabilities[0].Details 130 131 if gotDetails != wantDetails { 132 t.Errorf("unexpected details: got: %#q, want: %#q", gotDetails, wantDetails) 133 } 134 } 135 136 func TestEngine_Run_docker_image(t *testing.T) { 137 var ( 138 checktypeURLs = []string{"testdata/engine/checktypes_trivy.json"} 139 targets = []config.Target{ 140 { 141 Identifier: "python:3.4-alpine", 142 AssetType: types.DockerImage, 143 }, 144 } 145 agentConfig = config.AgentConfig{ 146 PullPolicy: agentconfig.PullPolicyAlways, 147 } 148 ) 149 150 eng, err := New(agentConfig, checktypeURLs) 151 if err != nil { 152 t.Fatalf("engine initialization error: %v", err) 153 } 154 defer eng.Close() 155 156 engineReport, err := eng.Run(targets) 157 if err != nil { 158 t.Fatalf("engine run error: %v", err) 159 } 160 161 checkReportTarget(t, engineReport, eng.cli.HostGatewayHostname()) 162 163 var checkReports []report.Report 164 for _, v := range engineReport { 165 checkReports = append(checkReports, v) 166 } 167 168 if len(checkReports) != 1 { 169 t.Fatalf("unexpected number of reports: %v", len(checkReports)) 170 } 171 172 gotReport := checkReports[0] 173 174 if gotReport.Status != "FINISHED" { 175 t.Errorf("unexpected status: %v", gotReport.Status) 176 } 177 178 if len(gotReport.Vulnerabilities) == 0 { 179 t.Errorf("no vulnerabilities found") 180 } 181 182 t.Logf("found %v vulnerabilities", len(gotReport.Vulnerabilities)) 183 } 184 185 func TestEngine_Run_path(t *testing.T) { 186 var ( 187 checktypeURLs = []string{"testdata/engine/checktypes_trivy.json"} 188 agentConfig = config.AgentConfig{ 189 PullPolicy: agentconfig.PullPolicyAlways, 190 } 191 ) 192 193 tests := []struct { 194 name string 195 target config.Target 196 wantStatus string 197 wantVulns bool 198 }{ 199 { 200 name: "dir", 201 target: config.Target{ 202 Identifier: "testdata/engine/vulnpath", 203 AssetType: assettypes.Path, 204 }, 205 wantStatus: "FINISHED", 206 wantVulns: true, 207 }, 208 { 209 name: "file", 210 target: config.Target{ 211 Identifier: "testdata/engine/vulnpath/Dockerfile", 212 AssetType: assettypes.Path, 213 }, 214 wantStatus: "FINISHED", 215 wantVulns: true, 216 }, 217 } 218 219 for _, tt := range tests { 220 t.Run(tt.name, func(t *testing.T) { 221 eng, err := New(agentConfig, checktypeURLs) 222 if err != nil { 223 t.Fatalf("engine initialization error: %v", err) 224 } 225 defer eng.Close() 226 227 engineReport, err := eng.Run([]config.Target{tt.target}) 228 if err != nil { 229 t.Fatalf("engine run error: %v", err) 230 } 231 232 checkReportTarget(t, engineReport, eng.cli.HostGatewayHostname()) 233 234 var checkReports []report.Report 235 for _, v := range engineReport { 236 checkReports = append(checkReports, v) 237 } 238 239 if len(checkReports) != 1 { 240 t.Fatalf("unexpected number of reports: %v", len(checkReports)) 241 } 242 243 gotReport := checkReports[0] 244 245 if gotReport.Status != tt.wantStatus { 246 t.Errorf("unexpected status: %v", gotReport.Status) 247 } 248 249 if (len(gotReport.Vulnerabilities) > 0) != tt.wantVulns { 250 t.Errorf("unexpected number of vulnerabilities: %v", len(gotReport.Vulnerabilities)) 251 } 252 253 t.Logf("found %v vulnerabilities", len(gotReport.Vulnerabilities)) 254 }) 255 } 256 } 257 258 func TestEngine_Run_unreachable_target(t *testing.T) { 259 var ( 260 checktypeURLs = []string{"testdata/engine/checktypes_trivy.json"} 261 agentConfig = config.AgentConfig{ 262 PullPolicy: agentconfig.PullPolicyAlways, 263 } 264 target = config.Target{ 265 Identifier: "testdata/engine/notexist", 266 AssetType: assettypes.Path, 267 } 268 ) 269 270 eng, err := New(agentConfig, checktypeURLs) 271 if err != nil { 272 t.Fatalf("engine initialization error: %v", err) 273 } 274 defer eng.Close() 275 276 if _, err := eng.Run([]config.Target{target}); err == nil { 277 t.Fatal("unexpected nil error") 278 } 279 } 280 281 func TestEngine_Run_not_repo(t *testing.T) { 282 var ( 283 checktypeURLs = []string{"testdata/engine/checktypes_trivy.json"} 284 agentConfig = config.AgentConfig{ 285 PullPolicy: agentconfig.PullPolicyAlways, 286 } 287 target = config.Target{ 288 Identifier: "testdata/engine/vulnpath", 289 AssetType: types.GitRepository, 290 } 291 ) 292 293 eng, err := New(agentConfig, checktypeURLs) 294 if err != nil { 295 t.Fatalf("engine initialization error: %v", err) 296 } 297 defer eng.Close() 298 299 engineReport, err := eng.Run([]config.Target{target}) 300 if err != nil { 301 t.Fatalf("engine run error: %v", err) 302 } 303 304 checkReportTarget(t, engineReport, eng.cli.HostGatewayHostname()) 305 306 var checkReports []report.Report 307 for _, v := range engineReport { 308 checkReports = append(checkReports, v) 309 } 310 311 if len(checkReports) != 0 { 312 t.Fatalf("unexpected number of reports: %v", len(checkReports)) 313 } 314 } 315 316 func TestEngine_Run_no_jobs(t *testing.T) { 317 var ( 318 checktypeURLs = []string{"testdata/engine/checktypes_lava_engine_test.json"} 319 agentConfig = config.AgentConfig{ 320 PullPolicy: agentconfig.PullPolicyNever, 321 } 322 ) 323 324 eng, err := New(agentConfig, checktypeURLs) 325 if err != nil { 326 t.Fatalf("engine initialization error: %v", err) 327 } 328 defer eng.Close() 329 330 engineReport, err := eng.Run(nil) 331 if err != nil { 332 t.Fatalf("engine run error: %v", err) 333 } 334 335 if len(engineReport) != 0 { 336 t.Fatalf("unexpected number of reports: %v", len(engineReport)) 337 } 338 } 339 340 // checkReportTarget encodes report as JSON and looks for substr in 341 // the output. If substr is not found, checkReportTarget calls 342 // t.Errorf. 343 func checkReportTarget(t *testing.T, report Report, substr string) { 344 doc, err := json.MarshalIndent(report, "", " ") 345 if err != nil { 346 t.Fatalf("marshal error: %v", err) 347 } 348 349 if strings.Contains(string(doc), substr) { 350 t.Errorf("report contains %q:\n%s", substr, doc) 351 } 352 }