From 07f6467f8a377f83d69a7c4095296a82b6b008af Mon Sep 17 00:00:00 2001 From: tonyreed Date: Fri, 11 Oct 2024 10:14:31 -0500 Subject: [PATCH 1/8] remove byteskip & add vendor folder for non-go.mod package --- README.md | 7 -- compare.go | 9 +- compare_test.go | 19 ---- runner.go | 4 - runner_test.go | 16 --- vendor/snyk-code-pr-diff.go | 188 ++++++++++++++++++++++++++++++++++++ 6 files changed, 190 insertions(+), 53 deletions(-) create mode 100644 vendor/snyk-code-pr-diff.go diff --git a/README.md b/README.md index 53aee61..2c968aa 100644 --- a/README.md +++ b/README.md @@ -11,13 +11,6 @@ make build ``` 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..826a6b8 100644 --- a/compare.go +++ b/compare.go @@ -7,9 +7,8 @@ import ( ) type JSONDiff struct { - File1 File - File2 File - ByteSkip bool + File1 File + File2 File } func (j JSONDiff) FindDifferences() string { @@ -25,10 +24,6 @@ func (j JSONDiff) FindDifferences() string { return "No differences found." } - if j.ByteSkip && len(j.File2.Bytes) < len(j.File1.Bytes) { - return "Second file smaller than first and byteskip enabled" - } - if diff := deep.Equal(j.File1.Map, j.File2.Map); diff != nil { differences := "Differences found:" for _, d := range diff { diff --git a/compare_test.go b/compare_test.go index f04c55c..1c4f3f5 100644 --- a/compare_test.go +++ b/compare_test.go @@ -424,22 +424,3 @@ func TestJSONDiff_FindDifferences_NoMap(t *testing.T) { t.Errorf("JSONDiff.FindDifferences() = %v, want %v", got, "No map defined for File1 and/or File2.") } } - -func TestJSONDiff_FindDIfferences_ByteSkip(t *testing.T) { - j := JSONDiff{ - File1: File{ - Bytes: []byte(`{"key1": "value1"}, {"key2": "value2"}`), - Map: map[string]interface{}{"key1": "value1", "key2": "value2"}, - }, - File2: File{ - Bytes: []byte(`{"key1": "value1"}`), - Map: map[string]interface{}{"key1": "value1"}, - }, - 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) - } -} diff --git a/runner.go b/runner.go index 7569467..06eea5c 100644 --- a/runner.go +++ b/runner.go @@ -34,9 +34,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 } 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/vendor/snyk-code-pr-diff.go b/vendor/snyk-code-pr-diff.go new file mode 100644 index 0000000..f3f0881 --- /dev/null +++ b/vendor/snyk-code-pr-diff.go @@ -0,0 +1,188 @@ +package vendor + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "log" + "os" + "path/filepath" + "strings" +) + +// Original author: +// https://github.com/hezro/snyk-code-pr-diff +// Opted to pull in via vendor folder since no go.mod exists +// in the original repo for retrieving with go get +func check() { + + // Check if there are at least two arguments + if len(os.Args) < 3 { + log.Fatal("Usage: go run main.go ") + } + + // Read the baseline JSON file + baselineFile := filepath.Clean(os.Args[1]) + baselineJSON, err := ioutil.ReadFile(baselineFile) + if err != nil { + log.Fatalf("Failed to read the baseline JSON file: %v", err) + } + + // Read the PR JSON file + prFile := filepath.Clean(os.Args[2]) + prJSON, err := ioutil.ReadFile(prFile) + if err != nil { + log.Fatalf("Failed to read the PR JSON file: %v", err) + } + + // Parse the Baseline JSON scan + var baselineData map[string]interface{} + err = json.Unmarshal(baselineJSON, &baselineData) + if err != nil { + log.Fatalf("Failed to parse the Baseline JSON scan: %v", err) + } + + // Parse the PR JSON scan + var prData map[string]interface{} + err = json.Unmarshal(prJSON, &prData) + if err != nil { + log.Fatalf("Failed to parse the PR JSON scan: %v", err) + } + + fmt.Printf("\n") + fmt.Printf("Running Snyk Code PR Diff") + fmt.Printf("\n") + + // Extract the "results" array from the Baseline scan + baselineResults, ok := extractResults(baselineData) + if !ok { + log.Fatal("Failed to extract 'results' from the Baseline scan") + } + + // Extract the "results" array from the PR scan + prResults, ok := extractResults(prData) + if !ok { + log.Fatal("Failed to extract 'results' from 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) + + // Count the number of new issues found from the PR results + issueCount := len(newIssues) + + // 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) + fmt.Printf("✗ Severity: [%s]\n", level) + fmt.Printf("Path: %s\n", uri) + fmt.Printf("Start Line: %d\n", startLine) + fmt.Printf("Message: %s\n", message) + fmt.Printf("\n") + } + + // Output the count new issues found from the PR results + if issueCount > 0 { + fmt.Printf("\n") + fmt.Printf("Total issues found: %d\n", issueCount) + + // Replace the "results" array in the PR scan with only the new issues found + prData["runs"].([]interface{})[0].(map[string]interface{})["results"] = newIssues + + // Convert the new PR data to JSON + updatedPRScan, err := json.Marshal(prData) + if err != nil { + log.Fatalf("Failed to convert updated data to JSON: %v", err) + } + + // Write the updated PR diff scan to a file + err = ioutil.WriteFile("snyk_code_pr_diff_scan.json", updatedPRScan, 0644) + if err != nil { + log.Fatalf("Failed to write updated data to file: %v", err) + } + fmt.Printf("\n") + fmt.Println("Results saved in usnyk_code_pr_diff_scan.json") + os.Exit(1) + } + + fmt.Printf("\n") + fmt.Println("No issues found!") + +} + +// 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) +} \ No newline at end of file From baafd55ca27c5268220c26ae2c1e4fb31d891e53 Mon Sep 17 00:00:00 2001 From: tonyreed Date: Fri, 11 Oct 2024 10:38:44 -0500 Subject: [PATCH 2/8] update json diff tool to be snyk-specific --- compare.go | 22 ++-- compare_test.go | 10 +- go.mod | 2 - runner.go | 2 +- vendor/snyk-code-pr-diff.go => snyk-check.go | 130 +++++++------------ 5 files changed, 59 insertions(+), 107 deletions(-) rename vendor/snyk-code-pr-diff.go => snyk-check.go (54%) diff --git a/compare.go b/compare.go index 826a6b8..ad08681 100644 --- a/compare.go +++ b/compare.go @@ -2,8 +2,6 @@ package main import ( "bytes" - "fmt" - "github.com/go-test/deep" ) type JSONDiff struct { @@ -11,27 +9,23 @@ type JSONDiff struct { 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 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 + output, err := check(j.File1.Map, j.File2.Map) + if err != nil { + return "", err } - return "No differences found." + return output, nil } diff --git a/compare_test.go b/compare_test.go index 1c4f3f5..7692458 100644 --- a/compare_test.go +++ b/compare_test.go @@ -54,7 +54,7 @@ func TestJSONDiff_FindDifferences(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if got := tt.j.FindDifferences(); got != tt.want { + if got, _ := tt.j.FindDifferences(); got != tt.want { t.Errorf("JSONDiff.FindDifferences() = %v, want %v", got, tt.want) } }) @@ -207,7 +207,7 @@ func TestJSONDiff_FindDifferencesWithSnykShape_Same(t *testing.T) { for _, tt := range snykTests { t.Run(tt.name, func(t *testing.T) { - if got := tt.j.FindDifferences(); got != "No differences found." { + if got, _ := tt.j.FindDifferences(); got != "No differences found." { t.Errorf("JSONDiff.FindDifferences() = %v)", got) } }) @@ -384,7 +384,7 @@ func TestJSONDiff_FindDifferencesWithSnykShape_Vulnerabilities(t *testing.T) { for _, tt := range snykTests { t.Run(tt.name, func(t *testing.T) { - if got := tt.j.FindDifferences(); got == "No differences found." { + if got, _ := tt.j.FindDifferences(); got == "No differences found." { t.Errorf("JSONDiff.FindDifferences() = %v)", got) } }) @@ -403,7 +403,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,7 +420,7 @@ 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.") } } 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/runner.go b/runner.go index 06eea5c..8b07b1c 100644 --- a/runner.go +++ b/runner.go @@ -34,5 +34,5 @@ func (r Runner) Run(reader FileReader) (string, error) { File2: files[1], } - return comparator.FindDifferences(), nil + return comparator.FindDifferences() } diff --git a/vendor/snyk-code-pr-diff.go b/snyk-check.go similarity index 54% rename from vendor/snyk-code-pr-diff.go rename to snyk-check.go index f3f0881..fe7beb8 100644 --- a/vendor/snyk-code-pr-diff.go +++ b/snyk-check.go @@ -1,68 +1,27 @@ -package vendor +package main import ( - "encoding/json" + "errors" "fmt" - "io/ioutil" - "log" - "os" - "path/filepath" "strings" ) -// Original author: -// https://github.com/hezro/snyk-code-pr-diff -// Opted to pull in via vendor folder since no go.mod exists -// in the original repo for retrieving with go get -func check() { - - // Check if there are at least two arguments - if len(os.Args) < 3 { - log.Fatal("Usage: go run main.go ") - } - - // Read the baseline JSON file - baselineFile := filepath.Clean(os.Args[1]) - baselineJSON, err := ioutil.ReadFile(baselineFile) - if err != nil { - log.Fatalf("Failed to read the baseline JSON file: %v", err) - } - - // Read the PR JSON file - prFile := filepath.Clean(os.Args[2]) - prJSON, err := ioutil.ReadFile(prFile) - if err != nil { - log.Fatalf("Failed to read the PR JSON file: %v", err) - } - - // Parse the Baseline JSON scan - var baselineData map[string]interface{} - err = json.Unmarshal(baselineJSON, &baselineData) - if err != nil { - log.Fatalf("Failed to parse the Baseline JSON scan: %v", err) - } - - // Parse the PR JSON scan - var prData map[string]interface{} - err = json.Unmarshal(prJSON, &prData) - if err != nil { - log.Fatalf("Failed to parse the PR JSON scan: %v", err) - } - - fmt.Printf("\n") - fmt.Printf("Running Snyk Code PR Diff") - fmt.Printf("\n") +// 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(baselineData) + baselineResults, ok := extractResults(baseline) if !ok { - log.Fatal("Failed to extract 'results' from the Baseline scan") + return "", errors.New("failed to extract 'results' from the Baseline scan") } // Extract the "results" array from the PR scan - prResults, ok := extractResults(prData) + prResults, ok := extractResults(feature) if !ok { - log.Fatal("Failed to extract 'results' from PR scan") + return "", errors.New("failed to extract 'results' from the PR scan") } // Find the indices of new fingerprints from the PR results @@ -72,7 +31,7 @@ func check() { newIssues := extractNewIssues(prResults, newIndices) // Count the number of new issues found from the PR results - issueCount := len(newIssues) + //issueCount := len(newIssues) // Output the new issues from the PR results for _, result := range newIssues { @@ -80,39 +39,40 @@ func check() { level = strings.Replace(level, "note", "Low", 1) level = strings.Replace(level, "warning", "Medium", 1) level = strings.Replace(level, "error", "High", 1) - fmt.Printf("✗ Severity: [%s]\n", level) - fmt.Printf("Path: %s\n", uri) - fmt.Printf("Start Line: %d\n", startLine) - fmt.Printf("Message: %s\n", message) - fmt.Printf("\n") + 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") } - // Output the count new issues found from the PR results - if issueCount > 0 { - fmt.Printf("\n") - fmt.Printf("Total issues found: %d\n", issueCount) - - // Replace the "results" array in the PR scan with only the new issues found - prData["runs"].([]interface{})[0].(map[string]interface{})["results"] = newIssues - - // Convert the new PR data to JSON - updatedPRScan, err := json.Marshal(prData) - if err != nil { - log.Fatalf("Failed to convert updated data to JSON: %v", err) - } - - // Write the updated PR diff scan to a file - err = ioutil.WriteFile("snyk_code_pr_diff_scan.json", updatedPRScan, 0644) - if err != nil { - log.Fatalf("Failed to write updated data to file: %v", err) - } - fmt.Printf("\n") - fmt.Println("Results saved in usnyk_code_pr_diff_scan.json") - os.Exit(1) - } - - fmt.Printf("\n") - fmt.Println("No issues found!") + //// Output the count new issues found from the PR results + //if issueCount > 0 { + // results.WriteString("\n") + // results.WriteString(fmt.Sprintf("Total issues found: %d\n", issueCount)) + // + // // Replace the "results" array in the PR scan with only the new issues found + // feature["runs"].([]interface{})[0].(map[string]interface{})["results"] = newIssues + // + // // Convert the new PR data to JSON + // updatedPRScan, err := json.Marshal(feature) + // if err != nil { + // return "", errors.New("failed to convert updated data to JSON") + // } + // + // // Write the updated PR diff scan to a file + // //err = ioutil.WriteFile("snyk_code_pr_diff_scan.json", updatedPRScan, 0644) + // //if err != nil { + // // log.Fatalf("Failed to write updated data to file: %v", err) + // //} + // //fmt.Printf("\n") + // //fmt.Println("Results saved in usnyk_code_pr_diff_scan.json") + //} + + results.WriteString("\n") + results.WriteString("No issues found!") + + return results.String(), nil } @@ -185,4 +145,4 @@ func extractIssueData(result interface{}) (string, string, string, int) { 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) -} \ No newline at end of file +} From e0fda177257c1936ef4db7c3fe32ccb70a6d67cc Mon Sep 17 00:00:00 2001 From: tonyreed Date: Fri, 11 Oct 2024 10:40:57 -0500 Subject: [PATCH 3/8] remove out of date comment --- runner.go | 3 --- snyk-check.go | 29 ----------------------------- 2 files changed, 32 deletions(-) diff --git a/runner.go b/runner.go index 8b07b1c..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") diff --git a/snyk-check.go b/snyk-check.go index fe7beb8..91a7f0e 100644 --- a/snyk-check.go +++ b/snyk-check.go @@ -30,9 +30,6 @@ func check(baseline map[string]interface{}, feature map[string]interface{}) (str // Extract the new issues objects from the PR results newIssues := extractNewIssues(prResults, newIndices) - // Count the number of new issues found from the PR results - //issueCount := len(newIssues) - // Output the new issues from the PR results for _, result := range newIssues { level, message, uri, startLine := extractIssueData(result) @@ -46,32 +43,6 @@ func check(baseline map[string]interface{}, feature map[string]interface{}) (str results.WriteString("\n") } - //// Output the count new issues found from the PR results - //if issueCount > 0 { - // results.WriteString("\n") - // results.WriteString(fmt.Sprintf("Total issues found: %d\n", issueCount)) - // - // // Replace the "results" array in the PR scan with only the new issues found - // feature["runs"].([]interface{})[0].(map[string]interface{})["results"] = newIssues - // - // // Convert the new PR data to JSON - // updatedPRScan, err := json.Marshal(feature) - // if err != nil { - // return "", errors.New("failed to convert updated data to JSON") - // } - // - // // Write the updated PR diff scan to a file - // //err = ioutil.WriteFile("snyk_code_pr_diff_scan.json", updatedPRScan, 0644) - // //if err != nil { - // // log.Fatalf("Failed to write updated data to file: %v", err) - // //} - // //fmt.Printf("\n") - // //fmt.Println("Results saved in usnyk_code_pr_diff_scan.json") - //} - - results.WriteString("\n") - results.WriteString("No issues found!") - return results.String(), nil } From cd93b5bc3f6eee2d1b8c96b62c6b29f8012c1c00 Mon Sep 17 00:00:00 2001 From: tonyreed Date: Fri, 11 Oct 2024 12:14:22 -0500 Subject: [PATCH 4/8] add snyk check file & add full test coverage for it --- README.md | 4 +- compare_test.go | 393 ++------------------------------------------- snyk-check_test.go | 239 +++++++++++++++++++++++++++ 3 files changed, 252 insertions(+), 384 deletions(-) create mode 100644 snyk-check_test.go diff --git a/README.md b/README.md index 2c968aa..70b4cb8 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@ [![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 diff --git a/compare_test.go b/compare_test.go index 7692458..0916844 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) } } 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) + } + } + } + } + }) + } +} From d1bee5c82aa365b7ea65aa8a2c395f39a292342b Mon Sep 17 00:00:00 2001 From: tonyreed Date: Fri, 11 Oct 2024 12:18:34 -0500 Subject: [PATCH 5/8] add return type test --- compare_test.go | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/compare_test.go b/compare_test.go index 0916844..aeaebe7 100644 --- a/compare_test.go +++ b/compare_test.go @@ -53,3 +53,33 @@ func TestJSONDiff_FindDifferences_NoMap(t *testing.T) { t.Errorf("JSONDiff.FindDifferences() = %v, want %v", got, "No map defined for File1 and/or File2.") } } + +func TestJSONDiff_FindDifferences(t *testing.T) { + j := JSONDiff{ + File1: File{ + Bytes: []byte(`{"runs": "value1"}`), + Map: map[string]interface{}{ + "runs": []interface{}{ + map[string]interface{}{ + "results": []interface{}{}, + }, + }, + }, + }, + File2: File{ + Bytes: []byte(`{"runs": "value2"}`), + Map: map[string]interface{}{ + "runs": []interface{}{ + map[string]interface{}{ + "results": []interface{}{}, + }, + }, + }, + }, + } + + _, err := j.FindDifferences() + if err != nil { + t.Errorf("JSONDiff.FindDifferences() error = %v", err) + } +} From 6ca754994d222d0eb9e12223c03bfd533cb7b9fd Mon Sep 17 00:00:00 2001 From: tonyreed Date: Fri, 11 Oct 2024 12:32:15 -0500 Subject: [PATCH 6/8] update error response to exit code 1 when new vulnerabilities are found by default like Snyk CLI would do --- compare.go | 3 ++- main.go | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/compare.go b/compare.go index ad08681..9998013 100644 --- a/compare.go +++ b/compare.go @@ -2,6 +2,7 @@ package main import ( "bytes" + "errors" ) type JSONDiff struct { @@ -27,5 +28,5 @@ func (j JSONDiff) FindDifferences() (string, error) { return "", err } - return output, nil + return output, errors.New("new vulnerability(s) found") } 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) From 327be34e5442402af2e7dcbca1c6693ceb7bcdde Mon Sep 17 00:00:00 2001 From: tonyreed Date: Fri, 11 Oct 2024 12:35:55 -0500 Subject: [PATCH 7/8] update test case --- compare_test.go | 4 ++-- snyk-check.go | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/compare_test.go b/compare_test.go index aeaebe7..c9adf7f 100644 --- a/compare_test.go +++ b/compare_test.go @@ -78,8 +78,8 @@ func TestJSONDiff_FindDifferences(t *testing.T) { }, } - _, err := j.FindDifferences() - if err != nil { + output, err := j.FindDifferences() + if err == nil || output != "" { t.Errorf("JSONDiff.FindDifferences() error = %v", err) } } diff --git a/snyk-check.go b/snyk-check.go index 91a7f0e..4b6c80f 100644 --- a/snyk-check.go +++ b/snyk-check.go @@ -44,7 +44,6 @@ func check(baseline map[string]interface{}, feature map[string]interface{}) (str } return results.String(), nil - } // Extract the "results" array from the JSON data From 315de541d67d0bfa0ad50a73b322c1619c3af3e2 Mon Sep 17 00:00:00 2001 From: tonyreed Date: Fri, 11 Oct 2024 12:36:48 -0500 Subject: [PATCH 8/8] update workflow name --- .github/workflows/tests.yaml | 1 + 1 file changed, 1 insertion(+) 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