diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 92c14f9aa..4b41ecc3d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -60,7 +60,7 @@ jobs: # Run tests - name: Tests - run: go test ./... -v -race -timeout 20m -cover -coverprofile=covprofile -covermode=atomic + run: go test ./... -v -race -timeout 30m -cover -coverprofile=covprofile -covermode=atomic env: JF_URL: ${{ secrets.PLATFORM_URL }} JF_ACCESS_TOKEN: ${{ secrets.PLATFORM_ADMIN_TOKEN }} diff --git a/commands/createfixpullrequests_test.go b/commands/createfixpullrequests_test.go index b0486d4f6..cb8ab22d0 100644 --- a/commands/createfixpullrequests_test.go +++ b/commands/createfixpullrequests_test.go @@ -1,194 +1,194 @@ package commands -//import ( -// "github.com/jfrog/jfrog-cli-core/v2/xray/formats" -// "github.com/jfrog/jfrog-client-go/utils/io/fileutils" -// "github.com/jfrog/jfrog-client-go/utils/log" -// "github.com/stretchr/testify/assert" -// "os" -// "path/filepath" -// "testing" -// -// "github.com/jfrog/frogbot/commands/utils" -// "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" -// "github.com/jfrog/jfrog-client-go/xray/services" -//) -// -//var testPackagesData = []struct { -// packageType coreutils.Technology -// commandName string -// commandArgs []string -//}{ -// { -// packageType: coreutils.Go, -// }, -// { -// packageType: coreutils.Maven, -// }, -// { -// packageType: coreutils.Gradle, -// }, -// { -// packageType: coreutils.Npm, -// commandName: "npm", -// commandArgs: []string{"install"}, -// }, -// { -// packageType: coreutils.Yarn, -// commandName: "yarn", -// commandArgs: []string{"install"}, -// }, -// { -// packageType: coreutils.Dotnet, -// commandName: "dotnet", -// commandArgs: []string{"restore"}, -// }, -// { -// packageType: coreutils.Pip, -// }, -// { -// packageType: coreutils.Pipenv, -// }, -// { -// packageType: coreutils.Poetry, -// }, -//} -// -//// / 1.0 --> 1.0 ≤ x -//// / (,1.0] --> x ≤ 1.0 -//// / (,1.0) --> x < 1.0 -//// / [1.0] --> x == 1.0 -//// / (1.0,) --> 1.0 < x -//// / (1.0, 2.0) --> 1.0 < x < 2.0 -//// / [1.0, 2.0] --> 1.0 ≤ x ≤ 2.0 -//func TestParseVersionChangeString(t *testing.T) { -// tests := []struct { -// versionChangeString string -// expectedVersion string -// }{ -// {"1.2.3", "1.2.3"}, -// {"[1.2.3]", "1.2.3"}, -// {"[1.2.3, 2.0.0]", "1.2.3"}, -// -// {"(,1.2.3]", ""}, -// {"(,1.2.3)", ""}, -// {"(1.2.3,)", ""}, -// {"(1.2.3, 2.0.0)", ""}, -// } -// -// for _, test := range tests { -// t.Run(test.versionChangeString, func(t *testing.T) { -// assert.Equal(t, test.expectedVersion, parseVersionChangeString(test.versionChangeString)) -// }) -// } -//} -// -//func TestGenerateFixBranchName(t *testing.T) { -// tests := []struct { -// baseBranch string -// impactedPackage string -// fixVersion string -// expectedName string -// }{ -// {"dev", "gopkg.in/yaml.v3", "3.0.0", "frogbot-gopkg.in/yaml.v3-d61bde82dc594e5ccc5a042fe224bf7c"}, -// {"master", "gopkg.in/yaml.v3", "3.0.0", "frogbot-gopkg.in/yaml.v3-41405528994061bd108e3bbd4c039a03"}, -// {"dev", "replace:colons:colons", "3.0.0", "frogbot-replace_colons_colons-89e555131b4a70a32fe9d9c44d6ff0fc"}, -// } -// gitManager := utils.GitManager{} -// for _, test := range tests { -// t.Run(test.expectedName, func(t *testing.T) { -// branchName, err := gitManager.GenerateFixBranchName(test.baseBranch, test.impactedPackage, test.fixVersion) -// assert.NoError(t, err) -// assert.Equal(t, test.expectedName, branchName) -// }) -// } -//} -// -//func TestPackageTypeFromScan(t *testing.T) { -// environmentVars, restoreEnv := verifyEnv(t) -// defer restoreEnv() -// var testScan CreateFixPullRequestsCmd -// trueVal := true -// params := utils.Params{ -// Scan: utils.Scan{Projects: []utils.Project{{UseWrapper: &trueVal}}}, -// } -// var frogbotParams = utils.Repository{ -// Server: environmentVars, -// Params: params, -// } -// for _, pkg := range testPackagesData { -// // Create temp technology project -// projectPath := filepath.Join("testdata", "projects", pkg.packageType.ToString()) -// t.Run(pkg.packageType.ToString(), func(t *testing.T) { -// tmpDir, err := fileutils.CreateTempDir() -// assert.NoError(t, err) -// defer func() { -// assert.NoError(t, fileutils.RemoveTempDir(tmpDir)) -// }() -// assert.NoError(t, fileutils.CopyDir(projectPath, tmpDir, true, nil)) -// if pkg.packageType == coreutils.Gradle { -// assert.NoError(t, os.Chmod(filepath.Join(tmpDir, "gradlew"), 0777)) -// assert.NoError(t, os.Chmod(filepath.Join(tmpDir, "gradlew.bat"), 0777)) -// } -// frogbotParams.Projects[0].WorkingDirs = []string{tmpDir} -// files, err := fileutils.ListFiles(tmpDir, true) -// assert.NoError(t, err) -// for _, file := range files { -// log.Info(file) -// } -// frogbotParams.Projects[0].InstallCommandName = pkg.commandName -// frogbotParams.Projects[0].InstallCommandArgs = pkg.commandArgs -// scanSetup := utils.ScanDetails{ -// XrayGraphScanParams: &services.XrayGraphScanParams{}, -// Project: &frogbotParams.Projects[0], -// ServerDetails: &frogbotParams.Server, -// } -// testScan.details = &scanSetup -// scanResponse, err := testScan.scan(tmpDir) -// assert.NoError(t, err) -// verifyTechnologyNaming(t, scanResponse.ExtendedScanResults.XrayResults, pkg.packageType) -// }) -// } -//} -// -//func TestGetMinimalFixVersion(t *testing.T) { -// tests := []struct { -// impactedVersionPackage string -// fixVersions []string -// expected string -// }{ -// {impactedVersionPackage: "1.6.2", fixVersions: []string{"1.5.3", "1.6.1", "1.6.22", "1.7.0"}, expected: "1.6.22"}, -// {impactedVersionPackage: "v1.6.2", fixVersions: []string{"1.5.3", "1.6.1", "1.6.22", "1.7.0"}, expected: "1.6.22"}, -// {impactedVersionPackage: "1.7.1", fixVersions: []string{"1.5.3", "1.6.1", "1.6.22", "1.7.0"}, expected: ""}, -// {impactedVersionPackage: "1.7.1", fixVersions: []string{"2.5.3"}, expected: "2.5.3"}, -// {impactedVersionPackage: "v1.7.1", fixVersions: []string{"0.5.3", "0.9.9"}, expected: ""}, -// } -// for _, test := range tests { -// t.Run(test.expected, func(t *testing.T) { -// expected := getMinimalFixVersion(test.impactedVersionPackage, test.fixVersions) -// assert.Equal(t, test.expected, expected) -// }) -// } -//} -// -//// Verifies unsupported packages return specific error -//// Other logic is implemented inside each package-handler. -//func TestUpdatePackageToFixedVersion(t *testing.T) { -// var testScan CreateFixPullRequestsCmd -// for tech, buildToolsDependencies := range utils.BuildToolsDependenciesMap { -// for _, impactedDependency := range buildToolsDependencies { -// vulnDetails := &utils.VulnerabilityDetails{FixVersion: "3.3.3", VulnerabilityOrViolationRow: &formats.VulnerabilityOrViolationRow{Technology: tech, ImpactedDependencyName: impactedDependency}, IsDirectDependency: true} -// err := testScan.updatePackageToFixedVersion(vulnDetails) -// assert.Error(t, err, "Expected error to occur") -// assert.IsType(t, &utils.ErrUnsupportedFix{}, err, "Expected unsupported fix error") -// } -// } -//} -// -//func verifyTechnologyNaming(t *testing.T, scanResponse []services.ScanResponse, expectedType coreutils.Technology) { -// for _, resp := range scanResponse { -// for _, vulnerability := range resp.Vulnerabilities { -// assert.Equal(t, expectedType.ToString(), vulnerability.Technology) -// } -// } -//} +import ( + "github.com/jfrog/jfrog-cli-core/v2/xray/formats" + "github.com/jfrog/jfrog-client-go/utils/io/fileutils" + "github.com/jfrog/jfrog-client-go/utils/log" + "github.com/stretchr/testify/assert" + "os" + "path/filepath" + "testing" + + "github.com/jfrog/frogbot/commands/utils" + "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" + "github.com/jfrog/jfrog-client-go/xray/services" +) + +var testPackagesData = []struct { + packageType coreutils.Technology + commandName string + commandArgs []string +}{ + { + packageType: coreutils.Go, + }, + { + packageType: coreutils.Maven, + }, + { + packageType: coreutils.Gradle, + }, + { + packageType: coreutils.Npm, + commandName: "npm", + commandArgs: []string{"install"}, + }, + { + packageType: coreutils.Yarn, + commandName: "yarn", + commandArgs: []string{"install"}, + }, + { + packageType: coreutils.Dotnet, + commandName: "dotnet", + commandArgs: []string{"restore"}, + }, + { + packageType: coreutils.Pip, + }, + { + packageType: coreutils.Pipenv, + }, + { + packageType: coreutils.Poetry, + }, +} + +// / 1.0 --> 1.0 ≤ x +// / (,1.0] --> x ≤ 1.0 +// / (,1.0) --> x < 1.0 +// / [1.0] --> x == 1.0 +// / (1.0,) --> 1.0 < x +// / (1.0, 2.0) --> 1.0 < x < 2.0 +// / [1.0, 2.0] --> 1.0 ≤ x ≤ 2.0 +func TestParseVersionChangeString(t *testing.T) { + tests := []struct { + versionChangeString string + expectedVersion string + }{ + {"1.2.3", "1.2.3"}, + {"[1.2.3]", "1.2.3"}, + {"[1.2.3, 2.0.0]", "1.2.3"}, + + {"(,1.2.3]", ""}, + {"(,1.2.3)", ""}, + {"(1.2.3,)", ""}, + {"(1.2.3, 2.0.0)", ""}, + } + + for _, test := range tests { + t.Run(test.versionChangeString, func(t *testing.T) { + assert.Equal(t, test.expectedVersion, parseVersionChangeString(test.versionChangeString)) + }) + } +} + +func TestGenerateFixBranchName(t *testing.T) { + tests := []struct { + baseBranch string + impactedPackage string + fixVersion string + expectedName string + }{ + {"dev", "gopkg.in/yaml.v3", "3.0.0", "frogbot-gopkg.in/yaml.v3-d61bde82dc594e5ccc5a042fe224bf7c"}, + {"master", "gopkg.in/yaml.v3", "3.0.0", "frogbot-gopkg.in/yaml.v3-41405528994061bd108e3bbd4c039a03"}, + {"dev", "replace:colons:colons", "3.0.0", "frogbot-replace_colons_colons-89e555131b4a70a32fe9d9c44d6ff0fc"}, + } + gitManager := utils.GitManager{} + for _, test := range tests { + t.Run(test.expectedName, func(t *testing.T) { + branchName, err := gitManager.GenerateFixBranchName(test.baseBranch, test.impactedPackage, test.fixVersion) + assert.NoError(t, err) + assert.Equal(t, test.expectedName, branchName) + }) + } +} + +func TestPackageTypeFromScan(t *testing.T) { + environmentVars, restoreEnv := verifyEnv(t) + defer restoreEnv() + var testScan CreateFixPullRequestsCmd + trueVal := true + params := utils.Params{ + Scan: utils.Scan{Projects: []utils.Project{{UseWrapper: &trueVal}}}, + } + var frogbotParams = utils.Repository{ + Server: environmentVars, + Params: params, + } + for _, pkg := range testPackagesData { + // Create temp technology project + projectPath := filepath.Join("testdata", "projects", pkg.packageType.ToString()) + t.Run(pkg.packageType.ToString(), func(t *testing.T) { + tmpDir, err := fileutils.CreateTempDir() + assert.NoError(t, err) + defer func() { + assert.NoError(t, fileutils.RemoveTempDir(tmpDir)) + }() + assert.NoError(t, fileutils.CopyDir(projectPath, tmpDir, true, nil)) + if pkg.packageType == coreutils.Gradle { + assert.NoError(t, os.Chmod(filepath.Join(tmpDir, "gradlew"), 0777)) + assert.NoError(t, os.Chmod(filepath.Join(tmpDir, "gradlew.bat"), 0777)) + } + frogbotParams.Projects[0].WorkingDirs = []string{tmpDir} + files, err := fileutils.ListFiles(tmpDir, true) + assert.NoError(t, err) + for _, file := range files { + log.Info(file) + } + frogbotParams.Projects[0].InstallCommandName = pkg.commandName + frogbotParams.Projects[0].InstallCommandArgs = pkg.commandArgs + scanSetup := utils.ScanDetails{ + XrayGraphScanParams: &services.XrayGraphScanParams{}, + Project: &frogbotParams.Projects[0], + ServerDetails: &frogbotParams.Server, + } + testScan.details = &scanSetup + scanResponse, err := testScan.scan(tmpDir) + assert.NoError(t, err) + verifyTechnologyNaming(t, scanResponse.ExtendedScanResults.XrayResults, pkg.packageType) + }) + } +} + +func TestGetMinimalFixVersion(t *testing.T) { + tests := []struct { + impactedVersionPackage string + fixVersions []string + expected string + }{ + {impactedVersionPackage: "1.6.2", fixVersions: []string{"1.5.3", "1.6.1", "1.6.22", "1.7.0"}, expected: "1.6.22"}, + {impactedVersionPackage: "v1.6.2", fixVersions: []string{"1.5.3", "1.6.1", "1.6.22", "1.7.0"}, expected: "1.6.22"}, + {impactedVersionPackage: "1.7.1", fixVersions: []string{"1.5.3", "1.6.1", "1.6.22", "1.7.0"}, expected: ""}, + {impactedVersionPackage: "1.7.1", fixVersions: []string{"2.5.3"}, expected: "2.5.3"}, + {impactedVersionPackage: "v1.7.1", fixVersions: []string{"0.5.3", "0.9.9"}, expected: ""}, + } + for _, test := range tests { + t.Run(test.expected, func(t *testing.T) { + expected := getMinimalFixVersion(test.impactedVersionPackage, test.fixVersions) + assert.Equal(t, test.expected, expected) + }) + } +} + +// Verifies unsupported packages return specific error +// Other logic is implemented inside each package-handler. +func TestUpdatePackageToFixedVersion(t *testing.T) { + var testScan CreateFixPullRequestsCmd + for tech, buildToolsDependencies := range utils.BuildToolsDependenciesMap { + for _, impactedDependency := range buildToolsDependencies { + vulnDetails := &utils.VulnerabilityDetails{FixVersion: "3.3.3", VulnerabilityOrViolationRow: &formats.VulnerabilityOrViolationRow{Technology: tech, ImpactedDependencyName: impactedDependency}, IsDirectDependency: true} + err := testScan.updatePackageToFixedVersion(vulnDetails) + assert.Error(t, err, "Expected error to occur") + assert.IsType(t, &utils.ErrUnsupportedFix{}, err, "Expected unsupported fix error") + } + } +} + +func verifyTechnologyNaming(t *testing.T, scanResponse []services.ScanResponse, expectedType coreutils.Technology) { + for _, resp := range scanResponse { + for _, vulnerability := range resp.Vulnerabilities { + assert.Equal(t, expectedType.ToString(), vulnerability.Technology) + } + } +} diff --git a/commands/scanpullrequest_test.go b/commands/scanpullrequest_test.go index 762e129b5..e7ad91178 100644 --- a/commands/scanpullrequest_test.go +++ b/commands/scanpullrequest_test.go @@ -10,683 +10,670 @@ import ( "testing" ) -// import ( -// -// "bytes" -// "context" -// "errors" -// "fmt" -// audit "github.com/jfrog/jfrog-cli-core/v2/xray/commands/audit/generic" -// utils2 "github.com/jfrog/jfrog-cli-core/v2/xray/utils" -// "github.com/stretchr/testify/assert" -// "io" -// "net/http" -// "net/http/httptest" -// "os" -// "path/filepath" -// "strings" -// "testing" -// -// "github.com/jfrog/froggit-go/vcsclient" -// "github.com/jfrog/froggit-go/vcsutils" -// coreconfig "github.com/jfrog/jfrog-cli-core/v2/utils/config" -// -// "github.com/jfrog/frogbot/commands/utils" -// "github.com/jfrog/jfrog-cli-core/v2/xray/formats" -// "github.com/jfrog/jfrog-client-go/utils/io/fileutils" -// "github.com/jfrog/jfrog-client-go/utils/log" -// "github.com/jfrog/jfrog-client-go/xray/services" -// clitool "github.com/urfave/cli/v2" -// -// ) -// -// const ( -// -// testMultiDirProjConfigPath = "testdata/config/frogbot-config-multi-dir-test-proj.yml" -// testMultiDirProjConfigPathNoFail = "testdata/config/frogbot-config-multi-dir-test-proj-no-fail.yml" -// testProjSubdirConfigPath = "testdata/config/frogbot-config-test-proj-subdir.yml" -// testCleanProjConfigPath = "testdata/config/frogbot-config-clean-test-proj.yml" -// testProjConfigPath = "testdata/config/frogbot-config-test-proj.yml" -// testProjConfigPathNoFail = "testdata/config/frogbot-config-test-proj-no-fail.yml" -// -// ) -// -// func TestCreateVulnerabilitiesRows(t *testing.T) { -// // Previous scan with only one violation - XRAY-1 -// previousScan := services.ScanResponse{ -// Violations: []services.Violation{{ -// IssueId: "XRAY-1", -// Summary: "summary-1", -// Severity: "high", -// Cves: []services.Cve{}, -// ViolationType: "security", -// Components: map[string]services.Component{"component-A": {}, "component-B": {}}, -// }}, -// } -// -// // Current scan with 2 violations - XRAY-1 and XRAY-2 -// currentScan := services.ScanResponse{ -// Violations: []services.Violation{ -// { -// IssueId: "XRAY-1", -// Summary: "summary-1", -// Severity: "high", -// ViolationType: "security", -// Components: map[string]services.Component{"component-A": {}, "component-B": {}}, -// }, -// { -// IssueId: "XRAY-2", -// Summary: "summary-2", -// ViolationType: "security", -// Severity: "low", -// Components: map[string]services.Component{"component-C": {}, "component-D": {}}, -// }, -// }, -// } -// -// // Run createNewIssuesRows and make sure that only the XRAY-2 violation exists in the results -// rows, err := createNewIssuesRows( -// &audit.Results{ExtendedScanResults: &utils2.ExtendedScanResults{XrayResults: []services.ScanResponse{previousScan}}}, -// &audit.Results{ExtendedScanResults: &utils2.ExtendedScanResults{XrayResults: []services.ScanResponse{currentScan}}}, -// ) -// assert.NoError(t, err) -// assert.Len(t, rows, 2) -// assert.Equal(t, "XRAY-2", rows[0].IssueId) -// assert.Equal(t, "low", rows[0].Severity) -// assert.Equal(t, "XRAY-2", rows[1].IssueId) -// assert.Equal(t, "low", rows[1].Severity) -// -// impactedPackageOne := rows[0].ImpactedDependencyName -// impactedPackageTwo := rows[1].ImpactedDependencyName -// assert.ElementsMatch(t, []string{"component-C", "component-D"}, []string{impactedPackageOne, impactedPackageTwo}) -// } -// -// func TestCreateVulnerabilitiesRowsCaseNoPrevViolations(t *testing.T) { -// // Previous scan with no violation -// previousScan := services.ScanResponse{ -// Violations: []services.Violation{}, -// } -// -// // Current scan with 2 violations - XRAY-1 and XRAY-2 -// currentScan := services.ScanResponse{ -// Violations: []services.Violation{ -// { -// IssueId: "XRAY-1", -// Summary: "summary-1", -// Severity: "high", -// ViolationType: "security", -// Components: map[string]services.Component{"component-A": {}}, -// }, -// { -// IssueId: "XRAY-2", -// Summary: "summary-2", -// ViolationType: "security", -// Severity: "low", -// Components: map[string]services.Component{"component-C": {}}, -// }, -// }, -// } -// -// expected := []formats.VulnerabilityOrViolationRow{ -// { -// IssueId: "XRAY-1", -// Severity: "high", -// ImpactedDependencyName: "component-A", -// }, -// { -// IssueId: "XRAY-2", -// Severity: "low", -// ImpactedDependencyName: "component-C", -// }, -// } -// -// // Run createNewIssuesRows and expect both XRAY-1 and XRAY-2 violation in the results -// rows, err := createNewIssuesRows( -// &audit.Results{ExtendedScanResults: &utils2.ExtendedScanResults{XrayResults: []services.ScanResponse{previousScan}}}, -// &audit.Results{ExtendedScanResults: &utils2.ExtendedScanResults{XrayResults: []services.ScanResponse{currentScan}}}, -// ) -// assert.NoError(t, err) -// assert.Len(t, rows, 2) -// assert.ElementsMatch(t, expected, rows) -// } -// -// func TestGetNewViolationsCaseNoNewViolations(t *testing.T) { -// // Previous scan with 2 violations - XRAY-1 and XRAY-2 -// previousScan := services.ScanResponse{ -// Violations: []services.Violation{ -// { -// IssueId: "XRAY-1", -// Severity: "high", -// ViolationType: "security", -// Components: map[string]services.Component{"component-A": {}}, -// }, -// { -// IssueId: "XRAY-2", -// Summary: "summary-2", -// ViolationType: "security", -// Severity: "low", -// Components: map[string]services.Component{"component-C": {}}, -// }, -// }, -// } -// -// // Current scan with no violation -// currentScan := services.ScanResponse{ -// Violations: []services.Violation{}, -// } -// -// // Run createNewIssuesRows and expect no violations in the results -// rows, err := createNewIssuesRows( -// &audit.Results{ExtendedScanResults: &utils2.ExtendedScanResults{XrayResults: []services.ScanResponse{previousScan}}}, -// &audit.Results{ExtendedScanResults: &utils2.ExtendedScanResults{XrayResults: []services.ScanResponse{currentScan}}}, -// ) -// assert.NoError(t, err) -// assert.Len(t, rows, 0) -// } -// -// func TestGetAllVulnerabilities(t *testing.T) { -// // Current scan with 2 vulnerabilities - XRAY-1 and XRAY-2 -// currentScan := services.ScanResponse{ -// Vulnerabilities: []services.Vulnerability{ -// { -// IssueId: "XRAY-1", -// Summary: "summary-1", -// Severity: "high", -// Components: map[string]services.Component{"component-A": {}, "component-B": {}}, -// }, -// { -// IssueId: "XRAY-2", -// Summary: "summary-2", -// Severity: "low", -// Components: map[string]services.Component{"component-C": {}, "component-D": {}}, -// }, -// }, -// } -// -// expected := []formats.VulnerabilityOrViolationRow{ -// { -// Summary: "summary-1", -// IssueId: "XRAY-1", -// Severity: "high", -// ImpactedDependencyName: "component-A", -// }, -// { -// Summary: "summary-1", -// IssueId: "XRAY-1", -// Severity: "high", -// ImpactedDependencyName: "component-B", -// }, -// { -// Summary: "summary-2", -// IssueId: "XRAY-2", -// Severity: "low", -// ImpactedDependencyName: "component-C", -// }, -// { -// Summary: "summary-2", -// IssueId: "XRAY-2", -// Severity: "low", -// ImpactedDependencyName: "component-D", -// }, -// } -// -// // Run createAllIssuesRows and make sure that XRAY-1 and XRAY-2 vulnerabilities exists in the results -// rows, err := getScanVulnerabilitiesRows(&audit.Results{ExtendedScanResults: &utils2.ExtendedScanResults{XrayResults: []services.ScanResponse{currentScan}}}) -// assert.NoError(t, err) -// assert.Len(t, rows, 4) -// assert.ElementsMatch(t, expected, rows) -// } -// -// func TestGetNewVulnerabilities(t *testing.T) { -// // Previous scan with only one vulnerability - XRAY-1 -// previousScan := services.ScanResponse{ -// Vulnerabilities: []services.Vulnerability{{ -// IssueId: "XRAY-1", -// Summary: "summary-1", -// Severity: "high", -// Components: map[string]services.Component{"component-A": {}, "component-B": {}}, -// }}, -// } -// -// // Current scan with 2 vulnerabilities - XRAY-1 and XRAY-2 -// currentScan := services.ScanResponse{ -// Vulnerabilities: []services.Vulnerability{ -// { -// IssueId: "XRAY-1", -// Summary: "summary-1", -// Severity: "high", -// Components: map[string]services.Component{"component-A": {}, "component-B": {}}, -// }, -// { -// IssueId: "XRAY-2", -// Summary: "summary-2", -// Severity: "low", -// Components: map[string]services.Component{"component-C": {}, "component-D": {}}, -// }, -// }, -// } -// -// expected := []formats.VulnerabilityOrViolationRow{ -// { -// Summary: "summary-2", -// Applicable: "Undetermined", -// IssueId: "XRAY-2", -// Severity: "low", -// ImpactedDependencyName: "component-C", -// }, -// { -// Summary: "summary-2", -// Applicable: "Undetermined", -// IssueId: "XRAY-2", -// Severity: "low", -// ImpactedDependencyName: "component-D", -// }, -// } -// -// // Run createNewIssuesRows and make sure that only the XRAY-2 vulnerability exists in the results -// rows, err := createNewIssuesRows( -// &audit.Results{ExtendedScanResults: &utils2.ExtendedScanResults{XrayResults: []services.ScanResponse{previousScan}, EntitledForJas: true}}, -// &audit.Results{ExtendedScanResults: &utils2.ExtendedScanResults{XrayResults: []services.ScanResponse{currentScan}, EntitledForJas: true}}, -// ) -// assert.NoError(t, err) -// assert.Len(t, rows, 2) -// assert.ElementsMatch(t, expected, rows) -// } -// -// func TestGetNewVulnerabilitiesCaseNoPrevVulnerabilities(t *testing.T) { -// // Previous scan with no vulnerabilities -// previousScan := services.ScanResponse{ -// Vulnerabilities: []services.Vulnerability{}, -// } -// -// // Current scan with 2 vulnerabilities - XRAY-1 and XRAY-2 -// currentScan := services.ScanResponse{ -// Vulnerabilities: []services.Vulnerability{ -// { -// IssueId: "XRAY-1", -// Summary: "summary-1", -// Severity: "high", -// ExtendedInformation: &services.ExtendedInformation{FullDescription: "description-1"}, -// Components: map[string]services.Component{"component-A": {}}, -// }, -// { -// IssueId: "XRAY-2", -// Summary: "summary-2", -// Severity: "low", -// ExtendedInformation: &services.ExtendedInformation{FullDescription: "description-2"}, -// Components: map[string]services.Component{"component-B": {}}, -// }, -// }, -// } -// -// expected := []formats.VulnerabilityOrViolationRow{ -// { -// Summary: "summary-2", -// IssueId: "XRAY-2", -// Severity: "low", -// ImpactedDependencyName: "component-B", -// JfrogResearchInformation: &formats.JfrogResearchInformation{Details: "description-2"}, -// }, -// { -// Summary: "summary-1", -// IssueId: "XRAY-1", -// Severity: "high", -// ImpactedDependencyName: "component-A", -// JfrogResearchInformation: &formats.JfrogResearchInformation{Details: "description-1"}, -// }, -// } -// -// // Run createNewIssuesRows and expect both XRAY-1 and XRAY-2 vulnerability in the results -// rows, err := createNewIssuesRows( -// &audit.Results{ExtendedScanResults: &utils2.ExtendedScanResults{XrayResults: []services.ScanResponse{previousScan}}}, -// &audit.Results{ExtendedScanResults: &utils2.ExtendedScanResults{XrayResults: []services.ScanResponse{currentScan}}}, -// ) -// assert.NoError(t, err) -// assert.Len(t, rows, 2) -// assert.ElementsMatch(t, expected, rows) -// } -// -// func TestGetNewVulnerabilitiesCaseNoNewVulnerabilities(t *testing.T) { -// // Previous scan with 2 vulnerabilities - XRAY-1 and XRAY-2 -// previousScan := services.ScanResponse{ -// Vulnerabilities: []services.Vulnerability{ -// { -// IssueId: "XRAY-1", -// Summary: "summary-1", -// Severity: "high", -// Components: map[string]services.Component{"component-A": {}}, -// }, -// { -// IssueId: "XRAY-2", -// Summary: "summary-2", -// Severity: "low", -// Components: map[string]services.Component{"component-B": {}}, -// }, -// }, -// } -// -// // Current scan with no vulnerabilities -// currentScan := services.ScanResponse{ -// Vulnerabilities: []services.Vulnerability{}, -// } -// -// // Run createNewIssuesRows and expect no vulnerability in the results -// rows, err := createNewIssuesRows( -// &audit.Results{ExtendedScanResults: &utils2.ExtendedScanResults{XrayResults: []services.ScanResponse{previousScan}}}, -// &audit.Results{ExtendedScanResults: &utils2.ExtendedScanResults{XrayResults: []services.ScanResponse{currentScan}}}, -// ) -// assert.NoError(t, err) -// assert.Len(t, rows, 0) -// } -// -// func TestCreatePullRequestMessageNoVulnerabilities(t *testing.T) { -// vulnerabilities := []formats.VulnerabilityOrViolationRow{} -// message := createPullRequestMessage(vulnerabilities, nil, &utils.StandardOutput{}) -// -// expectedMessageByte, err := os.ReadFile(filepath.Join("testdata", "messages", "novulnerabilities.md")) -// assert.NoError(t, err) -// expectedMessage := strings.ReplaceAll(string(expectedMessageByte), "\r\n", "\n") -// assert.Equal(t, expectedMessage, message) -// -// outputWriter := &utils.StandardOutput{} -// outputWriter.SetVcsProvider(vcsutils.GitLab) -// message = createPullRequestMessage(vulnerabilities, nil, outputWriter) -// -// expectedMessageByte, err = os.ReadFile(filepath.Join("testdata", "messages", "novulnerabilitiesMR.md")) -// assert.NoError(t, err) -// expectedMessage = strings.ReplaceAll(string(expectedMessageByte), "\r\n", "\n") -// assert.Equal(t, expectedMessage, message) -// } -// -// func TestCreatePullRequestMessage(t *testing.T) { -// vulnerabilities := []formats.VulnerabilityOrViolationRow{ -// { -// Severity: "High", -// Applicable: "Undetermined", -// ImpactedDependencyName: "github.com/nats-io/nats-streaming-server", -// ImpactedDependencyVersion: "v0.21.0", -// FixedVersions: []string{"[0.24.1]"}, -// Components: []formats.ComponentRow{ -// { -// Name: "github.com/nats-io/nats-streaming-server", -// Version: "v0.21.0", -// }, -// }, -// Cves: []formats.CveRow{{Id: "CVE-2022-24450"}}, -// }, -// { -// Severity: "High", -// Applicable: "Undetermined", -// ImpactedDependencyName: "github.com/mholt/archiver/v3", -// ImpactedDependencyVersion: "v3.5.1", -// Components: []formats.ComponentRow{ -// { -// Name: "github.com/mholt/archiver/v3", -// Version: "v3.5.1", -// }, -// }, -// Cves: []formats.CveRow{}, -// }, -// { -// Severity: "Medium", -// Applicable: "Undetermined", -// ImpactedDependencyName: "github.com/nats-io/nats-streaming-server", -// ImpactedDependencyVersion: "v0.21.0", -// FixedVersions: []string{"[0.24.3]"}, -// Components: []formats.ComponentRow{ -// { -// Name: "github.com/nats-io/nats-streaming-server", -// Version: "v0.21.0", -// }, -// }, -// Cves: []formats.CveRow{{Id: "CVE-2022-26652"}}, -// }, -// } -// iac := []formats.IacSecretsRow{ -// { -// Severity: "Low", -// File: "test.js", -// LineColumn: "1:20", -// Text: "kms_key_id='' was detected", -// Type: "aws_cloudtrail_encrypt", -// }, -// { -// Severity: "High", -// File: "test2.js", -// LineColumn: "4:30", -// Text: "Deprecated TLS version was detected", -// Type: "aws_cloudfront_tls_version", -// }, -// } -// writerOutput := &utils.StandardOutput{} -// writerOutput.SetEntitledForJas(true) -// message := createPullRequestMessage(vulnerabilities, iac, writerOutput) -// -// expectedMessage := "[![](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/vulnerabilitiesBannerPR.png)](https://github.com/jfrog/frogbot#readme)\n## 📦 Vulnerable Dependencies \n\n### ✍️ Summary\n\n
\n\n| SEVERITY | CONTEXTUAL ANALYSIS | DIRECT DEPENDENCIES | IMPACTED DEPENDENCY | FIXED VERSIONS |\n| :---------------------: | :----------------------------------: | :----------------------------------: | :-----------------------------------: | :---------------------------------: | \n| ![](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/applicableHighSeverity.png)
High | $\\color{}{\\textsf{Undetermined}}$ |github.com/nats-io/nats-streaming-server:v0.21.0 | github.com/nats-io/nats-streaming-server:v0.21.0 | [0.24.1] |\n| ![](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/applicableHighSeverity.png)
High | $\\color{}{\\textsf{Undetermined}}$ |github.com/mholt/archiver/v3:v3.5.1 | github.com/mholt/archiver/v3:v3.5.1 | |\n| ![](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/applicableMediumSeverity.png)
Medium | $\\color{}{\\textsf{Undetermined}}$ |github.com/nats-io/nats-streaming-server:v0.21.0 | github.com/nats-io/nats-streaming-server:v0.21.0 | [0.24.3] |\n\n
\n\n## 👇 Details\n\n\n
\n github.com/nats-io/nats-streaming-server v0.21.0 \n
\n\n- **Severity:** 🔥 High\n- **Package Name:** github.com/nats-io/nats-streaming-server\n- **Current Version:** v0.21.0\n- **Fixed Version:** [0.24.1]\n- **CVEs:** CVE-2022-24450\n\n**Description:**\n\n\n\n\n
\n\n\n
\n github.com/mholt/archiver/v3 v3.5.1 \n
\n\n- **Severity:** 🔥 High\n- **Package Name:** github.com/mholt/archiver/v3\n- **Current Version:** v3.5.1\n- **Fixed Version:** \n- **CVEs:** \n\n**Description:**\n\n\n\n\n
\n\n\n
\n github.com/nats-io/nats-streaming-server v0.21.0 \n
\n\n- **Severity:** 🎃 Medium\n- **Package Name:** github.com/nats-io/nats-streaming-server\n- **Current Version:** v0.21.0\n- **Fixed Version:** [0.24.3]\n- **CVEs:** CVE-2022-26652\n\n**Description:**\n\n\n\n\n
\n\n\n## 🛠️ Infrastructure as Code \n\n
\n\n\n| SEVERITY | FILE | LINE:COLUMN | FINDING | SCANNER |\n| :---------------------: | :----------------------------------: | :-----------------------------------: | :---------------------------------: | :---------------------------------: | \n| ![](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/applicableLowSeverity.png)
Low | test.js | 1:20 | kms_key_id='' was detected | aws_cloudtrail_encrypt |\n| ![](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/applicableHighSeverity.png)
High | test2.js | 4:30 | Deprecated TLS version was detected | aws_cloudfront_tls_version |\n\n
\n\n\n---\n\n
\n\n[This comment was generated by JFrog Frogbot 🐸](https://github.com/jfrog/frogbot#readme)\n\n
\n" -// assert.Equal(t, expectedMessage, message) -// -// writerOutput.SetVcsProvider(vcsutils.GitLab) -// message = createPullRequestMessage(vulnerabilities, iac, writerOutput) -// expectedMessage = "[![](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/vulnerabilitiesBannerMR.png)](https://github.com/jfrog/frogbot#readme)\n## 📦 Vulnerable Dependencies \n\n### ✍️ Summary\n\n
\n\n| SEVERITY | CONTEXTUAL ANALYSIS | DIRECT DEPENDENCIES | IMPACTED DEPENDENCY | FIXED VERSIONS |\n| :---------------------: | :----------------------------------: | :----------------------------------: | :-----------------------------------: | :---------------------------------: | \n| ![](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/applicableHighSeverity.png)
High | $\\color{}{\\textsf{Undetermined}}$ |github.com/nats-io/nats-streaming-server:v0.21.0 | github.com/nats-io/nats-streaming-server:v0.21.0 | [0.24.1] |\n| ![](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/applicableHighSeverity.png)
High | $\\color{}{\\textsf{Undetermined}}$ |github.com/mholt/archiver/v3:v3.5.1 | github.com/mholt/archiver/v3:v3.5.1 | |\n| ![](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/applicableMediumSeverity.png)
Medium | $\\color{}{\\textsf{Undetermined}}$ |github.com/nats-io/nats-streaming-server:v0.21.0 | github.com/nats-io/nats-streaming-server:v0.21.0 | [0.24.3] |\n\n
\n\n## 👇 Details\n\n\n
\n github.com/nats-io/nats-streaming-server v0.21.0 \n
\n\n- **Severity:** 🔥 High\n- **Package Name:** github.com/nats-io/nats-streaming-server\n- **Current Version:** v0.21.0\n- **Fixed Version:** [0.24.1]\n- **CVEs:** CVE-2022-24450\n\n**Description:**\n\n\n\n\n
\n\n\n
\n github.com/mholt/archiver/v3 v3.5.1 \n
\n\n- **Severity:** 🔥 High\n- **Package Name:** github.com/mholt/archiver/v3\n- **Current Version:** v3.5.1\n- **Fixed Version:** \n- **CVEs:** \n\n**Description:**\n\n\n\n\n
\n\n\n
\n github.com/nats-io/nats-streaming-server v0.21.0 \n
\n\n- **Severity:** 🎃 Medium\n- **Package Name:** github.com/nats-io/nats-streaming-server\n- **Current Version:** v0.21.0\n- **Fixed Version:** [0.24.3]\n- **CVEs:** CVE-2022-26652\n\n**Description:**\n\n\n\n\n
\n\n\n## 🛠️ Infrastructure as Code \n\n
\n\n\n| SEVERITY | FILE | LINE:COLUMN | FINDING | SCANNER |\n| :---------------------: | :----------------------------------: | :-----------------------------------: | :---------------------------------: | :---------------------------------: | \n| ![](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/applicableLowSeverity.png)
Low | test.js | 1:20 | kms_key_id='' was detected | aws_cloudtrail_encrypt |\n| ![](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/applicableHighSeverity.png)
High | test2.js | 4:30 | Deprecated TLS version was detected | aws_cloudfront_tls_version |\n\n
\n\n\n---\n\n
\n\n[This comment was generated by JFrog Frogbot 🐸](https://github.com/jfrog/frogbot#readme)\n\n
\n" -// assert.Equal(t, expectedMessage, message) -// -// } -// -// func TestRunInstallIfNeeded(t *testing.T) { -// scanSetup := utils.ScanDetails{ -// Project: &utils.Project{}, -// } -// scanSetup.SetFailOnInstallationErrors(true) -// assert.NoError(t, runInstallIfNeeded(&scanSetup, "")) -// tmpDir, err := fileutils.CreateTempDir() -// assert.NoError(t, err) -// params := &utils.Project{ -// InstallCommandName: "echo", -// InstallCommandArgs: []string{"Hello"}, -// } -// scanSetup.Project = params -// assert.NoError(t, runInstallIfNeeded(&scanSetup, tmpDir)) -// -// scanSetup.InstallCommandName = "not-exist" -// scanSetup.InstallCommandArgs = []string{"1", "2"} -// scanSetup.SetFailOnInstallationErrors(false) -// assert.NoError(t, runInstallIfNeeded(&scanSetup, tmpDir)) -// -// params = &utils.Project{ -// InstallCommandName: "not-existed", -// InstallCommandArgs: []string{"1", "2"}, -// } -// scanSetup.Project = params -// scanSetup.SetFailOnInstallationErrors(true) -// assert.Error(t, runInstallIfNeeded(&scanSetup, tmpDir)) -// } -// -// func TestScanPullRequest(t *testing.T) { -// testScanPullRequest(t, testProjConfigPath, "test-proj", true) -// } -// -// func TestScanPullRequestNoFail(t *testing.T) { -// testScanPullRequest(t, testProjConfigPathNoFail, "test-proj", false) -// } -// -// func TestScanPullRequestSubdir(t *testing.T) { -// testScanPullRequest(t, testProjSubdirConfigPath, "test-proj-subdir", true) -// } -// -// func TestScanPullRequestNoIssues(t *testing.T) { -// testScanPullRequest(t, testCleanProjConfigPath, "clean-test-proj", false) -// } -// -// func TestScanPullRequestMultiWorkDir(t *testing.T) { -// testScanPullRequest(t, testMultiDirProjConfigPath, "multi-dir-test-proj", true) -// } -// -// func TestScanPullRequestMultiWorkDirNoFail(t *testing.T) { -// testScanPullRequest(t, testMultiDirProjConfigPathNoFail, "multi-dir-test-proj", false) -// } -// -// func testScanPullRequest(t *testing.T, configPath, projectName string, failOnSecurityIssues bool) { -// params, restoreEnv := verifyEnv(t) -// defer restoreEnv() -// -// // Create mock GitLab server -// server := httptest.NewServer(createGitLabHandler(t, projectName)) -// defer server.Close() -// -// configAggregator, client := prepareConfigAndClient(t, configPath, server, params) -// _, cleanUp := utils.PrepareTestEnvironment(t, projectName, "scanpullrequest") -// defer cleanUp() -// -// // Run "frogbot scan pull request" -// var scanPullRequest ScanPullRequestCmd -// err := scanPullRequest.Run(configAggregator, client) -// if failOnSecurityIssues { -// assert.EqualErrorf(t, err, securityIssueFoundErr, "Error should be: %v, got: %v", securityIssueFoundErr, err) -// } else { -// assert.NoError(t, err) -// } -// -// // Check env sanitize -// err = utils.SanitizeEnv() -// assert.NoError(t, err) -// utils.AssertSanitizedEnv(t) -// } -// -// func TestVerifyGitHubFrogbotEnvironment(t *testing.T) { -// // Init mock -// client := mockVcsClient(t) -// environment := "frogbot" -// client.EXPECT().GetRepositoryInfo(context.Background(), gitParams.RepoOwner, gitParams.RepoName).Return(vcsclient.RepositoryInfo{}, nil) -// client.EXPECT().GetRepositoryEnvironmentInfo(context.Background(), gitParams.RepoOwner, gitParams.RepoName, environment).Return(vcsclient.RepositoryEnvironmentInfo{Reviewers: []string{"froggy"}}, nil) -// assert.NoError(t, os.Setenv(utils.GitHubActionsEnv, "true")) -// -// // Run verifyGitHubFrogbotEnvironment -// err := verifyGitHubFrogbotEnvironment(client, gitParams) -// assert.NoError(t, err) -// } -// -// func TestVerifyGitHubFrogbotEnvironmentNoEnv(t *testing.T) { -// // Redirect log to avoid negative output -// previousLogger := redirectLogOutputToNil() -// defer log.SetLogger(previousLogger) -// -// // Init mock -// client := mockVcsClient(t) -// environment := "frogbot" -// client.EXPECT().GetRepositoryInfo(context.Background(), gitParams.RepoOwner, gitParams.RepoName).Return(vcsclient.RepositoryInfo{}, nil) -// client.EXPECT().GetRepositoryEnvironmentInfo(context.Background(), gitParams.RepoOwner, gitParams.RepoName, environment).Return(vcsclient.RepositoryEnvironmentInfo{}, errors.New("404")) -// assert.NoError(t, os.Setenv(utils.GitHubActionsEnv, "true")) -// -// // Run verifyGitHubFrogbotEnvironment -// err := verifyGitHubFrogbotEnvironment(client, gitParams) -// assert.ErrorContains(t, err, noGitHubEnvErr) -// } -// -// func TestVerifyGitHubFrogbotEnvironmentNoReviewers(t *testing.T) { -// // Init mock -// client := mockVcsClient(t) -// environment := "frogbot" -// client.EXPECT().GetRepositoryInfo(context.Background(), gitParams.RepoOwner, gitParams.RepoName).Return(vcsclient.RepositoryInfo{}, nil) -// client.EXPECT().GetRepositoryEnvironmentInfo(context.Background(), gitParams.RepoOwner, gitParams.RepoName, environment).Return(vcsclient.RepositoryEnvironmentInfo{}, nil) -// assert.NoError(t, os.Setenv(utils.GitHubActionsEnv, "true")) -// -// // Run verifyGitHubFrogbotEnvironment -// err := verifyGitHubFrogbotEnvironment(client, gitParams) -// assert.ErrorContains(t, err, noGitHubEnvReviewersErr) -// } -// -// func TestVerifyGitHubFrogbotEnvironmentOnPrem(t *testing.T) { -// repoConfig := &utils.Repository{ -// Params: utils.Params{Git: utils.Git{ClientInfo: utils.ClientInfo{ -// VcsInfo: vcsclient.VcsInfo{APIEndpoint: "https://acme.vcs.io"}}}, -// }, -// } -// -// // Run verifyGitHubFrogbotEnvironment -// err := verifyGitHubFrogbotEnvironment(&vcsclient.GitHubClient{}, repoConfig) -// assert.NoError(t, err) -// } -// -// func prepareConfigAndClient(t *testing.T, configPath string, server *httptest.Server, serverParams coreconfig.ServerDetails) (utils.RepoAggregator, vcsclient.VcsClient) { -// git := &utils.ClientInfo{ -// GitProvider: vcsutils.GitHub, -// RepoOwner: "jfrog", -// VcsInfo: vcsclient.VcsInfo{ -// Token: "123456", -// APIEndpoint: server.URL, -// }, -// } -// utils.SetEnvAndAssert(t, map[string]string{utils.GitPullRequestIDEnv: "1"}) -// -// configData, err := utils.ReadConfigFromFileSystem(configPath) -// assert.NoError(t, err) -// configAggregator, err := utils.BuildRepoAggregator(configData, git, &serverParams) -// assert.NoError(t, err) -// -// client, err := vcsclient.NewClientBuilder(vcsutils.GitLab).ApiEndpoint(server.URL).Token("123456").Build() -// assert.NoError(t, err) -// return configAggregator, client -// } -// -// func TestScanPullRequestError(t *testing.T) { -// app := clitool.App{Commands: GetCommands()} -// assert.Error(t, app.Run([]string{"frogbot", "spr"})) -// } -// -// // Create HTTP handler to mock GitLab server -// -// func createGitLabHandler(t *testing.T, projectName string) http.HandlerFunc { -// return func(w http.ResponseWriter, r *http.Request) { -// // Return 200 on ping -// if r.RequestURI == "/api/v4/" { -// w.WriteHeader(http.StatusOK) -// return -// } -// -// // Return test-proj.tar.gz when using DownloadRepository -// if r.RequestURI == fmt.Sprintf("/api/v4/projects/jfrog%s/repository/archive.tar.gz?sha=master", "%2F"+projectName) { -// w.WriteHeader(http.StatusOK) -// repoFile, err := os.ReadFile(filepath.Join("..", projectName+".tar.gz")) -// assert.NoError(t, err) -// _, err = w.Write(repoFile) -// assert.NoError(t, err) -// } -// // clean-test-proj should not include any vulnerabilities so assertion is not needed. -// if r.RequestURI == fmt.Sprintf("/api/v4/projects/jfrog%s/merge_requests/1/notes", "%2Fclean-test-proj") { -// w.WriteHeader(http.StatusOK) -// _, err := w.Write([]byte("{}")) -// assert.NoError(t, err) -// return -// } -// -// // Return 200 when using the REST that creates the comment -// if r.RequestURI == fmt.Sprintf("/api/v4/projects/jfrog%s/merge_requests/1/notes", "%2F"+projectName) { -// buf := new(bytes.Buffer) -// _, err := buf.ReadFrom(r.Body) -// assert.NoError(t, err) -// assert.NotEmpty(t, buf.String()) -// -// var expectedResponse []byte -// switch { -// case strings.Contains(projectName, "multi-dir"): -// expectedResponse, err = os.ReadFile(filepath.Join("..", "expectedResponseMultiDir.json")) -// case strings.Contains(projectName, "pip"): -// expectedResponse, err = os.ReadFile(filepath.Join("..", "expectedResponsePip.json")) -// default: -// expectedResponse, err = os.ReadFile(filepath.Join("..", "expectedResponse.json")) -// } -// assert.NoError(t, err) -// assert.JSONEq(t, string(expectedResponse), buf.String()) -// -// w.WriteHeader(http.StatusOK) -// _, err = w.Write([]byte("{}")) -// assert.NoError(t, err) -// } -// } -// } -// -// // Check connection details with JFrog instance. -// // Return a callback method that restores the credentials after the test is done. +import ( + "bytes" + "context" + "errors" + "github.com/jfrog/froggit-go/vcsclient" + "github.com/jfrog/froggit-go/vcsutils" + audit "github.com/jfrog/jfrog-cli-core/v2/xray/commands/audit/generic" + "github.com/jfrog/jfrog-cli-core/v2/xray/formats" + utils2 "github.com/jfrog/jfrog-cli-core/v2/xray/utils" + "github.com/jfrog/jfrog-client-go/utils/io/fileutils" + "github.com/jfrog/jfrog-client-go/utils/log" + "github.com/jfrog/jfrog-client-go/xray/services" + clitool "github.com/urfave/cli/v2" + "io" + "net/http" + "net/http/httptest" + "path/filepath" +) + +const ( + testMultiDirProjConfigPath = "testdata/config/frogbot-config-multi-dir-test-proj.yml" + testMultiDirProjConfigPathNoFail = "testdata/config/frogbot-config-multi-dir-test-proj-no-fail.yml" + testProjSubdirConfigPath = "testdata/config/frogbot-config-test-proj-subdir.yml" + testCleanProjConfigPath = "testdata/config/frogbot-config-clean-test-proj.yml" + testProjConfigPath = "testdata/config/frogbot-config-test-proj.yml" + testProjConfigPathNoFail = "testdata/config/frogbot-config-test-proj-no-fail.yml" +) + +func TestCreateVulnerabilitiesRows(t *testing.T) { + // Previous scan with only one violation - XRAY-1 + previousScan := services.ScanResponse{ + Violations: []services.Violation{{ + IssueId: "XRAY-1", + Summary: "summary-1", + Severity: "high", + Cves: []services.Cve{}, + ViolationType: "security", + Components: map[string]services.Component{"component-A": {}, "component-B": {}}, + }}, + } + + // Current scan with 2 violations - XRAY-1 and XRAY-2 + currentScan := services.ScanResponse{ + Violations: []services.Violation{ + { + IssueId: "XRAY-1", + Summary: "summary-1", + Severity: "high", + ViolationType: "security", + Components: map[string]services.Component{"component-A": {}, "component-B": {}}, + }, + { + IssueId: "XRAY-2", + Summary: "summary-2", + ViolationType: "security", + Severity: "low", + Components: map[string]services.Component{"component-C": {}, "component-D": {}}, + }, + }, + } + + // Run createNewIssuesRows and make sure that only the XRAY-2 violation exists in the results + rows, err := createNewIssuesRows( + &audit.Results{ExtendedScanResults: &utils2.ExtendedScanResults{XrayResults: []services.ScanResponse{previousScan}}}, + &audit.Results{ExtendedScanResults: &utils2.ExtendedScanResults{XrayResults: []services.ScanResponse{currentScan}}}, + ) + assert.NoError(t, err) + assert.Len(t, rows, 2) + assert.Equal(t, "XRAY-2", rows[0].IssueId) + assert.Equal(t, "low", rows[0].Severity) + assert.Equal(t, "XRAY-2", rows[1].IssueId) + assert.Equal(t, "low", rows[1].Severity) + + impactedPackageOne := rows[0].ImpactedDependencyName + impactedPackageTwo := rows[1].ImpactedDependencyName + assert.ElementsMatch(t, []string{"component-C", "component-D"}, []string{impactedPackageOne, impactedPackageTwo}) +} + +func TestCreateVulnerabilitiesRowsCaseNoPrevViolations(t *testing.T) { + // Previous scan with no violation + previousScan := services.ScanResponse{ + Violations: []services.Violation{}, + } + + // Current scan with 2 violations - XRAY-1 and XRAY-2 + currentScan := services.ScanResponse{ + Violations: []services.Violation{ + { + IssueId: "XRAY-1", + Summary: "summary-1", + Severity: "high", + ViolationType: "security", + Components: map[string]services.Component{"component-A": {}}, + }, + { + IssueId: "XRAY-2", + Summary: "summary-2", + ViolationType: "security", + Severity: "low", + Components: map[string]services.Component{"component-C": {}}, + }, + }, + } + + expected := []formats.VulnerabilityOrViolationRow{ + { + IssueId: "XRAY-1", + Severity: "high", + ImpactedDependencyName: "component-A", + }, + { + IssueId: "XRAY-2", + Severity: "low", + ImpactedDependencyName: "component-C", + }, + } + + // Run createNewIssuesRows and expect both XRAY-1 and XRAY-2 violation in the results + rows, err := createNewIssuesRows( + &audit.Results{ExtendedScanResults: &utils2.ExtendedScanResults{XrayResults: []services.ScanResponse{previousScan}}}, + &audit.Results{ExtendedScanResults: &utils2.ExtendedScanResults{XrayResults: []services.ScanResponse{currentScan}}}, + ) + assert.NoError(t, err) + assert.Len(t, rows, 2) + assert.ElementsMatch(t, expected, rows) +} + +func TestGetNewViolationsCaseNoNewViolations(t *testing.T) { + // Previous scan with 2 violations - XRAY-1 and XRAY-2 + previousScan := services.ScanResponse{ + Violations: []services.Violation{ + { + IssueId: "XRAY-1", + Severity: "high", + ViolationType: "security", + Components: map[string]services.Component{"component-A": {}}, + }, + { + IssueId: "XRAY-2", + Summary: "summary-2", + ViolationType: "security", + Severity: "low", + Components: map[string]services.Component{"component-C": {}}, + }, + }, + } + + // Current scan with no violation + currentScan := services.ScanResponse{ + Violations: []services.Violation{}, + } + + // Run createNewIssuesRows and expect no violations in the results + rows, err := createNewIssuesRows( + &audit.Results{ExtendedScanResults: &utils2.ExtendedScanResults{XrayResults: []services.ScanResponse{previousScan}}}, + &audit.Results{ExtendedScanResults: &utils2.ExtendedScanResults{XrayResults: []services.ScanResponse{currentScan}}}, + ) + assert.NoError(t, err) + assert.Len(t, rows, 0) +} + +func TestGetAllVulnerabilities(t *testing.T) { + // Current scan with 2 vulnerabilities - XRAY-1 and XRAY-2 + currentScan := services.ScanResponse{ + Vulnerabilities: []services.Vulnerability{ + { + IssueId: "XRAY-1", + Summary: "summary-1", + Severity: "high", + Components: map[string]services.Component{"component-A": {}, "component-B": {}}, + }, + { + IssueId: "XRAY-2", + Summary: "summary-2", + Severity: "low", + Components: map[string]services.Component{"component-C": {}, "component-D": {}}, + }, + }, + } + + expected := []formats.VulnerabilityOrViolationRow{ + { + Summary: "summary-1", + IssueId: "XRAY-1", + Severity: "high", + ImpactedDependencyName: "component-A", + }, + { + Summary: "summary-1", + IssueId: "XRAY-1", + Severity: "high", + ImpactedDependencyName: "component-B", + }, + { + Summary: "summary-2", + IssueId: "XRAY-2", + Severity: "low", + ImpactedDependencyName: "component-C", + }, + { + Summary: "summary-2", + IssueId: "XRAY-2", + Severity: "low", + ImpactedDependencyName: "component-D", + }, + } + + // Run createAllIssuesRows and make sure that XRAY-1 and XRAY-2 vulnerabilities exists in the results + rows, err := getScanVulnerabilitiesRows(&audit.Results{ExtendedScanResults: &utils2.ExtendedScanResults{XrayResults: []services.ScanResponse{currentScan}}}) + assert.NoError(t, err) + assert.Len(t, rows, 4) + assert.ElementsMatch(t, expected, rows) +} + +func TestGetNewVulnerabilities(t *testing.T) { + // Previous scan with only one vulnerability - XRAY-1 + previousScan := services.ScanResponse{ + Vulnerabilities: []services.Vulnerability{{ + IssueId: "XRAY-1", + Summary: "summary-1", + Severity: "high", + Components: map[string]services.Component{"component-A": {}, "component-B": {}}, + }}, + } + + // Current scan with 2 vulnerabilities - XRAY-1 and XRAY-2 + currentScan := services.ScanResponse{ + Vulnerabilities: []services.Vulnerability{ + { + IssueId: "XRAY-1", + Summary: "summary-1", + Severity: "high", + Components: map[string]services.Component{"component-A": {}, "component-B": {}}, + }, + { + IssueId: "XRAY-2", + Summary: "summary-2", + Severity: "low", + Components: map[string]services.Component{"component-C": {}, "component-D": {}}, + }, + }, + } + + expected := []formats.VulnerabilityOrViolationRow{ + { + Summary: "summary-2", + Applicable: "Undetermined", + IssueId: "XRAY-2", + Severity: "low", + ImpactedDependencyName: "component-C", + }, + { + Summary: "summary-2", + Applicable: "Undetermined", + IssueId: "XRAY-2", + Severity: "low", + ImpactedDependencyName: "component-D", + }, + } + + // Run createNewIssuesRows and make sure that only the XRAY-2 vulnerability exists in the results + rows, err := createNewIssuesRows( + &audit.Results{ExtendedScanResults: &utils2.ExtendedScanResults{XrayResults: []services.ScanResponse{previousScan}, EntitledForJas: true}}, + &audit.Results{ExtendedScanResults: &utils2.ExtendedScanResults{XrayResults: []services.ScanResponse{currentScan}, EntitledForJas: true}}, + ) + assert.NoError(t, err) + assert.Len(t, rows, 2) + assert.ElementsMatch(t, expected, rows) +} + +func TestGetNewVulnerabilitiesCaseNoPrevVulnerabilities(t *testing.T) { + // Previous scan with no vulnerabilities + previousScan := services.ScanResponse{ + Vulnerabilities: []services.Vulnerability{}, + } + + // Current scan with 2 vulnerabilities - XRAY-1 and XRAY-2 + currentScan := services.ScanResponse{ + Vulnerabilities: []services.Vulnerability{ + { + IssueId: "XRAY-1", + Summary: "summary-1", + Severity: "high", + ExtendedInformation: &services.ExtendedInformation{FullDescription: "description-1"}, + Components: map[string]services.Component{"component-A": {}}, + }, + { + IssueId: "XRAY-2", + Summary: "summary-2", + Severity: "low", + ExtendedInformation: &services.ExtendedInformation{FullDescription: "description-2"}, + Components: map[string]services.Component{"component-B": {}}, + }, + }, + } + + expected := []formats.VulnerabilityOrViolationRow{ + { + Summary: "summary-2", + IssueId: "XRAY-2", + Severity: "low", + ImpactedDependencyName: "component-B", + JfrogResearchInformation: &formats.JfrogResearchInformation{Details: "description-2"}, + }, + { + Summary: "summary-1", + IssueId: "XRAY-1", + Severity: "high", + ImpactedDependencyName: "component-A", + JfrogResearchInformation: &formats.JfrogResearchInformation{Details: "description-1"}, + }, + } + + // Run createNewIssuesRows and expect both XRAY-1 and XRAY-2 vulnerability in the results + rows, err := createNewIssuesRows( + &audit.Results{ExtendedScanResults: &utils2.ExtendedScanResults{XrayResults: []services.ScanResponse{previousScan}}}, + &audit.Results{ExtendedScanResults: &utils2.ExtendedScanResults{XrayResults: []services.ScanResponse{currentScan}}}, + ) + assert.NoError(t, err) + assert.Len(t, rows, 2) + assert.ElementsMatch(t, expected, rows) +} + +func TestGetNewVulnerabilitiesCaseNoNewVulnerabilities(t *testing.T) { + // Previous scan with 2 vulnerabilities - XRAY-1 and XRAY-2 + previousScan := services.ScanResponse{ + Vulnerabilities: []services.Vulnerability{ + { + IssueId: "XRAY-1", + Summary: "summary-1", + Severity: "high", + Components: map[string]services.Component{"component-A": {}}, + }, + { + IssueId: "XRAY-2", + Summary: "summary-2", + Severity: "low", + Components: map[string]services.Component{"component-B": {}}, + }, + }, + } + + // Current scan with no vulnerabilities + currentScan := services.ScanResponse{ + Vulnerabilities: []services.Vulnerability{}, + } + + // Run createNewIssuesRows and expect no vulnerability in the results + rows, err := createNewIssuesRows( + &audit.Results{ExtendedScanResults: &utils2.ExtendedScanResults{XrayResults: []services.ScanResponse{previousScan}}}, + &audit.Results{ExtendedScanResults: &utils2.ExtendedScanResults{XrayResults: []services.ScanResponse{currentScan}}}, + ) + assert.NoError(t, err) + assert.Len(t, rows, 0) +} + +func TestCreatePullRequestMessageNoVulnerabilities(t *testing.T) { + vulnerabilities := []formats.VulnerabilityOrViolationRow{} + message := createPullRequestMessage(vulnerabilities, nil, &utils.StandardOutput{}) + + expectedMessageByte, err := os.ReadFile(filepath.Join("testdata", "messages", "novulnerabilities.md")) + assert.NoError(t, err) + expectedMessage := strings.ReplaceAll(string(expectedMessageByte), "\r\n", "\n") + assert.Equal(t, expectedMessage, message) + + outputWriter := &utils.StandardOutput{} + outputWriter.SetVcsProvider(vcsutils.GitLab) + message = createPullRequestMessage(vulnerabilities, nil, outputWriter) + + expectedMessageByte, err = os.ReadFile(filepath.Join("testdata", "messages", "novulnerabilitiesMR.md")) + assert.NoError(t, err) + expectedMessage = strings.ReplaceAll(string(expectedMessageByte), "\r\n", "\n") + assert.Equal(t, expectedMessage, message) +} + +func TestCreatePullRequestMessage(t *testing.T) { + vulnerabilities := []formats.VulnerabilityOrViolationRow{ + { + Severity: "High", + Applicable: "Undetermined", + ImpactedDependencyName: "github.com/nats-io/nats-streaming-server", + ImpactedDependencyVersion: "v0.21.0", + FixedVersions: []string{"[0.24.1]"}, + Components: []formats.ComponentRow{ + { + Name: "github.com/nats-io/nats-streaming-server", + Version: "v0.21.0", + }, + }, + Cves: []formats.CveRow{{Id: "CVE-2022-24450"}}, + }, + { + Severity: "High", + Applicable: "Undetermined", + ImpactedDependencyName: "github.com/mholt/archiver/v3", + ImpactedDependencyVersion: "v3.5.1", + Components: []formats.ComponentRow{ + { + Name: "github.com/mholt/archiver/v3", + Version: "v3.5.1", + }, + }, + Cves: []formats.CveRow{}, + }, + { + Severity: "Medium", + Applicable: "Undetermined", + ImpactedDependencyName: "github.com/nats-io/nats-streaming-server", + ImpactedDependencyVersion: "v0.21.0", + FixedVersions: []string{"[0.24.3]"}, + Components: []formats.ComponentRow{ + { + Name: "github.com/nats-io/nats-streaming-server", + Version: "v0.21.0", + }, + }, + Cves: []formats.CveRow{{Id: "CVE-2022-26652"}}, + }, + } + iac := []formats.IacSecretsRow{ + { + Severity: "Low", + File: "test.js", + LineColumn: "1:20", + Text: "kms_key_id='' was detected", + Type: "aws_cloudtrail_encrypt", + }, + { + Severity: "High", + File: "test2.js", + LineColumn: "4:30", + Text: "Deprecated TLS version was detected", + Type: "aws_cloudfront_tls_version", + }, + } + writerOutput := &utils.StandardOutput{} + writerOutput.SetEntitledForJas(true) + message := createPullRequestMessage(vulnerabilities, iac, writerOutput) + + expectedMessage := "[![](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/vulnerabilitiesBannerPR.png)](https://github.com/jfrog/frogbot#readme)\n## 📦 Vulnerable Dependencies \n\n### ✍️ Summary\n\n
\n\n| SEVERITY | CONTEXTUAL ANALYSIS | DIRECT DEPENDENCIES | IMPACTED DEPENDENCY | FIXED VERSIONS |\n| :---------------------: | :----------------------------------: | :----------------------------------: | :-----------------------------------: | :---------------------------------: | \n| ![](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/applicableHighSeverity.png)
High | $\\color{}{\\textsf{Undetermined}}$ |github.com/nats-io/nats-streaming-server:v0.21.0 | github.com/nats-io/nats-streaming-server:v0.21.0 | [0.24.1] |\n| ![](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/applicableHighSeverity.png)
High | $\\color{}{\\textsf{Undetermined}}$ |github.com/mholt/archiver/v3:v3.5.1 | github.com/mholt/archiver/v3:v3.5.1 | |\n| ![](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/applicableMediumSeverity.png)
Medium | $\\color{}{\\textsf{Undetermined}}$ |github.com/nats-io/nats-streaming-server:v0.21.0 | github.com/nats-io/nats-streaming-server:v0.21.0 | [0.24.3] |\n\n
\n\n## 👇 Details\n\n\n
\n github.com/nats-io/nats-streaming-server v0.21.0 \n
\n\n- **Severity:** 🔥 High\n- **Package Name:** github.com/nats-io/nats-streaming-server\n- **Current Version:** v0.21.0\n- **Fixed Version:** [0.24.1]\n- **CVEs:** CVE-2022-24450\n\n**Description:**\n\n\n\n\n
\n\n\n
\n github.com/mholt/archiver/v3 v3.5.1 \n
\n\n- **Severity:** 🔥 High\n- **Package Name:** github.com/mholt/archiver/v3\n- **Current Version:** v3.5.1\n- **Fixed Version:** \n- **CVEs:** \n\n**Description:**\n\n\n\n\n
\n\n\n
\n github.com/nats-io/nats-streaming-server v0.21.0 \n
\n\n- **Severity:** 🎃 Medium\n- **Package Name:** github.com/nats-io/nats-streaming-server\n- **Current Version:** v0.21.0\n- **Fixed Version:** [0.24.3]\n- **CVEs:** CVE-2022-26652\n\n**Description:**\n\n\n\n\n
\n\n\n## 🛠️ Infrastructure as Code \n\n
\n\n\n| SEVERITY | FILE | LINE:COLUMN | FINDING | SCANNER |\n| :---------------------: | :----------------------------------: | :-----------------------------------: | :---------------------------------: | :---------------------------------: | \n| ![](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/applicableLowSeverity.png)
Low | test.js | 1:20 | kms_key_id='' was detected | aws_cloudtrail_encrypt |\n| ![](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/applicableHighSeverity.png)
High | test2.js | 4:30 | Deprecated TLS version was detected | aws_cloudfront_tls_version |\n\n
\n\n\n---\n\n
\n\n[This comment was generated by JFrog Frogbot 🐸](https://github.com/jfrog/frogbot#readme)\n\n
\n" + assert.Equal(t, expectedMessage, message) + + writerOutput.SetVcsProvider(vcsutils.GitLab) + message = createPullRequestMessage(vulnerabilities, iac, writerOutput) + expectedMessage = "[![](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/vulnerabilitiesBannerMR.png)](https://github.com/jfrog/frogbot#readme)\n## 📦 Vulnerable Dependencies \n\n### ✍️ Summary\n\n
\n\n| SEVERITY | CONTEXTUAL ANALYSIS | DIRECT DEPENDENCIES | IMPACTED DEPENDENCY | FIXED VERSIONS |\n| :---------------------: | :----------------------------------: | :----------------------------------: | :-----------------------------------: | :---------------------------------: | \n| ![](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/applicableHighSeverity.png)
High | $\\color{}{\\textsf{Undetermined}}$ |github.com/nats-io/nats-streaming-server:v0.21.0 | github.com/nats-io/nats-streaming-server:v0.21.0 | [0.24.1] |\n| ![](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/applicableHighSeverity.png)
High | $\\color{}{\\textsf{Undetermined}}$ |github.com/mholt/archiver/v3:v3.5.1 | github.com/mholt/archiver/v3:v3.5.1 | |\n| ![](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/applicableMediumSeverity.png)
Medium | $\\color{}{\\textsf{Undetermined}}$ |github.com/nats-io/nats-streaming-server:v0.21.0 | github.com/nats-io/nats-streaming-server:v0.21.0 | [0.24.3] |\n\n
\n\n## 👇 Details\n\n\n
\n github.com/nats-io/nats-streaming-server v0.21.0 \n
\n\n- **Severity:** 🔥 High\n- **Package Name:** github.com/nats-io/nats-streaming-server\n- **Current Version:** v0.21.0\n- **Fixed Version:** [0.24.1]\n- **CVEs:** CVE-2022-24450\n\n**Description:**\n\n\n\n\n
\n\n\n
\n github.com/mholt/archiver/v3 v3.5.1 \n
\n\n- **Severity:** 🔥 High\n- **Package Name:** github.com/mholt/archiver/v3\n- **Current Version:** v3.5.1\n- **Fixed Version:** \n- **CVEs:** \n\n**Description:**\n\n\n\n\n
\n\n\n
\n github.com/nats-io/nats-streaming-server v0.21.0 \n
\n\n- **Severity:** 🎃 Medium\n- **Package Name:** github.com/nats-io/nats-streaming-server\n- **Current Version:** v0.21.0\n- **Fixed Version:** [0.24.3]\n- **CVEs:** CVE-2022-26652\n\n**Description:**\n\n\n\n\n
\n\n\n## 🛠️ Infrastructure as Code \n\n
\n\n\n| SEVERITY | FILE | LINE:COLUMN | FINDING | SCANNER |\n| :---------------------: | :----------------------------------: | :-----------------------------------: | :---------------------------------: | :---------------------------------: | \n| ![](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/applicableLowSeverity.png)
Low | test.js | 1:20 | kms_key_id='' was detected | aws_cloudtrail_encrypt |\n| ![](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/applicableHighSeverity.png)
High | test2.js | 4:30 | Deprecated TLS version was detected | aws_cloudfront_tls_version |\n\n
\n\n\n---\n\n
\n\n[This comment was generated by JFrog Frogbot 🐸](https://github.com/jfrog/frogbot#readme)\n\n
\n" + assert.Equal(t, expectedMessage, message) + +} + +func TestRunInstallIfNeeded(t *testing.T) { + scanSetup := utils.ScanDetails{ + Project: &utils.Project{}, + } + scanSetup.SetFailOnInstallationErrors(true) + assert.NoError(t, runInstallIfNeeded(&scanSetup, "")) + tmpDir, err := fileutils.CreateTempDir() + assert.NoError(t, err) + params := &utils.Project{ + InstallCommandName: "echo", + InstallCommandArgs: []string{"Hello"}, + } + scanSetup.Project = params + assert.NoError(t, runInstallIfNeeded(&scanSetup, tmpDir)) + + scanSetup.InstallCommandName = "not-exist" + scanSetup.InstallCommandArgs = []string{"1", "2"} + scanSetup.SetFailOnInstallationErrors(false) + assert.NoError(t, runInstallIfNeeded(&scanSetup, tmpDir)) + + params = &utils.Project{ + InstallCommandName: "not-existed", + InstallCommandArgs: []string{"1", "2"}, + } + scanSetup.Project = params + scanSetup.SetFailOnInstallationErrors(true) + assert.Error(t, runInstallIfNeeded(&scanSetup, tmpDir)) +} + +func TestScanPullRequest(t *testing.T) { + testScanPullRequest(t, testProjConfigPath, "test-proj", true) +} + +func TestScanPullRequestNoFail(t *testing.T) { + testScanPullRequest(t, testProjConfigPathNoFail, "test-proj", false) +} + +func TestScanPullRequestSubdir(t *testing.T) { + testScanPullRequest(t, testProjSubdirConfigPath, "test-proj-subdir", true) +} + +func TestScanPullRequestNoIssues(t *testing.T) { + testScanPullRequest(t, testCleanProjConfigPath, "clean-test-proj", false) +} + +func TestScanPullRequestMultiWorkDir(t *testing.T) { + testScanPullRequest(t, testMultiDirProjConfigPath, "multi-dir-test-proj", true) +} + +func TestScanPullRequestMultiWorkDirNoFail(t *testing.T) { + testScanPullRequest(t, testMultiDirProjConfigPathNoFail, "multi-dir-test-proj", false) +} + +func testScanPullRequest(t *testing.T, configPath, projectName string, failOnSecurityIssues bool) { + params, restoreEnv := verifyEnv(t) + defer restoreEnv() + + // Create mock GitLab server + server := httptest.NewServer(createGitLabHandler(t, projectName)) + defer server.Close() + + configAggregator, client := prepareConfigAndClient(t, configPath, server, params) + _, cleanUp := utils.PrepareTestEnvironment(t, projectName, "scanpullrequest") + defer cleanUp() + + // Run "frogbot scan pull request" + var scanPullRequest ScanPullRequestCmd + err := scanPullRequest.Run(configAggregator, client) + if failOnSecurityIssues { + assert.EqualErrorf(t, err, securityIssueFoundErr, "Error should be: %v, got: %v", securityIssueFoundErr, err) + } else { + assert.NoError(t, err) + } + + // Check env sanitize + err = utils.SanitizeEnv() + assert.NoError(t, err) + utils.AssertSanitizedEnv(t) +} + +func TestVerifyGitHubFrogbotEnvironment(t *testing.T) { + // Init mock + client := mockVcsClient(t) + environment := "frogbot" + client.EXPECT().GetRepositoryInfo(context.Background(), gitParams.RepoOwner, gitParams.RepoName).Return(vcsclient.RepositoryInfo{}, nil) + client.EXPECT().GetRepositoryEnvironmentInfo(context.Background(), gitParams.RepoOwner, gitParams.RepoName, environment).Return(vcsclient.RepositoryEnvironmentInfo{Reviewers: []string{"froggy"}}, nil) + assert.NoError(t, os.Setenv(utils.GitHubActionsEnv, "true")) + + // Run verifyGitHubFrogbotEnvironment + err := verifyGitHubFrogbotEnvironment(client, gitParams) + assert.NoError(t, err) +} + +func TestVerifyGitHubFrogbotEnvironmentNoEnv(t *testing.T) { + // Redirect log to avoid negative output + previousLogger := redirectLogOutputToNil() + defer log.SetLogger(previousLogger) + + // Init mock + client := mockVcsClient(t) + environment := "frogbot" + client.EXPECT().GetRepositoryInfo(context.Background(), gitParams.RepoOwner, gitParams.RepoName).Return(vcsclient.RepositoryInfo{}, nil) + client.EXPECT().GetRepositoryEnvironmentInfo(context.Background(), gitParams.RepoOwner, gitParams.RepoName, environment).Return(vcsclient.RepositoryEnvironmentInfo{}, errors.New("404")) + assert.NoError(t, os.Setenv(utils.GitHubActionsEnv, "true")) + + // Run verifyGitHubFrogbotEnvironment + err := verifyGitHubFrogbotEnvironment(client, gitParams) + assert.ErrorContains(t, err, noGitHubEnvErr) +} + +func TestVerifyGitHubFrogbotEnvironmentNoReviewers(t *testing.T) { + // Init mock + client := mockVcsClient(t) + environment := "frogbot" + client.EXPECT().GetRepositoryInfo(context.Background(), gitParams.RepoOwner, gitParams.RepoName).Return(vcsclient.RepositoryInfo{}, nil) + client.EXPECT().GetRepositoryEnvironmentInfo(context.Background(), gitParams.RepoOwner, gitParams.RepoName, environment).Return(vcsclient.RepositoryEnvironmentInfo{}, nil) + assert.NoError(t, os.Setenv(utils.GitHubActionsEnv, "true")) + + // Run verifyGitHubFrogbotEnvironment + err := verifyGitHubFrogbotEnvironment(client, gitParams) + assert.ErrorContains(t, err, noGitHubEnvReviewersErr) +} + +func TestVerifyGitHubFrogbotEnvironmentOnPrem(t *testing.T) { + repoConfig := &utils.Repository{ + Params: utils.Params{Git: utils.Git{ClientInfo: utils.ClientInfo{ + VcsInfo: vcsclient.VcsInfo{APIEndpoint: "https://acme.vcs.io"}}}, + }, + } + + // Run verifyGitHubFrogbotEnvironment + err := verifyGitHubFrogbotEnvironment(&vcsclient.GitHubClient{}, repoConfig) + assert.NoError(t, err) +} + +func prepareConfigAndClient(t *testing.T, configPath string, server *httptest.Server, serverParams coreconfig.ServerDetails) (utils.RepoAggregator, vcsclient.VcsClient) { + git := &utils.ClientInfo{ + GitProvider: vcsutils.GitHub, + RepoOwner: "jfrog", + VcsInfo: vcsclient.VcsInfo{ + Token: "123456", + APIEndpoint: server.URL, + }, + } + utils.SetEnvAndAssert(t, map[string]string{utils.GitPullRequestIDEnv: "1"}) + + configData, err := utils.ReadConfigFromFileSystem(configPath) + assert.NoError(t, err) + configAggregator, err := utils.BuildRepoAggregator(configData, git, &serverParams) + assert.NoError(t, err) + + client, err := vcsclient.NewClientBuilder(vcsutils.GitLab).ApiEndpoint(server.URL).Token("123456").Build() + assert.NoError(t, err) + return configAggregator, client +} + +func TestScanPullRequestError(t *testing.T) { + app := clitool.App{Commands: GetCommands()} + assert.Error(t, app.Run([]string{"frogbot", "spr"})) +} + +// Create HTTP handler to mock GitLab server + +func createGitLabHandler(t *testing.T, projectName string) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + // Return 200 on ping + if r.RequestURI == "/api/v4/" { + w.WriteHeader(http.StatusOK) + return + } + + // Return test-proj.tar.gz when using DownloadRepository + if r.RequestURI == fmt.Sprintf("/api/v4/projects/jfrog%s/repository/archive.tar.gz?sha=master", "%2F"+projectName) { + w.WriteHeader(http.StatusOK) + repoFile, err := os.ReadFile(filepath.Join("..", projectName+".tar.gz")) + assert.NoError(t, err) + _, err = w.Write(repoFile) + assert.NoError(t, err) + } + // clean-test-proj should not include any vulnerabilities so assertion is not needed. + if r.RequestURI == fmt.Sprintf("/api/v4/projects/jfrog%s/merge_requests/1/notes", "%2Fclean-test-proj") { + w.WriteHeader(http.StatusOK) + _, err := w.Write([]byte("{}")) + assert.NoError(t, err) + return + } + + // Return 200 when using the REST that creates the comment + if r.RequestURI == fmt.Sprintf("/api/v4/projects/jfrog%s/merge_requests/1/notes", "%2F"+projectName) { + buf := new(bytes.Buffer) + _, err := buf.ReadFrom(r.Body) + assert.NoError(t, err) + assert.NotEmpty(t, buf.String()) + + var expectedResponse []byte + switch { + case strings.Contains(projectName, "multi-dir"): + expectedResponse, err = os.ReadFile(filepath.Join("..", "expectedResponseMultiDir.json")) + case strings.Contains(projectName, "pip"): + expectedResponse, err = os.ReadFile(filepath.Join("..", "expectedResponsePip.json")) + default: + expectedResponse, err = os.ReadFile(filepath.Join("..", "expectedResponse.json")) + } + assert.NoError(t, err) + assert.JSONEq(t, string(expectedResponse), buf.String()) + + w.WriteHeader(http.StatusOK) + _, err = w.Write([]byte("{}")) + assert.NoError(t, err) + } + } +} + +// Check connection details with JFrog instance. +// Return a callback method that restores the credentials after the test is done. func verifyEnv(t *testing.T) (server coreconfig.ServerDetails, restoreFunc func()) { url := strings.TrimSuffix(os.Getenv(utils.JFrogUrlEnv), "/") username := os.Getenv(utils.JFrogUserEnv) @@ -715,111 +702,110 @@ func verifyEnv(t *testing.T) (server coreconfig.ServerDetails, restoreFunc func( return } -// -//func TestGetFullPathWorkingDirs(t *testing.T) { -// sampleProject := utils.Project{ -// WorkingDirs: []string{filepath.Join("a", "b"), filepath.Join("a", "b", "c"), ".", filepath.Join("c", "d", "e", "f")}, -// } -// baseWd := "tempDir" -// fullPathWds := getFullPathWorkingDirs(sampleProject.WorkingDirs, baseWd) -// expectedWds := []string{filepath.Join("tempDir", "a", "b"), filepath.Join("tempDir", "a", "b", "c"), "tempDir", filepath.Join("tempDir", "c", "d", "e", "f")} -// for _, expectedWd := range expectedWds { -// assert.Contains(t, fullPathWds, expectedWd) -// } -//} -// -//func TestCreateNewIacRows(t *testing.T) { -// testCases := []struct { -// name string -// targetIacResults []utils2.IacOrSecretResult -// sourceIacResults []utils2.IacOrSecretResult -// expectedAddedIacVulnerabilities []formats.IacSecretsRow -// }{ -// { -// name: "No vulnerabilities in source IaC results", -// targetIacResults: []utils2.IacOrSecretResult{ -// { -// Severity: "High", -// File: "file1", -// LineColumn: "1:10", -// Type: "Secret", -// Text: "Sensitive information", -// }, -// }, -// sourceIacResults: []utils2.IacOrSecretResult{}, -// expectedAddedIacVulnerabilities: []formats.IacSecretsRow{}, -// }, -// { -// name: "No vulnerabilities in target IaC results", -// targetIacResults: []utils2.IacOrSecretResult{}, -// sourceIacResults: []utils2.IacOrSecretResult{ -// { -// Severity: "High", -// File: "file1", -// LineColumn: "1:10", -// Type: "Secret", -// Text: "Sensitive information", -// }, -// }, -// expectedAddedIacVulnerabilities: []formats.IacSecretsRow{ -// { -// Severity: "High", -// File: "file1", -// LineColumn: "1:10", -// Type: "Secret", -// Text: "Sensitive information", -// SeverityNumValue: 9, -// }, -// }, -// }, -// { -// name: "Some new vulnerabilities in source IaC results", -// targetIacResults: []utils2.IacOrSecretResult{ -// { -// Severity: "High", -// File: "file1", -// LineColumn: "1:10", -// Type: "Secret", -// Text: "Sensitive information", -// }, -// }, -// sourceIacResults: []utils2.IacOrSecretResult{ -// { -// Severity: "Medium", -// File: "file2", -// LineColumn: "2:5", -// Type: "Secret", -// Text: "Confidential data", -// }, -// }, -// expectedAddedIacVulnerabilities: []formats.IacSecretsRow{ -// { -// Severity: "Medium", -// SeverityNumValue: 6, -// File: "file2", -// LineColumn: "2:5", -// Text: "Confidential data", -// Type: "Secret", -// }, -// }, -// }, -// } -// -// for _, tc := range testCases { -// t.Run(tc.name, func(t *testing.T) { -// addedIacVulnerabilities := createNewIacRows(tc.targetIacResults, tc.sourceIacResults) -// assert.ElementsMatch(t, tc.expectedAddedIacVulnerabilities, addedIacVulnerabilities) -// }) -// } -//} -// -//// Set new logger with output redirection to a null logger. This is useful for negative tests. -//// Caller is responsible to set the old log back. -//func redirectLogOutputToNil() (previousLog log.Log) { -// previousLog = log.Logger -// newLog := log.NewLogger(log.ERROR, nil) -// newLog.SetOutputWriter(io.Discard) -// newLog.SetLogsWriter(io.Discard, 0) -// log.SetLogger(newLog) -// return previousLog -//} +func TestGetFullPathWorkingDirs(t *testing.T) { + sampleProject := utils.Project{ + WorkingDirs: []string{filepath.Join("a", "b"), filepath.Join("a", "b", "c"), ".", filepath.Join("c", "d", "e", "f")}, + } + baseWd := "tempDir" + fullPathWds := getFullPathWorkingDirs(sampleProject.WorkingDirs, baseWd) + expectedWds := []string{filepath.Join("tempDir", "a", "b"), filepath.Join("tempDir", "a", "b", "c"), "tempDir", filepath.Join("tempDir", "c", "d", "e", "f")} + for _, expectedWd := range expectedWds { + assert.Contains(t, fullPathWds, expectedWd) + } +} + +func TestCreateNewIacRows(t *testing.T) { + testCases := []struct { + name string + targetIacResults []utils2.IacOrSecretResult + sourceIacResults []utils2.IacOrSecretResult + expectedAddedIacVulnerabilities []formats.IacSecretsRow + }{ + { + name: "No vulnerabilities in source IaC results", + targetIacResults: []utils2.IacOrSecretResult{ + { + Severity: "High", + File: "file1", + LineColumn: "1:10", + Type: "Secret", + Text: "Sensitive information", + }, + }, + sourceIacResults: []utils2.IacOrSecretResult{}, + expectedAddedIacVulnerabilities: []formats.IacSecretsRow{}, + }, + { + name: "No vulnerabilities in target IaC results", + targetIacResults: []utils2.IacOrSecretResult{}, + sourceIacResults: []utils2.IacOrSecretResult{ + { + Severity: "High", + File: "file1", + LineColumn: "1:10", + Type: "Secret", + Text: "Sensitive information", + }, + }, + expectedAddedIacVulnerabilities: []formats.IacSecretsRow{ + { + Severity: "High", + File: "file1", + LineColumn: "1:10", + Type: "Secret", + Text: "Sensitive information", + SeverityNumValue: 9, + }, + }, + }, + { + name: "Some new vulnerabilities in source IaC results", + targetIacResults: []utils2.IacOrSecretResult{ + { + Severity: "High", + File: "file1", + LineColumn: "1:10", + Type: "Secret", + Text: "Sensitive information", + }, + }, + sourceIacResults: []utils2.IacOrSecretResult{ + { + Severity: "Medium", + File: "file2", + LineColumn: "2:5", + Type: "Secret", + Text: "Confidential data", + }, + }, + expectedAddedIacVulnerabilities: []formats.IacSecretsRow{ + { + Severity: "Medium", + SeverityNumValue: 6, + File: "file2", + LineColumn: "2:5", + Text: "Confidential data", + Type: "Secret", + }, + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + addedIacVulnerabilities := createNewIacRows(tc.targetIacResults, tc.sourceIacResults) + assert.ElementsMatch(t, tc.expectedAddedIacVulnerabilities, addedIacVulnerabilities) + }) + } +} + +// Set new logger with output redirection to a null logger. This is useful for negative tests. +// Caller is responsible to set the old log back. +func redirectLogOutputToNil() (previousLog log.Log) { + previousLog = log.Logger + newLog := log.NewLogger(log.ERROR, nil) + newLog.SetOutputWriter(io.Discard) + newLog.SetLogsWriter(io.Discard, 0) + log.SetLogger(newLog) + return previousLog +} diff --git a/commands/scanpullrequests_test.go b/commands/scanpullrequests_test.go index 7aec5b03a..b8e292a2a 100644 --- a/commands/scanpullrequests_test.go +++ b/commands/scanpullrequests_test.go @@ -3,6 +3,7 @@ package commands import ( "context" "fmt" + "github.com/jfrog/froggit-go/vcsutils" "github.com/stretchr/testify/assert" "path/filepath" "testing" @@ -156,43 +157,42 @@ func TestScanAllPullRequestsMultiRepo(t *testing.T) { assert.Equal(t, expectedMessage, frogbotMessages[3]) } -// -//func TestScanAllPullRequests(t *testing.T) { -// // This integration test, requires JFrog platform connection details -// server, restoreEnv := verifyEnv(t) -// defer restoreEnv() -// falseVal := false -// gitParams.Git.GitProvider = vcsutils.BitbucketServer -// params := utils.Params{ -// Scan: utils.Scan{ -// FailOnSecurityIssues: &falseVal, -// Projects: []utils.Project{{ -// InstallCommandName: "npm", -// InstallCommandArgs: []string{"i"}, -// WorkingDirs: []string{"."}, -// UseWrapper: &utils.TrueVal, -// }}, -// }, -// Git: gitParams.Git, -// } -// repoParams := &utils.Repository{ -// OutputWriter: &utils.SimplifiedOutput{}, -// Server: server, -// Params: params, -// } -// paramsAggregator := utils.RepoAggregator{} -// paramsAggregator = append(paramsAggregator, *repoParams) -// var frogbotMessages []string -// client := getMockClient(t, &frogbotMessages, MockParams{repoParams.RepoName, repoParams.RepoOwner, "test-proj-with-vulnerability", "test-proj"}) -// scanAllPullRequestsCmd := ScanAllPullRequestsCmd{} -// err := scanAllPullRequestsCmd.Run(paramsAggregator, client) -// assert.NoError(t, err) -// assert.Len(t, frogbotMessages, 2) -// expectedMessage := "**🚨 Frogbot scanned this pull request and found the below:**\n\n---\n## 📦 Vulnerable Dependencies\n---\n\n### ✍️ Summary \n\n| SEVERITY | CONTEXTUAL ANALYSIS | DIRECT DEPENDENCIES | IMPACTED DEPENDENCY | FIXED VERSIONS |\n| :---------------------: | :----------------------------------: | :----------------------------------: | :-----------------------------------: | :---------------------------------: | \n| Critical | **NOT APPLICABLE** |minimist:1.2.5 | minimist:1.2.5 | [0.2.4], [1.2.6] |\n\n---\n### 👇 Details\n---\n\n\n#### minimist 1.2.5\n\n\n- **Severity:** 💀 Critical\n- **Contextual Analysis:** **NOT APPLICABLE**\n- **Package Name:** minimist\n- **Current Version:** 1.2.5\n- **Fixed Version:** [0.2.4],[1.2.6]\n- **CVEs:** CVE-2021-44906\n\n**Description:**\n\n[Minimist](https://github.com/substack/minimist) is a simple and very popular argument parser. It is used by more than 14 million by Mar 2022. This package developers stopped developing it since April 2020 and its community released a [newer version](https://github.com/meszaros-lajos-gyorgy/minimist-lite) supported by the community.\n\n\nAn incomplete fix for [CVE-2020-7598](https://nvd.nist.gov/vuln/detail/CVE-2020-7598) partially blocked prototype pollution attacks. Researchers discovered that it does not check for constructor functions which means they can be overridden. This behavior can be triggered easily when using it insecurely (which is the common usage). For example:\n```\nvar argv = parse(['--_.concat.constructor.prototype.y', '123']);\nt.equal((function(){}).foo, undefined);\nt.equal(argv.y, undefined);\n```\nIn this example, `prototype.y` is assigned with `123` which will be derived to every newly created object. \n\nThis vulnerability can be triggered when the attacker-controlled input is parsed using Minimist without any validation. As always with prototype pollution, the impact depends on the code that follows the attack, but denial of service is almost always guaranteed.\n\n**Remediation:**\n\n##### Development mitigations\n\nAdd the `Object.freeze(Object.prototype);` directive once at the beginning of your main JS source code file (ex. `index.js`), preferably after all your `require` directives. This will prevent any changes to the prototype object, thus completely negating prototype pollution attacks.\n\n\n\n\n---\n[This comment was generated by JFrog Frogbot 🐸](https://github.com/jfrog/frogbot#readme)" -// assert.Equal(t, expectedMessage, frogbotMessages[0]) -// expectedMessage = "**👍 Frogbot scanned this pull request and found that it did not add vulnerable dependencies.** \n\n\n---\n[This comment was generated by JFrog Frogbot 🐸](https://github.com/jfrog/frogbot#readme)" -// assert.Equal(t, expectedMessage, frogbotMessages[1]) -//} +func TestScanAllPullRequests(t *testing.T) { + // This integration test, requires JFrog platform connection details + server, restoreEnv := verifyEnv(t) + defer restoreEnv() + falseVal := false + gitParams.Git.GitProvider = vcsutils.BitbucketServer + params := utils.Params{ + Scan: utils.Scan{ + FailOnSecurityIssues: &falseVal, + Projects: []utils.Project{{ + InstallCommandName: "npm", + InstallCommandArgs: []string{"i"}, + WorkingDirs: []string{"."}, + UseWrapper: &utils.TrueVal, + }}, + }, + Git: gitParams.Git, + } + repoParams := &utils.Repository{ + OutputWriter: &utils.SimplifiedOutput{}, + Server: server, + Params: params, + } + paramsAggregator := utils.RepoAggregator{} + paramsAggregator = append(paramsAggregator, *repoParams) + var frogbotMessages []string + client := getMockClient(t, &frogbotMessages, MockParams{repoParams.RepoName, repoParams.RepoOwner, "test-proj-with-vulnerability", "test-proj"}) + scanAllPullRequestsCmd := ScanAllPullRequestsCmd{} + err := scanAllPullRequestsCmd.Run(paramsAggregator, client) + assert.NoError(t, err) + assert.Len(t, frogbotMessages, 2) + expectedMessage := "**🚨 Frogbot scanned this pull request and found the below:**\n\n---\n## 📦 Vulnerable Dependencies\n---\n\n### ✍️ Summary \n\n| SEVERITY | CONTEXTUAL ANALYSIS | DIRECT DEPENDENCIES | IMPACTED DEPENDENCY | FIXED VERSIONS |\n| :---------------------: | :----------------------------------: | :----------------------------------: | :-----------------------------------: | :---------------------------------: | \n| Critical | **NOT APPLICABLE** |minimist:1.2.5 | minimist:1.2.5 | [0.2.4], [1.2.6] |\n\n---\n### 👇 Details\n---\n\n\n#### minimist 1.2.5\n\n\n- **Severity:** 💀 Critical\n- **Contextual Analysis:** **NOT APPLICABLE**\n- **Package Name:** minimist\n- **Current Version:** 1.2.5\n- **Fixed Version:** [0.2.4],[1.2.6]\n- **CVEs:** CVE-2021-44906\n\n**Description:**\n\n[Minimist](https://github.com/substack/minimist) is a simple and very popular argument parser. It is used by more than 14 million by Mar 2022. This package developers stopped developing it since April 2020 and its community released a [newer version](https://github.com/meszaros-lajos-gyorgy/minimist-lite) supported by the community.\n\n\nAn incomplete fix for [CVE-2020-7598](https://nvd.nist.gov/vuln/detail/CVE-2020-7598) partially blocked prototype pollution attacks. Researchers discovered that it does not check for constructor functions which means they can be overridden. This behavior can be triggered easily when using it insecurely (which is the common usage). For example:\n```\nvar argv = parse(['--_.concat.constructor.prototype.y', '123']);\nt.equal((function(){}).foo, undefined);\nt.equal(argv.y, undefined);\n```\nIn this example, `prototype.y` is assigned with `123` which will be derived to every newly created object. \n\nThis vulnerability can be triggered when the attacker-controlled input is parsed using Minimist without any validation. As always with prototype pollution, the impact depends on the code that follows the attack, but denial of service is almost always guaranteed.\n\n**Remediation:**\n\n##### Development mitigations\n\nAdd the `Object.freeze(Object.prototype);` directive once at the beginning of your main JS source code file (ex. `index.js`), preferably after all your `require` directives. This will prevent any changes to the prototype object, thus completely negating prototype pollution attacks.\n\n\n\n\n---\n[This comment was generated by JFrog Frogbot 🐸](https://github.com/jfrog/frogbot#readme)" + assert.Equal(t, expectedMessage, frogbotMessages[0]) + expectedMessage = "**👍 Frogbot scanned this pull request and found that it did not add vulnerable dependencies.** \n\n\n---\n[This comment was generated by JFrog Frogbot 🐸](https://github.com/jfrog/frogbot#readme)" + assert.Equal(t, expectedMessage, frogbotMessages[1]) +} func getMockClient(t *testing.T, frogbotMessages *[]string, mockParams ...MockParams) *testdata.MockVcsClient { // Init mock diff --git a/commands/utils/outputwriter_test.go b/commands/utils/outputwriter_test.go index ed19f4da6..c076bd859 100644 --- a/commands/utils/outputwriter_test.go +++ b/commands/utils/outputwriter_test.go @@ -1,40 +1,39 @@ package utils -// -//import ( -// "github.com/jfrog/froggit-go/vcsutils" -// "github.com/stretchr/testify/assert" -// "testing" -//) -// -//func TestFormattedApplicabilityText(t *testing.T) { -// tests := []struct { -// provider vcsutils.VcsProvider -// text string -// expected string -// }{ -// // Test cases for GitHub as gitProvider -// {vcsutils.GitHub, "applicable", "$\\color{#FF7377}{\\textsf{applicable}}$"}, -// {vcsutils.GitHub, "not applicable", "$\\color{#3CB371}{\\textsf{not applicable}}$"}, -// {vcsutils.GitHub, "undetermined", "$\\color{}{\\textsf{undetermined}}$"}, -// -// // Test cases for GitLab as gitProvider -// {vcsutils.GitLab, "applicable", "$\\color{#FF7377}{\\textsf{applicable}}$"}, -// {vcsutils.GitLab, "not applicable", "$\\color{#3CB371}{\\textsf{not applicable}}$"}, -// {vcsutils.GitLab, "undetermined", "$\\color{}{\\textsf{undetermined}}$"}, -// -// // Test cases for AzureRepos as gitProvider -// {vcsutils.AzureRepos, "applicable", "applicable"}, -// {vcsutils.AzureRepos, "not applicable", "not applicable"}, -// {vcsutils.AzureRepos, "undetermined", "undetermined"}, -// -// {vcsutils.BitbucketServer, "applicable", "**APPLICABLE**"}, -// {vcsutils.BitbucketServer, "not applicable", "**NOT APPLICABLE**"}, -// {vcsutils.BitbucketServer, "undetermined", "**UNDETERMINED**"}, -// } -// -// for _, test := range tests { -// result := formattedApplicabilityText(test.text, test.provider) -// assert.Equal(t, result, test.expected) -// } -//} +import ( + "github.com/jfrog/froggit-go/vcsutils" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestFormattedApplicabilityText(t *testing.T) { + tests := []struct { + provider vcsutils.VcsProvider + text string + expected string + }{ + // Test cases for GitHub as gitProvider + {vcsutils.GitHub, "applicable", "$\\color{#FF7377}{\\textsf{applicable}}$"}, + {vcsutils.GitHub, "not applicable", "$\\color{#3CB371}{\\textsf{not applicable}}$"}, + {vcsutils.GitHub, "undetermined", "$\\color{}{\\textsf{undetermined}}$"}, + + // Test cases for GitLab as gitProvider + {vcsutils.GitLab, "applicable", "$\\color{#FF7377}{\\textsf{applicable}}$"}, + {vcsutils.GitLab, "not applicable", "$\\color{#3CB371}{\\textsf{not applicable}}$"}, + {vcsutils.GitLab, "undetermined", "$\\color{}{\\textsf{undetermined}}$"}, + + // Test cases for AzureRepos as gitProvider + {vcsutils.AzureRepos, "applicable", "applicable"}, + {vcsutils.AzureRepos, "not applicable", "not applicable"}, + {vcsutils.AzureRepos, "undetermined", "undetermined"}, + + {vcsutils.BitbucketServer, "applicable", "**APPLICABLE**"}, + {vcsutils.BitbucketServer, "not applicable", "**NOT APPLICABLE**"}, + {vcsutils.BitbucketServer, "undetermined", "**UNDETERMINED**"}, + } + + for _, test := range tests { + result := formattedApplicabilityText(test.text, test.provider) + assert.Equal(t, result, test.expected) + } +} diff --git a/commands/utils/packagehandlers/packagehandlers_test.go b/commands/utils/packagehandlers/packagehandlers_test.go index 0ff5c034b..06852a1f5 100644 --- a/commands/utils/packagehandlers/packagehandlers_test.go +++ b/commands/utils/packagehandlers/packagehandlers_test.go @@ -1,564 +1,563 @@ package packagehandlers -// -//import ( -// "fmt" -// testdatautils "github.com/jfrog/build-info-go/build/testdata" -// "github.com/jfrog/frogbot/commands/utils" -// "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" -// "github.com/jfrog/jfrog-cli-core/v2/xray/formats" -// "github.com/jfrog/jfrog-client-go/utils/io/fileutils" -// "github.com/stretchr/testify/assert" -// "os" -// "path/filepath" -// "regexp" -// "strconv" -// "strings" -// "testing" -//) -// -//type dependencyFixTest struct { -// vulnDetails *utils.VulnerabilityDetails -// fixSupported bool -//} -// -//type pythonIndirectDependencies struct { -// dependencyFixTest -// requirementsPath string -//} -// -//const requirementsFile = "oslo.config>=1.12.1,<1.13\noslo.utils<5.0,>=4.0.0\nparamiko==2.7.2\npasslib<=1.7.4\nprance>=0.9.0\nprompt-toolkit~=1.0.15\npyinotify>0.9.6\nPyJWT>1.7.1\nurllib3 > 1.1.9, < 1.5.*" -// -//type pipPackageRegexTest struct { -// packageName string -// expectedRequirement string -//} -// -//// Go -//func TestGoPackageHandler_UpdateDependency(t *testing.T) { -// goPackageHandler := GoPackageHandler{} -// testcases := []dependencyFixTest{ -// { -// vulnDetails: &utils.VulnerabilityDetails{ -// FixVersion: "0.0.0-20201216223049-8b5274cf687f", -// IsDirectDependency: false, -// VulnerabilityOrViolationRow: &formats.VulnerabilityOrViolationRow{Technology: coreutils.Go, ImpactedDependencyName: "golang.org/x/crypto"}, -// }, fixSupported: true, -// }, -// { -// vulnDetails: &utils.VulnerabilityDetails{ -// FixVersion: "1.7.7", -// IsDirectDependency: true, -// VulnerabilityOrViolationRow: &formats.VulnerabilityOrViolationRow{Technology: coreutils.Go, ImpactedDependencyName: "github.com/gin-gonic/gin"}, -// }, fixSupported: true, -// }, -// { -// vulnDetails: &utils.VulnerabilityDetails{ -// FixVersion: "1.3.0", -// IsDirectDependency: true, -// VulnerabilityOrViolationRow: &formats.VulnerabilityOrViolationRow{Technology: coreutils.Go, ImpactedDependencyName: "github.com/google/uuid"}, -// }, fixSupported: true, -// }, -// } -// for _, test := range testcases { -// t.Run(test.vulnDetails.ImpactedDependencyName+" direct:"+strconv.FormatBool(test.vulnDetails.IsDirectDependency), func(t *testing.T) { -// testDataDir := getTestDataDir(t, test.vulnDetails.IsDirectDependency) -// cleanup := createTempDirAndChDir(t, testDataDir, coreutils.Go) -// err := goPackageHandler.UpdateDependency(test.vulnDetails) -// if !test.fixSupported { -// assert.Error(t, err, "Expected error to occur") -// assert.IsType(t, &utils.ErrUnsupportedFix{}, err, "Expected unsupported fix error") -// } else { -// assert.NoError(t, err) -// assertFixVersionInPackageDescriptor(t, test, "go.mod") -// } -// assert.NoError(t, os.Chdir(testDataDir)) -// cleanup() -// }) -// } -//} -// -//// Python, includes pip,pipenv, poetry -//func TestPythonPackageHandler_UpdateDependency(t *testing.T) { -// testcases := []pythonIndirectDependencies{ -// {dependencyFixTest: dependencyFixTest{ -// vulnDetails: &utils.VulnerabilityDetails{ -// FixVersion: "1.25.9", -// VulnerabilityOrViolationRow: &formats.VulnerabilityOrViolationRow{Technology: coreutils.Pip, ImpactedDependencyName: "urllib3"}, -// }}, requirementsPath: "requirements.txt"}, -// {dependencyFixTest: dependencyFixTest{ -// vulnDetails: &utils.VulnerabilityDetails{ -// FixVersion: "1.25.9", -// VulnerabilityOrViolationRow: &formats.VulnerabilityOrViolationRow{Technology: coreutils.Poetry, ImpactedDependencyName: "urllib3"}}}, -// requirementsPath: "pyproejct.toml"}, -// {dependencyFixTest: dependencyFixTest{ -// vulnDetails: &utils.VulnerabilityDetails{ -// FixVersion: "1.25.9", -// VulnerabilityOrViolationRow: &formats.VulnerabilityOrViolationRow{Technology: coreutils.Pipenv, ImpactedDependencyName: "urllib3"}}}, -// requirementsPath: "Pipfile"}, -// {dependencyFixTest: dependencyFixTest{ -// vulnDetails: &utils.VulnerabilityDetails{ -// FixVersion: "2.4.0", -// VulnerabilityOrViolationRow: &formats.VulnerabilityOrViolationRow{Technology: coreutils.Pip, ImpactedDependencyName: "pyjwt"}, -// IsDirectDependency: true}, -// fixSupported: true}, -// requirementsPath: "requirements.txt"}, -// {dependencyFixTest: dependencyFixTest{ -// vulnDetails: &utils.VulnerabilityDetails{ -// FixVersion: "2.4.0", -// VulnerabilityOrViolationRow: &formats.VulnerabilityOrViolationRow{Technology: coreutils.Pip, ImpactedDependencyName: "Pyjwt"}, -// IsDirectDependency: true}, -// fixSupported: true}, -// requirementsPath: "requirements.txt"}, -// {dependencyFixTest: dependencyFixTest{ -// vulnDetails: &utils.VulnerabilityDetails{ -// FixVersion: "2.4.0", -// VulnerabilityOrViolationRow: &formats.VulnerabilityOrViolationRow{Technology: coreutils.Pip, ImpactedDependencyName: "pyjwt"}, -// IsDirectDependency: true}, fixSupported: true}, -// requirementsPath: "setup.py"}, -// {dependencyFixTest: dependencyFixTest{ -// vulnDetails: &utils.VulnerabilityDetails{ -// FixVersion: "2.4.0", -// VulnerabilityOrViolationRow: &formats.VulnerabilityOrViolationRow{Technology: coreutils.Poetry, ImpactedDependencyName: "pyjwt"}, -// IsDirectDependency: true}, -// fixSupported: true}, -// requirementsPath: "pyproject.toml"}, -// {dependencyFixTest: dependencyFixTest{ -// vulnDetails: &utils.VulnerabilityDetails{ -// FixVersion: "2.4.0", -// VulnerabilityOrViolationRow: &formats.VulnerabilityOrViolationRow{Technology: coreutils.Poetry, ImpactedDependencyName: "pyjwt"}, -// IsDirectDependency: true}, -// fixSupported: true}, -// requirementsPath: "pyproject.toml"}, -// } -// for _, test := range testcases { -// t.Run(test.vulnDetails.ImpactedDependencyName+" direct:"+strconv.FormatBool(test.vulnDetails.IsDirectDependency), func(t *testing.T) { -// testDataDir := getTestDataDir(t, test.vulnDetails.IsDirectDependency) -// pythonPackageHandler := GetCompatiblePackageHandler(test.vulnDetails, &utils.ScanDetails{ -// Project: &utils.Project{PipRequirementsFile: test.requirementsPath}}) -// cleanup := createTempDirAndChDir(t, testDataDir, test.vulnDetails.Technology) -// err := pythonPackageHandler.UpdateDependency(test.vulnDetails) -// if !test.fixSupported { -// assert.Error(t, err, "Expected error to occur") -// assert.IsType(t, &utils.ErrUnsupportedFix{}, err, "Expected unsupported fix error") -// } else { -// assert.NoError(t, err) -// } -// assert.NoError(t, os.Chdir(testDataDir)) -// cleanup() -// }) -// } -//} -// -//func TestPipPackageRegex(t *testing.T) { -// var pipPackagesRegexTests = []pipPackageRegexTest{ -// {"oslo.config", "oslo.config>=1.12.1,<1.13"}, -// {"oslo.utils", "oslo.utils<5.0,>=4.0.0"}, -// {"paramiko", "paramiko==2.7.2"}, -// {"passlib", "passlib<=1.7.4"}, -// {"PassLib", "passlib<=1.7.4"}, -// {"prance", "prance>=0.9.0"}, -// {"prompt-toolkit", "prompt-toolkit~=1.0.15"}, -// {"pyinotify", "pyinotify>0.9.6"}, -// {"pyjwt", "pyjwt>1.7.1"}, -// {"PyJWT", "pyjwt>1.7.1"}, -// {"urllib3", "urllib3 > 1.1.9, < 1.5.*"}, -// } -// for _, pack := range pipPackagesRegexTests { -// re := regexp.MustCompile(PythonPackageRegexPrefix + "(" + pack.packageName + "|" + strings.ToLower(pack.packageName) + ")" + PythonPackageRegexSuffix) -// found := re.FindString(requirementsFile) -// assert.Equal(t, pack.expectedRequirement, strings.ToLower(found)) -// } -//} -// -//// Npm -//func TestNpmPackageHandler_UpdateDependency(t *testing.T) { -// npmPackageHandler := &NpmPackageHandler{} -// testcases := []dependencyFixTest{ -// { -// vulnDetails: &utils.VulnerabilityDetails{ -// FixVersion: "0.8.4", -// VulnerabilityOrViolationRow: &formats.VulnerabilityOrViolationRow{Technology: coreutils.Npm, ImpactedDependencyName: "mpath"}, -// }, fixSupported: false, -// }, -// { -// vulnDetails: &utils.VulnerabilityDetails{ -// FixVersion: "3.0.2", -// IsDirectDependency: true, -// VulnerabilityOrViolationRow: &formats.VulnerabilityOrViolationRow{Technology: coreutils.Npm, ImpactedDependencyName: "minimatch"}, -// }, fixSupported: true, -// }, -// } -// for _, test := range testcases { -// t.Run(test.vulnDetails.ImpactedDependencyName+" direct:"+strconv.FormatBool(test.vulnDetails.IsDirectDependency), func(t *testing.T) { -// testDataDir := getTestDataDir(t, test.vulnDetails.IsDirectDependency) -// cleanup := createTempDirAndChDir(t, testDataDir, coreutils.Npm) -// err := npmPackageHandler.UpdateDependency(test.vulnDetails) -// if !test.fixSupported { -// assert.Error(t, err, "Expected error to occur") -// assert.IsType(t, &utils.ErrUnsupportedFix{}, err, "Expected unsupported fix error") -// } else { -// assert.NoError(t, err) -// } -// assert.NoError(t, os.Chdir(testDataDir)) -// cleanup() -// }) -// } -//} -// -//// Yarn -//func TestYarnPackageHandler_UpdateDependency(t *testing.T) { -// yarnPackageHandler := &YarnPackageHandler{} -// testcases := []dependencyFixTest{ -// { -// vulnDetails: &utils.VulnerabilityDetails{ -// FixVersion: "1.2.6", -// VulnerabilityOrViolationRow: &formats.VulnerabilityOrViolationRow{Technology: coreutils.Yarn, ImpactedDependencyName: "minimist"}, -// }, fixSupported: false, -// }, -// { -// vulnDetails: &utils.VulnerabilityDetails{ -// FixVersion: "1.2.6", -// IsDirectDependency: true, -// VulnerabilityOrViolationRow: &formats.VulnerabilityOrViolationRow{Technology: coreutils.Yarn, ImpactedDependencyName: "minimist"}, -// }, fixSupported: true, -// }, -// } -// for _, test := range testcases { -// t.Run(test.vulnDetails.ImpactedDependencyName+" direct:"+strconv.FormatBool(test.vulnDetails.IsDirectDependency), func(t *testing.T) { -// testDataDir := getTestDataDir(t, test.vulnDetails.IsDirectDependency) -// cleanup := createTempDirAndChDir(t, testDataDir, coreutils.Yarn) -// err := yarnPackageHandler.UpdateDependency(test.vulnDetails) -// if !test.fixSupported { -// assert.Error(t, err, "Expected error to occur") -// assert.IsType(t, &utils.ErrUnsupportedFix{}, err, "Expected unsupported fix error") -// } else { -// assert.NoError(t, err) -// } -// assert.NoError(t, os.Chdir(testDataDir)) -// cleanup() -// }) -// } -//} -// -//// Maven -//func TestMavenPackageHandler_UpdateDependency(t *testing.T) { -// tests := []dependencyFixTest{ -// {vulnDetails: &utils.VulnerabilityDetails{ -// FixVersion: "2.7", -// VulnerabilityOrViolationRow: &formats.VulnerabilityOrViolationRow{Technology: coreutils.Maven, ImpactedDependencyName: "commons-io:commons-io"}, -// IsDirectDependency: true}, fixSupported: true}, -// {vulnDetails: &utils.VulnerabilityDetails{ -// FixVersion: "4.3.20", -// VulnerabilityOrViolationRow: &formats.VulnerabilityOrViolationRow{Technology: coreutils.Maven, ImpactedDependencyName: "org.springframework:spring-core"}, -// IsDirectDependency: false}, fixSupported: false}, -// } -// for _, test := range tests { -// t.Run(test.vulnDetails.ImpactedDependencyName+" direct:"+strconv.FormatBool(test.vulnDetails.IsDirectDependency), func(t *testing.T) { -// mavenPackageHandler := MavenPackageHandler{} -// testDataDir := getTestDataDir(t, test.vulnDetails.IsDirectDependency) -// cleanup := createTempDirAndChDir(t, testDataDir, coreutils.Maven) -// err := mavenPackageHandler.UpdateDependency(test.vulnDetails) -// if !test.fixSupported { -// assert.Error(t, err, "Expected error to occur") -// assert.IsType(t, &utils.ErrUnsupportedFix{}, err, "Expected unsupported fix error") -// } else { -// assert.NoError(t, err) -// } -// assert.NoError(t, os.Chdir(testDataDir)) -// cleanup() -// }) -// } -//} -// -//// Maven utils functions -//func TestGetDependenciesFromPomXmlSingleDependency(t *testing.T) { -// testCases := []string{` -// org.apache.commons -// commons-email -// 1.1 -// compile -//`, -// ` -// org.apache.commons -// commons-email -// 1.1 -// compile -//`, -// } -// -// for _, testCase := range testCases { -// result, err := getMavenDependencies([]byte(testCase)) -// assert.NoError(t, err) -// -// assert.Len(t, result, 1) -// assert.Equal(t, "org.apache.commons", result[0].GroupId) -// assert.Equal(t, "commons-email", result[0].ArtifactId) -// assert.Equal(t, "1.1", result[0].Version) -// } -//} -// -//func TestGetDependenciesFromPomXmlMultiDependency(t *testing.T) { -// testCases := []string{` -// -// -// -// org.apache.commons -// commons-email -// 1.1 -// compile -// -// -// org.codehaus.plexus -// plexus-utils -// 1.5.1 -// -// -//`, -// } -// -// for _, testCase := range testCases { -// result, err := getMavenDependencies([]byte(testCase)) -// assert.NoError(t, err) -// -// assert.Len(t, result, 2) -// assert.Equal(t, "org.apache.commons", result[0].GroupId) -// assert.Equal(t, "commons-email", result[0].ArtifactId) -// assert.Equal(t, "1.1", result[0].Version) -// -// assert.Equal(t, "org.codehaus.plexus", result[1].GroupId) -// assert.Equal(t, "plexus-utils", result[1].ArtifactId) -// assert.Equal(t, "1.5.1", result[1].Version) -// } -//} -// -//func TestGetPluginsFromPomXml(t *testing.T) { -// testCase := -// ` -// -// -// -// org.apache.maven.plugins -// maven-source-plugin -// -// -// com.github.spotbugs -// spotbugs-maven-plugin -// 4.5.3.0 -// -// spotbugs-security-exclude.xml -// -// -// com.h3xstream.findsecbugs -// findsecbugs-plugin -// 1.12.0 -// -// -// -// -// -// org.apache.maven.plugins -// maven-surefire-plugin -// 2.22.1 -// -// -// -// true -// -// -// **/InjectedTest.java -// **/*ITest.java -// -// -// -// -// -// -// ` -// plugins, err := getMavenDependencies([]byte(testCase)) -// assert.NoError(t, err) -// assert.Equal(t, "org.apache.maven.plugins", plugins[0].GroupId) -// assert.Equal(t, "maven-source-plugin", plugins[0].ArtifactId) -// assert.Equal(t, "com.github.spotbugs", plugins[1].GroupId) -// assert.Equal(t, "spotbugs-maven-plugin", plugins[1].ArtifactId) -// assert.Equal(t, "4.5.3.0", plugins[1].Version) -// assert.Equal(t, "com.h3xstream.findsecbugs", plugins[2].GroupId) -// assert.Equal(t, "findsecbugs-plugin", plugins[2].ArtifactId) -// assert.Equal(t, "1.12.0", plugins[2].Version) -// assert.Equal(t, "org.apache.maven.plugins", plugins[3].GroupId) -// assert.Equal(t, "maven-surefire-plugin", plugins[3].ArtifactId) -// assert.Equal(t, "2.22.1", plugins[3].Version) -//} -// -//func TestGetDependenciesFromDependencyManagement(t *testing.T) { -// testCase := ` -// -// -// -// -// io.jenkins.tools.bom -// bom-2.346.x -// 1607.va_c1576527071 -// import -// pom -// -// -// com.fasterxml.jackson.core -// jackson-core -// 2.13.4 -// -// -// com.fasterxml.jackson.core -// jackson-databind -// 2.13.4.2 -// -// -// com.fasterxml.jackson.core -// jackson-annotations -// 2.13.4 -// -// -// org.apache.httpcomponents -// httpcore -// 4.4.15 -// -// -// org.jenkins-ci.plugins.workflow -// workflow-durable-task-step -// 1190.vc93d7d457042 -// test -// -// -// -// -//` -// dependencies, err := getMavenDependencies([]byte(testCase)) -// assert.NoError(t, err) -// assert.Len(t, dependencies, 6) -// for _, dependency := range dependencies { -// assert.True(t, dependency.foundInDependencyManagement) -// } -//} -// -//func TestMavenGavReader(t *testing.T) { -// mvnHandler := &MavenPackageHandler{} -// currDir, err := os.Getwd() -// assert.NoError(t, err) -// tmpDir, err := os.MkdirTemp("", "") -// assert.NoError(t, err) -// assert.NoError(t, fileutils.CopyDir(filepath.Join("..", "..", "testdata", "projects", "maven"), tmpDir, true, nil)) -// assert.NoError(t, os.Chdir(tmpDir)) -// defer func() { -// assert.NoError(t, os.Chdir(currDir)) -// }() -// // Test installMavenGavReader -// assert.NoError(t, mvnHandler.installMavenGavReader()) -// assert.True(t, mvnHandler.isMavenGavReaderInstalled) -// // Test getProjectPoms using the maven-gav-reader plugin -// assert.NoError(t, mvnHandler.getProjectPoms()) -// assert.Len(t, mvnHandler.pomPaths, 2) -//} -// -//// General Utils functions -//func TestFixVersionInfo_UpdateFixVersionIfMax(t *testing.T) { -// type testCase struct { -// fixVersionInfo utils.VulnerabilityDetails -// newFixVersion string -// expectedOutput string -// } -// -// testCases := []testCase{ -// {fixVersionInfo: utils.VulnerabilityDetails{FixVersion: "1.2.3", IsDirectDependency: true}, newFixVersion: "1.2.4", expectedOutput: "1.2.4"}, -// {fixVersionInfo: utils.VulnerabilityDetails{FixVersion: "1.2.3", IsDirectDependency: true}, newFixVersion: "1.0.4", expectedOutput: "1.2.3"}, -// } -// -// for _, tc := range testCases { -// t.Run(tc.expectedOutput, func(t *testing.T) { -// tc.fixVersionInfo.UpdateFixVersionIfMax(tc.newFixVersion) -// assert.Equal(t, tc.expectedOutput, tc.fixVersionInfo.FixVersion) -// }) -// } -//} -// -//func TestUpdatePackageVersion(t *testing.T) { -// testProjectPath := filepath.Join("..", "..", "testdata", "packagehandlers") -// currDir, err := os.Getwd() -// assert.NoError(t, err) -// tmpDir, err := os.MkdirTemp("", "") -// assert.NoError(t, err) -// assert.NoError(t, fileutils.CopyDir(testProjectPath, tmpDir, true, nil)) -// assert.NoError(t, os.Chdir(tmpDir)) -// defer func() { -// assert.NoError(t, os.Chdir(currDir)) -// }() -// testCases := []struct { -// impactedPackage string -// fixedVersion string -// foundInDependencyManagement bool -// }{ -// {impactedPackage: "org.jfrog.filespecs:file-specs-java", fixedVersion: "1.1.2"}, -// {impactedPackage: "com.fasterxml.jackson.core:jackson-core", fixedVersion: "2.15.0", foundInDependencyManagement: true}, -// {impactedPackage: "org.apache.httpcomponents:httpcore", fixedVersion: "4.4.16", foundInDependencyManagement: true}, -// } -// mvnHandler := &MavenPackageHandler{} -// for _, test := range testCases { -// assert.NoError(t, mvnHandler.updatePackageVersion(test.impactedPackage, test.fixedVersion, test.foundInDependencyManagement)) -// } -// modifiedPom, err := os.ReadFile("pom.xml") -// assert.NoError(t, err) -// for _, test := range testCases { -// assert.Contains(t, fmt.Sprintf("%s", string(modifiedPom)), test.fixedVersion) -// } -//} -// -//func TestUpdatePropertiesVersion(t *testing.T) { -// testProjectPath := filepath.Join("..", "..", "testdata", "packagehandlers") -// currDir, err := os.Getwd() -// assert.NoError(t, err) -// tmpDir, err := os.MkdirTemp("", "") -// assert.NoError(t, err) -// assert.NoError(t, fileutils.CopyDir(testProjectPath, tmpDir, true, nil)) -// assert.NoError(t, os.Chdir(tmpDir)) -// defer func() { -// assert.NoError(t, os.Chdir(currDir)) -// }() -// mvnHandler := &MavenPackageHandler{} -// assert.NoError(t, mvnHandler.updateProperties(&pomDependencyDetails{properties: []string{"buildinfo.version"}}, "2.39.9")) -// modifiedPom, err := os.ReadFile("pom.xml") -// assert.NoError(t, err) -// assert.Contains(t, string(modifiedPom), "2.39.9") -//} -// -//func getTestDataDir(t *testing.T, directDependency bool) string { -// var projectDir string -// if directDependency { -// projectDir = "projects" -// } else { -// projectDir = "indirect-projects" -// } -// testdataDir, err := filepath.Abs(filepath.Join("..", "..", "testdata/"+projectDir)) -// assert.NoError(t, err) -// return testdataDir -//} -// -//func createTempDirAndChDir(t *testing.T, testdataDir string, tech coreutils.Technology) func() { -// // Create temp technology project -// projectPath := filepath.Join(testdataDir, tech.ToString()) -// tmpProjectPath, cleanup := testdatautils.CreateTestProject(t, projectPath) -// assert.NoError(t, os.Chdir(tmpProjectPath)) -// return cleanup -//} -// -//func assertFixVersionInPackageDescriptor(t *testing.T, test dependencyFixTest, packageDescriptor string) { -// file, err := os.ReadFile(packageDescriptor) -// assert.NoError(t, err) -// if !test.fixSupported { -// assert.NotContains(t, string(file), test.vulnDetails) -// } else { -// assert.Contains(t, string(file), test.vulnDetails.FixVersion) -// // Verify that case-sensitive packages in python are lowered -// assert.Contains(t, string(file), strings.ToLower(test.vulnDetails.ImpactedDependencyName)) -// } -//} +import ( + "fmt" + testdatautils "github.com/jfrog/build-info-go/build/testdata" + "github.com/jfrog/frogbot/commands/utils" + "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" + "github.com/jfrog/jfrog-cli-core/v2/xray/formats" + "github.com/jfrog/jfrog-client-go/utils/io/fileutils" + "github.com/stretchr/testify/assert" + "os" + "path/filepath" + "regexp" + "strconv" + "strings" + "testing" +) + +type dependencyFixTest struct { + vulnDetails *utils.VulnerabilityDetails + fixSupported bool +} + +type pythonIndirectDependencies struct { + dependencyFixTest + requirementsPath string +} + +const requirementsFile = "oslo.config>=1.12.1,<1.13\noslo.utils<5.0,>=4.0.0\nparamiko==2.7.2\npasslib<=1.7.4\nprance>=0.9.0\nprompt-toolkit~=1.0.15\npyinotify>0.9.6\nPyJWT>1.7.1\nurllib3 > 1.1.9, < 1.5.*" + +type pipPackageRegexTest struct { + packageName string + expectedRequirement string +} + +// Go +func TestGoPackageHandler_UpdateDependency(t *testing.T) { + goPackageHandler := GoPackageHandler{} + testcases := []dependencyFixTest{ + { + vulnDetails: &utils.VulnerabilityDetails{ + FixVersion: "0.0.0-20201216223049-8b5274cf687f", + IsDirectDependency: false, + VulnerabilityOrViolationRow: &formats.VulnerabilityOrViolationRow{Technology: coreutils.Go, ImpactedDependencyName: "golang.org/x/crypto"}, + }, fixSupported: true, + }, + { + vulnDetails: &utils.VulnerabilityDetails{ + FixVersion: "1.7.7", + IsDirectDependency: true, + VulnerabilityOrViolationRow: &formats.VulnerabilityOrViolationRow{Technology: coreutils.Go, ImpactedDependencyName: "github.com/gin-gonic/gin"}, + }, fixSupported: true, + }, + { + vulnDetails: &utils.VulnerabilityDetails{ + FixVersion: "1.3.0", + IsDirectDependency: true, + VulnerabilityOrViolationRow: &formats.VulnerabilityOrViolationRow{Technology: coreutils.Go, ImpactedDependencyName: "github.com/google/uuid"}, + }, fixSupported: true, + }, + } + for _, test := range testcases { + t.Run(test.vulnDetails.ImpactedDependencyName+" direct:"+strconv.FormatBool(test.vulnDetails.IsDirectDependency), func(t *testing.T) { + testDataDir := getTestDataDir(t, test.vulnDetails.IsDirectDependency) + cleanup := createTempDirAndChDir(t, testDataDir, coreutils.Go) + err := goPackageHandler.UpdateDependency(test.vulnDetails) + if !test.fixSupported { + assert.Error(t, err, "Expected error to occur") + assert.IsType(t, &utils.ErrUnsupportedFix{}, err, "Expected unsupported fix error") + } else { + assert.NoError(t, err) + assertFixVersionInPackageDescriptor(t, test, "go.mod") + } + assert.NoError(t, os.Chdir(testDataDir)) + cleanup() + }) + } +} + +// Python, includes pip,pipenv, poetry +func TestPythonPackageHandler_UpdateDependency(t *testing.T) { + testcases := []pythonIndirectDependencies{ + {dependencyFixTest: dependencyFixTest{ + vulnDetails: &utils.VulnerabilityDetails{ + FixVersion: "1.25.9", + VulnerabilityOrViolationRow: &formats.VulnerabilityOrViolationRow{Technology: coreutils.Pip, ImpactedDependencyName: "urllib3"}, + }}, requirementsPath: "requirements.txt"}, + {dependencyFixTest: dependencyFixTest{ + vulnDetails: &utils.VulnerabilityDetails{ + FixVersion: "1.25.9", + VulnerabilityOrViolationRow: &formats.VulnerabilityOrViolationRow{Technology: coreutils.Poetry, ImpactedDependencyName: "urllib3"}}}, + requirementsPath: "pyproejct.toml"}, + {dependencyFixTest: dependencyFixTest{ + vulnDetails: &utils.VulnerabilityDetails{ + FixVersion: "1.25.9", + VulnerabilityOrViolationRow: &formats.VulnerabilityOrViolationRow{Technology: coreutils.Pipenv, ImpactedDependencyName: "urllib3"}}}, + requirementsPath: "Pipfile"}, + {dependencyFixTest: dependencyFixTest{ + vulnDetails: &utils.VulnerabilityDetails{ + FixVersion: "2.4.0", + VulnerabilityOrViolationRow: &formats.VulnerabilityOrViolationRow{Technology: coreutils.Pip, ImpactedDependencyName: "pyjwt"}, + IsDirectDependency: true}, + fixSupported: true}, + requirementsPath: "requirements.txt"}, + {dependencyFixTest: dependencyFixTest{ + vulnDetails: &utils.VulnerabilityDetails{ + FixVersion: "2.4.0", + VulnerabilityOrViolationRow: &formats.VulnerabilityOrViolationRow{Technology: coreutils.Pip, ImpactedDependencyName: "Pyjwt"}, + IsDirectDependency: true}, + fixSupported: true}, + requirementsPath: "requirements.txt"}, + {dependencyFixTest: dependencyFixTest{ + vulnDetails: &utils.VulnerabilityDetails{ + FixVersion: "2.4.0", + VulnerabilityOrViolationRow: &formats.VulnerabilityOrViolationRow{Technology: coreutils.Pip, ImpactedDependencyName: "pyjwt"}, + IsDirectDependency: true}, fixSupported: true}, + requirementsPath: "setup.py"}, + {dependencyFixTest: dependencyFixTest{ + vulnDetails: &utils.VulnerabilityDetails{ + FixVersion: "2.4.0", + VulnerabilityOrViolationRow: &formats.VulnerabilityOrViolationRow{Technology: coreutils.Poetry, ImpactedDependencyName: "pyjwt"}, + IsDirectDependency: true}, + fixSupported: true}, + requirementsPath: "pyproject.toml"}, + {dependencyFixTest: dependencyFixTest{ + vulnDetails: &utils.VulnerabilityDetails{ + FixVersion: "2.4.0", + VulnerabilityOrViolationRow: &formats.VulnerabilityOrViolationRow{Technology: coreutils.Poetry, ImpactedDependencyName: "pyjwt"}, + IsDirectDependency: true}, + fixSupported: true}, + requirementsPath: "pyproject.toml"}, + } + for _, test := range testcases { + t.Run(test.vulnDetails.ImpactedDependencyName+" direct:"+strconv.FormatBool(test.vulnDetails.IsDirectDependency), func(t *testing.T) { + testDataDir := getTestDataDir(t, test.vulnDetails.IsDirectDependency) + pythonPackageHandler := GetCompatiblePackageHandler(test.vulnDetails, &utils.ScanDetails{ + Project: &utils.Project{PipRequirementsFile: test.requirementsPath}}) + cleanup := createTempDirAndChDir(t, testDataDir, test.vulnDetails.Technology) + err := pythonPackageHandler.UpdateDependency(test.vulnDetails) + if !test.fixSupported { + assert.Error(t, err, "Expected error to occur") + assert.IsType(t, &utils.ErrUnsupportedFix{}, err, "Expected unsupported fix error") + } else { + assert.NoError(t, err) + } + assert.NoError(t, os.Chdir(testDataDir)) + cleanup() + }) + } +} + +func TestPipPackageRegex(t *testing.T) { + var pipPackagesRegexTests = []pipPackageRegexTest{ + {"oslo.config", "oslo.config>=1.12.1,<1.13"}, + {"oslo.utils", "oslo.utils<5.0,>=4.0.0"}, + {"paramiko", "paramiko==2.7.2"}, + {"passlib", "passlib<=1.7.4"}, + {"PassLib", "passlib<=1.7.4"}, + {"prance", "prance>=0.9.0"}, + {"prompt-toolkit", "prompt-toolkit~=1.0.15"}, + {"pyinotify", "pyinotify>0.9.6"}, + {"pyjwt", "pyjwt>1.7.1"}, + {"PyJWT", "pyjwt>1.7.1"}, + {"urllib3", "urllib3 > 1.1.9, < 1.5.*"}, + } + for _, pack := range pipPackagesRegexTests { + re := regexp.MustCompile(PythonPackageRegexPrefix + "(" + pack.packageName + "|" + strings.ToLower(pack.packageName) + ")" + PythonPackageRegexSuffix) + found := re.FindString(requirementsFile) + assert.Equal(t, pack.expectedRequirement, strings.ToLower(found)) + } +} + +// Npm +func TestNpmPackageHandler_UpdateDependency(t *testing.T) { + npmPackageHandler := &NpmPackageHandler{} + testcases := []dependencyFixTest{ + { + vulnDetails: &utils.VulnerabilityDetails{ + FixVersion: "0.8.4", + VulnerabilityOrViolationRow: &formats.VulnerabilityOrViolationRow{Technology: coreutils.Npm, ImpactedDependencyName: "mpath"}, + }, fixSupported: false, + }, + { + vulnDetails: &utils.VulnerabilityDetails{ + FixVersion: "3.0.2", + IsDirectDependency: true, + VulnerabilityOrViolationRow: &formats.VulnerabilityOrViolationRow{Technology: coreutils.Npm, ImpactedDependencyName: "minimatch"}, + }, fixSupported: true, + }, + } + for _, test := range testcases { + t.Run(test.vulnDetails.ImpactedDependencyName+" direct:"+strconv.FormatBool(test.vulnDetails.IsDirectDependency), func(t *testing.T) { + testDataDir := getTestDataDir(t, test.vulnDetails.IsDirectDependency) + cleanup := createTempDirAndChDir(t, testDataDir, coreutils.Npm) + err := npmPackageHandler.UpdateDependency(test.vulnDetails) + if !test.fixSupported { + assert.Error(t, err, "Expected error to occur") + assert.IsType(t, &utils.ErrUnsupportedFix{}, err, "Expected unsupported fix error") + } else { + assert.NoError(t, err) + } + assert.NoError(t, os.Chdir(testDataDir)) + cleanup() + }) + } +} + +// Yarn +func TestYarnPackageHandler_UpdateDependency(t *testing.T) { + yarnPackageHandler := &YarnPackageHandler{} + testcases := []dependencyFixTest{ + { + vulnDetails: &utils.VulnerabilityDetails{ + FixVersion: "1.2.6", + VulnerabilityOrViolationRow: &formats.VulnerabilityOrViolationRow{Technology: coreutils.Yarn, ImpactedDependencyName: "minimist"}, + }, fixSupported: false, + }, + { + vulnDetails: &utils.VulnerabilityDetails{ + FixVersion: "1.2.6", + IsDirectDependency: true, + VulnerabilityOrViolationRow: &formats.VulnerabilityOrViolationRow{Technology: coreutils.Yarn, ImpactedDependencyName: "minimist"}, + }, fixSupported: true, + }, + } + for _, test := range testcases { + t.Run(test.vulnDetails.ImpactedDependencyName+" direct:"+strconv.FormatBool(test.vulnDetails.IsDirectDependency), func(t *testing.T) { + testDataDir := getTestDataDir(t, test.vulnDetails.IsDirectDependency) + cleanup := createTempDirAndChDir(t, testDataDir, coreutils.Yarn) + err := yarnPackageHandler.UpdateDependency(test.vulnDetails) + if !test.fixSupported { + assert.Error(t, err, "Expected error to occur") + assert.IsType(t, &utils.ErrUnsupportedFix{}, err, "Expected unsupported fix error") + } else { + assert.NoError(t, err) + } + assert.NoError(t, os.Chdir(testDataDir)) + cleanup() + }) + } +} + +// Maven +func TestMavenPackageHandler_UpdateDependency(t *testing.T) { + tests := []dependencyFixTest{ + {vulnDetails: &utils.VulnerabilityDetails{ + FixVersion: "2.7", + VulnerabilityOrViolationRow: &formats.VulnerabilityOrViolationRow{Technology: coreutils.Maven, ImpactedDependencyName: "commons-io:commons-io"}, + IsDirectDependency: true}, fixSupported: true}, + {vulnDetails: &utils.VulnerabilityDetails{ + FixVersion: "4.3.20", + VulnerabilityOrViolationRow: &formats.VulnerabilityOrViolationRow{Technology: coreutils.Maven, ImpactedDependencyName: "org.springframework:spring-core"}, + IsDirectDependency: false}, fixSupported: false}, + } + for _, test := range tests { + t.Run(test.vulnDetails.ImpactedDependencyName+" direct:"+strconv.FormatBool(test.vulnDetails.IsDirectDependency), func(t *testing.T) { + mavenPackageHandler := MavenPackageHandler{} + testDataDir := getTestDataDir(t, test.vulnDetails.IsDirectDependency) + cleanup := createTempDirAndChDir(t, testDataDir, coreutils.Maven) + err := mavenPackageHandler.UpdateDependency(test.vulnDetails) + if !test.fixSupported { + assert.Error(t, err, "Expected error to occur") + assert.IsType(t, &utils.ErrUnsupportedFix{}, err, "Expected unsupported fix error") + } else { + assert.NoError(t, err) + } + assert.NoError(t, os.Chdir(testDataDir)) + cleanup() + }) + } +} + +// Maven utils functions +func TestGetDependenciesFromPomXmlSingleDependency(t *testing.T) { + testCases := []string{` + org.apache.commons + commons-email + 1.1 + compile +`, + ` + org.apache.commons + commons-email + 1.1 + compile +`, + } + + for _, testCase := range testCases { + result, err := getMavenDependencies([]byte(testCase)) + assert.NoError(t, err) + + assert.Len(t, result, 1) + assert.Equal(t, "org.apache.commons", result[0].GroupId) + assert.Equal(t, "commons-email", result[0].ArtifactId) + assert.Equal(t, "1.1", result[0].Version) + } +} + +func TestGetDependenciesFromPomXmlMultiDependency(t *testing.T) { + testCases := []string{` + + + + org.apache.commons + commons-email + 1.1 + compile + + + org.codehaus.plexus + plexus-utils + 1.5.1 + + +`, + } + + for _, testCase := range testCases { + result, err := getMavenDependencies([]byte(testCase)) + assert.NoError(t, err) + + assert.Len(t, result, 2) + assert.Equal(t, "org.apache.commons", result[0].GroupId) + assert.Equal(t, "commons-email", result[0].ArtifactId) + assert.Equal(t, "1.1", result[0].Version) + + assert.Equal(t, "org.codehaus.plexus", result[1].GroupId) + assert.Equal(t, "plexus-utils", result[1].ArtifactId) + assert.Equal(t, "1.5.1", result[1].Version) + } +} + +func TestGetPluginsFromPomXml(t *testing.T) { + testCase := + ` + + + + org.apache.maven.plugins + maven-source-plugin + + + com.github.spotbugs + spotbugs-maven-plugin + 4.5.3.0 + + spotbugs-security-exclude.xml + + + com.h3xstream.findsecbugs + findsecbugs-plugin + 1.12.0 + + + + + + org.apache.maven.plugins + maven-surefire-plugin + 2.22.1 + + + + true + + + **/InjectedTest.java + **/*ITest.java + + + + + + + ` + plugins, err := getMavenDependencies([]byte(testCase)) + assert.NoError(t, err) + assert.Equal(t, "org.apache.maven.plugins", plugins[0].GroupId) + assert.Equal(t, "maven-source-plugin", plugins[0].ArtifactId) + assert.Equal(t, "com.github.spotbugs", plugins[1].GroupId) + assert.Equal(t, "spotbugs-maven-plugin", plugins[1].ArtifactId) + assert.Equal(t, "4.5.3.0", plugins[1].Version) + assert.Equal(t, "com.h3xstream.findsecbugs", plugins[2].GroupId) + assert.Equal(t, "findsecbugs-plugin", plugins[2].ArtifactId) + assert.Equal(t, "1.12.0", plugins[2].Version) + assert.Equal(t, "org.apache.maven.plugins", plugins[3].GroupId) + assert.Equal(t, "maven-surefire-plugin", plugins[3].ArtifactId) + assert.Equal(t, "2.22.1", plugins[3].Version) +} + +func TestGetDependenciesFromDependencyManagement(t *testing.T) { + testCase := ` + + + + + io.jenkins.tools.bom + bom-2.346.x + 1607.va_c1576527071 + import + pom + + + com.fasterxml.jackson.core + jackson-core + 2.13.4 + + + com.fasterxml.jackson.core + jackson-databind + 2.13.4.2 + + + com.fasterxml.jackson.core + jackson-annotations + 2.13.4 + + + org.apache.httpcomponents + httpcore + 4.4.15 + + + org.jenkins-ci.plugins.workflow + workflow-durable-task-step + 1190.vc93d7d457042 + test + + + + +` + dependencies, err := getMavenDependencies([]byte(testCase)) + assert.NoError(t, err) + assert.Len(t, dependencies, 6) + for _, dependency := range dependencies { + assert.True(t, dependency.foundInDependencyManagement) + } +} + +func TestMavenGavReader(t *testing.T) { + mvnHandler := &MavenPackageHandler{} + currDir, err := os.Getwd() + assert.NoError(t, err) + tmpDir, err := os.MkdirTemp("", "") + assert.NoError(t, err) + assert.NoError(t, fileutils.CopyDir(filepath.Join("..", "..", "testdata", "projects", "maven"), tmpDir, true, nil)) + assert.NoError(t, os.Chdir(tmpDir)) + defer func() { + assert.NoError(t, os.Chdir(currDir)) + }() + // Test installMavenGavReader + assert.NoError(t, mvnHandler.installMavenGavReader()) + assert.True(t, mvnHandler.isMavenGavReaderInstalled) + // Test getProjectPoms using the maven-gav-reader plugin + assert.NoError(t, mvnHandler.getProjectPoms()) + assert.Len(t, mvnHandler.pomPaths, 2) +} + +// General Utils functions +func TestFixVersionInfo_UpdateFixVersionIfMax(t *testing.T) { + type testCase struct { + fixVersionInfo utils.VulnerabilityDetails + newFixVersion string + expectedOutput string + } + + testCases := []testCase{ + {fixVersionInfo: utils.VulnerabilityDetails{FixVersion: "1.2.3", IsDirectDependency: true}, newFixVersion: "1.2.4", expectedOutput: "1.2.4"}, + {fixVersionInfo: utils.VulnerabilityDetails{FixVersion: "1.2.3", IsDirectDependency: true}, newFixVersion: "1.0.4", expectedOutput: "1.2.3"}, + } + + for _, tc := range testCases { + t.Run(tc.expectedOutput, func(t *testing.T) { + tc.fixVersionInfo.UpdateFixVersionIfMax(tc.newFixVersion) + assert.Equal(t, tc.expectedOutput, tc.fixVersionInfo.FixVersion) + }) + } +} + +func TestUpdatePackageVersion(t *testing.T) { + testProjectPath := filepath.Join("..", "..", "testdata", "packagehandlers") + currDir, err := os.Getwd() + assert.NoError(t, err) + tmpDir, err := os.MkdirTemp("", "") + assert.NoError(t, err) + assert.NoError(t, fileutils.CopyDir(testProjectPath, tmpDir, true, nil)) + assert.NoError(t, os.Chdir(tmpDir)) + defer func() { + assert.NoError(t, os.Chdir(currDir)) + }() + testCases := []struct { + impactedPackage string + fixedVersion string + foundInDependencyManagement bool + }{ + {impactedPackage: "org.jfrog.filespecs:file-specs-java", fixedVersion: "1.1.2"}, + {impactedPackage: "com.fasterxml.jackson.core:jackson-core", fixedVersion: "2.15.0", foundInDependencyManagement: true}, + {impactedPackage: "org.apache.httpcomponents:httpcore", fixedVersion: "4.4.16", foundInDependencyManagement: true}, + } + mvnHandler := &MavenPackageHandler{} + for _, test := range testCases { + assert.NoError(t, mvnHandler.updatePackageVersion(test.impactedPackage, test.fixedVersion, test.foundInDependencyManagement)) + } + modifiedPom, err := os.ReadFile("pom.xml") + assert.NoError(t, err) + for _, test := range testCases { + assert.Contains(t, fmt.Sprintf("%s", string(modifiedPom)), test.fixedVersion) + } +} + +func TestUpdatePropertiesVersion(t *testing.T) { + testProjectPath := filepath.Join("..", "..", "testdata", "packagehandlers") + currDir, err := os.Getwd() + assert.NoError(t, err) + tmpDir, err := os.MkdirTemp("", "") + assert.NoError(t, err) + assert.NoError(t, fileutils.CopyDir(testProjectPath, tmpDir, true, nil)) + assert.NoError(t, os.Chdir(tmpDir)) + defer func() { + assert.NoError(t, os.Chdir(currDir)) + }() + mvnHandler := &MavenPackageHandler{} + assert.NoError(t, mvnHandler.updateProperties(&pomDependencyDetails{properties: []string{"buildinfo.version"}}, "2.39.9")) + modifiedPom, err := os.ReadFile("pom.xml") + assert.NoError(t, err) + assert.Contains(t, string(modifiedPom), "2.39.9") +} + +func getTestDataDir(t *testing.T, directDependency bool) string { + var projectDir string + if directDependency { + projectDir = "projects" + } else { + projectDir = "indirect-projects" + } + testdataDir, err := filepath.Abs(filepath.Join("..", "..", "testdata/"+projectDir)) + assert.NoError(t, err) + return testdataDir +} + +func createTempDirAndChDir(t *testing.T, testdataDir string, tech coreutils.Technology) func() { + // Create temp technology project + projectPath := filepath.Join(testdataDir, tech.ToString()) + tmpProjectPath, cleanup := testdatautils.CreateTestProject(t, projectPath) + assert.NoError(t, os.Chdir(tmpProjectPath)) + return cleanup +} + +func assertFixVersionInPackageDescriptor(t *testing.T, test dependencyFixTest, packageDescriptor string) { + file, err := os.ReadFile(packageDescriptor) + assert.NoError(t, err) + if !test.fixSupported { + assert.NotContains(t, string(file), test.vulnDetails) + } else { + assert.Contains(t, string(file), test.vulnDetails.FixVersion) + // Verify that case-sensitive packages in python are lowered + assert.Contains(t, string(file), strings.ToLower(test.vulnDetails.ImpactedDependencyName)) + } +} diff --git a/commands/utils/simplifiedoutput_test.go b/commands/utils/simplifiedoutput_test.go index 99d90ec18..212571b72 100644 --- a/commands/utils/simplifiedoutput_test.go +++ b/commands/utils/simplifiedoutput_test.go @@ -1,345 +1,344 @@ package utils -// -//import ( -// "fmt" -// "github.com/jfrog/froggit-go/vcsutils" -// "github.com/jfrog/jfrog-cli-core/v2/xray/formats" -// "github.com/stretchr/testify/assert" -// "testing" -//) -// -//func TestSimplifiedOutput_VulnerabilitiesTableRow(t *testing.T) { -// type testCase struct { -// name string -// vulnerability formats.VulnerabilityOrViolationRow -// expectedOutput string -// } -// -// testCases := []testCase{ -// { -// name: "Single CVE and one direct dependency", -// vulnerability: formats.VulnerabilityOrViolationRow{ -// Severity: "High", -// Components: []formats.ComponentRow{ -// {Name: "dep1", Version: "1.0.0"}, -// }, -// ImpactedDependencyName: "impacted_dep", -// ImpactedDependencyVersion: "2.0.0", -// FixedVersions: []string{"3.0.0"}, -// Cves: []formats.CveRow{ -// {Id: "CVE-2022-0001"}, -// }, -// }, -// expectedOutput: "| High | dep1:1.0.0 | impacted_dep:2.0.0 | 3.0.0 |", -// }, -// { -// name: "No CVE and multiple direct dependencies", -// vulnerability: formats.VulnerabilityOrViolationRow{ -// Severity: "Low", -// Components: []formats.ComponentRow{ -// {Name: "dep1", Version: "1.0.0"}, -// {Name: "dep2", Version: "2.0.0"}, -// }, -// ImpactedDependencyName: "impacted_dep", -// ImpactedDependencyVersion: "3.0.0", -// FixedVersions: []string{"4.0.0"}, -// Cves: []formats.CveRow{}, -// }, -// expectedOutput: "| Low | dep1:1.0.0, dep2:2.0.0 | impacted_dep:3.0.0 | 4.0.0 |", -// }, -// { -// name: "Multiple CVEs and no direct dependencies", -// vulnerability: formats.VulnerabilityOrViolationRow{ -// Severity: "Critical", -// Components: []formats.ComponentRow{}, -// ImpactedDependencyName: "impacted_dep", -// ImpactedDependencyVersion: "4.0.0", -// FixedVersions: []string{"5.0.0", "6.0.0"}, -// Cves: []formats.CveRow{ -// {Id: "CVE-2022-0002"}, -// {Id: "CVE-2022-0003"}, -// }, -// }, -// expectedOutput: "| Critical | | impacted_dep:4.0.0 | 5.0.0, 6.0.0 |", -// }, -// } -// -// for _, tc := range testCases { -// t.Run(tc.name, func(t *testing.T) { -// smo := &SimplifiedOutput{} -// actualOutput := smo.VulnerabilitiesTableRow(tc.vulnerability) -// assert.Equal(t, tc.expectedOutput, actualOutput) -// }) -// } -//} -// -//func TestSimplifiedOutput_IsFrogbotResultComment(t *testing.T) { -// testCases := []struct { -// name string -// comment string -// expected bool -// }{ -// { -// name: "Starts with No Vulnerability Banner", -// comment: "**👍 Frogbot scanned this pull request and found that it did not add vulnerable dependencies.** \n", -// expected: true, -// }, -// { -// name: "Starts with Vulnerabilities Banner", -// comment: "**🚨 Frogbot scanned this pull request and found the below:**\n", -// expected: true, -// }, -// { -// name: "Does not start with Banner", -// comment: "This is a random comment.", -// expected: false, -// }, -// } -// -// for _, tc := range testCases { -// t.Run(tc.name, func(t *testing.T) { -// smo := &SimplifiedOutput{} -// actual := smo.IsFrogbotResultComment(tc.comment) -// assert.Equal(t, tc.expected, actual) -// }) -// } -//} -// -//func TestSimplifiedOutput_VulnerabilitiesContent(t *testing.T) { -// // Create a new instance of StandardOutput -// so := &SimplifiedOutput{} -// -// // Create some sample vulnerabilitiesRows for testing -// vulnerabilitiesRows := []formats.VulnerabilityOrViolationRow{ -// { -// ImpactedDependencyName: "Dependency1", -// FixedVersions: []string{"2.2.3"}, -// Cves: []formats.CveRow{{Id: "CVE-2023-1234"}}, -// ImpactedDependencyVersion: "1.0.0", -// }, -// { -// ImpactedDependencyName: "Dependency2", -// FixedVersions: []string{"2.2.3"}, -// Cves: []formats.CveRow{{Id: "CVE-2023-1234"}}, -// ImpactedDependencyVersion: "2.0.0", -// }, -// } -// -// // Set the expected content string based on the sample data -// expectedContent := fmt.Sprintf(` -//--- -//## 📦 Vulnerable Dependencies -//--- -// -//### ✍️ Summary -// -//%s %s -// -//--- -//### 👇 Details -//--- -// -// -//#### %s %s -// -//%s -// -// -//#### %s %s -// -//%s -// -//`, -// so.VulnerabilitiesTableHeader(), -// getVulnerabilitiesTableContent(vulnerabilitiesRows, so), -// vulnerabilitiesRows[0].ImpactedDependencyName, -// vulnerabilitiesRows[0].ImpactedDependencyVersion, -// createVulnerabilityDescription(&vulnerabilitiesRows[0], so.VcsProvider()), -// vulnerabilitiesRows[1].ImpactedDependencyName, -// vulnerabilitiesRows[1].ImpactedDependencyVersion, -// createVulnerabilityDescription(&vulnerabilitiesRows[1], so.VcsProvider()), -// ) -// -// actualContent := so.VulnerabilitiesContent(vulnerabilitiesRows) -// assert.Equal(t, expectedContent, actualContent, "Content mismatch") -//} -// -//func TestSimplifiedOutput_ContentWithContextualAnalysis(t *testing.T) { -// // Create a new instance of StandardOutput -// so := &SimplifiedOutput{entitledForJas: true, vcsProvider: vcsutils.BitbucketServer} -// -// // Create some sample vulnerabilitiesRows for testing -// vulnerabilitiesRows := []formats.VulnerabilityOrViolationRow{ -// { -// ImpactedDependencyName: "Dependency1", -// ImpactedDependencyVersion: "1.0.0", -// FixedVersions: []string{"2.2.3"}, -// Cves: []formats.CveRow{{Id: "CVE-2023-1234"}}, -// Applicable: "Applicable", -// }, -// { -// ImpactedDependencyName: "Dependency2", -// ImpactedDependencyVersion: "2.0.0", -// FixedVersions: []string{"2.2.3"}, -// Cves: []formats.CveRow{{Id: "CVE-2023-1234"}}, -// Applicable: "Not Applicable", -// }, -// } -// -// // Set the expected content string based on the sample data -// expectedContent := fmt.Sprintf(` -//--- -//## 📦 Vulnerable Dependencies -//--- -// -//### ✍️ Summary -// -//%s %s -// -//--- -//### 👇 Details -//--- -// -// -//#### %s %s -// -//%s -// -// -//#### %s %s -// -//%s -// -//`, -// so.VulnerabilitiesTableHeader(), -// getVulnerabilitiesTableContent(vulnerabilitiesRows, so), -// vulnerabilitiesRows[0].ImpactedDependencyName, -// vulnerabilitiesRows[0].ImpactedDependencyVersion, -// createVulnerabilityDescription(&vulnerabilitiesRows[0], so.VcsProvider()), -// vulnerabilitiesRows[1].ImpactedDependencyName, -// vulnerabilitiesRows[1].ImpactedDependencyVersion, -// createVulnerabilityDescription(&vulnerabilitiesRows[1], so.VcsProvider()), -// ) -// -// actualContent := so.VulnerabilitiesContent(vulnerabilitiesRows) -// assert.Equal(t, expectedContent, actualContent, "Content mismatch") -// assert.Contains(t, actualContent, "CONTEXTUAL ANALYSIS") -// assert.Contains(t, actualContent, "**APPLICABLE**") -// assert.Contains(t, actualContent, "**NOT APPLICABLE**") -//} -// -//func TestSimplifiedOutput_IacContent(t *testing.T) { -// testCases := []struct { -// name string -// iacRows []formats.IacSecretsRow -// expectedOutput string -// }{ -// { -// name: "Empty IAC rows", -// iacRows: []formats.IacSecretsRow{}, -// expectedOutput: "", -// }, -// { -// name: "Single IAC row", -// iacRows: []formats.IacSecretsRow{ -// { -// Severity: "High", -// SeverityNumValue: 3, -// File: "applicable/req_sw_terraform_azure_redis_auth.tf", -// LineColumn: "11:1", -// Text: "Missing Periodic patching was detected", -// Type: "azure_redis_patch", -// }, -// }, -// expectedOutput: "\n## 🛠️ Infrastructure as Code \n\n\n| SEVERITY | FILE | LINE:COLUMN | FINDING | SCANNER |\n| :---------------------: | :----------------------------------: | :-----------------------------------: | :---------------------------------: | :---------------------------------: | \n| High | applicable/req_sw_terraform_azure_redis_auth.tf | 11:1 | Missing Periodic patching was detected | azure_redis_patch |\n\n", -// }, -// { -// name: "Multiple IAC rows", -// iacRows: []formats.IacSecretsRow{ -// { -// Severity: "High", -// SeverityNumValue: 3, -// File: "applicable/req_sw_terraform_azure_redis_patch.tf", -// LineColumn: "11:1", -// Text: "Missing redis firewall definition or start_ip=0.0.0.0 was detected, Missing redis firewall definition or start_ip=0.0.0.0 was detected", -// Type: "azure_redis_no_public", -// }, -// { -// Severity: "High", -// SeverityNumValue: 3, -// File: "applicable/req_sw_terraform_azure_redis_auth.tf", -// LineColumn: "11:1", -// Text: "Missing Periodic patching was detected", -// Type: "azure_redis_patch", -// }, -// }, -// expectedOutput: "\n## 🛠️ Infrastructure as Code \n\n\n| SEVERITY | FILE | LINE:COLUMN | FINDING | SCANNER |\n| :---------------------: | :----------------------------------: | :-----------------------------------: | :---------------------------------: | :---------------------------------: | \n| High | applicable/req_sw_terraform_azure_redis_patch.tf | 11:1 | Missing redis firewall definition or start_ip=0.0.0.0 was detected, Missing redis firewall definition or start_ip=0.0.0.0 was detected | azure_redis_no_public |\n| High | applicable/req_sw_terraform_azure_redis_auth.tf | 11:1 | Missing Periodic patching was detected | azure_redis_patch |\n\n", -// }, -// } -// -// writer := &SimplifiedOutput{} -// for _, tc := range testCases { -// t.Run(tc.name, func(t *testing.T) { -// output := writer.IacContent(tc.iacRows) -// assert.Equal(t, tc.expectedOutput, output) -// }) -// } -//} -// -//func TestSimplifiedOutput_GetIacTableContent(t *testing.T) { -// testCases := []struct { -// name string -// iacRows []formats.IacSecretsRow -// expectedOutput string -// }{ -// { -// name: "Empty IAC rows", -// iacRows: []formats.IacSecretsRow{}, -// expectedOutput: "", -// }, -// { -// name: "Single IAC row", -// iacRows: []formats.IacSecretsRow{ -// { -// Severity: "Medium", -// SeverityNumValue: 2, -// File: "file1", -// LineColumn: "1:10", -// Text: "Public access to MySQL was detected", -// Type: "azure_mysql_no_public", -// }, -// }, -// expectedOutput: "\n| Medium | file1 | 1:10 | Public access to MySQL was detected | azure_mysql_no_public |", -// }, -// { -// name: "Multiple IAC rows", -// iacRows: []formats.IacSecretsRow{ -// { -// Severity: "High", -// SeverityNumValue: 3, -// File: "file1", -// LineColumn: "1:10", -// Text: "Public access to MySQL was detected", -// Type: "azure_mysql_no_public", -// }, -// { -// Severity: "Medium", -// SeverityNumValue: 2, -// File: "file2", -// LineColumn: "2:5", -// Text: "Public access to MySQL was detected", -// Type: "azure_mysql_no_public", -// }, -// }, -// expectedOutput: "\n| High | file1 | 1:10 | Public access to MySQL was detected | azure_mysql_no_public |\n| Medium | file2 | 2:5 | Public access to MySQL was detected | azure_mysql_no_public |", -// }, -// } -// -// for _, tc := range testCases { -// t.Run(tc.name, func(t *testing.T) { -// output := getIacTableContent(tc.iacRows, &SimplifiedOutput{}) -// assert.Equal(t, tc.expectedOutput, output) -// }) -// } -//} +import ( + "fmt" + "github.com/jfrog/froggit-go/vcsutils" + "github.com/jfrog/jfrog-cli-core/v2/xray/formats" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestSimplifiedOutput_VulnerabilitiesTableRow(t *testing.T) { + type testCase struct { + name string + vulnerability formats.VulnerabilityOrViolationRow + expectedOutput string + } + + testCases := []testCase{ + { + name: "Single CVE and one direct dependency", + vulnerability: formats.VulnerabilityOrViolationRow{ + Severity: "High", + Components: []formats.ComponentRow{ + {Name: "dep1", Version: "1.0.0"}, + }, + ImpactedDependencyName: "impacted_dep", + ImpactedDependencyVersion: "2.0.0", + FixedVersions: []string{"3.0.0"}, + Cves: []formats.CveRow{ + {Id: "CVE-2022-0001"}, + }, + }, + expectedOutput: "| High | dep1:1.0.0 | impacted_dep:2.0.0 | 3.0.0 |", + }, + { + name: "No CVE and multiple direct dependencies", + vulnerability: formats.VulnerabilityOrViolationRow{ + Severity: "Low", + Components: []formats.ComponentRow{ + {Name: "dep1", Version: "1.0.0"}, + {Name: "dep2", Version: "2.0.0"}, + }, + ImpactedDependencyName: "impacted_dep", + ImpactedDependencyVersion: "3.0.0", + FixedVersions: []string{"4.0.0"}, + Cves: []formats.CveRow{}, + }, + expectedOutput: "| Low | dep1:1.0.0, dep2:2.0.0 | impacted_dep:3.0.0 | 4.0.0 |", + }, + { + name: "Multiple CVEs and no direct dependencies", + vulnerability: formats.VulnerabilityOrViolationRow{ + Severity: "Critical", + Components: []formats.ComponentRow{}, + ImpactedDependencyName: "impacted_dep", + ImpactedDependencyVersion: "4.0.0", + FixedVersions: []string{"5.0.0", "6.0.0"}, + Cves: []formats.CveRow{ + {Id: "CVE-2022-0002"}, + {Id: "CVE-2022-0003"}, + }, + }, + expectedOutput: "| Critical | | impacted_dep:4.0.0 | 5.0.0, 6.0.0 |", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + smo := &SimplifiedOutput{} + actualOutput := smo.VulnerabilitiesTableRow(tc.vulnerability) + assert.Equal(t, tc.expectedOutput, actualOutput) + }) + } +} + +func TestSimplifiedOutput_IsFrogbotResultComment(t *testing.T) { + testCases := []struct { + name string + comment string + expected bool + }{ + { + name: "Starts with No Vulnerability Banner", + comment: "**👍 Frogbot scanned this pull request and found that it did not add vulnerable dependencies.** \n", + expected: true, + }, + { + name: "Starts with Vulnerabilities Banner", + comment: "**🚨 Frogbot scanned this pull request and found the below:**\n", + expected: true, + }, + { + name: "Does not start with Banner", + comment: "This is a random comment.", + expected: false, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + smo := &SimplifiedOutput{} + actual := smo.IsFrogbotResultComment(tc.comment) + assert.Equal(t, tc.expected, actual) + }) + } +} + +func TestSimplifiedOutput_VulnerabilitiesContent(t *testing.T) { + // Create a new instance of StandardOutput + so := &SimplifiedOutput{} + + // Create some sample vulnerabilitiesRows for testing + vulnerabilitiesRows := []formats.VulnerabilityOrViolationRow{ + { + ImpactedDependencyName: "Dependency1", + FixedVersions: []string{"2.2.3"}, + Cves: []formats.CveRow{{Id: "CVE-2023-1234"}}, + ImpactedDependencyVersion: "1.0.0", + }, + { + ImpactedDependencyName: "Dependency2", + FixedVersions: []string{"2.2.3"}, + Cves: []formats.CveRow{{Id: "CVE-2023-1234"}}, + ImpactedDependencyVersion: "2.0.0", + }, + } + + // Set the expected content string based on the sample data + expectedContent := fmt.Sprintf(` +--- +## 📦 Vulnerable Dependencies +--- + +### ✍️ Summary + +%s %s + +--- +### 👇 Details +--- + + +#### %s %s + +%s + + +#### %s %s + +%s + +`, + so.VulnerabilitiesTableHeader(), + getVulnerabilitiesTableContent(vulnerabilitiesRows, so), + vulnerabilitiesRows[0].ImpactedDependencyName, + vulnerabilitiesRows[0].ImpactedDependencyVersion, + createVulnerabilityDescription(&vulnerabilitiesRows[0], so.VcsProvider()), + vulnerabilitiesRows[1].ImpactedDependencyName, + vulnerabilitiesRows[1].ImpactedDependencyVersion, + createVulnerabilityDescription(&vulnerabilitiesRows[1], so.VcsProvider()), + ) + + actualContent := so.VulnerabilitiesContent(vulnerabilitiesRows) + assert.Equal(t, expectedContent, actualContent, "Content mismatch") +} + +func TestSimplifiedOutput_ContentWithContextualAnalysis(t *testing.T) { + // Create a new instance of StandardOutput + so := &SimplifiedOutput{entitledForJas: true, vcsProvider: vcsutils.BitbucketServer} + + // Create some sample vulnerabilitiesRows for testing + vulnerabilitiesRows := []formats.VulnerabilityOrViolationRow{ + { + ImpactedDependencyName: "Dependency1", + ImpactedDependencyVersion: "1.0.0", + FixedVersions: []string{"2.2.3"}, + Cves: []formats.CveRow{{Id: "CVE-2023-1234"}}, + Applicable: "Applicable", + }, + { + ImpactedDependencyName: "Dependency2", + ImpactedDependencyVersion: "2.0.0", + FixedVersions: []string{"2.2.3"}, + Cves: []formats.CveRow{{Id: "CVE-2023-1234"}}, + Applicable: "Not Applicable", + }, + } + + // Set the expected content string based on the sample data + expectedContent := fmt.Sprintf(` +--- +## 📦 Vulnerable Dependencies +--- + +### ✍️ Summary + +%s %s + +--- +### 👇 Details +--- + + +#### %s %s + +%s + + +#### %s %s + +%s + +`, + so.VulnerabilitiesTableHeader(), + getVulnerabilitiesTableContent(vulnerabilitiesRows, so), + vulnerabilitiesRows[0].ImpactedDependencyName, + vulnerabilitiesRows[0].ImpactedDependencyVersion, + createVulnerabilityDescription(&vulnerabilitiesRows[0], so.VcsProvider()), + vulnerabilitiesRows[1].ImpactedDependencyName, + vulnerabilitiesRows[1].ImpactedDependencyVersion, + createVulnerabilityDescription(&vulnerabilitiesRows[1], so.VcsProvider()), + ) + + actualContent := so.VulnerabilitiesContent(vulnerabilitiesRows) + assert.Equal(t, expectedContent, actualContent, "Content mismatch") + assert.Contains(t, actualContent, "CONTEXTUAL ANALYSIS") + assert.Contains(t, actualContent, "**APPLICABLE**") + assert.Contains(t, actualContent, "**NOT APPLICABLE**") +} + +func TestSimplifiedOutput_IacContent(t *testing.T) { + testCases := []struct { + name string + iacRows []formats.IacSecretsRow + expectedOutput string + }{ + { + name: "Empty IAC rows", + iacRows: []formats.IacSecretsRow{}, + expectedOutput: "", + }, + { + name: "Single IAC row", + iacRows: []formats.IacSecretsRow{ + { + Severity: "High", + SeverityNumValue: 3, + File: "applicable/req_sw_terraform_azure_redis_auth.tf", + LineColumn: "11:1", + Text: "Missing Periodic patching was detected", + Type: "azure_redis_patch", + }, + }, + expectedOutput: "\n## 🛠️ Infrastructure as Code \n\n\n| SEVERITY | FILE | LINE:COLUMN | FINDING | SCANNER |\n| :---------------------: | :----------------------------------: | :-----------------------------------: | :---------------------------------: | :---------------------------------: | \n| High | applicable/req_sw_terraform_azure_redis_auth.tf | 11:1 | Missing Periodic patching was detected | azure_redis_patch |\n\n", + }, + { + name: "Multiple IAC rows", + iacRows: []formats.IacSecretsRow{ + { + Severity: "High", + SeverityNumValue: 3, + File: "applicable/req_sw_terraform_azure_redis_patch.tf", + LineColumn: "11:1", + Text: "Missing redis firewall definition or start_ip=0.0.0.0 was detected, Missing redis firewall definition or start_ip=0.0.0.0 was detected", + Type: "azure_redis_no_public", + }, + { + Severity: "High", + SeverityNumValue: 3, + File: "applicable/req_sw_terraform_azure_redis_auth.tf", + LineColumn: "11:1", + Text: "Missing Periodic patching was detected", + Type: "azure_redis_patch", + }, + }, + expectedOutput: "\n## 🛠️ Infrastructure as Code \n\n\n| SEVERITY | FILE | LINE:COLUMN | FINDING | SCANNER |\n| :---------------------: | :----------------------------------: | :-----------------------------------: | :---------------------------------: | :---------------------------------: | \n| High | applicable/req_sw_terraform_azure_redis_patch.tf | 11:1 | Missing redis firewall definition or start_ip=0.0.0.0 was detected, Missing redis firewall definition or start_ip=0.0.0.0 was detected | azure_redis_no_public |\n| High | applicable/req_sw_terraform_azure_redis_auth.tf | 11:1 | Missing Periodic patching was detected | azure_redis_patch |\n\n", + }, + } + + writer := &SimplifiedOutput{} + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + output := writer.IacContent(tc.iacRows) + assert.Equal(t, tc.expectedOutput, output) + }) + } +} + +func TestSimplifiedOutput_GetIacTableContent(t *testing.T) { + testCases := []struct { + name string + iacRows []formats.IacSecretsRow + expectedOutput string + }{ + { + name: "Empty IAC rows", + iacRows: []formats.IacSecretsRow{}, + expectedOutput: "", + }, + { + name: "Single IAC row", + iacRows: []formats.IacSecretsRow{ + { + Severity: "Medium", + SeverityNumValue: 2, + File: "file1", + LineColumn: "1:10", + Text: "Public access to MySQL was detected", + Type: "azure_mysql_no_public", + }, + }, + expectedOutput: "\n| Medium | file1 | 1:10 | Public access to MySQL was detected | azure_mysql_no_public |", + }, + { + name: "Multiple IAC rows", + iacRows: []formats.IacSecretsRow{ + { + Severity: "High", + SeverityNumValue: 3, + File: "file1", + LineColumn: "1:10", + Text: "Public access to MySQL was detected", + Type: "azure_mysql_no_public", + }, + { + Severity: "Medium", + SeverityNumValue: 2, + File: "file2", + LineColumn: "2:5", + Text: "Public access to MySQL was detected", + Type: "azure_mysql_no_public", + }, + }, + expectedOutput: "\n| High | file1 | 1:10 | Public access to MySQL was detected | azure_mysql_no_public |\n| Medium | file2 | 2:5 | Public access to MySQL was detected | azure_mysql_no_public |", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + output := getIacTableContent(tc.iacRows, &SimplifiedOutput{}) + assert.Equal(t, tc.expectedOutput, output) + }) + } +} diff --git a/commands/utils/standardoutput_test.go b/commands/utils/standardoutput_test.go index 530d18d6e..026af436f 100644 --- a/commands/utils/standardoutput_test.go +++ b/commands/utils/standardoutput_test.go @@ -1,354 +1,353 @@ package utils -// -//import ( -// "fmt" -// "github.com/jfrog/froggit-go/vcsutils" -// "github.com/jfrog/jfrog-cli-core/v2/xray/formats" -// "github.com/stretchr/testify/assert" -// "testing" -//) -// -//func TestStandardOutput_TableRow(t *testing.T) { -// var tests = []struct { -// vulnerability formats.VulnerabilityOrViolationRow -// expected string -// name string -// }{ -// { -// name: "Single CVE and no direct dependencies", -// vulnerability: formats.VulnerabilityOrViolationRow{ -// Severity: "Critical", -// ImpactedDependencyName: "testdep", -// ImpactedDependencyVersion: "1.0.0", -// FixedVersions: []string{"2.0.0"}, -// Cves: []formats.CveRow{{Id: "CVE-2022-1234"}}, -// }, -// expected: "| ![](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/applicableCriticalSeverity.png)
Critical | | testdep:1.0.0 | 2.0.0 |", -// }, -// { -// name: "Multiple CVEs and no direct dependencies", -// vulnerability: formats.VulnerabilityOrViolationRow{ -// Severity: "High", -// ImpactedDependencyName: "testdep2", -// ImpactedDependencyVersion: "1.0.0", -// FixedVersions: []string{"2.0.0", "3.0.0"}, -// Cves: []formats.CveRow{ -// {Id: "CVE-2022-1234"}, -// {Id: "CVE-2022-5678"}, -// }, -// }, -// expected: "| ![](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/applicableHighSeverity.png)
High | | testdep2:1.0.0 | 2.0.0
3.0.0 |", -// }, -// { -// name: "Single CVE and direct dependencies", -// vulnerability: formats.VulnerabilityOrViolationRow{ -// Severity: "Low", -// ImpactedDependencyName: "testdep3", -// ImpactedDependencyVersion: "1.0.0", -// FixedVersions: []string{"2.0.0"}, -// Cves: []formats.CveRow{{Id: "CVE-2022-1234"}}, -// Components: []formats.ComponentRow{ -// {Name: "dep1", Version: "1.0.0"}, -// {Name: "dep2", Version: "2.0.0"}, -// }, -// }, -// expected: "| ![](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/applicableLowSeverity.png)
Low | dep1:1.0.0
dep2:2.0.0 | testdep3:1.0.0 | 2.0.0 |", -// }, -// { -// name: "Multiple CVEs and direct dependencies", -// vulnerability: formats.VulnerabilityOrViolationRow{ -// Severity: "High", -// Cves: []formats.CveRow{ -// {Id: "CVE-1"}, -// {Id: "CVE-2"}, -// }, -// Components: []formats.ComponentRow{ -// {Name: "dep1", Version: "1.0.0"}, -// {Name: "dep2", Version: "2.0.0"}, -// }, -// ImpactedDependencyName: "impacted", -// ImpactedDependencyVersion: "3.0.0", -// FixedVersions: []string{"4.0.0", "5.0.0"}, -// }, -// expected: "| ![](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/applicableHighSeverity.png)
High | dep1:1.0.0
dep2:2.0.0 | impacted:3.0.0 | 4.0.0
5.0.0 |", -// }, -// } -// -// for _, tc := range tests { -// t.Run(tc.name, func(t *testing.T) { -// smo := &StandardOutput{} -// actualOutput := smo.VulnerabilitiesTableRow(tc.vulnerability) -// assert.Equal(t, tc.expected, actualOutput) -// }) -// } -//} -// -//func TestStandardOutput_IsFrogbotResultComment(t *testing.T) { -// so := &StandardOutput{} -// -// tests := []struct { -// comment string -// expected bool -// }{ -// { -// comment: "This is a comment with the " + GetIconTag(NoVulnerabilityPrBannerSource) + " icon", -// expected: true, -// }, -// { -// comment: "This is a comment with the " + GetIconTag(VulnerabilitiesPrBannerSource) + " icon", -// expected: true, -// }, -// { -// comment: "This is a comment with no icons", -// expected: false, -// }, -// } -// -// for _, test := range tests { -// result := so.IsFrogbotResultComment(test.comment) -// assert.Equal(t, test.expected, result) -// } -//} -// -//func TestStandardOutput_VulnerabilitiesContent(t *testing.T) { -// // Create a new instance of StandardOutput -// so := &StandardOutput{} -// -// // Create some sample vulnerabilitiesRows for testing -// vulnerabilitiesRows := []formats.VulnerabilityOrViolationRow{ -// { -// ImpactedDependencyName: "Dependency1", -// ImpactedDependencyVersion: "1.0.0", -// }, -// { -// ImpactedDependencyName: "Dependency2", -// ImpactedDependencyVersion: "2.0.0", -// }, -// } -// -// // Set the expected content string based on the sample data -// expectedContent := fmt.Sprintf(` -//## 📦 Vulnerable Dependencies -// -//### ✍️ Summary -// -//
-// -//%s %s -// -//
-// -//## 👇 Details -// -// -//
-// %s %s -//
-//%s -// -//
-// -// -//
-// %s %s -//
-//%s -// -//
-// -//`, -// so.VulnerabilitiesTableHeader(), -// getVulnerabilitiesTableContent(vulnerabilitiesRows, so), -// vulnerabilitiesRows[0].ImpactedDependencyName, -// vulnerabilitiesRows[0].ImpactedDependencyVersion, -// createVulnerabilityDescription(&vulnerabilitiesRows[0], so.VcsProvider()), -// vulnerabilitiesRows[1].ImpactedDependencyName, -// vulnerabilitiesRows[1].ImpactedDependencyVersion, -// createVulnerabilityDescription(&vulnerabilitiesRows[1], so.VcsProvider()), -// ) -// -// actualContent := so.VulnerabilitiesContent(vulnerabilitiesRows) -// assert.Equal(t, expectedContent, actualContent, "Content mismatch") -//} -// -//func TestStandardOutput_ContentWithContextualAnalysis(t *testing.T) { -// // Create a new instance of StandardOutput -// so := &StandardOutput{entitledForJas: true, vcsProvider: vcsutils.GitHub} -// -// // Create some sample vulnerabilitiesRows for testing -// vulnerabilitiesRows := []formats.VulnerabilityOrViolationRow{ -// { -// ImpactedDependencyName: "Dependency1", -// ImpactedDependencyVersion: "1.0.0", -// Applicable: "Applicable", -// }, -// { -// ImpactedDependencyName: "Dependency2", -// ImpactedDependencyVersion: "2.0.0", -// Applicable: "Not Applicable", -// }, -// } -// -// // Set the expected content string based on the sample data -// expectedContent := fmt.Sprintf(` -//## 📦 Vulnerable Dependencies -// -//### ✍️ Summary -// -//
-// -//%s %s -// -//
-// -//## 👇 Details -// -// -//
-// %s %s -//
-//%s -// -//
-// -// -//
-// %s %s -//
-//%s -// -//
-// -//`, -// so.VulnerabilitiesTableHeader(), -// getVulnerabilitiesTableContent(vulnerabilitiesRows, so), -// vulnerabilitiesRows[0].ImpactedDependencyName, -// vulnerabilitiesRows[0].ImpactedDependencyVersion, -// createVulnerabilityDescription(&vulnerabilitiesRows[0], so.VcsProvider()), -// vulnerabilitiesRows[1].ImpactedDependencyName, -// vulnerabilitiesRows[1].ImpactedDependencyVersion, -// createVulnerabilityDescription(&vulnerabilitiesRows[1], so.VcsProvider()), -// ) -// -// actualContent := so.VulnerabilitiesContent(vulnerabilitiesRows) -// assert.Equal(t, expectedContent, actualContent, "Content mismatch") -// assert.Contains(t, actualContent, "CONTEXTUAL ANALYSIS") -// assert.Contains(t, actualContent, "Applicable") -// assert.Contains(t, actualContent, "Not Applicable") -//} -// -//func TestStandardOutput_IacContent(t *testing.T) { -// testCases := []struct { -// name string -// iacRows []formats.IacSecretsRow -// expectedOutput string -// }{ -// { -// name: "Empty IAC rows", -// iacRows: []formats.IacSecretsRow{}, -// expectedOutput: "", -// }, -// { -// name: "Single IAC row", -// iacRows: []formats.IacSecretsRow{ -// { -// Severity: "High", -// SeverityNumValue: 3, -// File: "applicable/req_sw_terraform_azure_redis_auth.tf", -// LineColumn: "11:1", -// Text: "Missing Periodic patching was detected", -// Type: "azure_redis_patch", -// }, -// }, -// expectedOutput: "\n## 🛠️ Infrastructure as Code \n\n
\n\n\n| SEVERITY | FILE | LINE:COLUMN | FINDING | SCANNER |\n| :---------------------: | :----------------------------------: | :-----------------------------------: | :---------------------------------: | :---------------------------------: | \n| ![](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/applicableHighSeverity.png)
High | applicable/req_sw_terraform_azure_redis_auth.tf | 11:1 | Missing Periodic patching was detected | azure_redis_patch |\n\n
\n\n", -// }, -// { -// name: "Multiple IAC rows", -// iacRows: []formats.IacSecretsRow{ -// { -// Severity: "High", -// SeverityNumValue: 3, -// File: "applicable/req_sw_terraform_azure_redis_patch.tf", -// LineColumn: "11:1", -// Text: "Missing redis firewall definition or start_ip=0.0.0.0 was detected, Missing redis firewall definition or start_ip=0.0.0.0 was detected", -// Type: "azure_redis_no_public", -// }, -// { -// Severity: "High", -// SeverityNumValue: 3, -// File: "applicable/req_sw_terraform_azure_redis_auth.tf", -// LineColumn: "11:1", -// Text: "Missing Periodic patching was detected", -// Type: "azure_redis_patch", -// }, -// }, -// expectedOutput: "\n## 🛠️ Infrastructure as Code \n\n
\n\n\n| SEVERITY | FILE | LINE:COLUMN | FINDING | SCANNER |\n| :---------------------: | :----------------------------------: | :-----------------------------------: | :---------------------------------: | :---------------------------------: | \n| ![](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/applicableHighSeverity.png)
High | applicable/req_sw_terraform_azure_redis_patch.tf | 11:1 | Missing redis firewall definition or start_ip=0.0.0.0 was detected, Missing redis firewall definition or start_ip=0.0.0.0 was detected | azure_redis_no_public |\n| ![](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/applicableHighSeverity.png)
High | applicable/req_sw_terraform_azure_redis_auth.tf | 11:1 | Missing Periodic patching was detected | azure_redis_patch |\n\n
\n\n", -// }, -// } -// -// writer := &StandardOutput{} -// for _, tc := range testCases { -// t.Run(tc.name, func(t *testing.T) { -// output := writer.IacContent(tc.iacRows) -// assert.Equal(t, tc.expectedOutput, output) -// }) -// } -//} -// -//func TestStandardOutput_GetIacTableContent(t *testing.T) { -// testCases := []struct { -// name string -// iacRows []formats.IacSecretsRow -// expectedOutput string -// }{ -// { -// name: "Empty IAC rows", -// iacRows: []formats.IacSecretsRow{}, -// expectedOutput: "", -// }, -// { -// name: "Single IAC row", -// iacRows: []formats.IacSecretsRow{ -// { -// Severity: "Medium", -// SeverityNumValue: 2, -// File: "file1", -// LineColumn: "1:10", -// Text: "Public access to MySQL was detected", -// Type: "azure_mysql_no_public", -// }, -// }, -// expectedOutput: "\n| ![](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/applicableMediumSeverity.png)
Medium | file1 | 1:10 | Public access to MySQL was detected | azure_mysql_no_public |", -// }, -// { -// name: "Multiple IAC rows", -// iacRows: []formats.IacSecretsRow{ -// { -// Severity: "High", -// SeverityNumValue: 3, -// File: "file1", -// LineColumn: "1:10", -// Text: "Public access to MySQL was detected", -// Type: "azure_mysql_no_public", -// }, -// { -// Severity: "Medium", -// SeverityNumValue: 2, -// File: "file2", -// LineColumn: "2:5", -// Text: "Public access to MySQL was detected", -// Type: "azure_mysql_no_public", -// }, -// }, -// expectedOutput: "\n| ![](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/applicableHighSeverity.png)
High | file1 | 1:10 | Public access to MySQL was detected | azure_mysql_no_public |\n| ![](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/applicableMediumSeverity.png)
Medium | file2 | 2:5 | Public access to MySQL was detected | azure_mysql_no_public |", -// }, -// } -// -// for _, tc := range testCases { -// t.Run(tc.name, func(t *testing.T) { -// output := getIacTableContent(tc.iacRows, &StandardOutput{}) -// assert.Equal(t, tc.expectedOutput, output) -// }) -// } -//} +import ( + "fmt" + "github.com/jfrog/froggit-go/vcsutils" + "github.com/jfrog/jfrog-cli-core/v2/xray/formats" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestStandardOutput_TableRow(t *testing.T) { + var tests = []struct { + vulnerability formats.VulnerabilityOrViolationRow + expected string + name string + }{ + { + name: "Single CVE and no direct dependencies", + vulnerability: formats.VulnerabilityOrViolationRow{ + Severity: "Critical", + ImpactedDependencyName: "testdep", + ImpactedDependencyVersion: "1.0.0", + FixedVersions: []string{"2.0.0"}, + Cves: []formats.CveRow{{Id: "CVE-2022-1234"}}, + }, + expected: "| ![](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/applicableCriticalSeverity.png)
Critical | | testdep:1.0.0 | 2.0.0 |", + }, + { + name: "Multiple CVEs and no direct dependencies", + vulnerability: formats.VulnerabilityOrViolationRow{ + Severity: "High", + ImpactedDependencyName: "testdep2", + ImpactedDependencyVersion: "1.0.0", + FixedVersions: []string{"2.0.0", "3.0.0"}, + Cves: []formats.CveRow{ + {Id: "CVE-2022-1234"}, + {Id: "CVE-2022-5678"}, + }, + }, + expected: "| ![](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/applicableHighSeverity.png)
High | | testdep2:1.0.0 | 2.0.0
3.0.0 |", + }, + { + name: "Single CVE and direct dependencies", + vulnerability: formats.VulnerabilityOrViolationRow{ + Severity: "Low", + ImpactedDependencyName: "testdep3", + ImpactedDependencyVersion: "1.0.0", + FixedVersions: []string{"2.0.0"}, + Cves: []formats.CveRow{{Id: "CVE-2022-1234"}}, + Components: []formats.ComponentRow{ + {Name: "dep1", Version: "1.0.0"}, + {Name: "dep2", Version: "2.0.0"}, + }, + }, + expected: "| ![](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/applicableLowSeverity.png)
Low | dep1:1.0.0
dep2:2.0.0 | testdep3:1.0.0 | 2.0.0 |", + }, + { + name: "Multiple CVEs and direct dependencies", + vulnerability: formats.VulnerabilityOrViolationRow{ + Severity: "High", + Cves: []formats.CveRow{ + {Id: "CVE-1"}, + {Id: "CVE-2"}, + }, + Components: []formats.ComponentRow{ + {Name: "dep1", Version: "1.0.0"}, + {Name: "dep2", Version: "2.0.0"}, + }, + ImpactedDependencyName: "impacted", + ImpactedDependencyVersion: "3.0.0", + FixedVersions: []string{"4.0.0", "5.0.0"}, + }, + expected: "| ![](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/applicableHighSeverity.png)
High | dep1:1.0.0
dep2:2.0.0 | impacted:3.0.0 | 4.0.0
5.0.0 |", + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + smo := &StandardOutput{} + actualOutput := smo.VulnerabilitiesTableRow(tc.vulnerability) + assert.Equal(t, tc.expected, actualOutput) + }) + } +} + +func TestStandardOutput_IsFrogbotResultComment(t *testing.T) { + so := &StandardOutput{} + + tests := []struct { + comment string + expected bool + }{ + { + comment: "This is a comment with the " + GetIconTag(NoVulnerabilityPrBannerSource) + " icon", + expected: true, + }, + { + comment: "This is a comment with the " + GetIconTag(VulnerabilitiesPrBannerSource) + " icon", + expected: true, + }, + { + comment: "This is a comment with no icons", + expected: false, + }, + } + + for _, test := range tests { + result := so.IsFrogbotResultComment(test.comment) + assert.Equal(t, test.expected, result) + } +} + +func TestStandardOutput_VulnerabilitiesContent(t *testing.T) { + // Create a new instance of StandardOutput + so := &StandardOutput{} + + // Create some sample vulnerabilitiesRows for testing + vulnerabilitiesRows := []formats.VulnerabilityOrViolationRow{ + { + ImpactedDependencyName: "Dependency1", + ImpactedDependencyVersion: "1.0.0", + }, + { + ImpactedDependencyName: "Dependency2", + ImpactedDependencyVersion: "2.0.0", + }, + } + + // Set the expected content string based on the sample data + expectedContent := fmt.Sprintf(` +## 📦 Vulnerable Dependencies + +### ✍️ Summary + +
+ +%s %s + +
+ +## 👇 Details + + +
+ %s %s +
+%s + +
+ + +
+ %s %s +
+%s + +
+ +`, + so.VulnerabilitiesTableHeader(), + getVulnerabilitiesTableContent(vulnerabilitiesRows, so), + vulnerabilitiesRows[0].ImpactedDependencyName, + vulnerabilitiesRows[0].ImpactedDependencyVersion, + createVulnerabilityDescription(&vulnerabilitiesRows[0], so.VcsProvider()), + vulnerabilitiesRows[1].ImpactedDependencyName, + vulnerabilitiesRows[1].ImpactedDependencyVersion, + createVulnerabilityDescription(&vulnerabilitiesRows[1], so.VcsProvider()), + ) + + actualContent := so.VulnerabilitiesContent(vulnerabilitiesRows) + assert.Equal(t, expectedContent, actualContent, "Content mismatch") +} + +func TestStandardOutput_ContentWithContextualAnalysis(t *testing.T) { + // Create a new instance of StandardOutput + so := &StandardOutput{entitledForJas: true, vcsProvider: vcsutils.GitHub} + + // Create some sample vulnerabilitiesRows for testing + vulnerabilitiesRows := []formats.VulnerabilityOrViolationRow{ + { + ImpactedDependencyName: "Dependency1", + ImpactedDependencyVersion: "1.0.0", + Applicable: "Applicable", + }, + { + ImpactedDependencyName: "Dependency2", + ImpactedDependencyVersion: "2.0.0", + Applicable: "Not Applicable", + }, + } + + // Set the expected content string based on the sample data + expectedContent := fmt.Sprintf(` +## 📦 Vulnerable Dependencies + +### ✍️ Summary + +
+ +%s %s + +
+ +## 👇 Details + + +
+ %s %s +
+%s + +
+ + +
+ %s %s +
+%s + +
+ +`, + so.VulnerabilitiesTableHeader(), + getVulnerabilitiesTableContent(vulnerabilitiesRows, so), + vulnerabilitiesRows[0].ImpactedDependencyName, + vulnerabilitiesRows[0].ImpactedDependencyVersion, + createVulnerabilityDescription(&vulnerabilitiesRows[0], so.VcsProvider()), + vulnerabilitiesRows[1].ImpactedDependencyName, + vulnerabilitiesRows[1].ImpactedDependencyVersion, + createVulnerabilityDescription(&vulnerabilitiesRows[1], so.VcsProvider()), + ) + + actualContent := so.VulnerabilitiesContent(vulnerabilitiesRows) + assert.Equal(t, expectedContent, actualContent, "Content mismatch") + assert.Contains(t, actualContent, "CONTEXTUAL ANALYSIS") + assert.Contains(t, actualContent, "Applicable") + assert.Contains(t, actualContent, "Not Applicable") +} + +func TestStandardOutput_IacContent(t *testing.T) { + testCases := []struct { + name string + iacRows []formats.IacSecretsRow + expectedOutput string + }{ + { + name: "Empty IAC rows", + iacRows: []formats.IacSecretsRow{}, + expectedOutput: "", + }, + { + name: "Single IAC row", + iacRows: []formats.IacSecretsRow{ + { + Severity: "High", + SeverityNumValue: 3, + File: "applicable/req_sw_terraform_azure_redis_auth.tf", + LineColumn: "11:1", + Text: "Missing Periodic patching was detected", + Type: "azure_redis_patch", + }, + }, + expectedOutput: "\n## 🛠️ Infrastructure as Code \n\n
\n\n\n| SEVERITY | FILE | LINE:COLUMN | FINDING | SCANNER |\n| :---------------------: | :----------------------------------: | :-----------------------------------: | :---------------------------------: | :---------------------------------: | \n| ![](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/applicableHighSeverity.png)
High | applicable/req_sw_terraform_azure_redis_auth.tf | 11:1 | Missing Periodic patching was detected | azure_redis_patch |\n\n
\n\n", + }, + { + name: "Multiple IAC rows", + iacRows: []formats.IacSecretsRow{ + { + Severity: "High", + SeverityNumValue: 3, + File: "applicable/req_sw_terraform_azure_redis_patch.tf", + LineColumn: "11:1", + Text: "Missing redis firewall definition or start_ip=0.0.0.0 was detected, Missing redis firewall definition or start_ip=0.0.0.0 was detected", + Type: "azure_redis_no_public", + }, + { + Severity: "High", + SeverityNumValue: 3, + File: "applicable/req_sw_terraform_azure_redis_auth.tf", + LineColumn: "11:1", + Text: "Missing Periodic patching was detected", + Type: "azure_redis_patch", + }, + }, + expectedOutput: "\n## 🛠️ Infrastructure as Code \n\n
\n\n\n| SEVERITY | FILE | LINE:COLUMN | FINDING | SCANNER |\n| :---------------------: | :----------------------------------: | :-----------------------------------: | :---------------------------------: | :---------------------------------: | \n| ![](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/applicableHighSeverity.png)
High | applicable/req_sw_terraform_azure_redis_patch.tf | 11:1 | Missing redis firewall definition or start_ip=0.0.0.0 was detected, Missing redis firewall definition or start_ip=0.0.0.0 was detected | azure_redis_no_public |\n| ![](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/applicableHighSeverity.png)
High | applicable/req_sw_terraform_azure_redis_auth.tf | 11:1 | Missing Periodic patching was detected | azure_redis_patch |\n\n
\n\n", + }, + } + + writer := &StandardOutput{} + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + output := writer.IacContent(tc.iacRows) + assert.Equal(t, tc.expectedOutput, output) + }) + } +} + +func TestStandardOutput_GetIacTableContent(t *testing.T) { + testCases := []struct { + name string + iacRows []formats.IacSecretsRow + expectedOutput string + }{ + { + name: "Empty IAC rows", + iacRows: []formats.IacSecretsRow{}, + expectedOutput: "", + }, + { + name: "Single IAC row", + iacRows: []formats.IacSecretsRow{ + { + Severity: "Medium", + SeverityNumValue: 2, + File: "file1", + LineColumn: "1:10", + Text: "Public access to MySQL was detected", + Type: "azure_mysql_no_public", + }, + }, + expectedOutput: "\n| ![](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/applicableMediumSeverity.png)
Medium | file1 | 1:10 | Public access to MySQL was detected | azure_mysql_no_public |", + }, + { + name: "Multiple IAC rows", + iacRows: []formats.IacSecretsRow{ + { + Severity: "High", + SeverityNumValue: 3, + File: "file1", + LineColumn: "1:10", + Text: "Public access to MySQL was detected", + Type: "azure_mysql_no_public", + }, + { + Severity: "Medium", + SeverityNumValue: 2, + File: "file2", + LineColumn: "2:5", + Text: "Public access to MySQL was detected", + Type: "azure_mysql_no_public", + }, + }, + expectedOutput: "\n| ![](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/applicableHighSeverity.png)
High | file1 | 1:10 | Public access to MySQL was detected | azure_mysql_no_public |\n| ![](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/applicableMediumSeverity.png)
Medium | file2 | 2:5 | Public access to MySQL was detected | azure_mysql_no_public |", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + output := getIacTableContent(tc.iacRows, &StandardOutput{}) + assert.Equal(t, tc.expectedOutput, output) + }) + } +} diff --git a/commands/utils/utils_test.go b/commands/utils/utils_test.go index 7a424ca76..533720e94 100644 --- a/commands/utils/utils_test.go +++ b/commands/utils/utils_test.go @@ -1,227 +1,216 @@ package utils import ( + "bytes" + "fmt" "github.com/jfrog/jfrog-cli-core/v2/utils/config" + "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" + "github.com/jfrog/jfrog-cli-core/v2/xray/formats" "github.com/stretchr/testify/assert" + "net/http" + "net/http/httptest" "os" + "path/filepath" "strings" "testing" ) -// import ( -// -// "bytes" -// "fmt" -// "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" -// "github.com/jfrog/jfrog-cli-core/v2/xray/formats" -// "net/http" -// "net/http/httptest" -// "os" -// "path/filepath" -// "strings" -// "testing" -// -// "github.com/jfrog/jfrog-cli-core/v2/utils/config" -// "github.com/stretchr/testify/assert" -// -// ) -// -// func TestChdir(t *testing.T) { -// originCwd, err := os.Getwd() -// assert.NoError(t, err) -// -// restoreDir, err := Chdir("..") -// assert.NoError(t, err) -// -// cwd, err := os.Getwd() -// assert.NoError(t, err) -// assert.Equal(t, filepath.Dir(originCwd), cwd) -// -// assert.NoError(t, restoreDir()) -// cwd, err = os.Getwd() -// assert.NoError(t, err) -// assert.Equal(t, originCwd, cwd) -// } -// -// func TestChdirErr(t *testing.T) { -// originCwd, err := os.Getwd() -// assert.NoError(t, err) -// -// _, err = Chdir("not-existed") -// assert.Error(t, err) -// -// cwd, err := os.Getwd() -// assert.NoError(t, err) -// assert.Equal(t, originCwd, cwd) -// } -// -// func TestReportUsage(t *testing.T) { -// const commandName = "test-command" -// server := httptest.NewServer(createUsageHandler(t, commandName)) -// defer server.Close() -// -// serverDetails := &config.ServerDetails{ArtifactoryUrl: server.URL + "/"} -// channel := make(chan error) -// go ReportUsage(commandName, serverDetails, channel) -// assert.NoError(t, <-channel) -// } -// -// func TestReportUsageError(t *testing.T) { -// channel := make(chan error) -// go ReportUsage("", &config.ServerDetails{}, channel) -// assert.NoError(t, <-channel) -// -// channel = make(chan error) -// go ReportUsage("", &config.ServerDetails{ArtifactoryUrl: "http://httpbin.org/status/404"}, channel) -// assert.Error(t, <-channel) -// } -// -// // Create HTTP handler to mock an Artifactory server suitable for report usage requests -// -// func createUsageHandler(t *testing.T, commandName string) http.HandlerFunc { -// return func(w http.ResponseWriter, r *http.Request) { -// if r.RequestURI == "/api/system/version" { -// w.WriteHeader(http.StatusOK) -// _, err := w.Write([]byte(`{"version":"6.9.0"}`)) -// assert.NoError(t, err) -// return -// } -// if r.RequestURI == "/api/system/usage" { -// // Check request -// buf := new(bytes.Buffer) -// _, err := buf.ReadFrom(r.Body) -// assert.NoError(t, err) -// assert.Equal(t, fmt.Sprintf(`{"productId":"%s","features":[{"featureId":"%s"}]}`, productId, commandName), buf.String()) -// -// // Send response OK -// w.WriteHeader(http.StatusOK) -// _, err = w.Write([]byte("{}")) -// assert.NoError(t, err) -// } -// } -// } -// -// func TestMd5Hash(t *testing.T) { -// tests := []struct { -// values []string -// expectedHash string -// }{ -// {[]string{"frogbot", "dev", "gopkg.in/yaml.v3", "3.0.0"}, -// "d61bde82dc594e5ccc5a042fe224bf7c"}, -// -// {[]string{"frogbot", "master", "gopkg.in/yaml.v3", "3.0.0"}, -// "41405528994061bd108e3bbd4c039a03"}, -// -// {[]string{"frogbot", "master", "gopkg.in/yaml.v3", "4.0.0"}, -// "54d9e69ea1cba0c009445ad94778c083"}, -// -// {[]string{"frogbot", "master", "go", "1.17"}, -// "cedc1e5462e504fc992318d24e343e48"}, -// -// {[]string{"frogbot", "master", "go", "17.1"}, -// "67c768266553d80deb21fe6e2e9ec652"}, -// -// {[]string{"frogbot", "frogbot-Go-golang.org/x/crypto-0.0.0-20220314234659-1baeb1ce4c0b", "golang.org/x/crypto", "0.0.0-20220314234659-1baeb1ce4c0b"}, -// "a7f1c0ffb51035f860521ce11ac38288"}, -// -// {[]string{"frogbot"}, -// "99990025ad24adf5d780bbed740a2868"}, -// -// {[]string{""}, -// "d41d8cd98f00b204e9800998ecf8427e"}, -// } -// -// for _, test := range tests { -// t.Run(test.expectedHash, func(t *testing.T) { -// hash, err := Md5Hash(test.values...) -// assert.NoError(t, err) -// assert.Equal(t, test.expectedHash, hash) -// }) -// } -// } -// -// func TestFixVersionsMapToMd5Hash(t *testing.T) { -// tests := []struct { -// fixVersionMap map[string]*VulnerabilityDetails -// expectedHash string -// }{ -// { -// fixVersionMap: map[string]*VulnerabilityDetails{ -// "pkg": {FixVersion: "1.2.3", VulnerabilityOrViolationRow: &formats.VulnerabilityOrViolationRow{Technology: coreutils.Npm}, IsDirectDependency: false}}, -// expectedHash: "0aa066970b613b114f8e21d11c74ff94", -// }, { -// fixVersionMap: map[string]*VulnerabilityDetails{ -// "pkg": {FixVersion: "5.2.3", VulnerabilityOrViolationRow: &formats.VulnerabilityOrViolationRow{Technology: coreutils.Go}, IsDirectDependency: false}, -// "pkg2": {FixVersion: "1.2.3", VulnerabilityOrViolationRow: &formats.VulnerabilityOrViolationRow{Technology: coreutils.Go}, IsDirectDependency: false}}, -// expectedHash: "a0d4119dfe5fc5186d6c2cf1497f8c7c", -// }, -// { -// // The Same map with different order should be the same hash. -// fixVersionMap: map[string]*VulnerabilityDetails{ -// "pkg2": {FixVersion: "1.2.3", VulnerabilityOrViolationRow: &formats.VulnerabilityOrViolationRow{Technology: coreutils.Go}, IsDirectDependency: false}, -// "pkg": {FixVersion: "5.2.3", VulnerabilityOrViolationRow: &formats.VulnerabilityOrViolationRow{Technology: coreutils.Go}, IsDirectDependency: false}}, -// expectedHash: "a0d4119dfe5fc5186d6c2cf1497f8c7c", -// }, { -// fixVersionMap: map[string]*VulnerabilityDetails{ -// "myNuget": {FixVersion: "0.2.33", VulnerabilityOrViolationRow: &formats.VulnerabilityOrViolationRow{Technology: coreutils.Nuget}, IsDirectDependency: false}}, -// expectedHash: "887ac2c931920c20956409702c0dfbc7", -// }, -// } -// for _, test := range tests { -// t.Run(test.expectedHash, func(t *testing.T) { -// hash, err := fixVersionsMapToMd5Hash(test.fixVersionMap) -// assert.NoError(t, err) -// assert.Equal(t, test.expectedHash, hash) -// }) -// } -// } -// -// func TestGetRelativeWd(t *testing.T) { -// fullPath := filepath.Join("a", "b", "c", "d", "e") -// baseWd := filepath.Join("a", "b", "c") -// assert.Equal(t, filepath.Join("d", "e"), GetRelativeWd(fullPath, baseWd)) -// -// baseWd = filepath.Join("a", "b", "c", "d", "e") -// assert.Equal(t, "", GetRelativeWd(fullPath, baseWd)) -// fullPath += string(os.PathSeparator) -// assert.Equal(t, "", GetRelativeWd(fullPath, baseWd)) -// } -// -// func TestIsDirectDependency(t *testing.T) { -// tests := []struct { -// impactPath [][]formats.ComponentRow -// expected bool -// expectedError bool -// }{ -// { -// impactPath: [][]formats.ComponentRow{{{Name: "jfrog:pack1", Version: "1.2.3"}, {Name: "jfrog:pack2", Version: "1.2.3"}}}, -// expected: true, -// expectedError: false, -// }, { -// impactPath: [][]formats.ComponentRow{{{Name: "jfrog:pack1", Version: "1.2.3"}, {Name: "jfrog:pack21", Version: "1.2.3"}, {Name: "jfrog:pack3", Version: "1.2.3"}}, {{Name: "jfrog:pack1", Version: "1.2.3"}, {Name: "jfrog:pack22", Version: "1.2.3"}, {Name: "jfrog:pack3", Version: "1.2.3"}}}, -// expected: false, -// expectedError: false, -// }, { -// impactPath: [][]formats.ComponentRow{}, -// expected: false, -// expectedError: true, -// }, -// } -// for _, test := range tests { -// t.Run("", func(t *testing.T) { -// isDirect, err := IsDirectDependency(test.impactPath) -// assert.Equal(t, test.expected, isDirect) -// if test.expectedError { -// assert.Error(t, err) -// } else { -// assert.NoError(t, err) -// } -// }) -// } -// } -// -// // Check connection details with JFrog instance. -// // Return a callback method that restores the credentials after the test is done. +func TestChdir(t *testing.T) { + originCwd, err := os.Getwd() + assert.NoError(t, err) + + restoreDir, err := Chdir("..") + assert.NoError(t, err) + + cwd, err := os.Getwd() + assert.NoError(t, err) + assert.Equal(t, filepath.Dir(originCwd), cwd) + + assert.NoError(t, restoreDir()) + cwd, err = os.Getwd() + assert.NoError(t, err) + assert.Equal(t, originCwd, cwd) +} + +func TestChdirErr(t *testing.T) { + originCwd, err := os.Getwd() + assert.NoError(t, err) + + _, err = Chdir("not-existed") + assert.Error(t, err) + + cwd, err := os.Getwd() + assert.NoError(t, err) + assert.Equal(t, originCwd, cwd) +} + +func TestReportUsage(t *testing.T) { + const commandName = "test-command" + server := httptest.NewServer(createUsageHandler(t, commandName)) + defer server.Close() + + serverDetails := &config.ServerDetails{ArtifactoryUrl: server.URL + "/"} + channel := make(chan error) + go ReportUsage(commandName, serverDetails, channel) + assert.NoError(t, <-channel) +} + +func TestReportUsageError(t *testing.T) { + channel := make(chan error) + go ReportUsage("", &config.ServerDetails{}, channel) + assert.NoError(t, <-channel) + + channel = make(chan error) + go ReportUsage("", &config.ServerDetails{ArtifactoryUrl: "http://httpbin.org/status/404"}, channel) + assert.Error(t, <-channel) +} + +// Create HTTP handler to mock an Artifactory server suitable for report usage requests + +func createUsageHandler(t *testing.T, commandName string) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + if r.RequestURI == "/api/system/version" { + w.WriteHeader(http.StatusOK) + _, err := w.Write([]byte(`{"version":"6.9.0"}`)) + assert.NoError(t, err) + return + } + if r.RequestURI == "/api/system/usage" { + // Check request + buf := new(bytes.Buffer) + _, err := buf.ReadFrom(r.Body) + assert.NoError(t, err) + assert.Equal(t, fmt.Sprintf(`{"productId":"%s","features":[{"featureId":"%s"}]}`, productId, commandName), buf.String()) + + // Send response OK + w.WriteHeader(http.StatusOK) + _, err = w.Write([]byte("{}")) + assert.NoError(t, err) + } + } +} + +func TestMd5Hash(t *testing.T) { + tests := []struct { + values []string + expectedHash string + }{ + {[]string{"frogbot", "dev", "gopkg.in/yaml.v3", "3.0.0"}, + "d61bde82dc594e5ccc5a042fe224bf7c"}, + + {[]string{"frogbot", "master", "gopkg.in/yaml.v3", "3.0.0"}, + "41405528994061bd108e3bbd4c039a03"}, + + {[]string{"frogbot", "master", "gopkg.in/yaml.v3", "4.0.0"}, + "54d9e69ea1cba0c009445ad94778c083"}, + + {[]string{"frogbot", "master", "go", "1.17"}, + "cedc1e5462e504fc992318d24e343e48"}, + + {[]string{"frogbot", "master", "go", "17.1"}, + "67c768266553d80deb21fe6e2e9ec652"}, + + {[]string{"frogbot", "frogbot-Go-golang.org/x/crypto-0.0.0-20220314234659-1baeb1ce4c0b", "golang.org/x/crypto", "0.0.0-20220314234659-1baeb1ce4c0b"}, + "a7f1c0ffb51035f860521ce11ac38288"}, + + {[]string{"frogbot"}, + "99990025ad24adf5d780bbed740a2868"}, + + {[]string{""}, + "d41d8cd98f00b204e9800998ecf8427e"}, + } + + for _, test := range tests { + t.Run(test.expectedHash, func(t *testing.T) { + hash, err := Md5Hash(test.values...) + assert.NoError(t, err) + assert.Equal(t, test.expectedHash, hash) + }) + } +} + +func TestFixVersionsMapToMd5Hash(t *testing.T) { + tests := []struct { + fixVersionMap map[string]*VulnerabilityDetails + expectedHash string + }{ + { + fixVersionMap: map[string]*VulnerabilityDetails{ + "pkg": {FixVersion: "1.2.3", VulnerabilityOrViolationRow: &formats.VulnerabilityOrViolationRow{Technology: coreutils.Npm}, IsDirectDependency: false}}, + expectedHash: "0aa066970b613b114f8e21d11c74ff94", + }, { + fixVersionMap: map[string]*VulnerabilityDetails{ + "pkg": {FixVersion: "5.2.3", VulnerabilityOrViolationRow: &formats.VulnerabilityOrViolationRow{Technology: coreutils.Go}, IsDirectDependency: false}, + "pkg2": {FixVersion: "1.2.3", VulnerabilityOrViolationRow: &formats.VulnerabilityOrViolationRow{Technology: coreutils.Go}, IsDirectDependency: false}}, + expectedHash: "a0d4119dfe5fc5186d6c2cf1497f8c7c", + }, + { + // The Same map with different order should be the same hash. + fixVersionMap: map[string]*VulnerabilityDetails{ + "pkg2": {FixVersion: "1.2.3", VulnerabilityOrViolationRow: &formats.VulnerabilityOrViolationRow{Technology: coreutils.Go}, IsDirectDependency: false}, + "pkg": {FixVersion: "5.2.3", VulnerabilityOrViolationRow: &formats.VulnerabilityOrViolationRow{Technology: coreutils.Go}, IsDirectDependency: false}}, + expectedHash: "a0d4119dfe5fc5186d6c2cf1497f8c7c", + }, { + fixVersionMap: map[string]*VulnerabilityDetails{ + "myNuget": {FixVersion: "0.2.33", VulnerabilityOrViolationRow: &formats.VulnerabilityOrViolationRow{Technology: coreutils.Nuget}, IsDirectDependency: false}}, + expectedHash: "887ac2c931920c20956409702c0dfbc7", + }, + } + for _, test := range tests { + t.Run(test.expectedHash, func(t *testing.T) { + hash, err := fixVersionsMapToMd5Hash(test.fixVersionMap) + assert.NoError(t, err) + assert.Equal(t, test.expectedHash, hash) + }) + } +} + +func TestGetRelativeWd(t *testing.T) { + fullPath := filepath.Join("a", "b", "c", "d", "e") + baseWd := filepath.Join("a", "b", "c") + assert.Equal(t, filepath.Join("d", "e"), GetRelativeWd(fullPath, baseWd)) + + baseWd = filepath.Join("a", "b", "c", "d", "e") + assert.Equal(t, "", GetRelativeWd(fullPath, baseWd)) + fullPath += string(os.PathSeparator) + assert.Equal(t, "", GetRelativeWd(fullPath, baseWd)) +} + +func TestIsDirectDependency(t *testing.T) { + tests := []struct { + impactPath [][]formats.ComponentRow + expected bool + expectedError bool + }{ + { + impactPath: [][]formats.ComponentRow{{{Name: "jfrog:pack1", Version: "1.2.3"}, {Name: "jfrog:pack2", Version: "1.2.3"}}}, + expected: true, + expectedError: false, + }, { + impactPath: [][]formats.ComponentRow{{{Name: "jfrog:pack1", Version: "1.2.3"}, {Name: "jfrog:pack21", Version: "1.2.3"}, {Name: "jfrog:pack3", Version: "1.2.3"}}, {{Name: "jfrog:pack1", Version: "1.2.3"}, {Name: "jfrog:pack22", Version: "1.2.3"}, {Name: "jfrog:pack3", Version: "1.2.3"}}}, + expected: false, + expectedError: false, + }, { + impactPath: [][]formats.ComponentRow{}, + expected: false, + expectedError: true, + }, + } + for _, test := range tests { + t.Run("", func(t *testing.T) { + isDirect, err := IsDirectDependency(test.impactPath) + assert.Equal(t, test.expected, isDirect) + if test.expectedError { + assert.Error(t, err) + } else { + assert.NoError(t, err) + } + }) + } +} + +// Check connection details with JFrog instance. +// Return a callback method that restores the credentials after the test is done. func verifyEnv(t *testing.T) (server config.ServerDetails, restoreFunc func()) { url := strings.TrimSuffix(os.Getenv(JFrogUrlEnv), "/") token := os.Getenv(JFrogTokenEnv) @@ -244,68 +233,67 @@ func verifyEnv(t *testing.T) (server config.ServerDetails, restoreFunc func()) { return } -// -//func TestValidatedBranchName(t *testing.T) { -// tests := []struct { -// branchName string -// expectedError bool -// errorMessage string -// }{ -// { -// branchName: "thi?s-is-my-test", -// expectedError: true, -// errorMessage: branchInvalidChars, -// }, { -// branchName: "thi^s-is-my-test", -// expectedError: true, -// errorMessage: branchInvalidChars, -// }, { -// branchName: "thi~s-is-my-test", -// expectedError: true, -// errorMessage: branchInvalidChars, -// }, { -// branchName: "this[]-is-my-test", -// expectedError: true, -// errorMessage: branchInvalidChars, -// }, { -// branchName: "this@-is-my-test", -// expectedError: true, -// errorMessage: branchInvalidChars, -// }, { -// branchName: "this is myt est ${BRANCH_NAME_HASH}", -// expectedError: false, -// errorMessage: "", -// }, { -// branchName: "(Feature)New branch ${BRANCH_NAME_HASH}", -// expectedError: false, -// errorMessage: "", -// }, { -// branchName: "(frogbot)(feature) ${BRANCH_NAME_HASH} my name", -// expectedError: false, -// errorMessage: "", -// }, { -// branchName: "-this_should_not_work_prefix", -// expectedError: true, -// errorMessage: branchInvalidPrefix, -// }, { -// branchName: "Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Nulla consequat massa quis enim. Donec pede justo, fringilla vel, aliquet nec, vulputate eget, arcu. In enim justo, rhoncus ut, imperdiet a, venenatis vitae, justo. Nullam dictum felis eu pede mollis pretium. Integer tincidunt. Cras dapibus. Vivamus elementum semper nisi. Aenean vulputate eleifend tellus. Aenean leo ligula, porttitor eu, consequat vitae, eleifend ac, enim. Aliquam lorem ante, dapibus in, viverra quis, feugiat a, tellus. Phasellus viverra nulla ut metus varius laoreet. Quisque rutrum. Aenean imperdiet. Etiam ultricies nisi vel augue. Curabitur ullamcorper ultricies nisi. Nam eget dui. Etiam rhoncus. Maecenas tempus, tellus eget condimentum rhoncus, sem quam semper libero, sit amet adipiscing sem neque sed ipsum. Nam quam nunc, blandit vel, luctus pulvinar, hendrerit id, lorem. Maecenas nec odio et ante tincidunt tempus. Donec vitae sapien ut libero venenatis faucibus. Nullam quis ante. Etiam sit amet orci eget eros faucibus tincidunt. Duis leo. Sed fringilla mauris sit amet nibh. Donec sodales sagittis magna. Sed consequat, leo eget bibendum sodales, augue velit cursus nunc, quis gravida magna mi a libero. Fusce vulputate eleifend sapien. Vestibulum purus quam, scelerisque ut, mollis sed, nonummy id, metus. Nullam accumsan lorem in dui. Cras ultricies mi eu turpis hendrerit fringilla. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; In ac dui quis mi consectetuer lacinia. Nam pretium turpis et", -// expectedError: true, -// errorMessage: branchInvalidLength, -// }, { -// branchName: "", -// expectedError: false, -// errorMessage: "", -// }, -// } -// for _, test := range tests { -// t.Run(test.branchName, func(t *testing.T) { -// err := validateBranchName(test.branchName) -// if test.expectedError { -// assert.Error(t, err) -// assert.Equal(t, err.Error(), test.errorMessage) -// } else { -// assert.NoError(t, err) -// } -// }) -// } -//} +func TestValidatedBranchName(t *testing.T) { + tests := []struct { + branchName string + expectedError bool + errorMessage string + }{ + { + branchName: "thi?s-is-my-test", + expectedError: true, + errorMessage: branchInvalidChars, + }, { + branchName: "thi^s-is-my-test", + expectedError: true, + errorMessage: branchInvalidChars, + }, { + branchName: "thi~s-is-my-test", + expectedError: true, + errorMessage: branchInvalidChars, + }, { + branchName: "this[]-is-my-test", + expectedError: true, + errorMessage: branchInvalidChars, + }, { + branchName: "this@-is-my-test", + expectedError: true, + errorMessage: branchInvalidChars, + }, { + branchName: "this is myt est ${BRANCH_NAME_HASH}", + expectedError: false, + errorMessage: "", + }, { + branchName: "(Feature)New branch ${BRANCH_NAME_HASH}", + expectedError: false, + errorMessage: "", + }, { + branchName: "(frogbot)(feature) ${BRANCH_NAME_HASH} my name", + expectedError: false, + errorMessage: "", + }, { + branchName: "-this_should_not_work_prefix", + expectedError: true, + errorMessage: branchInvalidPrefix, + }, { + branchName: "Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Nulla consequat massa quis enim. Donec pede justo, fringilla vel, aliquet nec, vulputate eget, arcu. In enim justo, rhoncus ut, imperdiet a, venenatis vitae, justo. Nullam dictum felis eu pede mollis pretium. Integer tincidunt. Cras dapibus. Vivamus elementum semper nisi. Aenean vulputate eleifend tellus. Aenean leo ligula, porttitor eu, consequat vitae, eleifend ac, enim. Aliquam lorem ante, dapibus in, viverra quis, feugiat a, tellus. Phasellus viverra nulla ut metus varius laoreet. Quisque rutrum. Aenean imperdiet. Etiam ultricies nisi vel augue. Curabitur ullamcorper ultricies nisi. Nam eget dui. Etiam rhoncus. Maecenas tempus, tellus eget condimentum rhoncus, sem quam semper libero, sit amet adipiscing sem neque sed ipsum. Nam quam nunc, blandit vel, luctus pulvinar, hendrerit id, lorem. Maecenas nec odio et ante tincidunt tempus. Donec vitae sapien ut libero venenatis faucibus. Nullam quis ante. Etiam sit amet orci eget eros faucibus tincidunt. Duis leo. Sed fringilla mauris sit amet nibh. Donec sodales sagittis magna. Sed consequat, leo eget bibendum sodales, augue velit cursus nunc, quis gravida magna mi a libero. Fusce vulputate eleifend sapien. Vestibulum purus quam, scelerisque ut, mollis sed, nonummy id, metus. Nullam accumsan lorem in dui. Cras ultricies mi eu turpis hendrerit fringilla. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; In ac dui quis mi consectetuer lacinia. Nam pretium turpis et", + expectedError: true, + errorMessage: branchInvalidLength, + }, { + branchName: "", + expectedError: false, + errorMessage: "", + }, + } + for _, test := range tests { + t.Run(test.branchName, func(t *testing.T) { + err := validateBranchName(test.branchName) + if test.expectedError { + assert.Error(t, err) + assert.Equal(t, err.Error(), test.errorMessage) + } else { + assert.NoError(t, err) + } + }) + } +}