package report_test import ( "bytes" "io/ioutil" "os" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" dbTypes "github.com/aquasecurity/trivy-db/pkg/types" "github.com/aquasecurity/trivy/pkg/report" "github.com/aquasecurity/trivy/pkg/types" ) func TestReportWriter_Template(t *testing.T) { testCases := []struct { name string detectedVulns []types.DetectedVulnerability template string expected string }{ { name: "happy path", detectedVulns: []types.DetectedVulnerability{ { VulnerabilityID: "CVE-2019-0000", PkgName: "foo", Vulnerability: dbTypes.Vulnerability{ Severity: dbTypes.SeverityHigh.String(), }, }, { VulnerabilityID: "CVE-2019-0000", PkgName: "bar", Vulnerability: dbTypes.Vulnerability{ Severity: dbTypes.SeverityHigh.String()}, }, { VulnerabilityID: "CVE-2019-0001", PkgName: "baz", Vulnerability: dbTypes.Vulnerability{ Severity: dbTypes.SeverityCritical.String(), }, }, }, template: "{{ range . }}{{ range .Vulnerabilities}}{{ println .VulnerabilityID .Severity }}{{ end }}{{ end }}", expected: "CVE-2019-0000 HIGH\nCVE-2019-0000 HIGH\nCVE-2019-0001 CRITICAL\n", }, { name: "happy path", detectedVulns: []types.DetectedVulnerability{ { VulnerabilityID: "123", PkgName: `foo \ test`, InstalledVersion: "1.2.3", FixedVersion: "3.4.5", Vulnerability: dbTypes.Vulnerability{ Title: `gcc: POWER9 "DARN" RNG intrinsic produces repeated output`, Description: `curl version curl \X 7.20.0 to and including curl 7.59.0 contains a CWE-126: Buffer Over-read vulnerability in denial of service that can result in curl can be tricked into reading data beyond the end of a heap based buffer used to store downloaded RTSP content.. This vulnerability appears to have been fixed in curl < 7.20.0 and curl >= 7.60.0.`, Severity: "HIGH", }, }, }, template: ` {{- range . -}} {{- $failures := len .Vulnerabilities }} {{- if not (eq .Type "") }} {{- end -}} {{ range .Vulnerabilities }} {{ escapeXML .Description }} {{- end }} {{- end }} `, expected: ` curl version curl \X 7.20.0 to and including curl 7.59.0 contains a CWE-126: Buffer Over-read vulnerability in denial of service that can result in curl can be tricked into reading data beyond the end of a heap based buffer used to store downloaded RTSP content.. This vulnerability appears to have been fixed in curl < 7.20.0 and curl >= 7.60.0. `, }, { name: "happy path with/without period description should return with period", detectedVulns: []types.DetectedVulnerability{ { VulnerabilityID: "CVE-2019-0000", PkgName: "foo", Vulnerability: dbTypes.Vulnerability{ Description: "without period", }, }, { VulnerabilityID: "CVE-2019-0000", PkgName: "bar", Vulnerability: dbTypes.Vulnerability{ Description: "with period.", }, }, { VulnerabilityID: "CVE-2019-0000", PkgName: "bar", Vulnerability: dbTypes.Vulnerability{ Description: `with period and unescaped string curl: Use-after-free when closing 'easy' handle in Curl_close().`, }, }, }, template: `{{ range . }}{{ range .Vulnerabilities}}{{.VulnerabilityID}} {{ endWithPeriod (escapeString .Description) | printf "%q" }}{{ end }}{{ end }}`, expected: `CVE-2019-0000 "without period."CVE-2019-0000 "with period."CVE-2019-0000 "with period and unescaped string curl: Use-after-free when closing 'easy' handle in Curl_close()."`, }, { name: "Calculate using sprig", detectedVulns: []types.DetectedVulnerability{ { VulnerabilityID: "CVE-2019-0000", PkgName: "foo", Vulnerability: dbTypes.Vulnerability{ Description: "without period", Severity: dbTypes.SeverityCritical.String(), }, }, { VulnerabilityID: "CVE-2019-0000", PkgName: "bar", Vulnerability: dbTypes.Vulnerability{ Description: "with period.", Severity: dbTypes.SeverityCritical.String(), }, }, { VulnerabilityID: "CVE-2019-0000", PkgName: "bar", Vulnerability: dbTypes.Vulnerability{ Description: `with period and unescaped string curl: Use-after-free when closing 'easy' handle in Curl_close().`, Severity: dbTypes.SeverityHigh.String(), }, }, }, template: `{{ $high := 0 }}{{ $critical := 0 }}{{ range . }}{{ range .Vulnerabilities}}{{ if eq .Severity "HIGH" }}{{ $high = add $high 1 }}{{ end }}{{ if eq .Severity "CRITICAL" }}{{ $critical = add $critical 1 }}{{ end }}{{ end }}Critical: {{ $critical }}, High: {{ $high }}{{ end }}`, expected: `Critical: 2, High: 1`, }, { name: "happy path: env var parsing and getCurrentTime", detectedVulns: []types.DetectedVulnerability{}, template: `{{ toLower (getEnv "AWS_ACCOUNT_ID") }} {{ getCurrentTime }}`, expected: `123456789012 2020-08-10T07:28:17.000958601Z`, }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { report.Now = func() time.Time { return time.Date(2020, 8, 10, 7, 28, 17, 958601, time.UTC) } os.Setenv("AWS_ACCOUNT_ID", "123456789012") tmplWritten := bytes.Buffer{} inputReport := report.Report{ Results: report.Results{ { Target: "foojunit", Type: "test", Vulnerabilities: tc.detectedVulns, }, }, } assert.NoError(t, report.Write("template", &tmplWritten, nil, inputReport, tc.template, false, false)) assert.Equal(t, tc.expected, tmplWritten.String()) }) } } func TestReportWriter_Template_SARIF(t *testing.T) { testCases := []struct { name string target string detectedVulns []types.DetectedVulnerability want string }{ //TODO: refactor tests } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { templateFile := "../../contrib/sarif.tpl" got := bytes.Buffer{} template, err := ioutil.ReadFile(templateFile) require.NoError(t, err, tc.name) inputReport := report.Report{ Results: report.Results{ { Target: tc.target, Type: "footype", Vulnerabilities: tc.detectedVulns, }, }, } assert.NoError(t, report.Write("template", &got, nil, inputReport, string(template), false, false), tc.name) assert.JSONEq(t, tc.want, got.String(), tc.name) }) } }