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 <vaibhavmalik2018@gmail.com>
This commit is contained in:
VaibhavMalik4187
2023-11-27 11:35:24 +05:30
parent 491ed09f6c
commit 8cd0bddf6f
3 changed files with 354 additions and 1 deletions
@@ -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))
})
}
}
@@ -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
}
@@ -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))
})
}
}