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  }