github.com/ouraigua/jenkins-library@v0.0.0-20231028010029-fbeaf2f3aa9b/cmd/whitesourceExecuteScan_test.go (about) 1 package cmd 2 3 import ( 4 "context" 5 "fmt" 6 "path/filepath" 7 "testing" 8 "time" 9 10 "github.com/SAP/jenkins-library/pkg/format" 11 "github.com/SAP/jenkins-library/pkg/mock" 12 "github.com/SAP/jenkins-library/pkg/piperutils" 13 "github.com/SAP/jenkins-library/pkg/reporting" 14 "github.com/SAP/jenkins-library/pkg/versioning" 15 ws "github.com/SAP/jenkins-library/pkg/whitesource" 16 "github.com/pkg/errors" 17 "github.com/stretchr/testify/assert" 18 19 "github.com/google/go-github/v45/github" 20 ) 21 22 type whitesourceUtilsMock struct { 23 *ws.ScanUtilsMock 24 coordinates versioning.Coordinates 25 usedBuildTool string 26 usedBuildDescriptorFile string 27 usedOptions versioning.Options 28 } 29 30 func (w *whitesourceUtilsMock) GetArtifactCoordinates(buildTool, buildDescriptorFile string, 31 options *versioning.Options, 32 ) (versioning.Coordinates, error) { 33 w.usedBuildTool = buildTool 34 w.usedBuildDescriptorFile = buildDescriptorFile 35 w.usedOptions = *options 36 return w.coordinates, nil 37 } 38 39 const wsTimeNow = "2010-05-10 00:15:42" 40 41 func (w *whitesourceUtilsMock) Now() time.Time { 42 now, _ := time.Parse("2006-01-02 15:04:05", wsTimeNow) 43 return now 44 } 45 46 func (w *whitesourceUtilsMock) GetIssueService() *github.IssuesService { 47 return nil 48 } 49 50 func (w *whitesourceUtilsMock) GetSearchService() *github.SearchService { 51 return nil 52 } 53 54 func newWhitesourceUtilsMock() *whitesourceUtilsMock { 55 return &whitesourceUtilsMock{ 56 ScanUtilsMock: &ws.ScanUtilsMock{ 57 FilesMock: &mock.FilesMock{}, 58 ExecMockRunner: &mock.ExecMockRunner{}, 59 }, 60 coordinates: versioning.Coordinates{ 61 GroupID: "mock-group-id", 62 ArtifactID: "mock-artifact-id", 63 Version: "1.0.42", 64 }, 65 } 66 } 67 68 func TestNewWhitesourceUtils(t *testing.T) { 69 t.Parallel() 70 config := ScanOptions{} 71 utils := newWhitesourceUtils(&config, &github.Client{Issues: &github.IssuesService{}, Search: &github.SearchService{}}) 72 73 assert.NotNil(t, utils.Client) 74 assert.NotNil(t, utils.Command) 75 assert.NotNil(t, utils.Files) 76 } 77 78 func TestRunWhitesourceExecuteScan(t *testing.T) { 79 t.Parallel() 80 t.Run("fails for invalid configured project token", func(t *testing.T) { 81 ctx := context.Background() 82 // init 83 config := ScanOptions{ 84 BuildDescriptorFile: "my-mta.yml", 85 VersioningModel: "major", 86 ProductName: "mock-product", 87 ProjectToken: "no-such-project-token", 88 AgentDownloadURL: "https://whitesource.com/agent.jar", 89 AgentFileName: "ua.jar", 90 } 91 utilsMock := newWhitesourceUtilsMock() 92 utilsMock.AddFile("wss-generated-file.config", []byte("key=value")) 93 systemMock := ws.NewSystemMock("ignored") 94 scan := newWhitesourceScan(&config) 95 cpe := whitesourceExecuteScanCommonPipelineEnvironment{} 96 influx := whitesourceExecuteScanInflux{} 97 // test 98 err := runWhitesourceExecuteScan(ctx, &config, scan, utilsMock, systemMock, &cpe, &influx) 99 // assert 100 assert.EqualError(t, err, "failed to resolve and aggregate project name: failed to get project by token: no project with token 'no-such-project-token' found in Whitesource") 101 assert.Equal(t, "", config.ProjectName) 102 assert.Equal(t, "", scan.AggregateProjectName) 103 }) 104 t.Run("retrieves aggregate project name by configured token", func(t *testing.T) { 105 ctx := context.Background() 106 // init 107 config := ScanOptions{ 108 BuildDescriptorFile: "my-mta.yml", 109 VersioningModel: "major", 110 AgentDownloadURL: "https://whitesource.com/agent.jar", 111 VulnerabilityReportFormat: "pdf", 112 Reporting: true, 113 AgentFileName: "ua.jar", 114 ProductName: "mock-product", 115 ProjectToken: "mock-project-token", 116 } 117 utilsMock := newWhitesourceUtilsMock() 118 utilsMock.AddFile("wss-generated-file.config", []byte("key=value")) 119 lastUpdatedDate := time.Now().Format(ws.DateTimeLayout) 120 systemMock := ws.NewSystemMock(lastUpdatedDate) 121 systemMock.Alerts = []ws.Alert{} 122 scan := newWhitesourceScan(&config) 123 cpe := whitesourceExecuteScanCommonPipelineEnvironment{} 124 influx := whitesourceExecuteScanInflux{} 125 // test 126 err := runWhitesourceExecuteScan(ctx, &config, scan, utilsMock, systemMock, &cpe, &influx) 127 // assert 128 assert.NoError(t, err) 129 // Retrieved project name is stored in scan.AggregateProjectName, but not in config.ProjectName 130 // in order to differentiate between aggregate-project scanning and multi-project scanning. 131 assert.Equal(t, "", config.ProjectName) 132 assert.Equal(t, "mock-project", scan.AggregateProjectName) 133 if assert.Len(t, utilsMock.DownloadedFiles, 1) { 134 assert.Equal(t, ws.DownloadedFile{ 135 SourceURL: "https://whitesource.com/agent.jar", 136 FilePath: "ua.jar", 137 }, utilsMock.DownloadedFiles[0]) 138 } 139 if assert.Len(t, cpe.custom.whitesourceProjectNames, 1) { 140 assert.Equal(t, []string{"mock-project - 1"}, cpe.custom.whitesourceProjectNames) 141 } 142 assert.True(t, utilsMock.HasWrittenFile(filepath.Join(ws.ReportsDirectory, "mock-project - 1-vulnerability-report.pdf"))) 143 assert.True(t, utilsMock.HasWrittenFile(filepath.Join(ws.ReportsDirectory, "mock-project - 1-vulnerability-report.pdf"))) 144 assert.Equal(t, 3, len(utilsMock.ExecMockRunner.Calls), "no InstallCommand must be executed") 145 }) 146 t.Run("executes the InstallCommand prior to the scan", func(t *testing.T) { 147 ctx := context.Background() 148 // init 149 config := ScanOptions{ 150 BuildDescriptorFile: "my-mta.yml", 151 VersioningModel: "major", 152 AgentDownloadURL: "https://whitesource.com/agent.jar", 153 VulnerabilityReportFormat: "pdf", 154 Reporting: true, 155 AgentFileName: "ua.jar", 156 ProductName: "mock-product", 157 ProjectToken: "mock-project-token", 158 InstallCommand: "echo hello world", 159 } 160 utilsMock := newWhitesourceUtilsMock() 161 utilsMock.AddFile("wss-generated-file.config", []byte("key=value")) 162 lastUpdatedDate := time.Now().Format(ws.DateTimeLayout) 163 systemMock := ws.NewSystemMock(lastUpdatedDate) 164 systemMock.Alerts = []ws.Alert{} 165 scan := newWhitesourceScan(&config) 166 cpe := whitesourceExecuteScanCommonPipelineEnvironment{} 167 influx := whitesourceExecuteScanInflux{} 168 // test 169 err := runWhitesourceExecuteScan(ctx, &config, scan, utilsMock, systemMock, &cpe, &influx) 170 // assert 171 assert.NoError(t, err) 172 assert.Equal(t, 4, len(utilsMock.ExecMockRunner.Calls), "InstallCommand not executed") 173 assert.Equal(t, mock.ExecCall{Exec: "echo", Params: []string{"hello", "world"}}, utilsMock.ExecMockRunner.Calls[0], "run command/params of InstallCommand incorrect") 174 }) 175 t.Run("fails if the InstallCommand fails", func(t *testing.T) { 176 ctx := context.Background() 177 // init 178 config := ScanOptions{ 179 BuildDescriptorFile: "my-mta.yml", 180 VersioningModel: "major", 181 AgentDownloadURL: "https://whitesource.com/agent.jar", 182 VulnerabilityReportFormat: "pdf", 183 Reporting: true, 184 AgentFileName: "ua.jar", 185 ProductName: "mock-product", 186 ProjectToken: "mock-project-token", 187 InstallCommand: "echo this-will-fail", 188 } 189 utilsMock := newWhitesourceUtilsMock() 190 utilsMock.AddFile("wss-generated-file.config", []byte("key=value")) 191 lastUpdatedDate := time.Now().Format(ws.DateTimeLayout) 192 systemMock := ws.NewSystemMock(lastUpdatedDate) 193 systemMock.Alerts = []ws.Alert{} 194 scan := newWhitesourceScan(&config) 195 cpe := whitesourceExecuteScanCommonPipelineEnvironment{} 196 influx := whitesourceExecuteScanInflux{} 197 utilsMock.ExecMockRunner.ShouldFailOnCommand = map[string]error{ 198 "echo this-will-fail": errors.New("error case"), 199 } 200 // test 201 err := runWhitesourceExecuteScan(ctx, &config, scan, utilsMock, systemMock, &cpe, &influx) 202 // assert 203 assert.EqualError(t, err, "failed to execute WhiteSource scan: failed to execute Scan: failed to execute install command: echo this-will-fail: error case") 204 }) 205 } 206 207 func TestCheckAndReportScanResults(t *testing.T) { 208 t.Parallel() 209 t.Run("no reports requested", func(t *testing.T) { 210 ctx := context.Background() 211 // init 212 config := &ScanOptions{ 213 ProductToken: "mock-product-token", 214 ProjectToken: "mock-project-token", 215 Version: "1", 216 } 217 scan := newWhitesourceScan(config) 218 utils := newWhitesourceUtilsMock() 219 system := ws.NewSystemMock(time.Now().Format(ws.DateTimeLayout)) 220 influx := whitesourceExecuteScanInflux{} 221 // test 222 _, err := checkAndReportScanResults(ctx, config, scan, utils, system, &influx) 223 // assert 224 assert.NoError(t, err) 225 vPath := filepath.Join(ws.ReportsDirectory, "mock-project-vulnerability-report.txt") 226 assert.False(t, utils.HasWrittenFile(vPath)) 227 rPath := filepath.Join(ws.ReportsDirectory, "mock-project-risk-report.pdf") 228 assert.False(t, utils.HasWrittenFile(rPath)) 229 }) 230 t.Run("check vulnerabilities - invalid limit", func(t *testing.T) { 231 ctx := context.Background() 232 // init 233 config := &ScanOptions{ 234 SecurityVulnerabilities: true, 235 CvssSeverityLimit: "invalid", 236 } 237 scan := newWhitesourceScan(config) 238 utils := newWhitesourceUtilsMock() 239 system := ws.NewSystemMock(time.Now().Format(ws.DateTimeLayout)) 240 influx := whitesourceExecuteScanInflux{} 241 // test 242 _, err := checkAndReportScanResults(ctx, config, scan, utils, system, &influx) 243 // assert 244 assert.EqualError(t, err, "failed to parse parameter cvssSeverityLimit (invalid) as floating point number: strconv.ParseFloat: parsing \"invalid\": invalid syntax") 245 }) 246 t.Run("check vulnerabilities - limit not hit", func(t *testing.T) { 247 ctx := context.Background() 248 // init 249 config := &ScanOptions{ 250 ProductToken: "mock-product-token", 251 ProjectToken: "mock-project-token", 252 Version: "1", 253 SecurityVulnerabilities: true, 254 CvssSeverityLimit: "6.0", 255 } 256 scan := newWhitesourceScan(config) 257 utils := newWhitesourceUtilsMock() 258 system := ws.NewSystemMock(time.Now().Format(ws.DateTimeLayout)) 259 influx := whitesourceExecuteScanInflux{} 260 // test 261 _, err := checkAndReportScanResults(ctx, config, scan, utils, system, &influx) 262 // assert 263 assert.NoError(t, err) 264 }) 265 t.Run("check vulnerabilities - limit exceeded", func(t *testing.T) { 266 ctx := context.Background() 267 // init 268 config := &ScanOptions{ 269 ProductToken: "mock-product-token", 270 ProjectName: "mock-project - 1", 271 ProjectToken: "mock-project-token", 272 Version: "1", 273 SecurityVulnerabilities: true, 274 CvssSeverityLimit: "4", 275 FailOnSevereVulnerabilities: true, 276 } 277 scan := newWhitesourceScan(config) 278 utils := newWhitesourceUtilsMock() 279 system := ws.NewSystemMock(time.Now().Format(ws.DateTimeLayout)) 280 influx := whitesourceExecuteScanInflux{} 281 // test 282 _, err := checkAndReportScanResults(ctx, config, scan, utils, system, &influx) 283 // assert 284 assert.EqualError(t, err, "1 Open Source Software Security vulnerabilities with CVSS score greater or equal to 4.0 detected in project mock-project - 1") 285 }) 286 } 287 288 func TestResolveProjectIdentifiers(t *testing.T) { 289 t.Parallel() 290 t.Run("success", func(t *testing.T) { 291 // init 292 config := ScanOptions{ 293 BuildTool: "mta", 294 BuildDescriptorFile: "my-mta.yml", 295 VersioningModel: "major", 296 ProductName: "mock-product", 297 M2Path: "m2/path", 298 ProjectSettingsFile: "project-settings.xml", 299 GlobalSettingsFile: "global-settings.xml", 300 } 301 utilsMock := newWhitesourceUtilsMock() 302 systemMock := ws.NewSystemMock("ignored") 303 scan := newWhitesourceScan(&config) 304 // test 305 err := resolveProjectIdentifiers(&config, scan, utilsMock, systemMock) 306 // assert 307 if assert.NoError(t, err) { 308 assert.Equal(t, "mock-group-id-mock-artifact-id", scan.AggregateProjectName) 309 assert.Equal(t, "1", config.Version) 310 assert.Equal(t, "mock-product-token", config.ProductToken) 311 assert.Equal(t, "mta", utilsMock.usedBuildTool) 312 assert.Equal(t, "my-mta.yml", utilsMock.usedBuildDescriptorFile) 313 assert.Equal(t, "project-settings.xml", utilsMock.usedOptions.ProjectSettingsFile) 314 assert.Equal(t, "global-settings.xml", utilsMock.usedOptions.GlobalSettingsFile) 315 assert.Equal(t, "m2/path", utilsMock.usedOptions.M2Path) 316 } 317 }) 318 t.Run("success - with version from default", func(t *testing.T) { 319 // init 320 config := ScanOptions{ 321 BuildTool: "mta", 322 BuildDescriptorFile: "my-mta.yml", 323 Version: "1.2.3-20200101", 324 VersioningModel: "major", 325 ProductName: "mock-product", 326 M2Path: "m2/path", 327 ProjectSettingsFile: "project-settings.xml", 328 GlobalSettingsFile: "global-settings.xml", 329 } 330 utilsMock := newWhitesourceUtilsMock() 331 systemMock := ws.NewSystemMock("ignored") 332 scan := newWhitesourceScan(&config) 333 // test 334 err := resolveProjectIdentifiers(&config, scan, utilsMock, systemMock) 335 // assert 336 if assert.NoError(t, err) { 337 assert.Equal(t, "mock-group-id-mock-artifact-id", scan.AggregateProjectName) 338 assert.Equal(t, "1", config.Version) 339 assert.Equal(t, "mock-product-token", config.ProductToken) 340 assert.Equal(t, "mta", utilsMock.usedBuildTool) 341 assert.Equal(t, "my-mta.yml", utilsMock.usedBuildDescriptorFile) 342 assert.Equal(t, "project-settings.xml", utilsMock.usedOptions.ProjectSettingsFile) 343 assert.Equal(t, "global-settings.xml", utilsMock.usedOptions.GlobalSettingsFile) 344 assert.Equal(t, "m2/path", utilsMock.usedOptions.M2Path) 345 } 346 }) 347 t.Run("success - with custom scan version", func(t *testing.T) { 348 // init 349 config := ScanOptions{ 350 BuildTool: "mta", 351 BuildDescriptorFile: "my-mta.yml", 352 CustomScanVersion: "2.3.4", 353 VersioningModel: "major", 354 ProductName: "mock-product", 355 M2Path: "m2/path", 356 ProjectSettingsFile: "project-settings.xml", 357 GlobalSettingsFile: "global-settings.xml", 358 } 359 utilsMock := newWhitesourceUtilsMock() 360 systemMock := ws.NewSystemMock("ignored") 361 scan := newWhitesourceScan(&config) 362 // test 363 err := resolveProjectIdentifiers(&config, scan, utilsMock, systemMock) 364 // assert 365 if assert.NoError(t, err) { 366 assert.Equal(t, "mock-group-id-mock-artifact-id", scan.AggregateProjectName) 367 assert.Equal(t, "2.3.4", config.Version) 368 assert.Equal(t, "mock-product-token", config.ProductToken) 369 assert.Equal(t, "mta", utilsMock.usedBuildTool) 370 assert.Equal(t, "my-mta.yml", utilsMock.usedBuildDescriptorFile) 371 assert.Equal(t, "project-settings.xml", utilsMock.usedOptions.ProjectSettingsFile) 372 assert.Equal(t, "global-settings.xml", utilsMock.usedOptions.GlobalSettingsFile) 373 assert.Equal(t, "m2/path", utilsMock.usedOptions.M2Path) 374 } 375 }) 376 t.Run("success - with custom scan version (projectName is filled)", func(t *testing.T) { 377 // init 378 config := ScanOptions{ 379 BuildTool: "mta", 380 CustomScanVersion: "latest", 381 VersioningModel: "major", 382 ProductName: "mock-product", 383 ProjectName: "mock-project", 384 Version: "0.0.1", 385 } 386 utilsMock := newWhitesourceUtilsMock() 387 systemMock := ws.NewSystemMock("ignored") 388 scan := newWhitesourceScan(&config) 389 // test 390 err := resolveProjectIdentifiers(&config, scan, utilsMock, systemMock) 391 // assert 392 if assert.NoError(t, err) { 393 assert.Equal(t, "mock-project", scan.AggregateProjectName) 394 assert.Equal(t, "latest", config.Version) 395 assert.Equal(t, "mock-product-token", config.ProductToken) 396 } 397 }) 398 t.Run("success - with version from default (projectName is filled)", func(t *testing.T) { 399 // init 400 config := ScanOptions{ 401 BuildTool: "mta", 402 VersioningModel: "major-minor", 403 ProductName: "mock-product", 404 ProjectName: "mock-project", 405 Version: "1.2.3", 406 } 407 utilsMock := newWhitesourceUtilsMock() 408 systemMock := ws.NewSystemMock("ignored") 409 scan := newWhitesourceScan(&config) 410 // test 411 err := resolveProjectIdentifiers(&config, scan, utilsMock, systemMock) 412 // assert 413 if assert.NoError(t, err) { 414 assert.Equal(t, "mock-project", scan.AggregateProjectName) 415 assert.Equal(t, "1.2", config.Version) 416 assert.Equal(t, "mock-product-token", config.ProductToken) 417 } 418 }) 419 t.Run("retrieves token for configured project name", func(t *testing.T) { 420 // init 421 config := ScanOptions{ 422 BuildTool: "mta", 423 BuildDescriptorFile: "my-mta.yml", 424 VersioningModel: "major", 425 ProductName: "mock-product", 426 ProjectName: "mock-project", 427 } 428 utilsMock := newWhitesourceUtilsMock() 429 systemMock := ws.NewSystemMock("ignored") 430 scan := newWhitesourceScan(&config) 431 // test 432 err := resolveProjectIdentifiers(&config, scan, utilsMock, systemMock) 433 // assert 434 if assert.NoError(t, err) { 435 assert.Equal(t, "mock-project", scan.AggregateProjectName) 436 assert.Equal(t, "1", config.Version) 437 assert.Equal(t, "mock-product-token", config.ProductToken) 438 assert.Equal(t, "mta", utilsMock.usedBuildTool) 439 assert.Equal(t, "my-mta.yml", utilsMock.usedBuildDescriptorFile) 440 assert.Equal(t, "mock-project-token", config.ProjectToken) 441 } 442 }) 443 t.Run("product not found", func(t *testing.T) { 444 // init 445 config := ScanOptions{ 446 BuildTool: "mta", 447 VersioningModel: "major", 448 ProductName: "does-not-exist", 449 } 450 utilsMock := newWhitesourceUtilsMock() 451 systemMock := ws.NewSystemMock("ignored") 452 scan := newWhitesourceScan(&config) 453 // test 454 err := resolveProjectIdentifiers(&config, scan, utilsMock, systemMock) 455 // assert 456 assert.EqualError(t, err, "error resolving product token: failed to get product by name: no product with name 'does-not-exist' found in Whitesource") 457 }) 458 t.Run("product not found, created from pipeline", func(t *testing.T) { 459 // init 460 config := ScanOptions{ 461 BuildTool: "mta", 462 CreateProductFromPipeline: true, 463 EmailAddressesOfInitialProductAdmins: []string{"user1@domain.org", "user2@domain.org"}, 464 VersioningModel: "major", 465 ProductName: "created-by-pipeline", 466 } 467 utilsMock := newWhitesourceUtilsMock() 468 systemMock := ws.NewSystemMock("ignored") 469 scan := newWhitesourceScan(&config) 470 // test 471 err := resolveProjectIdentifiers(&config, scan, utilsMock, systemMock) 472 // assert 473 assert.NoError(t, err) 474 assert.Len(t, systemMock.Products, 2) 475 assert.Equal(t, "created-by-pipeline", systemMock.Products[1].Name) 476 assert.Equal(t, "mock-product-token-1", config.ProductToken) 477 }) 478 } 479 480 func TestCheckPolicyViolations(t *testing.T) { 481 t.Parallel() 482 483 t.Run("success - no violations", func(t *testing.T) { 484 ctx := context.Background() 485 config := ScanOptions{ProductName: "mock-product", Version: "1"} 486 scan := newWhitesourceScan(&config) 487 if err := scan.AppendScannedProject("testProject1"); err != nil { 488 t.Fail() 489 } 490 systemMock := ws.NewSystemMock("ignored") 491 systemMock.Alerts = []ws.Alert{} 492 utilsMock := newWhitesourceUtilsMock() 493 reportPaths := []piperutils.Path{ 494 {Target: filepath.Join("whitesource", "report1.pdf")}, 495 {Target: filepath.Join("whitesource", "report2.pdf")}, 496 } 497 influx := whitesourceExecuteScanInflux{} 498 499 path, err := checkPolicyViolations(ctx, &config, scan, systemMock, utilsMock, reportPaths, &influx) 500 assert.NoError(t, err) 501 assert.Equal(t, filepath.Join(ws.ReportsDirectory, "whitesource-ip.json"), path.Target) 502 503 fileContent, _ := utilsMock.FileRead(path.Target) 504 content := string(fileContent) 505 assert.Contains(t, content, `"policyViolations":0`) 506 assert.Contains(t, content, `"reports":["report1.pdf","report2.pdf"]`) 507 508 exists, err := utilsMock.FileExists(filepath.Join(reporting.StepReportDirectory, "whitesourceExecuteScan_ip_2d3120020f3f46393a54575a7f6f5675ad536721.json")) 509 assert.True(t, exists) 510 }) 511 512 t.Run("success - no reports", func(t *testing.T) { 513 ctx := context.Background() 514 config := ScanOptions{} 515 scan := newWhitesourceScan(&config) 516 if err := scan.AppendScannedProject("testProject1"); err != nil { 517 t.Fail() 518 } 519 systemMock := ws.NewSystemMock("ignored") 520 systemMock.Alerts = []ws.Alert{} 521 utilsMock := newWhitesourceUtilsMock() 522 reportPaths := []piperutils.Path{} 523 influx := whitesourceExecuteScanInflux{} 524 525 path, err := checkPolicyViolations(ctx, &config, scan, systemMock, utilsMock, reportPaths, &influx) 526 assert.NoError(t, err) 527 528 fileContent, _ := utilsMock.FileRead(path.Target) 529 content := string(fileContent) 530 assert.Contains(t, content, `reports":[]`) 531 }) 532 533 t.Run("error - policy violations", func(t *testing.T) { 534 ctx := context.Background() 535 config := ScanOptions{FailOnSevereVulnerabilities: true} 536 scan := newWhitesourceScan(&config) 537 if err := scan.AppendScannedProject("testProject1"); err != nil { 538 t.Fail() 539 } 540 systemMock := ws.NewSystemMock("ignored") 541 systemMock.Alerts = []ws.Alert{ 542 {Vulnerability: ws.Vulnerability{Name: "policyVul1"}}, 543 {Vulnerability: ws.Vulnerability{Name: "policyVul2"}}, 544 } 545 utilsMock := newWhitesourceUtilsMock() 546 reportPaths := []piperutils.Path{ 547 {Target: "report1.pdf"}, 548 {Target: "report2.pdf"}, 549 } 550 influx := whitesourceExecuteScanInflux{} 551 552 path, err := checkPolicyViolations(ctx, &config, scan, systemMock, utilsMock, reportPaths, &influx) 553 assert.Contains(t, fmt.Sprint(err), "2 policy violation(s) found") 554 555 fileContent, _ := utilsMock.FileRead(path.Target) 556 content := string(fileContent) 557 assert.Contains(t, content, `"policyViolations":2`) 558 assert.Contains(t, content, `"reports":["report1.pdf","report2.pdf"]`) 559 }) 560 561 t.Run("error - get alerts", func(t *testing.T) { 562 ctx := context.Background() 563 config := ScanOptions{} 564 scan := newWhitesourceScan(&config) 565 if err := scan.AppendScannedProject("testProject1"); err != nil { 566 t.Fail() 567 } 568 systemMock := ws.NewSystemMock("ignored") 569 systemMock.AlertError = fmt.Errorf("failed to read alerts") 570 utilsMock := newWhitesourceUtilsMock() 571 reportPaths := []piperutils.Path{} 572 influx := whitesourceExecuteScanInflux{} 573 574 _, err := checkPolicyViolations(ctx, &config, scan, systemMock, utilsMock, reportPaths, &influx) 575 assert.Contains(t, fmt.Sprint(err), "failed to retrieve project policy alerts from WhiteSource") 576 }) 577 578 t.Run("error - write file", func(t *testing.T) { 579 ctx := context.Background() 580 config := ScanOptions{} 581 scan := newWhitesourceScan(&config) 582 if err := scan.AppendScannedProject("testProject1"); err != nil { 583 t.Fail() 584 } 585 systemMock := ws.NewSystemMock("ignored") 586 systemMock.Alerts = []ws.Alert{} 587 utilsMock := newWhitesourceUtilsMock() 588 utilsMock.FileWriteError = fmt.Errorf("failed to write file") 589 reportPaths := []piperutils.Path{} 590 influx := whitesourceExecuteScanInflux{} 591 592 _, err := checkPolicyViolations(ctx, &config, scan, systemMock, utilsMock, reportPaths, &influx) 593 assert.Contains(t, fmt.Sprint(err), "failed to write policy violation report:") 594 }) 595 596 t.Run("failed to write json report", func(t *testing.T) { 597 ctx := context.Background() 598 config := ScanOptions{ProductName: "mock-product", Version: "1"} 599 scan := newWhitesourceScan(&config) 600 if err := scan.AppendScannedProject("testProject1"); err != nil { 601 t.Fail() 602 } 603 systemMock := ws.NewSystemMock("ignored") 604 systemMock.Alerts = []ws.Alert{} 605 utilsMock := newWhitesourceUtilsMock() 606 utilsMock.FileWriteErrors = map[string]error{ 607 filepath.Join(reporting.StepReportDirectory, "whitesourceExecuteScan_ip_2d3120020f3f46393a54575a7f6f5675ad536721.json"): fmt.Errorf("write error"), 608 } 609 reportPaths := []piperutils.Path{} 610 influx := whitesourceExecuteScanInflux{} 611 612 _, err := checkPolicyViolations(ctx, &config, scan, systemMock, utilsMock, reportPaths, &influx) 613 assert.Contains(t, fmt.Sprint(err), "failed to write json report") 614 }) 615 } 616 617 func TestCheckSecurityViolations(t *testing.T) { 618 t.Parallel() 619 620 t.Run("success - non-aggregated", func(t *testing.T) { 621 ctx := context.Background() 622 config := ScanOptions{ 623 CvssSeverityLimit: "7", 624 } 625 scan := newWhitesourceScan(&config) 626 if err := scan.AppendScannedProject("testProject1"); err != nil { 627 t.Fail() 628 } 629 systemMock := ws.NewSystemMock("ignored") 630 systemMock.Alerts = []ws.Alert{ 631 {Vulnerability: ws.Vulnerability{Name: "vul1", CVSS3Score: 6.0}}, 632 } 633 utilsMock := newWhitesourceUtilsMock() 634 influx := whitesourceExecuteScanInflux{} 635 636 reportPaths, err := checkSecurityViolations(ctx, &config, scan, systemMock, utilsMock, &influx) 637 assert.NoError(t, err) 638 fileContent, err := utilsMock.FileRead(reportPaths[0].Target) 639 assert.NoError(t, err) 640 assert.True(t, len(fileContent) > 0) 641 }) 642 643 t.Run("success - aggregated", func(t *testing.T) { 644 ctx := context.Background() 645 config := ScanOptions{ 646 CvssSeverityLimit: "7", 647 ProjectToken: "theProjectToken", 648 } 649 scan := newWhitesourceScan(&config) 650 systemMock := ws.NewSystemMock("ignored") 651 systemMock.Alerts = []ws.Alert{ 652 {Vulnerability: ws.Vulnerability{Name: "vul1", CVSS3Score: 6.0}}, 653 } 654 utilsMock := newWhitesourceUtilsMock() 655 influx := whitesourceExecuteScanInflux{} 656 657 reportPaths, err := checkSecurityViolations(ctx, &config, scan, systemMock, utilsMock, &influx) 658 assert.NoError(t, err) 659 assert.Equal(t, 3, len(reportPaths)) 660 }) 661 662 t.Run("error - wrong limit", func(t *testing.T) { 663 ctx := context.Background() 664 config := ScanOptions{CvssSeverityLimit: "x"} 665 scan := newWhitesourceScan(&config) 666 systemMock := ws.NewSystemMock("ignored") 667 utilsMock := newWhitesourceUtilsMock() 668 influx := whitesourceExecuteScanInflux{} 669 670 _, err := checkSecurityViolations(ctx, &config, scan, systemMock, utilsMock, &influx) 671 assert.Contains(t, fmt.Sprint(err), "failed to parse parameter cvssSeverityLimit") 672 }) 673 674 t.Run("error - non-aggregated", func(t *testing.T) { 675 ctx := context.Background() 676 config := ScanOptions{ 677 CvssSeverityLimit: "5", 678 FailOnSevereVulnerabilities: true, 679 } 680 scan := newWhitesourceScan(&config) 681 if err := scan.AppendScannedProject("testProject1"); err != nil { 682 t.Fail() 683 } 684 systemMock := ws.NewSystemMock("ignored") 685 systemMock.Alerts = []ws.Alert{ 686 {Vulnerability: ws.Vulnerability{Name: "vul1", CVSS3Score: 6.0}}, 687 } 688 utilsMock := newWhitesourceUtilsMock() 689 influx := whitesourceExecuteScanInflux{} 690 691 reportPaths, err := checkSecurityViolations(ctx, &config, scan, systemMock, utilsMock, &influx) 692 assert.Contains(t, fmt.Sprint(err), "1 Open Source Software Security vulnerabilities") 693 fileContent, err := utilsMock.FileRead(reportPaths[0].Target) 694 assert.NoError(t, err) 695 assert.True(t, len(fileContent) > 0) 696 }) 697 698 t.Run("error - aggregated", func(t *testing.T) { 699 ctx := context.Background() 700 config := ScanOptions{ 701 CvssSeverityLimit: "5", 702 ProjectToken: "theProjectToken", 703 FailOnSevereVulnerabilities: true, 704 } 705 scan := newWhitesourceScan(&config) 706 systemMock := ws.NewSystemMock("ignored") 707 systemMock.Alerts = []ws.Alert{ 708 {Vulnerability: ws.Vulnerability{Name: "vul1", CVSS3Score: 6.0}}, 709 } 710 utilsMock := newWhitesourceUtilsMock() 711 influx := whitesourceExecuteScanInflux{} 712 713 reportPaths, err := checkSecurityViolations(ctx, &config, scan, systemMock, utilsMock, &influx) 714 assert.Contains(t, fmt.Sprint(err), "1 Open Source Software Security vulnerabilities") 715 assert.Equal(t, 3, len(reportPaths)) 716 }) 717 } 718 719 func TestCheckProjectSecurityViolations(t *testing.T) { 720 project := ws.Project{Name: "testProject - 1", Token: "testToken"} 721 722 t.Run("success - no alerts", func(t *testing.T) { 723 systemMock := ws.NewSystemMock("ignored") 724 systemMock.Alerts = []ws.Alert{} 725 influx := whitesourceExecuteScanInflux{} 726 727 severeVulnerabilities, alerts, assessedAlerts, err := checkProjectSecurityViolations(&ScanOptions{FailOnSevereVulnerabilities: true}, 7.0, project, systemMock, &[]format.Assessment{}, &influx) 728 assert.NoError(t, err) 729 assert.Equal(t, 0, severeVulnerabilities) 730 assert.Equal(t, 0, len(alerts)) 731 assert.Equal(t, 0, len(assessedAlerts)) 732 }) 733 734 t.Run("error - some vulnerabilities", func(t *testing.T) { 735 systemMock := ws.NewSystemMock("ignored") 736 systemMock.Alerts = []ws.Alert{ 737 {Vulnerability: ws.Vulnerability{CVSS3Score: 7, Name: "CVE-2025-001"}, Library: ws.Library{KeyID: 42, Name: "test", GroupID: "com.sap", ArtifactID: "test", Version: "1.2.3", LibType: "Java"}}, 738 {Vulnerability: ws.Vulnerability{CVSS3Score: 6, Name: "CVE-2025-002"}, Library: ws.Library{KeyID: 42, Name: "test", GroupID: "com.sap", ArtifactID: "test", Version: "1.2.3", LibType: "Java"}}, 739 } 740 influx := whitesourceExecuteScanInflux{} 741 742 severeVulnerabilities, alerts, assessedAlerts, err := checkProjectSecurityViolations(&ScanOptions{FailOnSevereVulnerabilities: true}, 7.0, project, systemMock, &[]format.Assessment{}, &influx) 743 assert.Contains(t, fmt.Sprint(err), "1 Open Source Software Security vulnerabilities") 744 assert.Equal(t, 1, severeVulnerabilities) 745 assert.Equal(t, 2, len(alerts)) 746 assert.Equal(t, 0, len(assessedAlerts)) 747 }) 748 749 t.Run("success - assessed vulnerabilities", func(t *testing.T) { 750 systemMock := ws.NewSystemMock("ignored") 751 systemMock.Alerts = []ws.Alert{ 752 {Vulnerability: ws.Vulnerability{CVSS3Score: 7.8, Name: "CVE-2025-001"}, Library: ws.Library{KeyID: 42, Name: "test", GroupID: "com.sap", ArtifactID: "test", Version: "1.2.3", LibType: "Java"}}, 753 {Vulnerability: ws.Vulnerability{CVSS3Score: 6, Name: "CVE-2025-002"}, Library: ws.Library{KeyID: 42, Name: "test", GroupID: "com.sap", ArtifactID: "test", Version: "1.2.3", LibType: "Java"}}, 754 } 755 influx := whitesourceExecuteScanInflux{} 756 757 severeVulnerabilities, alerts, assessedAlerts, err := checkProjectSecurityViolations(&ScanOptions{FailOnSevereVulnerabilities: true}, 7.0, project, systemMock, &[]format.Assessment{{Vulnerability: "CVE-2025-001", Purls: []format.Purl{{Purl: "pkg:/maven/com.sap/test@1.2.3"}}}, {Vulnerability: "CVE-2025-002", Purls: []format.Purl{{Purl: "pkg:/maven/com.sap/test@1.2.3"}}}}, &influx) 758 assert.NoError(t, err) 759 assert.Equal(t, 0, severeVulnerabilities) 760 assert.Equal(t, 0, len(alerts)) 761 assert.Equal(t, 2, len(assessedAlerts)) 762 }) 763 764 t.Run("error - WhiteSource failure", func(t *testing.T) { 765 systemMock := ws.NewSystemMock("ignored") 766 systemMock.AlertError = fmt.Errorf("failed to read alerts") 767 influx := whitesourceExecuteScanInflux{} 768 769 _, _, _, err := checkProjectSecurityViolations(&ScanOptions{FailOnSevereVulnerabilities: true}, 7.0, project, systemMock, &[]format.Assessment{}, &influx) 770 assert.Contains(t, fmt.Sprint(err), "failed to retrieve project alerts from WhiteSource") 771 }) 772 } 773 774 func TestAggregateVersionWideLibraries(t *testing.T) { 775 t.Parallel() 776 t.Run("happy path", func(t *testing.T) { 777 // init 778 config := &ScanOptions{ 779 FailOnSevereVulnerabilities: true, 780 ProductToken: "mock-product-token", 781 Version: "1", 782 } 783 utils := newWhitesourceUtilsMock() 784 system := ws.NewSystemMock("2010-05-30 00:15:00 +0100") 785 // test 786 err := aggregateVersionWideLibraries(config, utils, system) 787 // assert 788 resource := filepath.Join(ws.ReportsDirectory, "libraries-20100510-001542.csv") 789 if assert.NoError(t, err) && assert.True(t, utils.HasWrittenFile(resource)) { 790 contents, _ := utils.FileRead(resource) 791 asString := string(contents) 792 assert.Equal(t, "Library Name, Project Name\nmock-library, mock-project\n", asString) 793 c, _ := utils.ReadFile("/whitesourceExecuteScan_reports.json") 794 assert.NotEmpty(t, c) 795 } 796 }) 797 } 798 799 func TestAggregateVersionWideVulnerabilities(t *testing.T) { 800 t.Parallel() 801 t.Run("happy path", func(t *testing.T) { 802 // init 803 config := &ScanOptions{ 804 ProductToken: "mock-product-token", 805 Version: "1", 806 } 807 utils := newWhitesourceUtilsMock() 808 system := ws.NewSystemMock("2010-05-30 00:15:00 +0100") 809 // test 810 err := aggregateVersionWideVulnerabilities(config, utils, system) 811 // assert 812 resource := filepath.Join(ws.ReportsDirectory, "project-names-aggregated.txt") 813 assert.NoError(t, err) 814 if assert.True(t, utils.HasWrittenFile(resource)) { 815 contents, _ := utils.FileRead(resource) 816 asString := string(contents) 817 assert.Equal(t, "mock-project - 1\n", asString) 818 } 819 reportSheet := filepath.Join(ws.ReportsDirectory, "vulnerabilities-20100510-001542.xlsx") 820 sheetContents, err := utils.FileRead(reportSheet) 821 assert.NoError(t, err) 822 assert.NotEmpty(t, sheetContents) 823 c, _ := utils.ReadFile("whitesourceExecuteScan_reports.json") 824 assert.NotEmpty(t, c) 825 }) 826 } 827 828 func TestPersistScannedProjects(t *testing.T) { 829 t.Parallel() 830 t.Run("write 1 scanned projects", func(t *testing.T) { 831 // init 832 cpe := whitesourceExecuteScanCommonPipelineEnvironment{} 833 config := &ScanOptions{Version: "1"} 834 scan := newWhitesourceScan(config) 835 _ = scan.AppendScannedProject("project") 836 // test 837 persistScannedProjects(config, scan, &cpe) 838 // assert 839 assert.Equal(t, []string{"project - 1"}, cpe.custom.whitesourceProjectNames) 840 }) 841 t.Run("write 2 scanned projects", func(t *testing.T) { 842 // init 843 cpe := whitesourceExecuteScanCommonPipelineEnvironment{} 844 config := &ScanOptions{Version: "1"} 845 scan := newWhitesourceScan(config) 846 _ = scan.AppendScannedProject("project-app") 847 _ = scan.AppendScannedProject("project-db") 848 // test 849 persistScannedProjects(config, scan, &cpe) 850 // assert 851 assert.Equal(t, []string{"project-app - 1", "project-db - 1"}, cpe.custom.whitesourceProjectNames) 852 }) 853 t.Run("write no projects", func(t *testing.T) { 854 // init 855 cpe := whitesourceExecuteScanCommonPipelineEnvironment{} 856 config := &ScanOptions{Version: "1"} 857 scan := newWhitesourceScan(config) 858 // test 859 persistScannedProjects(config, scan, &cpe) 860 // assert 861 assert.Equal(t, []string{}, cpe.custom.whitesourceProjectNames) 862 }) 863 t.Run("write aggregated project", func(t *testing.T) { 864 // init 865 cpe := whitesourceExecuteScanCommonPipelineEnvironment{} 866 config := &ScanOptions{ProjectName: "project", Version: "1"} 867 scan := newWhitesourceScan(config) 868 // test 869 persistScannedProjects(config, scan, &cpe) 870 // assert 871 assert.Equal(t, []string{"project - 1"}, cpe.custom.whitesourceProjectNames) 872 }) 873 }