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