From 9b86bd2d2d4936b6f24285991482b2933483598a Mon Sep 17 00:00:00 2001 From: Matheus Alcantara Date: Tue, 9 Nov 2021 09:16:49 -0300 Subject: [PATCH] printresults: add tests to cover output and Sonarqube output type This commit add new tests to cover Sonarqube output type and add asserts to check if what was printed is correctly. The tests was changed to use table testings approach to make more easily to add a new testcase. The PrintResults implementation was improved too. Basically a new io.Writer field was added to customize where we will write outputs. The default constructor will always write to Stdout, but on tests we use a custom BufferString to write. This commit also make some improvements on code organization and private method names. Updates #718 Signed-off-by: Matheus Alcantara --- .../controllers/printresults/print_results.go | 233 +++++++------- .../printresults/print_results_test.go | 287 ++++++++++-------- 2 files changed, 282 insertions(+), 238 deletions(-) diff --git a/internal/controllers/printresults/print_results.go b/internal/controllers/printresults/print_results.go index 23a75e7bc..fc0eb7c9f 100644 --- a/internal/controllers/printresults/print_results.go +++ b/internal/controllers/printresults/print_results.go @@ -18,6 +18,7 @@ import ( "encoding/json" "errors" "fmt" + "io" "os" "path/filepath" "strings" @@ -25,7 +26,7 @@ import ( "github.com/ZupIT/horusec-devkit/pkg/entities/analysis" "github.com/ZupIT/horusec-devkit/pkg/entities/vulnerability" "github.com/ZupIT/horusec-devkit/pkg/enums/severities" - enumsVulnerability "github.com/ZupIT/horusec-devkit/pkg/enums/vulnerability" + vulnerabilityenum "github.com/ZupIT/horusec-devkit/pkg/enums/vulnerability" "github.com/ZupIT/horusec-devkit/pkg/utils/logger" "github.com/ZupIT/horusec/config" @@ -49,19 +50,26 @@ type analysisOutputJSON struct { analysis.Analysis } +// PrintResults is reponsable to print results of an analysis +// to a given io.Writer. type PrintResults struct { analysis *analysis.Analysis - configs *config.Config + config *config.Config totalVulns int sonarqubeService SonarQubeConverter textOutput string + writer io.Writer } -func NewPrintResults(entity *analysis.Analysis, configs *config.Config) *PrintResults { +// NewPrintResults create a new PrintResults using os.Stdout as writer. +func NewPrintResults(entity *analysis.Analysis, cfg *config.Config) *PrintResults { return &PrintResults{ analysis: entity, - configs: configs, + config: cfg, sonarqubeService: sonarqube.NewSonarQube(entity), + writer: os.Stdout, + totalVulns: 0, + textOutput: "", } } @@ -70,7 +78,7 @@ func (pr *PrintResults) SetAnalysis(entity *analysis.Analysis) { } func (pr *PrintResults) Print() (totalVulns int, err error) { - if err := pr.factoryPrintByType(); err != nil { + if err := pr.printByOutputType(); err != nil { return 0, err } @@ -78,34 +86,34 @@ func (pr *PrintResults) Print() (totalVulns int, err error) { pr.verifyRepositoryAuthorizationToken() pr.printResponseAnalysis() pr.checkIfExistsErrorsInAnalysis() - if pr.configs.IsTimeout { + if pr.config.IsTimeout { logger.LogWarnWithLevel(messages.MsgWarnTimeoutOccurs) } return pr.totalVulns, nil } -func (pr *PrintResults) factoryPrintByType() error { +func (pr *PrintResults) printByOutputType() error { switch { - case pr.configs.PrintOutputType == outputtype.JSON: - return pr.runPrintResultsJSON() - case pr.configs.PrintOutputType == outputtype.SonarQube: - return pr.runPrintResultsSonarQube() + case pr.config.PrintOutputType == outputtype.JSON: + return pr.printResultsJSON() + case pr.config.PrintOutputType == outputtype.SonarQube: + return pr.printResultsSonarQube() default: - return pr.runPrintResultsText() + return pr.printResultsText() } } -func (pr *PrintResults) runPrintResultsText() error { - fmt.Print("\n") +func (pr *PrintResults) printResultsText() error { + fmt.Fprint(pr.writer, "\n") pr.logSeparator(true) - pr.printLNF("HORUSEC ENDED THE ANALYSIS WITH STATUS OF \"%s\" AND WITH THE FOLLOWING RESULTS:", pr.analysis.Status) + pr.printlnf(`HORUSEC ENDED THE ANALYSIS WITH STATUS OF "%s" AND WITH THE FOLLOWING RESULTS:`, pr.analysis.Status) pr.logSeparator(true) - pr.printLNF("Analysis StartedAt: %s", pr.analysis.CreatedAt.Format("2006-01-02 15:04:05")) - pr.printLNF("Analysis FinishedAt: %s", pr.analysis.FinishedAt.Format("2006-01-02 15:04:05")) + pr.printlnf("Analysis StartedAt: %s", pr.analysis.CreatedAt.Format("2006-01-02 15:04:05")) + pr.printlnf("Analysis FinishedAt: %s", pr.analysis.FinishedAt.Format("2006-01-02 15:04:05")) pr.logSeparator(true) @@ -114,22 +122,33 @@ func (pr *PrintResults) runPrintResultsText() error { return pr.createTxtOutputFile() } -func (pr *PrintResults) runPrintResultsJSON() error { +func (pr *PrintResults) printResultsJSON() error { a := analysisOutputJSON{ Analysis: *pr.analysis, - Version: pr.configs.Version, + Version: pr.config.Version, } - bytesToWrite, err := json.MarshalIndent(a, "", " ") + b, err := json.MarshalIndent(a, "", " ") if err != nil { logger.LogErrorWithLevel(messages.MsgErrorGenerateJSONFile, err) return err } - return pr.parseFilePathToAbsAndCreateOutputJSON(bytesToWrite) + + return pr.createOutputJSON(b) } -func (pr *PrintResults) runPrintResultsSonarQube() error { - return pr.saveSonarQubeFormatResults() +func (pr *PrintResults) printResultsSonarQube() error { + logger.LogInfoWithLevel(messages.MsgInfoStartGenerateSonarQubeFile) + + report := pr.sonarqubeService.ConvertVulnerabilityToSonarQube() + + b, err := json.MarshalIndent(report, "", " ") + if err != nil { + logger.LogErrorWithLevel(messages.MsgErrorGenerateJSONFile, err) + return err + } + + return pr.createOutputJSON(b) } func (pr *PrintResults) checkIfExistVulnerabilityOrNoSec() { @@ -150,34 +169,20 @@ func (pr *PrintResults) validateVulnerabilityToCheckTotalErrors(vuln *vulnerabil } func (pr *PrintResults) isTypeVulnToSkip(vuln *vulnerability.Vulnerability) bool { - return vuln.Type == enumsVulnerability.FalsePositive || - vuln.Type == enumsVulnerability.RiskAccepted || - vuln.Type == enumsVulnerability.Corrected + return vuln.Type == vulnerabilityenum.FalsePositive || + vuln.Type == vulnerabilityenum.RiskAccepted || + vuln.Type == vulnerabilityenum.Corrected } -func (pr *PrintResults) isIgnoredVulnerability(vulnerabilityType string) (ignore bool) { - ignore = false - - for _, typeToIgnore := range pr.configs.SeveritiesToIgnore { +func (pr *PrintResults) isIgnoredVulnerability(vulnerabilityType string) bool { + for _, typeToIgnore := range pr.config.SeveritiesToIgnore { if strings.EqualFold(vulnerabilityType, strings.TrimSpace(typeToIgnore)) || vulnerabilityType == string(severities.Info) { - ignore = true - return ignore + return true } } - return ignore -} - -func (pr *PrintResults) saveSonarQubeFormatResults() error { - logger.LogInfoWithLevel(messages.MsgInfoStartGenerateSonarQubeFile) - report := pr.sonarqubeService.ConvertVulnerabilityToSonarQube() - bytesToWrite, err := json.MarshalIndent(report, "", " ") - if err != nil { - logger.LogErrorWithLevel(messages.MsgErrorGenerateJSONFile, err) - return err - } - return pr.parseFilePathToAbsAndCreateOutputJSON(bytesToWrite) + return false } func (pr *PrintResults) returnDefaultErrOutputJSON(err error) error { @@ -185,32 +190,38 @@ func (pr *PrintResults) returnDefaultErrOutputJSON(err error) error { return ErrOutputJSON } -func (pr *PrintResults) parseFilePathToAbsAndCreateOutputJSON(bytesToWrite []byte) error { - completePath, err := filepath.Abs(pr.configs.JSONOutputFilePath) +func (pr *PrintResults) createOutputJSON(content []byte) error { + path, err := filepath.Abs(pr.config.JSONOutputFilePath) if err != nil { return pr.returnDefaultErrOutputJSON(err) } - if _, err := os.Create(completePath); err != nil { + + f, err := os.Create(path) + if err != nil { return pr.returnDefaultErrOutputJSON(err) } - logger.LogInfoWithLevel(messages.MsgInfoStartWriteFile + completePath) - return pr.openJSONFileAndWriteBytes(bytesToWrite, completePath) + + logger.LogInfoWithLevel(messages.MsgInfoStartWriteFile + path) + + if err := pr.truncateAndWriteFile(content, f); err != nil { + return err + } + + return f.Close() } //nolint:gomnd // magic number -func (pr *PrintResults) openJSONFileAndWriteBytes(bytesToWrite []byte, completePath string) error { - outputFile, err := os.OpenFile(completePath, os.O_CREATE|os.O_WRONLY, 0600) - if err != nil { +func (pr *PrintResults) truncateAndWriteFile(content []byte, file *os.File) error { + if err := file.Truncate(0); err != nil { return pr.returnDefaultErrOutputJSON(err) } - if err = outputFile.Truncate(0); err != nil { - return pr.returnDefaultErrOutputJSON(err) - } - bytesWritten, err := outputFile.Write(bytesToWrite) - if err != nil || bytesWritten != len(bytesToWrite) { + + bytesWritten, err := file.Write(content) + if err != nil || bytesWritten != len(content) { return pr.returnDefaultErrOutputJSON(err) } - return outputFile.Close() + + return nil } func (pr *PrintResults) printTextOutputVulnerability() { @@ -225,34 +236,40 @@ func (pr *PrintResults) printTextOutputVulnerability() { func (pr *PrintResults) printTotalVulnerabilities() { totalVulnerabilities := pr.analysis.GetTotalVulnerabilities() if totalVulnerabilities > 0 { - pr.printLNF("In this analysis, a total of %v possible vulnerabilities "+ - "were found and we classified them into:", totalVulnerabilities) + pr.printlnf( + "In this analysis, a total of %v possible vulnerabilities were found and we classified them into:", + totalVulnerabilities, + ) } - totalVulnerabilitiesBySeverity := pr.GetTotalVulnsBySeverity() + + totalVulnerabilitiesBySeverity := pr.getTotalVulnsBySeverity() for vulnType, countBySeverity := range totalVulnerabilitiesBySeverity { for severityName, count := range countBySeverity { if count > 0 { - pr.printLNF("Total of %s %s is: %v", vulnType.ToString(), severityName.ToString(), count) + pr.printlnf("Total of %s %s is: %v", vulnType.ToString(), severityName.ToString(), count) } } } } -func (pr *PrintResults) GetTotalVulnsBySeverity() (total map[enumsVulnerability.Type]map[severities.Severity]int) { - total = pr.getDefaultTotalVulnBySeverity() +func (pr *PrintResults) getTotalVulnsBySeverity() map[vulnerabilityenum.Type]map[severities.Severity]int { + total := pr.getDefaultTotalVulnBySeverity() + for index := range pr.analysis.AnalysisVulnerabilities { vuln := pr.analysis.AnalysisVulnerabilities[index].Vulnerability total[vuln.Type][vuln.Severity]++ } + return total } -func (pr *PrintResults) getDefaultTotalVulnBySeverity() map[enumsVulnerability.Type]map[severities.Severity]int { - return map[enumsVulnerability.Type]map[severities.Severity]int{ - enumsVulnerability.Vulnerability: pr.getDefaultCountBySeverity(), - enumsVulnerability.RiskAccepted: pr.getDefaultCountBySeverity(), - enumsVulnerability.FalsePositive: pr.getDefaultCountBySeverity(), - enumsVulnerability.Corrected: pr.getDefaultCountBySeverity(), +func (pr *PrintResults) getDefaultTotalVulnBySeverity() map[vulnerabilityenum.Type]map[severities.Severity]int { + count := pr.getDefaultCountBySeverity() + return map[vulnerabilityenum.Type]map[severities.Severity]int{ + vulnerabilityenum.Vulnerability: count, + vulnerabilityenum.RiskAccepted: count, + vulnerabilityenum.FalsePositive: count, + vulnerabilityenum.Corrected: count, } } @@ -269,62 +286,62 @@ func (pr *PrintResults) getDefaultCountBySeverity() map[severities.Severity]int // nolint func (pr *PrintResults) printTextOutputVulnerabilityData(vulnerability *vulnerability.Vulnerability) { - pr.printLNF("Language: %s", vulnerability.Language) - pr.printLNF("Severity: %s", vulnerability.Severity) - pr.printLNF("Line: %s", vulnerability.Line) - pr.printLNF("Column: %s", vulnerability.Column) - pr.printLNF("SecurityTool: %s", vulnerability.SecurityTool) - pr.printLNF("Confidence: %s", vulnerability.Confidence) - pr.printLNF("File: %s", pr.getProjectPath(vulnerability.File)) - pr.printLNF("Code: %s", vulnerability.Code) + pr.printlnf("Language: %s", vulnerability.Language) + pr.printlnf("Severity: %s", vulnerability.Severity) + pr.printlnf("Line: %s", vulnerability.Line) + pr.printlnf("Column: %s", vulnerability.Column) + pr.printlnf("SecurityTool: %s", vulnerability.SecurityTool) + pr.printlnf("Confidence: %s", vulnerability.Confidence) + pr.printlnf("File: %s", pr.getProjectPath(vulnerability.File)) + pr.printlnf("Code: %s", vulnerability.Code) if vulnerability.RuleID != "" { - pr.printLNF("RuleID: %s", vulnerability.RuleID) + pr.printlnf("RuleID: %s", vulnerability.RuleID) } - pr.printLNF("Details: %s", vulnerability.Details) - pr.printLNF("Type: %s", vulnerability.Type) + pr.printlnf("Details: %s", vulnerability.Details) + pr.printlnf("Type: %s", vulnerability.Type) pr.printCommitAuthor(vulnerability) - pr.printLNF("ReferenceHash: %s", vulnerability.VulnHash) + pr.printlnf("ReferenceHash: %s", vulnerability.VulnHash) pr.logSeparator(true) } // nolint func (pr *PrintResults) printCommitAuthor(vulnerability *vulnerability.Vulnerability) { - if !pr.configs.EnableCommitAuthor { + if !pr.config.EnableCommitAuthor { return } - pr.printLNF("Commit Author: %s", vulnerability.CommitAuthor) - pr.printLNF("Commit Date: %s", vulnerability.CommitDate) - pr.printLNF("Commit Email: %s", vulnerability.CommitEmail) - pr.printLNF("Commit CommitHash: %s", vulnerability.CommitHash) - pr.printLNF("Commit Message: %s", vulnerability.CommitMessage) + pr.printlnf("Commit Author: %s", vulnerability.CommitAuthor) + pr.printlnf("Commit Date: %s", vulnerability.CommitDate) + pr.printlnf("Commit Email: %s", vulnerability.CommitEmail) + pr.printlnf("Commit CommitHash: %s", vulnerability.CommitHash) + pr.printlnf("Commit Message: %s", vulnerability.CommitMessage) } func (pr *PrintResults) verifyRepositoryAuthorizationToken() { - if pr.configs.IsEmptyRepositoryAuthorization() { - fmt.Print("\n") + if pr.config.IsEmptyRepositoryAuthorization() { + fmt.Fprint(pr.writer, "\n") logger.LogWarnWithLevel(messages.MsgWarnAuthorizationNotFound) - fmt.Print("\n") + fmt.Fprint(pr.writer, "\n") } } func (pr *PrintResults) checkIfExistsErrorsInAnalysis() { - if !pr.configs.EnableInformationSeverity { + if !pr.config.EnableInformationSeverity { logger.LogWarnWithLevel(messages.MsgWarnInfoVulnerabilitiesDisabled) } if pr.analysis.HasErrors() { pr.logSeparator(true) logger.LogWarnWithLevel(messages.MsgWarnFoundErrorsInAnalysis) - fmt.Print("\n") + fmt.Fprint(pr.writer, "\n") for _, errorMessage := range strings.SplitAfter(pr.analysis.Errors, ";") { pr.printErrors(errorMessage) } - fmt.Print("\n") + fmt.Fprint(pr.writer, "\n") } } @@ -343,44 +360,46 @@ func (pr *PrintResults) printErrors(errorMessage string) { func (pr *PrintResults) printResponseAnalysis() { if pr.totalVulns > 0 { logger.LogWarnWithLevel(fmt.Sprintf(messages.MsgWarnAnalysisFoundVulns, pr.totalVulns)) - fmt.Print("\n") + fmt.Fprint(pr.writer, "\n") return } logger.LogWarnWithLevel(messages.MsgWarnAnalysisFinishedWithoutVulns) - fmt.Print("\n") + fmt.Fprint(pr.writer, "\n") } func (pr *PrintResults) logSeparator(isToShow bool) { if isToShow { - pr.printLNF("\n==================================================================================\n") + pr.printlnf("\n==================================================================================\n") } } func (pr *PrintResults) getProjectPath(path string) string { - if strings.Contains(path, pr.configs.ProjectPath) { + if strings.Contains(path, pr.config.ProjectPath) { return path } - if pr.configs.ContainerBindProjectPath != "" { - return fmt.Sprintf("%s/%s", pr.configs.ContainerBindProjectPath, path) + if pr.config.ContainerBindProjectPath != "" { + return fmt.Sprintf("%s/%s", pr.config.ContainerBindProjectPath, path) } - return fmt.Sprintf("%s/%s", pr.configs.ProjectPath, path) + return fmt.Sprintf("%s/%s", pr.config.ProjectPath, path) } -func (pr *PrintResults) printLNF(text string, args ...interface{}) { - if pr.configs.PrintOutputType == outputtype.Text { - pr.textOutput += fmt.Sprintln(fmt.Sprintf(text, args...)) +func (pr *PrintResults) printlnf(text string, args ...interface{}) { + msg := fmt.Sprintf(text, args...) + + if pr.config.PrintOutputType == outputtype.Text { + pr.textOutput += fmt.Sprintln(msg) } - fmt.Println(fmt.Sprintf(text, args...)) + fmt.Fprintln(pr.writer, msg) } func (pr *PrintResults) createTxtOutputFile() error { - if pr.configs.PrintOutputType != outputtype.Text || pr.configs.JSONOutputFilePath == "" { + if pr.config.PrintOutputType != outputtype.Text || pr.config.JSONOutputFilePath == "" { return nil } - return file.CreateAndWriteFile(pr.textOutput, pr.configs.JSONOutputFilePath) + return file.CreateAndWriteFile(pr.textOutput, pr.config.JSONOutputFilePath) } diff --git a/internal/controllers/printresults/print_results_test.go b/internal/controllers/printresults/print_results_test.go index ecae2d327..b97cbb2e2 100644 --- a/internal/controllers/printresults/print_results_test.go +++ b/internal/controllers/printresults/print_results_test.go @@ -15,9 +15,15 @@ package printresults import ( + "bytes" + "path/filepath" "testing" + "github.com/ZupIT/horusec-devkit/pkg/entities/analysis" entitiesAnalysis "github.com/ZupIT/horusec-devkit/pkg/entities/analysis" + "github.com/ZupIT/horusec-devkit/pkg/utils/logger" + "github.com/ZupIT/horusec/internal/enums/outputtype" + "github.com/ZupIT/horusec/internal/helpers/messages" "github.com/ZupIT/horusec/internal/utils/mock" "github.com/stretchr/testify/assert" @@ -25,6 +31,18 @@ import ( "github.com/ZupIT/horusec/config" ) +type validateFn func(t *testing.T, tt testcase) + +type testcase struct { + name string + cfg config.Config + analysis analysis.Analysis + vulnerabilities int + outputs []string + err bool + validateFn validateFn +} + func TestStartPrintResultsMock(t *testing.T) { t.Run("Should return correctly mock", func(t *testing.T) { m := &Mock{} @@ -36,141 +54,148 @@ func TestStartPrintResultsMock(t *testing.T) { }) } -func TestPrintResults_StartPrintResults(t *testing.T) { - t.Run("Should not return errors with type TEXT", func(t *testing.T) { - configs := &config.Config{} - - analysis := &entitiesAnalysis.Analysis{ - AnalysisVulnerabilities: []entitiesAnalysis.AnalysisVulnerabilities{}, - } - - totalVulns, err := NewPrintResults(analysis, configs).Print() - - assert.NoError(t, err) - assert.Equal(t, 0, totalVulns) - }) - - t.Run("Should not return errors with type JSON", func(t *testing.T) { - analysis := &entitiesAnalysis.Analysis{ - AnalysisVulnerabilities: []entitiesAnalysis.AnalysisVulnerabilities{}, - } - - configs := &config.Config{} - configs.JSONOutputFilePath = "/tmp/horusec.json" - - printResults := &PrintResults{ - analysis: analysis, - configs: configs, - } - - totalVulns, err := printResults.Print() - assert.NoError(t, err) - assert.Equal(t, 0, totalVulns) - }) - - t.Run("Should return not errors because exists error in analysis", func(t *testing.T) { - analysis := &entitiesAnalysis.Analysis{ - Errors: "Exists an error when read analysis", - } - - configs := &config.Config{} - configs.PrintOutputType = "JSON" - - totalVulns, err := NewPrintResults(analysis, configs).Print() - - assert.NoError(t, err) - assert.Equal(t, 0, totalVulns) - }) - - t.Run("Should return errors with type JSON", func(t *testing.T) { - analysis := mock.CreateAnalysisMock() - - analysis.Errors += "ERROR GET REPOSITORY" - - configs := &config.Config{} - configs.PrintOutputType = "json" - - printResults := &PrintResults{ - analysis: analysis, - configs: configs, - } - - _, err := printResults.Print() - - assert.Error(t, err) - }) - - t.Run("Should return 12 vulnerabilities with timeout occurs", func(t *testing.T) { - analysisMock := mock.CreateAnalysisMock() - - analysisMock.AnalysisVulnerabilities = append(analysisMock.AnalysisVulnerabilities, entitiesAnalysis.AnalysisVulnerabilities{Vulnerability: mock.CreateAnalysisMock().AnalysisVulnerabilities[0].Vulnerability}) - configs := &config.Config{} - configs.IsTimeout = true - printResults := &PrintResults{ - analysis: analysisMock, - configs: configs, - } - - totalVulns, err := printResults.Print() - - assert.NoError(t, err) - assert.Equal(t, 12, totalVulns) - }) - - t.Run("Should return 12 vulnerabilities", func(t *testing.T) { - analysisMock := mock.CreateAnalysisMock() - - analysisMock.AnalysisVulnerabilities = append(analysisMock.AnalysisVulnerabilities, entitiesAnalysis.AnalysisVulnerabilities{Vulnerability: mock.CreateAnalysisMock().AnalysisVulnerabilities[0].Vulnerability}) - - printResults := &PrintResults{ - analysis: analysisMock, - configs: &config.Config{}, - } - - totalVulns, err := printResults.Print() - - assert.NoError(t, err) - assert.Equal(t, 12, totalVulns) - }) - - t.Run("Should return 12 vulnerabilities with commit authors", func(t *testing.T) { - configs := &config.Config{} - configs.EnableCommitAuthor = true - analysisMock := mock.CreateAnalysisMock() - - analysisMock.AnalysisVulnerabilities = append(analysisMock.AnalysisVulnerabilities, entitiesAnalysis.AnalysisVulnerabilities{Vulnerability: mock.CreateAnalysisMock().AnalysisVulnerabilities[0].Vulnerability}) - - totalVulns, err := NewPrintResults(analysisMock, configs).Print() - - assert.NoError(t, err) - assert.Equal(t, 12, totalVulns) - }) - - t.Run("Should not return errors when configured to ignore vulnerabilities with severity LOW and MEDIUM", func(t *testing.T) { - analysisMock := mock.CreateAnalysisMock() - - analysisMock.AnalysisVulnerabilities = []entitiesAnalysis.AnalysisVulnerabilities{ - { - Vulnerability: mock.CreateAnalysisMock().AnalysisVulnerabilities[0].Vulnerability, +func TestPrintResultsStartPrintResults(t *testing.T) { + testcases := []testcase{ + { + name: "Should not return error using default output type text", + cfg: config.Config{}, + analysis: entitiesAnalysis.Analysis{ + AnalysisVulnerabilities: []entitiesAnalysis.AnalysisVulnerabilities{}, + }, + }, + { + name: "Should not return error using output type json", + cfg: config.Config{ + StartOptions: config.StartOptions{ + JSONOutputFilePath: filepath.Join(t.TempDir(), "json-output.json"), + PrintOutputType: outputtype.JSON, + }, + }, + analysis: entitiesAnalysis.Analysis{ + AnalysisVulnerabilities: []entitiesAnalysis.AnalysisVulnerabilities{}, + }, + }, + { + name: "Should not return error using output type sonarqube", + cfg: config.Config{ + StartOptions: config.StartOptions{ + PrintOutputType: outputtype.SonarQube, + JSONOutputFilePath: filepath.Join(t.TempDir(), "sonar-output.json"), + }, + }, + analysis: entitiesAnalysis.Analysis{ + AnalysisVulnerabilities: []entitiesAnalysis.AnalysisVulnerabilities{}, + }, + outputs: []string{messages.MsgInfoStartGenerateSonarQubeFile}, + }, + { + name: "Should return not errors because exists error in analysis", + cfg: config.Config{}, + analysis: entitiesAnalysis.Analysis{ + AnalysisVulnerabilities: []entitiesAnalysis.AnalysisVulnerabilities{}, + Errors: "Exists an error when read analysis", }, - { - Vulnerability: mock.CreateAnalysisMock().AnalysisVulnerabilities[1].Vulnerability, + }, + { + name: "Should return error when using json output type without output file path", + cfg: config.Config{ + StartOptions: config.StartOptions{ + PrintOutputType: outputtype.JSON, + }, }, - { - Vulnerability: mock.CreateAnalysisMock().AnalysisVulnerabilities[2].Vulnerability, + analysis: *mock.CreateAnalysisMock(), + err: true, + outputs: []string{messages.MsgErrorGenerateJSONFile}, + }, + { + name: "Should return 11 vulnerabilities with timeout occurs", + cfg: config.Config{ + GlobalOptions: config.GlobalOptions{ + IsTimeout: true, + }, }, - } + analysis: *mock.CreateAnalysisMock(), + vulnerabilities: 11, + outputs: []string{messages.MsgWarnTimeoutOccurs}, + }, + { + name: "Should print 11 vulnerabilities", + cfg: config.Config{}, + analysis: *mock.CreateAnalysisMock(), + vulnerabilities: 11, + }, + { + name: "Should print 11 vulnerabilities with commit authors", + cfg: config.Config{ + StartOptions: config.StartOptions{ + EnableCommitAuthor: true, + }, + }, + analysis: *mock.CreateAnalysisMock(), + vulnerabilities: 11, + outputs: []string{ + "Commit Author", "Commit Date", "Commit Email", "Commit CommitHash", "Commit Message", + }, + }, + { + name: "Should not return errors when configured to ignore vulnerabilities with severity LOW and MEDIUM", + cfg: config.Config{ + StartOptions: config.StartOptions{ + SeveritiesToIgnore: []string{"MEDIUM", "LOW"}, + }, + }, + analysis: *mock.CreateAnalysisMock(), + vulnerabilities: 3, + }, + { + name: "Should save output to file when using json output file path and text format", + cfg: config.Config{ + StartOptions: config.StartOptions{ + PrintOutputType: outputtype.Text, + JSONOutputFilePath: filepath.Join(t.TempDir(), "output"), + }, + }, + analysis: *mock.CreateAnalysisMock(), + vulnerabilities: 11, + validateFn: func(t *testing.T, tt testcase) { + assert.FileExists(t, tt.cfg.JSONOutputFilePath, "output") + }, + }, + } + + for _, tt := range testcases { + t.Run(tt.name, func(t *testing.T) { + pr, output := newPrintResultsTest(&tt.analysis, &tt.cfg) + totalVulns, err := pr.Print() + + if tt.err { + assert.Error(t, err) + } else { + assert.NoError(t, err) + } + + assert.Equal(t, tt.vulnerabilities, totalVulns) + + s := output.String() + for _, output := range tt.outputs { + assert.Contains(t, s, output) + } + + if tt.validateFn != nil { + tt.validateFn(t, tt) + } + }) + } +} - configs := &config.Config{} - configs.SeveritiesToIgnore = []string{"MEDIUM", "LOW"} +// newPrintResultsTest creates a new PrintResults using the bytes.Buffer +// from return as a print results writer and logger output. +func newPrintResultsTest(entity *analysis.Analysis, cfg *config.Config) (*PrintResults, *bytes.Buffer) { + output := bytes.NewBufferString("") + pr := NewPrintResults(entity, cfg) + pr.writer = output - printResults := &PrintResults{ - analysis: analysisMock, - configs: configs, - } + logger.LogSetOutput(output) - totalVulns, err := printResults.Print() - assert.NoError(t, err) - assert.Equal(t, 1, totalVulns) - }) + return pr, output }