diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 70abbdf..6d148ac 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -11,6 +11,7 @@ on: jobs: build: + name: Go tests runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 diff --git a/README.md b/README.md index 53aee61..70b4cb8 100644 --- a/README.md +++ b/README.md @@ -1,23 +1,16 @@ [![Tests](https://github.com/DI-Tony-Reed/JSONDiff/actions/workflows/tests.yaml/badge.svg)](https://github.com/DI-Tony-Reed/JSONDiff/actions/workflows/tests.yaml) # What is this -This tool simply accepts two JSON files and returns the difference between them. It utilizes https://github.com/go-test/deep for the comparison. +This tool accepts two JSON Snyk scans and returns the difference between them. It utilizes a slightly modified version of https://github.com/hezro/snyk-code-pr-diff for the comparison. # How do I use this? -Locally, you can clone this repository, build it via the Makefile, and run it by feeding it two JSON files: +Locally, you can clone this repository, build it via the Makefile, and run it by feeding it two JSON Snyk scan files: ```bash make build ./bin/jsonDiff-darwin json1.json json2.json ``` Make sure to use the appropriate binary for your OS. The above example is assuming you are on a Mac. -## Additional CLI flags -You can also use the `--byteskip` to skip the comparison if the second file has fewer bytes than the first. -```bash -./bin/jsonDiff-darwin json1.json json2.json --byteskip -``` - - # Viewing test coverage ```bash make tests-coverage && open coverage.html diff --git a/compare.go b/compare.go index 284c37b..9998013 100644 --- a/compare.go +++ b/compare.go @@ -2,41 +2,31 @@ package main import ( "bytes" - "fmt" - "github.com/go-test/deep" + "errors" ) type JSONDiff struct { - File1 File - File2 File - ByteSkip bool + File1 File + File2 File } -func (j JSONDiff) FindDifferences() string { +func (j JSONDiff) FindDifferences() (string, error) { if j.File1.Bytes == nil || j.File2.Bytes == nil { - return "No bytes defined for File1 and/or File2." + return "No bytes defined for File1 and/or File2.", nil } if j.File1.Map == nil || j.File2.Map == nil { - return "No map defined for File1 and/or File2." + return "No map defined for File1 and/or File2.", nil } if bytes.Equal(j.File1.Bytes, j.File2.Bytes) { - return "No differences found." + return "No differences found.", nil } - if j.ByteSkip && len(j.File2.Bytes) < len(j.File1.Bytes) { - return "Second file smaller than first and byteskip enabled" + output, err := check(j.File1.Map, j.File2.Map) + if err != nil { + return "", err } - if diff := deep.Equal(j.File1.Map, j.File2.Map); diff != nil { - differences := "Differences found:" - for _, d := range diff { - differences += fmt.Sprintf("\n%v", d) - } - - return differences - } - - return "No differences found." + return output, errors.New("new vulnerability(s) found") } diff --git a/compare_test.go b/compare_test.go index f04c55c..c9adf7f 100644 --- a/compare_test.go +++ b/compare_test.go @@ -2,392 +2,21 @@ package main import "testing" -func TestJSONDiff_FindDifferences(t *testing.T) { - tests := []struct { - name string - j JSONDiff - want string - }{ - { - name: "no differences", - j: JSONDiff{ - File1: File{ - Bytes: []byte(`{"key1": "value1"}`), - Map: map[string]interface{}{"key1": "value1"}, - }, - File2: File{ - Bytes: []byte(`{"key1": "value1"}`), - Map: map[string]interface{}{"key1": "value1"}, - }, - }, - want: "No differences found.", - }, - { - name: "differences found", - j: JSONDiff{ - File1: File{ - Bytes: []byte(`{"key1": "value1"}`), - Map: map[string]interface{}{"key1": "value1"}, - }, - File2: File{ - Bytes: []byte(`{"key1": "value2"}`), - Map: map[string]interface{}{"key1": "value2"}, - }, - }, - want: "Differences found:\nmap[key1]: value1 != value2", - }, - { - name: "differences found with nested map", - j: JSONDiff{ - File1: File{ - Bytes: []byte(`{"key1": {"key2": "value1"}}`), - Map: map[string]interface{}{"key1": map[string]interface{}{"key2": "value1"}}, - }, - File2: File{ - Bytes: []byte(`{"key1": {"key2": "value2"}}`), - Map: map[string]interface{}{"key1": map[string]interface{}{"key2": "value1"}}, - }, - }, - want: "No differences found.", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := tt.j.FindDifferences(); got != tt.want { - t.Errorf("JSONDiff.FindDifferences() = %v, want %v", got, tt.want) - } - }) - } -} - -func TestJSONDiff_FindDifferencesWithSnykShape_Same(t *testing.T) { - snykTests := []struct { - name string - j JSONDiff - }{ - { - name: "snyk test case no differences", - j: JSONDiff{ - File1: File{ - Bytes: []byte(`{ - "vulnerabilities": [], - "ok": true, - "dependencyCount": 0, - "org": "example-org", - "policy": "# Snyk (https://snyk.io) policy file, patches or ignores known vulnerabilities.\nversion: v1.25.1\nignore: {}\npatch: {}\n", - "isPrivate": true, - "licensesPolicy": { - "severities": {}, - "orgLicenseRules": { - "AGPL-1.0": { - "licenseType": "AGPL-1.0", - "severity": "high", - "instructions": "" - } - } - }, - "packageManager": "yarn", - "ignoreSettings": { - "adminOnly": false, - "reasonRequired": false, - "disregardFilesystemIgnores": false - }, - "summary": "No known vulnerabilities", - "filesystemPolicy": false, - "uniqueCount": 0, - "projectName": "package.json", - "foundProjectCount": 1, - "displayTargetFile": "yarn.lock", - "hasUnknownVersions": false, - "path": "/path/to/project" - }`), - Map: map[string]interface{}{ - "vulnerabilities": []interface{}{}, - "ok": true, - "dependencyCount": 0, - "org": "example-org", - "policy": "# Snyk (https://snyk.io) policy file, patches or ignores known vulnerabilities.\nversion: v1.25.1\nignore: {}\npatch: {}\n", - "isPrivate": true, - "licensesPolicy": map[string]interface{}{ - "severities": map[string]interface{}{}, - "orgLicenseRules": map[string]interface{}{ - "AGPL-1.0": map[string]interface{}{ - "licenseType": "AGPL-1.0", - "severity": "high", - "instructions": "", - }, - }, - }, - "packageManager": "yarn", - "ignoreSettings": map[string]interface{}{ - "adminOnly": false, - "reasonRequired": false, - "disregardFilesystemIgnores": false, - }, - "summary": "No known vulnerabilities", - "filesystemPolicy": false, - "uniqueCount": 0, - "projectName": "package.json", - "foundProjectCount": 1, - "displayTargetFile": "yarn.lock", - "hasUnknownVersions": false, - "path": "/path/to/project", - }, - }, - File2: File{ - Bytes: []byte(`{ - "vulnerabilities": [], - "ok": true, - "dependencyCount": 0, - "org": "example-org", - "policy": "# Snyk (https://snyk.io) policy file, patches or ignores known vulnerabilities.\nversion: v1.25.1\nignore: {}\npatch: {}\n", - "isPrivate": true, - "licensesPolicy": { - "severities": {}, - "orgLicenseRules": { - "AGPL-1.0": { - "licenseType": "AGPL-1.0", - "severity": "high", - "instructions": "" - } - } - }, - "packageManager": "yarn", - "ignoreSettings": { - "adminOnly": false, - "reasonRequired": false, - "disregardFilesystemIgnores": false - }, - "summary": "No known vulnerabilities", - "filesystemPolicy": false, - "uniqueCount": 0, - "projectName": "package.json", - "foundProjectCount": 1, - "displayTargetFile": "yarn.lock", - "hasUnknownVersions": false, - "path": "/path/to/project" - }`), - Map: map[string]interface{}{ - "vulnerabilities": []interface{}{}, - "ok": true, - "dependencyCount": 0, - "org": "example-org", - "policy": "# Snyk (https://snyk.io) policy file, patches or ignores known vulnerabilities.\nversion: v1.25.1\nignore: {}\npatch: {}\n", - "isPrivate": true, - "licensesPolicy": map[string]interface{}{ - "severities": map[string]interface{}{}, - "orgLicenseRules": map[string]interface{}{ - "AGPL-1.0": map[string]interface{}{ - "licenseType": "AGPL-1.0", - "severity": "high", - "instructions": "", - }, - }, - }, - "packageManager": "yarn", - "ignoreSettings": map[string]interface{}{ - "adminOnly": false, - "reasonRequired": false, - "disregardFilesystemIgnores": false, - }, - "summary": "No known vulnerabilities", - "filesystemPolicy": false, - "uniqueCount": 0, - "projectName": "package.json", - "foundProjectCount": 1, - "displayTargetFile": "yarn.lock", - "hasUnknownVersions": false, - "path": "/path/to/project", - }, - }, - }, +func TestJSONDiff_FindDifferences_WithDifferences_WrongShape(t *testing.T) { + j := JSONDiff{ + File1: File{ + Bytes: []byte(`{"key1": "value1"}`), + Map: map[string]interface{}{"key1": "value1"}, }, - } - - for _, tt := range snykTests { - t.Run(tt.name, func(t *testing.T) { - if got := tt.j.FindDifferences(); got != "No differences found." { - t.Errorf("JSONDiff.FindDifferences() = %v)", got) - } - }) - } -} - -func TestJSONDiff_FindDifferencesWithSnykShape_Vulnerabilities(t *testing.T) { - snykTests := []struct { - name string - j JSONDiff - }{ - { - name: "snyk test case with vulnerabilities", - j: JSONDiff{ - File1: File{ - Bytes: []byte(`{ - "vulnerabilities": [ - { - "id": "vuln-1", - "title": "Vulnerability 1", - "severity": "high" - } - ], - "ok": true, - "dependencyCount": 0, - "org": "example-org", - "policy": "# Snyk (https://snyk.io) policy file, patches or ignores known vulnerabilities.\nversion: v1.25.1\nignore: {}\npatch: {}\n", - "isPrivate": true, - "licensesPolicy": { - "severities": {}, - "orgLicenseRules": { - "AGPL-1.0": { - "licenseType": "AGPL-1.0", - "severity": "high", - "instructions": "" - } - } - }, - "packageManager": "yarn", - "ignoreSettings": { - "adminOnly": false, - "reasonRequired": false, - "disregardFilesystemIgnores": false - }, - "summary": "No known vulnerabilities", - "filesystemPolicy": false, - "uniqueCount": 0, - "projectName": "package.json", - "foundProjectCount": 1, - "displayTargetFile": "yarn.lock", - "hasUnknownVersions": false, - "path": "/path/to/project" - }`), - Map: map[string]interface{}{ - "vulnerabilities": []interface{}{ - map[string]interface{}{ - "id": "vuln-1", - "title": "Vulnerability 1", - "severity": "high", - }, - }, - "ok": true, - "dependencyCount": 0, - "org": "example-org", - "policy": "# Snyk (https://snyk.io) policy file, patches or ignores known vulnerabilities.\nversion: v1.25.1\nignore: {}\npatch: {}\n", - "isPrivate": true, - "licensesPolicy": map[string]interface{}{ - "severities": map[string]interface{}{}, - "orgLicenseRules": map[string]interface{}{ - "AGPL-1.0": map[string]interface{}{ - "licenseType": "AGPL-1.0", - "severity": "high", - "instructions": "", - }, - }, - }, - "packageManager": "yarn", - "ignoreSettings": map[string]interface{}{ - "adminOnly": false, - "reasonRequired": false, - "disregardFilesystemIgnores": false, - }, - "summary": "No known vulnerabilities", - "filesystemPolicy": false, - "uniqueCount": 0, - "projectName": "package.json", - "foundProjectCount": 1, - "displayTargetFile": "yarn.lock", - "hasUnknownVersions": false, - "path": "/path/to/project", - }, - }, - File2: File{ - Bytes: []byte(`{ - "vulnerabilities": [ - { - "id": "vuln-2", - "title": "Vulnerability 2", - "severity": "medium" - } - ], - "ok": true, - "dependencyCount": 0, - "org": "example-org", - "policy": "# Snyk (https://snyk.io) policy file, patches or ignores known vulnerabilities.\nversion: v1.25.1\nignore: {}\npatch: {}\n", - "isPrivate": true, - "licensesPolicy": { - "severities": {}, - "orgLicenseRules": { - "AGPL-1.0": { - "licenseType": "AGPL-1.0", - "severity": "high", - "instructions": "" - } - } - }, - "packageManager": "yarn", - "ignoreSettings": { - "adminOnly": false, - "reasonRequired": false, - "disregardFilesystemIgnores": false - }, - "summary": "No known vulnerabilities", - "filesystemPolicy": false, - "uniqueCount": 0, - "projectName": "package.json", - "foundProjectCount": 1, - "displayTargetFile": "yarn.lock", - "hasUnknownVersions": false, - "path": "/path/to/project" - }`), - Map: map[string]interface{}{ - "vulnerabilities": []interface{}{ - map[string]interface{}{ - "id": "vuln-2", - "title": "Vulnerability 2", - "severity": "medium", - }, - }, - "ok": true, - "dependencyCount": 0, - "org": "example-org", - "policy": "# Snyk (https://snyk.io) policy file, patches or ignores known vulnerabilities.\nversion: v1.25.1\nignore: {}\npatch: {}\n", - "isPrivate": true, - "licensesPolicy": map[string]interface{}{ - "severities": map[string]interface{}{}, - "orgLicenseRules": map[string]interface{}{ - "AGPL-1.0": map[string]interface{}{ - "licenseType": "AGPL-1.0", - "severity": "high", - "instructions": "", - }, - }, - }, - "packageManager": "yarn", - "ignoreSettings": map[string]interface{}{ - "adminOnly": false, - "reasonRequired": false, - "disregardFilesystemIgnores": false, - }, - "summary": "No known vulnerabilities", - "filesystemPolicy": false, - "uniqueCount": 0, - "projectName": "package.json", - "foundProjectCount": 1, - "displayTargetFile": "yarn.lock", - "hasUnknownVersions": false, - "path": "/path/to/project", - }, - }, - }, + File2: File{ + Bytes: []byte(`{"key1": "value2"}`), + Map: map[string]interface{}{"key1": "value2"}, }, } - for _, tt := range snykTests { - t.Run(tt.name, func(t *testing.T) { - if got := tt.j.FindDifferences(); got == "No differences found." { - t.Errorf("JSONDiff.FindDifferences() = %v)", got) - } - }) + _, err := j.FindDifferences() + if err == nil { + t.Errorf("JSONDiff.FindDifferences() error = %v", err) } } @@ -403,7 +32,7 @@ func TestJSONDiff_FindDifferences_NoBytes(t *testing.T) { }, } - if got := j.FindDifferences(); got != "No bytes defined for File1 and/or File2." { + if got, _ := j.FindDifferences(); got != "No bytes defined for File1 and/or File2." { t.Errorf("JSONDiff.FindDifferences() = %v, want %v", got, "No bytes defined for File1 and/or File2.") } } @@ -420,26 +49,37 @@ func TestJSONDiff_FindDifferences_NoMap(t *testing.T) { }, } - if got := j.FindDifferences(); got != "No map defined for File1 and/or File2." { + if got, _ := j.FindDifferences(); got != "No map defined for File1 and/or File2." { t.Errorf("JSONDiff.FindDifferences() = %v, want %v", got, "No map defined for File1 and/or File2.") } } -func TestJSONDiff_FindDIfferences_ByteSkip(t *testing.T) { +func TestJSONDiff_FindDifferences(t *testing.T) { j := JSONDiff{ File1: File{ - Bytes: []byte(`{"key1": "value1"}, {"key2": "value2"}`), - Map: map[string]interface{}{"key1": "value1", "key2": "value2"}, + Bytes: []byte(`{"runs": "value1"}`), + Map: map[string]interface{}{ + "runs": []interface{}{ + map[string]interface{}{ + "results": []interface{}{}, + }, + }, + }, }, File2: File{ - Bytes: []byte(`{"key1": "value1"}`), - Map: map[string]interface{}{"key1": "value1"}, + Bytes: []byte(`{"runs": "value2"}`), + Map: map[string]interface{}{ + "runs": []interface{}{ + map[string]interface{}{ + "results": []interface{}{}, + }, + }, + }, }, - ByteSkip: true, } - expected := "Second file smaller than first and byteskip enabled" - if got := j.FindDifferences(); got != expected { - t.Errorf("JSONDiff.FindDifferences() = %v, want %v", got, expected) + output, err := j.FindDifferences() + if err == nil || output != "" { + t.Errorf("JSONDiff.FindDifferences() error = %v", err) } } diff --git a/go.mod b/go.mod index 5ae11eb..8b0c2f2 100644 --- a/go.mod +++ b/go.mod @@ -1,5 +1,3 @@ module github.com/DI-Tony-Reed/JSONDiff go 1.22 - -require github.com/go-test/deep v1.1.1 diff --git a/main.go b/main.go index cb9780d..45f11fc 100644 --- a/main.go +++ b/main.go @@ -12,7 +12,7 @@ func main() { output, err := runner.Run(OSFileReader{}) if err != nil { - log.Fatal(err) + log.Fatal(output, err) } log.Print(output) diff --git a/runner.go b/runner.go index 7569467..1efd977 100644 --- a/runner.go +++ b/runner.go @@ -8,9 +8,6 @@ type Runner struct { Arguments []string } -// Run Simple proof of concept displaying output of difference of JSON files using existing package from go-test. -// https://github.com/go-test/deep -// https://pkg.go.dev/github.com/go-test/deep func (r Runner) Run(reader FileReader) (string, error) { if len(r.Arguments) < 2 { return "", errors.New("please provide two JSON files to compare") @@ -34,9 +31,5 @@ func (r Runner) Run(reader FileReader) (string, error) { File2: files[1], } - if (len(r.Arguments) == 3) && (r.Arguments[2] == "--byteskip") { - comparator.ByteSkip = true - } - - return comparator.FindDifferences(), nil + return comparator.FindDifferences() } diff --git a/runner_test.go b/runner_test.go index 38a2dc9..ec94fec 100644 --- a/runner_test.go +++ b/runner_test.go @@ -66,19 +66,3 @@ func TestRunner_Run_InvalidFiles(t *testing.T) { t.Errorf("Expected error, but got did not get one") } } - -func TestRunner_Run_ByteSkip(t *testing.T) { - mockFileReader := MockFileReader{ - Content: []byte(`{"key1": "value1"}`), - Err: nil, - } - - runner := Runner{ - Arguments: []string{"file1.json", "file2.json", "--byteskip"}, - } - - _, err := runner.Run(mockFileReader) - if err != nil { - t.Fatalf("Expected no error, but got %v", err) - } -} diff --git a/snyk-check.go b/snyk-check.go new file mode 100644 index 0000000..4b6c80f --- /dev/null +++ b/snyk-check.go @@ -0,0 +1,118 @@ +package main + +import ( + "errors" + "fmt" + "strings" +) + +// Original author: https://github.com/hezro/snyk-code-pr-diff/tree/main +// Went this route as the original code does not have a go.mod and cannot be +// pulled in with Go's package management +func check(baseline map[string]interface{}, feature map[string]interface{}) (string, error) { + var results = strings.Builder{} + + // Extract the "results" array from the Baseline scan + baselineResults, ok := extractResults(baseline) + if !ok { + return "", errors.New("failed to extract 'results' from the Baseline scan") + } + + // Extract the "results" array from the PR scan + prResults, ok := extractResults(feature) + if !ok { + return "", errors.New("failed to extract 'results' from the PR scan") + } + + // Find the indices of new fingerprints from the PR results + newIndices := findNewFingerprintIndices(baselineResults, prResults) + + // Extract the new issues objects from the PR results + newIssues := extractNewIssues(prResults, newIndices) + + // Output the new issues from the PR results + for _, result := range newIssues { + level, message, uri, startLine := extractIssueData(result) + level = strings.Replace(level, "note", "Low", 1) + level = strings.Replace(level, "warning", "Medium", 1) + level = strings.Replace(level, "error", "High", 1) + results.WriteString(fmt.Sprintf("✗ Severity: [%s]\n", level)) + results.WriteString(fmt.Sprintf("Path: %s\n", uri)) + results.WriteString(fmt.Sprintf("Start Line: %d\n", startLine)) + results.WriteString(fmt.Sprintf("Message: %s\n", message)) + results.WriteString("\n") + } + + return results.String(), nil +} + +// Extract the "results" array from the JSON data +func extractResults(data map[string]interface{}) ([]interface{}, bool) { + runs, ok := data["runs"].([]interface{}) + if !ok { + return nil, false + } + + if len(runs) > 0 { + results, ok := runs[0].(map[string]interface{})["results"].([]interface{}) + if !ok { + return nil, false + } + return results, true + } + + return nil, false +} + +// Find the indices of the new fingerprints in the PR results array +func findNewFingerprintIndices(baselineResults, prResults []interface{}) []int { + var newIndices []int + + for i, prResult := range prResults { + prObject := prResult.(map[string]interface{}) + if prFingerprints, ok := prObject["fingerprints"].(map[string]interface{}); ok { + matchFound := false + for _, baselineResult := range baselineResults { + baselineObject := baselineResult.(map[string]interface{}) + if baselineFingerprints, ok := baselineObject["fingerprints"].(map[string]interface{}); ok { + // Ignore the "identity" key + delete(baselineFingerprints, "identity") + delete(prFingerprints, "identity") + + match := fmt.Sprint(prFingerprints) == fmt.Sprint(baselineFingerprints) + if match { + matchFound = true + break + } + } + } + if !matchFound { + newIndices = append(newIndices, i) + } + } + } + + return newIndices +} + +// Extract new issues objects from the PR "results" array +func extractNewIssues(results []interface{}, indices []int) []interface{} { + var newIssues []interface{} + + for _, idx := range indices { + newIssues = append(newIssues, results[idx]) + } + + return newIssues +} + +// Extract new issue data from the results to output to the console +func extractIssueData(result interface{}) (string, string, string, int) { + resultObj := result.(map[string]interface{}) + level := resultObj["level"].(string) + message := resultObj["message"].(map[string]interface{})["text"].(string) + locations := resultObj["locations"].([]interface{}) + uri := locations[0].(map[string]interface{})["physicalLocation"].(map[string]interface{})["artifactLocation"].(map[string]interface{})["uri"].(string) + startLine := locations[0].(map[string]interface{})["physicalLocation"].(map[string]interface{})["region"].(map[string]interface{})["startLine"].(float64) + return level, message, uri, int(startLine) +} diff --git a/snyk-check_test.go b/snyk-check_test.go new file mode 100644 index 0000000..dd904cf --- /dev/null +++ b/snyk-check_test.go @@ -0,0 +1,239 @@ +package main + +import "testing" + +func TestCheck(t *testing.T) { + type args struct { + file1 map[string]interface{} + file2 map[string]interface{} + } + tests := []struct { + name string + args args + want string + wantErr bool + }{ + { + name: "No new issues", + args: args{ + file1: map[string]interface{}{ + "runs": []interface{}{ + map[string]interface{}{ + "results": []interface{}{ + map[string]interface{}{ + "fingerprints": map[string]interface{}{ + "identity": "12345", + }, + "level": "note", + "message": map[string]interface{}{ + "text": "Issue 1", + }, + "locations": []interface{}{ + map[string]interface{}{ + "physicalLocation": map[string]interface{}{ + "artifactLocation": map[string]interface{}{ + "uri": "file1.go", + }, + "region": map[string]interface{}{ + "startLine": 10.0, + }, + }, + }, + }, + }, + }, + }, + }, + }, + file2: map[string]interface{}{ + "runs": []interface{}{ + map[string]interface{}{ + "results": []interface{}{ + map[string]interface{}{ + "fingerprints": map[string]interface{}{ + "identity": "12345", + }, + "level": "note", + "message": map[string]interface{}{ + "text": "Issue 1", + }, + "locations": []interface{}{ + map[string]interface{}{ + "physicalLocation": map[string]interface{}{ + "artifactLocation": map[string]interface{}{ + "uri": "file1.go", + }, + "region": map[string]interface{}{ + "startLine": 10.0, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + want: "", + wantErr: false, + }, + { + name: "New issue found", + args: args{ + file1: map[string]interface{}{ + "runs": []interface{}{ + map[string]interface{}{ + "results": []interface{}{}, + }, + }, + }, + file2: map[string]interface{}{ + "runs": []interface{}{ + map[string]interface{}{ + "results": []interface{}{ + map[string]interface{}{ + "fingerprints": map[string]interface{}{ + "identity": "67890", + }, + "level": "error", + "message": map[string]interface{}{ + "text": "New Issue", + }, + "locations": []interface{}{ + map[string]interface{}{ + "physicalLocation": map[string]interface{}{ + "artifactLocation": map[string]interface{}{ + "uri": "file2.go", + }, + "region": map[string]interface{}{ + "startLine": 20.0, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + want: "✗ Severity: [High]\nPath: file2.go\nStart Line: 20\nMessage: New Issue\n\n", + wantErr: false, + }, + { + name: "Error extracting baseline results", + args: args{ + file1: map[string]interface{}{ + "invalid_key": "invalid_value", + }, + file2: map[string]interface{}{ + "runs": []interface{}{ + map[string]interface{}{ + "results": []interface{}{}, + }, + }, + }, + }, + want: "", + wantErr: true, + }, + { + name: "Error extracting PR results", + args: args{ + file1: map[string]interface{}{ + "runs": []interface{}{ + map[string]interface{}{ + "results": []interface{}{}, + }, + }, + }, + file2: map[string]interface{}{ + "invalid_key": "invalid_value", + }, + }, + want: "", + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := check(tt.args.file1, tt.args.file2) + if (err != nil) != tt.wantErr { + t.Errorf("check() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("check() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestExtractResults(t *testing.T) { + type args struct { + data map[string]interface{} + } + tests := []struct { + name string + args args + want []interface{} + wantBool bool + }{ + { + name: "Invalid data", + args: args{ + data: map[string]interface{}{ + "runs": []interface{}{ + map[string]interface{}{ + "results": "invalid", + }, + }, + }, + }, + want: nil, + wantBool: false, + }, + { + name: "No results", + args: args{ + data: map[string]interface{}{ + "runs": []interface{}{ + map[string]interface{}{}, + }, + }, + }, + want: nil, + wantBool: false, + }, + { + name: "Runs exist, but empty", + args: args{ + data: map[string]interface{}{ + "runs": []interface{}{}, + }, + }, + want: nil, + wantBool: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, gotBool := extractResults(tt.args.data) + if gotBool != tt.wantBool { + t.Errorf("extractResults() gotBool = %v, want %v", gotBool, tt.wantBool) + } + if gotBool { + if len(got) != len(tt.want) { + t.Errorf("extractResults() got = %v, want %v", got, tt.want) + } else { + for i := range got { + if got[i] != tt.want[i] { + t.Errorf("extractResults() got = %v, want %v", got, tt.want) + } + } + } + } + }) + } +}