diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 41126799d..6e6689490 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -432,6 +432,89 @@ jobs: JF_ACCESS_TOKEN: ${{ secrets.PLATFORM_ADMIN_TOKEN }} FROGBOT_V3_TESTS_GITLAB_TOKEN: ${{ secrets.FROGBOT_V3_TESTS_GITLAB_TOKEN }} + bitbucket-cloud-integration: + name: Bitbucket Cloud Integration Tests + needs: Pretest + runs-on: ${{ matrix.os }}-latest + strategy: + fail-fast: false + max-parallel: 1 + matrix: + os: [ ubuntu, windows, macos ] + concurrency: + group: bitbucket-cloud-integration-fixture + cancel-in-progress: false + env: + JFROG_CLI_LOG_LEVEL: "DEBUG" + steps: + - uses: actions/checkout@v7 + with: + ref: ${{ github.event.pull_request.head.sha }} + allow-unsafe-pr-checkout: true + persist-credentials: false + + - uses: jfrog/boost@v0 + with: + accept_terms: yes + + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version-file: 'go.mod' + cache: false + - name: Go Cache Build & Tests + uses: actions/cache@v4 + with: + path: | + ~/.cache/go-build + ~\AppData\Local\go-build + ~/Library/Caches/go-build + key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}-${{ hashFiles('**/*.go') }} + restore-keys: | + ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}- + ${{ runner.os }}-go- + + - name: Run Tests + run: go test -tags integration -run 'TestBitbucketCloud_' bitbucket_cloud_test.go integrationutils.go commands.go -v -race -timeout 30m -cover + env: + JF_URL: ${{ secrets.PLATFORM_URL }} + JF_ACCESS_TOKEN: ${{ secrets.PLATFORM_ADMIN_TOKEN }} + FROGBOT_V3_TESTS_BITBUCKET_CLOUD_TOKEN: ${{ secrets.FROGBOT_V3_TESTS_BITBUCKET_CLOUD_TOKEN }} + + bitbucket-cloud-integration-cleanup: + name: Cleanup Bitbucket Cloud Integration Test Artifacts + needs: [ Pretest, bitbucket-cloud-integration ] + if: ${{ always() && !cancelled() && needs.Pretest.result != 'skipped' && needs.bitbucket-cloud-integration.result != 'skipped' }} + runs-on: ubuntu-latest + concurrency: + group: bitbucket-cloud-integration-fixture + cancel-in-progress: false + env: + JFROG_CLI_LOG_LEVEL: "DEBUG" + steps: + - uses: actions/checkout@v7 + with: + ref: ${{ github.event.pull_request.head.sha }} + allow-unsafe-pr-checkout: true + persist-credentials: false + + - uses: jfrog/boost@v0 + with: + accept_terms: yes + + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version-file: 'go.mod' + cache: false + + - name: Run Cleanup + run: go test -tags integration -run TestHelper_CleanupIntegrationTestsArtifactsBitbucketCloud bitbucket_cloud_test.go integrationutils.go commands.go -v + env: + JF_URL: ${{ secrets.PLATFORM_URL }} + JF_ACCESS_TOKEN: ${{ secrets.PLATFORM_ADMIN_TOKEN }} + FROGBOT_V3_TESTS_BITBUCKET_CLOUD_TOKEN: ${{ secrets.FROGBOT_V3_TESTS_BITBUCKET_CLOUD_TOKEN }} + bitbucket-server-integration: name: Bitbucket Server Integration Tests needs: Pretest diff --git a/bitbucket_cloud_test.go b/bitbucket_cloud_test.go new file mode 100644 index 000000000..ad7c3948b --- /dev/null +++ b/bitbucket_cloud_test.go @@ -0,0 +1,56 @@ +//go:build integration + +package main + +import ( + "github.com/jfrog/frogbot/v3/utils" + "github.com/jfrog/froggit-go/vcsclient" + "github.com/jfrog/froggit-go/vcsutils" + "github.com/stretchr/testify/assert" + "testing" +) + +const ( + //#nosec G101 -- False positive - no hardcoded credentials. + bitbucketCloudIntegrationTokenEnv = "FROGBOT_V3_TESTS_BITBUCKET_CLOUD_TOKEN" + bitbucketCloudGitCloneUrl = "https://bitbucket.org/frogbot-e2e-test/frogbot-test.git" + bitbucketCloudRepoOwner = "frogbot-e2e-test" + // Matches utils.toBasicAuth's own default for git operations. Bitbucket Cloud's REST API rejects + // this username though, hence GitPushUsername being kept separate from the (empty) REST username. + bitbucketCloudGitPushUsername = "x-token-auth" +) + +func buildBitbucketCloudClient(t *testing.T, bitbucketCloudToken string) vcsclient.VcsClient { + bbClient, err := vcsclient.NewClientBuilder(vcsutils.BitbucketCloud).Token(bitbucketCloudToken).Build() + assert.NoError(t, err) + return bbClient +} + +func buildBitbucketCloudIntegrationTestDetails(t *testing.T) *IntegrationTestDetails { + integrationRepoToken := getIntegrationToken(t, bitbucketCloudIntegrationTokenEnv) + testDetails := NewIntegrationTestDetails(integrationRepoToken, string(utils.BitbucketCloud), bitbucketCloudGitCloneUrl, bitbucketCloudRepoOwner) + testDetails.GitUsername = "" + testDetails.GitPushUsername = bitbucketCloudGitPushUsername + return testDetails +} + +func bitbucketCloudTestsInit(t *testing.T) (vcsclient.VcsClient, *IntegrationTestDetails) { + testDetails := buildBitbucketCloudIntegrationTestDetails(t) + bbClient := buildBitbucketCloudClient(t, testDetails.GitToken) + return bbClient, testDetails +} + +func TestBitbucketCloud_ScanPullRequestIntegration(t *testing.T) { + bbClient, testDetails := bitbucketCloudTestsInit(t) + runScanPullRequestCmd(t, bbClient, testDetails) +} + +func TestBitbucketCloud_ScanRepositoryIntegration(t *testing.T) { + bbClient, testDetails := bitbucketCloudTestsInit(t) + runScanRepositoryCmd(t, bbClient, testDetails) +} + +func TestHelper_CleanupIntegrationTestsArtifactsBitbucketCloud(t *testing.T) { + bbClient, testDetails := bitbucketCloudTestsInit(t) + cleanupIntegrationArtifacts(t, bbClient, testDetails) +} diff --git a/go.mod b/go.mod index 6b1f2f123..341010eb7 100644 --- a/go.mod +++ b/go.mod @@ -140,4 +140,4 @@ require ( // replace github.com/jfrog/jfrog-client-go => github.com/jfrog/jfrog-client-go master -// replace github.com/jfrog/froggit-go => github.com/jfrog/froggit-go master +replace github.com/jfrog/froggit-go => github.com/jfrog/froggit-go v1.23.2-0.20260909160929-7d982ce8ac90 diff --git a/go.sum b/go.sum index bf9e8d0c8..9074fa709 100644 --- a/go.sum +++ b/go.sum @@ -144,8 +144,8 @@ github.com/jfrog/archiver/v3 v3.6.5 h1:AiNXJoe8jYDOtyykfVuwh26aM4rk/ei+YzBpfBukd github.com/jfrog/archiver/v3 v3.6.5/go.mod h1:5V9l+Fte30Y4qe9dUOAd3yNTf8lmtVNuhKNrvI8PMhg= github.com/jfrog/build-info-go v1.13.1-0.20260902120316-b325d342b210 h1:u1Ijj6fOX9hCzz27L3IpqFqdSsjOlg6Td7URtmNFVR8= github.com/jfrog/build-info-go v1.13.1-0.20260902120316-b325d342b210/go.mod h1:CYRUCvLKfyARjoJXLWAxce1qNUxTEtbRKAARkV42vpE= -github.com/jfrog/froggit-go v1.23.1 h1:4wmaHeuptxVINbovMaeITzVhi3+VQoc/FFIjF4axzu0= -github.com/jfrog/froggit-go v1.23.1/go.mod h1:wRDryqyp3oe+eHgME2mpnEQmO8XBECIPagFwj0nHmdI= +github.com/jfrog/froggit-go v1.23.2-0.20260909160929-7d982ce8ac90 h1:PKe1Qo+/leup348YO8UXCK5wZmfgW8mSy2vE+XM4Dzw= +github.com/jfrog/froggit-go v1.23.2-0.20260909160929-7d982ce8ac90/go.mod h1:wRDryqyp3oe+eHgME2mpnEQmO8XBECIPagFwj0nHmdI= github.com/jfrog/gofrog v1.7.7 h1:I2MSi+ytPqfprhOC+MLDk5fVvuRzVjTqNkzrp9RtEvY= github.com/jfrog/gofrog v1.7.7/go.mod h1:b/eFC21upqkt+fNwWXnAEkhjeAgP9b2gu55kT8ovAJU= github.com/jfrog/jfrog-apps-config v1.0.1 h1:mtv6k7g8A8BVhlHGlSveapqf4mJfonwvXYLipdsOFMY= diff --git a/integrationutils.go b/integrationutils.go index 48ab778af..775031d01 100644 --- a/integrationutils.go +++ b/integrationutils.go @@ -44,11 +44,19 @@ type IntegrationTestDetails struct { GitProvider string GitProject string GitUsername string + GitPushUsername string ApiEndpoint string PullRequestID string CustomBranchName string } +func (d *IntegrationTestDetails) gitPushUsername() string { + if d.GitPushUsername != "" { + return d.GitPushUsername + } + return d.GitUsername +} + func NewIntegrationTestDetails(token, gitProvider, gitCloneUrl, repoOwner string) *IntegrationTestDetails { return &IntegrationTestDetails{ GitProject: repoName, @@ -63,7 +71,7 @@ func NewIntegrationTestDetails(token, gitProvider, gitCloneUrl, repoOwner string func buildGitManager(t *testing.T, testDetails *IntegrationTestDetails) *utils.GitManager { gitManager, err := utils.NewGitManager(). - SetAuth(testDetails.GitUsername, testDetails.GitToken). + SetAuth(testDetails.gitPushUsername(), testDetails.GitToken). SetRemoteGitUrl(testDetails.GitCloneURL) assert.NoError(t, err) return gitManager @@ -200,7 +208,7 @@ func runScanRepositoryCmd(t *testing.T, client vcsclient.VcsClient, testDetails cloneOptions := &git.CloneOptions{ URL: testDetails.GitCloneURL, Auth: &githttp.BasicAuth{ - Username: testDetails.GitUsername, + Username: testDetails.gitPushUsername(), Password: testDetails.GitToken, }, RemoteName: "origin", @@ -270,7 +278,7 @@ func cleanupIntegrationArtifacts(t *testing.T, client vcsclient.VcsClient, testD cloneOptions := &git.CloneOptions{ URL: testDetails.GitCloneURL, Auth: &githttp.BasicAuth{ - Username: testDetails.GitUsername, + Username: testDetails.gitPushUsername(), Password: testDetails.GitToken, }, RemoteName: "origin", @@ -316,6 +324,8 @@ func validateResults(t *testing.T, ctx context.Context, client vcsclient.VcsClie validateAzureComments(t, comments) case *vcsclient.BitbucketServerClient: validateBitbucketServerComments(t, comments) + case *vcsclient.BitbucketCloudClient: + validateBitbucketCloudComments(t, comments) case *vcsclient.GitLabClient: validateGitLabComments(t, comments) } @@ -349,6 +359,15 @@ func validateBitbucketServerComments(t *testing.T, comments []vcsclient.CommentI assertBannerExists(t, comments, outputwriter.GetSimplifiedTitle(outputwriter.VulnerabilitiesPrBannerSource)) } +func validateBitbucketCloudComments(t *testing.T, comments []vcsclient.CommentInfo) { + assert.True(t, containsCommentMentioning(comments, outputwriter.GetSimplifiedTitle(outputwriter.VulnerabilitiesPrBannerSource)), + "expected a PR comment containing the Frogbot banner") + assert.True(t, containsCommentMentioning(comments, scanPrTestAddedVulnDependency), + "expected a PR comment mentioning the vulnerable dependency "+scanPrTestAddedVulnDependency) + assert.True(t, containsCommentMentioning(comments, cveCommentPrefix), + "expected a PR comment with CVE findings") +} + func validateGitLabComments(t *testing.T, comments []vcsclient.CommentInfo) { assert.True(t, containsCommentMentioning(comments, string(outputwriter.VulnerabilitiesMrBannerSource)), "expected a MR comment containing the Frogbot banner")