github.com/seilagamo/poc-lava-release@v0.3.3-rc3/internal/report/report_test.go (about)

     1  // Copyright 2023 Adevinta
     2  
     3  package report
     4  
     5  import (
     6  	"fmt"
     7  	"os"
     8  	"path"
     9  	"testing"
    10  
    11  	vreport "github.com/adevinta/vulcan-report"
    12  	"github.com/google/go-cmp/cmp"
    13  	"github.com/google/go-cmp/cmp/cmpopts"
    14  
    15  	"github.com/seilagamo/poc-lava-release/internal/config"
    16  	"github.com/seilagamo/poc-lava-release/internal/engine"
    17  )
    18  
    19  func TestWriter_calculateExitCode(t *testing.T) {
    20  	tests := []struct {
    21  		name    string
    22  		sum     summary
    23  		rConfig config.ReportConfig
    24  		want    ExitCode
    25  	}{
    26  		{
    27  			name: "critical",
    28  			sum: summary{
    29  				count: map[config.Severity]int{
    30  					config.SeverityCritical: 1,
    31  					config.SeverityHigh:     1,
    32  					config.SeverityMedium:   1,
    33  					config.SeverityLow:      1,
    34  					config.SeverityInfo:     1,
    35  				},
    36  			},
    37  			rConfig: config.ReportConfig{
    38  				Severity: config.SeverityInfo,
    39  			},
    40  			want: ExitCodeCritical,
    41  		},
    42  		{
    43  			name: "high",
    44  			sum: summary{
    45  				count: map[config.Severity]int{
    46  					config.SeverityCritical: 0,
    47  					config.SeverityHigh:     1,
    48  					config.SeverityMedium:   1,
    49  					config.SeverityLow:      1,
    50  					config.SeverityInfo:     1,
    51  				},
    52  			},
    53  			rConfig: config.ReportConfig{
    54  				Severity: config.SeverityInfo,
    55  			},
    56  			want: ExitCodeHigh,
    57  		},
    58  		{
    59  			name: "medium",
    60  			sum: summary{
    61  				count: map[config.Severity]int{
    62  					config.SeverityCritical: 0,
    63  					config.SeverityHigh:     0,
    64  					config.SeverityMedium:   1,
    65  					config.SeverityLow:      1,
    66  					config.SeverityInfo:     1,
    67  				},
    68  			},
    69  			rConfig: config.ReportConfig{
    70  				Severity: config.SeverityInfo,
    71  			},
    72  			want: ExitCodeMedium,
    73  		},
    74  		{
    75  			name: "low",
    76  			sum: summary{
    77  				count: map[config.Severity]int{
    78  					config.SeverityCritical: 0,
    79  					config.SeverityHigh:     0,
    80  					config.SeverityMedium:   0,
    81  					config.SeverityLow:      1,
    82  					config.SeverityInfo:     1,
    83  				},
    84  			},
    85  			rConfig: config.ReportConfig{
    86  				Severity: config.SeverityInfo,
    87  			},
    88  			want: ExitCodeLow,
    89  		},
    90  		{
    91  			name: "info",
    92  			sum: summary{
    93  				count: map[config.Severity]int{
    94  					config.SeverityCritical: 0,
    95  					config.SeverityHigh:     0,
    96  					config.SeverityMedium:   0,
    97  					config.SeverityLow:      0,
    98  					config.SeverityInfo:     1,
    99  				},
   100  			},
   101  			rConfig: config.ReportConfig{
   102  				Severity: config.SeverityInfo,
   103  			},
   104  			want: ExitCodeInfo,
   105  		},
   106  		{
   107  			name: "no exit code",
   108  			sum: summary{
   109  				count: map[config.Severity]int{
   110  					config.SeverityCritical: 0,
   111  					config.SeverityHigh:     0,
   112  					config.SeverityMedium:   1,
   113  					config.SeverityLow:      1,
   114  					config.SeverityInfo:     1,
   115  				},
   116  			},
   117  			rConfig: config.ReportConfig{
   118  				Severity: config.SeverityHigh,
   119  			},
   120  			want: 0,
   121  		},
   122  	}
   123  	for _, tt := range tests {
   124  		t.Run(tt.name, func(t *testing.T) {
   125  			w, err := NewWriter(tt.rConfig)
   126  			if err != nil {
   127  				t.Fatalf("unable to create a report writer: %v", err)
   128  			}
   129  			got := w.calculateExitCode(tt.sum)
   130  			if got != tt.want {
   131  				t.Errorf("unexpected exit code: got: %v, want: %v", got, tt.want)
   132  			}
   133  		})
   134  	}
   135  }
   136  
   137  func TestScoreToSeverity(t *testing.T) {
   138  	tests := []struct {
   139  		name  string
   140  		score float32
   141  		want  config.Severity
   142  	}{
   143  		{
   144  			name:  "critical",
   145  			score: 9,
   146  			want:  config.SeverityCritical,
   147  		},
   148  		{
   149  			name:  "high",
   150  			score: 7,
   151  			want:  config.SeverityHigh,
   152  		},
   153  		{
   154  			name:  "medium",
   155  			score: 4,
   156  			want:  config.SeverityMedium,
   157  		},
   158  		{
   159  			name:  "low",
   160  			score: 0.1,
   161  			want:  config.SeverityLow,
   162  		},
   163  		{
   164  			name:  "info",
   165  			score: 0,
   166  			want:  config.SeverityInfo,
   167  		},
   168  	}
   169  	for _, tt := range tests {
   170  		t.Run(tt.name, func(t *testing.T) {
   171  			got := scoreToSeverity(tt.score)
   172  			if got != tt.want {
   173  				t.Errorf("unexpected severity: got: %v, want: %v", got, tt.want)
   174  			}
   175  		})
   176  	}
   177  }
   178  
   179  func TestWriter_parseReport(t *testing.T) {
   180  	tests := []struct {
   181  		name       string
   182  		report     engine.Report
   183  		rConfig    config.ReportConfig
   184  		want       []vulnerability
   185  		wantNilErr bool
   186  	}{
   187  		{
   188  			name: "all vulnerabilities included",
   189  			report: map[string]vreport.Report{
   190  				"CheckID1": {
   191  					CheckData: vreport.CheckData{
   192  						CheckID: "CheckID1",
   193  					},
   194  					ResultData: vreport.ResultData{
   195  						Vulnerabilities: []vreport.Vulnerability{
   196  							{
   197  								Summary: "Vulnerability Summary 1",
   198  							},
   199  						},
   200  					},
   201  				},
   202  				"CheckID2": {
   203  					CheckData: vreport.CheckData{
   204  						CheckID: "CheckID2",
   205  					},
   206  					ResultData: vreport.ResultData{
   207  						Vulnerabilities: []vreport.Vulnerability{
   208  							{
   209  								Summary: "Vulnerability Summary 2",
   210  								Score:   6.7,
   211  							},
   212  						},
   213  					},
   214  				},
   215  			},
   216  			rConfig: config.ReportConfig{
   217  				Exclusions: []config.Exclusion{},
   218  			},
   219  			want: []vulnerability{
   220  				{
   221  					CheckData: vreport.CheckData{
   222  						CheckID: "CheckID1",
   223  					},
   224  					Vulnerability: vreport.Vulnerability{
   225  						Summary: "Vulnerability Summary 1",
   226  					},
   227  					Severity: config.SeverityInfo,
   228  					excluded: false,
   229  				},
   230  				{
   231  					CheckData: vreport.CheckData{
   232  						CheckID: "CheckID2",
   233  					},
   234  					Vulnerability: vreport.Vulnerability{
   235  						Summary: "Vulnerability Summary 2",
   236  						Score:   6.7,
   237  					},
   238  					Severity: config.SeverityMedium,
   239  					excluded: false,
   240  				},
   241  			},
   242  			wantNilErr: true,
   243  		},
   244  		{
   245  			name: "some vulnerabilities excluded",
   246  			report: map[string]vreport.Report{
   247  				"CheckID1": {
   248  					CheckData: vreport.CheckData{
   249  						CheckID: "CheckID1",
   250  					},
   251  					ResultData: vreport.ResultData{
   252  						Vulnerabilities: []vreport.Vulnerability{
   253  							{
   254  								Summary: "Vulnerability Summary 1",
   255  							},
   256  						},
   257  					},
   258  				},
   259  				"CheckID2": {
   260  					CheckData: vreport.CheckData{
   261  						CheckID: "CheckID2",
   262  					},
   263  					ResultData: vreport.ResultData{
   264  						Vulnerabilities: []vreport.Vulnerability{
   265  							{
   266  								Summary: "Vulnerability Summary 2",
   267  								Score:   6.7,
   268  							},
   269  						},
   270  					},
   271  				},
   272  			},
   273  			rConfig: config.ReportConfig{
   274  				Exclusions: []config.Exclusion{
   275  					{Summary: "Summary 2"},
   276  				},
   277  			},
   278  			want: []vulnerability{
   279  				{
   280  					CheckData: vreport.CheckData{
   281  						CheckID: "CheckID1",
   282  					},
   283  					Vulnerability: vreport.Vulnerability{
   284  						Summary: "Vulnerability Summary 1",
   285  					},
   286  					Severity: config.SeverityInfo,
   287  					excluded: false,
   288  				},
   289  				{
   290  					CheckData: vreport.CheckData{
   291  						CheckID: "CheckID2",
   292  					},
   293  					Vulnerability: vreport.Vulnerability{
   294  						Summary: "Vulnerability Summary 2",
   295  						Score:   6.7,
   296  					},
   297  					Severity: config.SeverityMedium,
   298  					excluded: true,
   299  				},
   300  			},
   301  			wantNilErr: true,
   302  		},
   303  	}
   304  	for _, tt := range tests {
   305  		t.Run(tt.name, func(t *testing.T) {
   306  			w, err := NewWriter(tt.rConfig)
   307  			if err != nil {
   308  				t.Fatalf("unable to create a report writer: %v", err)
   309  			}
   310  			got, err := w.parseReport(tt.report)
   311  			if (err == nil) != tt.wantNilErr {
   312  				t.Errorf("unexpected error value: %v", err)
   313  			}
   314  			diffOpts := []cmp.Option{
   315  				cmp.AllowUnexported(vulnerability{}),
   316  				cmpopts.SortSlices(vulnLess),
   317  			}
   318  			if diff := cmp.Diff(tt.want, got, diffOpts...); diff != "" {
   319  				t.Errorf("vulnerabilities mismatch (-want +got):\n%v", diff)
   320  			}
   321  		})
   322  	}
   323  }
   324  
   325  func TestWriter_isExcluded(t *testing.T) {
   326  	tests := []struct {
   327  		name          string
   328  		vulnerability vreport.Vulnerability
   329  		target        string
   330  		rConfig       config.ReportConfig
   331  		want          bool
   332  		wantNilErr    bool
   333  	}{
   334  		{
   335  			name: "empty exclusions",
   336  			vulnerability: vreport.Vulnerability{
   337  				Summary: "Vulnerability Summary 1",
   338  				Score:   6.7,
   339  			},
   340  			target: ".",
   341  			rConfig: config.ReportConfig{
   342  				Exclusions: []config.Exclusion{},
   343  			},
   344  			want:       false,
   345  			wantNilErr: true,
   346  		},
   347  		{
   348  			name: "exclude by summary",
   349  			vulnerability: vreport.Vulnerability{
   350  				Summary: "Vulnerability Summary 1",
   351  				Score:   6.7,
   352  			},
   353  			target: ".",
   354  			rConfig: config.ReportConfig{
   355  				Exclusions: []config.Exclusion{
   356  					{
   357  						Summary:     "Summary 1",
   358  						Description: "Excluded vulnerabilities Summary 1",
   359  					},
   360  				},
   361  			},
   362  			want:       true,
   363  			wantNilErr: true,
   364  		},
   365  		{
   366  			name: "not exclude by summary",
   367  			vulnerability: vreport.Vulnerability{
   368  				Summary: "Vulnerability Summary 1",
   369  				Score:   6.7,
   370  			},
   371  			target: ".",
   372  			rConfig: config.ReportConfig{
   373  				Exclusions: []config.Exclusion{
   374  					{
   375  						Summary:     "Summary 2",
   376  						Description: "Excluded vulnerabilities Summary 2",
   377  					},
   378  				},
   379  			},
   380  			want:       false,
   381  			wantNilErr: true,
   382  		},
   383  		{
   384  			name: "exclude by fingerprint",
   385  			vulnerability: vreport.Vulnerability{
   386  				Summary:     "Vulnerability Summary 1",
   387  				Score:       6.7,
   388  				Fingerprint: "12345",
   389  			},
   390  			target: ".",
   391  			rConfig: config.ReportConfig{
   392  				Exclusions: []config.Exclusion{
   393  					{
   394  						Fingerprint: "12345",
   395  					},
   396  				},
   397  			},
   398  			want:       true,
   399  			wantNilErr: true,
   400  		},
   401  		{
   402  			name: "exclude by affected resource",
   403  			vulnerability: vreport.Vulnerability{
   404  				Summary:          "Vulnerability Summary 1",
   405  				Score:            6.7,
   406  				AffectedResource: "Resource 1",
   407  			},
   408  			target: ".",
   409  			rConfig: config.ReportConfig{
   410  				Exclusions: []config.Exclusion{
   411  					{
   412  						Resource: "Resource 1",
   413  					},
   414  				},
   415  			},
   416  			want:       true,
   417  			wantNilErr: true,
   418  		},
   419  		{
   420  			name: "exclude by affected resource string",
   421  			vulnerability: vreport.Vulnerability{
   422  				Summary:                "Vulnerability Summary 1",
   423  				Score:                  6.7,
   424  				AffectedResourceString: "Resource String 1",
   425  			},
   426  			target: ".",
   427  			rConfig: config.ReportConfig{
   428  				Exclusions: []config.Exclusion{
   429  					{
   430  						Resource: "Resource String 1",
   431  					},
   432  				},
   433  			},
   434  			want:       true,
   435  			wantNilErr: true,
   436  		},
   437  		{
   438  			name: "exclude by target",
   439  			vulnerability: vreport.Vulnerability{
   440  				Summary: "Vulnerability Summary 1",
   441  				Score:   6.7,
   442  			},
   443  			target: ".",
   444  			rConfig: config.ReportConfig{
   445  				Exclusions: []config.Exclusion{
   446  					{
   447  						Target: ".",
   448  					},
   449  				},
   450  			},
   451  			want:       true,
   452  			wantNilErr: true,
   453  		},
   454  		{
   455  			name: "match all exclusion criteria (resource)",
   456  			vulnerability: vreport.Vulnerability{
   457  				Summary:          "Vulnerability Summary 1",
   458  				Score:            6.7,
   459  				AffectedResource: "Resource 1",
   460  				Fingerprint:      "12345",
   461  			},
   462  			target: ".",
   463  			rConfig: config.ReportConfig{
   464  				Exclusions: []config.Exclusion{
   465  					{
   466  						Summary:     "Summary 1",
   467  						Resource:    "Resource 1",
   468  						Fingerprint: "12345",
   469  						Target:      ".",
   470  					},
   471  				},
   472  			},
   473  			want:       true,
   474  			wantNilErr: true,
   475  		},
   476  		{
   477  			name: "match all exclusion criteria (resource string)",
   478  			vulnerability: vreport.Vulnerability{
   479  				Summary:                "Vulnerability Summary 1",
   480  				Score:                  6.7,
   481  				AffectedResourceString: "Resource String 1",
   482  				Fingerprint:            "12345",
   483  			},
   484  			target: ".",
   485  			rConfig: config.ReportConfig{
   486  				Exclusions: []config.Exclusion{
   487  					{
   488  						Summary:     "Summary 1",
   489  						Resource:    "Resource String 1",
   490  						Fingerprint: "12345",
   491  						Target:      ".",
   492  					},
   493  				},
   494  			},
   495  			want:       true,
   496  			wantNilErr: true,
   497  		},
   498  		{
   499  			name: "match all exclusion criteria (resource and resource string)",
   500  			vulnerability: vreport.Vulnerability{
   501  				Summary:                "Vulnerability Summary 1",
   502  				Score:                  6.7,
   503  				AffectedResource:       "Resource 1",
   504  				AffectedResourceString: "Resource String 1",
   505  				Fingerprint:            "12345",
   506  			},
   507  			target: ".",
   508  			rConfig: config.ReportConfig{
   509  				Exclusions: []config.Exclusion{
   510  					{
   511  						Summary:     "Summary 1",
   512  						Resource:    "Resource",
   513  						Fingerprint: "12345",
   514  						Target:      ".",
   515  					},
   516  				},
   517  			},
   518  			want:       true,
   519  			wantNilErr: true,
   520  		},
   521  		{
   522  			name: "fail an exclusion criteria",
   523  			vulnerability: vreport.Vulnerability{
   524  				Summary:                "Vulnerability Summary 1",
   525  				Score:                  6.7,
   526  				AffectedResource:       "Resource 1",
   527  				AffectedResourceString: "Resource String 1",
   528  				Fingerprint:            "12345",
   529  			},
   530  			target: ".",
   531  			rConfig: config.ReportConfig{
   532  				Exclusions: []config.Exclusion{
   533  					{
   534  						Summary:     "Summary 1",
   535  						Resource:    "not found",
   536  						Fingerprint: "12345",
   537  						Target:      ".",
   538  					},
   539  				},
   540  			},
   541  			want:       false,
   542  			wantNilErr: true,
   543  		},
   544  	}
   545  	for _, tt := range tests {
   546  		t.Run(tt.name, func(t *testing.T) {
   547  			w, err := NewWriter(tt.rConfig)
   548  			if err != nil {
   549  				t.Fatalf("unable to create a report writer: %v", err)
   550  			}
   551  			got, err := w.isExcluded(tt.vulnerability, tt.target)
   552  			if (err == nil) != tt.wantNilErr {
   553  				t.Errorf("unexpected error value: %v", err)
   554  			}
   555  			if got != tt.want {
   556  				t.Errorf("unexpected excluded value: got: %v, want: %v", got, tt.want)
   557  			}
   558  		})
   559  	}
   560  }
   561  
   562  func TestMkSummary(t *testing.T) {
   563  	tests := []struct {
   564  		name            string
   565  		vulnerabilities []vulnerability
   566  		want            summary
   567  		wantNilErr      bool
   568  	}{
   569  		{
   570  			name: "happy path",
   571  			vulnerabilities: []vulnerability{
   572  				{
   573  					Vulnerability: vreport.Vulnerability{
   574  						Summary: "Vulnerability Summary 1",
   575  					},
   576  					Severity: config.SeverityCritical,
   577  					excluded: false,
   578  				},
   579  				{
   580  					Vulnerability: vreport.Vulnerability{
   581  						Summary: "Vulnerability Summary 2",
   582  					},
   583  					Severity: config.SeverityCritical,
   584  					excluded: true,
   585  				},
   586  				{
   587  					Vulnerability: vreport.Vulnerability{
   588  						Summary: "Vulnerability Summary 3",
   589  					},
   590  					Severity: config.SeverityHigh,
   591  					excluded: false,
   592  				},
   593  				{
   594  					Vulnerability: vreport.Vulnerability{
   595  						Summary: "Vulnerability Summary 4",
   596  					},
   597  					Severity: config.SeverityHigh,
   598  					excluded: true,
   599  				},
   600  				{
   601  					Vulnerability: vreport.Vulnerability{
   602  						Summary: "Vulnerability Summary 5",
   603  					},
   604  					Severity: config.SeverityMedium,
   605  					excluded: false,
   606  				},
   607  				{
   608  					Vulnerability: vreport.Vulnerability{
   609  						Summary: "Vulnerability Summary 6",
   610  					},
   611  					Severity: config.SeverityMedium,
   612  					excluded: true,
   613  				},
   614  				{
   615  					Vulnerability: vreport.Vulnerability{
   616  						Summary: "Vulnerability Summary 7",
   617  					},
   618  					Severity: config.SeverityLow,
   619  					excluded: false,
   620  				},
   621  				{
   622  					Vulnerability: vreport.Vulnerability{
   623  						Summary: "Vulnerability Summary 8",
   624  					},
   625  					Severity: config.SeverityLow,
   626  					excluded: true,
   627  				},
   628  				{
   629  					Vulnerability: vreport.Vulnerability{
   630  						Summary: "Vulnerability Summary 9",
   631  					},
   632  					Severity: config.SeverityInfo,
   633  					excluded: false,
   634  				},
   635  				{
   636  					Vulnerability: vreport.Vulnerability{
   637  						Summary: "Vulnerability Summary 10",
   638  					},
   639  					Severity: config.SeverityInfo,
   640  					excluded: true,
   641  				},
   642  			},
   643  			want: summary{
   644  				count: map[config.Severity]int{
   645  					config.SeverityCritical: 1,
   646  					config.SeverityHigh:     1,
   647  					config.SeverityMedium:   1,
   648  					config.SeverityLow:      1,
   649  					config.SeverityInfo:     1,
   650  				},
   651  				excluded: 5,
   652  			},
   653  			wantNilErr: true,
   654  		},
   655  		{
   656  			name: "unknown severity",
   657  			vulnerabilities: []vulnerability{
   658  				{
   659  					Vulnerability: vreport.Vulnerability{
   660  						Summary: "Vulnerability Summary 1",
   661  					},
   662  					Severity: 7,
   663  					excluded: false,
   664  				},
   665  			},
   666  			want: summary{
   667  				count:    nil,
   668  				excluded: 0,
   669  			},
   670  			wantNilErr: false,
   671  		},
   672  		{
   673  			name:            "empty summary",
   674  			vulnerabilities: []vulnerability{},
   675  			want: summary{
   676  				count:    nil,
   677  				excluded: 0,
   678  			},
   679  			wantNilErr: true,
   680  		},
   681  	}
   682  	for _, tt := range tests {
   683  		t.Run(tt.name, func(t *testing.T) {
   684  			got, err := mkSummary(tt.vulnerabilities)
   685  			if (err == nil) != tt.wantNilErr {
   686  				t.Errorf("unexpected error value: %v", err)
   687  			}
   688  			if diff := cmp.Diff(tt.want, got, cmp.AllowUnexported(summary{})); diff != "" {
   689  				t.Errorf("summary mismatch (-want +got):\n%v", diff)
   690  			}
   691  		})
   692  	}
   693  }
   694  
   695  func TestWriter_filterVulns(t *testing.T) {
   696  	tests := []struct {
   697  		name            string
   698  		vulnerabilities []vulnerability
   699  		rConfig         config.ReportConfig
   700  		want            []vulnerability
   701  	}{
   702  		{
   703  			name: "filter excluded",
   704  			vulnerabilities: []vulnerability{
   705  				{
   706  					Vulnerability: vreport.Vulnerability{
   707  						Summary: "Vulnerability Summary 1",
   708  					},
   709  					Severity: config.SeverityCritical,
   710  					excluded: false,
   711  				},
   712  				{
   713  					Vulnerability: vreport.Vulnerability{
   714  						Summary: "Vulnerability Summary 2",
   715  					},
   716  					Severity: config.SeverityCritical,
   717  					excluded: true,
   718  				},
   719  				{
   720  					Vulnerability: vreport.Vulnerability{
   721  						Summary: "Vulnerability Summary 3",
   722  					},
   723  					Severity: config.SeverityHigh,
   724  					excluded: false,
   725  				},
   726  				{
   727  					Vulnerability: vreport.Vulnerability{
   728  						Summary: "Vulnerability Summary 4",
   729  					},
   730  					Severity: config.SeverityHigh,
   731  					excluded: true,
   732  				},
   733  				{
   734  					Vulnerability: vreport.Vulnerability{
   735  						Summary: "Vulnerability Summary 5",
   736  					},
   737  					Severity: config.SeverityMedium,
   738  					excluded: false,
   739  				},
   740  				{
   741  					Vulnerability: vreport.Vulnerability{
   742  						Summary: "Vulnerability Summary 6",
   743  					},
   744  					Severity: config.SeverityMedium,
   745  					excluded: true,
   746  				},
   747  				{
   748  					Vulnerability: vreport.Vulnerability{
   749  						Summary: "Vulnerability Summary 7",
   750  					},
   751  					Severity: config.SeverityLow,
   752  					excluded: false,
   753  				},
   754  				{
   755  					Vulnerability: vreport.Vulnerability{
   756  						Summary: "Vulnerability Summary 8",
   757  					},
   758  					Severity: config.SeverityLow,
   759  					excluded: true,
   760  				},
   761  				{
   762  					Vulnerability: vreport.Vulnerability{
   763  						Summary: "Vulnerability Summary 9",
   764  					},
   765  					Severity: config.SeverityInfo,
   766  					excluded: false,
   767  				},
   768  				{
   769  					Vulnerability: vreport.Vulnerability{
   770  						Summary: "Vulnerability Summary 10",
   771  					},
   772  					Severity: config.SeverityInfo,
   773  					excluded: true,
   774  				},
   775  			},
   776  			rConfig: config.ReportConfig{
   777  				Severity: config.SeverityInfo,
   778  			},
   779  			want: []vulnerability{
   780  				{
   781  					Vulnerability: vreport.Vulnerability{
   782  						Summary: "Vulnerability Summary 1",
   783  					},
   784  					Severity: config.SeverityCritical,
   785  					excluded: false,
   786  				},
   787  				{
   788  					Vulnerability: vreport.Vulnerability{
   789  						Summary: "Vulnerability Summary 3",
   790  					},
   791  					Severity: config.SeverityHigh,
   792  					excluded: false,
   793  				},
   794  				{
   795  					Vulnerability: vreport.Vulnerability{
   796  						Summary: "Vulnerability Summary 5",
   797  					},
   798  					Severity: config.SeverityMedium,
   799  					excluded: false,
   800  				},
   801  				{
   802  					Vulnerability: vreport.Vulnerability{
   803  						Summary: "Vulnerability Summary 7",
   804  					},
   805  					Severity: config.SeverityLow,
   806  					excluded: false,
   807  				},
   808  				{
   809  					Vulnerability: vreport.Vulnerability{
   810  						Summary: "Vulnerability Summary 9",
   811  					},
   812  					Severity: config.SeverityInfo,
   813  					excluded: false,
   814  				},
   815  			},
   816  		},
   817  		{
   818  			name: "filter excluded and lower than high",
   819  			vulnerabilities: []vulnerability{
   820  				{
   821  					Vulnerability: vreport.Vulnerability{
   822  						Summary: "Vulnerability Summary 1",
   823  					},
   824  					Severity: config.SeverityCritical,
   825  					excluded: false,
   826  				},
   827  				{
   828  					Vulnerability: vreport.Vulnerability{
   829  						Summary: "Vulnerability Summary 2",
   830  					},
   831  					Severity: config.SeverityCritical,
   832  					excluded: true,
   833  				},
   834  				{
   835  					Vulnerability: vreport.Vulnerability{
   836  						Summary: "Vulnerability Summary 3",
   837  					},
   838  					Severity: config.SeverityHigh,
   839  					excluded: false,
   840  				},
   841  				{
   842  					Vulnerability: vreport.Vulnerability{
   843  						Summary: "Vulnerability Summary 4",
   844  					},
   845  					Severity: config.SeverityHigh,
   846  					excluded: true,
   847  				},
   848  				{
   849  					Vulnerability: vreport.Vulnerability{
   850  						Summary: "Vulnerability Summary 5",
   851  					},
   852  					Severity: config.SeverityMedium,
   853  					excluded: false,
   854  				},
   855  				{
   856  					Vulnerability: vreport.Vulnerability{
   857  						Summary: "Vulnerability Summary 6",
   858  					},
   859  					Severity: config.SeverityMedium,
   860  					excluded: true,
   861  				},
   862  				{
   863  					Vulnerability: vreport.Vulnerability{
   864  						Summary: "Vulnerability Summary 7",
   865  					},
   866  					Severity: config.SeverityLow,
   867  					excluded: false,
   868  				},
   869  				{
   870  					Vulnerability: vreport.Vulnerability{
   871  						Summary: "Vulnerability Summary 8",
   872  					},
   873  					Severity: config.SeverityLow,
   874  					excluded: true,
   875  				},
   876  				{
   877  					Vulnerability: vreport.Vulnerability{
   878  						Summary: "Vulnerability Summary 9",
   879  					},
   880  					Severity: config.SeverityInfo,
   881  					excluded: false,
   882  				},
   883  				{
   884  					Vulnerability: vreport.Vulnerability{
   885  						Summary: "Vulnerability Summary 10",
   886  					},
   887  					Severity: config.SeverityInfo,
   888  					excluded: true,
   889  				},
   890  			},
   891  			rConfig: config.ReportConfig{
   892  				Severity: config.SeverityHigh,
   893  			},
   894  			want: []vulnerability{
   895  				{
   896  					Vulnerability: vreport.Vulnerability{
   897  						Summary: "Vulnerability Summary 1",
   898  					},
   899  					Severity: config.SeverityCritical,
   900  					excluded: false,
   901  				},
   902  				{
   903  					Vulnerability: vreport.Vulnerability{
   904  						Summary: "Vulnerability Summary 3",
   905  					},
   906  					Severity: config.SeverityHigh,
   907  					excluded: false,
   908  				},
   909  			},
   910  		},
   911  	}
   912  	for _, tt := range tests {
   913  		t.Run(tt.name, func(t *testing.T) {
   914  			w, err := NewWriter(tt.rConfig)
   915  			if err != nil {
   916  				t.Fatalf("unable to create a report writer: %v", err)
   917  			}
   918  			got := w.filterVulns(tt.vulnerabilities)
   919  			if diff := cmp.Diff(tt.want, got, cmp.AllowUnexported(vulnerability{})); diff != "" {
   920  				t.Errorf("summary mismatch (-want +got):\n%v", diff)
   921  			}
   922  		})
   923  	}
   924  }
   925  
   926  func TestNewWriter_OutputFile(t *testing.T) {
   927  	tests := []struct {
   928  		name         string
   929  		report       engine.Report
   930  		rConfig      config.ReportConfig
   931  		wantExitCode ExitCode
   932  		wantNilErr   bool
   933  	}{
   934  		{
   935  			name: "Standard Output JSON Report",
   936  			report: map[string]vreport.Report{
   937  				"CheckID1": {
   938  					CheckData: vreport.CheckData{
   939  						CheckID: "CheckID1",
   940  					},
   941  					ResultData: vreport.ResultData{
   942  						Vulnerabilities: []vreport.Vulnerability{
   943  							{
   944  								Summary: "Vulnerability Summary 1",
   945  								Description: "Lorem ipsum dolor sit amet, " +
   946  									"consectetur adipiscing elit. Nam malesuada " +
   947  									"pretium ligula, ac egestas leo egestas nec. " +
   948  									"Morbi id placerat ipsum. Donec semper enim urna, " +
   949  									"et bibendum ex dictum in. Quisque venenatis " +
   950  									"in sem in lacinia. Fusce lacus odio, molestie " +
   951  									"vitae mi nec, elementum pellentesque augue. " +
   952  									"Aenean imperdiet odio eu sodales molestie. " +
   953  									"Fusce ut elementum leo. Nam sodales molestie " +
   954  									"lorem in rutrum. Pellentesque nec sapien elit. " +
   955  									"Sed tincidunt ut augue sit amet cursus. " +
   956  									"In convallis magna sit amet tempus pellentesque. " +
   957  									"Nam commodo porttitor ante sed volutpat. " +
   958  									"Ut vulputate leo quis ultricies sodales.",
   959  								AffectedResource: "Affected Resource 1",
   960  								ImpactDetails:    "Impact detail 1",
   961  								Recommendations: []string{
   962  									"Recommendation 1",
   963  									"Recommendation 2",
   964  									"Recommendation 3",
   965  								},
   966  								Details: "Vulnerability Detail 1",
   967  								References: []string{
   968  									"Reference 1",
   969  									"Reference 2",
   970  									"Reference 3",
   971  								},
   972  								Resources: []vreport.ResourcesGroup{
   973  									{
   974  										Name: "Resource 1",
   975  										Header: []string{
   976  											"Header 1",
   977  											"Header 2",
   978  											"Header 3",
   979  											"Header 4",
   980  										},
   981  										Rows: []map[string]string{
   982  											{
   983  												"Header 1": "row 11",
   984  												"Header 2": "row 12",
   985  												"Header 3": "row 13",
   986  												"Header 4": "row 14",
   987  											},
   988  											{
   989  												"Header 1": "row 21",
   990  												"Header 2": "row 22",
   991  												"Header 3": "row 23",
   992  												"Header 4": "row 24",
   993  											},
   994  											{
   995  												"Header 1": "row 31",
   996  												"Header 2": "row 32",
   997  												"Header 3": "row 33",
   998  												"Header 4": "row 34",
   999  											},
  1000  											{
  1001  												"Header 1": "row 41",
  1002  												"Header 2": "row 42",
  1003  												"Header 3": "row 43",
  1004  												"Header 4": "row 44",
  1005  											},
  1006  										},
  1007  									},
  1008  									{
  1009  										Name: "Resource 2",
  1010  										Header: []string{
  1011  											"Header 1",
  1012  											"Header 2",
  1013  										},
  1014  										Rows: []map[string]string{
  1015  											{
  1016  												"Header 1": "row 11",
  1017  												"Header 2": "row 12",
  1018  											},
  1019  											{
  1020  												"Header 1": "row 21",
  1021  												"Header 2": "row 22",
  1022  											},
  1023  										},
  1024  									},
  1025  									{
  1026  										Name: "Resource 3",
  1027  										Header: []string{
  1028  											"Header 1",
  1029  											"Header 2",
  1030  										},
  1031  										Rows: []map[string]string{
  1032  											{
  1033  												"Header 1": "row 11",
  1034  												"Header 2": "row 12",
  1035  											},
  1036  											{
  1037  												"Header 1": "row 21",
  1038  												"Header 2": "row 22",
  1039  											},
  1040  										},
  1041  									},
  1042  									{
  1043  										Name: "Resource 4",
  1044  										Header: []string{
  1045  											"Header 1",
  1046  											"Header 2",
  1047  										},
  1048  										Rows: []map[string]string{
  1049  											{
  1050  												"Header 1": "row 11",
  1051  												"Header 2": "row 12",
  1052  											},
  1053  											{
  1054  												"Header 1": "row 21",
  1055  												"Header 2": "row 22",
  1056  											},
  1057  										},
  1058  									},
  1059  								},
  1060  							},
  1061  						},
  1062  					},
  1063  				},
  1064  			},
  1065  			rConfig: config.ReportConfig{
  1066  				Severity:   config.SeverityInfo,
  1067  				OutputFile: "test.json",
  1068  				Format:     config.OutputFormatJSON,
  1069  			},
  1070  			wantExitCode: ExitCodeInfo,
  1071  			wantNilErr:   true,
  1072  		},
  1073  	}
  1074  	for _, tt := range tests {
  1075  		t.Run(tt.name, func(t *testing.T) {
  1076  			tmpPath, err := os.MkdirTemp("", "")
  1077  			if err != nil {
  1078  				t.Fatalf("unable to create a temporary dir")
  1079  			}
  1080  			defer os.RemoveAll(tmpPath)
  1081  
  1082  			tt.rConfig.OutputFile = path.Join(tmpPath, tt.rConfig.OutputFile)
  1083  			writer, err := NewWriter(tt.rConfig)
  1084  			if err != nil {
  1085  				t.Fatalf("unable to create a report writer: %v", err)
  1086  			}
  1087  			defer writer.Close()
  1088  			gotExitCode, err := writer.Write(tt.report)
  1089  			if (err == nil) != tt.wantNilErr {
  1090  				t.Errorf("unexpected error value: %v", err)
  1091  			}
  1092  			if gotExitCode != tt.wantExitCode {
  1093  				t.Errorf("unexpected error value: got: %d, want: %d", gotExitCode, tt.wantExitCode)
  1094  			}
  1095  
  1096  			if _, err = os.Stat(tt.rConfig.OutputFile); err != nil {
  1097  				t.Fatalf("unexpected error value: %v", err)
  1098  			}
  1099  		})
  1100  	}
  1101  }
  1102  
  1103  func vulnLess(a, b vulnerability) bool {
  1104  	h := func(v vulnerability) string {
  1105  		return fmt.Sprintf("%#v", v)
  1106  	}
  1107  	return h(a) < h(b)
  1108  }