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