From fd8b279f17af43692dd67b0047ac51edb0e4de8c Mon Sep 17 00:00:00 2001 From: Assaf Attias <49212512+attiasas@users.noreply.github.com> Date: Wed, 20 Sep 2023 16:08:27 +0300 Subject: [PATCH 1/2] Show findings in Sast (#970) --- xray/utils/resultstable.go | 1 + 1 file changed, 1 insertion(+) diff --git a/xray/utils/resultstable.go b/xray/utils/resultstable.go index ee04b677c..27fc58ccd 100644 --- a/xray/utils/resultstable.go +++ b/xray/utils/resultstable.go @@ -420,6 +420,7 @@ func prepareSast(sasts []*sarif.Run, isTable bool) []formats.SourceCodeRow { formats.SourceCodeRow{ SeverityDetails: formats.SeverityDetails{Severity: currSeverity.printableTitle(isTable), SeverityNumValue: currSeverity.NumValue()}, ScannerDescription: scannerDescription, + Finding: GetResultMsgText(sastResult), Location: formats.Location{ File: GetRelativeLocationFileName(location, sastRun.Invocations), StartLine: GetLocationStartLine(location), From 0414290c7deba247c86d9d969d5c35997177400c Mon Sep 17 00:00:00 2001 From: Assaf Attias <49212512+attiasas@users.noreply.github.com> Date: Thu, 21 Sep 2023 14:34:52 +0300 Subject: [PATCH 2/2] Add tests for audit (#965) --- xray/commands/audit/jas/common_test.go | 90 +++ xray/commands/audit/jas/sast/sastscanner.go | 34 +- .../audit/jas/sast/sastscanner_test.go | 92 ++- .../audit/jas/secrets/secretsscanner.go | 3 +- xray/utils/analyzermanager_test.go | 117 ---- xray/utils/resultstable_test.go | 370 +++++++++- xray/utils/sarifutils.go | 108 +-- xray/utils/sarifutils_test.go | 656 +++++++++++++++++- 8 files changed, 1250 insertions(+), 220 deletions(-) create mode 100644 xray/commands/audit/jas/common_test.go diff --git a/xray/commands/audit/jas/common_test.go b/xray/commands/audit/jas/common_test.go new file mode 100644 index 000000000..5466039d8 --- /dev/null +++ b/xray/commands/audit/jas/common_test.go @@ -0,0 +1,90 @@ +package jas + +import ( + "testing" + + "github.com/jfrog/jfrog-cli-core/v2/xray/utils" + "github.com/owenrumney/go-sarif/v2/sarif" + "github.com/stretchr/testify/assert" +) + +func TestExcludeSuppressResults(t *testing.T) { + tests := []struct { + name string + sarifResults []*sarif.Result + expectedOutput []*sarif.Result + }{ + { + sarifResults: []*sarif.Result{ + utils.CreateResultWithOneLocation("", 0, 0, 0, 0, "snippet1", "ruleId1", "level1"), + utils.CreateResultWithOneLocation("", 0, 0, 0, 0, "snippet2", "ruleId2", "level2"), + }, + expectedOutput: []*sarif.Result{ + utils.CreateResultWithOneLocation("", 0, 0, 0, 0, "snippet1", "ruleId1", "level1"), + utils.CreateResultWithOneLocation("", 0, 0, 0, 0, "snippet2", "ruleId2", "level2"), + }, + }, + { + sarifResults: []*sarif.Result{ + utils.CreateResultWithOneLocation("", 0, 0, 0, 0, "snippet1", "ruleId1", "level1").WithSuppression([]*sarif.Suppression{sarif.NewSuppression("")}), + utils.CreateResultWithOneLocation("", 0, 0, 0, 0, "snippet2", "ruleId2", "level2"), + }, + expectedOutput: []*sarif.Result{ + utils.CreateResultWithOneLocation("", 0, 0, 0, 0, "snippet2", "ruleId2", "level2"), + }, + }, + { + sarifResults: []*sarif.Result{ + utils.CreateResultWithOneLocation("", 0, 0, 0, 0, "snippet1", "ruleId1", "level1").WithSuppression([]*sarif.Suppression{sarif.NewSuppression("")}), + utils.CreateResultWithOneLocation("", 0, 0, 0, 0, "snippet2", "ruleId2", "level2").WithSuppression([]*sarif.Suppression{sarif.NewSuppression("")}), + }, + expectedOutput: []*sarif.Result{}, + }, + } + + for _, test := range tests { + assert.Equal(t, test.expectedOutput, excludeSuppressResults(test.sarifResults)) + } +} + +func TestAddScoreToRunRules(t *testing.T) { + + tests := []struct { + name string + sarifRun *sarif.Run + expectedOutput []*sarif.ReportingDescriptor + }{ + { + sarifRun: utils.CreateRunWithDummyResults( + utils.CreateResultWithOneLocation("file1", 0, 0, 0, 0, "snippet", "rule1", "info"), + utils.CreateResultWithOneLocation("file2", 0, 0, 0, 0, "snippet", "rule1", "info"), + utils.CreateResultWithOneLocation("file", 0, 0, 0, 0, "snippet", "rule2", "warning"), + ), + expectedOutput: []*sarif.ReportingDescriptor{ + sarif.NewRule("rule1").WithProperties(sarif.Properties{"security-severity": "6.9"}), + sarif.NewRule("rule2").WithProperties(sarif.Properties{"security-severity": "6.9"}), + }, + }, + { + sarifRun: utils.CreateRunWithDummyResults( + utils.CreateResultWithOneLocation("file", 0, 0, 0, 0, "snippet", "rule1", "none"), + utils.CreateResultWithOneLocation("file", 0, 0, 0, 0, "snippet", "rule2", "note"), + utils.CreateResultWithOneLocation("file", 0, 0, 0, 0, "snippet", "rule3", "info"), + utils.CreateResultWithOneLocation("file", 0, 0, 0, 0, "snippet", "rule4", "warning"), + utils.CreateResultWithOneLocation("file", 0, 0, 0, 0, "snippet", "rule5", "error"), + ), + expectedOutput: []*sarif.ReportingDescriptor{ + sarif.NewRule("rule1").WithProperties(sarif.Properties{"security-severity": "0.0"}), + sarif.NewRule("rule2").WithProperties(sarif.Properties{"security-severity": "3.9"}), + sarif.NewRule("rule3").WithProperties(sarif.Properties{"security-severity": "6.9"}), + sarif.NewRule("rule4").WithProperties(sarif.Properties{"security-severity": "6.9"}), + sarif.NewRule("rule5").WithProperties(sarif.Properties{"security-severity": "8.9"}), + }, + }, + } + + for _, test := range tests { + addScoreToRunRules(test.sarifRun) + assert.Equal(t, test.expectedOutput, test.sarifRun.Tool.Driver.Rules) + } +} diff --git a/xray/commands/audit/jas/sast/sastscanner.go b/xray/commands/audit/jas/sast/sastscanner.go index 35211396f..d4402cd68 100644 --- a/xray/commands/audit/jas/sast/sastscanner.go +++ b/xray/commands/audit/jas/sast/sastscanner.go @@ -1,6 +1,8 @@ package sast import ( + "fmt" + "github.com/jfrog/jfrog-cli-core/v2/xray/commands/audit/jas" "github.com/jfrog/jfrog-cli-core/v2/xray/utils" "github.com/jfrog/jfrog-client-go/utils/log" @@ -47,7 +49,8 @@ func (ssm *SastScanManager) Run(wd string) (err error) { if err != nil { return } - ssm.sastScannerResults = append(ssm.sastScannerResults, groupResultsByLocation(workingDirRuns)...) + groupResultsByLocation(workingDirRuns) + ssm.sastScannerResults = append(ssm.sastScannerResults, workingDirRuns...) return } @@ -58,7 +61,7 @@ func (ssm *SastScanManager) runAnalyzerManager(wd string) error { // In the Sast scanner, there can be multiple results with the same location. // The only difference is that their CodeFlow values are different. // We combine those under the same result location value -func groupResultsByLocation(sarifRuns []*sarif.Run) []*sarif.Run { +func groupResultsByLocation(sarifRuns []*sarif.Run) { for _, sastRun := range sarifRuns { locationToResult := map[string]*sarif.Result{} for _, sastResult := range sastRun.Results { @@ -71,25 +74,28 @@ func groupResultsByLocation(sarifRuns []*sarif.Run) []*sarif.Run { } sastRun.Results = maps.Values(locationToResult) } - return sarifRuns } -// In Sast there is only one location for each result -func getResultFileName(result *sarif.Result) string { - if len(result.Locations) > 0 { - return utils.GetLocationFileName(result.Locations[0]) +func getResultLocationStr(result *sarif.Result) string { + if len(result.Locations) == 0 { + return "" } - return "" + location := result.Locations[0] + return fmt.Sprintf("%s%d%d%d%d", + utils.GetLocationFileName(location), + utils.GetLocationStartLine(location), + utils.GetLocationStartColumn(location), + utils.GetLocationEndLine(location), + utils.GetLocationEndColumn(location)) } -// In Sast there is only one location for each result -func getResultStartLocationInFile(result *sarif.Result) string { - if len(result.Locations) > 0 { - return utils.GetStartLocationInFile(result.Locations[0]) +func getResultRuleId(result *sarif.Result) string { + if result.RuleID == nil { + return "" } - return "" + return *result.RuleID } func getResultId(result *sarif.Result) string { - return getResultFileName(result) + getResultStartLocationInFile(result) + utils.GetResultSeverity(result) + utils.GetResultMsgText(result) + return getResultRuleId(result) + utils.GetResultSeverity(result) + utils.GetResultMsgText(result) + getResultLocationStr(result) } diff --git a/xray/commands/audit/jas/sast/sastscanner_test.go b/xray/commands/audit/jas/sast/sastscanner_test.go index 66c423403..6ed1980f3 100644 --- a/xray/commands/audit/jas/sast/sastscanner_test.go +++ b/xray/commands/audit/jas/sast/sastscanner_test.go @@ -5,6 +5,8 @@ import ( "testing" "github.com/jfrog/jfrog-cli-core/v2/xray/commands/audit/jas" + "github.com/jfrog/jfrog-cli-core/v2/xray/utils" + "github.com/owenrumney/go-sarif/v2/sarif" "github.com/stretchr/testify/assert" ) @@ -40,7 +42,7 @@ func TestSastParseResults_EmptyResults(t *testing.T) { if assert.NoError(t, err) && assert.NotNil(t, sastScanManager.sastScannerResults) { assert.Len(t, sastScanManager.sastScannerResults, 1) assert.Empty(t, sastScanManager.sastScannerResults[0].Results) - sastScanManager.sastScannerResults = groupResultsByLocation(sastScanManager.sastScannerResults) + groupResultsByLocation(sastScanManager.sastScannerResults) assert.Len(t, sastScanManager.sastScannerResults, 1) assert.Empty(t, sastScanManager.sastScannerResults[0].Results) } @@ -61,8 +63,94 @@ func TestSastParseResults_ResultsContainIacViolations(t *testing.T) { if assert.NoError(t, err) && assert.NotNil(t, sastScanManager.sastScannerResults) { assert.Len(t, sastScanManager.sastScannerResults, 1) assert.NotEmpty(t, sastScanManager.sastScannerResults[0].Results) - sastScanManager.sastScannerResults = groupResultsByLocation(sastScanManager.sastScannerResults) + groupResultsByLocation(sastScanManager.sastScannerResults) // File has 4 results, 2 of them at the same location different codeFlow assert.Len(t, sastScanManager.sastScannerResults[0].Results, 3) } } + +func TestGroupResultsByLocation(t *testing.T) { + tests := []struct { + run *sarif.Run + expectedOutput *sarif.Run + }{ + { + run: utils.CreateRunWithDummyResults(), + expectedOutput: utils.CreateRunWithDummyResults(), + }, + { + // No similar groups at all + run: utils.CreateRunWithDummyResults( + utils.CreateResultWithOneLocation("file", 1, 2, 3, 4, "snippet", "rule1", "info"), + utils.CreateResultWithOneLocation("file", 1, 2, 3, 4, "snippet", "rule1", "note"), + utils.CreateResultWithOneLocation("file", 5, 6, 7, 8, "snippet", "rule1", "info"), + utils.CreateResultWithOneLocation("file2", 1, 2, 3, 4, "snippet", "rule1", "info").WithCodeFlows([]*sarif.CodeFlow{ + utils.CreateCodeFlow(utils.CreateThreadFlow( + utils.CreateLocation("other", 0, 0, 0, 0, "other-snippet"), + utils.CreateLocation("file2", 1, 2, 3, 4, "snippet"), + )), + }), + utils.CreateResultWithOneLocation("file2", 1, 2, 3, 4, "snippet", "rule2", "info").WithCodeFlows([]*sarif.CodeFlow{ + utils.CreateCodeFlow(utils.CreateThreadFlow( + utils.CreateLocation("other2", 1, 1, 1, 1, "other-snippet2"), + utils.CreateLocation("file2", 1, 2, 3, 4, "snippet"), + )), + }), + ), + expectedOutput: utils.CreateRunWithDummyResults( + utils.CreateResultWithOneLocation("file", 1, 2, 3, 4, "snippet", "rule1", "info"), + utils.CreateResultWithOneLocation("file", 1, 2, 3, 4, "snippet", "rule1", "note"), + utils.CreateResultWithOneLocation("file", 5, 6, 7, 8, "snippet", "rule1", "info"), + utils.CreateResultWithOneLocation("file2", 1, 2, 3, 4, "snippet", "rule1", "info").WithCodeFlows([]*sarif.CodeFlow{ + utils.CreateCodeFlow(utils.CreateThreadFlow( + utils.CreateLocation("other", 0, 0, 0, 0, "other-snippet"), + utils.CreateLocation("file2", 1, 2, 3, 4, "snippet"), + )), + }), + utils.CreateResultWithOneLocation("file2", 1, 2, 3, 4, "snippet", "rule2", "info").WithCodeFlows([]*sarif.CodeFlow{ + utils.CreateCodeFlow(utils.CreateThreadFlow( + utils.CreateLocation("other2", 1, 1, 1, 1, "other-snippet2"), + utils.CreateLocation("file2", 1, 2, 3, 4, "snippet"), + )), + }), + ), + }, + { + // With similar groups + run: utils.CreateRunWithDummyResults( + utils.CreateResultWithOneLocation("file", 1, 2, 3, 4, "snippet", "rule1", "info").WithCodeFlows([]*sarif.CodeFlow{ + utils.CreateCodeFlow(utils.CreateThreadFlow( + utils.CreateLocation("other", 0, 0, 0, 0, "other-snippet"), + utils.CreateLocation("file", 1, 2, 3, 4, "snippet"), + )), + }), + utils.CreateResultWithOneLocation("file", 1, 2, 3, 4, "snippet", "rule1", "info").WithCodeFlows([]*sarif.CodeFlow{ + utils.CreateCodeFlow(utils.CreateThreadFlow( + utils.CreateLocation("other2", 1, 1, 1, 1, "other-snippet"), + utils.CreateLocation("file", 1, 2, 3, 4, "snippet"), + )), + }), + utils.CreateResultWithOneLocation("file", 5, 6, 7, 8, "snippet", "rule1", "info"), + utils.CreateResultWithOneLocation("file", 1, 2, 3, 4, "snippet", "rule1", "info"), + ), + expectedOutput: utils.CreateRunWithDummyResults( + utils.CreateResultWithOneLocation("file", 1, 2, 3, 4, "snippet", "rule1", "info").WithCodeFlows([]*sarif.CodeFlow{ + utils.CreateCodeFlow(utils.CreateThreadFlow( + utils.CreateLocation("other", 0, 0, 0, 0, "other-snippet"), + utils.CreateLocation("file", 1, 2, 3, 4, "snippet"), + )), + utils.CreateCodeFlow(utils.CreateThreadFlow( + utils.CreateLocation("other2", 1, 1, 1, 1, "other-snippet"), + utils.CreateLocation("file", 1, 2, 3, 4, "snippet"), + )), + }), + utils.CreateResultWithOneLocation("file", 5, 6, 7, 8, "snippet", "rule1", "info"), + ), + }, + } + + for _, test := range tests { + groupResultsByLocation([]*sarif.Run{test.run}) + assert.ElementsMatch(t, test.expectedOutput.Results, test.run.Results) + } +} diff --git a/xray/commands/audit/jas/secrets/secretsscanner.go b/xray/commands/audit/jas/secrets/secretsscanner.go index cf5df05f8..6f0b985ff 100644 --- a/xray/commands/audit/jas/secrets/secretsscanner.go +++ b/xray/commands/audit/jas/secrets/secretsscanner.go @@ -105,8 +105,7 @@ func processSecretScanRuns(sarifRuns []*sarif.Run) []*sarif.Run { // Hide discovered secrets value for _, secretResult := range secretRun.Results { for _, location := range secretResult.Locations { - secret := utils.GetLocationSnippetPointer(location) - utils.SetLocationSnippet(location, maskSecret(*secret)) + utils.SetLocationSnippet(location, maskSecret(utils.GetLocationSnippet(location))) } } } diff --git a/xray/utils/analyzermanager_test.go b/xray/utils/analyzermanager_test.go index c8637a7e2..d493bfb4b 100644 --- a/xray/utils/analyzermanager_test.go +++ b/xray/utils/analyzermanager_test.go @@ -5,126 +5,9 @@ import ( "fmt" "testing" - "github.com/owenrumney/go-sarif/v2/sarif" "github.com/stretchr/testify/assert" ) -func TestGetResultFileName(t *testing.T) { - fileNameValue := "fileNameValue" - tests := []struct { - result *sarif.Result - expectedOutput string - }{ - {result: &sarif.Result{ - Locations: []*sarif.Location{ - {PhysicalLocation: &sarif.PhysicalLocation{ArtifactLocation: &sarif.ArtifactLocation{URI: nil}}}, - }}, - expectedOutput: ""}, - {result: &sarif.Result{ - Locations: []*sarif.Location{ - {PhysicalLocation: &sarif.PhysicalLocation{ArtifactLocation: &sarif.ArtifactLocation{URI: &fileNameValue}}}, - }}, - expectedOutput: fileNameValue}, - } - - for _, test := range tests { - assert.Equal(t, test.expectedOutput, GetLocationFileName(test.result.Locations[0])) - } - -} - -func TestGetResultLocationInFile(t *testing.T) { - startLine := 19 - startColumn := 25 - - tests := []struct { - result *sarif.Result - expectedOutput string - }{ - {result: &sarif.Result{Locations: []*sarif.Location{ - {PhysicalLocation: &sarif.PhysicalLocation{Region: &sarif.Region{ - StartLine: &startLine, - StartColumn: &startColumn, - }}}}}, - expectedOutput: "19:25"}, - {result: &sarif.Result{Locations: []*sarif.Location{ - {PhysicalLocation: &sarif.PhysicalLocation{Region: &sarif.Region{ - StartLine: nil, - StartColumn: &startColumn, - }}}}}, - expectedOutput: ""}, - {result: &sarif.Result{Locations: []*sarif.Location{ - {PhysicalLocation: &sarif.PhysicalLocation{Region: &sarif.Region{ - StartLine: &startLine, - StartColumn: nil, - }}}}}, - expectedOutput: ""}, - {result: &sarif.Result{Locations: []*sarif.Location{ - {PhysicalLocation: &sarif.PhysicalLocation{Region: &sarif.Region{ - StartLine: nil, - StartColumn: nil, - }}}}}, - expectedOutput: ""}, - } - - for _, test := range tests { - assert.Equal(t, test.expectedOutput, GetStartLocationInFile(test.result.Locations[0])) - } -} - -func TestExtractRelativePath(t *testing.T) { - tests := []struct { - secretPath string - projectPath string - expectedResult string - }{ - {secretPath: "file:///Users/user/Desktop/secrets_scanner/tests/req.nodejs/file.js", - projectPath: "Users/user/Desktop/secrets_scanner/", expectedResult: "tests/req.nodejs/file.js"}, - {secretPath: "invalidSecretPath", - projectPath: "Users/user/Desktop/secrets_scanner/", expectedResult: "invalidSecretPath"}, - {secretPath: "", - projectPath: "Users/user/Desktop/secrets_scanner/", expectedResult: ""}, - {secretPath: "file:///Users/user/Desktop/secrets_scanner/tests/req.nodejs/file.js", - projectPath: "invalidProjectPath", expectedResult: "Users/user/Desktop/secrets_scanner/tests/req.nodejs/file.js"}, - {secretPath: "file:///private/Users/user/Desktop/secrets_scanner/tests/req.nodejs/file.js", - projectPath: "invalidProjectPath", expectedResult: "Users/user/Desktop/secrets_scanner/tests/req.nodejs/file.js"}, - } - - for _, test := range tests { - assert.Equal(t, test.expectedResult, ExtractRelativePath(test.secretPath, test.projectPath)) - } -} - -func TestGetResultSeverity(t *testing.T) { - levelValueHigh := string(errorLevel) - levelValueMedium := string(warningLevel) - levelValueMedium2 := string(infoLevel) - levelValueLow := string(noteLevel) - levelValueUnknown := string(noneLevel) - - tests := []struct { - result *sarif.Result - expectedSeverity string - }{ - {result: &sarif.Result{}, - expectedSeverity: "Medium"}, - {result: &sarif.Result{Level: &levelValueHigh}, - expectedSeverity: "High"}, - {result: &sarif.Result{Level: &levelValueMedium}, - expectedSeverity: "Medium"}, - {result: &sarif.Result{Level: &levelValueMedium2}, - expectedSeverity: "Medium"}, - {result: &sarif.Result{Level: &levelValueLow}, - expectedSeverity: "Low"}, - {result: &sarif.Result{Level: &levelValueUnknown}, - expectedSeverity: "Unknown"}, - } - - for _, test := range tests { - assert.Equal(t, test.expectedSeverity, GetResultSeverity(test.result)) - } -} - func TestScanTypeErrorMsg(t *testing.T) { tests := []struct { scanner JasScanType diff --git a/xray/utils/resultstable_test.go b/xray/utils/resultstable_test.go index 66d72bfb3..3606c1b8a 100644 --- a/xray/utils/resultstable_test.go +++ b/xray/utils/resultstable_test.go @@ -439,9 +439,9 @@ func TestGetApplicableCveValue(t *testing.T) { { scanResults: &ExtendedScanResults{ ApplicabilityScanResults: []*sarif.Run{ - getRunWithDummyResults( - getDummyResultWithOneLocation("fileName1", 0, 1, "snippet1", "applic_testCve1", "info"), - getDummyPassingResult("applic_testCve2"), + CreateRunWithDummyResults( + CreateResultWithOneLocation("fileName1", 0, 1, 0, 0, "snippet1", "applic_testCve1", "info"), + CreateDummyPassingResult("applic_testCve2"), ), }, EntitledForJas: true, @@ -453,9 +453,9 @@ func TestGetApplicableCveValue(t *testing.T) { { scanResults: &ExtendedScanResults{ ApplicabilityScanResults: []*sarif.Run{ - getRunWithDummyResults( - getDummyPassingResult("applic_testCve1"), - getDummyResultWithOneLocation("fileName2", 1, 0, "snippet2", "applic_testCve2", "warning"), + CreateRunWithDummyResults( + CreateDummyPassingResult("applic_testCve1"), + CreateResultWithOneLocation("fileName2", 1, 0, 0, 0, "snippet2", "applic_testCve2", "warning"), ), }, EntitledForJas: true, @@ -467,9 +467,9 @@ func TestGetApplicableCveValue(t *testing.T) { { scanResults: &ExtendedScanResults{ ApplicabilityScanResults: []*sarif.Run{ - getRunWithDummyResults( - getDummyPassingResult("applic_testCve1"), - getDummyResultWithOneLocation("fileName3", 0, 1, "snippet3", "applic_testCve2", "info"), + CreateRunWithDummyResults( + CreateDummyPassingResult("applic_testCve1"), + CreateResultWithOneLocation("fileName3", 0, 1, 0, 0, "snippet3", "applic_testCve2", "info"), ), }, EntitledForJas: true, @@ -481,9 +481,9 @@ func TestGetApplicableCveValue(t *testing.T) { { scanResults: &ExtendedScanResults{ ApplicabilityScanResults: []*sarif.Run{ - getRunWithDummyResults( - getDummyPassingResult("applic_testCve1"), - getDummyPassingResult("applic_testCve2"), + CreateRunWithDummyResults( + CreateDummyPassingResult("applic_testCve1"), + CreateDummyPassingResult("applic_testCve2"), ), }, EntitledForJas: true, @@ -495,9 +495,9 @@ func TestGetApplicableCveValue(t *testing.T) { { scanResults: &ExtendedScanResults{ ApplicabilityScanResults: []*sarif.Run{ - getRunWithDummyResults( - getDummyPassingResult("applic_testCve1"), - getDummyResultWithOneLocation("fileName4", 1, 0, "snippet", "applic_testCve2", "warning"), + CreateRunWithDummyResults( + CreateDummyPassingResult("applic_testCve1"), + CreateResultWithOneLocation("fileName4", 1, 0, 0, 0, "snippet", "applic_testCve2", "warning"), ), }, EntitledForJas: true, @@ -509,7 +509,7 @@ func TestGetApplicableCveValue(t *testing.T) { { scanResults: &ExtendedScanResults{ ApplicabilityScanResults: []*sarif.Run{ - getRunWithDummyResults(getDummyPassingResult("applic_testCve1")), + CreateRunWithDummyResults(CreateDummyPassingResult("applic_testCve1")), }, EntitledForJas: true}, cves: []services.Cve{{Id: "testCve1"}, {Id: "testCve2"}}, @@ -706,6 +706,344 @@ func TestShouldDisqualifyEvidence(t *testing.T) { } } +func TestPrepareIac(t *testing.T) { + testCases := []struct { + name string + input []*sarif.Run + expectedOutput []formats.SourceCodeRow + }{ + { + name: "No Iac run", + input: []*sarif.Run{}, + expectedOutput: []formats.SourceCodeRow{}, + }, + { + name: "Prepare Iac run - no results", + input: []*sarif.Run{ + CreateRunWithDummyResults(), + CreateRunWithDummyResults(), + CreateRunWithDummyResults(), + }, + expectedOutput: []formats.SourceCodeRow{}, + }, + { + name: "Prepare Iac run - with results", + input: []*sarif.Run{ + CreateRunWithDummyResults(), + CreateRunWithDummyResults( + CreateResultWithLocations("iac finding", "rule1", "info", + CreateLocation("file://wd/file", 1, 2, 3, 4, "snippet"), + CreateLocation("file://wd/file2", 5, 6, 7, 8, "other-snippet"), + ), + ).WithInvocations([]*sarif.Invocation{ + sarif.NewInvocation().WithWorkingDirectory(sarif.NewSimpleArtifactLocation("wd")), + }), + CreateRunWithDummyResults( + CreateResultWithLocations("other iac finding", "rule2", "error", + CreateLocation("file://wd2/file3", 1, 2, 3, 4, "snippet"), + ), + ).WithInvocations([]*sarif.Invocation{ + sarif.NewInvocation().WithWorkingDirectory(sarif.NewSimpleArtifactLocation("wd2")), + }), + }, + expectedOutput: []formats.SourceCodeRow{ + { + SeverityDetails: formats.SeverityDetails{ + Severity: "High", + SeverityNumValue: 13, + }, + Finding: "other iac finding", + Location: formats.Location{ + File: "file3", + StartLine: 1, + StartColumn: 2, + EndLine: 3, + EndColumn: 4, + Snippet: "snippet", + }, + }, + { + SeverityDetails: formats.SeverityDetails{ + Severity: "Medium", + SeverityNumValue: 11, + }, + Finding: "iac finding", + Location: formats.Location{ + File: "file", + StartLine: 1, + StartColumn: 2, + EndLine: 3, + EndColumn: 4, + Snippet: "snippet", + }, + }, + { + SeverityDetails: formats.SeverityDetails{ + Severity: "Medium", + SeverityNumValue: 11, + }, + Finding: "iac finding", + Location: formats.Location{ + File: "file2", + StartLine: 5, + StartColumn: 6, + EndLine: 7, + EndColumn: 8, + Snippet: "other-snippet", + }, + }, + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + assert.ElementsMatch(t, tc.expectedOutput, prepareIacs(tc.input, false)) + }) + } +} + +func TestPrepareSecrets(t *testing.T) { + testCases := []struct { + name string + input []*sarif.Run + expectedOutput []formats.SourceCodeRow + }{ + { + name: "No Secret run", + input: []*sarif.Run{}, + expectedOutput: []formats.SourceCodeRow{}, + }, + { + name: "Prepare Secret run - no results", + input: []*sarif.Run{ + CreateRunWithDummyResults(), + CreateRunWithDummyResults(), + CreateRunWithDummyResults(), + }, + expectedOutput: []formats.SourceCodeRow{}, + }, + { + name: "Prepare Secret run - with results", + input: []*sarif.Run{ + CreateRunWithDummyResults(), + CreateRunWithDummyResults( + CreateResultWithLocations("secret finding", "rule1", "info", + CreateLocation("file://wd/file", 1, 2, 3, 4, "some-secret-snippet"), + CreateLocation("file://wd/file2", 5, 6, 7, 8, "other-secret-snippet"), + ), + ).WithInvocations([]*sarif.Invocation{ + sarif.NewInvocation().WithWorkingDirectory(sarif.NewSimpleArtifactLocation("wd")), + }), + CreateRunWithDummyResults( + CreateResultWithLocations("other secret finding", "rule2", "note", + CreateLocation("file://wd2/file3", 1, 2, 3, 4, "some-secret-snippet"), + ), + ).WithInvocations([]*sarif.Invocation{ + sarif.NewInvocation().WithWorkingDirectory(sarif.NewSimpleArtifactLocation("wd2")), + }), + }, + expectedOutput: []formats.SourceCodeRow{ + { + SeverityDetails: formats.SeverityDetails{ + Severity: "Low", + SeverityNumValue: 9, + }, + Finding: "other secret finding", + Location: formats.Location{ + File: "file3", + StartLine: 1, + StartColumn: 2, + EndLine: 3, + EndColumn: 4, + Snippet: "some-secret-snippet", + }, + }, + { + SeverityDetails: formats.SeverityDetails{ + Severity: "Medium", + SeverityNumValue: 11, + }, + Finding: "secret finding", + Location: formats.Location{ + File: "file", + StartLine: 1, + StartColumn: 2, + EndLine: 3, + EndColumn: 4, + Snippet: "some-secret-snippet", + }, + }, + { + SeverityDetails: formats.SeverityDetails{ + Severity: "Medium", + SeverityNumValue: 11, + }, + Finding: "secret finding", + Location: formats.Location{ + File: "file2", + StartLine: 5, + StartColumn: 6, + EndLine: 7, + EndColumn: 8, + Snippet: "other-secret-snippet", + }, + }, + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + assert.ElementsMatch(t, tc.expectedOutput, prepareSecrets(tc.input, false)) + }) + } +} + +func TestPrepareSast(t *testing.T) { + testCases := []struct { + name string + input []*sarif.Run + expectedOutput []formats.SourceCodeRow + }{ + { + name: "No Sast run", + input: []*sarif.Run{}, + expectedOutput: []formats.SourceCodeRow{}, + }, + { + name: "Prepare Sast run - no results", + input: []*sarif.Run{ + CreateRunWithDummyResults(), + CreateRunWithDummyResults(), + CreateRunWithDummyResults(), + }, + expectedOutput: []formats.SourceCodeRow{}, + }, + { + name: "Prepare Sast run - with results", + input: []*sarif.Run{ + CreateRunWithDummyResults(), + CreateRunWithDummyResults( + CreateResultWithLocations("sast finding", "rule1", "info", + CreateLocation("file://wd/file", 1, 2, 3, 4, "snippet"), + CreateLocation("file://wd/file2", 5, 6, 7, 8, "other-snippet"), + ).WithCodeFlows([]*sarif.CodeFlow{ + CreateCodeFlow(CreateThreadFlow( + CreateLocation("file://wd/file2", 0, 2, 0, 2, "snippetA"), + CreateLocation("file://wd/file", 1, 2, 3, 4, "snippet"), + )), + CreateCodeFlow(CreateThreadFlow( + CreateLocation("file://wd/file4", 1, 0, 1, 8, "snippetB"), + CreateLocation("file://wd/file", 1, 2, 3, 4, "snippet"), + )), + }), + ).WithInvocations([]*sarif.Invocation{ + sarif.NewInvocation().WithWorkingDirectory(sarif.NewSimpleArtifactLocation("wd")), + }), + CreateRunWithDummyResults( + CreateResultWithLocations("other sast finding", "rule2", "error", + CreateLocation("file://wd2/file3", 1, 2, 3, 4, "snippet"), + ), + ).WithInvocations([]*sarif.Invocation{ + sarif.NewInvocation().WithWorkingDirectory(sarif.NewSimpleArtifactLocation("wd2")), + }), + }, + expectedOutput: []formats.SourceCodeRow{ + { + SeverityDetails: formats.SeverityDetails{ + Severity: "High", + SeverityNumValue: 13, + }, + Finding: "other sast finding", + Location: formats.Location{ + File: "file3", + StartLine: 1, + StartColumn: 2, + EndLine: 3, + EndColumn: 4, + Snippet: "snippet", + }, + }, + { + SeverityDetails: formats.SeverityDetails{ + Severity: "Medium", + SeverityNumValue: 11, + }, + Finding: "sast finding", + Location: formats.Location{ + File: "file", + StartLine: 1, + StartColumn: 2, + EndLine: 3, + EndColumn: 4, + Snippet: "snippet", + }, + CodeFlow: [][]formats.Location{ + { + { + File: "file2", + StartLine: 0, + StartColumn: 2, + EndLine: 0, + EndColumn: 2, + Snippet: "snippetA", + }, + { + File: "file", + StartLine: 1, + StartColumn: 2, + EndLine: 3, + EndColumn: 4, + Snippet: "snippet", + }, + }, + { + { + File: "file4", + StartLine: 1, + StartColumn: 0, + EndLine: 1, + EndColumn: 8, + Snippet: "snippetB", + }, + { + File: "file", + StartLine: 1, + StartColumn: 2, + EndLine: 3, + EndColumn: 4, + Snippet: "snippet", + }, + }, + }, + }, + { + SeverityDetails: formats.SeverityDetails{ + Severity: "Medium", + SeverityNumValue: 11, + }, + Finding: "sast finding", + Location: formats.Location{ + File: "file2", + StartLine: 5, + StartColumn: 6, + EndLine: 7, + EndColumn: 8, + Snippet: "other-snippet", + }, + }, + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + assert.ElementsMatch(t, tc.expectedOutput, prepareSast(tc.input, false)) + }) + } +} + func newBoolPtr(v bool) *bool { return &v } diff --git a/xray/utils/sarifutils.go b/xray/utils/sarifutils.go index 5b5d31ab2..d0a959bfe 100644 --- a/xray/utils/sarifutils.go +++ b/xray/utils/sarifutils.go @@ -3,7 +3,6 @@ package utils import ( "fmt" "path/filepath" - "strconv" "strings" "github.com/jfrog/jfrog-client-go/utils/errorutils" @@ -115,24 +114,6 @@ func GetResultsLocationCount(runs ...*sarif.Run) (count int) { return } -func GetLevelResultsLocationCount(run *sarif.Run, level SarifLevel) (count int) { - for _, result := range run.Results { - if level == SarifLevel(*result.Level) { - count += len(result.Locations) - } - } - return -} - -func GetResultsByRuleId(run *sarif.Run, ruleId string) (results []*sarif.Result) { - for _, result := range run.Results { - if *result.RuleID == ruleId { - results = append(results, result) - } - } - return -} - func GetResultMsgText(result *sarif.Result) string { if result.Message.Text != nil { return *result.Message.Text @@ -141,19 +122,11 @@ func GetResultMsgText(result *sarif.Result) string { } func GetLocationSnippet(location *sarif.Location) string { - snippet := GetLocationSnippetPointer(location) - if snippet == nil { - return "" - } - return *snippet -} - -func GetLocationSnippetPointer(location *sarif.Location) *string { region := getLocationRegion(location) if region != nil && region.Snippet != nil { - return region.Snippet.Text + return *region.Snippet.Text } - return nil + return "" } func SetLocationSnippet(location *sarif.Location, snippet string) { @@ -163,9 +136,8 @@ func SetLocationSnippet(location *sarif.Location, snippet string) { } func GetLocationFileName(location *sarif.Location) string { - filePath := location.PhysicalLocation.ArtifactLocation.URI - if filePath != nil { - return *filePath + if location != nil && location.PhysicalLocation != nil && location.PhysicalLocation.ArtifactLocation != nil && location.PhysicalLocation.ArtifactLocation.URI != nil { + return *location.PhysicalLocation.ArtifactLocation.URI } return "" } @@ -228,15 +200,6 @@ func GetLocationEndColumn(location *sarif.Location) int { return 0 } -func GetStartLocationInFile(location *sarif.Location) string { - startLine := location.PhysicalLocation.Region.StartLine - startColumn := location.PhysicalLocation.Region.StartColumn - if startLine != nil && startColumn != nil { - return strconv.Itoa(*startLine) + ":" + strconv.Itoa(*startColumn) - } - return "" -} - func ExtractRelativePath(resultPath string, projectRoot string) string { // Remove OS-specific file prefix resultPath = strings.TrimPrefix(resultPath, "file:///private") @@ -291,8 +254,69 @@ func GetRunRules(run *sarif.Run) []*sarif.ReportingDescriptor { } func GetInvocationWorkingDirectory(invocation *sarif.Invocation) string { - if invocation.WorkingDirectory != nil && invocation.WorkingDirectory.URI != nil { + if invocation != nil && invocation.WorkingDirectory != nil && invocation.WorkingDirectory.URI != nil { return *invocation.WorkingDirectory.URI } return "" } + +func CreateRunWithDummyResults(results ...*sarif.Result) *sarif.Run { + run := sarif.NewRunWithInformationURI("", "") + for _, result := range results { + if result.RuleID != nil { + run.AddRule(*result.RuleID) + } + run.AddResult(result) + } + return run +} + +func CreateResultWithLocations(msg, ruleId, level string, locations ...*sarif.Location) *sarif.Result { + return &sarif.Result{ + Message: *sarif.NewTextMessage(msg), + Locations: locations, + Level: &level, + RuleID: &ruleId, + } +} + +func CreateLocation(fileName string, startLine, startCol, endLine, endCol int, snippet string) *sarif.Location { + return &sarif.Location{ + PhysicalLocation: &sarif.PhysicalLocation{ + ArtifactLocation: &sarif.ArtifactLocation{URI: &fileName}, + Region: &sarif.Region{ + StartLine: &startLine, + StartColumn: &startCol, + EndLine: &endLine, + EndColumn: &endCol, + Snippet: &sarif.ArtifactContent{Text: &snippet}}}, + } +} + +func CreateDummyPassingResult(ruleId string) *sarif.Result { + kind := "pass" + return &sarif.Result{ + Kind: &kind, + RuleID: &ruleId, + } +} + +func CreateResultWithOneLocation(fileName string, startLine, startCol, endLine, endCol int, snippet, ruleId, level string) *sarif.Result { + return CreateResultWithLocations("", ruleId, level, CreateLocation(fileName, startLine, startCol, endLine, endCol, snippet)) +} + +func CreateCodeFlow(threadFlows ...*sarif.ThreadFlow) *sarif.CodeFlow { + flow := sarif.NewCodeFlow() + for _, threadFlow := range threadFlows { + flow.AddThreadFlow(threadFlow) + } + return flow +} + +func CreateThreadFlow(locations ...*sarif.Location) *sarif.ThreadFlow { + stackStrace := sarif.NewThreadFlow() + for _, location := range locations { + stackStrace.AddLocation(sarif.NewThreadFlowLocation().WithLocation(location)) + } + return stackStrace +} diff --git a/xray/utils/sarifutils_test.go b/xray/utils/sarifutils_test.go index 4e0031268..20a92de41 100644 --- a/xray/utils/sarifutils_test.go +++ b/xray/utils/sarifutils_test.go @@ -1,43 +1,645 @@ package utils import ( - "github.com/jfrog/gofrog/datastructures" + "testing" + "github.com/owenrumney/go-sarif/v2/sarif" + "github.com/stretchr/testify/assert" ) -func getRunWithDummyResults(results ...*sarif.Result) *sarif.Run { - run := sarif.NewRunWithInformationURI("", "") - ids := datastructures.MakeSet[string]() - for _, result := range results { - if !ids.Exists(*result.RuleID) { - run.Tool.Driver.Rules = append(run.Tool.Driver.Rules, sarif.NewRule(*result.RuleID)) - ids.Add(*result.RuleID) - } +func TestAggregateMultipleRunsIntoSingle(t *testing.T) { + tests := []struct { + runs []*sarif.Run + expectedOutput *sarif.Run + }{ + { + runs: []*sarif.Run{}, + expectedOutput: CreateRunWithDummyResults(), + }, + { + runs: []*sarif.Run{ + CreateRunWithDummyResults( + CreateDummyPassingResult("rule1"), + CreateResultWithOneLocation("file", 1, 2, 3, 4, "snippet", "rule2", "level"), + ).WithInvocations([]*sarif.Invocation{ + sarif.NewInvocation().WithWorkingDirectory(sarif.NewSimpleArtifactLocation("wd")), + }), + CreateRunWithDummyResults(), + }, + expectedOutput: CreateRunWithDummyResults( + CreateDummyPassingResult("rule1"), + CreateResultWithOneLocation("file", 1, 2, 3, 4, "snippet", "rule2", "level"), + ).WithInvocations([]*sarif.Invocation{ + sarif.NewInvocation().WithWorkingDirectory(sarif.NewSimpleArtifactLocation("wd")), + }), + }, + { + runs: []*sarif.Run{ + CreateRunWithDummyResults( + CreateDummyPassingResult("rule1"), + CreateResultWithOneLocation("file", 1, 2, 3, 4, "snippet", "rule2", "level"), + CreateResultWithOneLocation("file", 1, 2, 3, 4, "snippet", "rule3", "level"), + ).WithInvocations([]*sarif.Invocation{ + sarif.NewInvocation().WithWorkingDirectory(sarif.NewSimpleArtifactLocation("wd")), + }), + CreateRunWithDummyResults( + CreateResultWithLocations("msg", "rule2", "level", + CreateLocation("file", 1, 2, 3, 4, "snippet"), + CreateLocation("file2", 1, 2, 3, 4, "other-snippet"), + ), + CreateResultWithOneLocation("file", 5, 6, 7, 8, "snippet2", "rule2", "level"), + ).WithInvocations([]*sarif.Invocation{ + sarif.NewInvocation().WithWorkingDirectory(sarif.NewSimpleArtifactLocation("wd2")), + }), + }, + expectedOutput: CreateRunWithDummyResults( + // First run results + CreateDummyPassingResult("rule1"), + CreateResultWithOneLocation("file", 1, 2, 3, 4, "snippet", "rule2", "level"), + CreateResultWithOneLocation("file", 1, 2, 3, 4, "snippet", "rule3", "level"), + // Second run results + CreateResultWithLocations("msg", "rule2", "level", + CreateLocation("file", 1, 2, 3, 4, "snippet"), + CreateLocation("file2", 1, 2, 3, 4, "other-snippet"), + ), + CreateResultWithOneLocation("file", 5, 6, 7, 8, "snippet2", "rule2", "level"), + ).WithInvocations([]*sarif.Invocation{ + sarif.NewInvocation().WithWorkingDirectory(sarif.NewSimpleArtifactLocation("wd")), + sarif.NewInvocation().WithWorkingDirectory(sarif.NewSimpleArtifactLocation("wd2")), + }), + }, + } + + for _, test := range tests { + run := CreateRunWithDummyResults() + AggregateMultipleRunsIntoSingle(test.runs, run) + assert.Equal(t, test.expectedOutput, run) } - return run.WithResults(results) } -func getDummyPassingResult(ruleId string) *sarif.Result { - kind := "pass" - return &sarif.Result{ - Kind: &kind, - RuleID: &ruleId, +func TestGetLocationRelatedCodeFlowsFromResult(t *testing.T) { + tests := []struct { + result *sarif.Result + location *sarif.Location + expectedOutput []*sarif.CodeFlow + }{ + { + result: CreateDummyPassingResult("rule"), + location: CreateLocation("file", 0, 0, 0, 0, "snippet"), + expectedOutput: nil, + }, + { + result: CreateResultWithOneLocation("file", 0, 0, 0, 0, "snippet", "rule", "level"), + location: CreateLocation("file", 0, 0, 0, 0, "snippet"), + expectedOutput: nil, + }, + { + result: CreateResultWithOneLocation("file", 0, 0, 0, 0, "snippet", "rule", "level").WithCodeFlows([]*sarif.CodeFlow{CreateCodeFlow(CreateThreadFlow(CreateLocation("file", 0, 0, 0, 0, "snippet")))}), + location: CreateLocation("file2", 0, 0, 0, 0, "snippet"), + expectedOutput: nil, + }, + { + result: CreateResultWithOneLocation("file", 0, 0, 0, 0, "snippet", "rule", "level").WithCodeFlows([]*sarif.CodeFlow{CreateCodeFlow(CreateThreadFlow(CreateLocation("file", 0, 0, 0, 0, "snippet")))}), + location: CreateLocation("file", 0, 0, 0, 0, "snippet"), + expectedOutput: []*sarif.CodeFlow{CreateCodeFlow(CreateThreadFlow(CreateLocation("file", 0, 0, 0, 0, "snippet")))}, + }, + { + result: CreateResultWithOneLocation("file", 0, 0, 0, 0, "snippet", "rule", "level").WithCodeFlows([]*sarif.CodeFlow{ + CreateCodeFlow(CreateThreadFlow( + CreateLocation("file4", 2, 0, 2, 0, "snippetB"), + CreateLocation("file2", 0, 2, 0, 2, "snippetA"), + CreateLocation("file", 0, 0, 0, 0, "snippet"), + )), + CreateCodeFlow(CreateThreadFlow( + CreateLocation("file", 0, 0, 0, 0, "snippet"), + CreateLocation("file2", 1, 0, 1, 0, "snippet"), + )), + CreateCodeFlow(CreateThreadFlow( + CreateLocation("fileC", 1, 1, 1, 1, "snippetC"), + CreateLocation("file", 0, 0, 0, 0, "snippet"), + )), + }), + location: CreateLocation("file", 0, 0, 0, 0, "snippet"), + expectedOutput: []*sarif.CodeFlow{ + CreateCodeFlow(CreateThreadFlow( + CreateLocation("file4", 2, 0, 2, 0, "snippetB"), + CreateLocation("file2", 0, 2, 0, 2, "snippetA"), + CreateLocation("file", 0, 0, 0, 0, "snippet"), + )), + CreateCodeFlow(CreateThreadFlow( + CreateLocation("fileC", 1, 1, 1, 1, "snippetC"), + CreateLocation("file", 0, 0, 0, 0, "snippet"), + )), + }, + }, + } + + for _, test := range tests { + assert.Equal(t, test.expectedOutput, GetLocationRelatedCodeFlowsFromResult(test.location, test.result)) } } -func getDummyResultWithOneLocation(fileName string, startLine, startCol int, snippet, ruleId string, level string) *sarif.Result { - return &sarif.Result{ - Locations: []*sarif.Location{ - { - PhysicalLocation: &sarif.PhysicalLocation{ - ArtifactLocation: &sarif.ArtifactLocation{URI: &fileName}, - Region: &sarif.Region{ - StartLine: &startLine, - StartColumn: &startCol, - Snippet: &sarif.ArtifactContent{Text: &snippet}}}, +func TestGetResultsLocationCount(t *testing.T) { + tests := []struct { + runs []*sarif.Run + expectedOutput int + }{ + { + runs: []*sarif.Run{}, + expectedOutput: 0, + }, + { + runs: []*sarif.Run{CreateRunWithDummyResults()}, + expectedOutput: 0, + }, + { + runs: []*sarif.Run{CreateRunWithDummyResults( + CreateDummyPassingResult("rule"), + CreateResultWithOneLocation("file", 0, 0, 0, 0, "snippet", "rule", "level"), + )}, + expectedOutput: 1, + }, + { + runs: []*sarif.Run{ + CreateRunWithDummyResults( + CreateDummyPassingResult("rule"), + CreateResultWithOneLocation("file", 0, 0, 0, 0, "snippet", "rule", "level"), + ), + CreateRunWithDummyResults( + CreateResultWithLocations( + "msg", + "rule", + "level", + CreateLocation("file", 0, 0, 0, 0, "snippet"), + CreateLocation("file", 0, 0, 0, 0, "snippet"), + CreateLocation("file", 0, 0, 0, 0, "snippet"), + ), + ), }, + expectedOutput: 4, + }, + } + + for _, test := range tests { + assert.Equal(t, test.expectedOutput, GetResultsLocationCount(test.runs...)) + } +} + +func TestGetResultMsgText(t *testing.T) { + tests := []struct { + result *sarif.Result + expectedOutput string + }{ + { + result: &sarif.Result{}, + expectedOutput: "", + }, + { + result: CreateResultWithLocations("msg", "rule", "level"), + expectedOutput: "msg", + }, + } + + for _, test := range tests { + assert.Equal(t, test.expectedOutput, GetResultMsgText(test.result)) + } +} + +func TestGetLocationSnippet(t *testing.T) { + tests := []struct { + location *sarif.Location + expectedOutput string + }{ + { + location: nil, + expectedOutput: "", + }, + { + location: CreateLocation("filename", 1, 2, 3, 4, "snippet"), + expectedOutput: "snippet", + }, + } + + for _, test := range tests { + assert.Equal(t, test.expectedOutput, GetLocationSnippet(test.location)) + } +} + +func TestSetLocationSnippet(t *testing.T) { + tests := []struct { + location *sarif.Location + expectedOutput string + }{ + { + location: nil, + expectedOutput: "", + }, + { + location: CreateLocation("filename", 1, 2, 3, 4, "snippet"), + expectedOutput: "changedSnippet", + }, + } + + for _, test := range tests { + SetLocationSnippet(test.location, test.expectedOutput) + assert.Equal(t, test.expectedOutput, GetLocationSnippet(test.location)) + } +} + +func TestGetLocationFileName(t *testing.T) { + tests := []struct { + location *sarif.Location + expectedOutput string + }{ + { + location: nil, + expectedOutput: "", + }, + { + location: CreateLocation("filename", 1, 2, 3, 4, "snippet"), + expectedOutput: "filename", + }, + } + + for _, test := range tests { + assert.Equal(t, test.expectedOutput, GetLocationFileName(test.location)) + } +} + +func TestGetRelativeLocationFileName(t *testing.T) { + tests := []struct { + location *sarif.Location + invocations []*sarif.Invocation + expectedOutput string + }{ + { + location: CreateLocation("file:///root/someDir/another/file", 1, 2, 3, 4, "snippet"), + invocations: []*sarif.Invocation{}, + expectedOutput: "root/someDir/another/file", + }, + { + location: CreateLocation("file:///root/someDir/another/file", 1, 2, 3, 4, "snippet"), + invocations: []*sarif.Invocation{{WorkingDirectory: sarif.NewSimpleArtifactLocation("/not/relevant")}}, + expectedOutput: "root/someDir/another/file", + }, + { + location: CreateLocation("file:///root/someDir/another/file", 1, 2, 3, 4, "snippet"), + invocations: []*sarif.Invocation{{WorkingDirectory: sarif.NewSimpleArtifactLocation("/root/someDir/")}}, + expectedOutput: "another/file", + }, + } + + for _, test := range tests { + assert.Equal(t, test.expectedOutput, GetRelativeLocationFileName(test.location, test.invocations)) + } +} + +func TestSetLocationFileName(t *testing.T) { + tests := []struct { + location *sarif.Location + expectedOutput string + }{ + { + location: nil, + expectedOutput: "", + }, + { + location: CreateLocation("filename", 1, 2, 3, 4, "snippet"), + expectedOutput: "changedFilename", + }, + } + + for _, test := range tests { + SetLocationFileName(test.location, test.expectedOutput) + assert.Equal(t, test.expectedOutput, GetLocationFileName(test.location)) + } +} + +func TestGetLocationRegion(t *testing.T) { + tests := []struct { + location *sarif.Location + expectedOutput *sarif.Region + }{ + { + location: nil, + expectedOutput: nil, + }, + { + location: &sarif.Location{PhysicalLocation: &sarif.PhysicalLocation{}}, + expectedOutput: nil, + }, + { + location: CreateLocation("filename", 1, 2, 3, 4, "snippet"), + expectedOutput: sarif.NewRegion().WithStartLine(1).WithStartColumn(2).WithEndLine(3).WithEndColumn(4). + WithSnippet(sarif.NewArtifactContent().WithText("snippet")), + }, + } + + for _, test := range tests { + assert.Equal(t, test.expectedOutput, getLocationRegion(test.location)) + } +} + +func TestGetLocationStartLine(t *testing.T) { + tests := []struct { + location *sarif.Location + expectedOutput int + }{ + { + location: nil, + expectedOutput: 0, + }, + { + location: CreateLocation("filename", 1, 2, 3, 4, "snippet"), + expectedOutput: 1, + }, + } + + for _, test := range tests { + assert.Equal(t, test.expectedOutput, GetLocationStartLine(test.location)) + } +} + +func TestGetLocationStartColumn(t *testing.T) { + tests := []struct { + location *sarif.Location + expectedOutput int + }{ + { + location: nil, + expectedOutput: 0, + }, + { + location: CreateLocation("filename", 1, 2, 3, 4, "snippet"), + expectedOutput: 2, + }, + } + + for _, test := range tests { + assert.Equal(t, test.expectedOutput, GetLocationStartColumn(test.location)) + } +} + +func TestGetLocationEndLine(t *testing.T) { + tests := []struct { + location *sarif.Location + expectedOutput int + }{ + { + location: nil, + expectedOutput: 0, + }, + { + location: CreateLocation("filename", 1, 2, 3, 4, "snippet"), + expectedOutput: 3, + }, + } + + for _, test := range tests { + assert.Equal(t, test.expectedOutput, GetLocationEndLine(test.location)) + } +} + +func TestGetLocationEndColumn(t *testing.T) { + tests := []struct { + location *sarif.Location + expectedOutput int + }{ + { + location: nil, + expectedOutput: 0, + }, + { + location: CreateLocation("filename", 1, 2, 3, 4, "snippet"), + expectedOutput: 4, + }, + } + + for _, test := range tests { + assert.Equal(t, test.expectedOutput, GetLocationEndColumn(test.location)) + } +} + +func TestExtractRelativePath(t *testing.T) { + tests := []struct { + fullPath string + projectPath string + expectedResult string + }{ + {fullPath: "file:///Users/user/Desktop/secrets_scanner/tests/req.nodejs/file.js", + projectPath: "Users/user/Desktop/secrets_scanner/", expectedResult: "tests/req.nodejs/file.js"}, + {fullPath: "file:///private/Users/user/Desktop/secrets_scanner/tests/req.nodejs/file.js", + projectPath: "Users/user/Desktop/secrets_scanner/", expectedResult: "tests/req.nodejs/file.js"}, + {fullPath: "invalidFullPath", + projectPath: "Users/user/Desktop/secrets_scanner/", expectedResult: "invalidFullPath"}, + {fullPath: "", + projectPath: "Users/user/Desktop/secrets_scanner/", expectedResult: ""}, + {fullPath: "file:///Users/user/Desktop/secrets_scanner/tests/req.nodejs/file.js", + projectPath: "invalidProjectPath", expectedResult: "Users/user/Desktop/secrets_scanner/tests/req.nodejs/file.js"}, + {fullPath: "file:///private/Users/user/Desktop/secrets_scanner/tests/req.nodejs/file.js", + projectPath: "invalidProjectPath", expectedResult: "Users/user/Desktop/secrets_scanner/tests/req.nodejs/file.js"}, + } + + for _, test := range tests { + assert.Equal(t, test.expectedResult, ExtractRelativePath(test.fullPath, test.projectPath)) + } +} + +func TestGetResultSeverity(t *testing.T) { + levelValueHigh := string(errorLevel) + levelValueMedium := string(warningLevel) + levelValueMedium2 := string(infoLevel) + levelValueLow := string(noteLevel) + levelValueUnknown := string(noneLevel) + + tests := []struct { + result *sarif.Result + expectedSeverity string + }{ + {result: &sarif.Result{}, + expectedSeverity: "Medium"}, + {result: &sarif.Result{Level: &levelValueHigh}, + expectedSeverity: "High"}, + {result: &sarif.Result{Level: &levelValueMedium}, + expectedSeverity: "Medium"}, + {result: &sarif.Result{Level: &levelValueMedium2}, + expectedSeverity: "Medium"}, + {result: &sarif.Result{Level: &levelValueLow}, + expectedSeverity: "Low"}, + {result: &sarif.Result{Level: &levelValueUnknown}, + expectedSeverity: "Unknown"}, + } + + for _, test := range tests { + assert.Equal(t, test.expectedSeverity, GetResultSeverity(test.result)) + } +} + +func TestConvertToSarifLevel(t *testing.T) { + tests := []struct { + severity string + expectedOutput string + }{ + { + severity: "Unknown", + expectedOutput: "none", + }, + { + severity: "Low", + expectedOutput: "note", + }, + { + severity: "Medium", + expectedOutput: "warning", + }, + { + severity: "High", + expectedOutput: "error", + }, + { + severity: "Critical", + expectedOutput: "error", + }, + } + + for _, test := range tests { + assert.Equal(t, test.expectedOutput, ConvertToSarifLevel(test.severity)) + } +} + +func TestIsApplicableResult(t *testing.T) { + tests := []struct { + name string + sarifResult *sarif.Result + expectedOutput bool + }{ + { + sarifResult: CreateDummyPassingResult("rule"), + expectedOutput: false, + }, + { + sarifResult: CreateResultWithOneLocation("file", 0, 0, 0, 0, "snippet1", "ruleId1", "level1"), + expectedOutput: true, + }, + } + + for _, test := range tests { + assert.Equal(t, test.expectedOutput, IsApplicableResult(test.sarifResult)) + } +} + +func TestGetRuleFullDescription(t *testing.T) { + tests := []struct { + rule *sarif.ReportingDescriptor + expectedOutput string + }{ + { + rule: sarif.NewRule("rule"), + expectedOutput: "", + }, + { + rule: sarif.NewRule("rule").WithFullDescription(nil), + expectedOutput: "", + }, + { + rule: sarif.NewRule("rule").WithFullDescription(sarif.NewMultiformatMessageString("description")), + expectedOutput: "description", }, - Level: &level, - RuleID: &ruleId, + } + + for _, test := range tests { + assert.Equal(t, test.expectedOutput, GetRuleFullDescription(test.rule)) + } +} + +func TestCveToApplicabilityRuleId(t *testing.T) { + assert.Equal(t, "applic_cve", CveToApplicabilityRuleId("cve")) +} + +func TestApplicabilityRuleIdToCve(t *testing.T) { + tests := []struct { + ruleId string + expectedOutput string + }{ + { + ruleId: "rule", + expectedOutput: "rule", + }, + { + ruleId: "applic_cve", + expectedOutput: "cve", + }, + } + + for _, test := range tests { + assert.Equal(t, test.expectedOutput, ApplicabilityRuleIdToCve(test.ruleId)) + } +} + +func TestGetRunRules(t *testing.T) { + tests := []struct { + run *sarif.Run + expectedOutput []*sarif.ReportingDescriptor + }{ + { + run: &sarif.Run{}, + expectedOutput: []*sarif.ReportingDescriptor{}, + }, + { + run: CreateRunWithDummyResults(), + expectedOutput: []*sarif.ReportingDescriptor{}, + }, + { + run: CreateRunWithDummyResults( + CreateDummyPassingResult("rule1"), + ), + expectedOutput: []*sarif.ReportingDescriptor{sarif.NewRule("rule1")}, + }, + { + run: CreateRunWithDummyResults( + CreateDummyPassingResult("rule1"), + CreateDummyPassingResult("rule1"), + CreateDummyPassingResult("rule2"), + CreateDummyPassingResult("rule3"), + CreateDummyPassingResult("rule2"), + ), + expectedOutput: []*sarif.ReportingDescriptor{sarif.NewRule("rule1"), sarif.NewRule("rule2"), sarif.NewRule("rule3")}, + }, + } + + for _, test := range tests { + assert.Equal(t, test.expectedOutput, GetRunRules(test.run)) + } +} + +func TestGetInvocationWorkingDirectory(t *testing.T) { + tests := []struct { + invocation *sarif.Invocation + expectedOutput string + }{ + { + invocation: nil, + expectedOutput: "", + }, + { + invocation: sarif.NewInvocation(), + expectedOutput: "", + }, + { + invocation: sarif.NewInvocation().WithWorkingDirectory(nil), + expectedOutput: "", + }, + { + invocation: sarif.NewInvocation().WithWorkingDirectory(sarif.NewArtifactLocation()), + expectedOutput: "", + }, + { + invocation: sarif.NewInvocation().WithWorkingDirectory(sarif.NewArtifactLocation().WithUri("file_to_wd")), + expectedOutput: "file_to_wd", + }, + } + + for _, test := range tests { + assert.Equal(t, test.expectedOutput, GetInvocationWorkingDirectory(test.invocation)) } }