From 30e545700bb46d508570bf735bf26616123944e5 Mon Sep 17 00:00:00 2001 From: Marcus Watson Date: Wed, 27 Sep 2023 17:48:14 +0100 Subject: [PATCH 01/13] Small refactor to optimise API URL generation Tests to support varying cases in scheme/host Unit tests around platform url checking in general --- data/app_info.go | 2 +- data/build_info.go | 2 +- main.go | 6 ++- utils/platform_url.go | 96 +++++++++++++++++++++++++++++-------------- 4 files changed, 72 insertions(+), 34 deletions(-) diff --git a/data/app_info.go b/data/app_info.go index e2f27f9..3972b0d 100644 --- a/data/app_info.go +++ b/data/app_info.go @@ -22,7 +22,7 @@ type appInfoApplication struct { } func (api API) populateAppInfo(report *report.Report) { - url := fmt.Sprintf("https://analysiscenter.veracode.com/api/5.0/getappinfo.do?app_id=%d", report.Scan.ApplicationId) + url := fmt.Sprintf("/api/5.0/getappinfo.do?app_id=%d", report.Scan.ApplicationId) response := api.makeApiRequest(url, http.MethodGet) data := appInfo{} diff --git a/data/build_info.go b/data/build_info.go index c43785f..fe5c060 100644 --- a/data/build_info.go +++ b/data/build_info.go @@ -20,7 +20,7 @@ type buildInfoBuild struct { } func (api API) populateBuildInfo(report *report.Report) { - var url = fmt.Sprintf("https://analysiscenter.veracode.com/api/5.0/getbuildinfo.do?app_id=%d&build_id=%d", report.Scan.ApplicationId, report.Scan.BuildId) + var url = fmt.Sprintf("/api/5.0/getbuildinfo.do?app_id=%d&build_id=%d", report.Scan.ApplicationId, report.Scan.BuildId) response := api.makeApiRequest(url, http.MethodGet) data := buildInfo{} diff --git a/main.go b/main.go index 3e824a3..ba24f18 100644 --- a/main.go +++ b/main.go @@ -55,7 +55,11 @@ func main() { apiId, apiKey := getCredentials(*vid, *vkey, *profile) api := data.API{Id: apiId, Key: apiKey, Region: regionToUse, AppVersion: AppVersion, EnableCaching: *enableCaching} - buildId := utils.ParseBuildIdFromPlatformUrl(*scan) + + buildId, err := utils.ParseBuildIdFromScanInformation(*scan) + if err != nil { + utils.ErrorAndExit("", err) + } api.AssertCredentialsWork() diff --git a/utils/platform_url.go b/utils/platform_url.go index 489b6ca..6990c51 100644 --- a/utils/platform_url.go +++ b/utils/platform_url.go @@ -1,7 +1,9 @@ package utils import ( + "errors" "fmt" + "net/url" "strconv" "strings" ) @@ -16,75 +18,107 @@ var supportedPages = []string{ "ViewReportsDetailedReport", "ReviewResultsSCA"} -func PlatformUrlInvalid(url string) { - ErrorAndExit(fmt.Sprintf("%s is not a valid or supported Veracode Platform URL.\nThis tool requires a URL to one of the following Veracode Platform pages: %s", - url, - strings.Join(supportedPages, ", ")), nil) +type Region struct { + ID string + URL string } -func IsPlatformURL(url string) bool { - return strings.HasPrefix(url, "https://analysiscenter.veracode.com/auth/index.jsp") || - strings.HasPrefix(url, "https://analysiscenter.veracode.us/auth/index.jsp") || - strings.HasPrefix(url, "https://analysiscenter.veracode.eu/auth/index.jsp") +var regions = []Region{ + {ID: "european", URL: "https://analysiscenter.veracode.eu"}, + {ID: "us", URL: "https://analysiscenter.veracode.us"}, + {ID: "commercial", URL: "https://analysiscenter.veracode.com"}, } -func IsParseableURL(urlFragment string) bool { - for _, page := range supportedPages { - if strings.HasPrefix(urlFragment, page) { +var defaultRegion = regions[2] + +func reportPlatformUrlInvalid(url string) string { + return fmt.Sprintf("%s is not a valid or supported Veracode Platform URL.\nThis tool requires a URL to one of the following Veracode Platform pages: %s", + url, + strings.Join(supportedPages, ", ")) +} + +func IsPlatformURL(platformUrl string) bool { + + // We need to normalize, as schemes and hosts may be + // variable casing (as per RFC3986) + parsedInputURL, err := url.Parse(strings.ToLower(platformUrl)) + if err != nil { + return false + } + + baseInputURL := fmt.Sprintf("%s://%s%s", + strings.ToLower(parsedInputURL.Scheme), + strings.ToLower(parsedInputURL.Host), + parsedInputURL.Path) + + for _, region := range regions { + if strings.HasPrefix(baseInputURL, region.URL+"/auth/index.jsp") { return true } } + return false } func ParseRegionFromUrl(url string) string { - if strings.HasPrefix(url, "https://analysiscenter.veracode.us") { - return "us" - } - if strings.HasPrefix(url, "https://analysiscenter.veracode.eu") { - return "european" + for _, region := range regions { + if strings.HasPrefix(strings.ToLower(url), region.URL) { + return region.ID + } } - return "commercial" + // Should we raise an error here instead? + return defaultRegion.ID } func ParseBaseUrlFromRegion(region string) string { - if region == "us" { - return "https://analysiscenter.veracode.us" - } - if region == "european" { - return "https://analysiscenter.veracode.eu" + for _, regionData := range regions { + if regionData.ID == region { + return regionData.URL + } } - return "https://analysiscenter.veracode.com" + return defaultRegion.URL } -func ParseBuildIdFromPlatformUrl(urlOrBuildId string) int { +func isParseableURL(urlFragment string) bool { + for _, page := range supportedPages { + if strings.HasPrefix(urlFragment, page) { + return true + } + } + return false +} + +func ParseBuildIdFromScanInformation(urlOrBuildId string) (int, error) { buildId, err := strconv.Atoi(urlOrBuildId) if err == nil { - return buildId + if buildId <= 0 { + return -1, errors.New("build ID must be positive") + } + + return buildId, nil } if !IsPlatformURL(urlOrBuildId) { - PlatformUrlInvalid(urlOrBuildId) + return -1, errors.New(reportPlatformUrlInvalid(urlOrBuildId)) } var urlFragment = strings.Split(urlOrBuildId, "#")[1] - if IsParseableURL(urlFragment) { + if isParseableURL(urlFragment) { buildId, err := strconv.Atoi(strings.Split(urlFragment, ":")[3]) if err != nil { - PlatformUrlInvalid(urlOrBuildId) + return -1, errors.New(reportPlatformUrlInvalid(urlOrBuildId)) } - return buildId + return buildId, nil } - PlatformUrlInvalid(urlOrBuildId) - return -1 + return -1, errors.New(reportPlatformUrlInvalid(urlOrBuildId)) } From 78f7302d51ec0946183c0ed2f74125b4fae7c804 Mon Sep 17 00:00:00 2001 From: Marcus Watson Date: Wed, 27 Sep 2023 17:49:16 +0100 Subject: [PATCH 02/13] Small refactor to optimise API URL generation Tests to support varying cases in scheme/host Unit tests around platform url checking in general --- data/api.go | 16 +++---- data/detailed_report.go | 2 +- data/prescan_file_list.go | 2 +- data/prescan_module_list.go | 2 +- data/sandbox_info.go | 2 +- utils/platform_url_test.go | 87 +++++++++++++++++++++++++++++++++++++ 6 files changed, 98 insertions(+), 13 deletions(-) create mode 100644 utils/platform_url_test.go diff --git a/data/api.go b/data/api.go index 04421a8..f074da4 100644 --- a/data/api.go +++ b/data/api.go @@ -20,21 +20,19 @@ type API struct { } func (api API) makeApiRequest(apiUrl, httpMethod string) []byte { - if api.Region == "us" { - apiUrl = strings.Replace(apiUrl, ".com", ".us", 1) - } else if api.Region == "european" { - apiUrl = strings.Replace(apiUrl, ".com", ".eu", 1) - } + + baseUrl := utils.ParseBaseUrlFromRegion(api.Region) + fullUrl := baseUrl + apiUrl if api.EnableCaching { - cachedResponse := getCachedResponse(apiUrl) + cachedResponse := getCachedResponse(fullUrl) if cachedResponse != nil { return cachedResponse } } - parsedUrl, err := url.Parse(apiUrl) + parsedUrl, err := url.Parse(fullUrl) if err != nil { utils.ErrorAndExit("Invalid API URL", err) @@ -85,12 +83,12 @@ func (api API) makeApiRequest(apiUrl, httpMethod string) []byte { } if api.EnableCaching { - cacheResponse(apiUrl, body) + cacheResponse(fullUrl, body) } return body } func (api API) AssertCredentialsWork() { - api.makeApiRequest("https://analysiscenter.veracode.com/api/3.0/getmaintenancescheduleinfo.do", http.MethodGet) + api.makeApiRequest("/api/3.0/getmaintenancescheduleinfo.do", http.MethodGet) } diff --git a/data/detailed_report.go b/data/detailed_report.go index 55e7e68..1de4795 100644 --- a/data/detailed_report.go +++ b/data/detailed_report.go @@ -77,7 +77,7 @@ type detailedReportSCAComponent struct { func (api API) populateDetailedReport(r *report.Report) { detailedReport := detailedReport{} - var url = fmt.Sprintf("https://analysiscenter.veracode.com/api/5.0/detailedreport.do?build_id=%d", r.Scan.BuildId) + var url = fmt.Sprintf("/api/5.0/detailedreport.do?build_id=%d", r.Scan.BuildId) response := api.makeApiRequest(url, http.MethodGet) if strings.Contains(string(response[:]), "No report available.") { diff --git a/data/prescan_file_list.go b/data/prescan_file_list.go index 368fe02..5fcb72b 100644 --- a/data/prescan_file_list.go +++ b/data/prescan_file_list.go @@ -24,7 +24,7 @@ type fileListFile struct { } func (api API) getPrescanFileList(r *report.Report) { - var url = fmt.Sprintf("https://analysiscenter.veracode.com/api/5.0/getfilelist.do?app_id=%d&build_id=%d", r.Scan.ApplicationId, r.Scan.BuildId) + var url = fmt.Sprintf("/api/5.0/getfilelist.do?app_id=%d&build_id=%d", r.Scan.ApplicationId, r.Scan.BuildId) response := api.makeApiRequest(url, http.MethodGet) fileList := prescanFileList{} diff --git a/data/prescan_module_list.go b/data/prescan_module_list.go index 1fbb2de..3d1b457 100644 --- a/data/prescan_module_list.go +++ b/data/prescan_module_list.go @@ -43,7 +43,7 @@ type prescanModuleIssue struct { } func (api API) getPrescanModuleList(r *report.Report) { - var url = fmt.Sprintf("https://analysiscenter.veracode.com/api/5.0/getprescanresults.do?app_id=%d&build_id=%d", r.Scan.ApplicationId, r.Scan.BuildId) + var url = fmt.Sprintf("/api/5.0/getprescanresults.do?app_id=%d&build_id=%d", r.Scan.ApplicationId, r.Scan.BuildId) response := api.makeApiRequest(url, http.MethodGet) moduleList := prescanModuleList{} diff --git a/data/sandbox_info.go b/data/sandbox_info.go index 1b71e4e..91302ce 100644 --- a/data/sandbox_info.go +++ b/data/sandbox_info.go @@ -21,7 +21,7 @@ type sandboxInfo struct { } func (api API) populateSandboxInfo(report *report.Report) { - var url = fmt.Sprintf("https://analysiscenter.veracode.com/api/5.0/getsandboxlist.do?app_id=%d", report.Scan.ApplicationId) + var url = fmt.Sprintf("/api/5.0/getsandboxlist.do?app_id=%d", report.Scan.ApplicationId) response := api.makeApiRequest(url, http.MethodGet) data := sandboxList{} diff --git a/utils/platform_url_test.go b/utils/platform_url_test.go new file mode 100644 index 0000000..3c9bb90 --- /dev/null +++ b/utils/platform_url_test.go @@ -0,0 +1,87 @@ +package utils + +import ( + "github.com/stretchr/testify/assert" + "testing" +) + +func TestPlatformUrl(t *testing.T) { + t.Run("Valid Platform Url", func(t *testing.T) { + t.Parallel() + assert.True(t, IsPlatformURL("https://analysiscenter.veracode.com/auth/index.jsp#AnalyzeAppModuleList:41807:380748:24113946:5206587:24100796::::469963")) + }) + + t.Run("Valid Url, not the platform", func(t *testing.T) { + t.Parallel() + assert.False(t, IsPlatformURL("https://test.com/")) + }) + + t.Run("Valid Platform Url, mixed scheme/host case", func(t *testing.T) { + t.Parallel() + assert.True(t, IsPlatformURL("hTtps://aNalysiscenter.verAcode.cOm/auth/index.jsp#AnalyzeAppModuleList:41807:380748:24113946:5206587:24100796::::469963")) + }) + + t.Run("Valid Platform Domain, not a valid path", func(t *testing.T) { + t.Parallel() + assert.False(t, IsPlatformURL("https://analysiscenter.veracode.com/help")) + }) + + t.Run("Check Commercial Region From Url", func(t *testing.T) { + t.Parallel() + assert.Equal(t, ParseRegionFromUrl("https://analysiscenter.veracode.com/auth/index.jsp#AnalyzeAppModuleList:41807:380748:24113946:5206587:24100796::::469963"), "commercial") + }) + + t.Run("Check EU Region From Url", func(t *testing.T) { + t.Parallel() + assert.Equal(t, ParseRegionFromUrl("https://analysiscenter.veracode.eu/auth/index.jsp#AnalyzeAppModuleList:41807:380748:24113946:5206587:24100796::::469963"), "european") + }) + + t.Run("Check US Region From Url", func(t *testing.T) { + t.Parallel() + assert.Equal(t, ParseRegionFromUrl("https://analysiscenter.veracode.us/auth/index.jsp#AnalyzeAppModuleList:41807:380748:24113946:5206587:24100796::::469963"), "us") + }) + + t.Run("Get Base URL for Commercial", func(t *testing.T) { + t.Parallel() + assert.Equal(t, ParseBaseUrlFromRegion("commercial"), "https://analysiscenter.veracode.com") + }) + + t.Run("Get Base URL defaults to Commercial", func(t *testing.T) { + t.Parallel() + assert.Equal(t, ParseBaseUrlFromRegion("region_does_not_exist"), "https://analysiscenter.veracode.com") + }) + + t.Run("Get Base URL for EU", func(t *testing.T) { + t.Parallel() + assert.Equal(t, ParseBaseUrlFromRegion("european"), "https://analysiscenter.veracode.eu") + }) + + t.Run("Get Build ID from Valid Build ID", func(t *testing.T) { + t.Parallel() + buildId, err := ParseBuildIdFromScanInformation("12345678") + assert.Equal(t, buildId, 12345678) + assert.Nil(t, err) + + }) + + t.Run("Get Build ID from Valid URL", func(t *testing.T) { + t.Parallel() + buildId, err := ParseBuildIdFromScanInformation("https://analysiscenter.veracode.com/auth/index.jsp#AnalyzeAppModuleList:41807:380748:12345678:5206587:24100796::::469963") + assert.Equal(t, buildId, 12345678) + assert.Nil(t, err) + }) + + t.Run("Get Error From Negative Build ID", func(t *testing.T) { + t.Parallel() + buildId, err := ParseBuildIdFromScanInformation("-40") + assert.Equal(t, buildId, -1) + assert.Contains(t, err.Error(), "build ID must be positive") + }) + + t.Run("Get Error From Build ID in invalid URL", func(t *testing.T) { + t.Parallel() + buildId, err := ParseBuildIdFromScanInformation("https://analysisceasdasdnter.veracode.com/auth/index.jsp#AnalyzeAppModuleList:41807:380748:12345678:5206587:24100796::::469963") + assert.Equal(t, buildId, -1) + assert.Contains(t, err.Error(), "not a valid or supported Veracode Platform URL") + }) +} From 0f0d04427fdc450cd7fb7d2e5c29baf577834702 Mon Sep 17 00:00:00 2001 From: Marcus Watson Date: Thu, 28 Sep 2023 09:51:22 +0100 Subject: [PATCH 03/13] Made region validity less 'magic' --- main.go | 11 ++++++----- utils/platform_url.go | 9 +++++++++ utils/platform_url_test.go | 15 ++++++++++++++- 3 files changed, 29 insertions(+), 6 deletions(-) diff --git a/main.go b/main.go index ba24f18..0864446 100644 --- a/main.go +++ b/main.go @@ -3,12 +3,13 @@ package main import ( "flag" "fmt" + "os" + "strings" + "github.com/antfie/scan_health/v2/checks" "github.com/antfie/scan_health/v2/data" "github.com/antfie/scan_health/v2/report" "github.com/antfie/scan_health/v2/utils" - "os" - "strings" ) func main() { @@ -16,7 +17,7 @@ func main() { vid := flag.String("vid", "", "Veracode API ID - See https://docs.veracode.com/r/t_create_api_creds") vkey := flag.String("vkey", "", "Veracode API key - See https://docs.veracode.com/r/t_create_api_creds") profile := flag.String("profile", "default", "Veracode credential profile - See https://docs.veracode.com/r/c_httpie_tool#using-multiple-profiles") - region := flag.String("region", "", "Veracode Region [commercial, us, european]") + region := flag.String("region", "", "Veracode Region [commercial, us, european]. Required if a Build ID is specified.") scan := flag.String("sast", "", "Veracode Platform URL or build ID for a SAST application health review") outputFormat := flag.String("format", "console", "Output format [console, json]") jsonFilePath := flag.String("json-file", "", "Optional file for writing JSON output to") @@ -25,8 +26,8 @@ func main() { flag.Parse() - if !(*region == "" || *region == "commercial" || *region == "us" || *region == "european") { - utils.ErrorAndExitWithUsage("Invalid region. Must be either \"commercial\", \"us\" or \"european\"") + if *region != "" && utils.IsValidRegion(*region) == false { + utils.ErrorAndExitWithUsage(fmt.Sprintf("Invalid region \"%s\". Must be either \"commercial\", \"us\" or \"european\"", *region)) } if *region != "" && diff --git a/utils/platform_url.go b/utils/platform_url.go index 6990c51..182e14a 100644 --- a/utils/platform_url.go +++ b/utils/platform_url.go @@ -83,6 +83,15 @@ func ParseBaseUrlFromRegion(region string) string { return defaultRegion.URL } +func IsValidRegion(region string) bool { + for _, regionData := range regions { + if regionData.ID == region { + return true + } + } + return false +} + func isParseableURL(urlFragment string) bool { for _, page := range supportedPages { if strings.HasPrefix(urlFragment, page) { diff --git a/utils/platform_url_test.go b/utils/platform_url_test.go index 3c9bb90..905ad40 100644 --- a/utils/platform_url_test.go +++ b/utils/platform_url_test.go @@ -1,8 +1,9 @@ package utils import ( - "github.com/stretchr/testify/assert" "testing" + + "github.com/stretchr/testify/assert" ) func TestPlatformUrl(t *testing.T) { @@ -84,4 +85,16 @@ func TestPlatformUrl(t *testing.T) { assert.Equal(t, buildId, -1) assert.Contains(t, err.Error(), "not a valid or supported Veracode Platform URL") }) + + t.Run("Checking A Valid Region", func(t *testing.T) { + t.Parallel() + validRegion := IsValidRegion("european") + assert.True(t, validRegion) + }) + + t.Run("Checking An Invalid Region", func(t *testing.T) { + t.Parallel() + validRegion := IsValidRegion("apac") + assert.False(t, validRegion) + }) } From ff74e8a2424579be98ccdc20b3e3c04b57fc0f98 Mon Sep 17 00:00:00 2001 From: Marcus Watson Date: Thu, 28 Sep 2023 11:09:22 +0100 Subject: [PATCH 04/13] Improved tests for readability --- checks/dependencies_selected_test.go | 4 +- checks/fatal_errors_test.go | 9 +- checks/files_to_ignore_test.go | 5 +- checks/flaw_count_test.go | 9 +- checks/minified_javascript_test.go | 23 +++-- checks/missing_debug_symbols_test.go | 12 +-- checks/overscanning_test.go | 11 +-- checks/regular_scans_test.go | 3 +- checks/testing_artefacts_test.go | 16 ++-- checks/third_party_test.go | 8 +- checks/unexpected_source_code_test.go | 93 ++++++++++++++++++++ checks/unscannable_java_test.go | 102 ++++++++++++++++++++++ checks/unselected_first_party_test.go | 120 ++++++++++++++++++++++++++ 13 files changed, 361 insertions(+), 54 deletions(-) create mode 100644 checks/unexpected_source_code_test.go create mode 100644 checks/unscannable_java_test.go create mode 100644 checks/unselected_first_party_test.go diff --git a/checks/dependencies_selected_test.go b/checks/dependencies_selected_test.go index 27d0e51..39ed7d8 100644 --- a/checks/dependencies_selected_test.go +++ b/checks/dependencies_selected_test.go @@ -20,7 +20,7 @@ func TestDependenciesSelected(t *testing.T) { dependenciesSelected(mockReport) - assert.Equal(t, len(mockReport.Issues), 0, "Issues were reported which should not have been") + assert.Empty(t, mockReport.Issues, "Issues were reported which should not have been") }) t.Run("Selected dependencies should be reported", func(t *testing.T) { @@ -37,6 +37,6 @@ func TestDependenciesSelected(t *testing.T) { dependenciesSelected(mockReport) - assert.Equal(t, len(mockReport.Issues), 1, "Issues were not reported which should have been") + assert.Equal(t, 1, len(mockReport.Issues), "Issues were not reported which should have been") }) } diff --git a/checks/fatal_errors_test.go b/checks/fatal_errors_test.go index 30cd016..5259518 100644 --- a/checks/fatal_errors_test.go +++ b/checks/fatal_errors_test.go @@ -1,7 +1,6 @@ package checks import ( - "strings" "testing" "github.com/antfie/scan_health/v2/report" @@ -79,7 +78,7 @@ func TestModulesAreFine(t *testing.T) { t.FailNow() } - if !assert.True(t, strings.Contains(testReport.Issues[0].Description, "2 modules")) { + if !assert.Contains(t, testReport.Issues[0].Description, "2 modules") { t.FailNow() } @@ -89,7 +88,7 @@ func TestModulesAreFine(t *testing.T) { t.FailNow() } - assert.True(t, strings.Contains(testReport.Recommendations[0], "PDB")) + assert.Contains(t, testReport.Recommendations[0], "PDB") }) t.Run("Two Java Modules With No Scannable Binaries", func(t *testing.T) { @@ -132,7 +131,7 @@ func TestModulesAreFine(t *testing.T) { t.FailNow() } - if !assert.True(t, strings.Contains(testReport.Issues[0].Description, "3 Java modules")) { + if !assert.Contains(t, testReport.Issues[0].Description, "3 Java modules") { t.FailNow() } @@ -165,7 +164,7 @@ func TestModulesAreFine(t *testing.T) { t.FailNow() } - if !assert.True(t, strings.Contains(testReport.Issues[0].Description, "nested/shaded")) { + if !assert.Contains(t, testReport.Issues[0].Description, "nested/shaded") { t.FailNow() } diff --git a/checks/files_to_ignore_test.go b/checks/files_to_ignore_test.go index 9daa430..c1cfe2a 100644 --- a/checks/files_to_ignore_test.go +++ b/checks/files_to_ignore_test.go @@ -1,7 +1,6 @@ package checks import ( - "strings" "testing" "github.com/antfie/scan_health/v2/report" @@ -56,7 +55,7 @@ func TestFilesToIgnore(t *testing.T) { t.FailNow() } - assert.True(t, strings.Contains(testReport.Issues[0].Description, "unnecessary")) + assert.Contains(t, testReport.Issues[0].Description, "unnecessary") if !assert.Equal(t, 1, len(testReport.Recommendations)) { t.FailNow() @@ -79,7 +78,7 @@ func TestFilesToIgnore(t *testing.T) { t.FailNow() } - assert.True(t, strings.Contains(testReport.Issues[0].Description, "2 unnecessary")) + assert.Contains(t, testReport.Issues[0].Description, "2 unnecessary") if !assert.Equal(t, 1, len(testReport.Recommendations)) { t.FailNow() diff --git a/checks/flaw_count_test.go b/checks/flaw_count_test.go index 21fe47f..1efec8f 100644 --- a/checks/flaw_count_test.go +++ b/checks/flaw_count_test.go @@ -2,7 +2,6 @@ package checks import ( "github.com/antfie/scan_health/v2/utils" - "strings" "testing" "github.com/antfie/scan_health/v2/report" @@ -42,13 +41,13 @@ func TestTooManyFlaws(t *testing.T) { t.FailNow() } - assert.True(t, strings.Contains(testReport.Issues[0].Description, "No flaws")) + assert.Contains(t, testReport.Issues[0].Description, "No flaws") if !assert.Equal(t, 1, len(testReport.Recommendations)) { t.FailNow() } - assert.True(t, strings.Contains(testReport.Recommendations[0], "When no flaws have been found")) + assert.Contains(t, testReport.Recommendations[0], "When no flaws have been found") }) t.Run("Too Many Flaws", func(t *testing.T) { @@ -66,12 +65,12 @@ func TestTooManyFlaws(t *testing.T) { t.FailNow() } - assert.True(t, strings.Contains(testReport.Issues[0].Description, "A large number")) + assert.Contains(t, testReport.Issues[0].Description, "A large number") if !assert.Equal(t, 1, len(testReport.Recommendations)) { t.FailNow() } - assert.True(t, strings.Contains(testReport.Recommendations[0], "scan could be misconfigured")) + assert.Contains(t, testReport.Recommendations[0], "scan could be misconfigured") }) } diff --git a/checks/minified_javascript_test.go b/checks/minified_javascript_test.go index 875628d..7780838 100644 --- a/checks/minified_javascript_test.go +++ b/checks/minified_javascript_test.go @@ -26,7 +26,7 @@ func TestMinifiedJavaScript(t *testing.T) { minifiedJavaScript(&mockReport) - assert.Equal(t, len(mockReport.Issues), 0) + assert.Empty(t, mockReport.Issues) }) t.Run("No JS Minified Issues", func(t *testing.T) { @@ -43,7 +43,7 @@ func TestMinifiedJavaScript(t *testing.T) { minifiedJavaScript(&mockReport) - assert.Equal(t, len(mockReport.Issues), 0) + assert.Empty(t, mockReport.Issues) }) t.Run("Testing for JS Minified Warning", func(t *testing.T) { @@ -65,13 +65,13 @@ func TestMinifiedJavaScript(t *testing.T) { minifiedJavaScript(&mockReport) - if !assert.Equal(t, len(mockReport.Issues), 1) { + if !assert.Equal(t, 1, len(mockReport.Issues)) { t.FailNow() } assert.Contains(t, mockReport.Issues[0].Description, "2 minified") - assert.Equal(t, mockReport.Issues[0].Severity, report.IssueSeverityMedium) - assert.Equal(t, len(mockReport.Recommendations), 2) + assert.Equal(t, report.IssueSeverityMedium, mockReport.Issues[0].Severity) + assert.Equal(t, 2, len(mockReport.Recommendations)) }) t.Run("Testing for /dist/ JS Files", func(t *testing.T) { @@ -88,9 +88,9 @@ func TestMinifiedJavaScript(t *testing.T) { minifiedJavaScript(&mockReport) - assert.Equal(t, len(mockReport.Issues), 1) - assert.Equal(t, mockReport.Issues[0].Severity, report.IssueSeverityMedium) - assert.Equal(t, len(mockReport.Recommendations), 2) + assert.Equal(t, 1, len(mockReport.Issues)) + assert.Equal(t, report.IssueSeverityMedium, mockReport.Issues[0].Severity) + assert.Equal(t, 2, len(mockReport.Recommendations)) }) t.Run("Testing for minification by name", func(t *testing.T) { @@ -104,14 +104,13 @@ func TestMinifiedJavaScript(t *testing.T) { } minifiedJavaScript(&mockReport) - if !assert.Equal(t, len(mockReport.Issues), 1) { + if !assert.Equal(t, 1, len(mockReport.Issues)) { t.FailNow() } assert.Contains(t, mockReport.Issues[0].Description, "2 minified") - assert.Equal(t, mockReport.Issues[0].Severity, report.IssueSeverityMedium) + assert.Equal(t, report.IssueSeverityMedium, mockReport.Issues[0].Severity) - assert.Equal(t, len(mockReport.Recommendations), 2) + assert.Equal(t, 2, len(mockReport.Recommendations)) }) - } diff --git a/checks/missing_debug_symbols_test.go b/checks/missing_debug_symbols_test.go index a6f5689..1c38a6d 100644 --- a/checks/missing_debug_symbols_test.go +++ b/checks/missing_debug_symbols_test.go @@ -25,7 +25,7 @@ func TestMissingDebugSymbols(t *testing.T) { missingDebugSymbols(&mockReport) - assert.Equal(t, len(mockReport.Issues), 0) + assert.Empty(t, mockReport.Issues) }) t.Run("Executables/DLLs but no issues", func(t *testing.T) { @@ -49,7 +49,7 @@ func TestMissingDebugSymbols(t *testing.T) { missingDebugSymbols(&mockReport) - assert.Equal(t, len(mockReport.Issues), 0) + assert.Empty(t, mockReport.Issues) }) t.Run("Java with missing debug", func(t *testing.T) { @@ -78,8 +78,8 @@ func TestMissingDebugSymbols(t *testing.T) { missingDebugSymbols(&mockReport) - assert.Equal(t, len(mockReport.Issues), 0) - assert.Equal(t, len(mockReport.Recommendations), 0) + assert.Empty(t, mockReport.Issues) + assert.Empty(t, mockReport.Recommendations) }) t.Run("Executables + DLLs with missing debug", func(t *testing.T) { @@ -105,10 +105,10 @@ func TestMissingDebugSymbols(t *testing.T) { missingDebugSymbols(&mockReport) - if !assert.Equal(t, len(mockReport.Issues), 1) { + if !assert.Equal(t, 1, len(mockReport.Issues)) { assert.Contains(t, mockReport.Issues[0].Description, "2 modules") } - assert.Equal(t, len(mockReport.Recommendations), 1) + assert.Equal(t, 1, len(mockReport.Recommendations)) }) } diff --git a/checks/overscanning_test.go b/checks/overscanning_test.go index 7d0cbc2..8ee56a3 100644 --- a/checks/overscanning_test.go +++ b/checks/overscanning_test.go @@ -1,7 +1,6 @@ package checks import ( - "strings" "testing" "github.com/antfie/scan_health/v2/report" @@ -34,7 +33,7 @@ func TestOverScanning(t *testing.T) { t.FailNow() } - assert.True(t, strings.Contains(testReport.Issues[0].Description, "This is because it was already included in the analysis")) + assert.Contains(t, testReport.Issues[0].Description, "This is because it was already included in the analysis") if !assert.Equal(t, 2, len(testReport.Recommendations)) { t.FailNow() @@ -72,7 +71,7 @@ func TestOverScanning(t *testing.T) { t.FailNow() } - assert.True(t, strings.Contains(testReport.Issues[0].Description, "This is because they were already included in the analysis")) + assert.Contains(t, testReport.Issues[0].Description, "This is because they were already included in the analysis") if !assert.Equal(t, 2, len(testReport.Recommendations)) { t.FailNow() @@ -98,13 +97,11 @@ func TestOverScanning(t *testing.T) { overScanning(&testReport) - if !assert.Equal(t, 0, len(testReport.Issues)) { + if !assert.Empty(t, testReport.Issues) { t.FailNow() } - if !assert.Equal(t, 0, len(testReport.Recommendations)) { - t.FailNow() - } + assert.Empty(t, testReport.Recommendations) }) } diff --git a/checks/regular_scans_test.go b/checks/regular_scans_test.go index 2011760..81cb88f 100644 --- a/checks/regular_scans_test.go +++ b/checks/regular_scans_test.go @@ -2,7 +2,6 @@ package checks import ( "github.com/antfie/scan_health/v2/utils" - "strings" "testing" "time" @@ -51,7 +50,7 @@ func TestScanFrequency(t *testing.T) { t.FailNow() } - assert.True(t, strings.Contains(testReport.Issues[0].Description, "last scanned over")) + assert.Contains(t, testReport.Issues[0].Description, "last scanned over") assert.Equal(t, 1, len(testReport.Recommendations)) }) diff --git a/checks/testing_artefacts_test.go b/checks/testing_artefacts_test.go index 778a746..5c2824c 100644 --- a/checks/testing_artefacts_test.go +++ b/checks/testing_artefacts_test.go @@ -7,7 +7,7 @@ import ( "github.com/stretchr/testify/assert" ) -func TestTestingArtefacst(t *testing.T) { +func TestTestingArtefacts(t *testing.T) { t.Run("No Testing Artefacts", func(t *testing.T) { t.Parallel() @@ -25,7 +25,7 @@ func TestTestingArtefacst(t *testing.T) { testingArtefacts(&mockReport) - assert.Equal(t, len(mockReport.Issues), 0) + assert.Empty(t, mockReport.Issues) }) t.Run("Testing File uploaded", func(t *testing.T) { @@ -51,7 +51,7 @@ func TestTestingArtefacst(t *testing.T) { } assert.Contains(t, mockReport.Issues[0].Description, "2 testing artefacts") - assert.Equal(t, len(mockReport.Recommendations), 1) + assert.Equal(t, 1, len(mockReport.Recommendations)) }) @@ -75,7 +75,7 @@ func TestTestingArtefacst(t *testing.T) { testingArtefacts(&mockReport) - assert.Equal(t, len(mockReport.Issues), 0) + assert.Empty(t, mockReport.Issues) }) t.Run("Testing Module found, selected", func(t *testing.T) { @@ -100,12 +100,12 @@ func TestTestingArtefacst(t *testing.T) { testingArtefacts(&mockReport) - if !assert.Equal(t, len(mockReport.Issues), 1) { + if !assert.Equal(t, 1, len(mockReport.Issues)) { t.FailNow() } assert.Contains(t, mockReport.Issues[0].Description, "2 testing artefacts") - assert.Equal(t, len(mockReport.Recommendations), 2) + assert.Equal(t, 2, len(mockReport.Recommendations)) }) t.Run("Module dependant on testing artefacts", func(t *testing.T) { @@ -132,11 +132,11 @@ func TestTestingArtefacst(t *testing.T) { testingArtefacts(&mockReport) - if !assert.Equal(t, len(mockReport.Issues), 1) { + if !assert.Equal(t, 1, len(mockReport.Issues)) { t.FailNow() } assert.Contains(t, mockReport.Issues[0].Description, "2 modules") - assert.Equal(t, len(mockReport.Recommendations), 1) + assert.Equal(t, 1, len(mockReport.Recommendations)) }) } diff --git a/checks/third_party_test.go b/checks/third_party_test.go index bc3d4f9..937bfd6 100644 --- a/checks/third_party_test.go +++ b/checks/third_party_test.go @@ -25,7 +25,7 @@ func TestThirdParty(t *testing.T) { thirdParty(&mockReport) - assert.Equal(t, len(mockReport.Issues), 0) + assert.Empty(t, mockReport.Issues) }) t.Run("Third Party uploaded, not selected", func(t *testing.T) { @@ -50,7 +50,7 @@ func TestThirdParty(t *testing.T) { thirdParty(&mockReport) - assert.Equal(t, len(mockReport.Issues), 0) + assert.Empty(t, mockReport.Issues) }) t.Run("Third Party uploaded, selected", func(t *testing.T) { @@ -81,14 +81,14 @@ func TestThirdParty(t *testing.T) { thirdParty(&mockReport) - if !assert.Equal(t, len(mockReport.Issues), 1) { + if !assert.Equal(t, 1, len(mockReport.Issues)) { t.FailNow() } assert.Contains(t, mockReport.Issues[0].Description, "2 third-party components") assert.Equal(t, report.IssueSeverityMedium, mockReport.Issues[0].Severity) - assert.Equal(t, len(mockReport.Recommendations), 1) + assert.Equal(t, 1, len(mockReport.Recommendations)) assert.True(t, mockReport.UploadedFiles[0].IsThirdParty) assert.True(t, mockReport.UploadedFiles[2].IsThirdParty) }) diff --git a/checks/unexpected_source_code_test.go b/checks/unexpected_source_code_test.go new file mode 100644 index 0000000..0333608 --- /dev/null +++ b/checks/unexpected_source_code_test.go @@ -0,0 +1,93 @@ +package checks + +import ( + "testing" + + "github.com/antfie/scan_health/v2/report" + "github.com/stretchr/testify/assert" +) + +// Test Cases +func TestUnexpectedSourceCode(t *testing.T) { + + // Test Case 1: No Roslyn Files + t.Run("No Unexpected Files", func(t *testing.T) { + t.Parallel() + testReport := report.Report{ + UploadedFiles: []report.UploadedFile{ + {Id: 111111, Name: "file1", MD5: "hash1", IsIgnored: false, IsThirdParty: false}, + {Id: 222222, Name: "file2", MD5: "hash2", IsIgnored: false, IsThirdParty: false}, + }, + Issues: []report.Issue{}, + } + + unexpectedSourceCode(&testReport) + assert.Empty(t, testReport.Issues) + }) + + t.Run("4 files uploaded, 1 unexpected Files", func(t *testing.T) { + t.Parallel() + testReport := report.Report{ + UploadedFiles: []report.UploadedFile{ + {Id: 111111, Name: "file1.jar", MD5: "hash1", IsIgnored: false}, + {Id: 222222, Name: "file2.cs", MD5: "hash2", IsIgnored: false}, + {Id: 333333, Name: "file3.dll", MD5: "hash3", IsIgnored: false}, + {Id: 444444, Name: "file4.exe", MD5: "hash4", IsIgnored: false}, + }, + Issues: []report.Issue{}, + } + + unexpectedSourceCode(&testReport) + if !assert.Equal(t, 1, len(testReport.Issues)) { + t.FailNow() + } + + assert.Contains(t, testReport.Issues[0].Description, "A C# source code file") + assert.Equal(t, 2, len(testReport.Recommendations)) + }) + + t.Run("4 files uploaded, 2 unexpected Files (same type)", func(t *testing.T) { + t.Parallel() + testReport := report.Report{ + UploadedFiles: []report.UploadedFile{ + {Id: 111111, Name: "file1.jar", MD5: "hash1", IsIgnored: false}, + {Id: 222222, Name: "file2.cs", MD5: "hash2", IsIgnored: false}, + {Id: 333333, Name: "file3.cs", MD5: "hash3", IsIgnored: false}, + {Id: 444444, Name: "file4.exe", MD5: "hash4", IsIgnored: false}, + }, + Issues: []report.Issue{}, + } + + unexpectedSourceCode(&testReport) + if !assert.Equal(t, 1, len(testReport.Issues)) { + t.FailNow() + } + + assert.Contains(t, testReport.Issues[0].Description, "2 C# source code files") + assert.Equal(t, 2, len(testReport.Recommendations)) + }) + + t.Run("4 files uploaded, 2 unexpected Files (different types)", func(t *testing.T) { + t.Parallel() + testReport := report.Report{ + UploadedFiles: []report.UploadedFile{ + {Id: 111111, Name: "file1.jar", MD5: "hash1", IsIgnored: false}, + {Id: 222222, Name: "file2.cs", MD5: "hash2", IsIgnored: false}, + {Id: 333333, Name: "file3.java", MD5: "hash3", IsIgnored: false}, + {Id: 444444, Name: "file4.exe", MD5: "hash4", IsIgnored: false}, + }, + Issues: []report.Issue{}, + } + + unexpectedSourceCode(&testReport) + if !assert.Equal(t, 2, len(testReport.Issues)) { + t.FailNow() + } + + assert.Contains(t, testReport.Issues[0].Description, "A Java source code file") + assert.Contains(t, testReport.Issues[1].Description, "A C# source code file") + if !assert.Equal(t, 4, len(testReport.Recommendations)) { + t.FailNow() + } + }) +} diff --git a/checks/unscannable_java_test.go b/checks/unscannable_java_test.go new file mode 100644 index 0000000..1467065 --- /dev/null +++ b/checks/unscannable_java_test.go @@ -0,0 +1,102 @@ +package checks + +import ( + "testing" + + "github.com/antfie/scan_health/v2/report" + "github.com/stretchr/testify/assert" +) + +func TestUnscannableJava(t *testing.T) { + + t.Run("Normal Java File", func(t *testing.T) { + t.Parallel() + mockReport := report.Report{ + Modules: []report.Module{ + {Name: "file3.jar", + Instances: []report.ModuleInstance{ + {IsDependency: false}, + }}, + }, + } + + unscannableJava(&mockReport) + + assert.Empty(t, mockReport.Issues) + }) + + t.Run("No Java Files", func(t *testing.T) { + t.Parallel() + mockReport := report.Report{ + Modules: []report.Module{ + {Name: "file3.exe", + Instances: []report.ModuleInstance{ + {IsDependency: false}, + }}, + }, + } + + unscannableJava(&mockReport) + + assert.Empty(t, mockReport.Issues) + }) + + t.Run("2 Java Files, 1 unscannable", func(t *testing.T) { + t.Parallel() + mockReport := report.Report{ + Modules: []report.Module{ + {Name: "file3.exe", + Instances: []report.ModuleInstance{ + {IsDependency: false}, + }}, + {Name: "file4.jar", + Instances: []report.ModuleInstance{ + {IsDependency: false}, + {HasFatalErrors: true}, + {Issues: []string{`A Java module "file4.jar" was not scannable as it contained no Java binaries.`}}, + }, + }, + }, + } + + unscannableJava(&mockReport) + + assert.Equal(t, 1, len(mockReport.Issues)) + assert.Equal(t, 1, len(mockReport.Recommendations)) + }) + + t.Run("3 Java Files, 2 unscannable", func(t *testing.T) { + t.Parallel() + mockReport := report.Report{ + Modules: []report.Module{ + {Name: "file3.exe", + Instances: []report.ModuleInstance{ + {IsDependency: false}, + }}, + {Name: "file4.jar", + Instances: []report.ModuleInstance{ + {IsDependency: false}, + {HasFatalErrors: true}, + {Issues: []string{`A Java module "file4.jar" was not scannable as it contained no Java binaries.`}}, + }, + }, + {Name: "file5.war", + Instances: []report.ModuleInstance{ + {IsDependency: false}, + {HasFatalErrors: true}, + {Issues: []string{`A Java module "file5.war" was not scannable as it contained no Java binaries.`}}, + }, + }, + }, + } + + unscannableJava(&mockReport) + + if !assert.Equal(t, 1, len(mockReport.Issues)) { + t.FailNow() + } + + assert.Contains(t, mockReport.Issues[0].Description, "2 Java modules") + assert.Equal(t, 1, len(mockReport.Recommendations)) + }) +} diff --git a/checks/unselected_first_party_test.go b/checks/unselected_first_party_test.go new file mode 100644 index 0000000..82dad3c --- /dev/null +++ b/checks/unselected_first_party_test.go @@ -0,0 +1,120 @@ +package checks + +import ( + "testing" + + "github.com/antfie/scan_health/v2/report" + "github.com/stretchr/testify/assert" +) + +func TestUnselectedFirstPartyFiles(t *testing.T) { + + t.Run("1st party top level module selected", func(t *testing.T) { + t.Parallel() + mockReport := report.Report{ + Modules: []report.Module{ + {Name: "file3.exe", + IsIgnored: false, + IsThirdParty: false, + Instances: []report.ModuleInstance{ + {IsDependency: false}, + {IsSelected: true}, + }}, + }, + } + + unselectedFirstParty(&mockReport) + assert.Empty(t, mockReport.Issues) + }) + + t.Run("3rd party top level module not selected", func(t *testing.T) { + t.Parallel() + mockReport := report.Report{ + Modules: []report.Module{ + {Name: "file3.exe", + IsIgnored: false, + IsThirdParty: true, + Instances: []report.ModuleInstance{ + {IsDependency: false}, + {IsSelected: false}, + }}, + }, + } + + unselectedFirstParty(&mockReport) + assert.Empty(t, mockReport.Issues) + }) + + t.Run("1st party dependency not selected", func(t *testing.T) { + t.Parallel() + mockReport := report.Report{ + Modules: []report.Module{ + {Name: "file3.exe", + IsIgnored: false, + IsThirdParty: false, + Instances: []report.ModuleInstance{ + {IsDependency: true}, + {IsSelected: false}, + }}, + }, + } + + unselectedFirstParty(&mockReport) + assert.Empty(t, mockReport.Issues) + }) + + t.Run("1st party top level module not selected", func(t *testing.T) { + t.Parallel() + mockReport := report.Report{ + Modules: []report.Module{ + {Name: "file3.exe", + IsIgnored: false, + IsThirdParty: false, + Instances: []report.ModuleInstance{ + {IsDependency: false}, + {IsSelected: false}, + }}, + }, + } + + unselectedFirstParty(&mockReport) + + if !assert.Equal(t, 1, len(mockReport.Issues)) { + t.FailNow() + } + + assert.Contains(t, mockReport.Issues[0].Description, "A potential first-party module") + assert.Equal(t, 1, len(mockReport.Recommendations)) + }) + + t.Run("2 1st party top level modules not selected", func(t *testing.T) { + t.Parallel() + mockReport := report.Report{ + Modules: []report.Module{ + {Name: "file3.exe", + IsIgnored: false, + IsThirdParty: false, + Instances: []report.ModuleInstance{ + {IsDependency: false}, + {IsSelected: false}, + }}, + {Name: "file4.dll", + IsIgnored: false, + IsThirdParty: false, + Instances: []report.ModuleInstance{ + {IsDependency: false}, + {IsSelected: false}, + }}, + }, + } + + unselectedFirstParty(&mockReport) + + if !assert.Equal(t, 1, len(mockReport.Issues)) { + t.FailNow() + } + + assert.Contains(t, mockReport.Issues[0].Description, "2 potential first-party modules") + assert.Equal(t, 1, len(mockReport.Recommendations)) + }) +} From 9e6559f659264bca8f9e500d54cd578821b27932 Mon Sep 17 00:00:00 2001 From: Marcus Watson Date: Fri, 29 Sep 2023 12:52:53 +0100 Subject: [PATCH 05/13] More tests --- checks/module_count_test.go | 62 +++++++++++ checks/unselected_javascript_test.go | 154 +++++++++++++++++++++++++++ checks/unsupported_compiler_test.go | 108 +++++++++++++++++++ checks/unwanted_files_test.go | 62 +++++++++++ 4 files changed, 386 insertions(+) create mode 100644 checks/module_count_test.go create mode 100644 checks/unselected_javascript_test.go create mode 100644 checks/unsupported_compiler_test.go create mode 100644 checks/unwanted_files_test.go diff --git a/checks/module_count_test.go b/checks/module_count_test.go new file mode 100644 index 0000000..18f8d81 --- /dev/null +++ b/checks/module_count_test.go @@ -0,0 +1,62 @@ +package checks + +import ( + "fmt" + "github.com/antfie/scan_health/v2/utils" + "testing" + + "github.com/antfie/scan_health/v2/report" + "github.com/stretchr/testify/assert" +) + +func generateModule(name string) report.Module { + + return report.Module{ + Name: name, + IsIgnored: false, + IsThirdParty: false, + Instances: []report.ModuleInstance{ + {IsDependency: false}, + {IsSelected: true}, + }} +} + +func TestModuleCount(t *testing.T) { + + t.Run("Small Number of modules", func(t *testing.T) { + t.Parallel() + mockReport := report.Report{ + Modules: []report.Module{ + generateModule("module1.exe"), + generateModule("module2.dll"), + }, + } + + moduleCount(&mockReport) + assert.Empty(t, mockReport.Issues) + }) + + t.Run("Large Number of modules", func(t *testing.T) { + t.Parallel() + + lotsOfModules := []report.Module{} + + for i := 0; i < utils.MaximumModuleSelectedCountThreshold+1; i++ { + moduleName := fmt.Sprintf("module%d", i) + lotsOfModules = append(lotsOfModules, generateModule(moduleName)) + } + + mockReport := report.Report{ + Modules: lotsOfModules, + } + + moduleCount(&mockReport) + + if !assert.Equal(t, 1, len(mockReport.Issues)) { + t.FailNow() + } + + assert.Contains(t, mockReport.Issues[0].Description, fmt.Sprintf("%d modules were selected", utils.MaximumModuleSelectedCountThreshold+1)) + }) + +} diff --git a/checks/unselected_javascript_test.go b/checks/unselected_javascript_test.go new file mode 100644 index 0000000..9741b5d --- /dev/null +++ b/checks/unselected_javascript_test.go @@ -0,0 +1,154 @@ +package checks + +import ( + "testing" + + "github.com/antfie/scan_health/v2/report" + "github.com/stretchr/testify/assert" +) + +func TestUnselectedJavaScript(t *testing.T) { + + t.Run("No JavaScript modules", func(t *testing.T) { + t.Parallel() + mockReport := report.Report{ + Modules: []report.Module{ + {Name: "file3.exe", + IsIgnored: false, + IsThirdParty: false, + Instances: []report.ModuleInstance{ + {IsDependency: false}, + {IsSelected: true}, + }}, + }, + } + + unselectedJavaScriptModules(&mockReport) + assert.Empty(t, mockReport.Issues) + }) + + t.Run("JS files within selected", func(t *testing.T) { + t.Parallel() + mockReport := report.Report{ + Modules: []report.Module{ + {Name: "js files within test.zip", + IsIgnored: false, + IsThirdParty: false, + Instances: []report.ModuleInstance{ + {IsDependency: false}, + {IsSelected: true}, + }}, + }, + } + + unselectedJavaScriptModules(&mockReport) + assert.Empty(t, mockReport.Issues) + }) + + t.Run("Ignoring Node Modules", func(t *testing.T) { + t.Parallel() + mockReport := report.Report{ + Modules: []report.Module{ + {Name: "test_nodemodule_", + IsIgnored: false, + IsThirdParty: false, + Instances: []report.ModuleInstance{ + {IsDependency: false}, + {IsSelected: true}, + }}, + }, + } + + unselectedJavaScriptModules(&mockReport) + assert.Empty(t, mockReport.Issues) + }) + + t.Run("Single JS files within not selected", func(t *testing.T) { + t.Parallel() + mockReport := report.Report{ + Modules: []report.Module{ + {Name: "js files within test.zip", + IsIgnored: false, + IsThirdParty: false, + Instances: []report.ModuleInstance{ + {IsDependency: false}, + {IsSelected: false}, + }}, + }, + } + + unselectedJavaScriptModules(&mockReport) + if !assert.Equal(t, 1, len(mockReport.Issues)) { + t.FailNow() + } + + assert.Contains(t, mockReport.Issues[0].Description, "A JavaScript module was not selected") + assert.Equal(t, report.IssueSeverityMedium, mockReport.Issues[0].Severity) + assert.Equal(t, 2, len(mockReport.Recommendations)) + }) + + t.Run("Multiple JS files within not selected", func(t *testing.T) { + t.Parallel() + mockReport := report.Report{ + Modules: []report.Module{ + {Name: "js files within test.zip", + IsIgnored: false, + IsThirdParty: false, + Instances: []report.ModuleInstance{ + {IsDependency: false}, + {IsSelected: false}, + }}, + {Name: "js files extracted from test2.war", + IsIgnored: false, + IsThirdParty: false, + Instances: []report.ModuleInstance{ + {IsDependency: false}, + {IsSelected: false}, + }}, + }, + } + + unselectedJavaScriptModules(&mockReport) + if !assert.Equal(t, 1, len(mockReport.Issues)) { + t.FailNow() + } + + assert.Contains(t, mockReport.Issues[0].Description, "2 JavaScript module") + assert.Equal(t, report.IssueSeverityMedium, mockReport.Issues[0].Severity) + assert.Equal(t, 2, len(mockReport.Recommendations)) + }) + + t.Run("Ignore Fatal JS files", func(t *testing.T) { + t.Parallel() + mockReport := report.Report{ + Modules: []report.Module{ + {Name: "js files within test.zip", + IsIgnored: false, + IsThirdParty: false, + Instances: []report.ModuleInstance{ + {IsDependency: false}, + {IsSelected: false}, + {HasFatalErrors: true}, + }}, + {Name: "js files extracted from test2.war", + IsIgnored: false, + IsThirdParty: false, + Instances: []report.ModuleInstance{ + {IsDependency: false}, + {IsSelected: false}, + {HasFatalErrors: true}, + }}, + }, + } + + unselectedJavaScriptModules(&mockReport) + if !assert.Equal(t, 0, len(mockReport.Issues)) { + t.FailNow() + } + + assert.Contains(t, mockReport.Issues[0].Description, "2 JavaScript module") + assert.Equal(t, report.IssueSeverityMedium, mockReport.Issues[0].Severity) + assert.Equal(t, 2, len(mockReport.Recommendations)) + }) + +} diff --git a/checks/unsupported_compiler_test.go b/checks/unsupported_compiler_test.go new file mode 100644 index 0000000..2d5497c --- /dev/null +++ b/checks/unsupported_compiler_test.go @@ -0,0 +1,108 @@ +package checks + +import ( + "testing" + + "github.com/antfie/scan_health/v2/report" + "github.com/stretchr/testify/assert" +) + +func TestUnsupportedCompiler(t *testing.T) { + + t.Run("All Supported Modules", func(t *testing.T) { + t.Parallel() + mockReport := report.Report{ + Modules: []report.Module{ + {Name: "file3.exe", + IsIgnored: false, + IsThirdParty: false, + Instances: []report.ModuleInstance{ + {IsDependency: false}, + {IsSelected: true}, + }}, + }, + } + + unsupportedPlatformOrCompiler(&mockReport) + assert.Empty(t, mockReport.Issues) + }) + + t.Run("Ignores third party files", func(t *testing.T) { + t.Parallel() + mockReport := report.Report{ + Modules: []report.Module{ + {Name: "file3.exe", + IsIgnored: false, + IsThirdParty: true, + Instances: []report.ModuleInstance{ + {IsDependency: false}, + {IsSelected: true}, + }}, + }, + } + + unsupportedPlatformOrCompiler(&mockReport) + assert.Empty(t, mockReport.Issues) + }) + + t.Run("One unsupported module", func(t *testing.T) { + t.Parallel() + mockReport := report.Report{ + Modules: []report.Module{ + {Name: "bob.exe", + IsIgnored: false, + IsThirdParty: false, + Instances: []report.ModuleInstance{ + {IsDependency: false}, + {IsSelected: true}, + {HasFatalErrors: true}, + {Status: "(Fatal)Unsupported Platform"}, + }}, + }, + } + + unsupportedPlatformOrCompiler(&mockReport) + if !assert.Equal(t, 1, len(mockReport.Issues)) { + t.FailNow() + } + + assert.Contains(t, mockReport.Issues[0].Description, "A module could not be scanned") + assert.Equal(t, 1, len(mockReport.Recommendations)) + }) + + t.Run("2 unsupported modules", func(t *testing.T) { + t.Parallel() + mockReport := report.Report{ + Modules: []report.Module{ + {Name: "bob.exe", + IsIgnored: false, + IsThirdParty: false, + Instances: []report.ModuleInstance{ + {IsDependency: false}, + {IsSelected: true}, + {HasFatalErrors: true}, + {Status: "(Fatal)Unsupported Platform"}, + }}, + + {Name: "bob2.exe", + IsIgnored: false, + IsThirdParty: false, + Instances: []report.ModuleInstance{ + {IsDependency: false}, + {IsSelected: true}, + {HasFatalErrors: true}, + {Status: "(Fatal)Unsupported Compiler"}, + }}, + }, + } + + unsupportedPlatformOrCompiler(&mockReport) + if !assert.Equal(t, 1, len(mockReport.Issues)) { + t.FailNow() + } + + assert.Contains(t, mockReport.Issues[0].Description, "2 modules could not be scanned") + assert.Equal(t, 1, len(mockReport.Recommendations)) + }) + +} diff --git a/checks/unwanted_files_test.go b/checks/unwanted_files_test.go new file mode 100644 index 0000000..2830fd6 --- /dev/null +++ b/checks/unwanted_files_test.go @@ -0,0 +1,62 @@ +package checks + +import ( + "testing" + + "github.com/antfie/scan_health/v2/report" + "github.com/stretchr/testify/assert" +) + +// Test Cases +func TestUnwantedFiles(t *testing.T) { + + t.Run("No sensitive files", func(t *testing.T) { + t.Parallel() + testReport := report.Report{ + UploadedFiles: []report.UploadedFile{ + {Id: 111111, Name: "file1", MD5: "hash1", IsIgnored: false, IsThirdParty: false}, + {Id: 222222, Name: "file2", MD5: "hash2", IsIgnored: false, IsThirdParty: false}, + }, + Issues: []report.Issue{}, + } + + detectUnwantedFiles(&testReport) + assert.Empty(t, testReport.Issues) + }) + + t.Run("Many Files, A Single Unwanted File", func(t *testing.T) { + t.Parallel() + testReport := report.Report{ + UploadedFiles: []report.UploadedFile{ + {Id: 111111, Name: "file1", MD5: "hash1", IsIgnored: false, IsThirdParty: false}, + {Id: 222222, Name: "test.7z", MD5: "hash2", IsIgnored: false, IsThirdParty: false}, + }, + Issues: []report.Issue{}, + } + + detectUnwantedFiles(&testReport) + + assert.Equal(t, 1, len(testReport.Issues)) + assert.Contains(t, testReport.Issues[0].Description, "A 7-zip file was uploaded: \"test.7z\"") + assert.Equal(t, 2, len(testReport.Recommendations)) + }) + + t.Run("Many Files, Many Unwanted Files", func(t *testing.T) { + t.Parallel() + testReport := report.Report{ + UploadedFiles: []report.UploadedFile{ + {Id: 111111, Name: "file1", MD5: "hash1", IsIgnored: false, IsThirdParty: false}, + {Id: 222222, Name: "test.7z", MD5: "hash2", IsIgnored: false, IsThirdParty: false}, + {Id: 222222, Name: "test.pyc", MD5: "hash2", IsIgnored: false, IsThirdParty: false}, + }, + Issues: []report.Issue{}, + } + + detectUnwantedFiles(&testReport) + + assert.Equal(t, 2, len(testReport.Issues)) + assert.Contains(t, testReport.Issues[0].Description, "A 7-zip file was uploaded: \"test.7z\"") + assert.Contains(t, testReport.Issues[1].Description, "A compiled Python file was uploaded: \"test.pyc\"") + assert.Equal(t, 3, len(testReport.Recommendations)) + }) +} From 369000d37322db5898a4d3cd5bd266e95c548825 Mon Sep 17 00:00:00 2001 From: Marcus Watson Date: Fri, 29 Sep 2023 12:56:13 +0100 Subject: [PATCH 06/13] More tests --- checks/unselected_javascript_test.go | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/checks/unselected_javascript_test.go b/checks/unselected_javascript_test.go index 9741b5d..84bddee 100644 --- a/checks/unselected_javascript_test.go +++ b/checks/unselected_javascript_test.go @@ -142,13 +142,7 @@ func TestUnselectedJavaScript(t *testing.T) { } unselectedJavaScriptModules(&mockReport) - if !assert.Equal(t, 0, len(mockReport.Issues)) { - t.FailNow() - } - - assert.Contains(t, mockReport.Issues[0].Description, "2 JavaScript module") - assert.Equal(t, report.IssueSeverityMedium, mockReport.Issues[0].Severity) - assert.Equal(t, 2, len(mockReport.Recommendations)) + assert.Empty(t, mockReport.Issues) }) } From dcec743ea0e56e466cde4a64482b1ace65395d85 Mon Sep 17 00:00:00 2001 From: Marcus Watson Date: Fri, 29 Sep 2023 13:12:21 +0100 Subject: [PATCH 07/13] More tests --- checks/loose_class_files_test.go | 94 ++++++++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 checks/loose_class_files_test.go diff --git a/checks/loose_class_files_test.go b/checks/loose_class_files_test.go new file mode 100644 index 0000000..ac905e9 --- /dev/null +++ b/checks/loose_class_files_test.go @@ -0,0 +1,94 @@ +package checks + +import ( + "testing" + + "github.com/antfie/scan_health/v2/report" + "github.com/stretchr/testify/assert" +) + +// Test Cases +func TestLooseClassFiles(t *testing.T) { + + // Test Case 1: No Roslyn Files + t.Run("Normal Files", func(t *testing.T) { + t.Parallel() + testReport := report.Report{ + UploadedFiles: []report.UploadedFile{ + {Id: 111111, Name: "file1.exe", MD5: "hash1", IsIgnored: false, IsThirdParty: false}, + {Id: 222222, Name: "file2.dll", MD5: "hash2", IsIgnored: false, IsThirdParty: false}, + }, + Issues: []report.Issue{}, + } + + looseClassFiles(&testReport) + assert.Empty(t, testReport.Issues) + }) + + t.Run("One Class File", func(t *testing.T) { + t.Parallel() + testReport := report.Report{ + UploadedFiles: []report.UploadedFile{ + {Id: 111111, Name: "file1.class", MD5: "hash1", IsIgnored: false, IsThirdParty: false}, + {Id: 222222, Name: "file2.dll", MD5: "hash2", IsIgnored: false, IsThirdParty: false}, + }, + Issues: []report.Issue{}, + } + + looseClassFiles(&testReport) + if !assert.Equal(t, 1, len(testReport.Issues)) { + t.FailNow() + } + + assert.Contains(t, testReport.Issues[0].Description, "Java class files were not packaged") + assert.Equal(t, 1, len(testReport.Recommendations)) + }) + + t.Run("One class Module", func(t *testing.T) { + t.Parallel() + testReport := report.Report{ + Modules: []report.Module{ + {Name: "Class Files Within test.zip", + IsThirdParty: false, + IsIgnored: false, + Instances: []report.ModuleInstance{{ + IsSelected: true, + HasFatalErrors: false, + }}, + }, + }, + + Issues: []report.Issue{}, + } + + looseClassFiles(&testReport) + assert.Contains(t, testReport.Issues[0].Description, "Java class files were not packaged") + assert.Equal(t, 1, len(testReport.Recommendations)) + }) + + t.Run("A Class Files and a class module", func(t *testing.T) { + t.Parallel() + testReport := report.Report{ + Modules: []report.Module{ + {Name: "Class Files Within test.zip", + IsThirdParty: false, + IsIgnored: false, + Instances: []report.ModuleInstance{{ + IsSelected: true, + HasFatalErrors: false, + }}, + }, + }, + UploadedFiles: []report.UploadedFile{ + {Id: 111111, Name: "file1.class", MD5: "hash1", IsIgnored: false, IsThirdParty: false}, + {Id: 333333, Name: "test2.jar", MD5: "hash2", IsIgnored: false, IsThirdParty: false}, + }, + + Issues: []report.Issue{}, + } + + looseClassFiles(&testReport) + assert.Contains(t, testReport.Issues[0].Description, "Java class files were not packaged") + assert.Equal(t, 1, len(testReport.Recommendations)) + }) +} From 5210abfc2776e38d919c311053bafbdaf32a1f68 Mon Sep 17 00:00:00 2001 From: Marcus Watson Date: Fri, 29 Sep 2023 13:28:11 +0100 Subject: [PATCH 08/13] better coverage on duplicate file check --- checks/duplicates_test.go | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/checks/duplicates_test.go b/checks/duplicates_test.go index c5aab8c..a49f739 100644 --- a/checks/duplicates_test.go +++ b/checks/duplicates_test.go @@ -50,7 +50,31 @@ func TestIdenticalModulesParallel(t *testing.T) { } duplicateModules(&testReport) - assert.Equal(t, 1, len(testReport.Issues)) + if !assert.Equal(t, 1, len(testReport.Issues)) { + t.FailNow() + } + + assert.Contains(t, testReport.Issues[0].Description, "A duplicate file name") + assert.Equal(t, report.IssueSeverityHigh, testReport.Issues[0].Severity) + }) + + t.Run("Different Files with same name different hashes", func(t *testing.T) { + t.Parallel() + testReport := report.Report{ + UploadedFiles: []report.UploadedFile{ + {Id: 111111, Name: "file1", MD5: "hash1", IsIgnored: false, IsThirdParty: false}, + {Id: 222222, Name: "file1", MD5: "hash2", IsIgnored: false, IsThirdParty: false}, + {Id: 333333, Name: "file2", MD5: "hash1", IsIgnored: false, IsThirdParty: false}, + {Id: 444444, Name: "file2", MD5: "hash2", IsIgnored: false, IsThirdParty: false}, + }, + } + + duplicateModules(&testReport) + if !assert.Equal(t, 1, len(testReport.Issues)) { + t.FailNow() + } + + assert.Contains(t, testReport.Issues[0].Description, "2 duplicate file names") assert.Equal(t, report.IssueSeverityHigh, testReport.Issues[0].Severity) }) } From 59f298810d577ab5619e35c8ef619bf0ced0cdd6 Mon Sep 17 00:00:00 2001 From: Marcus Watson Date: Fri, 29 Sep 2023 16:32:24 +0100 Subject: [PATCH 09/13] more tests --- checks/general_recommendations_test.go | 131 +++++++++++++++++++++++++ checks/gradle_wrapper_test.go | 122 +++++++++++++++++++++++ checks/missing_sca_components_test.go | 67 +++++++++++++ 3 files changed, 320 insertions(+) create mode 100644 checks/general_recommendations_test.go create mode 100644 checks/gradle_wrapper_test.go create mode 100644 checks/missing_sca_components_test.go diff --git a/checks/general_recommendations_test.go b/checks/general_recommendations_test.go new file mode 100644 index 0000000..f639554 --- /dev/null +++ b/checks/general_recommendations_test.go @@ -0,0 +1,131 @@ +package checks + +import ( + "testing" + + "github.com/antfie/scan_health/v2/report" + "github.com/stretchr/testify/assert" +) + +// Test Cases +func TestGeneralRecommendations(t *testing.T) { + + t.Run("No Recommendations Exists", func(t *testing.T) { + t.Parallel() + testReport := report.Report{ + Modules: []report.Module{ + {Name: "test.jar", + IsThirdParty: false, + IsIgnored: false, + Instances: []report.ModuleInstance{{ + IsSelected: true, + HasFatalErrors: false, + }}, + }, + }, + + Issues: []report.Issue{}, + Recommendations: []string{}, + } + + generalRecommendations(&testReport) + assert.Empty(t, testReport.Issues) + assert.Empty(t, testReport.Recommendations) + }) + + t.Run("No Module Recommendation Exists", func(t *testing.T) { + t.Parallel() + testReport := report.Report{ + Modules: []report.Module{ + {Name: "test.jar", + IsThirdParty: false, + IsIgnored: false, + Instances: []report.ModuleInstance{{ + IsSelected: true, + HasFatalErrors: false, + }}, + }, + }, + + Issues: []report.Issue{}, + Recommendations: []string{ + "Test Recommendation", + }, + } + + generalRecommendations(&testReport) + assert.Empty(t, testReport.Issues) + + // No additional recommendations made + assert.Equal(t, 1, len(testReport.Recommendations)) + }) + + t.Run("Module Recommendation Exists", func(t *testing.T) { + t.Parallel() + testReport := report.Report{ + Modules: []report.Module{ + {Name: "test.jar", + IsThirdParty: false, + IsIgnored: false, + Instances: []report.ModuleInstance{{ + IsSelected: true, + HasFatalErrors: false, + }}, + }, + }, + + Issues: []report.Issue{}, + Recommendations: []string{ + "module", + }, + } + + generalRecommendations(&testReport) + assert.Empty(t, testReport.Issues) + + // Additional recommendations are made on the presence of 'module' in the recommendations list + assert.Equal(t, 6, len(testReport.Recommendations)) + }) + + t.Run("Gradle Wrapper is the only selected one", func(t *testing.T) { + t.Parallel() + testReport := report.Report{ + Modules: []report.Module{ + {Name: "test.jar", + IsThirdParty: false, + IsIgnored: false, + Instances: []report.ModuleInstance{{ + IsSelected: false, + HasFatalErrors: false, + }}, + }, + {Name: "gradle-wrapper.jar", + IsThirdParty: false, + IsIgnored: false, + Instances: []report.ModuleInstance{{ + IsSelected: true, + HasFatalErrors: false, + }}, + }, + {Name: "test.jar", + IsThirdParty: false, + IsIgnored: false, + Instances: []report.ModuleInstance{{ + IsSelected: false, + HasFatalErrors: false, + }}, + }, + }, + Issues: []report.Issue{}, + } + + gradleWrapper(&testReport) + if !assert.Equal(t, 1, len(testReport.Issues)) { + t.FailNow() + } + + assert.Contains(t, testReport.Issues[0].Description, "The only module selected ") + + assert.Equal(t, 1, len(testReport.Recommendations)) + }) +} diff --git a/checks/gradle_wrapper_test.go b/checks/gradle_wrapper_test.go new file mode 100644 index 0000000..2198746 --- /dev/null +++ b/checks/gradle_wrapper_test.go @@ -0,0 +1,122 @@ +package checks + +import ( + "testing" + + "github.com/antfie/scan_health/v2/report" + "github.com/stretchr/testify/assert" +) + +// Test Cases +func TestGradleWrapper(t *testing.T) { + + // Test Case 1: No Roslyn Files + t.Run("No Gradle Wrapper Module", func(t *testing.T) { + t.Parallel() + testReport := report.Report{ + Modules: []report.Module{ + {Name: "test.jar", + IsThirdParty: false, + IsIgnored: false, + Instances: []report.ModuleInstance{{ + IsSelected: true, + HasFatalErrors: false, + }}, + }, + }, + + Issues: []report.Issue{}, + } + + gradleWrapper(&testReport) + assert.Empty(t, testReport.Issues) + }) + + t.Run("Gradle Wrapper is the only selected one", func(t *testing.T) { + t.Parallel() + testReport := report.Report{ + Modules: []report.Module{ + {Name: "test.jar", + IsThirdParty: false, + IsIgnored: false, + Instances: []report.ModuleInstance{{ + IsSelected: false, + HasFatalErrors: false, + }}, + }, + {Name: "gradle-wrapper.jar", + IsThirdParty: false, + IsIgnored: false, + Instances: []report.ModuleInstance{{ + IsSelected: true, + HasFatalErrors: false, + }}, + }, + {Name: "test.jar", + IsThirdParty: false, + IsIgnored: false, + Instances: []report.ModuleInstance{{ + IsSelected: false, + HasFatalErrors: false, + }}, + }, + }, + Issues: []report.Issue{}, + } + + gradleWrapper(&testReport) + if !assert.Equal(t, 1, len(testReport.Issues)) { + t.FailNow() + } + + assert.Contains(t, testReport.Issues[0].Description, "The only module selected ") + + assert.Equal(t, 1, len(testReport.Recommendations)) + }) + + /* Do we need to test further? */ + /* Check gradle_wrapper.go for whether `if len(selectedModules) > 1` will ever be true */ + /* + t.Run("Gradle Wrapper is the only selected one", func(t *testing.T) { + t.Parallel() + testReport := report.Report{ + Modules: []report.Module{ + {Name: "test.jar", + IsThirdParty: false, + IsIgnored: false, + Instances: []report.ModuleInstance{{ + IsSelected: false, + HasFatalErrors: false, + }}, + }, + {Name: "gradle-wrapper.jar", + IsThirdParty: false, + IsIgnored: false, + Instances: []report.ModuleInstance{{ + IsSelected: true, + HasFatalErrors: false, + }}, + }, + {Name: "gradle-wrapper.jar", + IsThirdParty: false, + IsIgnored: false, + Instances: []report.ModuleInstance{{ + IsSelected: true, + HasFatalErrors: false, + }}, + }, + }, + Issues: []report.Issue{}, + } + + gradleWrapper(&testReport) + if !assert.Equal(t, 1, len(testReport.Issues)) { + t.FailNow() + } + + assert.Contains(t, testReport.Issues[0].Description, "The \"gradle-wrapper.jar\" build tool selected") + + assert.Equal(t, 1, len(testReport.Recommendations)) + }) + */ +} diff --git a/checks/missing_sca_components_test.go b/checks/missing_sca_components_test.go new file mode 100644 index 0000000..66e1694 --- /dev/null +++ b/checks/missing_sca_components_test.go @@ -0,0 +1,67 @@ +package checks + +import ( + "testing" + + "github.com/antfie/scan_health/v2/report" + "github.com/stretchr/testify/assert" +) + +// Test Cases +func TestMissingSCAComponents(t *testing.T) { + + t.Run("Valid Files", func(t *testing.T) { + t.Parallel() + testReport := report.Report{ + UploadedFiles: []report.UploadedFile{ + {Id: 111111, Name: "file1.dll", MD5: "hash1", IsIgnored: false, IsThirdParty: false}, + {Id: 222222, Name: "file2.jar", MD5: "hash2", IsIgnored: false, IsThirdParty: false}, + }, + Scan: report.Scan{ + IsSCADataAvailable: true, + }, + SCAComponents: []string{"test1.dll", "file2.jar"}, + Issues: []report.Issue{}, + } + + missingSCAComponents(&testReport) + assert.Empty(t, testReport.Issues) + }) + + t.Run("SCA Data is not available", func(t *testing.T) { + t.Parallel() + testReport := report.Report{ + UploadedFiles: []report.UploadedFile{ + {Id: 111111, Name: "file1.dll", MD5: "hash1", IsIgnored: false, IsThirdParty: false}, + {Id: 222222, Name: "file2.jar", MD5: "hash2", IsIgnored: false, IsThirdParty: false}, + }, + Scan: report.Scan{ + IsSCADataAvailable: false, + }, + Issues: []report.Issue{}, + } + + missingSCAComponents(&testReport) + assert.Empty(t, testReport.Issues) + }) + + t.Run("No SCA files were found", func(t *testing.T) { + t.Parallel() + testReport := report.Report{ + UploadedFiles: []report.UploadedFile{ + {Id: 111111, Name: "csc.exe", MD5: "hash1", IsIgnored: false, IsThirdParty: false}, + {Id: 222222, Name: "file1", MD5: "hash1", IsIgnored: false, IsThirdParty: false}, + }, + Scan: report.Scan{ + IsSCADataAvailable: true, + }, + SCAComponents: []string{}, + Issues: []report.Issue{}, + } + + missingSCAComponents(&testReport) + assert.Equal(t, 1, len(testReport.Issues)) + assert.Equal(t, 1, len(testReport.Recommendations)) + }) + +} From 972b72ed1a6edfc47f70f7d913bca93a4f057df4 Mon Sep 17 00:00:00 2001 From: Marcus Watson Date: Fri, 29 Sep 2023 17:22:45 +0100 Subject: [PATCH 10/13] more tests --- checks/missing_precompiled_files_test.go | 160 +++++++++++++++++++++++ checks/nested_archives_test.go | 71 ++++++++++ 2 files changed, 231 insertions(+) create mode 100644 checks/missing_precompiled_files_test.go create mode 100644 checks/nested_archives_test.go diff --git a/checks/missing_precompiled_files_test.go b/checks/missing_precompiled_files_test.go new file mode 100644 index 0000000..3c2ad30 --- /dev/null +++ b/checks/missing_precompiled_files_test.go @@ -0,0 +1,160 @@ +package checks + +import ( + "testing" + + "github.com/antfie/scan_health/v2/report" + "github.com/stretchr/testify/assert" +) + +// Test Cases +func TestMissingPrecompiledFiles(t *testing.T) { + + t.Run("Valid Files and Modules", func(t *testing.T) { + t.Parallel() + testReport := report.Report{ + Modules: []report.Module{ + {Name: "file1.dll", Instances: []report.ModuleInstance{{IsDependency: false}}}, + {Name: "file2.dll", Instances: []report.ModuleInstance{{IsDependency: false}}}, + {Name: "file3.dll", Instances: []report.ModuleInstance{{IsDependency: false}}}, + }, + UploadedFiles: []report.UploadedFile{ + {Id: 111111, Name: "file1.dll", MD5: "hash1", IsIgnored: false, IsThirdParty: false}, + {Id: 222222, Name: "file2.dll", MD5: "hash2", IsIgnored: false, IsThirdParty: false}, + {Id: 222222, Name: "file3.dll", MD5: "hash2", IsIgnored: false, IsThirdParty: false}, + }, + Issues: []report.Issue{}, + } + + missingPrecompiledFiles(&testReport) + assert.Empty(t, testReport.Issues) + }) + + t.Run("CSHTML File uploaded", func(t *testing.T) { + t.Parallel() + testReport := report.Report{ + Modules: []report.Module{ + {Name: "file1.dll", Instances: []report.ModuleInstance{{IsDependency: false}}}, + {Name: "file2.dll", Instances: []report.ModuleInstance{{IsDependency: false}}}, + {Name: "file3.dll", Instances: []report.ModuleInstance{{IsDependency: false}}}, + }, + UploadedFiles: []report.UploadedFile{ + {Id: 111111, Name: "file1.dll", MD5: "hash1", IsIgnored: false, IsThirdParty: false}, + {Id: 222222, Name: "file2.dll", MD5: "hash2", IsIgnored: false, IsThirdParty: false}, + {Id: 333333, Name: "login.cshtml", MD5: "hash2", IsIgnored: false, IsThirdParty: false}, + {Id: 444444, Name: "file3.dll", MD5: "hash2", IsIgnored: false, IsThirdParty: false}, + }, + Issues: []report.Issue{}, + } + + missingPrecompiledFiles(&testReport) + if !assert.Equal(t, 1, len(testReport.Issues)) { + t.FailNow() + } + + assert.Contains(t, testReport.Issues[0].Description, "A .NET view/template/control file") + assert.Equal(t, 1, len(testReport.Recommendations)) + }) + + t.Run("CSHTML and ASPX Files uploaded", func(t *testing.T) { + t.Parallel() + testReport := report.Report{ + Modules: []report.Module{ + {Name: "file1.dll", Instances: []report.ModuleInstance{{IsDependency: false}}}, + {Name: "file2.dll", Instances: []report.ModuleInstance{{IsDependency: false}}}, + {Name: "file3.dll", Instances: []report.ModuleInstance{{IsDependency: false}}}, + }, + UploadedFiles: []report.UploadedFile{ + {Id: 111111, Name: "file1.dll", MD5: "hash1", IsIgnored: false, IsThirdParty: false}, + {Id: 222222, Name: "file2.dll", MD5: "hash2", IsIgnored: false, IsThirdParty: false}, + {Id: 333333, Name: "login.cshtml", MD5: "hash2", IsIgnored: false, IsThirdParty: false}, + {Id: 444444, Name: "logout.aspx", MD5: "hash2", IsIgnored: false, IsThirdParty: false}, + {Id: 555555, Name: "file3.dll", MD5: "hash2", IsIgnored: false, IsThirdParty: false}, + }, + Issues: []report.Issue{}, + } + + missingPrecompiledFiles(&testReport) + if !assert.Equal(t, 1, len(testReport.Issues)) { + t.FailNow() + } + + assert.Contains(t, testReport.Issues[0].Description, "2 .NET views/templates/control files") + assert.Equal(t, 1, len(testReport.Recommendations)) + }) + + t.Run("Module Reports No Precompiled Files", func(t *testing.T) { + t.Parallel() + testReport := report.Report{ + Modules: []report.Module{ + {Name: "file1.dll", Instances: []report.ModuleInstance{{IsDependency: false}, {IsSelected: true}}}, + {Name: "file2.dll", Instances: []report.ModuleInstance{{IsDependency: false}, {IsSelected: true}}}, + {Name: "file3.dll", Instances: []report.ModuleInstance{{IsDependency: false}, {IsSelected: true}, + {Issues: []string{"No precompiled files were found for this .NET web application"}}}, + }, + }, + UploadedFiles: []report.UploadedFile{ + {Id: 111111, Name: "file1.dll", MD5: "hash1", IsIgnored: false, IsThirdParty: false}, + {Id: 222222, Name: "file2.dll", MD5: "hash2", IsIgnored: false, IsThirdParty: false}, + {Id: 222222, Name: "file3.dll", MD5: "hash2", IsIgnored: false, IsThirdParty: false}, + }, + Issues: []report.Issue{}, + } + + missingPrecompiledFiles(&testReport) + if !assert.Equal(t, 1, len(testReport.Issues)) { + t.FailNow() + } + + assert.Contains(t, testReport.Issues[0].Description, "A .NET component was identified ") + }) + + t.Run("Multiple Modules Report No Precompiled Files", func(t *testing.T) { + t.Parallel() + testReport := report.Report{ + Modules: []report.Module{ + {Name: "file1.dll", Instances: []report.ModuleInstance{{IsDependency: false}, {IsSelected: true}}}, + {Name: "file2.dll", Instances: []report.ModuleInstance{{IsDependency: false}, {IsSelected: true}, + {Issues: []string{"No precompiled files were found for this .NET web application"}}}, + }, + {Name: "file3.dll", Instances: []report.ModuleInstance{{IsDependency: false}, {IsSelected: true}, + {Issues: []string{"No precompiled files were found for this .NET web application"}}}, + }, + }, + UploadedFiles: []report.UploadedFile{ + {Id: 111111, Name: "file1.dll", MD5: "hash1", IsIgnored: false, IsThirdParty: false}, + {Id: 222222, Name: "file2.dll", MD5: "hash2", IsIgnored: false, IsThirdParty: false}, + {Id: 222222, Name: "file3.dll", MD5: "hash2", IsIgnored: false, IsThirdParty: false}, + }, + Issues: []report.Issue{}, + } + + missingPrecompiledFiles(&testReport) + if !assert.Equal(t, 1, len(testReport.Issues)) { + t.FailNow() + } + + assert.Contains(t, testReport.Issues[0].Description, "2 .NET components were identified ") + }) + + t.Run("Unselected Module With No Precompiled Files", func(t *testing.T) { + t.Parallel() + testReport := report.Report{ + Modules: []report.Module{ + {Name: "file1.dll", Instances: []report.ModuleInstance{{IsDependency: false}, {IsSelected: true}}}, + {Name: "file2.dll", Instances: []report.ModuleInstance{{IsDependency: false}, {IsSelected: false}, + {Issues: []string{"No precompiled files were found for this .NET web application"}}}, + }, + }, + UploadedFiles: []report.UploadedFile{ + {Id: 111111, Name: "file1.dll", MD5: "hash1", IsIgnored: false, IsThirdParty: false}, + {Id: 222222, Name: "file2.dll", MD5: "hash2", IsIgnored: false, IsThirdParty: false}, + {Id: 222222, Name: "file3.dll", MD5: "hash2", IsIgnored: false, IsThirdParty: false}, + }, + Issues: []report.Issue{}, + } + + missingPrecompiledFiles(&testReport) + assert.Empty(t, len(testReport.Issues)) + }) +} diff --git a/checks/nested_archives_test.go b/checks/nested_archives_test.go new file mode 100644 index 0000000..3042b4a --- /dev/null +++ b/checks/nested_archives_test.go @@ -0,0 +1,71 @@ +package checks + +import ( + "testing" + + "github.com/antfie/scan_health/v2/report" + "github.com/stretchr/testify/assert" +) + +func TestNestedArchives(t *testing.T) { + + t.Run("Normal Files", func(t *testing.T) { + t.Parallel() + + testReport := report.Report{ + UploadedFiles: []report.UploadedFile{ + {Id: 1, Name: "test.jar"}, + {Id: 2, Name: "test2.jar"}, + }, + Issues: []report.Issue{}, + } + + nestedArchives(&testReport) + assert.Empty(t, testReport.Issues) + }) + + t.Run("Normal Files and a nested Archive", func(t *testing.T) { + t.Parallel() + + testReport := report.Report{ + UploadedFiles: []report.UploadedFile{ + {Id: 1, Name: "test.jar"}, + {Id: 2, Name: "test2.jar"}, + {Id: 3, Name: "test3.jar", Status: "Archive File Within Another Archive"}, + }, + Issues: []report.Issue{}, + } + + nestedArchives(&testReport) + if !assert.Equal(t, 1, len(testReport.Issues)) { + t.FailNow() + } + + assert.Contains(t, testReport.Issues[0].Description, "A nested archive") + + assert.Equal(t, 1, len(testReport.Recommendations)) + }) + + t.Run("Normal Files and multiple nested Archive", func(t *testing.T) { + t.Parallel() + + testReport := report.Report{ + UploadedFiles: []report.UploadedFile{ + {Id: 1, Name: "test.jar"}, + {Id: 2, Name: "test2.jar"}, + {Id: 3, Name: "test3.jar", Status: "Archive File Within Another Archive"}, + {Id: 4, Name: "test4.jar", Status: "Archive File Within Another Archive"}, + }, + Issues: []report.Issue{}, + } + + nestedArchives(&testReport) + if !assert.Equal(t, 1, len(testReport.Issues)) { + t.FailNow() + } + + assert.Contains(t, testReport.Issues[0].Description, "2 nested archives") + + assert.Equal(t, 1, len(testReport.Recommendations)) + }) +} From 123d2f8e420b01d90f678123a9e0690ea51d1e9f Mon Sep 17 00:00:00 2001 From: Marcus Watson Date: Fri, 29 Sep 2023 17:50:36 +0100 Subject: [PATCH 11/13] more tests - achieved >85% coverage --- checks/missing_supporting_files_test.go | 120 ++++++++++++++++++++++++ 1 file changed, 120 insertions(+) create mode 100644 checks/missing_supporting_files_test.go diff --git a/checks/missing_supporting_files_test.go b/checks/missing_supporting_files_test.go new file mode 100644 index 0000000..1e1813a --- /dev/null +++ b/checks/missing_supporting_files_test.go @@ -0,0 +1,120 @@ +package checks + +import ( + "testing" + + "github.com/antfie/scan_health/v2/report" + "github.com/stretchr/testify/assert" +) + +// Test Cases +func TestMissingSupportingFiles(t *testing.T) { + + t.Run("Valid Files and Modules", func(t *testing.T) { + t.Parallel() + testReport := report.Report{ + Modules: []report.Module{ + {Name: "file1.dll", Instances: []report.ModuleInstance{{IsDependency: false}, {IsSelected: true}}}, + {Name: "file2.dll", Instances: []report.ModuleInstance{{IsDependency: false}, {IsSelected: true}}}, + {Name: "file3.dll", Instances: []report.ModuleInstance{{IsDependency: false}, {IsSelected: false}}}, + }, + Issues: []report.Issue{}, + } + + missingSupportingFiles(&testReport) + assert.Empty(t, testReport.Issues) + }) + + t.Run("Module with 1 missing file", func(t *testing.T) { + t.Parallel() + testReport := report.Report{ + Modules: []report.Module{ + {Name: "file1.dll", Instances: []report.ModuleInstance{{IsDependency: false}, {IsSelected: true}}}, + {Name: "file2.dll", Instances: []report.ModuleInstance{{IsDependency: false}, {IsSelected: true}, {Issues: []string{ + "Missing Supporting Files - 1 File", + }}}}, + {Name: "file3.dll", Instances: []report.ModuleInstance{{IsDependency: false}, {IsSelected: false}}}, + }, + Issues: []report.Issue{}, + } + + missingSupportingFiles(&testReport) + if !assert.Equal(t, 1, len(testReport.Issues)) { + t.FailNow() + } + + assert.Contains(t, testReport.Issues[0].Description, "A selected module \"file2.dll\" was found to be missing 1 file.") + assert.Equal(t, 2, len(testReport.Recommendations)) + }) + + t.Run("Module with 3 missing files", func(t *testing.T) { + t.Parallel() + testReport := report.Report{ + Modules: []report.Module{ + {Name: "file1.dll", Instances: []report.ModuleInstance{{IsDependency: false}, {IsSelected: true}}}, + {Name: "file2.dll", Instances: []report.ModuleInstance{{IsDependency: false}, {IsSelected: true}, {Issues: []string{ + "Missing Supporting Files - 3 Files", + }}}}, + {Name: "file3.dll", Instances: []report.ModuleInstance{{IsDependency: false}, {IsSelected: false}}}, + }, + Issues: []report.Issue{}, + } + + missingSupportingFiles(&testReport) + if !assert.Equal(t, 1, len(testReport.Issues)) { + t.FailNow() + } + + assert.Contains(t, testReport.Issues[0].Description, "A selected module \"file2.dll\" was found to be missing 3 files.") + assert.Equal(t, 2, len(testReport.Recommendations)) + }) + + t.Run("Module with 4 'missing 1 file'", func(t *testing.T) { + t.Parallel() + testReport := report.Report{ + Modules: []report.Module{ + {Name: "file1.dll", Instances: []report.ModuleInstance{{IsDependency: false}, {IsSelected: true}}}, + {Name: "file2.dll", Instances: []report.ModuleInstance{{IsDependency: false}, {IsSelected: true}, {Issues: []string{ + "Missing Supporting Files - 1 File", + "Missing Supporting Files - 1 File", + "Missing Supporting Files - 1 File", + "Missing Supporting Files - 1 File", + }}}}, + {Name: "file3.dll", Instances: []report.ModuleInstance{{IsDependency: false}, {IsSelected: false}}}, + }, + Issues: []report.Issue{}, + } + + missingSupportingFiles(&testReport) + if !assert.Equal(t, 1, len(testReport.Issues)) { + t.FailNow() + } + + assert.Contains(t, testReport.Issues[0].Description, "A selected module \"file2.dll\" was found to be missing 4 files.") + assert.Equal(t, 2, len(testReport.Recommendations)) + }) + + t.Run("Multiple Modules missing 1 file", func(t *testing.T) { + t.Parallel() + testReport := report.Report{ + Modules: []report.Module{ + {Name: "file1.dll", Instances: []report.ModuleInstance{{IsDependency: false}, {IsSelected: true}, {Issues: []string{ + "Missing Supporting Files - 1 File", + }}}}, + {Name: "file2.dll", Instances: []report.ModuleInstance{{IsDependency: false}, {IsSelected: true}, {Issues: []string{ + "Missing Supporting Files - 1 File", + }}}}, + {Name: "file3.dll", Instances: []report.ModuleInstance{{IsDependency: false}, {IsSelected: false}}}, + }, + Issues: []report.Issue{}, + } + + missingSupportingFiles(&testReport) + if !assert.Equal(t, 1, len(testReport.Issues)) { + t.FailNow() + } + + assert.Contains(t, testReport.Issues[0].Description, "2 selected modules were selected as entry points that were found to be missing a total of 2 files") + assert.Equal(t, 2, len(testReport.Recommendations)) + }) +} From 333e75a73aaf41660621cd13b6e0e7f20096708e Mon Sep 17 00:00:00 2001 From: Anthony Fielding Date: Mon, 30 Oct 2023 12:22:00 +0000 Subject: [PATCH 12/13] Refactor to simplify region validation --- main.go | 2 +- utils/platform_url.go | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/main.go b/main.go index 0864446..bb3508b 100644 --- a/main.go +++ b/main.go @@ -26,7 +26,7 @@ func main() { flag.Parse() - if *region != "" && utils.IsValidRegion(*region) == false { + if !utils.IsValidRegion(*region) { utils.ErrorAndExitWithUsage(fmt.Sprintf("Invalid region \"%s\". Must be either \"commercial\", \"us\" or \"european\"", *region)) } diff --git a/utils/platform_url.go b/utils/platform_url.go index 182e14a..bead21d 100644 --- a/utils/platform_url.go +++ b/utils/platform_url.go @@ -84,9 +84,11 @@ func ParseBaseUrlFromRegion(region string) string { } func IsValidRegion(region string) bool { - for _, regionData := range regions { - if regionData.ID == region { - return true + if len(region) > 0 { + for _, regionData := range regions { + if regionData.ID == region { + return true + } } } return false From 3ce906cd5c793982d20a8b12a78ce95b5fba350f Mon Sep 17 00:00:00 2001 From: Anthony Fielding Date: Mon, 30 Oct 2023 12:25:16 +0000 Subject: [PATCH 13/13] Revert "Refactor to simplify region validation" This reverts commit 333e75a73aaf41660621cd13b6e0e7f20096708e. --- main.go | 2 +- utils/platform_url.go | 8 +++----- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/main.go b/main.go index bb3508b..0864446 100644 --- a/main.go +++ b/main.go @@ -26,7 +26,7 @@ func main() { flag.Parse() - if !utils.IsValidRegion(*region) { + if *region != "" && utils.IsValidRegion(*region) == false { utils.ErrorAndExitWithUsage(fmt.Sprintf("Invalid region \"%s\". Must be either \"commercial\", \"us\" or \"european\"", *region)) } diff --git a/utils/platform_url.go b/utils/platform_url.go index bead21d..182e14a 100644 --- a/utils/platform_url.go +++ b/utils/platform_url.go @@ -84,11 +84,9 @@ func ParseBaseUrlFromRegion(region string) string { } func IsValidRegion(region string) bool { - if len(region) > 0 { - for _, regionData := range regions { - if regionData.ID == region { - return true - } + for _, regionData := range regions { + if regionData.ID == region { + return true } } return false