diff --git a/internal/controllers/printresults/print_results.go b/internal/controllers/printresults/print_results.go index 23a75e7bc..27125b4a8 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) +//nolint:funlen +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 { - return pr.returnDefaultErrOutputJSON(err) - } - logger.LogInfoWithLevel(messages.MsgInfoStartWriteFile + completePath) - return pr.openJSONFileAndWriteBytes(bytesToWrite, completePath) -} -//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) + f, err := os.Create(path) if err != nil { return pr.returnDefaultErrOutputJSON(err) } - if err = outputFile.Truncate(0); err != nil { + + logger.LogInfoWithLevel(messages.MsgInfoStartWriteFile + path) + + if err := pr.truncateAndWriteFile(content, f); err != nil { + return err + } + + return f.Close() +} + +func (pr *PrintResults) truncateAndWriteFile(content []byte, f *os.File) error { + if err := f.Truncate(0); err != nil { return pr.returnDefaultErrOutputJSON(err) } - bytesWritten, err := outputFile.Write(bytesToWrite) - if err != nil || bytesWritten != len(bytesToWrite) { + + bytesWritten, err := f.Write(content) + if err != nil || bytesWritten != len(content) { return pr.returnDefaultErrOutputJSON(err) } - return outputFile.Close() + + return nil } func (pr *PrintResults) printTextOutputVulnerability() { @@ -222,37 +233,44 @@ func (pr *PrintResults) printTextOutputVulnerability() { pr.printTotalVulnerabilities() } +//nolint:funlen 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 +287,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 +361,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 }