diff --git a/.changeset/lucky-news-guess.md b/.changeset/lucky-news-guess.md new file mode 100644 index 0000000000..9c2cc4e8b0 --- /dev/null +++ b/.changeset/lucky-news-guess.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend-module-bitbucket-server': patch +--- + +Add examples for `publish:bitbucketServer:pull-request` scaffolder action & improve related tests diff --git a/plugins/scaffolder-backend-module-bitbucket-server/src/actions/bitbucketServerPullRequest.examples.test.ts b/plugins/scaffolder-backend-module-bitbucket-server/src/actions/bitbucketServerPullRequest.examples.test.ts new file mode 100644 index 0000000000..72bd571000 --- /dev/null +++ b/plugins/scaffolder-backend-module-bitbucket-server/src/actions/bitbucketServerPullRequest.examples.test.ts @@ -0,0 +1,406 @@ +/* + * Copyright 2023 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +jest.mock('@backstage/plugin-scaffolder-node', () => { + return { + ...jest.requireActual('@backstage/plugin-scaffolder-node'), + initRepoAndPush: jest.fn().mockResolvedValue({ + commitHash: '220f19cc36b551763d157f1b5e4a4b446165dbd6', + }), + commitAndPushRepo: jest.fn().mockResolvedValue({ + commitHash: '220f19cc36b551763d157f1b5e4a4b446165dbd6', + }), + }; +}); + +import { createPublishBitbucketServerPullRequestAction } from './bitbucketServerPullRequest'; +import { rest } from 'msw'; +import { setupServer } from 'msw/node'; +import { setupRequestMockHandlers } from '@backstage/backend-test-utils'; +import { ScmIntegrations } from '@backstage/integration'; +import { ConfigReader } from '@backstage/config'; +import yaml from 'yaml'; +import { examples } from './bitbucketServerPullRequest.examples'; +import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; + +describe('publish:bitbucketServer:pull-request', () => { + const config = new ConfigReader({ + integrations: { + bitbucketServer: [ + { + host: 'hosted.bitbucket.com', + token: 'thing', + apiBaseUrl: 'https://hosted.bitbucket.com/rest/api/1.0', + }, + { + host: 'basic-auth.bitbucket.com', + username: 'test-user', + password: 'test-password', + apiBaseUrl: 'https://basic-auth.bitbucket.com/rest/api/1.0', + }, + { + host: 'no-credentials.bitbucket.com', + }, + ], + }, + }); + + const integrations = ScmIntegrations.fromConfig(config); + const action = createPublishBitbucketServerPullRequestAction({ + integrations, + config, + }); + const mockContext = createMockActionContext({ + input: { + repoUrl: 'hosted.bitbucket.com?project=project&repo=repo', + title: 'Add Scaffolder actions for Bitbucket Server', + description: + 'I just made a Pull Request that Add Scaffolder actions for Bitbucket Server', + targetBranch: 'master', + sourceBranch: 'develop', + }, + }); + const responseOfBranches = { + size: 3, + limit: 25, + isLastPage: true, + values: [ + { + id: 'refs/heads/master', + displayId: 'master', + type: 'BRANCH', + latestCommit: 'b1041e3f9b071b3d5cacd6826b7549cd624418f1', + latestChangeset: 'b1041e3f9b071b3d5cacd6826b7549cd624418f1', + isDefault: true, + }, + { + id: 'refs/heads/my-feature-branch', + displayId: 'my-feature-branch', + type: 'BRANCH', + latestCommit: 'b1041e3f9b071b3d5cacd6826b7549cd624418f1', + latestChangeset: 'b1041e3f9b071b3d5cacd6826b7549cd624418f1', + isDefault: true, + }, + { + id: 'refs/heads/develop', + displayId: 'develop', + type: 'BRANCH', + latestCommit: '98e21148205367aeb11c25a52eaca3c2945253fa', + latestChangeset: '98e21148205367aeb11c25a52eaca3c2945253fa', + isDefault: false, + }, + { + id: 'refs/heads/development', + displayId: 'development', + type: 'BRANCH', + latestCommit: 'b1041e3f9b071b3d5cacd6826b7549cd624418f1', + latestChangeset: 'b1041e3f9b071b3d5cacd6826b7549cd624418f1', + isDefault: false, + }, + ], + start: 0, + }; + const responseOfPullRequests = { + id: 19, + version: 0, + title: 'Test for bitbucket server pull-requests', + description: 'Test for bitbucket server pull-requests', + state: 'OPEN', + open: true, + closed: false, + createdDate: 1684200289521, + updatedDate: 1684200289521, + fromRef: { + id: 'refs/heads/develop', + displayId: 'develop', + latestCommit: '98e21148205367aeb11c25a52eaca3c2945253fa', + type: 'BRANCH', + repository: { + slug: 'repo', + id: 1812, + name: 'repo', + description: 'This is a test repo', + hierarchyId: '1da8822903a9b11a27b8', + scmId: 'git', + state: 'AVAILABLE', + statusMessage: 'Available', + forkable: true, + project: {}, + public: false, + links: {}, + }, + }, + toRef: { + id: 'refs/heads/master', + displayId: 'master', + latestCommit: 'b1041e3f9b071b3d5cacd6826b7549cd624418f1', + type: 'BRANCH', + repository: { + slug: 'repo', + id: 1812, + name: 'repo', + description: 'This is a test repo', + hierarchyId: '1da8822903a9b11a27b8', + scmId: 'git', + state: 'AVAILABLE', + statusMessage: 'Available', + forkable: true, + project: {}, + public: false, + links: {}, + }, + }, + locked: false, + author: { + user: { + name: 'test-user', + emailAddress: 'test-user@sample.com', + id: 2944, + displayName: 'test-user', + active: true, + slug: 'test-user', + type: 'NORMAL', + links: {}, + }, + role: 'AUTHOR', + approved: false, + status: 'UNAPPROVED', + }, + reviewers: [], + participants: [], + links: { + self: [ + { + href: 'https://hosted.bitbucket.com/projects/project/repos/repo/pull-requests/1', + }, + ], + }, + }; + + const server = setupServer(); + setupRequestMockHandlers(server); + + beforeEach(() => { + jest.resetAllMocks(); + }); + + it(`should ${examples[0].description}`, async () => { + expect.assertions(3); + server.use( + rest.get( + 'https://hosted.bitbucket.com/rest/api/1.0/projects/project/repos/repo/branches', + (req, res, ctx) => { + expect(req.headers.get('Authorization')).toBe('Bearer thing'); + return res( + ctx.status(200), + ctx.set('Content-Type', 'application/json'), + ctx.json(responseOfBranches), + ); + }, + ), + rest.post( + 'https://hosted.bitbucket.com/rest/api/1.0/projects/project/repos/repo/pull-requests', + (req, res, ctx) => { + expect(req.headers.get('Authorization')).toBe('Bearer thing'); + return res( + ctx.status(201), + ctx.set('Content-Type', 'application/json'), + ctx.json(responseOfPullRequests), + ); + }, + ), + ); + + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...yaml.parse(examples[0].example).steps[0].input, + }, + }); + }); + + it(`should ${examples[1].description}`, async () => { + expect.assertions(6); + server.use( + rest.get( + 'https://hosted.bitbucket.com/rest/api/1.0/projects/project/repos/repo/branches', + (req, res, ctx) => { + expect(req.headers.get('Authorization')).toBe('Bearer thing'); + return res( + ctx.status(200), + ctx.set('Content-Type', 'application/json'), + ctx.json(responseOfBranches), + ); + }, + ), + rest.post( + 'https://hosted.bitbucket.com/rest/api/1.0/projects/project/repos/repo/pull-requests', + (req, res, ctx) => { + const requestBody = req.body; + expect(requestBody.title).toBe('My pull request'); + expect(requestBody.fromRef.displayId).toBe('my-feature-branch'); + expect(requestBody.description).toBe( + 'This is a detailed description of my pull request', + ); + expect(req.headers.get('Authorization')).toBe('Bearer thing'); + return res( + ctx.status(201), + ctx.set('Content-Type', 'application/json'), + ctx.json(responseOfPullRequests), + ); + }, + ), + ); + + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...yaml.parse(examples[1].example).steps[0].input, + }, + }); + }); + + it(`should ${examples[2].description}`, async () => { + expect.assertions(6); + server.use( + rest.get( + 'https://hosted.bitbucket.com/rest/api/1.0/projects/project/repos/repo/branches', + (req, res, ctx) => { + expect(req.headers.get('Authorization')).toBe('Bearer thing'); + return res( + ctx.status(200), + ctx.set('Content-Type', 'application/json'), + ctx.json(responseOfBranches), + ); + }, + ), + rest.post( + 'https://hosted.bitbucket.com/rest/api/1.0/projects/project/repos/repo/pull-requests', + (req, res, ctx) => { + const requestBody = req.body; + expect(requestBody.title).toBe('My pull request'); + expect(requestBody.toRef.displayId).toBe('development'); + expect(requestBody.description).toBe( + 'I just made a Pull Request that Add Scaffolder actions for Bitbucket Server', + ); + expect(req.headers.get('Authorization')).toBe('Bearer thing'); + return res( + ctx.status(201), + ctx.set('Content-Type', 'application/json'), + ctx.json(responseOfPullRequests), + ); + }, + ), + ); + + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...yaml.parse(examples[2].example).steps[0].input, + }, + }); + }); + + it(`should ${examples[3].description}`, async () => { + expect.assertions(3); + server.use( + rest.get( + 'https://no-credentials.bitbucket.com/rest/api/1.0/projects/project/repos/repo/branches', + (req, res, ctx) => { + expect(req.headers.get('Authorization')).toBe( + `Bearer ${yaml.parse(examples[3].example).steps[0].input.token}`, + ); + return res( + ctx.status(200), + ctx.set('Content-Type', 'application/json'), + ctx.json(responseOfBranches), + ); + }, + ), + rest.post( + 'https://no-credentials.bitbucket.com/rest/api/1.0/projects/project/repos/repo/pull-requests', + (req, res, ctx) => { + expect(req.headers.get('Authorization')).toBe( + `Bearer ${yaml.parse(examples[3].example).steps[0].input.token}`, + ); + return res( + ctx.status(201), + ctx.set('Content-Type', 'application/json'), + ctx.json(responseOfPullRequests), + ); + }, + ), + ); + + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...yaml.parse(examples[3].example).steps[0].input, + }, + }); + }); + + it(`should ${examples[4].description}`, async () => { + expect.assertions(7); + server.use( + rest.get( + 'https://no-credentials.bitbucket.com/rest/api/1.0/projects/project/repos/repo/branches', + (req, res, ctx) => { + expect(req.headers.get('Authorization')).toBe( + `Bearer ${yaml.parse(examples[4].example).steps[0].input.token}`, + ); + return res( + ctx.status(200), + ctx.set('Content-Type', 'application/json'), + ctx.json(responseOfBranches), + ); + }, + ), + rest.post( + 'https://no-credentials.bitbucket.com/rest/api/1.0/projects/project/repos/repo/pull-requests', + (req, res, ctx) => { + const requestBody = req.body; + expect(requestBody.title).toBe('My pull request'); + expect(requestBody.fromRef.displayId).toBe('my-feature-branch'); + expect(requestBody.toRef.displayId).toBe('development'); + expect(requestBody.description).toBe( + 'This is a detailed description of my pull request', + ); + expect(req.headers.get('Authorization')).toBe( + `Bearer ${yaml.parse(examples[4].example).steps[0].input.token}`, + ); + return res( + ctx.status(201), + ctx.set('Content-Type', 'application/json'), + ctx.json(responseOfPullRequests), + ); + }, + ), + ); + + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...yaml.parse(examples[4].example).steps[0].input, + }, + }); + }); +}); diff --git a/plugins/scaffolder-backend-module-bitbucket-server/src/actions/bitbucketServerPullRequest.examples.ts b/plugins/scaffolder-backend-module-bitbucket-server/src/actions/bitbucketServerPullRequest.examples.ts new file mode 100644 index 0000000000..77287e64e1 --- /dev/null +++ b/plugins/scaffolder-backend-module-bitbucket-server/src/actions/bitbucketServerPullRequest.examples.ts @@ -0,0 +1,118 @@ +/* + * Copyright 2023 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { TemplateExample } from '@backstage/plugin-scaffolder-node'; +import yaml from 'yaml'; + +export const examples: TemplateExample[] = [ + { + description: + 'Creating pull request on bitbucket server with required fields', + example: yaml.stringify({ + steps: [ + { + action: 'publish:bitbucketServer:pull-request', + id: 'publish-bitbucket-server-pull-request-minimal', + name: 'Creating pull request on bitbucket server', + input: { + repoUrl: 'hosted.bitbucket.com?project=project&repo=repo', + title: 'My pull request', + sourceBranch: 'my-feature-branch', + }, + }, + ], + }), + }, + { + description: + 'Creating pull request on bitbucket server with custom descriptions', + example: yaml.stringify({ + steps: [ + { + action: 'publish:bitbucketServer:pull-request', + id: 'publish-bitbucket-server-pull-request-minimal', + name: 'Creating pull request on bitbucket server', + input: { + repoUrl: 'hosted.bitbucket.com?project=project&repo=repo', + title: 'My pull request', + sourceBranch: 'my-feature-branch', + description: 'This is a detailed description of my pull request', + }, + }, + ], + }), + }, + { + description: + 'Creating pull request on bitbucket server with different target branch', + example: yaml.stringify({ + steps: [ + { + action: 'publish:bitbucketServer:pull-request', + id: 'publish-bitbucket-server-pull-request-target-branch', + name: 'Creating pull request on bitbucket server', + input: { + repoUrl: 'hosted.bitbucket.com?project=project&repo=repo', + title: 'My pull request', + sourceBranch: 'my-feature-branch', + targetBranch: 'development', + }, + }, + ], + }), + }, + { + description: + 'Creating pull request on bitbucket server with authorization token', + example: yaml.stringify({ + steps: [ + { + action: 'publish:bitbucketServer:pull-request', + id: 'publish-bitbucket-server-pull-request-minimal', + name: 'Creating pull request on bitbucket server', + input: { + repoUrl: 'no-credentials.bitbucket.com?project=project&repo=repo', + title: 'My pull request', + sourceBranch: 'my-feature-branch', + token: 'my-auth-token', + }, + }, + ], + }), + }, + { + description: 'Creating pull request on bitbucket server with all fields', + example: yaml.stringify({ + steps: [ + { + action: 'publish:bitbucketServer:pull-request', + id: 'publish-bitbucket-server-pull-request-minimal', + name: 'Creating pull request on bitbucket server', + input: { + repoUrl: 'no-credentials.bitbucket.com?project=project&repo=repo', + title: 'My pull request', + sourceBranch: 'my-feature-branch', + targetBranch: 'development', + description: 'This is a detailed description of my pull request', + token: 'my-auth-token', + gitAuthorName: 'test-user', + gitAuthorEmail: 'test-user@sample.com', + }, + }, + ], + }), + }, +]; diff --git a/plugins/scaffolder-backend-module-bitbucket-server/src/actions/bitbucketServerPullRequest.ts b/plugins/scaffolder-backend-module-bitbucket-server/src/actions/bitbucketServerPullRequest.ts index 80412ca413..8fddffaba4 100644 --- a/plugins/scaffolder-backend-module-bitbucket-server/src/actions/bitbucketServerPullRequest.ts +++ b/plugins/scaffolder-backend-module-bitbucket-server/src/actions/bitbucketServerPullRequest.ts @@ -31,6 +31,7 @@ import { import fetch, { RequestInit, Response } from 'node-fetch'; import { Config } from '@backstage/config'; import fs from 'fs-extra'; +import { examples } from './bitbucketServerPullRequest.examples'; const createPullRequest = async (opts: { project: string; @@ -224,6 +225,7 @@ export function createPublishBitbucketServerPullRequestAction(options: { gitAuthorEmail?: string; }>({ id: 'publish:bitbucketServer:pull-request', + examples, schema: { input: { type: 'object',