From 8cd0bddf6f321646628da61b3605602ec8fd4e40 Mon Sep 17 00:00:00 2001 From: VaibhavMalik4187 Date: Mon, 27 Nov 2023 03:41:52 +0530 Subject: [PATCH] Enhancements and tests for configurationprinter Wrote new tests for `categorytable` and `frameworkscan` Refactored the `shortFormatRow` function for: - Imporved readability - Consistency - Improve code modularity - Encapsulation: The formatted string construction is encapsulated within the function, maintaining code modularity and separation of concerns. Added the `MockISeverityCounters` mock struct to test the `renderSeverityCountersSummary` function. Signed-off-by: VaibhavMalik4187 --- .../categorytable_test.go | 147 ++++++++++++++ .../configurationprinter/frameworkscan.go | 16 +- .../frameworkscan_test.go | 192 ++++++++++++++++++ 3 files changed, 354 insertions(+), 1 deletion(-) create mode 100644 core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/configurationprinter/frameworkscan_test.go diff --git a/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/configurationprinter/categorytable_test.go b/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/configurationprinter/categorytable_test.go index dbabbe7d..ba5e44f2 100644 --- a/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/configurationprinter/categorytable_test.go +++ b/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/configurationprinter/categorytable_test.go @@ -1,6 +1,8 @@ package configurationprinter import ( + "io" + "os" "reflect" "testing" @@ -187,3 +189,148 @@ func TestGenerateCategoryStatusRow(t *testing.T) { }) } } + +func TestGetCategoryTableWriter(t *testing.T) { + tests := []struct { + name string + headers []string + columnAligments []int + want string + }{ + { + name: "Test1", + headers: []string{"Control name", "Resources", "View details"}, + columnAligments: []int{tablewriter.ALIGN_LEFT, tablewriter.ALIGN_CENTER, tablewriter.ALIGN_LEFT}, + want: "┌──────────────┬───────────┬──────────────┐\n│ Control name │ Resources │ View details │\n├──────────────┼───────────┼──────────────┤\n└──────────────┴───────────┴──────────────┘\n", + }, + { + name: "Test2", + headers: []string{"", "Control name", "Docs"}, + columnAligments: []int{tablewriter.ALIGN_CENTER, tablewriter.ALIGN_LEFT, tablewriter.ALIGN_CENTER}, + want: "┌──┬──────────────┬──────┐\n│ │ Control name │ Docs │\n├──┼──────────────┼──────┤\n└──┴──────────────┴──────┘\n", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Create a temporary file to capture output + f, err := os.CreateTemp("", "print") + if err != nil { + panic(err) + } + defer f.Close() + + tableWriter := getCategoryTableWriter(f, tt.headers, tt.columnAligments) + + // Redirect stderr to the temporary file + oldStderr := os.Stderr + defer func() { + os.Stderr = oldStderr + }() + os.Stderr = f + + tableWriter.Render() + + // Read the contents of the temporary file + f.Seek(0, 0) + got, err := io.ReadAll(f) + if err != nil { + panic(err) + } + + assert.NotNil(t, tableWriter) + assert.Equal(t, tt.want, string(got)) + }) + } +} + +func TestRenderSingleCategory(t *testing.T) { + tests := []struct { + name string + categoryName string + rows [][]string + infoToPrintInfo []utils.InfoStars + headers []string + columnAligments []int + want string + }{ + { + name: "Test1", + categoryName: "Resources", + rows: [][]string{ + {"Regular", "regular line", "1"}, + {"Thick", "particularly thick line", "2"}, + {"Double", "double line", "3"}, + }, + infoToPrintInfo: []utils.InfoStars{ + utils.InfoStars{ + Stars: "1", + Info: "Low severity", + }, + utils.InfoStars{ + Stars: "5", + Info: "Critical severity", + }, + }, + headers: []string{"Control name", "Resources", "View details"}, + columnAligments: []int{tablewriter.ALIGN_LEFT, tablewriter.ALIGN_CENTER, tablewriter.ALIGN_LEFT}, + want: "Resources\n┌──────────────┬─────────────────────────┬──────────────┐\n│ Control name │ Resources │ View details │\n├──────────────┼─────────────────────────┼──────────────┤\n│ Regular │ regular line │ 1 │\n│ Thick │ particularly thick line │ 2 │\n│ Double │ double line │ 3 │\n└──────────────┴─────────────────────────┴──────────────┘\n1 Low severity\n5 Critical severity\n\n", + }, + { + name: "Test2", + categoryName: "Control name", + rows: [][]string{ + {"Regular", "regular line", "1"}, + {"Thick", "particularly thick line", "2"}, + {"Double", "double line", "3"}, + }, + infoToPrintInfo: []utils.InfoStars{ + utils.InfoStars{ + Stars: "1", + Info: "Low severity", + }, + utils.InfoStars{ + Stars: "5", + Info: "Critical severity", + }, + utils.InfoStars{ + Stars: "4", + Info: "High severity", + }, + }, + headers: []string{"Control name", "Resources", "View details"}, + columnAligments: []int{tablewriter.ALIGN_LEFT, tablewriter.ALIGN_CENTER, tablewriter.ALIGN_LEFT}, + want: "Control name\n┌──────────────┬─────────────────────────┬──────────────┐\n│ Control name │ Resources │ View details │\n├──────────────┼─────────────────────────┼──────────────┤\n│ Regular │ regular line │ 1 │\n│ Thick │ particularly thick line │ 2 │\n│ Double │ double line │ 3 │\n└──────────────┴─────────────────────────┴──────────────┘\n1 Low severity\n5 Critical severity\n4 High severity\n\n", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Create a temporary file to capture output + f, err := os.CreateTemp("", "print") + if err != nil { + panic(err) + } + defer f.Close() + + tableWriter := getCategoryTableWriter(f, tt.headers, tt.columnAligments) + + // Redirect stderr to the temporary file + oldStderr := os.Stderr + defer func() { + os.Stderr = oldStderr + }() + os.Stderr = f + + renderSingleCategory(f, tt.categoryName, tableWriter, tt.rows, tt.infoToPrintInfo) + + // Read the contents of the temporary file + f.Seek(0, 0) + got, err := io.ReadAll(f) + if err != nil { + panic(err) + } + + assert.NotNil(t, tableWriter) + assert.Equal(t, tt.want, string(got)) + }) + } +} diff --git a/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/configurationprinter/frameworkscan.go b/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/configurationprinter/frameworkscan.go index 8d20eb01..0f871301 100644 --- a/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/configurationprinter/frameworkscan.go +++ b/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/configurationprinter/frameworkscan.go @@ -111,7 +111,21 @@ func (fp *FrameworkPrinter) PrintSummaryTable(writer io.Writer, summaryDetails * func shortFormatRow(dataRows [][]string) [][]string { rows := [][]string{} for _, dataRow := range dataRows { - rows = append(rows, []string{fmt.Sprintf("Severity"+strings.Repeat(" ", 11)+": %+v\nControl Name"+strings.Repeat(" ", 7)+": %+v\nFailed Resources"+strings.Repeat(" ", 3)+": %+v\nAll Resources"+strings.Repeat(" ", 6)+": %+v\n%% Compliance-Score"+strings.Repeat(" ", 1)+": %+v", dataRow[summaryColumnSeverity], dataRow[summaryColumnName], dataRow[summaryColumnCounterFailed], dataRow[summaryColumnCounterAll], dataRow[summaryColumnComplianceScore])}) + // Define the row content using a formatted string + rowContent := fmt.Sprintf("Severity%s: %+v\nControl Name%s: %+v\nFailed Resources%s: %+v\nAll Resources%s: %+v\n%% Compliance-Score%s: %+v", + strings.Repeat(" ", 11), + dataRow[summaryColumnSeverity], + strings.Repeat(" ", 7), + dataRow[summaryColumnName], + strings.Repeat(" ", 3), + dataRow[summaryColumnCounterFailed], + strings.Repeat(" ", 6), + dataRow[summaryColumnCounterAll], + strings.Repeat(" ", 1), + dataRow[summaryColumnComplianceScore]) + + // Append the formatted row content to the rows slice + rows = append(rows, []string{rowContent}) } return rows } diff --git a/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/configurationprinter/frameworkscan_test.go b/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/configurationprinter/frameworkscan_test.go new file mode 100644 index 00000000..2c8c553f --- /dev/null +++ b/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/configurationprinter/frameworkscan_test.go @@ -0,0 +1,192 @@ +package configurationprinter + +import ( + "io" + "os" + "testing" + + "github.com/kubescape/opa-utils/reporthandling/results/v1/reportsummary" + "github.com/stretchr/testify/assert" +) + +type MockISeverityCounters struct { + CriticalCount int + HighCount int + MediumCount int + LowCount int +} + +func (m *MockISeverityCounters) NumberOfCriticalSeverity() int { + return m.CriticalCount +} + +func (m *MockISeverityCounters) NumberOfHighSeverity() int { + return m.HighCount +} + +func (m *MockISeverityCounters) NumberOfMediumSeverity() int { + return m.MediumCount +} + +func (m *MockISeverityCounters) NumberOfLowSeverity() int { + return m.LowCount +} + +func (m *MockISeverityCounters) Increase(severity string, amount int) { +} + +func TestNewFrameworkPrinter(t *testing.T) { + // Test case 1: Verifying default verbose mode + frameworkPrinter := NewFrameworkPrinter(false) + assert.NotNil(t, frameworkPrinter) + assert.Equal(t, false, frameworkPrinter.verboseMode) + + // Test case 2: Setting verbose mode to true + frameworkPrinter = NewFrameworkPrinter(true) + assert.NotNil(t, frameworkPrinter) + assert.Equal(t, true, frameworkPrinter.verboseMode) +} + +func TestGetVerboseMode(t *testing.T) { + // Test case 1: Verifying false verbose mode + frameworkPrinter := NewFrameworkPrinter(false) + assert.Equal(t, false, frameworkPrinter.getVerboseMode()) + + // Test case 2: Setting verbose mode to true + frameworkPrinter = NewFrameworkPrinter(true) + assert.Equal(t, true, frameworkPrinter.getVerboseMode()) +} + +func TestShortRowFormat(t *testing.T) { + tests := []struct { + name string + rows [][]string + expectedRows [][]string + }{ + { + name: "Test Empty rows", + rows: [][]string{}, + expectedRows: [][]string{}, + }, + { + name: "Test Non empty row", + rows: [][]string{ + {"Medium", "Control 1", "2", "20", "0.8"}, + }, + expectedRows: [][]string{[]string{"Severity : Medium\nControl Name : Control 1\nFailed Resources : 2\nAll Resources : 20\n% Compliance-Score : 0.8"}}, + }, + { + name: "Test Non empty rows", + rows: [][]string{ + {"Medium", "Control 1", "2", "20", "0.8"}, + {"Low", "Control 2", "0", "30", "1.0"}, + }, + expectedRows: [][]string{[]string{"Severity : Medium\nControl Name : Control 1\nFailed Resources : 2\nAll Resources : 20\n% Compliance-Score : 0.8"}, []string{"Severity : Low\nControl Name : Control 2\nFailed Resources : 0\nAll Resources : 30\n% Compliance-Score : 1.0"}}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.expectedRows, shortFormatRow(tt.rows)) + }) + } +} + +func TestRenderSeverityCountersSummary(t *testing.T) { + tests := []struct { + name string + counters MockISeverityCounters + expected [][]string + }{ + { + name: "All empty", + counters: MockISeverityCounters{}, + expected: [][]string{[]string{"Critical", "0"}, []string{"High", "0"}, []string{"Medium", "0"}, []string{"Low", "0"}}, + }, + { + name: "All different", + counters: MockISeverityCounters{ + CriticalCount: 7, + HighCount: 17, + MediumCount: 27, + LowCount: 37, + }, + expected: [][]string{[]string{"Critical", "7"}, []string{"High", "17"}, []string{"Medium", "27"}, []string{"Low", "37"}}, + }, + { + name: "All equal", + counters: MockISeverityCounters{ + CriticalCount: 7, + HighCount: 7, + MediumCount: 7, + LowCount: 7, + }, + expected: [][]string{[]string{"Critical", "7"}, []string{"High", "7"}, []string{"Medium", "7"}, []string{"Low", "7"}}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.expected, renderSeverityCountersSummary(&tt.counters)) + }) + } +} + +func TestPrintSummaryTable(t *testing.T) { + tests := []struct { + name string + summaryDetails *reportsummary.SummaryDetails + sortedControlIDs [][]string + want string + }{ + { + name: "All empty", + summaryDetails: &reportsummary.SummaryDetails{ + Frameworks: []reportsummary.FrameworkSummary{ + { + Name: "CIS Kubernetes Benchmark", + }, + { + Name: "nsa", + }, + { + Name: "mitre", + }, + }, + }, + sortedControlIDs: [][]string{}, + want: "\nKubescape did not scan any resources. Make sure you are scanning valid manifests (Deployments, Pods, etc.)\n", + }, + } + + fp := NewFrameworkPrinter(false) + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Create a temporary file to capture output + f, err := os.CreateTemp("", "print") + if err != nil { + panic(err) + } + defer f.Close() + + // Redirect stderr to the temporary file + oldStderr := os.Stderr + defer func() { + os.Stderr = oldStderr + }() + os.Stderr = f + + fp.PrintSummaryTable(f, tt.summaryDetails, tt.sortedControlIDs) + + // Read the contents of the temporary file + f.Seek(0, 0) + got, err := io.ReadAll(f) + if err != nil { + panic(err) + } + + assert.Equal(t, tt.want, string(got)) + }) + } +}