github.com/jfrog/frogbot@v1.1.1-0.20231221090046-821a26f50338/utils/outputwriter/outputcontent_test.go (about) 1 package outputwriter 2 3 import ( 4 "path/filepath" 5 "strconv" 6 "testing" 7 8 "github.com/jfrog/froggit-go/vcsutils" 9 "github.com/jfrog/jfrog-cli-core/v2/xray/formats" 10 "github.com/jfrog/jfrog-cli-core/v2/xray/utils" 11 "github.com/stretchr/testify/assert" 12 ) 13 14 func TestIsFrogbotSummaryComment(t *testing.T) { 15 testCases := []struct { 16 name string 17 comment string 18 cases []OutputTestCase 19 }{ 20 { 21 name: "No Summary comment", 22 comment: "This comment is unrelated to Frogbot", 23 cases: []OutputTestCase{ 24 { 25 name: "Standard output (PR)", 26 writer: &StandardOutput{}, 27 expectedOutput: "false", 28 }, 29 { 30 name: "Standard output (MR)", 31 writer: &StandardOutput{MarkdownOutput{vcsProvider: vcsutils.GitLab}}, 32 expectedOutput: "false", 33 }, 34 { 35 name: "Simplified output", 36 writer: &SimplifiedOutput{}, 37 expectedOutput: "false", 38 }, 39 }, 40 }, 41 { 42 name: "No Vulnerability PR", 43 comment: "This is a comment with the " + GetBanner(NoVulnerabilityPrBannerSource) + " icon", 44 cases: []OutputTestCase{ 45 { 46 name: "Standard output (PR)", 47 writer: &StandardOutput{}, 48 expectedOutput: "true", 49 }, 50 { 51 name: "Standard output (MR)", 52 writer: &StandardOutput{MarkdownOutput{vcsProvider: vcsutils.GitLab}}, 53 expectedOutput: "false", 54 }, 55 { 56 name: "Simplified output", 57 writer: &SimplifiedOutput{}, 58 expectedOutput: "true", 59 }, 60 }, 61 }, 62 { 63 name: "No Vulnerability MR", 64 comment: "This is a comment with the " + GetBanner(NoVulnerabilityMrBannerSource) + " icon", 65 cases: []OutputTestCase{ 66 { 67 name: "Standard output (PR)", 68 writer: &StandardOutput{}, 69 expectedOutput: "false", 70 }, 71 { 72 name: "Standard output (MR)", 73 writer: &StandardOutput{MarkdownOutput{vcsProvider: vcsutils.GitLab}}, 74 expectedOutput: "true", 75 }, 76 { 77 name: "Simplified output", 78 writer: &SimplifiedOutput{}, 79 expectedOutput: "false", 80 }, 81 }, 82 }, 83 { 84 name: "No Vulnerability simplified", 85 comment: "This is a comment with the " + MarkAsBold(GetSimplifiedTitle(NoVulnerabilityPrBannerSource)) + " icon", 86 cases: []OutputTestCase{ 87 { 88 name: "Standard output (PR)", 89 writer: &StandardOutput{}, 90 expectedOutput: "true", 91 }, 92 { 93 name: "Standard output (MR)", 94 writer: &StandardOutput{MarkdownOutput{vcsProvider: vcsutils.GitLab}}, 95 expectedOutput: "false", 96 }, 97 { 98 name: "Simplified output", 99 writer: &SimplifiedOutput{}, 100 expectedOutput: "true", 101 }, 102 }, 103 }, 104 { 105 name: "Vulnerability PR", 106 comment: "This is a comment with the " + GetBanner(VulnerabilitiesPrBannerSource) + " icon", 107 cases: []OutputTestCase{ 108 { 109 name: "Standard output (PR)", 110 writer: &StandardOutput{}, 111 expectedOutput: "true", 112 }, 113 { 114 name: "Standard output (MR)", 115 writer: &StandardOutput{MarkdownOutput{vcsProvider: vcsutils.GitLab}}, 116 expectedOutput: "false", 117 }, 118 { 119 name: "Simplified output", 120 writer: &SimplifiedOutput{}, 121 expectedOutput: "true", 122 }, 123 }, 124 }, 125 { 126 name: "Vulnerability MR", 127 comment: "This is a comment with the " + GetBanner(VulnerabilitiesMrBannerSource) + " icon", 128 cases: []OutputTestCase{ 129 { 130 name: "Standard output (PR)", 131 writer: &StandardOutput{}, 132 expectedOutput: "false", 133 }, 134 { 135 name: "Standard output (MR)", 136 writer: &StandardOutput{MarkdownOutput{vcsProvider: vcsutils.GitLab}}, 137 expectedOutput: "true", 138 }, 139 { 140 name: "Simplified output", 141 writer: &SimplifiedOutput{}, 142 expectedOutput: "false", 143 }, 144 }, 145 }, 146 { 147 name: "Vulnerability simplified", 148 comment: "This is a comment with the " + MarkAsBold(GetSimplifiedTitle(VulnerabilitiesPrBannerSource)) + " icon", 149 cases: []OutputTestCase{ 150 { 151 name: "Standard output (PR)", 152 writer: &StandardOutput{}, 153 expectedOutput: "true", 154 }, 155 { 156 name: "Standard output (MR)", 157 writer: &StandardOutput{MarkdownOutput{vcsProvider: vcsutils.GitLab}}, 158 expectedOutput: "false", 159 }, 160 { 161 name: "Simplified output", 162 writer: &SimplifiedOutput{}, 163 expectedOutput: "true", 164 }, 165 }, 166 }, 167 } 168 for _, tc := range testCases { 169 for _, test := range tc.cases { 170 expected, err := strconv.ParseBool(GetExpectedTestOutput(t, test)) 171 assert.NoError(t, err) 172 t.Run(tc.name+"_"+test.name, func(t *testing.T) { 173 assert.Equal(t, expected, IsFrogbotSummaryComment(test.writer, tc.comment)) 174 }) 175 } 176 } 177 } 178 179 func TestGetPRSummaryContent(t *testing.T) { 180 testCases := []struct { 181 name string 182 cases []OutputTestCase 183 issuesExists bool 184 isComment bool 185 }{ 186 { 187 name: "Summary comment No issues found", 188 issuesExists: false, 189 isComment: true, 190 cases: []OutputTestCase{ 191 { 192 name: "Pull Request not entitled (Standard output)", 193 writer: &StandardOutput{MarkdownOutput{hasInternetConnection: true}}, 194 expectedOutputPath: filepath.Join(testSummaryCommentDir, "structure", "summary_comment_no_issues_pr_not_entitled.md"), 195 }, 196 { 197 name: "Pull Request entitled (Standard output)", 198 writer: &StandardOutput{MarkdownOutput{hasInternetConnection: true, entitledForJas: true}}, 199 expectedOutputPath: filepath.Join(testSummaryCommentDir, "structure", "summary_comment_no_issues_pr_entitled.md"), 200 }, 201 { 202 name: "Merge Request not entitled (Standard output)", 203 writer: &StandardOutput{MarkdownOutput{hasInternetConnection: true, vcsProvider: vcsutils.GitLab}}, 204 expectedOutputPath: filepath.Join(testSummaryCommentDir, "structure", "summary_comment_no_issues_mr_not_entitled.md"), 205 }, 206 { 207 name: "Merge Request entitled (Standard output)", 208 writer: &StandardOutput{MarkdownOutput{hasInternetConnection: true, vcsProvider: vcsutils.GitLab, entitledForJas: true}}, 209 expectedOutputPath: filepath.Join(testSummaryCommentDir, "structure", "summary_comment_no_issues_mr_entitled.md"), 210 }, 211 { 212 name: "Simplified output not entitled", 213 writer: &SimplifiedOutput{MarkdownOutput{hasInternetConnection: true}}, 214 expectedOutputPath: filepath.Join(testSummaryCommentDir, "structure", "summary_comment_no_issues_simplified_not_entitled.md"), 215 }, 216 { 217 name: "Simplified output entitled", 218 writer: &SimplifiedOutput{MarkdownOutput{hasInternetConnection: true, entitledForJas: true}}, 219 expectedOutputPath: filepath.Join(testSummaryCommentDir, "structure", "summary_comment_no_issues_simplified_entitled.md"), 220 }, 221 { 222 name: "Pull request not entitled custom title (Standard output)", 223 writer: &StandardOutput{MarkdownOutput{hasInternetConnection: true, pullRequestCommentTitle: "Custom title"}}, 224 expectedOutputPath: filepath.Join(testSummaryCommentDir, "structure", "summary_comment_no_issues_pr_not_entitled_with_title.md"), 225 }, 226 { 227 name: "Pull Request not entitled custom title avoid extra messages (Standard output)", 228 writer: &StandardOutput{MarkdownOutput{hasInternetConnection: true, pullRequestCommentTitle: "Custom title", avoidExtraMessages: true}}, 229 expectedOutputPath: filepath.Join(testSummaryCommentDir, "structure", "summary_comment_no_issues_pr_entitled_with_title.md"), 230 }, 231 { 232 name: "Pull request not entitled custom title (Simplified output)", 233 writer: &SimplifiedOutput{MarkdownOutput{hasInternetConnection: true, pullRequestCommentTitle: "Custom title"}}, 234 expectedOutputPath: filepath.Join(testSummaryCommentDir, "structure", "summary_comment_no_issues_simplified_not_entitled_with_title.md"), 235 }, 236 { 237 name: "Merge Request not entitled avoid extra messages (Standard output)", 238 writer: &StandardOutput{MarkdownOutput{hasInternetConnection: true, vcsProvider: vcsutils.GitLab, avoidExtraMessages: true}}, 239 expectedOutputPath: filepath.Join(testSummaryCommentDir, "structure", "summary_comment_no_issues_mr_entitled.md"), 240 }, 241 }, 242 }, 243 { 244 name: "Summary comment Found issues", 245 issuesExists: true, 246 isComment: true, 247 cases: []OutputTestCase{ 248 { 249 name: "Pull Request not entitled (Standard output)", 250 writer: &StandardOutput{MarkdownOutput{hasInternetConnection: true}}, 251 expectedOutputPath: filepath.Join(testSummaryCommentDir, "structure", "summary_comment_issues_pr_not_entitled.md"), 252 }, 253 { 254 name: "Pull Request entitled (Standard output)", 255 writer: &StandardOutput{MarkdownOutput{hasInternetConnection: true, entitledForJas: true}}, 256 expectedOutputPath: filepath.Join(testSummaryCommentDir, "structure", "summary_comment_issues_pr_entitled.md"), 257 }, 258 { 259 name: "Merge Request not entitled (Standard output)", 260 writer: &StandardOutput{MarkdownOutput{hasInternetConnection: true, vcsProvider: vcsutils.GitLab}}, 261 expectedOutputPath: filepath.Join(testSummaryCommentDir, "structure", "summary_comment_issues_mr_not_entitled.md"), 262 }, 263 { 264 name: "Merge Request entitled (Standard output)", 265 writer: &StandardOutput{MarkdownOutput{hasInternetConnection: true, vcsProvider: vcsutils.GitLab, entitledForJas: true}}, 266 expectedOutputPath: filepath.Join(testSummaryCommentDir, "structure", "summary_comment_issues_mr_entitled.md"), 267 }, 268 { 269 name: "Simplified output not entitled", 270 writer: &SimplifiedOutput{MarkdownOutput{hasInternetConnection: true}}, 271 expectedOutputPath: filepath.Join(testSummaryCommentDir, "structure", "summary_comment_issues_simplified_not_entitled.md"), 272 }, 273 { 274 name: "Pull Request not entitled avoid extra messages (Standard output)", 275 writer: &StandardOutput{MarkdownOutput{hasInternetConnection: true, avoidExtraMessages: true}}, 276 expectedOutputPath: filepath.Join(testSummaryCommentDir, "structure", "summary_comment_issues_pr_entitled.md"), 277 }, 278 { 279 name: "Simplified output not entitled avoid extra messages", 280 writer: &SimplifiedOutput{MarkdownOutput{hasInternetConnection: true, avoidExtraMessages: true}}, 281 expectedOutputPath: filepath.Join(testSummaryCommentDir, "structure", "summary_comment_issues_simplified_entitled.md"), 282 }, 283 { 284 name: "Simplified output entitled", 285 writer: &SimplifiedOutput{MarkdownOutput{hasInternetConnection: true, entitledForJas: true}}, 286 expectedOutputPath: filepath.Join(testSummaryCommentDir, "structure", "summary_comment_issues_simplified_entitled.md"), 287 }, 288 { 289 name: "Merge Request entitled custom title (Standard output)", 290 writer: &StandardOutput{MarkdownOutput{hasInternetConnection: true, vcsProvider: vcsutils.GitLab, entitledForJas: true, pullRequestCommentTitle: "Custom title"}}, 291 expectedOutputPath: filepath.Join(testSummaryCommentDir, "structure", "summary_comment_issues_mr_entitled_with_title.md"), 292 }, 293 { 294 name: "Pull Request not entitled custom title (Standard output)", 295 writer: &StandardOutput{MarkdownOutput{hasInternetConnection: true, pullRequestCommentTitle: "Custom title"}}, 296 expectedOutputPath: filepath.Join(testSummaryCommentDir, "structure", "summary_comment_issues_pr_not_entitled_with_title.md"), 297 }, 298 { 299 name: "Pull request entitled custom title (Simplified output)", 300 writer: &SimplifiedOutput{MarkdownOutput{hasInternetConnection: true, entitledForJas: true, pullRequestCommentTitle: "Custom title"}}, 301 expectedOutputPath: filepath.Join(testSummaryCommentDir, "structure", "summary_comment_issues_simplified_entitled_with_title.md"), 302 }, 303 }, 304 }, 305 { 306 name: "Frogbot Fix issues details content", 307 issuesExists: true, 308 isComment: false, 309 cases: []OutputTestCase{ 310 { 311 name: "Pull Request not entitled (Standard output)", 312 writer: &StandardOutput{MarkdownOutput{hasInternetConnection: true}}, 313 expectedOutputPath: filepath.Join(testSummaryCommentDir, "structure", "fix_pr_not_entitled.md"), 314 }, 315 { 316 name: "Pull Request entitled (Standard output)", 317 writer: &StandardOutput{MarkdownOutput{hasInternetConnection: true, entitledForJas: true}}, 318 expectedOutputPath: filepath.Join(testSummaryCommentDir, "structure", "fix_pr_entitled.md"), 319 }, 320 { 321 name: "Merge Request not entitled (Standard output)", 322 writer: &StandardOutput{MarkdownOutput{hasInternetConnection: true, vcsProvider: vcsutils.GitLab}}, 323 expectedOutputPath: filepath.Join(testSummaryCommentDir, "structure", "fix_mr_not_entitled.md"), 324 }, 325 { 326 name: "Merge Request entitled (Standard output)", 327 writer: &StandardOutput{MarkdownOutput{hasInternetConnection: true, vcsProvider: vcsutils.GitLab, entitledForJas: true}}, 328 expectedOutputPath: filepath.Join(testSummaryCommentDir, "structure", "fix_mr_entitled.md"), 329 }, 330 { 331 name: "Simplified output not entitled", 332 writer: &SimplifiedOutput{MarkdownOutput{hasInternetConnection: true}}, 333 expectedOutputPath: filepath.Join(testSummaryCommentDir, "structure", "fix_simplified_not_entitled.md"), 334 }, 335 { 336 name: "Simplified output not entitled ", 337 writer: &SimplifiedOutput{MarkdownOutput{hasInternetConnection: true}}, 338 expectedOutputPath: filepath.Join(testSummaryCommentDir, "structure", "fix_simplified_not_entitled.md"), 339 }, 340 { 341 name: "Simplified output entitled avoid extra messages", 342 writer: &SimplifiedOutput{MarkdownOutput{hasInternetConnection: true, avoidExtraMessages: true}}, 343 expectedOutputPath: filepath.Join(testSummaryCommentDir, "structure", "fix_simplified_entitled.md"), 344 }, 345 }, 346 }, 347 } 348 349 content := "\n" + MarkAsCodeSnippet("some content") 350 351 for _, tc := range testCases { 352 for _, test := range tc.cases { 353 t.Run(tc.name+"_"+test.name, func(t *testing.T) { 354 expectedOutput := GetExpectedTestOutput(t, test) 355 output := GetPRSummaryContent(content, tc.issuesExists, tc.isComment, test.writer) 356 assert.Equal(t, expectedOutput, output) 357 }) 358 } 359 } 360 } 361 362 func TestVulnerabilitiesContent(t *testing.T) { 363 testCases := []struct { 364 name string 365 vulnerabilities []formats.VulnerabilityOrViolationRow 366 cases []OutputTestCase 367 }{ 368 { 369 name: "No vulnerabilities", 370 vulnerabilities: []formats.VulnerabilityOrViolationRow{}, 371 cases: []OutputTestCase{ 372 { 373 name: "Standard output", 374 writer: &StandardOutput{}, 375 expectedOutput: "", 376 }, 377 { 378 name: "Simplified output", 379 writer: &SimplifiedOutput{}, 380 expectedOutput: "", 381 }, 382 }, 383 }, 384 { 385 name: "One vulnerability", 386 vulnerabilities: []formats.VulnerabilityOrViolationRow{ 387 { 388 Summary: "Summary CVE-2022-26652", 389 ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ 390 SeverityDetails: formats.SeverityDetails{Severity: "Medium"}, 391 ImpactedDependencyName: "github.com/nats-io/nats-streaming-server", 392 ImpactedDependencyVersion: "v0.21.0", 393 Components: []formats.ComponentRow{ 394 { 395 Name: "github.com/nats-io/nats-streaming-server", 396 Version: "v0.21.0", 397 }, 398 }, 399 }, 400 Applicable: "Undetermined", 401 FixedVersions: []string{"[0.24.3]"}, 402 JfrogResearchInformation: &formats.JfrogResearchInformation{ 403 Details: "Research CVE-2022-26652 details", 404 Remediation: "some remediation", 405 }, 406 Cves: []formats.CveRow{{Id: "CVE-2022-26652"}}, 407 }, 408 }, 409 cases: []OutputTestCase{ 410 { 411 name: "Standard output", 412 writer: &StandardOutput{}, 413 expectedOutputPath: filepath.Join(testSummaryCommentDir, "vulnerabilities", "one_vulnerability_standard.md"), 414 }, 415 { 416 name: "Simplified output", 417 writer: &SimplifiedOutput{}, 418 expectedOutputPath: filepath.Join(testSummaryCommentDir, "vulnerabilities", "one_vulnerability_simplified.md"), 419 }, 420 }, 421 }, 422 { 423 name: "One vulnerability, no Details", 424 vulnerabilities: []formats.VulnerabilityOrViolationRow{ 425 { 426 ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ 427 SeverityDetails: formats.SeverityDetails{Severity: "Medium"}, 428 ImpactedDependencyName: "github.com/nats-io/nats-streaming-server", 429 ImpactedDependencyVersion: "v0.21.0", 430 Components: []formats.ComponentRow{ 431 { 432 Name: "github.com/nats-io/nats-streaming-server", 433 Version: "v0.21.0", 434 }, 435 }, 436 }, 437 Applicable: "Undetermined", 438 FixedVersions: []string{"[0.24.3]"}, 439 Cves: []formats.CveRow{{Id: "CVE-2022-26652"}}, 440 }, 441 }, 442 cases: []OutputTestCase{ 443 { 444 name: "Standard output", 445 writer: &StandardOutput{}, 446 expectedOutputPath: filepath.Join(testSummaryCommentDir, "vulnerabilities", "one_vulnerability_no_details_standard.md"), 447 }, 448 { 449 name: "Simplified output", 450 writer: &SimplifiedOutput{}, 451 expectedOutputPath: filepath.Join(testSummaryCommentDir, "vulnerabilities", "one_vulnerability_no_details_simplified.md"), 452 }, 453 }, 454 }, 455 { 456 name: "multiple Vulnerabilities with Contextual Analysis", 457 vulnerabilities: []formats.VulnerabilityOrViolationRow{ 458 { 459 ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ 460 SeverityDetails: formats.SeverityDetails{Severity: "Critical", SeverityNumValue: utils.GetSeverity("Critical", utils.NotApplicable).SeverityNumValue}, 461 ImpactedDependencyName: "impacted", 462 ImpactedDependencyVersion: "3.0.0", 463 Components: []formats.ComponentRow{ 464 {Name: "dep1", Version: "1.0.0"}, 465 {Name: "dep2", Version: "2.0.0"}, 466 }, 467 }, 468 Applicable: "Not Applicable", 469 FixedVersions: []string{"4.0.0", "5.0.0"}, 470 Cves: []formats.CveRow{{Id: "CVE-1111-11111", Applicability: &formats.Applicability{Status: "Not Applicable"}}}, 471 }, 472 { 473 Summary: "Summary XRAY-122345", 474 ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ 475 SeverityDetails: formats.SeverityDetails{Severity: "High", SeverityNumValue: utils.GetSeverity("High", utils.ApplicabilityUndetermined).SeverityNumValue}, 476 ImpactedDependencyName: "github.com/nats-io/nats-streaming-server", 477 ImpactedDependencyVersion: "v0.21.0", 478 Components: []formats.ComponentRow{ 479 { 480 Name: "github.com/nats-io/nats-streaming-server", 481 Version: "v0.21.0", 482 }, 483 }, 484 }, 485 Applicable: "Undetermined", 486 FixedVersions: []string{"[0.24.1]"}, 487 IssueId: "XRAY-122345", 488 JfrogResearchInformation: &formats.JfrogResearchInformation{ 489 Remediation: "some remediation", 490 }, 491 Cves: []formats.CveRow{{}}, 492 }, 493 { 494 ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ 495 SeverityDetails: formats.SeverityDetails{Severity: "Medium", SeverityNumValue: utils.GetSeverity("Medium", utils.Applicable).SeverityNumValue}, 496 ImpactedDependencyName: "component-D", 497 ImpactedDependencyVersion: "v0.21.0", 498 Components: []formats.ComponentRow{ 499 { 500 Name: "component-D", 501 Version: "v0.21.0", 502 }, 503 }, 504 }, 505 Applicable: "Applicable", 506 FixedVersions: []string{"[0.24.3]"}, 507 JfrogResearchInformation: &formats.JfrogResearchInformation{ 508 Remediation: "some remediation", 509 }, 510 Cves: []formats.CveRow{ 511 {Id: "CVE-2022-26652"}, 512 {Id: "CVE-2023-4321", Applicability: &formats.Applicability{Status: "Applicable"}}, 513 }, 514 }, 515 { 516 Summary: "Summary", 517 ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ 518 SeverityDetails: formats.SeverityDetails{Severity: "Low", SeverityNumValue: utils.GetSeverity("Low", utils.ApplicabilityUndetermined).SeverityNumValue}, 519 ImpactedDependencyName: "github.com/mholt/archiver/v3", 520 ImpactedDependencyVersion: "v3.5.1", 521 Components: []formats.ComponentRow{ 522 { 523 Name: "github.com/mholt/archiver/v3", 524 Version: "v3.5.1", 525 }, 526 }, 527 }, 528 Applicable: "Undetermined", 529 Cves: []formats.CveRow{}, 530 }, 531 }, 532 cases: []OutputTestCase{ 533 { 534 name: "Standard output", 535 writer: &StandardOutput{MarkdownOutput{showCaColumn: true}}, 536 expectedOutputPath: filepath.Join(testSummaryCommentDir, "vulnerabilities", "vulnerabilities_standard.md"), 537 }, 538 { 539 name: "Simplified output", 540 writer: &SimplifiedOutput{MarkdownOutput{showCaColumn: true}}, 541 expectedOutputPath: filepath.Join(testSummaryCommentDir, "vulnerabilities", "vulnerabilities_simplified.md"), 542 }, 543 }, 544 }, 545 } 546 for _, tc := range testCases { 547 for _, test := range tc.cases { 548 t.Run(tc.name+"_"+test.name, func(t *testing.T) { 549 assert.Equal(t, GetExpectedTestOutput(t, test), VulnerabilitiesContent(tc.vulnerabilities, test.writer)) 550 }) 551 } 552 } 553 } 554 555 func TestLicensesContent(t *testing.T) { 556 testCases := []struct { 557 name string 558 licenses []formats.LicenseRow 559 cases []OutputTestCase 560 }{ 561 { 562 name: "No license violations", 563 licenses: []formats.LicenseRow{}, 564 cases: []OutputTestCase{ 565 { 566 name: "Standard output", 567 writer: &StandardOutput{}, 568 expectedOutput: "", 569 }, 570 { 571 name: "Simplified output", 572 writer: &SimplifiedOutput{}, 573 expectedOutput: "", 574 }, 575 }, 576 }, 577 { 578 name: "License violations", 579 licenses: []formats.LicenseRow{ 580 { 581 LicenseKey: "License1", 582 ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ 583 Components: []formats.ComponentRow{{Name: "Comp1", Version: "1.0"}}, 584 ImpactedDependencyName: "Dep1", 585 ImpactedDependencyVersion: "2.0", 586 }, 587 }, 588 { 589 LicenseKey: "License2", 590 ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ 591 Components: []formats.ComponentRow{ 592 { 593 Name: "root", 594 Version: "1.0.0", 595 }, 596 { 597 Name: "minimatch", 598 Version: "1.2.3", 599 }, 600 }, 601 ImpactedDependencyName: "Dep2", 602 ImpactedDependencyVersion: "3.0", 603 }, 604 }, 605 }, 606 cases: []OutputTestCase{ 607 { 608 name: "Standard output", 609 writer: &StandardOutput{}, 610 expectedOutputPath: filepath.Join(testSummaryCommentDir, "license", "license_violation_standard.md"), 611 }, 612 { 613 name: "Simplified output", 614 writer: &SimplifiedOutput{}, 615 expectedOutputPath: filepath.Join(testSummaryCommentDir, "license", "license_violation_simplified.md"), 616 }, 617 }, 618 }, 619 } 620 for _, tc := range testCases { 621 for _, test := range tc.cases { 622 t.Run(tc.name+"_"+test.name, func(t *testing.T) { 623 assert.Equal(t, GetExpectedTestOutput(t, test), LicensesContent(tc.licenses, test.writer)) 624 }) 625 } 626 } 627 } 628 629 func TestIsFrogbotReviewComment(t *testing.T) { 630 testCases := []struct { 631 name string 632 content string 633 expectedOutput bool 634 }{ 635 { 636 name: "Not frogbot comments", 637 content: "This comment is unrelated to Frogbot", 638 expectedOutput: false, 639 }, 640 { 641 name: "Frogbot review comment", 642 content: MarkdownComment(ReviewCommentId) + "This is a review comment", 643 expectedOutput: true, 644 }, 645 } 646 for _, tc := range testCases { 647 t.Run(tc.name, func(t *testing.T) { 648 assert.Equal(t, tc.expectedOutput, IsFrogbotReviewComment(tc.content)) 649 }) 650 } 651 } 652 653 func TestGenerateReviewComment(t *testing.T) { 654 testCases := []struct { 655 name string 656 location *formats.Location 657 cases []OutputTestCase 658 }{ 659 { 660 name: "Review comment structure", 661 cases: []OutputTestCase{ 662 { 663 name: "Standard output", 664 writer: &StandardOutput{}, 665 expectedOutputPath: filepath.Join(testReviewCommentDir, "review_comment_standard.md"), 666 }, 667 { 668 name: "Simplified output", 669 writer: &SimplifiedOutput{}, 670 expectedOutputPath: filepath.Join(testReviewCommentDir, "review_comment_simplified.md"), 671 }, 672 }, 673 }, 674 { 675 name: "Fallback review comment structure", 676 location: &formats.Location{ 677 File: "file", 678 StartLine: 11, 679 StartColumn: 22, 680 EndLine: 33, 681 EndColumn: 44, 682 Snippet: "snippet", 683 }, 684 cases: []OutputTestCase{ 685 { 686 name: "Standard output", 687 writer: &StandardOutput{}, 688 expectedOutputPath: filepath.Join(testReviewCommentDir, "review_comment_fallback_standard.md"), 689 }, 690 { 691 name: "Simplified output", 692 writer: &SimplifiedOutput{}, 693 expectedOutputPath: filepath.Join(testReviewCommentDir, "review_comment_fallback_simplified.md"), 694 }, 695 }, 696 }, 697 } 698 699 content := "\n" + MarkAsCodeSnippet("some review content") 700 701 for _, tc := range testCases { 702 for _, test := range tc.cases { 703 t.Run(tc.name+"_"+test.name, func(t *testing.T) { 704 expectedOutput := GetExpectedTestOutput(t, test) 705 output := GenerateReviewCommentContent(content, test.writer) 706 if tc.location != nil { 707 output = GetFallbackReviewCommentContent(content, *tc.location, test.writer) 708 } 709 assert.Equal(t, expectedOutput, output) 710 }) 711 } 712 } 713 } 714 715 func TestApplicableReviewContent(t *testing.T) { 716 testCases := []struct { 717 name string 718 severity, finding, fullDetails, cve, cveDetails, impactedDependency, remediation string 719 cases []OutputTestCase 720 }{ 721 { 722 name: "Applicable CVE review comment content", 723 severity: "Critical", 724 finding: "The vulnerable function flask.Flask.run is called", 725 fullDetails: "The scanner checks whether the vulnerable `Development Server` of the `werkzeug` library is used by looking for calls to `werkzeug.serving.run_simple()`.", 726 cve: "CVE-2022-29361", 727 cveDetails: "cveDetails", 728 impactedDependency: "werkzeug:1.0.1", 729 remediation: "some remediation", 730 cases: []OutputTestCase{ 731 { 732 name: "Standard output", 733 writer: &StandardOutput{}, 734 expectedOutputPath: filepath.Join(testReviewCommentDir, "applicable", "applicable_review_content_standard.md"), 735 }, 736 { 737 name: "Simplified output", 738 writer: &SimplifiedOutput{}, 739 expectedOutputPath: filepath.Join(testReviewCommentDir, "applicable", "applicable_review_content_simplified.md"), 740 }, 741 }, 742 }, 743 { 744 name: "No remediation", 745 severity: "Critical", 746 finding: "The vulnerable function flask.Flask.run is called", 747 fullDetails: "The scanner checks whether the vulnerable `Development Server` of the `werkzeug` library is used by looking for calls to `werkzeug.serving.run_simple()`.", 748 cve: "CVE-2022-29361", 749 cveDetails: "cveDetails", 750 impactedDependency: "werkzeug:1.0.1", 751 cases: []OutputTestCase{ 752 { 753 name: "Standard output", 754 writer: &StandardOutput{}, 755 expectedOutputPath: filepath.Join(testReviewCommentDir, "applicable", "applicable_review_content_no_remediation_standard.md"), 756 }, 757 { 758 name: "Simplified output", 759 writer: &SimplifiedOutput{}, 760 expectedOutputPath: filepath.Join(testReviewCommentDir, "applicable", "applicable_review_content_no_remediation_simplified.md"), 761 }, 762 }, 763 }, 764 } 765 766 for _, tc := range testCases { 767 for _, test := range tc.cases { 768 t.Run(tc.name+"_"+test.name, func(t *testing.T) { 769 expectedOutput := GetExpectedTestOutput(t, test) 770 assert.Equal(t, expectedOutput, ApplicableCveReviewContent(tc.severity, tc.finding, tc.fullDetails, tc.cve, tc.cveDetails, tc.impactedDependency, tc.remediation, test.writer)) 771 }) 772 } 773 } 774 } 775 776 func TestIacReviewContent(t *testing.T) { 777 testCases := []struct { 778 name string 779 severity, finding, fullDetails string 780 cases []OutputTestCase 781 }{ 782 { 783 name: "Iac review comment content", 784 severity: "Medium", 785 finding: "Missing auto upgrade was detected", 786 fullDetails: "Resource `google_container_node_pool` should have `management.auto_upgrade=true`\n\nVulnerable example - \n```\nresource \"google_container_node_pool\" \"vulnerable_example\" {\n management {\n auto_upgrade = false\n }\n}\n```\n", 787 cases: []OutputTestCase{ 788 { 789 name: "Standard output", 790 writer: &StandardOutput{}, 791 expectedOutputPath: filepath.Join(testReviewCommentDir, "iac", "iac_review_content_standard.md"), 792 }, 793 { 794 name: "Simplified output", 795 writer: &SimplifiedOutput{}, 796 expectedOutputPath: filepath.Join(testReviewCommentDir, "iac", "iac_review_content_simplified.md"), 797 }, 798 }, 799 }, 800 } 801 802 for _, tc := range testCases { 803 for _, test := range tc.cases { 804 t.Run(tc.name+"_"+test.name, func(t *testing.T) { 805 expectedOutput := GetExpectedTestOutput(t, test) 806 assert.Equal(t, expectedOutput, IacReviewContent(tc.severity, tc.finding, tc.fullDetails, test.writer)) 807 }) 808 } 809 } 810 } 811 812 func TestSastReviewContent(t *testing.T) { 813 testCases := []struct { 814 name string 815 severity string 816 finding string 817 fullDetails string 818 codeFlows [][]formats.Location 819 cases []OutputTestCase 820 }{ 821 { 822 name: "Sast review comment content", 823 severity: "Low", 824 finding: "Stack Trace Exposure", 825 fullDetails: "\n### Overview\nStack trace exposure is a type of security vulnerability that occurs when a program reveals\nsensitive information, such as the names and locations of internal files and variables,\nin error messages or other diagnostic output. This can happen when a program crashes or\nencounters an error, and the stack trace (a record of the program's call stack at the time\nof the error) is included in the output.", 826 codeFlows: [][]formats.Location{ 827 { 828 { 829 File: "file2", 830 StartLine: 1, 831 StartColumn: 2, 832 EndLine: 3, 833 EndColumn: 4, 834 Snippet: "other-snippet", 835 }, 836 { 837 File: "file", 838 StartLine: 0, 839 StartColumn: 0, 840 EndLine: 0, 841 EndColumn: 0, 842 Snippet: "snippet", 843 }, 844 }, 845 { 846 { 847 File: "file", 848 StartLine: 10, 849 StartColumn: 20, 850 EndLine: 10, 851 EndColumn: 30, 852 Snippet: "a-snippet", 853 }, 854 { 855 File: "file", 856 StartLine: 0, 857 StartColumn: 0, 858 EndLine: 0, 859 EndColumn: 0, 860 Snippet: "snippet", 861 }, 862 }, 863 }, 864 cases: []OutputTestCase{ 865 { 866 name: "Standard output", 867 writer: &StandardOutput{}, 868 expectedOutputPath: filepath.Join(testReviewCommentDir, "sast", "sast_review_content_standard.md"), 869 }, 870 { 871 name: "Simplified output", 872 writer: &SimplifiedOutput{}, 873 expectedOutputPath: filepath.Join(testReviewCommentDir, "sast", "sast_review_content_simplified.md"), 874 }, 875 }, 876 }, 877 { 878 name: "No code flows", 879 severity: "Low", 880 finding: "Stack Trace Exposure", 881 fullDetails: "\n### Overview\nStack trace exposure is a type of security vulnerability that occurs when a program reveals\nsensitive information, such as the names and locations of internal files and variables,\nin error messages or other diagnostic output. This can happen when a program crashes or\nencounters an error, and the stack trace (a record of the program's call stack at the time\nof the error) is included in the output.", 882 cases: []OutputTestCase{ 883 { 884 name: "Standard output", 885 writer: &StandardOutput{}, 886 expectedOutputPath: filepath.Join(testReviewCommentDir, "sast", "sast_review_content_no_code_flow_standard.md"), 887 }, 888 { 889 name: "Simplified output", 890 writer: &SimplifiedOutput{}, 891 expectedOutputPath: filepath.Join(testReviewCommentDir, "sast", "sast_review_content_no_code_flow_simplified.md"), 892 }, 893 }, 894 }, 895 } 896 897 for _, tc := range testCases { 898 for _, test := range tc.cases { 899 t.Run(tc.name+"_"+test.name, func(t *testing.T) { 900 expectedOutput := GetExpectedTestOutput(t, test) 901 assert.Equal(t, expectedOutput, SastReviewContent(tc.severity, tc.finding, tc.fullDetails, tc.codeFlows, test.writer)) 902 }) 903 } 904 } 905 }