From 05e11b867736d4b49ac401faf5c8d7f0f1e8d8c2 Mon Sep 17 00:00:00 2001 From: seanconroy2021 Date: Wed, 27 Sep 2023 17:00:01 +0100 Subject: [PATCH] Add Markdown Summary Output in report.go & add Tests in report_test.go Implement generateMarkdownSummary() in report.go for Markdown CLI output Add test cases for generateMarkdownSummary() in report_test.go Introduce new output option summary-markdown for GitHub Action Add status icon to Markdown summary resolves: HACBS-2672 Signed-off-by: Sean Conroy sconroy@redhat.com --- .../__snapshots__/report_test.snap | 66 ++++++++++ internal/applicationsnapshot/report.go | 42 ++++++- internal/applicationsnapshot/report_test.go | 116 ++++++++++++++++++ 3 files changed, 221 insertions(+), 3 deletions(-) create mode 100755 internal/applicationsnapshot/__snapshots__/report_test.snap diff --git a/internal/applicationsnapshot/__snapshots__/report_test.snap b/internal/applicationsnapshot/__snapshots__/report_test.snap new file mode 100755 index 000000000..4c9656de9 --- /dev/null +++ b/internal/applicationsnapshot/__snapshots__/report_test.snap @@ -0,0 +1,66 @@ + +[Test_GenerateMarkdownSummary/One_Warning_and_One_Success - 1] +| Field | Value |Status| +|-----------|-------|-------| +| Time | 1970-01-01 00:00:00 | | +| Successes | 1 | :white_check_mark: | +| Failures | 0 | :white_check_mark: | +| Warnings | 1 | :x: | +| Result | | :white_check_mark: | + +--- + +[Test_GenerateMarkdownSummary/One_failure_and_One_Success - 1] +| Field | Value |Status| +|-----------|-------|-------| +| Time | 1970-01-01 00:00:00 | | +| Successes | 1 | :x: | +| Failures | 1 | :x: | +| Warnings | 0 | :white_check_mark: | +| Result | | :white_check_mark: | + +--- + +[Test_GenerateMarkdownSummary/One_Failure_and_One_Violation - 1] +| Field | Value |Status| +|-----------|-------|-------| +| Time | 1970-01-01 00:00:00 | | +| Successes | 0 | :x: | +| Failures | 1 | :x: | +| Warnings | 1 | :x: | +| Result | | :x: | + +--- + +[Test_GenerateMarkdownSummary/Multiple_Failure_and_One_Violation - 1] +| Field | Value |Status| +|-----------|-------|-------| +| Time | 1970-01-01 00:00:00 | | +| Successes | 0 | :x: | +| Failures | 3 | :x: | +| Warnings | 2 | :x: | +| Result | | :x: | + +--- + +[Test_GenerateMarkdownSummary/With_success - 1] +| Field | Value |Status| +|-----------|-------|-------| +| Time | 1970-01-01 00:00:00 | | +| Successes | 3 | :white_check_mark: | +| Failures | 0 | :white_check_mark: | +| Warnings | 0 | :white_check_mark: | +| Result | | :white_check_mark: | + +--- + +[Test_GenerateMarkdownSummary/With_Snapshot - 1] +| Field | Value |Status| +|-----------|-------|-------| +| Time | 1970-01-01 00:00:00 | | +| Successes | 0 | :x: | +| Failures | 2 | :x: | +| Warnings | 2 | :x: | +| Result | | :x: | + +--- diff --git a/internal/applicationsnapshot/report.go b/internal/applicationsnapshot/report.go index 704bd3287..7da7d3f48 100644 --- a/internal/applicationsnapshot/report.go +++ b/internal/applicationsnapshot/report.go @@ -94,9 +94,10 @@ type TestReport struct { // Possible formats the report can be written as. const ( - JSON = "json" - YAML = "yaml" - APPSTUDIO = "appstudio" + SummaryMarkdown = "summary-markdown" + JSON = "json" + YAML = "yaml" + APPSTUDIO = "appstudio" // Deprecated. Remove when hacbs output is removed HACBS = "hacbs" Summary = "summary" @@ -162,6 +163,8 @@ func (r Report) WriteAll(targets []string, p format.TargetParser) (allErrors err // toFormat converts the report into the given format. func (r *Report) toFormat(format string) (data []byte, err error) { switch format { + case SummaryMarkdown: + data, err = generateMarkdownSummary(r) case JSON: data, err = json.Marshal(r) case YAML: @@ -240,6 +243,39 @@ func condensedMsg(results []evaluator.Result) map[string][]string { return shortNames } +func generateMarkdownSummary(r *Report) ([]byte, error) { + var markdownBuffer bytes.Buffer + markdownBuffer.WriteString("| Field | Value |Status|\n") + markdownBuffer.WriteString("|-----------|-------|-------|\n") + + var totalViolations, totalWarnings, totalSuccesses int + pr := r.toSummary() + for _, component := range pr.Components { + totalViolations += component.TotalViolations + totalWarnings += component.TotalWarnings + totalSuccesses += component.TotalSuccesses + } + + writeIcon := func(condition bool) string { + if condition { + return ":white_check_mark:" + } + return ":x:" + } + + writeMarkdownField(&markdownBuffer, "Time", r.created.UTC().Format("2006-01-02 15:04:05"), "") + writeMarkdownField(&markdownBuffer, "Successes", totalSuccesses, writeIcon(totalSuccesses >= 1 && totalViolations == 0)) + writeMarkdownField(&markdownBuffer, "Failures", totalViolations, writeIcon(totalViolations == 0)) + writeMarkdownField(&markdownBuffer, "Warnings", totalWarnings, writeIcon(totalWarnings == 0)) + writeMarkdownField(&markdownBuffer, "Result", "", writeIcon(r.Success)) + return markdownBuffer.Bytes(), nil +} + +func writeMarkdownField(buffer *bytes.Buffer, name string, value any, icon string) { + valueStr := fmt.Sprintf("%v", value) + buffer.WriteString(fmt.Sprintf("| %s | %s | %s |\n", name, valueStr, icon)) +} + // toAppstudioReport returns a version of the report that conforms to the // TEST_OUTPUT format, usually written to the TEST_OUTPUT Tekton task result func (r *Report) toAppstudioReport() TestReport { diff --git a/internal/applicationsnapshot/report_test.go b/internal/applicationsnapshot/report_test.go index ecdd391f9..325a6fed3 100644 --- a/internal/applicationsnapshot/report_test.go +++ b/internal/applicationsnapshot/report_test.go @@ -27,6 +27,7 @@ import ( "testing" "time" + "github.com/gkampitakis/go-snaps/snaps" "github.com/spf13/afero" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -152,6 +153,121 @@ policy: assert.False(t, report.Success) } +func Test_GenerateMarkdownSummary(t *testing.T) { + cases := []struct { + name string + snapshot string + components []Component + }{ + { + name: "One Warning and One Success", + components: []Component{ + { + Successes: []evaluator.Result{ + {Message: "Success1"}, + }, + SuccessCount: 1, + Warnings: []evaluator.Result{ + {Message: "Warning1"}, + }, + Success: true, + }, + }, + }, + { + name: "One failure and One Success", + components: []Component{ + { + Successes: []evaluator.Result{ + {Message: "Success1"}, + }, + SuccessCount: 1, + Violations: []evaluator.Result{ + {Message: "faulure1"}, + }, + Success: true, + }, + }, + }, + { + name: "One Failure and One Violation", + components: []Component{ + { + Violations: []evaluator.Result{ + {Message: "failure1"}, + }, + Warnings: []evaluator.Result{ + {Message: "Warning1"}, + }, + Success: false, + }, + }, + }, + { + name: "Multiple Failure and One Violation", + components: []Component{ + { + Violations: []evaluator.Result{ + {Message: "failure1"}, + {Message: "failure2"}, + {Message: "failure3"}, + }, + Warnings: []evaluator.Result{ + {Message: "Warning1"}, + {Message: "Warning2"}, + }, + Success: false, + }, + }, + }, + { + name: "With success", + components: []Component{ + { + Successes: []evaluator.Result{ + {Message: "Success1"}, + {Message: "Success2"}, + {Message: "Success3"}, + }, + SuccessCount: 3, + Success: true, + }, + }, + }, + { + name: "With Snapshot", + snapshot: "snappy", + components: []Component{ + { + Violations: []evaluator.Result{ + {Message: "failure1"}, + {Message: "failure2"}, + }, + Warnings: []evaluator.Result{ + {Message: "Warning1"}, + {Message: "Warning2"}, + }, + Success: false, + }, + }, + }, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + + ctx := context.Background() + report, err := NewReport(c.snapshot, c.components, createTestPolicy(t, ctx), nil, nil) + assert.NoError(t, err) + report.created = time.Unix(0, 0).UTC() + + markdownSummary, err := generateMarkdownSummary(&report) + assert.NoError(t, err) + snaps.MatchSnapshot(t, string(markdownSummary)) + }) + } +} + func Test_ReportSummary(t *testing.T) { tests := []struct { name string