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 }