github.com/jfrog/frogbot@v1.1.1-0.20231221090046-821a26f50338/scanpullrequest/scanallpullrequests_test.go (about) 1 package scanpullrequest 2 3 import ( 4 "context" 5 "fmt" 6 "github.com/golang/mock/gomock" 7 biutils "github.com/jfrog/build-info-go/utils" 8 "github.com/jfrog/frogbot/testdata" 9 "github.com/jfrog/frogbot/utils" 10 "github.com/jfrog/frogbot/utils/outputwriter" 11 "github.com/jfrog/froggit-go/vcsclient" 12 "github.com/jfrog/froggit-go/vcsutils" 13 "github.com/stretchr/testify/assert" 14 "path/filepath" 15 "testing" 16 "time" 17 ) 18 19 var ( 20 gitParams = &utils.Repository{ 21 OutputWriter: &outputwriter.SimplifiedOutput{}, 22 Params: utils.Params{ 23 Git: utils.Git{ 24 RepoOwner: "repo-owner", 25 Branches: []string{"master"}, 26 RepoName: "repo-name", 27 }, 28 }, 29 } 30 allPrIntegrationPath = filepath.Join(outputwriter.TestMessagesDir, "integration") 31 ) 32 33 type MockParams struct { 34 repoName string 35 repoOwner string 36 sourceBranchName string 37 targetBranchName string 38 } 39 40 //go:generate go run github.com/golang/mock/mockgen@v1.6.0 -destination=../testdata/vcsclientmock.go -package=testdata github.com/jfrog/froggit-go/vcsclient VcsClient 41 func TestShouldScanPullRequestNewPR(t *testing.T) { 42 // Init mock 43 client := CreateMockVcsClient(t) 44 prID := 0 45 client.EXPECT().ListPullRequestComments(context.Background(), gitParams.RepoOwner, gitParams.RepoName, prID).Return([]vcsclient.CommentInfo{}, nil) 46 // Run handleFrogbotLabel 47 shouldScan, err := shouldScanPullRequest(*gitParams, client, prID) 48 assert.NoError(t, err) 49 assert.True(t, shouldScan) 50 } 51 52 func TestShouldScanPullRequestReScan(t *testing.T) { 53 // Init mock 54 client := CreateMockVcsClient(t) 55 prID := 0 56 client.EXPECT().ListPullRequestComments(context.Background(), gitParams.RepoOwner, gitParams.RepoName, prID).Return([]vcsclient.CommentInfo{ 57 {Content: outputwriter.GetSimplifiedTitle(outputwriter.VulnerabilitiesPrBannerSource) + "text \n table\n text text text", Created: time.Unix(1, 0)}, 58 {Content: utils.RescanRequestComment, Created: time.Unix(1, 1)}, 59 }, nil) 60 shouldScan, err := shouldScanPullRequest(*gitParams, client, prID) 61 assert.NoError(t, err) 62 assert.True(t, shouldScan) 63 } 64 65 func TestShouldNotScanPullRequestReScan(t *testing.T) { 66 // Init mock 67 client := CreateMockVcsClient(t) 68 prID := 0 69 client.EXPECT().ListPullRequestComments(context.Background(), gitParams.RepoOwner, gitParams.RepoName, prID).Return([]vcsclient.CommentInfo{ 70 {Content: outputwriter.MarkAsBold(outputwriter.GetSimplifiedTitle(outputwriter.VulnerabilitiesPrBannerSource)) + "text \n table\n text text text", Created: time.Unix(1, 0)}, 71 {Content: utils.RescanRequestComment, Created: time.Unix(1, 1)}, 72 {Content: outputwriter.MarkAsBold(outputwriter.GetSimplifiedTitle(outputwriter.NoVulnerabilityPrBannerSource)) + "text \n table\n text text text", Created: time.Unix(3, 0)}, 73 }, nil) 74 shouldScan, err := shouldScanPullRequest(*gitParams, client, prID) 75 assert.NoError(t, err) 76 assert.False(t, shouldScan) 77 } 78 79 func TestShouldNotScanPullRequest(t *testing.T) { 80 // Init mock 81 client := CreateMockVcsClient(t) 82 prID := 0 83 client.EXPECT().ListPullRequestComments(context.Background(), gitParams.RepoOwner, gitParams.RepoName, prID).Return([]vcsclient.CommentInfo{ 84 {Content: outputwriter.MarkAsBold(outputwriter.GetSimplifiedTitle(outputwriter.NoVulnerabilityPrBannerSource)) + "text \n table\n text text text", Created: time.Unix(3, 0)}, 85 }, nil) 86 shouldScan, err := shouldScanPullRequest(*gitParams, client, prID) 87 assert.NoError(t, err) 88 assert.False(t, shouldScan) 89 } 90 91 func TestShouldNotScanPullRequestError(t *testing.T) { 92 // Init mock 93 client := CreateMockVcsClient(t) 94 prID := 0 95 client.EXPECT().ListPullRequestComments(context.Background(), gitParams.RepoOwner, gitParams.RepoName, prID).Return([]vcsclient.CommentInfo{}, fmt.Errorf("Bad Request")) 96 shouldScan, err := shouldScanPullRequest(*gitParams, client, prID) 97 assert.Error(t, err) 98 assert.False(t, shouldScan) 99 } 100 101 func TestScanAllPullRequestsMultiRepo(t *testing.T) { 102 server, restoreEnv := utils.VerifyEnv(t) 103 defer restoreEnv() 104 failOnSecurityIssues := false 105 firstRepoParams := utils.Params{ 106 Scan: utils.Scan{ 107 FailOnSecurityIssues: &failOnSecurityIssues, 108 Projects: []utils.Project{{ 109 InstallCommandName: "npm", 110 InstallCommandArgs: []string{"i"}, 111 WorkingDirs: []string{utils.RootDir}, 112 UseWrapper: &utils.TrueVal, 113 }}, 114 }, 115 Git: gitParams.Git, 116 } 117 secondRepoParams := utils.Params{ 118 Git: gitParams.Git, 119 Scan: utils.Scan{ 120 FailOnSecurityIssues: &failOnSecurityIssues, 121 Projects: []utils.Project{{WorkingDirs: []string{utils.RootDir}, UseWrapper: &utils.TrueVal}}}, 122 } 123 124 configAggregator := utils.RepoAggregator{ 125 utils.Repository{ 126 OutputWriter: &outputwriter.StandardOutput{}, 127 Server: server, 128 Params: firstRepoParams, 129 }, 130 utils.Repository{ 131 OutputWriter: &outputwriter.StandardOutput{}, 132 Server: server, 133 Params: secondRepoParams, 134 }, 135 } 136 mockParams := []MockParams{ 137 {gitParams.RepoName, gitParams.RepoOwner, "test-proj-with-vulnerability", "test-proj"}, 138 {gitParams.RepoName, gitParams.RepoOwner, "test-proj-pip-with-vulnerability", "test-proj-pip"}, 139 } 140 var frogbotMessages []string 141 client := getMockClient(t, &frogbotMessages, mockParams...) 142 scanAllPullRequestsCmd := &ScanAllPullRequestsCmd{} 143 err := scanAllPullRequestsCmd.Run(configAggregator, client, utils.MockHasConnection()) 144 if assert.NoError(t, err) { 145 assert.Len(t, frogbotMessages, 4) 146 expectedMessage := outputwriter.GetOutputFromFile(t, filepath.Join(allPrIntegrationPath, "test_proj_with_vulnerability_standard.md")) 147 assert.Equal(t, expectedMessage, frogbotMessages[0]) 148 expectedMessage = outputwriter.GetPRSummaryContentNoIssues(t, outputwriter.TestSummaryCommentDir, true, false) 149 assert.Equal(t, expectedMessage, frogbotMessages[1]) 150 expectedMessage = outputwriter.GetOutputFromFile(t, filepath.Join(allPrIntegrationPath, "test_proj_pip_with_vulnerability.md")) 151 assert.Equal(t, expectedMessage, frogbotMessages[2]) 152 expectedMessage = outputwriter.GetPRSummaryContentNoIssues(t, outputwriter.TestSummaryCommentDir, true, false) 153 assert.Equal(t, expectedMessage, frogbotMessages[3]) 154 } 155 } 156 157 func TestScanAllPullRequests(t *testing.T) { 158 // This integration test, requires JFrog platform connection details 159 server, restoreEnv := utils.VerifyEnv(t) 160 defer restoreEnv() 161 falseVal := false 162 gitParams.Git.GitProvider = vcsutils.BitbucketServer 163 params := utils.Params{ 164 Scan: utils.Scan{ 165 FailOnSecurityIssues: &falseVal, 166 Projects: []utils.Project{{ 167 InstallCommandName: "npm", 168 InstallCommandArgs: []string{"i"}, 169 WorkingDirs: []string{"."}, 170 UseWrapper: &utils.TrueVal, 171 }}, 172 }, 173 Git: gitParams.Git, 174 } 175 repoParams := &utils.Repository{ 176 OutputWriter: &outputwriter.SimplifiedOutput{}, 177 Server: server, 178 Params: params, 179 } 180 paramsAggregator := utils.RepoAggregator{} 181 paramsAggregator = append(paramsAggregator, *repoParams) 182 var frogbotMessages []string 183 client := getMockClient(t, &frogbotMessages, MockParams{repoParams.RepoName, repoParams.RepoOwner, "test-proj-with-vulnerability", "test-proj"}) 184 scanAllPullRequestsCmd := &ScanAllPullRequestsCmd{} 185 err := scanAllPullRequestsCmd.Run(paramsAggregator, client, utils.MockHasConnection()) 186 assert.NoError(t, err) 187 assert.Len(t, frogbotMessages, 2) 188 expectedMessage := outputwriter.GetOutputFromFile(t, filepath.Join(allPrIntegrationPath, "test_proj_with_vulnerability_simplified.md")) 189 assert.Equal(t, expectedMessage, frogbotMessages[0]) 190 expectedMessage = outputwriter.GetPRSummaryContentNoIssues(t, outputwriter.TestSummaryCommentDir, true, true) 191 assert.Equal(t, expectedMessage, frogbotMessages[1]) 192 } 193 194 func getMockClient(t *testing.T, frogbotMessages *[]string, mockParams ...MockParams) *testdata.MockVcsClient { 195 // Init mock 196 client := CreateMockVcsClient(t) 197 for _, params := range mockParams { 198 sourceBranchInfo := vcsclient.BranchInfo{Name: params.sourceBranchName, Repository: params.repoName, Owner: params.repoOwner} 199 targetBranchInfo := vcsclient.BranchInfo{Name: params.targetBranchName, Repository: params.repoName, Owner: params.repoOwner} 200 // Return 2 pull requests to scan, the first with issues the second "clean". 201 client.EXPECT().ListOpenPullRequests(context.Background(), params.repoOwner, params.repoName).Return([]vcsclient.PullRequestInfo{{ID: 1, Source: sourceBranchInfo, Target: targetBranchInfo}, {ID: 2, Source: targetBranchInfo, Target: targetBranchInfo}}, nil) 202 // Return empty comments slice so expect the code to scan both pull requests. 203 client.EXPECT().ListPullRequestComments(context.Background(), params.repoOwner, params.repoName, gomock.Any()).Return([]vcsclient.CommentInfo{}, nil).AnyTimes() 204 client.EXPECT().ListPullRequestReviewComments(context.Background(), params.repoOwner, params.repoName, gomock.Any()).Return([]vcsclient.CommentInfo{}, nil).AnyTimes() 205 // Copy test project according to the given branch name, instead of download it. 206 client.EXPECT().DownloadRepository(context.Background(), params.repoOwner, params.repoName, gomock.Any(), gomock.Any()).DoAndReturn(fakeRepoDownload).AnyTimes() 207 // Capture the result comment post 208 client.EXPECT().AddPullRequestComment(context.Background(), params.repoOwner, params.repoName, gomock.Any(), gomock.Any()).DoAndReturn(func(_ context.Context, _, _, content string, _ int) error { 209 *frogbotMessages = append(*frogbotMessages, content) 210 return nil 211 }).AnyTimes() 212 client.EXPECT().AddPullRequestReviewComments(context.Background(), params.repoOwner, params.repoName, gomock.Any(), gomock.Any()).DoAndReturn(func(_ context.Context, _, _, content string, _ int) error { 213 *frogbotMessages = append(*frogbotMessages, content) 214 return nil 215 }).AnyTimes() 216 client.EXPECT().DeletePullRequestComment(context.Background(), params.repoOwner, params.repoName, gomock.Any(), gomock.Any()).Return(nil).AnyTimes() 217 client.EXPECT().DeletePullRequestReviewComments(context.Background(), params.repoOwner, params.repoName, gomock.Any(), gomock.Any()).Return(nil).AnyTimes() 218 // Return private repositories visibility 219 client.EXPECT().GetRepositoryInfo(context.Background(), gomock.Any(), gomock.Any()).Return(vcsclient.RepositoryInfo{RepositoryVisibility: vcsclient.Private}, nil).AnyTimes() 220 // Return latest commit info for XSC context. 221 client.EXPECT().GetLatestCommit(context.Background(), params.repoOwner, params.repoName, gomock.Any()).Return(vcsclient.CommitInfo{}, nil).AnyTimes() 222 } 223 return client 224 } 225 226 // To accurately simulate the "real" repository download, the tests project must be located in the same directory. 227 // The process involves the following steps: 228 // 1. First, the "test-proj-with-vulnerability" project, which includes a "test-proj" directory, will be copied to a temporary directory with a random name. This project will be utilized during the source auditing phase to mimic a pull request with a new vulnerable dependency. 229 // 2. Next, a second "download" will take place within the first temporary directory. As a result, the "test-proj" directory will be discovered and copied to a second temporary directory with another random name. This copied version will be used during the target auditing phase. 230 func fakeRepoDownload(_ context.Context, _, _, testProject, targetDir string) error { 231 sourceDir, err := filepath.Abs(filepath.Join("..", "testdata", "scanallpullrequests", testProject)) 232 if err != nil { 233 return err 234 } 235 return biutils.CopyDir(sourceDir, targetDir, true, []string{}) 236 } 237 238 func CreateMockVcsClient(t *testing.T) *testdata.MockVcsClient { 239 return testdata.NewMockVcsClient(gomock.NewController(t)) 240 }