github.com/jfrog/frogbot/v2@v2.21.0/scanrepository/scanrepository_test.go (about) 1 package scanrepository 2 3 import ( 4 "errors" 5 "fmt" 6 "github.com/google/go-github/v45/github" 7 biutils "github.com/jfrog/build-info-go/utils" 8 "github.com/jfrog/frogbot/v2/utils" 9 "github.com/jfrog/frogbot/v2/utils/outputwriter" 10 "github.com/jfrog/froggit-go/vcsclient" 11 "github.com/jfrog/froggit-go/vcsutils" 12 "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" 13 "github.com/jfrog/jfrog-cli-security/formats" 14 xrayutils "github.com/jfrog/jfrog-cli-security/utils" 15 "github.com/jfrog/jfrog-client-go/utils/io/fileutils" 16 "github.com/jfrog/jfrog-client-go/utils/log" 17 "github.com/jfrog/jfrog-client-go/xray/services" 18 "github.com/stretchr/testify/assert" 19 "github.com/stretchr/testify/require" 20 "net/http/httptest" 21 "os" 22 "os/exec" 23 "path/filepath" 24 "strings" 25 "testing" 26 ) 27 28 const rootTestDir = "scanrepository" 29 30 var testPackagesData = []struct { 31 packageType string 32 commandName string 33 commandArgs []string 34 }{ 35 { 36 packageType: coreutils.Go.String(), 37 }, 38 { 39 packageType: coreutils.Maven.String(), 40 }, 41 { 42 packageType: coreutils.Gradle.String(), 43 }, 44 { 45 packageType: coreutils.Npm.String(), 46 commandName: "npm", 47 commandArgs: []string{"install"}, 48 }, 49 { 50 packageType: "yarn1", 51 commandName: "yarn", 52 commandArgs: []string{"install"}, 53 }, 54 { 55 packageType: "yarn2", 56 commandName: "yarn", 57 commandArgs: []string{"install"}, 58 }, 59 { 60 packageType: coreutils.Dotnet.String(), 61 commandName: "dotnet", 62 commandArgs: []string{"restore"}, 63 }, 64 { 65 packageType: coreutils.Nuget.String(), 66 commandName: "nuget", 67 commandArgs: []string{"restore"}, 68 }, 69 { 70 packageType: coreutils.Pip.String(), 71 }, 72 { 73 packageType: coreutils.Pipenv.String(), 74 }, 75 { 76 packageType: coreutils.Poetry.String(), 77 }, 78 } 79 80 func TestScanRepositoryCmd_Run(t *testing.T) { 81 tests := []struct { 82 testName string 83 configPath string 84 expectedPackagesInBranch map[string][]string 85 expectedVersionUpdatesInBranch map[string][]string 86 packageDescriptorPaths []string 87 aggregateFixes bool 88 }{ 89 { 90 testName: "aggregate", 91 expectedPackagesInBranch: map[string][]string{"frogbot-update-npm-dependencies-master": {"uuid", "minimist", "mpath"}}, 92 expectedVersionUpdatesInBranch: map[string][]string{"frogbot-update-npm-dependencies-master": {"^1.2.6", "^9.0.0", "^0.8.4"}}, 93 packageDescriptorPaths: []string{"package.json"}, 94 aggregateFixes: true, 95 }, 96 { 97 testName: "aggregate-multi-dir", 98 expectedPackagesInBranch: map[string][]string{"frogbot-update-npm-dependencies-master": {"uuid", "minimatch", "mpath", "minimist"}}, 99 expectedVersionUpdatesInBranch: map[string][]string{"frogbot-update-npm-dependencies-master": {"^1.2.6", "^9.0.0", "^0.8.4", "^3.0.5"}}, 100 packageDescriptorPaths: []string{"npm1/package.json", "npm2/package.json"}, 101 aggregateFixes: true, 102 configPath: "../testdata/scanrepository/cmd/aggregate-multi-dir/.frogbot/frogbot-config.yml", 103 }, 104 { 105 testName: "aggregate-multi-project", 106 expectedPackagesInBranch: map[string][]string{"frogbot-update-npm-dependencies-master": {"uuid", "minimatch", "mpath"}, "frogbot-update-Pip-dependencies-master": {"pyjwt", "pexpect"}}, 107 expectedVersionUpdatesInBranch: map[string][]string{"frogbot-update-npm-dependencies-master": {"^9.0.0", "^0.8.4", "^3.0.5"}, "frogbot-update-Pip-dependencies-master": {"2.4.0"}}, 108 packageDescriptorPaths: []string{"npm/package.json", "pip/requirements.txt"}, 109 aggregateFixes: true, 110 configPath: "../testdata/scanrepository/cmd/aggregate-multi-project/.frogbot/frogbot-config.yml", 111 }, 112 { 113 testName: "aggregate-no-vul", 114 // No branch is being created because there are no vulnerabilities. 115 expectedPackagesInBranch: map[string][]string{"master": {}}, 116 expectedVersionUpdatesInBranch: map[string][]string{"master": {}}, 117 packageDescriptorPaths: []string{"package.json"}, 118 aggregateFixes: true, 119 }, 120 { 121 testName: "aggregate-cant-fix", 122 // Branch name stays master as no new branch is being created 123 expectedPackagesInBranch: map[string][]string{"master": {}}, 124 expectedVersionUpdatesInBranch: map[string][]string{"master": {}}, 125 // This is a build tool dependency which should not be fixed. 126 packageDescriptorPaths: []string{"setup.py"}, 127 aggregateFixes: true, 128 }, 129 { 130 testName: "non-aggregate", 131 expectedPackagesInBranch: map[string][]string{"frogbot-minimist-258ad6a538b5ba800f18ae4f6d660302": {"minimist"}}, 132 expectedVersionUpdatesInBranch: map[string][]string{"frogbot-minimist-258ad6a538b5ba800f18ae4f6d660302": {"^1.2.6"}}, 133 packageDescriptorPaths: []string{"package.json"}, 134 aggregateFixes: false, 135 }, 136 } 137 baseDir, err := os.Getwd() 138 assert.NoError(t, err) 139 testDir, cleanup := utils.CopyTestdataProjectsToTemp(t, filepath.Join(rootTestDir, "cmd")) 140 defer cleanup() 141 for _, test := range tests { 142 t.Run(test.testName, func(t *testing.T) { 143 // Prepare 144 serverParams, restoreEnv := utils.VerifyEnv(t) 145 defer restoreEnv() 146 if test.aggregateFixes { 147 assert.NoError(t, os.Setenv(utils.GitAggregateFixesEnv, "true")) 148 defer func() { 149 assert.NoError(t, os.Setenv(utils.GitAggregateFixesEnv, "false")) 150 }() 151 } 152 var port string 153 server := httptest.NewServer(createScanRepoGitHubHandler(t, &port, nil, test.testName)) 154 defer server.Close() 155 port = server.URL[strings.LastIndex(server.URL, ":")+1:] 156 gitTestParams := utils.Git{ 157 GitProvider: vcsutils.GitHub, 158 VcsInfo: vcsclient.VcsInfo{ 159 Token: "123456", 160 APIEndpoint: server.URL, 161 }, 162 RepoName: test.testName, 163 RepoOwner: "jfrog", 164 } 165 client, err := vcsclient.NewClientBuilder(vcsutils.GitHub).ApiEndpoint(server.URL).Token("123456").Build() 166 assert.NoError(t, err) 167 168 // Read config or resolve to default 169 var configData []byte 170 if test.configPath != "" { 171 configData, err = utils.ReadConfigFromFileSystem(test.configPath) 172 assert.NoError(t, err) 173 } else { 174 configData = []byte{} 175 // Manual set of "JF_GIT_BASE_BRANCH" 176 gitTestParams.Branches = []string{"master"} 177 } 178 179 utils.CreateDotGitWithCommit(t, testDir, port, test.testName) 180 configAggregator, err := utils.BuildRepoAggregator(client, configData, &gitTestParams, &serverParams, utils.ScanRepository) 181 assert.NoError(t, err) 182 // Run 183 var cmd = ScanRepositoryCmd{dryRun: true, dryRunRepoPath: testDir} 184 err = cmd.Run(configAggregator, client, utils.MockHasConnection()) 185 defer func() { 186 assert.NoError(t, os.Chdir(baseDir)) 187 }() 188 189 // Validate 190 assert.NoError(t, err) 191 for branch, packages := range test.expectedPackagesInBranch { 192 resultDiff, err := verifyDependencyFileDiff("master", branch, test.packageDescriptorPaths...) 193 assert.NoError(t, err) 194 if len(packages) > 0 { 195 assert.NotEmpty(t, resultDiff) 196 } 197 for _, packageToUpdate := range packages { 198 assert.Contains(t, string(resultDiff), packageToUpdate) 199 } 200 packageVersionUpdatesInBranch := test.expectedVersionUpdatesInBranch[branch] 201 for _, updatedVersion := range packageVersionUpdatesInBranch { 202 assert.Contains(t, string(resultDiff), updatedVersion) 203 } 204 } 205 }) 206 } 207 } 208 209 // Tests the lifecycle of aggregated pull request 210 // No open pull request -> Open 211 // If Pull request already active, compare scan results for current and remote branch 212 // Same scan results -> do nothing. 213 // Different scan results -> Update the pull request branch & body. 214 func TestAggregatePullRequestLifecycle(t *testing.T) { 215 mockPrId := 1 216 sourceBranchName := "frogbot-update-npm-dependencies" 217 targetBranchName := "main" 218 sourceLabel := "repo:frogbot-update-npm-dependencies" 219 targetLabel := "repo:main" 220 firstBody := ` 221 [comment]: <> (Checksum: 4608a55b621cb6337ac93487979ac09c) 222 pr body 223 ` 224 secondBody := ` 225 [comment]: <> (Checksum: 01373ac4d2c32e7da9be22f3e4b4e665) 226 pr body 227 ` 228 tests := []struct { 229 testName string 230 expectedUpdate bool 231 mockPullRequestResponse []*github.PullRequest 232 }{ 233 { 234 testName: "aggregate-dont-update-pr", 235 expectedUpdate: false, 236 mockPullRequestResponse: []*github.PullRequest{{ 237 Number: &mockPrId, 238 Head: &github.PullRequestBranch{ 239 Label: &sourceLabel, 240 Repo: &github.Repository{Name: &sourceBranchName, Owner: &github.User{}}, 241 }, 242 Base: &github.PullRequestBranch{ 243 Label: &targetLabel, 244 Repo: &github.Repository{Name: &targetBranchName, Owner: &github.User{}}, 245 }, 246 Body: &firstBody, 247 }}, 248 }, 249 { 250 testName: "aggregate-update-pr", 251 expectedUpdate: true, 252 mockPullRequestResponse: []*github.PullRequest{{ 253 Number: &mockPrId, 254 Head: &github.PullRequestBranch{ 255 Label: &sourceLabel, 256 Repo: &github.Repository{Name: &sourceBranchName, Owner: &github.User{}}, 257 }, 258 Base: &github.PullRequestBranch{ 259 Label: &targetLabel, 260 Repo: &github.Repository{Name: &targetBranchName, Owner: &github.User{}}, 261 }, 262 Body: &secondBody, 263 }}, 264 }, 265 } 266 267 baseDir, err := os.Getwd() 268 assert.NoError(t, err) 269 serverParams, restoreEnv := utils.VerifyEnv(t) 270 defer restoreEnv() 271 testDir, cleanup := utils.CopyTestdataProjectsToTemp(t, filepath.Join(rootTestDir, "aggregate-pr-lifecycle")) 272 defer cleanup() 273 for _, test := range tests { 274 t.Run(test.testName, func(t *testing.T) { 275 var port string 276 server := httptest.NewServer(createScanRepoGitHubHandler(t, &port, test.mockPullRequestResponse, test.testName)) 277 defer server.Close() 278 port = server.URL[strings.LastIndex(server.URL, ":")+1:] 279 280 assert.NoError(t, os.Setenv(utils.GitAggregateFixesEnv, "true")) 281 defer func() { 282 assert.NoError(t, os.Setenv(utils.GitAggregateFixesEnv, "false")) 283 }() 284 285 gitTestParams := &utils.Git{ 286 GitProvider: vcsutils.GitHub, 287 RepoOwner: "jfrog", 288 VcsInfo: vcsclient.VcsInfo{ 289 Token: "123456", 290 APIEndpoint: server.URL, 291 }, RepoName: test.testName, 292 } 293 294 utils.CreateDotGitWithCommit(t, testDir, port, test.testName) 295 client, err := vcsclient.NewClientBuilder(vcsutils.GitHub).ApiEndpoint(server.URL).Token("123456").Build() 296 assert.NoError(t, err) 297 // Load default configurations 298 var configData []byte 299 gitTestParams.Branches = []string{"master"} 300 configAggregator, err := utils.BuildRepoAggregator(client, configData, gitTestParams, &serverParams, utils.ScanRepository) 301 assert.NoError(t, err) 302 // Run 303 var cmd = ScanRepositoryCmd{dryRun: true, dryRunRepoPath: testDir} 304 err = cmd.Run(configAggregator, client, utils.MockHasConnection()) 305 defer func() { 306 assert.NoError(t, os.Chdir(baseDir)) 307 }() 308 assert.NoError(t, err) 309 }) 310 } 311 } 312 313 // / 1.0 --> 1.0 ≤ x 314 // / (,1.0] --> x ≤ 1.0 315 // / (,1.0) --> x < 1.0 316 // / [1.0] --> x == 1.0 317 // / (1.0,) --> 1.0 < x 318 // / (1.0, 2.0) --> 1.0 < x < 2.0 319 // / [1.0, 2.0] --> 1.0 ≤ x ≤ 2.0 320 func TestParseVersionChangeString(t *testing.T) { 321 tests := []struct { 322 versionChangeString string 323 expectedVersion string 324 }{ 325 {"1.2.3", "1.2.3"}, 326 {"[1.2.3]", "1.2.3"}, 327 {"[1.2.3, 2.0.0]", "1.2.3"}, 328 329 {"(,1.2.3]", ""}, 330 {"(,1.2.3)", ""}, 331 {"(1.2.3,)", ""}, 332 {"(1.2.3, 2.0.0)", ""}, 333 } 334 335 for _, test := range tests { 336 t.Run(test.versionChangeString, func(t *testing.T) { 337 assert.Equal(t, test.expectedVersion, parseVersionChangeString(test.versionChangeString)) 338 }) 339 } 340 } 341 342 func TestGenerateFixBranchName(t *testing.T) { 343 tests := []struct { 344 baseBranch string 345 impactedPackage string 346 fixVersion string 347 expectedName string 348 }{ 349 {"dev", "gopkg.in/yaml.v3", "3.0.0", "frogbot-gopkg.in/yaml.v3-d61bde82dc594e5ccc5a042fe224bf7c"}, 350 {"master", "gopkg.in/yaml.v3", "3.0.0", "frogbot-gopkg.in/yaml.v3-41405528994061bd108e3bbd4c039a03"}, 351 {"dev", "replace:colons:colons", "3.0.0", "frogbot-replace_colons_colons-89e555131b4a70a32fe9d9c44d6ff0fc"}, 352 } 353 gitManager := utils.GitManager{} 354 for _, test := range tests { 355 t.Run(test.expectedName, func(t *testing.T) { 356 branchName, err := gitManager.GenerateFixBranchName(test.baseBranch, test.impactedPackage, test.fixVersion) 357 assert.NoError(t, err) 358 assert.Equal(t, test.expectedName, branchName) 359 }) 360 } 361 } 362 363 func TestPackageTypeFromScan(t *testing.T) { 364 environmentVars, restoreEnv := utils.VerifyEnv(t) 365 defer restoreEnv() 366 testScan := &ScanRepositoryCmd{OutputWriter: &outputwriter.StandardOutput{}} 367 trueVal := true 368 params := utils.Params{ 369 Scan: utils.Scan{Projects: []utils.Project{{UseWrapper: &trueVal}}}, 370 } 371 var frogbotParams = utils.Repository{ 372 Server: environmentVars, 373 Params: params, 374 } 375 for _, pkg := range testPackagesData { 376 // Create temp technology project 377 projectPath := filepath.Join("..", "testdata", "projects", pkg.packageType) 378 t.Run(pkg.packageType, func(t *testing.T) { 379 tmpDir, err := fileutils.CreateTempDir() 380 defer func() { 381 err = fileutils.RemoveTempDir(tmpDir) 382 }() 383 assert.NoError(t, err) 384 assert.NoError(t, biutils.CopyDir(projectPath, tmpDir, true, nil)) 385 if pkg.packageType == coreutils.Gradle.String() { 386 assert.NoError(t, os.Chmod(filepath.Join(tmpDir, "gradlew"), 0777)) 387 assert.NoError(t, os.Chmod(filepath.Join(tmpDir, "gradlew.bat"), 0777)) 388 } 389 frogbotParams.Projects[0].WorkingDirs = []string{tmpDir} 390 files, err := fileutils.ListFiles(tmpDir, true) 391 assert.NoError(t, err) 392 for _, file := range files { 393 log.Info(file) 394 } 395 frogbotParams.Projects[0].InstallCommandName = pkg.commandName 396 frogbotParams.Projects[0].InstallCommandArgs = pkg.commandArgs 397 scanSetup := utils.ScanDetails{ 398 XrayGraphScanParams: &services.XrayGraphScanParams{}, 399 Project: &frogbotParams.Projects[0], 400 ServerDetails: &frogbotParams.Server, 401 } 402 testScan.scanDetails = &scanSetup 403 scanResponse, err := testScan.scan(tmpDir) 404 require.NoError(t, err) 405 verifyTechnologyNaming(t, scanResponse.GetScaScansXrayResults(), pkg.packageType) 406 }) 407 } 408 } 409 410 func TestGetMinimalFixVersion(t *testing.T) { 411 tests := []struct { 412 impactedVersionPackage string 413 fixVersions []string 414 expected string 415 }{ 416 {impactedVersionPackage: "1.6.2", fixVersions: []string{"1.5.3", "1.6.1", "1.6.22", "1.7.0"}, expected: "1.6.22"}, 417 {impactedVersionPackage: "v1.6.2", fixVersions: []string{"1.5.3", "1.6.1", "1.6.22", "1.7.0"}, expected: "1.6.22"}, 418 {impactedVersionPackage: "1.7.1", fixVersions: []string{"1.5.3", "1.6.1", "1.6.22", "1.7.0"}, expected: ""}, 419 {impactedVersionPackage: "1.7.1", fixVersions: []string{"2.5.3"}, expected: "2.5.3"}, 420 {impactedVersionPackage: "v1.7.1", fixVersions: []string{"0.5.3", "0.9.9"}, expected: ""}, 421 } 422 for _, test := range tests { 423 t.Run(test.expected, func(t *testing.T) { 424 expected := getMinimalFixVersion(test.impactedVersionPackage, test.fixVersions) 425 assert.Equal(t, test.expected, expected) 426 }) 427 } 428 } 429 430 func TestCreateVulnerabilitiesMap(t *testing.T) { 431 cfp := &ScanRepositoryCmd{} 432 433 testCases := []struct { 434 name string 435 scanResults *xrayutils.Results 436 isMultipleRoots bool 437 expectedMap map[string]*utils.VulnerabilityDetails 438 }{ 439 { 440 name: "Scan results with no violations and vulnerabilities", 441 scanResults: &xrayutils.Results{ 442 ScaResults: []xrayutils.ScaScanResult{}, 443 ExtendedScanResults: &xrayutils.ExtendedScanResults{}, 444 }, 445 expectedMap: map[string]*utils.VulnerabilityDetails{}, 446 }, 447 { 448 name: "Scan results with vulnerabilities and no violations", 449 scanResults: &xrayutils.Results{ 450 ScaResults: []xrayutils.ScaScanResult{{ 451 XrayResults: []services.ScanResponse{ 452 { 453 Vulnerabilities: []services.Vulnerability{ 454 { 455 Cves: []services.Cve{ 456 {Id: "CVE-2023-1234", CvssV3Score: "9.1"}, 457 {Id: "CVE-2023-4321", CvssV3Score: "8.9"}, 458 }, 459 Severity: "Critical", 460 Components: map[string]services.Component{ 461 "vuln1": { 462 FixedVersions: []string{"1.9.1", "2.0.3", "2.0.5"}, 463 ImpactPaths: [][]services.ImpactPathNode{{{ComponentId: "root"}, {ComponentId: "vuln1"}}}, 464 }, 465 }, 466 }, 467 { 468 Cves: []services.Cve{ 469 {Id: "CVE-2022-1234", CvssV3Score: "7.1"}, 470 {Id: "CVE-2022-4321", CvssV3Score: "7.9"}, 471 }, 472 Severity: "High", 473 Components: map[string]services.Component{ 474 "vuln2": { 475 FixedVersions: []string{"2.4.1", "2.6.3", "2.8.5"}, 476 ImpactPaths: [][]services.ImpactPathNode{{{ComponentId: "root"}, {ComponentId: "vuln1"}, {ComponentId: "vuln2"}}}, 477 }, 478 }, 479 }, 480 }, 481 }, 482 }, 483 }}, 484 ExtendedScanResults: &xrayutils.ExtendedScanResults{}, 485 }, 486 expectedMap: map[string]*utils.VulnerabilityDetails{ 487 "vuln1": { 488 SuggestedFixedVersion: "1.9.1", 489 IsDirectDependency: true, 490 Cves: []string{"CVE-2023-1234", "CVE-2023-4321"}, 491 }, 492 "vuln2": { 493 SuggestedFixedVersion: "2.4.1", 494 Cves: []string{"CVE-2022-1234", "CVE-2022-4321"}, 495 }, 496 }, 497 }, 498 { 499 name: "Scan results with violations and no vulnerabilities", 500 scanResults: &xrayutils.Results{ 501 ScaResults: []xrayutils.ScaScanResult{{ 502 XrayResults: []services.ScanResponse{ 503 { 504 Violations: []services.Violation{ 505 { 506 ViolationType: "security", 507 Cves: []services.Cve{ 508 {Id: "CVE-2023-1234", CvssV3Score: "9.1"}, 509 {Id: "CVE-2023-4321", CvssV3Score: "8.9"}, 510 }, 511 Severity: "Critical", 512 Components: map[string]services.Component{ 513 "viol1": { 514 FixedVersions: []string{"1.9.1", "2.0.3", "2.0.5"}, 515 ImpactPaths: [][]services.ImpactPathNode{{{ComponentId: "root"}, {ComponentId: "viol1"}}}, 516 }, 517 }, 518 }, 519 { 520 ViolationType: "security", 521 Cves: []services.Cve{ 522 {Id: "CVE-2022-1234", CvssV3Score: "7.1"}, 523 {Id: "CVE-2022-4321", CvssV3Score: "7.9"}, 524 }, 525 Severity: "High", 526 Components: map[string]services.Component{ 527 "viol2": { 528 FixedVersions: []string{"2.4.1", "2.6.3", "2.8.5"}, 529 ImpactPaths: [][]services.ImpactPathNode{{{ComponentId: "root"}, {ComponentId: "viol1"}, {ComponentId: "viol2"}}}, 530 }, 531 }, 532 }, 533 }, 534 }, 535 }, 536 }}, 537 ExtendedScanResults: &xrayutils.ExtendedScanResults{}, 538 }, 539 expectedMap: map[string]*utils.VulnerabilityDetails{ 540 "viol1": { 541 SuggestedFixedVersion: "1.9.1", 542 IsDirectDependency: true, 543 Cves: []string{"CVE-2023-1234", "CVE-2023-4321"}, 544 }, 545 "viol2": { 546 SuggestedFixedVersion: "2.4.1", 547 Cves: []string{"CVE-2022-1234", "CVE-2022-4321"}, 548 }, 549 }, 550 }, 551 } 552 553 for _, testCase := range testCases { 554 t.Run(testCase.name, func(t *testing.T) { 555 fixVersionsMap, err := cfp.createVulnerabilitiesMap(testCase.scanResults, testCase.isMultipleRoots) 556 assert.NoError(t, err) 557 for name, expectedVuln := range testCase.expectedMap { 558 actualVuln, exists := fixVersionsMap[name] 559 require.True(t, exists) 560 assert.Equal(t, expectedVuln.IsDirectDependency, actualVuln.IsDirectDependency) 561 assert.Equal(t, expectedVuln.SuggestedFixedVersion, actualVuln.SuggestedFixedVersion) 562 assert.ElementsMatch(t, expectedVuln.Cves, actualVuln.Cves) 563 } 564 }) 565 } 566 } 567 568 // Verifies unsupported packages return specific error 569 // Other logic is implemented inside each package-handler. 570 func TestUpdatePackageToFixedVersion(t *testing.T) { 571 var testScan ScanRepositoryCmd 572 for tech, buildToolsDependencies := range utils.BuildToolsDependenciesMap { 573 for _, impactedDependency := range buildToolsDependencies { 574 vulnDetails := &utils.VulnerabilityDetails{SuggestedFixedVersion: "3.3.3", VulnerabilityOrViolationRow: formats.VulnerabilityOrViolationRow{Technology: tech, ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ImpactedDependencyName: impactedDependency}}, IsDirectDependency: true} 575 err := testScan.updatePackageToFixedVersion(vulnDetails) 576 assert.Error(t, err, "Expected error to occur") 577 assert.IsType(t, &utils.ErrUnsupportedFix{}, err, "Expected unsupported fix error") 578 } 579 } 580 } 581 582 func TestGetRemoteBranchScanHash(t *testing.T) { 583 prBody := ` 584 a body 585 586 [Comment]: <> (Checksum: myhash4321) 587 ` 588 cfp := &ScanRepositoryCmd{} 589 result := cfp.getRemoteBranchScanHash(prBody) 590 assert.Equal(t, "myhash4321", result) 591 prBody = ` 592 random body 593 ` 594 result = cfp.getRemoteBranchScanHash(prBody) 595 assert.Equal(t, "", result) 596 } 597 598 func TestPreparePullRequestDetails(t *testing.T) { 599 cfp := ScanRepositoryCmd{OutputWriter: &outputwriter.StandardOutput{}, gitManager: &utils.GitManager{}} 600 cfp.OutputWriter.SetJasOutputFlags(true, false) 601 vulnerabilities := []*utils.VulnerabilityDetails{ 602 { 603 VulnerabilityOrViolationRow: formats.VulnerabilityOrViolationRow{ 604 Summary: "summary", 605 ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ 606 SeverityDetails: formats.SeverityDetails{Severity: "High", SeverityNumValue: 10}, 607 ImpactedDependencyName: "package1", 608 ImpactedDependencyVersion: "1.0.0", 609 }, 610 FixedVersions: []string{"1.0.0", "2.0.0"}, 611 Cves: []formats.CveRow{{Id: "CVE-2022-1234"}}, 612 }, 613 SuggestedFixedVersion: "1.0.0", 614 }, 615 } 616 expectedPrBody, expectedExtraComments := utils.GenerateFixPullRequestDetails(utils.ExtractVulnerabilitiesDetailsToRows(vulnerabilities), cfp.OutputWriter) 617 prTitle, prBody, extraComments, err := cfp.preparePullRequestDetails(vulnerabilities...) 618 assert.NoError(t, err) 619 assert.Equal(t, "[🐸 Frogbot] Update version of package1 to 1.0.0", prTitle) 620 assert.Equal(t, expectedPrBody, prBody) 621 assert.ElementsMatch(t, expectedExtraComments, extraComments) 622 vulnerabilities = append(vulnerabilities, &utils.VulnerabilityDetails{ 623 VulnerabilityOrViolationRow: formats.VulnerabilityOrViolationRow{ 624 Summary: "summary", 625 ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ 626 SeverityDetails: formats.SeverityDetails{Severity: "Critical", SeverityNumValue: 12}, 627 ImpactedDependencyName: "package2", 628 ImpactedDependencyVersion: "2.0.0", 629 }, 630 FixedVersions: []string{"2.0.0", "3.0.0"}, 631 Cves: []formats.CveRow{{Id: "CVE-2022-4321"}}, 632 }, 633 SuggestedFixedVersion: "2.0.0", 634 }) 635 cfp.aggregateFixes = true 636 expectedPrBody, expectedExtraComments = utils.GenerateFixPullRequestDetails(utils.ExtractVulnerabilitiesDetailsToRows(vulnerabilities), cfp.OutputWriter) 637 expectedPrBody += outputwriter.MarkdownComment("Checksum: bec823edaceb5d0478b789798e819bde") 638 prTitle, prBody, extraComments, err = cfp.preparePullRequestDetails(vulnerabilities...) 639 assert.NoError(t, err) 640 assert.Equal(t, cfp.gitManager.GenerateAggregatedPullRequestTitle([]coreutils.Technology{}), prTitle) 641 assert.Equal(t, expectedPrBody, prBody) 642 assert.ElementsMatch(t, expectedExtraComments, extraComments) 643 cfp.OutputWriter = &outputwriter.SimplifiedOutput{} 644 expectedPrBody, expectedExtraComments = utils.GenerateFixPullRequestDetails(utils.ExtractVulnerabilitiesDetailsToRows(vulnerabilities), cfp.OutputWriter) 645 expectedPrBody += outputwriter.MarkdownComment("Checksum: bec823edaceb5d0478b789798e819bde") 646 prTitle, prBody, extraComments, err = cfp.preparePullRequestDetails(vulnerabilities...) 647 assert.NoError(t, err) 648 assert.Equal(t, cfp.gitManager.GenerateAggregatedPullRequestTitle([]coreutils.Technology{}), prTitle) 649 assert.Equal(t, expectedPrBody, prBody) 650 assert.ElementsMatch(t, expectedExtraComments, extraComments) 651 } 652 653 func verifyTechnologyNaming(t *testing.T, scanResponse []services.ScanResponse, expectedType string) { 654 for _, resp := range scanResponse { 655 for _, vulnerability := range resp.Vulnerabilities { 656 assert.Equal(t, expectedType, vulnerability.Technology) 657 } 658 } 659 } 660 661 // Executing git diff to ensure that the intended changes to the dependent file have been made 662 func verifyDependencyFileDiff(baseBranch string, fixBranch string, packageDescriptorPaths ...string) (output []byte, err error) { 663 log.Debug(fmt.Sprintf("Checking differences in %s between branches %s and %s", packageDescriptorPaths, baseBranch, fixBranch)) 664 // Suppress condition always false warning 665 //goland:noinspection ALL 666 var args []string 667 if coreutils.IsWindows() { 668 args = []string{"/c", "git", "diff", baseBranch, fixBranch} 669 args = append(args, packageDescriptorPaths...) 670 output, err = exec.Command("cmd", args...).Output() 671 } else { 672 args = []string{"diff", baseBranch, fixBranch} 673 args = append(args, packageDescriptorPaths...) 674 output, err = exec.Command("git", args...).Output() 675 } 676 var exitError *exec.ExitError 677 if errors.As(err, &exitError) { 678 err = errors.New("git error: " + string(exitError.Stderr)) 679 } 680 return 681 }