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  }