From 770f660db57fc789575ba14eb1d5f23176a223a5 Mon Sep 17 00:00:00 2001 From: VaibhavMalik4187 Date: Sun, 26 Nov 2023 01:34:44 +0530 Subject: [PATCH] Enhancements and Test Suite for resourcetable Wrote new tests for the functions in the resourcetable.go file. Enhancements: 1. `generateResourceHeader`: - Dynamic Header Length - Efficient Header Creation - Clearer Code Structure 2. `appendFailedPathsIfNotInPaths`: - Create a set to efficiently check if a failed path already exists in the paths slice - Append failed paths if they are not already present Signed-off-by: VaibhavMalik4187 --- .../printer/v2/resourcetable.go | 24 +- .../printer/v2/resourcetable_test.go | 484 ++++++++++++++++++ 2 files changed, 498 insertions(+), 10 deletions(-) create mode 100644 core/pkg/resultshandling/printer/v2/resourcetable_test.go diff --git a/core/pkg/resultshandling/printer/v2/resourcetable.go b/core/pkg/resultshandling/printer/v2/resourcetable.go index dfa4decf..4f6d689c 100644 --- a/core/pkg/resultshandling/printer/v2/resourcetable.go +++ b/core/pkg/resultshandling/printer/v2/resourcetable.go @@ -12,7 +12,6 @@ import ( "github.com/kubescape/opa-utils/reporthandling/results/v1/reportsummary" "github.com/kubescape/opa-utils/reporthandling/results/v1/resourcesresults" "github.com/olekukonko/tablewriter" - "k8s.io/utils/strings/slices" ) const ( @@ -112,17 +111,14 @@ func generateResourceRows(controls []resourcesresults.ResourceAssociatedControl, } func generateResourceHeader(short bool) []string { - var headers []string + headers := make([]string, 0) + if short { - headers = make([]string, 1) - headers[0] = "Resources" + headers = append(headers, "Resources") } else { - headers = make([]string, _resourceRowLen) - headers[resourceColumnSeverity] = "Severity" - headers[resourceColumnName] = "Control name" - headers[resourceColumnURL] = "Docs" - headers[resourceColumnPath] = "Assisted remediation" + headers = append(headers, []string{"Severity", "Control name", "Docs", "Assisted remediation"}...) } + return headers } @@ -212,10 +208,18 @@ func AssistedRemediationPathsToString(control *resourcesresults.ResourceAssociat } func appendFailedPathsIfNotInPaths(paths []string, failedPaths []string) []string { + // Create a set to efficiently check if a failed path already exists in the paths slice + pathSet := make(map[string]struct{}) + for _, path := range paths { + pathSet[path] = struct{}{} + } + + // Append failed paths if they are not already present for _, failedPath := range failedPaths { - if !slices.Contains(paths, failedPath) { + if _, ok := pathSet[failedPath]; !ok { paths = append(paths, failedPath) } } + return paths } diff --git a/core/pkg/resultshandling/printer/v2/resourcetable_test.go b/core/pkg/resultshandling/printer/v2/resourcetable_test.go new file mode 100644 index 00000000..24bc53c5 --- /dev/null +++ b/core/pkg/resultshandling/printer/v2/resourcetable_test.go @@ -0,0 +1,484 @@ +package printer + +import ( + "testing" + + "github.com/armosec/armoapi-go/armotypes" + "github.com/kubescape/opa-utils/reporthandling/apis" + "github.com/kubescape/opa-utils/reporthandling/results/v1/reportsummary" + "github.com/kubescape/opa-utils/reporthandling/results/v1/resourcesresults" + "github.com/stretchr/testify/assert" +) + +func TestAppendFailedPathsIfNotInPaths(t *testing.T) { + tests := []struct { + paths []string + failedPaths []string + expectedPaths []string + }{ + { + paths: []string{"path1", "path2"}, + failedPaths: []string{"path3", "path1"}, + expectedPaths: []string{"path1", "path2", "path3"}, + }, + { + paths: []string{}, + failedPaths: []string{"path1", "path2"}, + expectedPaths: []string{"path1", "path2"}, + }, + { + paths: []string{"path1", "path2"}, + failedPaths: []string{}, + expectedPaths: []string{"path1", "path2"}, + }, + } + + for _, testcase := range tests { + updatedPaths := appendFailedPathsIfNotInPaths(testcase.paths, testcase.failedPaths) + assert.Equal(t, updatedPaths, testcase.expectedPaths) + } +} + +func TestAssistedRemediationPathsToString(t *testing.T) { + control1 := &resourcesresults.ResourceAssociatedControl{ + ControlID: "control-1", + Name: "Control 1", + Status: apis.StatusInfo{}, + ResourceAssociatedRules: []resourcesresults.ResourceAssociatedRule{ + { + Name: "Rule 1", + Status: "failed", + SubStatus: "skipped", + Paths: []armotypes.PosturePaths{ + { + FailedPath: "some-path1", + }, + { + FailedPath: "random-path1", + }, + }, + }, + }, + } + + control2 := &resourcesresults.ResourceAssociatedControl{ + ControlID: "control-2", + Name: "Control 2", + Status: apis.StatusInfo{}, + ResourceAssociatedRules: []resourcesresults.ResourceAssociatedRule{ + { + Name: "Rule 2", + Status: "success", + SubStatus: "passed", + Paths: []armotypes.PosturePaths{ + { + FailedPath: "some-path2", + }, + { + FailedPath: "random-path2", + }, + }, + }, + }, + } + + actualPaths := AssistedRemediationPathsToString(control1) + expectedPaths := []string{"some-path1", "random-path1"} + assert.Equal(t, expectedPaths, actualPaths) + + actualPaths = AssistedRemediationPathsToString(control2) + expectedPaths = []string{"some-path2", "random-path2"} + assert.Equal(t, expectedPaths, actualPaths) +} + +func TestReviewPathsToString(t *testing.T) { + // Create a test case with empty ResourceAssociatedRules + emptyControl := &resourcesresults.ResourceAssociatedControl{ + ResourceAssociatedRules: []resourcesresults.ResourceAssociatedRule{}, + } + + // Create a test case with one ResourceAssociatedRule and one ReviewPath + singleRuleControl := &resourcesresults.ResourceAssociatedControl{ + ResourceAssociatedRules: []resourcesresults.ResourceAssociatedRule{ + { + Name: "Rule 1", + Status: "success", + SubStatus: "passed", + Paths: []armotypes.PosturePaths{ + { + ReviewPath: "review-path1", + }, + }, + }, + }, + } + + // Create a test case with multiple ResourceAssociatedRules and multiple ReviewPaths + multipleRulesControl := &resourcesresults.ResourceAssociatedControl{ + ResourceAssociatedRules: []resourcesresults.ResourceAssociatedRule{ + { + Name: "Rule 2", + Status: "success", + SubStatus: "passed", + Paths: []armotypes.PosturePaths{ + { + ReviewPath: "review-path2", + }, + { + ReviewPath: "review-path3", + }, + }, + }, + }, + } + + // Test case 1: Empty ResourceAssociatedRules + actualPaths := reviewPathsToString(emptyControl) + assert.Nil(t, actualPaths) + + // Test case 2: Single ResourceAssociatedRule and one ReviewPath + expectedPaths := []string{"review-path1"} + actualPaths = reviewPathsToString(singleRuleControl) + assert.Equal(t, expectedPaths, actualPaths) + + // Test case 3: Multiple ResourceAssociatedRules and multiple ReviewPaths + expectedPaths = []string{"review-path2", "review-path3"} + actualPaths = reviewPathsToString(multipleRulesControl) + assert.Equal(t, expectedPaths, actualPaths) +} + +func TestDeletePathsToString(t *testing.T) { + // Create a test case with empty ResourceAssociatedRules + emptyControl := &resourcesresults.ResourceAssociatedControl{ + ResourceAssociatedRules: []resourcesresults.ResourceAssociatedRule{}, + } + + // Create a test case with one ResourceAssociatedRule and one ReviewPath + singleRuleControl := &resourcesresults.ResourceAssociatedControl{ + ResourceAssociatedRules: []resourcesresults.ResourceAssociatedRule{ + { + Name: "Rule 1", + Status: "success", + SubStatus: "passed", + Paths: []armotypes.PosturePaths{ + { + DeletePath: "delete-path1", + }, + }, + }, + }, + } + + // Create a test case with multiple ResourceAssociatedRules and multiple ReviewPaths + multipleRulesControl := &resourcesresults.ResourceAssociatedControl{ + ResourceAssociatedRules: []resourcesresults.ResourceAssociatedRule{ + { + Name: "Rule 2", + Status: "success", + SubStatus: "passed", + Paths: []armotypes.PosturePaths{ + { + DeletePath: "delete-path2", + }, + { + DeletePath: "delete-path3", + }, + }, + }, + }, + } + + // Test case 1: Empty ResourceAssociatedRules + actualPaths := deletePathsToString(emptyControl) + assert.Nil(t, actualPaths) + + // Test case 2: Single ResourceAssociatedRule and one ReviewPath + actualPaths = deletePathsToString(singleRuleControl) + expectedPath := []string{"delete-path1"} + assert.Equal(t, expectedPath, actualPaths) + + // Test case 3: Multiple ResourceAssociatedRules and multiple ReviewPaths + actualPaths = deletePathsToString(multipleRulesControl) + expectedPath = []string{"delete-path2", "delete-path3"} + assert.Equal(t, expectedPath, actualPaths) +} + +func TestFixPathsToString(t *testing.T) { + // Create a test case with empty ResourceAssociatedRules + emptyControl := &resourcesresults.ResourceAssociatedControl{ + ResourceAssociatedRules: []resourcesresults.ResourceAssociatedRule{}, + } + + // Create a test case with one ResourceAssociatedRule and one ReviewPath + singleRuleControl := &resourcesresults.ResourceAssociatedControl{ + ResourceAssociatedRules: []resourcesresults.ResourceAssociatedRule{ + { + Name: "Rule 1", + Status: "success", + SubStatus: "passed", + Paths: []armotypes.PosturePaths{ + { + FixPath: armotypes.FixPath{ + Path: "fix-path1", + Value: "fix-path-value1", + }, + }, + }, + }, + }, + } + + // Create a test case with multiple ResourceAssociatedRules and multiple ReviewPaths + multipleRulesControl := &resourcesresults.ResourceAssociatedControl{ + ResourceAssociatedRules: []resourcesresults.ResourceAssociatedRule{ + { + Name: "Rule 2", + Status: "success", + SubStatus: "passed", + Paths: []armotypes.PosturePaths{ + { + FixPath: armotypes.FixPath{ + Path: "fix-path2", + Value: "fix-path-value2", + }, + }, + { + FixPath: armotypes.FixPath{ + Path: "fix-path3", + Value: "fix-path-value3", + }, + }, + }, + }, + }, + } + + // Test case 1: Empty ResourceAssociatedRules + actualPaths := fixPathsToString(emptyControl) + assert.Nil(t, actualPaths) + + // Test case 2: Single ResourceAssociatedRule and one ReviewPath + actualPaths = fixPathsToString(singleRuleControl) + expectedPath := []string{"fix-path1=fix-path-value1"} + assert.Equal(t, expectedPath, actualPaths) + + // Test case 3: Multiple ResourceAssociatedRules and multiple ReviewPaths + actualPaths = fixPathsToString(multipleRulesControl) + expectedPath = []string{"fix-path2=fix-path-value2", "fix-path3=fix-path-value3"} + assert.Equal(t, expectedPath, actualPaths) +} + +func TestFailedPathsToString(t *testing.T) { + // Create a test case with empty ResourceAssociatedRules + emptyControl := &resourcesresults.ResourceAssociatedControl{ + ResourceAssociatedRules: []resourcesresults.ResourceAssociatedRule{}, + } + + // Create a test case with one ResourceAssociatedRule and one ReviewPath + singleRuleControl := &resourcesresults.ResourceAssociatedControl{ + ResourceAssociatedRules: []resourcesresults.ResourceAssociatedRule{ + { + Name: "Rule 1", + Status: "success", + SubStatus: "passed", + Paths: []armotypes.PosturePaths{ + { + FailedPath: "failed-path1", + }, + }, + }, + }, + } + + // Create a test case with multiple ResourceAssociatedRules and multiple ReviewPaths + multipleRulesControl := &resourcesresults.ResourceAssociatedControl{ + ResourceAssociatedRules: []resourcesresults.ResourceAssociatedRule{ + { + Name: "Rule 2", + Status: "success", + SubStatus: "passed", + Paths: []armotypes.PosturePaths{ + { + FailedPath: "failed-path2", + }, + { + FailedPath: "failed-path3", + }, + }, + }, + }, + } + + // Test case 1: Empty ResourceAssociatedRules + actualPaths := failedPathsToString(emptyControl) + assert.Nil(t, actualPaths) + + // Test case 2: Single ResourceAssociatedRule and one ReviewPath + actualPaths = failedPathsToString(singleRuleControl) + expectedPath := []string{"failed-path1"} + assert.Equal(t, expectedPath, actualPaths) + + // Test case 3: Multiple ResourceAssociatedRules and multiple ReviewPaths + actualPaths = failedPathsToString(multipleRulesControl) + expectedPath = []string{"failed-path2", "failed-path3"} + assert.Equal(t, expectedPath, actualPaths) +} + +func TestShortFormatResource(t *testing.T) { + // Create a test case with an empty resourceRows slice + emptyResourceRows := [][]string{} + + // Create a test case with a single resource row + singleResourceRow := [][]string{ + {"High", "Control1", "https://example.com/doc1", "Path1"}, + } + + // Create a test case with multiple resource rows + multipleResourceRows := [][]string{ + {"Medium", "Control2", "https://example.com/doc2", "Path2"}, + {"Low", "Control3", "https://example.com/doc3", "Path3"}, + } + + actualRows := shortFormatResource(emptyResourceRows) + assert.Empty(t, actualRows) + + actualRows = shortFormatResource(singleResourceRow) + expectedRows := [][]string{{"Severity : High\nControl Name : Control1\nDocs : https://example.com/doc1\nAssisted Remediation : Path1"}} + assert.Equal(t, expectedRows, actualRows) + + actualRows = shortFormatResource(multipleResourceRows) + expectedRows = [][]string{{"Severity : Medium\nControl Name : Control2\nDocs : https://example.com/doc2\nAssisted Remediation : Path2"}, + {"Severity : Low\nControl Name : Control3\nDocs : https://example.com/doc3\nAssisted Remediation : Path3"}} + assert.Equal(t, expectedRows, actualRows) +} + +func TestGenerateResourceHeader(t *testing.T) { + // Test case 1: Short headers + shortHeaders := generateResourceHeader(true) + expectedShortHeaders := []string{"Resources"} + assert.Equal(t, expectedShortHeaders, shortHeaders) + + // Test case 2: Full headers + fullHeaders := generateResourceHeader(false) + expectedFullHeaders := []string{"Severity", "Control name", "Docs", "Assisted remediation"} + assert.Equal(t, expectedFullHeaders, fullHeaders) +} + +func TestGenerateResourceRows_Loop(t *testing.T) { + tests := []struct { + name string + summaryDetails reportsummary.SummaryDetails + controls []resourcesresults.ResourceAssociatedControl + expectedLen int + }{ + { + name: "Empty controls", + summaryDetails: reportsummary.SummaryDetails{}, + controls: []resourcesresults.ResourceAssociatedControl{}, + expectedLen: 0, + }, + { + name: "2 Failed Controls", + summaryDetails: reportsummary.SummaryDetails{}, + controls: []resourcesresults.ResourceAssociatedControl{ + resourcesresults.ResourceAssociatedControl{ + ControlID: "control-1", + Name: "Control 1", + Status: apis.StatusInfo{}, + ResourceAssociatedRules: []resourcesresults.ResourceAssociatedRule{ + { + Name: "Rule 1", + Status: "failed", + SubStatus: "configuration", + + Paths: []armotypes.PosturePaths{ + { + FailedPath: "some-path1", + }, + { + FailedPath: "random-path1", + }, + }, + }, + }, + }, + resourcesresults.ResourceAssociatedControl{ + ControlID: "control-2", + Name: "Control 2", + Status: apis.StatusInfo{}, + ResourceAssociatedRules: []resourcesresults.ResourceAssociatedRule{ + { + Name: "Rule 2", + Status: "failed", + SubStatus: "configuration", + Paths: []armotypes.PosturePaths{ + { + FailedPath: "some-path2", + }, + { + FailedPath: "random-path2", + }, + }, + }, + }, + }, + }, + expectedLen: 2, + }, + { + name: "One failed control", + summaryDetails: reportsummary.SummaryDetails{}, + controls: []resourcesresults.ResourceAssociatedControl{ + resourcesresults.ResourceAssociatedControl{ + ControlID: "control-1", + Name: "Control 1", + Status: apis.StatusInfo{}, + ResourceAssociatedRules: []resourcesresults.ResourceAssociatedRule{ + { + Name: "Rule 1", + Status: "passed", + SubStatus: "configuration", + + Paths: []armotypes.PosturePaths{ + { + FailedPath: "some-path1", + }, + { + FailedPath: "random-path1", + }, + }, + }, + }, + }, + resourcesresults.ResourceAssociatedControl{ + ControlID: "control-2", + Name: "Control 2", + Status: apis.StatusInfo{}, + ResourceAssociatedRules: []resourcesresults.ResourceAssociatedRule{ + { + Name: "Rule 2", + Status: "failed", + SubStatus: "configuration", + Paths: []armotypes.PosturePaths{ + { + FailedPath: "some-path2", + }, + { + FailedPath: "random-path2", + }, + }, + }, + }, + }, + }, + expectedLen: 1, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + rows := generateResourceRows(tt.controls, &tt.summaryDetails) + assert.Equal(t, tt.expectedLen, len(rows)) + }) + } +}