github.com/jfrog/frogbot@v1.1.1-0.20231221090046-821a26f50338/scanpullrequest/scanpullrequest_test.go (about) 1 package scanpullrequest 2 3 import ( 4 "bytes" 5 "context" 6 "errors" 7 "fmt" 8 "io" 9 "net/http" 10 "net/http/httptest" 11 "os" 12 "path/filepath" 13 "strings" 14 "testing" 15 "time" 16 17 "github.com/jfrog/frogbot/utils" 18 "github.com/jfrog/frogbot/utils/outputwriter" 19 "github.com/jfrog/froggit-go/vcsclient" 20 "github.com/jfrog/froggit-go/vcsutils" 21 coreconfig "github.com/jfrog/jfrog-cli-core/v2/utils/config" 22 "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" 23 "github.com/jfrog/jfrog-cli-core/v2/xray/formats" 24 xrayutils "github.com/jfrog/jfrog-cli-core/v2/xray/utils" 25 "github.com/jfrog/jfrog-client-go/utils/io/fileutils" 26 "github.com/jfrog/jfrog-client-go/utils/log" 27 "github.com/jfrog/jfrog-client-go/xray/services" 28 "github.com/owenrumney/go-sarif/v2/sarif" 29 "github.com/stretchr/testify/assert" 30 ) 31 32 const ( 33 testMultiDirProjConfigPath = "testdata/config/frogbot-config-multi-dir-test-proj.yml" 34 testMultiDirProjConfigPathNoFail = "testdata/config/frogbot-config-multi-dir-test-proj-no-fail.yml" 35 testProjSubdirConfigPath = "testdata/config/frogbot-config-test-proj-subdir.yml" 36 testCleanProjConfigPath = "testdata/config/frogbot-config-clean-test-proj.yml" 37 testProjConfigPath = "testdata/config/frogbot-config-test-proj.yml" 38 testProjConfigPathNoFail = "testdata/config/frogbot-config-test-proj-no-fail.yml" 39 testSourceBranchName = "pr" 40 testTargetBranchName = "master" 41 ) 42 43 func TestCreateVulnerabilitiesRows(t *testing.T) { 44 // Previous scan with only one violation - XRAY-1 45 previousScan := services.ScanResponse{ 46 Violations: []services.Violation{ 47 { 48 IssueId: "XRAY-1", 49 Summary: "summary-1", 50 Severity: "high", 51 Cves: []services.Cve{}, 52 ViolationType: "security", 53 Components: map[string]services.Component{"component-A": {}, "component-B": {}}, 54 }, 55 { 56 IssueId: "XRAY-4", 57 ViolationType: "license", 58 LicenseKey: "Apache-2.0", 59 Components: map[string]services.Component{"Dep-2": {}}, 60 }, 61 }, 62 } 63 64 // Current scan with 2 violations - XRAY-1 and XRAY-2 65 currentScan := services.ScanResponse{ 66 Violations: []services.Violation{ 67 { 68 IssueId: "XRAY-1", 69 Summary: "summary-1", 70 Severity: "high", 71 ViolationType: "security", 72 Components: map[string]services.Component{"component-A": {}, "component-B": {}}, 73 }, 74 { 75 IssueId: "XRAY-2", 76 Summary: "summary-2", 77 ViolationType: "security", 78 Severity: "low", 79 Components: map[string]services.Component{"component-C": {}, "component-D": {}}, 80 }, 81 { 82 IssueId: "XRAY-3", 83 ViolationType: "license", 84 LicenseKey: "MIT", 85 Components: map[string]services.Component{"Dep-1": {}}, 86 }, 87 }, 88 } 89 90 // Run createNewIssuesRows and make sure that only the XRAY-2 violation exists in the results 91 securityViolationsRows, licenseViolations, err := createNewVulnerabilitiesRows( 92 &xrayutils.Results{ScaResults: []xrayutils.ScaScanResult{{XrayResults: []services.ScanResponse{previousScan}}}, ExtendedScanResults: &xrayutils.ExtendedScanResults{}}, 93 &xrayutils.Results{ScaResults: []xrayutils.ScaScanResult{{XrayResults: []services.ScanResponse{currentScan}}}, ExtendedScanResults: &xrayutils.ExtendedScanResults{}}, 94 nil, 95 ) 96 assert.NoError(t, err) 97 assert.Len(t, licenseViolations, 1) 98 assert.Len(t, securityViolationsRows, 2) 99 assert.Equal(t, "XRAY-2", securityViolationsRows[0].IssueId) 100 assert.Equal(t, "low", securityViolationsRows[0].Severity) 101 assert.Equal(t, "XRAY-2", securityViolationsRows[1].IssueId) 102 assert.Equal(t, "low", securityViolationsRows[1].Severity) 103 assert.Equal(t, "MIT", licenseViolations[0].LicenseKey) 104 assert.Equal(t, "Dep-1", licenseViolations[0].ImpactedDependencyName) 105 106 impactedPackageOne := securityViolationsRows[0].ImpactedDependencyName 107 impactedPackageTwo := securityViolationsRows[1].ImpactedDependencyName 108 assert.ElementsMatch(t, []string{"component-C", "component-D"}, []string{impactedPackageOne, impactedPackageTwo}) 109 } 110 111 func TestCreateVulnerabilitiesRowsCaseNoPrevViolations(t *testing.T) { 112 // Previous scan with no violation 113 previousScan := services.ScanResponse{ 114 Violations: []services.Violation{}, 115 } 116 117 // Current scan with 2 violations - XRAY-1 and XRAY-2 118 currentScan := services.ScanResponse{ 119 Violations: []services.Violation{ 120 { 121 IssueId: "XRAY-1", 122 Summary: "summary-1", 123 Severity: "high", 124 ViolationType: "security", 125 Components: map[string]services.Component{"component-A": {}}, 126 }, 127 { 128 IssueId: "XRAY-2", 129 Summary: "summary-2", 130 ViolationType: "security", 131 Severity: "low", 132 Components: map[string]services.Component{"component-C": {}}, 133 }, 134 { 135 IssueId: "XRAY-3", 136 ViolationType: "license", 137 LicenseKey: "MIT", 138 Components: map[string]services.Component{"Dep-1": {}}, 139 }, 140 }, 141 } 142 143 expectedVulns := []formats.VulnerabilityOrViolationRow{ 144 { 145 IssueId: "XRAY-1", 146 Summary: "summary-1", 147 ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ 148 SeverityDetails: formats.SeverityDetails{Severity: "high"}, 149 ImpactedDependencyName: "component-A", 150 }, 151 }, 152 { 153 IssueId: "XRAY-2", 154 Summary: "summary-2", 155 ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ 156 SeverityDetails: formats.SeverityDetails{Severity: "low"}, 157 ImpactedDependencyName: "component-C", 158 }, 159 }, 160 } 161 162 expectedLicenses := []formats.LicenseRow{ 163 { 164 ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ImpactedDependencyName: "Dep-1"}, 165 LicenseKey: "MIT", 166 }, 167 } 168 169 // Run createNewIssuesRows and expect both XRAY-1 and XRAY-2 violation in the results 170 vulnerabilities, licenses, err := createNewVulnerabilitiesRows( 171 &xrayutils.Results{ScaResults: []xrayutils.ScaScanResult{{XrayResults: []services.ScanResponse{previousScan}}}, ExtendedScanResults: &xrayutils.ExtendedScanResults{}}, 172 &xrayutils.Results{ScaResults: []xrayutils.ScaScanResult{{XrayResults: []services.ScanResponse{currentScan}}}, ExtendedScanResults: &xrayutils.ExtendedScanResults{}}, 173 []string{}, 174 ) 175 assert.NoError(t, err) 176 assert.Len(t, licenses, 1) 177 assert.Len(t, vulnerabilities, 2) 178 assert.ElementsMatch(t, expectedVulns, vulnerabilities) 179 assert.Equal(t, expectedLicenses[0].ImpactedDependencyName, licenses[0].ImpactedDependencyName) 180 assert.Equal(t, expectedLicenses[0].LicenseKey, licenses[0].LicenseKey) 181 } 182 183 func TestGetNewViolationsCaseNoNewViolations(t *testing.T) { 184 // Previous scan with 2 security violations and 1 license violation - XRAY-1 and XRAY-2 185 previousScan := services.ScanResponse{ 186 Violations: []services.Violation{ 187 { 188 IssueId: "XRAY-1", 189 Severity: "high", 190 ViolationType: "security", 191 Components: map[string]services.Component{"component-A": {}}, 192 }, 193 { 194 IssueId: "XRAY-2", 195 Summary: "summary-2", 196 ViolationType: "security", 197 Severity: "low", 198 Components: map[string]services.Component{"component-C": {}}, 199 }, 200 { 201 IssueId: "XRAY-3", 202 LicenseKey: "MIT", 203 ViolationType: "license", 204 Components: map[string]services.Component{"component-B": {}}, 205 }, 206 }, 207 } 208 209 // Current scan with no violation 210 currentScan := services.ScanResponse{ 211 Violations: []services.Violation{}, 212 } 213 214 // Run createNewIssuesRows and expect no violations in the results 215 securityViolations, licenseViolations, err := createNewVulnerabilitiesRows( 216 &xrayutils.Results{ScaResults: []xrayutils.ScaScanResult{{XrayResults: []services.ScanResponse{previousScan}}}, ExtendedScanResults: &xrayutils.ExtendedScanResults{}}, 217 &xrayutils.Results{ScaResults: []xrayutils.ScaScanResult{{XrayResults: []services.ScanResponse{currentScan}}}, ExtendedScanResults: &xrayutils.ExtendedScanResults{}}, 218 []string{"MIT"}, 219 ) 220 assert.NoError(t, err) 221 assert.Len(t, securityViolations, 0) 222 assert.Len(t, licenseViolations, 0) 223 } 224 225 func TestGetNewVulnerabilities(t *testing.T) { 226 // Previous scan with only one vulnerability - XRAY-1 227 previousScan := services.ScanResponse{ 228 Vulnerabilities: []services.Vulnerability{{ 229 IssueId: "XRAY-1", 230 Summary: "summary-1", 231 Severity: "high", 232 Cves: []services.Cve{{Id: "CVE-2023-1234"}}, 233 Components: map[string]services.Component{"component-A": {}, "component-B": {}}, 234 Technology: coreutils.Maven.String(), 235 }}, 236 } 237 238 // Current scan with 2 vulnerabilities - XRAY-1 and XRAY-2 239 currentScan := services.ScanResponse{ 240 Vulnerabilities: []services.Vulnerability{ 241 { 242 IssueId: "XRAY-1", 243 Summary: "summary-1", 244 Severity: "high", 245 Cves: []services.Cve{{Id: "CVE-2023-1234"}}, 246 Components: map[string]services.Component{"component-A": {}, "component-B": {}}, 247 Technology: coreutils.Maven.String(), 248 }, 249 { 250 IssueId: "XRAY-2", 251 Summary: "summary-2", 252 Severity: "low", 253 Cves: []services.Cve{{Id: "CVE-2023-4321"}}, 254 Components: map[string]services.Component{"component-C": {}, "component-D": {}}, 255 Technology: coreutils.Yarn.String(), 256 }, 257 }, 258 } 259 260 expected := []formats.VulnerabilityOrViolationRow{ 261 { 262 Summary: "summary-2", 263 Applicable: "Applicable", 264 IssueId: "XRAY-2", 265 ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ 266 SeverityDetails: formats.SeverityDetails{Severity: "low"}, 267 ImpactedDependencyName: "component-C", 268 }, 269 Cves: []formats.CveRow{{Id: "CVE-2023-4321", Applicability: &formats.Applicability{Status: "Applicable", Evidence: []formats.Evidence{{Location: formats.Location{File: "file1", StartLine: 1, StartColumn: 10, EndLine: 2, EndColumn: 11, Snippet: "snippet"}}}}}}, 270 Technology: coreutils.Yarn, 271 }, 272 { 273 Summary: "summary-2", 274 Applicable: "Applicable", 275 IssueId: "XRAY-2", 276 ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ 277 SeverityDetails: formats.SeverityDetails{Severity: "low"}, 278 ImpactedDependencyName: "component-D", 279 }, 280 Cves: []formats.CveRow{{Id: "CVE-2023-4321", Applicability: &formats.Applicability{Status: "Applicable", Evidence: []formats.Evidence{{Location: formats.Location{File: "file1", StartLine: 1, StartColumn: 10, EndLine: 2, EndColumn: 11, Snippet: "snippet"}}}}}}, 281 Technology: coreutils.Yarn, 282 }, 283 } 284 285 // Run createNewIssuesRows and make sure that only the XRAY-2 vulnerability exists in the results 286 vulnerabilities, licenses, err := createNewVulnerabilitiesRows( 287 &xrayutils.Results{ 288 ScaResults: []xrayutils.ScaScanResult{{XrayResults: []services.ScanResponse{previousScan}}}, 289 ExtendedScanResults: &xrayutils.ExtendedScanResults{ 290 EntitledForJas: true, 291 ApplicabilityScanResults: []*sarif.Run{xrayutils.CreateRunWithDummyResults(xrayutils.CreateResultWithOneLocation("file1", 1, 10, 2, 11, "snippet", "applic_CVE-2023-4321", ""))}, 292 }, 293 }, 294 &xrayutils.Results{ 295 ScaResults: []xrayutils.ScaScanResult{{XrayResults: []services.ScanResponse{currentScan}}}, 296 ExtendedScanResults: &xrayutils.ExtendedScanResults{ 297 EntitledForJas: true, 298 ApplicabilityScanResults: []*sarif.Run{xrayutils.CreateRunWithDummyResults(xrayutils.CreateResultWithOneLocation("file1", 1, 10, 2, 11, "snippet", "applic_CVE-2023-4321", ""))}, 299 }, 300 }, 301 nil, 302 ) 303 assert.NoError(t, err) 304 assert.Len(t, vulnerabilities, 2) 305 assert.Len(t, licenses, 0) 306 assert.ElementsMatch(t, expected, vulnerabilities) 307 } 308 309 func TestGetNewVulnerabilitiesCaseNoPrevVulnerabilities(t *testing.T) { 310 // Previous scan with no vulnerabilities 311 previousScan := services.ScanResponse{ 312 Vulnerabilities: []services.Vulnerability{}, 313 } 314 315 // Current scan with 2 vulnerabilities - XRAY-1 and XRAY-2 316 currentScan := services.ScanResponse{ 317 Vulnerabilities: []services.Vulnerability{ 318 { 319 IssueId: "XRAY-1", 320 Summary: "summary-1", 321 Severity: "high", 322 ExtendedInformation: &services.ExtendedInformation{FullDescription: "description-1"}, 323 Components: map[string]services.Component{"component-A": {}}, 324 }, 325 { 326 IssueId: "XRAY-2", 327 Summary: "summary-2", 328 Severity: "low", 329 ExtendedInformation: &services.ExtendedInformation{FullDescription: "description-2"}, 330 Components: map[string]services.Component{"component-B": {}}, 331 }, 332 }, 333 } 334 335 expected := []formats.VulnerabilityOrViolationRow{ 336 { 337 Summary: "summary-2", 338 IssueId: "XRAY-2", 339 ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ 340 SeverityDetails: formats.SeverityDetails{Severity: "low"}, 341 ImpactedDependencyName: "component-B", 342 }, 343 JfrogResearchInformation: &formats.JfrogResearchInformation{Details: "description-2"}, 344 }, 345 { 346 Summary: "summary-1", 347 IssueId: "XRAY-1", 348 ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ 349 SeverityDetails: formats.SeverityDetails{Severity: "high"}, 350 ImpactedDependencyName: "component-A", 351 }, 352 JfrogResearchInformation: &formats.JfrogResearchInformation{Details: "description-1"}, 353 }, 354 } 355 356 // Run createNewIssuesRows and expect both XRAY-1 and XRAY-2 vulnerability in the results 357 vulnerabilities, licenses, err := createNewVulnerabilitiesRows( 358 &xrayutils.Results{ScaResults: []xrayutils.ScaScanResult{{XrayResults: []services.ScanResponse{previousScan}}}, ExtendedScanResults: &xrayutils.ExtendedScanResults{}}, 359 &xrayutils.Results{ScaResults: []xrayutils.ScaScanResult{{XrayResults: []services.ScanResponse{currentScan}}}, ExtendedScanResults: &xrayutils.ExtendedScanResults{}}, 360 nil, 361 ) 362 assert.NoError(t, err) 363 assert.Len(t, vulnerabilities, 2) 364 assert.Len(t, licenses, 0) 365 assert.ElementsMatch(t, expected, vulnerabilities) 366 } 367 368 func TestGetNewVulnerabilitiesCaseNoNewVulnerabilities(t *testing.T) { 369 // Previous scan with 2 vulnerabilities - XRAY-1 and XRAY-2 370 previousScan := services.ScanResponse{ 371 Vulnerabilities: []services.Vulnerability{ 372 { 373 IssueId: "XRAY-1", 374 Summary: "summary-1", 375 Severity: "high", 376 Components: map[string]services.Component{"component-A": {}}, 377 }, 378 { 379 IssueId: "XRAY-2", 380 Summary: "summary-2", 381 Severity: "low", 382 Components: map[string]services.Component{"component-B": {}}, 383 }, 384 }, 385 } 386 387 // Current scan with no vulnerabilities 388 currentScan := services.ScanResponse{ 389 Vulnerabilities: []services.Vulnerability{}, 390 } 391 392 // Run createNewIssuesRows and expect no vulnerability in the results 393 vulnerabilities, licenses, err := createNewVulnerabilitiesRows( 394 &xrayutils.Results{ScaResults: []xrayutils.ScaScanResult{{XrayResults: []services.ScanResponse{previousScan}}}, ExtendedScanResults: &xrayutils.ExtendedScanResults{}}, 395 &xrayutils.Results{ScaResults: []xrayutils.ScaScanResult{{XrayResults: []services.ScanResponse{currentScan}}}, ExtendedScanResults: &xrayutils.ExtendedScanResults{}}, 396 nil, 397 ) 398 assert.NoError(t, err) 399 assert.Len(t, vulnerabilities, 0) 400 assert.Len(t, licenses, 0) 401 } 402 403 func TestGetAllIssues(t *testing.T) { 404 allowedLicenses := []string{"MIT"} 405 auditResults := &xrayutils.Results{ 406 ScaResults: []xrayutils.ScaScanResult{{ 407 XrayResults: []services.ScanResponse{{ 408 Vulnerabilities: []services.Vulnerability{ 409 {Cves: []services.Cve{{Id: "CVE-2022-2122"}}, Severity: "High", Components: map[string]services.Component{"Dep-1": {FixedVersions: []string{"1.2.3"}}}}, 410 {Cves: []services.Cve{{Id: "CVE-2023-3122"}}, Severity: "Low", Components: map[string]services.Component{"Dep-2": {FixedVersions: []string{"1.2.2"}}}}, 411 }, 412 Licenses: []services.License{{Key: "Apache-2.0", Components: map[string]services.Component{"Dep-1": {FixedVersions: []string{"1.2.3"}}}}}, 413 }}, 414 }}, 415 ExtendedScanResults: &xrayutils.ExtendedScanResults{ 416 ApplicabilityScanResults: []*sarif.Run{ 417 xrayutils.CreateRunWithDummyResults( 418 xrayutils.CreateDummyPassingResult("applic_CVE-2023-3122"), 419 xrayutils.CreateResultWithOneLocation("file1", 1, 10, 2, 11, "snippet", "applic_CVE-2022-2122", ""), 420 ), 421 }, 422 IacScanResults: []*sarif.Run{ 423 xrayutils.CreateRunWithDummyResults( 424 xrayutils.CreateResultWithLocations("Missing auto upgrade was detected", "rule", xrayutils.ConvertToSarifLevel("high"), 425 xrayutils.CreateLocation("file1", 1, 10, 2, 11, "aws-violation"), 426 ), 427 ), 428 }, 429 SecretsScanResults: []*sarif.Run{ 430 xrayutils.CreateRunWithDummyResults( 431 xrayutils.CreateResultWithLocations("Secret", "rule", xrayutils.ConvertToSarifLevel("high"), 432 xrayutils.CreateLocation("index.js", 5, 6, 7, 8, "access token exposed"), 433 ), 434 ), 435 }, 436 SastScanResults: []*sarif.Run{ 437 xrayutils.CreateRunWithDummyResults( 438 xrayutils.CreateResultWithLocations("XSS Vulnerability", "rule", xrayutils.ConvertToSarifLevel("high"), 439 xrayutils.CreateLocation("file1", 1, 10, 2, 11, "snippet"), 440 ), 441 ), 442 }, 443 EntitledForJas: true, 444 }, 445 } 446 expectedOutput := &utils.IssuesCollection{ 447 Vulnerabilities: []formats.VulnerabilityOrViolationRow{ 448 { 449 Applicable: "Applicable", 450 FixedVersions: []string{"1.2.3"}, 451 ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ 452 SeverityDetails: formats.SeverityDetails{Severity: "High", SeverityNumValue: 13}, 453 ImpactedDependencyName: "Dep-1", 454 }, 455 Cves: []formats.CveRow{{Id: "CVE-2022-2122", Applicability: &formats.Applicability{Status: "Applicable", Evidence: []formats.Evidence{{Location: formats.Location{File: "file1", StartLine: 1, StartColumn: 10, EndLine: 2, EndColumn: 11, Snippet: "snippet"}}}}}}, 456 }, 457 { 458 Applicable: "Not Applicable", 459 FixedVersions: []string{"1.2.2"}, 460 ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ 461 SeverityDetails: formats.SeverityDetails{Severity: "Low", SeverityNumValue: 2}, 462 ImpactedDependencyName: "Dep-2", 463 }, 464 Cves: []formats.CveRow{{Id: "CVE-2023-3122", Applicability: &formats.Applicability{Status: "Not Applicable"}}}, 465 }, 466 }, 467 Iacs: []formats.SourceCodeRow{ 468 { 469 SeverityDetails: formats.SeverityDetails{ 470 Severity: "High", 471 SeverityNumValue: 13, 472 }, 473 Finding: "Missing auto upgrade was detected", 474 Location: formats.Location{ 475 File: "file1", 476 StartLine: 1, 477 StartColumn: 10, 478 EndLine: 2, 479 EndColumn: 11, 480 Snippet: "aws-violation", 481 }, 482 }, 483 }, 484 Secrets: []formats.SourceCodeRow{ 485 { 486 SeverityDetails: formats.SeverityDetails{ 487 Severity: "High", 488 SeverityNumValue: 13, 489 }, 490 Finding: "Secret", 491 Location: formats.Location{ 492 File: "index.js", 493 StartLine: 5, 494 StartColumn: 6, 495 EndLine: 7, 496 EndColumn: 8, 497 Snippet: "access token exposed", 498 }, 499 }, 500 }, 501 Sast: []formats.SourceCodeRow{ 502 { 503 SeverityDetails: formats.SeverityDetails{ 504 Severity: "High", 505 SeverityNumValue: 13, 506 }, 507 Finding: "XSS Vulnerability", 508 Location: formats.Location{ 509 File: "file1", 510 StartLine: 1, 511 StartColumn: 10, 512 EndLine: 2, 513 EndColumn: 11, 514 Snippet: "snippet", 515 }, 516 }, 517 }, 518 Licenses: []formats.LicenseRow{ 519 { 520 LicenseKey: "Apache-2.0", 521 ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ 522 ImpactedDependencyName: "Dep-1", 523 }, 524 }, 525 }, 526 } 527 528 issuesRows, err := getAllIssues(auditResults, allowedLicenses) 529 530 if assert.NoError(t, err) { 531 assert.ElementsMatch(t, expectedOutput.Vulnerabilities, issuesRows.Vulnerabilities) 532 assert.ElementsMatch(t, expectedOutput.Iacs, issuesRows.Iacs) 533 assert.ElementsMatch(t, expectedOutput.Secrets, issuesRows.Secrets) 534 assert.ElementsMatch(t, expectedOutput.Sast, issuesRows.Sast) 535 assert.ElementsMatch(t, expectedOutput.Licenses, issuesRows.Licenses) 536 } 537 } 538 539 func TestScanPullRequest(t *testing.T) { 540 tests := []struct { 541 testName string 542 configPath string 543 projectName string 544 failOnSecurityIssues bool 545 }{ 546 { 547 testName: "ScanPullRequest", 548 configPath: testProjConfigPath, 549 projectName: "test-proj", 550 failOnSecurityIssues: true, 551 }, 552 { 553 testName: "ScanPullRequestNoFail", 554 configPath: testProjConfigPathNoFail, 555 projectName: "test-proj", 556 failOnSecurityIssues: false, 557 }, 558 { 559 testName: "ScanPullRequestSubdir", 560 configPath: testProjSubdirConfigPath, 561 projectName: "test-proj-subdir", 562 failOnSecurityIssues: true, 563 }, 564 { 565 testName: "ScanPullRequestNoIssues", 566 configPath: testCleanProjConfigPath, 567 projectName: "clean-test-proj", 568 failOnSecurityIssues: false, 569 }, 570 { 571 testName: "ScanPullRequestMultiWorkDir", 572 configPath: testMultiDirProjConfigPathNoFail, 573 projectName: "multi-dir-test-proj", 574 failOnSecurityIssues: false, 575 }, 576 { 577 testName: "ScanPullRequestMultiWorkDirNoFail", 578 configPath: testMultiDirProjConfigPath, 579 projectName: "multi-dir-test-proj", 580 failOnSecurityIssues: true, 581 }, 582 } 583 for _, test := range tests { 584 t.Run(test.testName, func(t *testing.T) { 585 testScanPullRequest(t, test.configPath, test.projectName, test.failOnSecurityIssues) 586 }) 587 } 588 } 589 590 func testScanPullRequest(t *testing.T, configPath, projectName string, failOnSecurityIssues bool) { 591 params, restoreEnv := utils.VerifyEnv(t) 592 defer restoreEnv() 593 594 // Create mock GitLab server 595 server := httptest.NewServer(createGitLabHandler(t, projectName)) 596 defer server.Close() 597 598 configAggregator, client := prepareConfigAndClient(t, configPath, server, params) 599 testDir, cleanUp := utils.CopyTestdataProjectsToTemp(t, "scanpullrequest") 600 defer cleanUp() 601 602 // Renames test git folder to .git 603 currentDir := filepath.Join(testDir, projectName) 604 restoreDir, err := utils.Chdir(currentDir) 605 assert.NoError(t, err) 606 defer func() { 607 assert.NoError(t, restoreDir()) 608 assert.NoError(t, fileutils.RemoveTempDir(currentDir)) 609 }() 610 611 // Run "frogbot scan pull request" 612 var scanPullRequest ScanPullRequestCmd 613 err = scanPullRequest.Run(configAggregator, client, utils.MockHasConnection()) 614 if failOnSecurityIssues { 615 assert.EqualErrorf(t, err, SecurityIssueFoundErr, "Error should be: %v, got: %v", SecurityIssueFoundErr, err) 616 } else { 617 assert.NoError(t, err) 618 } 619 620 // Check env sanitize 621 err = utils.SanitizeEnv() 622 assert.NoError(t, err) 623 utils.AssertSanitizedEnv(t) 624 } 625 626 func TestVerifyGitHubFrogbotEnvironment(t *testing.T) { 627 // Init mock 628 client := CreateMockVcsClient(t) 629 environment := "frogbot" 630 client.EXPECT().GetRepositoryInfo(context.Background(), gitParams.RepoOwner, gitParams.RepoName).Return(vcsclient.RepositoryInfo{}, nil) 631 client.EXPECT().GetRepositoryEnvironmentInfo(context.Background(), gitParams.RepoOwner, gitParams.RepoName, environment).Return(vcsclient.RepositoryEnvironmentInfo{Reviewers: []string{"froggy"}}, nil) 632 assert.NoError(t, os.Setenv(utils.GitHubActionsEnv, "true")) 633 634 // Run verifyGitHubFrogbotEnvironment 635 err := verifyGitHubFrogbotEnvironment(client, gitParams) 636 assert.NoError(t, err) 637 } 638 639 func TestVerifyGitHubFrogbotEnvironmentNoEnv(t *testing.T) { 640 // Redirect log to avoid negative output 641 previousLogger := redirectLogOutputToNil() 642 defer log.SetLogger(previousLogger) 643 644 // Init mock 645 client := CreateMockVcsClient(t) 646 environment := "frogbot" 647 client.EXPECT().GetRepositoryInfo(context.Background(), gitParams.RepoOwner, gitParams.RepoName).Return(vcsclient.RepositoryInfo{}, nil) 648 client.EXPECT().GetRepositoryEnvironmentInfo(context.Background(), gitParams.RepoOwner, gitParams.RepoName, environment).Return(vcsclient.RepositoryEnvironmentInfo{}, errors.New("404")) 649 assert.NoError(t, os.Setenv(utils.GitHubActionsEnv, "true")) 650 651 // Run verifyGitHubFrogbotEnvironment 652 err := verifyGitHubFrogbotEnvironment(client, gitParams) 653 assert.ErrorContains(t, err, noGitHubEnvErr) 654 } 655 656 func TestVerifyGitHubFrogbotEnvironmentNoReviewers(t *testing.T) { 657 // Init mock 658 client := CreateMockVcsClient(t) 659 environment := "frogbot" 660 client.EXPECT().GetRepositoryInfo(context.Background(), gitParams.RepoOwner, gitParams.RepoName).Return(vcsclient.RepositoryInfo{}, nil) 661 client.EXPECT().GetRepositoryEnvironmentInfo(context.Background(), gitParams.RepoOwner, gitParams.RepoName, environment).Return(vcsclient.RepositoryEnvironmentInfo{}, nil) 662 assert.NoError(t, os.Setenv(utils.GitHubActionsEnv, "true")) 663 664 // Run verifyGitHubFrogbotEnvironment 665 err := verifyGitHubFrogbotEnvironment(client, gitParams) 666 assert.ErrorContains(t, err, noGitHubEnvReviewersErr) 667 } 668 669 func TestVerifyGitHubFrogbotEnvironmentOnPrem(t *testing.T) { 670 repoConfig := &utils.Repository{ 671 Params: utils.Params{Git: utils.Git{ 672 VcsInfo: vcsclient.VcsInfo{APIEndpoint: "https://acme.vcs.io"}}, 673 }, 674 } 675 676 // Run verifyGitHubFrogbotEnvironment 677 err := verifyGitHubFrogbotEnvironment(&vcsclient.GitHubClient{}, repoConfig) 678 assert.NoError(t, err) 679 } 680 681 func prepareConfigAndClient(t *testing.T, configPath string, server *httptest.Server, serverParams coreconfig.ServerDetails) (utils.RepoAggregator, vcsclient.VcsClient) { 682 gitTestParams := &utils.Git{ 683 GitProvider: vcsutils.GitHub, 684 RepoOwner: "jfrog", 685 VcsInfo: vcsclient.VcsInfo{ 686 Token: "123456", 687 APIEndpoint: server.URL, 688 }, 689 PullRequestDetails: vcsclient.PullRequestInfo{ID: int64(1)}, 690 } 691 utils.SetEnvAndAssert(t, map[string]string{utils.GitPullRequestIDEnv: "1"}) 692 693 configData, err := utils.ReadConfigFromFileSystem(configPath) 694 assert.NoError(t, err) 695 configAggregator, err := utils.BuildRepoAggregator(configData, gitTestParams, &serverParams, utils.ScanPullRequest) 696 assert.NoError(t, err) 697 698 client, err := vcsclient.NewClientBuilder(vcsutils.GitLab).ApiEndpoint(server.URL).Token("123456").Build() 699 assert.NoError(t, err) 700 return configAggregator, client 701 } 702 703 // Create HTTP handler to mock GitLab server 704 func createGitLabHandler(t *testing.T, projectName string) http.HandlerFunc { 705 return func(w http.ResponseWriter, r *http.Request) { 706 switch { 707 // Return 200 on ping 708 case r.RequestURI == "/api/v4/": 709 w.WriteHeader(http.StatusOK) 710 // Mimic get pull request by ID 711 case r.RequestURI == fmt.Sprintf("/api/v4/projects/jfrog%s/merge_requests/1", "%2F"+projectName): 712 w.WriteHeader(http.StatusOK) 713 expectedResponse, err := os.ReadFile(filepath.Join("..", "expectedPullRequestDetailsResponse.json")) 714 assert.NoError(t, err) 715 _, err = w.Write(expectedResponse) 716 assert.NoError(t, err) 717 // Mimic download specific branch to scan 718 case r.RequestURI == fmt.Sprintf("/api/v4/projects/jfrog%s/repository/archive.tar.gz?sha=%s", "%2F"+projectName, testSourceBranchName): 719 w.WriteHeader(http.StatusOK) 720 repoFile, err := os.ReadFile(filepath.Join("..", projectName, "sourceBranch.gz")) 721 assert.NoError(t, err) 722 _, err = w.Write(repoFile) 723 assert.NoError(t, err) 724 // Download repository mock 725 case r.RequestURI == fmt.Sprintf("/api/v4/projects/jfrog%s/repository/archive.tar.gz?sha=%s", "%2F"+projectName, testTargetBranchName): 726 w.WriteHeader(http.StatusOK) 727 repoFile, err := os.ReadFile(filepath.Join("..", projectName, "targetBranch.gz")) 728 assert.NoError(t, err) 729 _, err = w.Write(repoFile) 730 assert.NoError(t, err) 731 return 732 // clean-test-proj should not include any vulnerabilities so assertion is not needed. 733 case r.RequestURI == fmt.Sprintf("/api/v4/projects/jfrog%s/merge_requests/133/notes", "%2Fclean-test-proj") && r.Method == http.MethodPost: 734 w.WriteHeader(http.StatusOK) 735 _, err := w.Write([]byte("{}")) 736 assert.NoError(t, err) 737 return 738 case r.RequestURI == fmt.Sprintf("/api/v4/projects/jfrog%s/merge_requests/133/notes", "%2Fclean-test-proj") && r.Method == http.MethodGet: 739 w.WriteHeader(http.StatusOK) 740 comments, err := os.ReadFile(filepath.Join("..", "commits.json")) 741 assert.NoError(t, err) 742 _, err = w.Write(comments) 743 assert.NoError(t, err) 744 // Return 200 when using the REST that creates the comment 745 case r.RequestURI == fmt.Sprintf("/api/v4/projects/jfrog%s/merge_requests/133/notes", "%2F"+projectName) && r.Method == http.MethodPost: 746 buf := new(bytes.Buffer) 747 _, err := buf.ReadFrom(r.Body) 748 assert.NoError(t, err) 749 assert.NotEmpty(t, buf.String()) 750 751 var expectedResponse []byte 752 if strings.Contains(projectName, "multi-dir") { 753 expectedResponse = outputwriter.GetJsonBodyOutputFromFile(t, filepath.Join("..", "expected_response_multi_dir.md")) 754 } else { 755 expectedResponse = outputwriter.GetJsonBodyOutputFromFile(t, filepath.Join("..", "expected_response.md")) 756 } 757 assert.NoError(t, err) 758 assert.JSONEq(t, string(expectedResponse), buf.String()) 759 760 w.WriteHeader(http.StatusOK) 761 _, err = w.Write([]byte("{}")) 762 assert.NoError(t, err) 763 case r.RequestURI == fmt.Sprintf("/api/v4/projects/jfrog%s/merge_requests/133/notes", "%2F"+projectName) && r.Method == http.MethodGet: 764 w.WriteHeader(http.StatusOK) 765 comments, err := os.ReadFile(filepath.Join("..", "commits.json")) 766 assert.NoError(t, err) 767 _, err = w.Write(comments) 768 assert.NoError(t, err) 769 case r.RequestURI == fmt.Sprintf("/api/v4/projects/jfrog%s", "%2F"+projectName): 770 jsonResponse := `{"id": 3,"visibility": "private","ssh_url_to_repo": "git@example.com:diaspora/diaspora-project-site.git","http_url_to_repo": "https://example.com/diaspora/diaspora-project-site.git"}` 771 _, err := w.Write([]byte(jsonResponse)) 772 assert.NoError(t, err) 773 case r.RequestURI == fmt.Sprintf("/api/v4/projects/jfrog%s/merge_requests/133/discussions", "%2F"+projectName): 774 discussions, err := os.ReadFile(filepath.Join("..", "list_merge_request_discussion_items.json")) 775 assert.NoError(t, err) 776 _, err = w.Write(discussions) 777 assert.NoError(t, err) 778 } 779 } 780 } 781 782 func TestCreateNewIacRows(t *testing.T) { 783 testCases := []struct { 784 name string 785 targetIacResults []*sarif.Result 786 sourceIacResults []*sarif.Result 787 expectedAddedIacVulnerabilities []formats.SourceCodeRow 788 }{ 789 { 790 name: "No vulnerabilities in source IaC results", 791 targetIacResults: []*sarif.Result{ 792 xrayutils.CreateResultWithLocations("Missing auto upgrade was detected", "rule", xrayutils.ConvertToSarifLevel("high"), 793 xrayutils.CreateLocation("file1", 1, 10, 2, 11, "aws-violation"), 794 ), 795 }, 796 sourceIacResults: []*sarif.Result{}, 797 expectedAddedIacVulnerabilities: []formats.SourceCodeRow{}, 798 }, 799 { 800 name: "No vulnerabilities in target IaC results", 801 targetIacResults: []*sarif.Result{}, 802 sourceIacResults: []*sarif.Result{ 803 xrayutils.CreateResultWithLocations("Missing auto upgrade was detected", "rule", xrayutils.ConvertToSarifLevel("high"), 804 xrayutils.CreateLocation("file1", 1, 10, 2, 11, "aws-violation"), 805 ), 806 }, 807 expectedAddedIacVulnerabilities: []formats.SourceCodeRow{ 808 { 809 SeverityDetails: formats.SeverityDetails{ 810 Severity: "High", 811 SeverityNumValue: 13, 812 }, 813 Finding: "Missing auto upgrade was detected", 814 Location: formats.Location{ 815 File: "file1", 816 StartLine: 1, 817 StartColumn: 10, 818 EndLine: 2, 819 EndColumn: 11, 820 Snippet: "aws-violation", 821 }, 822 }, 823 }, 824 }, 825 { 826 name: "Some new vulnerabilities in source IaC results", 827 targetIacResults: []*sarif.Result{ 828 xrayutils.CreateResultWithLocations("Missing auto upgrade was detected", "rule", xrayutils.ConvertToSarifLevel("high"), 829 xrayutils.CreateLocation("file1", 1, 10, 2, 11, "aws-violation"), 830 ), 831 }, 832 sourceIacResults: []*sarif.Result{ 833 xrayutils.CreateResultWithLocations("enable_private_endpoint=false was detected", "rule", xrayutils.ConvertToSarifLevel("medium"), 834 xrayutils.CreateLocation("file2", 2, 5, 3, 6, "gcp-violation"), 835 ), 836 }, 837 expectedAddedIacVulnerabilities: []formats.SourceCodeRow{ 838 { 839 SeverityDetails: formats.SeverityDetails{ 840 Severity: "Medium", 841 SeverityNumValue: 11, 842 }, 843 Finding: "enable_private_endpoint=false was detected", 844 Location: formats.Location{ 845 File: "file2", 846 StartLine: 2, 847 StartColumn: 5, 848 EndLine: 3, 849 EndColumn: 6, 850 Snippet: "gcp-violation", 851 }, 852 }, 853 }, 854 }, 855 } 856 857 for _, tc := range testCases { 858 t.Run(tc.name, func(t *testing.T) { 859 targetIacRows := xrayutils.PrepareIacs([]*sarif.Run{xrayutils.CreateRunWithDummyResults(tc.targetIacResults...)}) 860 sourceIacRows := xrayutils.PrepareIacs([]*sarif.Run{xrayutils.CreateRunWithDummyResults(tc.sourceIacResults...)}) 861 addedIacVulnerabilities := createNewSourceCodeRows(targetIacRows, sourceIacRows) 862 assert.ElementsMatch(t, tc.expectedAddedIacVulnerabilities, addedIacVulnerabilities) 863 }) 864 } 865 } 866 867 func TestCreateNewSecretRows(t *testing.T) { 868 testCases := []struct { 869 name string 870 targetSecretsResults []*sarif.Result 871 sourceSecretsResults []*sarif.Result 872 expectedAddedSecretsVulnerabilities []formats.SourceCodeRow 873 }{ 874 { 875 name: "No vulnerabilities in source secrets results", 876 targetSecretsResults: []*sarif.Result{ 877 xrayutils.CreateResultWithLocations("Secret", "rule", xrayutils.ConvertToSarifLevel("high"), 878 xrayutils.CreateLocation("file1", 1, 10, 2, 11, "Sensitive information"), 879 ), 880 }, 881 sourceSecretsResults: []*sarif.Result{}, 882 expectedAddedSecretsVulnerabilities: []formats.SourceCodeRow{}, 883 }, 884 { 885 name: "No vulnerabilities in target secrets results", 886 targetSecretsResults: []*sarif.Result{}, 887 sourceSecretsResults: []*sarif.Result{ 888 xrayutils.CreateResultWithLocations("Secret", "rule", xrayutils.ConvertToSarifLevel("high"), 889 xrayutils.CreateLocation("file1", 1, 10, 2, 11, "Sensitive information"), 890 ), 891 }, 892 expectedAddedSecretsVulnerabilities: []formats.SourceCodeRow{ 893 { 894 SeverityDetails: formats.SeverityDetails{ 895 Severity: "High", 896 SeverityNumValue: 13, 897 }, 898 Finding: "Secret", 899 Location: formats.Location{ 900 File: "file1", 901 StartLine: 1, 902 StartColumn: 10, 903 EndLine: 2, 904 EndColumn: 11, 905 Snippet: "Sensitive information", 906 }, 907 }, 908 }, 909 }, 910 { 911 name: "Some new vulnerabilities in source secrets results", 912 targetSecretsResults: []*sarif.Result{ 913 xrayutils.CreateResultWithLocations("Secret", "rule", xrayutils.ConvertToSarifLevel("high"), 914 xrayutils.CreateLocation("file1", 1, 10, 2, 11, "Sensitive information"), 915 ), 916 }, 917 sourceSecretsResults: []*sarif.Result{ 918 xrayutils.CreateResultWithLocations("Secret", "rule", xrayutils.ConvertToSarifLevel("medium"), 919 xrayutils.CreateLocation("file2", 2, 5, 3, 6, "Confidential data"), 920 ), 921 }, 922 expectedAddedSecretsVulnerabilities: []formats.SourceCodeRow{ 923 { 924 SeverityDetails: formats.SeverityDetails{ 925 Severity: "Medium", 926 SeverityNumValue: 11, 927 }, 928 Finding: "Secret", 929 Location: formats.Location{ 930 File: "file2", 931 StartLine: 2, 932 StartColumn: 5, 933 EndLine: 3, 934 EndColumn: 6, 935 Snippet: "Confidential data", 936 }, 937 }, 938 }, 939 }, 940 } 941 942 for _, tc := range testCases { 943 t.Run(tc.name, func(t *testing.T) { 944 targetSecretsRows := xrayutils.PrepareSecrets([]*sarif.Run{xrayutils.CreateRunWithDummyResults(tc.targetSecretsResults...)}) 945 sourceSecretsRows := xrayutils.PrepareSecrets([]*sarif.Run{xrayutils.CreateRunWithDummyResults(tc.sourceSecretsResults...)}) 946 addedSecretsVulnerabilities := createNewSourceCodeRows(targetSecretsRows, sourceSecretsRows) 947 assert.ElementsMatch(t, tc.expectedAddedSecretsVulnerabilities, addedSecretsVulnerabilities) 948 }) 949 } 950 } 951 952 func TestCreateNewSastRows(t *testing.T) { 953 testCases := []struct { 954 name string 955 targetSastResults []*sarif.Result 956 sourceSastResults []*sarif.Result 957 expectedAddedSastVulnerabilities []formats.SourceCodeRow 958 }{ 959 { 960 name: "No vulnerabilities in source Sast results", 961 targetSastResults: []*sarif.Result{ 962 xrayutils.CreateResultWithLocations("XSS Vulnerability", "rule", xrayutils.ConvertToSarifLevel("high"), 963 xrayutils.CreateLocation("file1", 1, 10, 2, 11, "snippet"), 964 ), 965 }, 966 sourceSastResults: []*sarif.Result{}, 967 expectedAddedSastVulnerabilities: []formats.SourceCodeRow{}, 968 }, 969 { 970 name: "No vulnerabilities in target Sast results", 971 targetSastResults: []*sarif.Result{}, 972 sourceSastResults: []*sarif.Result{ 973 xrayutils.CreateResultWithLocations("XSS Vulnerability", "rule", xrayutils.ConvertToSarifLevel("high"), 974 xrayutils.CreateLocation("file1", 1, 10, 2, 11, "snippet"), 975 ), 976 }, 977 expectedAddedSastVulnerabilities: []formats.SourceCodeRow{ 978 { 979 SeverityDetails: formats.SeverityDetails{ 980 Severity: "High", 981 SeverityNumValue: 13, 982 }, 983 Finding: "XSS Vulnerability", 984 Location: formats.Location{ 985 File: "file1", 986 StartLine: 1, 987 StartColumn: 10, 988 EndLine: 2, 989 EndColumn: 11, 990 Snippet: "snippet", 991 }, 992 }, 993 }, 994 }, 995 { 996 name: "Some new vulnerabilities in source Sast results", 997 targetSastResults: []*sarif.Result{ 998 xrayutils.CreateResultWithLocations("XSS Vulnerability", "rule", xrayutils.ConvertToSarifLevel("high"), 999 xrayutils.CreateLocation("file1", 1, 10, 2, 11, "snippet"), 1000 ), 1001 }, 1002 sourceSastResults: []*sarif.Result{ 1003 xrayutils.CreateResultWithLocations("Stack Trace Exposure", "rule", xrayutils.ConvertToSarifLevel("medium"), 1004 xrayutils.CreateLocation("file2", 2, 5, 3, 6, "other-snippet"), 1005 ), 1006 }, 1007 expectedAddedSastVulnerabilities: []formats.SourceCodeRow{ 1008 { 1009 SeverityDetails: formats.SeverityDetails{ 1010 Severity: "Medium", 1011 SeverityNumValue: 11, 1012 }, 1013 Finding: "Stack Trace Exposure", 1014 Location: formats.Location{ 1015 File: "file2", 1016 StartLine: 2, 1017 StartColumn: 5, 1018 EndLine: 3, 1019 EndColumn: 6, 1020 Snippet: "other-snippet", 1021 }, 1022 }, 1023 }, 1024 }, 1025 } 1026 1027 for _, tc := range testCases { 1028 t.Run(tc.name, func(t *testing.T) { 1029 targetSastRows := xrayutils.PrepareSast([]*sarif.Run{xrayutils.CreateRunWithDummyResults(tc.targetSastResults...)}) 1030 sourceSastRows := xrayutils.PrepareSast([]*sarif.Run{xrayutils.CreateRunWithDummyResults(tc.sourceSastResults...)}) 1031 addedSastVulnerabilities := createNewSourceCodeRows(targetSastRows, sourceSastRows) 1032 assert.ElementsMatch(t, tc.expectedAddedSastVulnerabilities, addedSastVulnerabilities) 1033 }) 1034 } 1035 } 1036 1037 func TestDeletePreviousPullRequestMessages(t *testing.T) { 1038 repository := &utils.Repository{ 1039 Params: utils.Params{ 1040 Git: utils.Git{ 1041 PullRequestDetails: vcsclient.PullRequestInfo{Target: vcsclient.BranchInfo{ 1042 Repository: "repo", 1043 Owner: "owner", 1044 }, ID: 17}, 1045 }, 1046 }, 1047 OutputWriter: &outputwriter.StandardOutput{}, 1048 } 1049 client := CreateMockVcsClient(t) 1050 1051 testCases := []struct { 1052 name string 1053 commentsOnPR []vcsclient.CommentInfo 1054 err error 1055 }{ 1056 { 1057 name: "Test with comment returned", 1058 commentsOnPR: []vcsclient.CommentInfo{ 1059 {ID: 20, Content: outputwriter.GetBanner(outputwriter.NoVulnerabilityPrBannerSource) + "text \n table\n text text text", Created: time.Unix(3, 0)}, 1060 }, 1061 }, 1062 { 1063 name: "Test with no comment returned", 1064 }, 1065 { 1066 name: "Test with error returned", 1067 err: errors.New("error"), 1068 }, 1069 } 1070 1071 for _, tc := range testCases { 1072 t.Run(tc.name, func(t *testing.T) { 1073 // Test with comment returned 1074 client.EXPECT().ListPullRequestComments(context.Background(), "owner", "repo", 17).Return(tc.commentsOnPR, tc.err) 1075 client.EXPECT().DeletePullRequestComment(context.Background(), "owner", "repo", 17, 20).Return(nil).AnyTimes() 1076 err := utils.DeleteExistingPullRequestComments(repository, client) 1077 if tc.err == nil { 1078 assert.NoError(t, err) 1079 } else { 1080 assert.Error(t, err) 1081 } 1082 }) 1083 } 1084 } 1085 1086 func TestDeletePreviousPullRequestReviewMessages(t *testing.T) { 1087 repository := &utils.Repository{ 1088 Params: utils.Params{ 1089 Git: utils.Git{ 1090 PullRequestDetails: vcsclient.PullRequestInfo{Target: vcsclient.BranchInfo{ 1091 Repository: "repo", 1092 Owner: "owner", 1093 }, ID: 17}, 1094 }, 1095 }, 1096 OutputWriter: &outputwriter.StandardOutput{}, 1097 } 1098 client := CreateMockVcsClient(t) 1099 1100 testCases := []struct { 1101 name string 1102 commentsOnPR []vcsclient.CommentInfo 1103 err error 1104 }{ 1105 { 1106 name: "Test with comment returned", 1107 commentsOnPR: []vcsclient.CommentInfo{ 1108 {ID: 20, Content: outputwriter.MarkdownComment(outputwriter.ReviewCommentId) + "text \n table\n text text text", Created: time.Unix(3, 0)}, 1109 }, 1110 }, 1111 { 1112 name: "Test with no comment returned", 1113 }, 1114 { 1115 name: "Test with error returned", 1116 err: errors.New("error"), 1117 }, 1118 } 1119 1120 for _, tc := range testCases { 1121 t.Run(tc.name, func(t *testing.T) { 1122 // Test with comment returned 1123 client.EXPECT().ListPullRequestReviewComments(context.Background(), "", "", 17).Return(tc.commentsOnPR, tc.err) 1124 client.EXPECT().DeletePullRequestReviewComments(context.Background(), "", "", 17, tc.commentsOnPR).Return(nil).AnyTimes() 1125 err := utils.DeleteExistingPullRequestReviewComments(repository, 17, client) 1126 if tc.err == nil { 1127 assert.NoError(t, err) 1128 } else { 1129 assert.Error(t, err) 1130 } 1131 }) 1132 } 1133 } 1134 1135 func TestAggregateScanResults(t *testing.T) { 1136 scanResult1 := services.ScanResponse{ 1137 Violations: []services.Violation{{IssueId: "Violation 1"}}, 1138 Vulnerabilities: []services.Vulnerability{{IssueId: "Vulnerability 1"}}, 1139 Licenses: []services.License{{Name: "License 1"}}, 1140 } 1141 1142 scanResult2 := services.ScanResponse{ 1143 Violations: []services.Violation{{IssueId: "Violation 2"}}, 1144 Vulnerabilities: []services.Vulnerability{{IssueId: "Vulnerability 2"}}, 1145 Licenses: []services.License{{Name: "License 2"}}, 1146 } 1147 1148 aggregateResult := aggregateScanResults([]services.ScanResponse{scanResult1, scanResult2}) 1149 expectedResult := services.ScanResponse{ 1150 Violations: []services.Violation{ 1151 {IssueId: "Violation 1"}, 1152 {IssueId: "Violation 2"}, 1153 }, 1154 Vulnerabilities: []services.Vulnerability{ 1155 {IssueId: "Vulnerability 1"}, 1156 {IssueId: "Vulnerability 2"}, 1157 }, 1158 Licenses: []services.License{ 1159 {Name: "License 1"}, 1160 {Name: "License 2"}, 1161 }, 1162 } 1163 1164 assert.Equal(t, expectedResult, aggregateResult) 1165 } 1166 1167 // Set new logger with output redirection to a null logger. This is useful for negative tests. 1168 // Caller is responsible to set the old log back. 1169 func redirectLogOutputToNil() (previousLog log.Log) { 1170 previousLog = log.Logger 1171 newLog := log.NewLogger(log.ERROR, nil) 1172 newLog.SetOutputWriter(io.Discard) 1173 newLog.SetLogsWriter(io.Discard, 0) 1174 log.SetLogger(newLog) 1175 return previousLog 1176 }