diff --git a/.changeset/fluffy-ducks-joke.md b/.changeset/fluffy-ducks-joke.md new file mode 100644 index 0000000000..932cbb5298 --- /dev/null +++ b/.changeset/fluffy-ducks-joke.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend-module-gitlab': patch +--- + +Add examples for `publish:gitlab:merge-request` scaffolder action & improve related tests diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabMergeRequest.examples.test.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabMergeRequest.examples.test.ts new file mode 100644 index 0000000000..6f26bc1c68 --- /dev/null +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabMergeRequest.examples.test.ts @@ -0,0 +1,305 @@ +/* + * Copyright 2022 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 { createRootLogger } from '@backstage/backend-common'; +import { ConfigReader } from '@backstage/config'; +import { ScmIntegrations } from '@backstage/integration'; +import { TemplateAction } from '@backstage/plugin-scaffolder-node'; +import { createPublishGitlabMergeRequestAction } from './gitlabMergeRequest'; +import { createMockDirectory } from '@backstage/backend-test-utils'; +import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; +import { examples } from './gitlabMergeRequest.examples'; +import yaml from 'yaml'; + +// Make sure root logger is initialized ahead of FS mock +createRootLogger(); + +const mockGitlabClient = { + Namespaces: { + show: jest.fn(), + }, + Branches: { + create: jest.fn(), + }, + Commits: { + create: jest.fn(), + }, + MergeRequests: { + create: jest.fn(async (_: any) => { + return { + default_branch: 'main', + }; + }), + }, + Projects: { + create: jest.fn(), + show: jest.fn(async (_: any) => { + return { + default_branch: 'main', + }; + }), + }, + Users: { + current: jest.fn(), + username: jest.fn(async (user: string) => { + const users: string[] = ['John Smith', 'my-assignee']; + if (!users.includes(user)) throw new Error('user does not exist'); + else + return [ + { + id: 123, + }, + ]; + }), + }, +}; + +jest.mock('@gitbeaker/node', () => ({ + Gitlab: class { + constructor() { + return mockGitlabClient; + } + }, +})); + +describe('createGitLabMergeRequest', () => { + let instance: TemplateAction; + + const mockDir = createMockDirectory(); + const workspacePath = mockDir.resolve('workspace'); + + beforeEach(() => { + jest.clearAllMocks(); + + mockDir.clear(); + + const config = new ConfigReader({ + integrations: { + gitlab: [ + { + host: 'gitlab.com', + token: 'token', + apiBaseUrl: 'https://api.gitlab.com', + }, + { + host: 'hosted.gitlab.com', + apiBaseUrl: 'https://api.hosted.gitlab.com', + }, + ], + }, + }); + + const integrations = ScmIntegrations.fromConfig(config); + instance = createPublishGitlabMergeRequestAction({ integrations }); + }); + + describe('createGitLabMergeRequestWithAssignee', () => { + it(`Should ${examples[0].description}`, async () => { + mockDir.setContent({ + [workspacePath]: { + source: { 'foo.txt': 'Hello there!' }, + irrelevant: { 'bar.txt': 'Nothing to see here' }, + }, + }); + + const input = yaml.parse(examples[0].example).steps[0].input; + const ctx = createMockActionContext({ input, workspacePath }); + await instance.handler(ctx); + + expect(mockGitlabClient.MergeRequests.create).toHaveBeenCalledWith( + 'owner/repo', + 'new-mr', + 'main', + 'Create my new MR', + { + description: 'This MR is really good', + removeSourceBranch: false, + assigneeId: 123, + }, + ); + }); + + it('assignee is not set when a valid assignee username is not passed in options', async () => { + const input = { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + title: 'Create my new MR', + branchName: 'new-mr', + description: 'This is an important change', + removeSourceBranch: false, + targetPath: 'Subdirectory', + assingnee: 'John Doe', + }; + mockDir.setContent({ + [workspacePath]: { + source: { 'foo.txt': 'Hello there!' }, + irrelevant: { 'bar.txt': 'Nothing to see here' }, + }, + }); + + const ctx = createMockActionContext({ input, workspacePath }); + await instance.handler(ctx); + + expect(mockGitlabClient.MergeRequests.create).toHaveBeenCalledWith( + 'owner/repo', + 'new-mr', + 'main', + 'Create my new MR', + { + description: 'This is an important change', + removeSourceBranch: false, + assigneeId: undefined, + }, + ); + }); + }); + + describe('createGitLabMergeRequestWithRemoveBranch', () => { + it(`Should ${examples[1].description}`, async () => { + const input = yaml.parse(examples[1].example).steps[0].input; + mockDir.setContent({ + [workspacePath]: { + source: { 'foo.txt': 'Hello there!' }, + irrelevant: { 'bar.txt': 'Nothing to see here' }, + }, + }); + + const ctx = createMockActionContext({ input, workspacePath }); + await instance.handler(ctx); + + expect(mockGitlabClient.MergeRequests.create).toHaveBeenCalledWith( + 'owner/repo', + 'new-mr', + 'main', + 'Create my new MR', + { description: 'This MR is really good', removeSourceBranch: true }, + ); + }); + }); + + describe('createGitLabMergeRequestWithSpecifiedTargetBranch', () => { + it(`Should ${examples[2].description}`, async () => { + const input = yaml.parse(examples[2].example).steps[0].input; + mockDir.setContent({ + [workspacePath]: { + source: { 'foo.txt': 'Hello there!' }, + irrelevant: { 'bar.txt': 'Nothing to see here' }, + }, + }); + const ctx = createMockActionContext({ input, workspacePath }); + await instance.handler(ctx); + + expect(mockGitlabClient.Projects.show).not.toHaveBeenCalled(); + expect(mockGitlabClient.Branches.create).toHaveBeenCalledWith( + 'owner/repo', + 'new-mr', + 'test', + ); + expect(mockGitlabClient.MergeRequests.create).toHaveBeenCalledWith( + 'owner/repo', + 'new-mr', + 'test', + 'Create my new MR', + { description: 'This MR is really good', removeSourceBranch: false }, + ); + expect(ctx.output).toHaveBeenCalledWith('targetBranchName', 'test'); + }); + }); + + describe('createGitLabMergeRequestWithCommitAction', () => { + it(`Should ${examples[3].description}`, async () => { + const input = yaml.parse(examples[3].example).steps[0].input; + mockDir.setContent({ + [workspacePath]: { + source: { 'foo.txt': 'Hello there!' }, + irrelevant: { 'bar.txt': 'Nothing to see here' }, + }, + }); + + const ctx = createMockActionContext({ input, workspacePath }); + await instance.handler(ctx); + + expect(mockGitlabClient.Commits.create).toHaveBeenCalledWith( + 'owner/repo', + 'new-mr', + 'Create my new MR', + [ + { + action: 'create', + filePath: 'source/foo.txt', + content: 'SGVsbG8gdGhlcmUh', + encoding: 'base64', + execute_filemode: false, + }, + ], + ); + }); + + it(`Should ${examples[4].description}`, async () => { + const input = yaml.parse(examples[4].example).steps[0].input; + mockDir.setContent({ + [workspacePath]: { + source: { 'foo.txt': 'Hello there!' }, + irrelevant: { 'bar.txt': 'Nothing to see here' }, + }, + }); + + const ctx = createMockActionContext({ input, workspacePath }); + await instance.handler(ctx); + + expect(mockGitlabClient.Commits.create).toHaveBeenCalledWith( + 'owner/repo', + 'new-mr', + 'Create my new MR', + [ + { + action: 'delete', + filePath: 'source/foo.txt', + content: 'SGVsbG8gdGhlcmUh', + encoding: 'base64', + execute_filemode: false, + }, + ], + ); + }); + + it(`Should ${examples[5].description}`, async () => { + const input = yaml.parse(examples[5].example).steps[0].input; + mockDir.setContent({ + [workspacePath]: { + source: { 'foo.txt': 'Hello there!' }, + irrelevant: { 'bar.txt': 'Nothing to see here' }, + }, + }); + + const ctx = createMockActionContext({ input, workspacePath }); + await instance.handler(ctx); + + expect(mockGitlabClient.Commits.create).toHaveBeenCalledWith( + 'owner/repo', + 'new-mr', + 'Create my new MR', + [ + { + action: 'update', + filePath: 'source/foo.txt', + content: 'SGVsbG8gdGhlcmUh', + encoding: 'base64', + execute_filemode: false, + }, + ], + ); + }); + }); +}); diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabMergeRequest.examples.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabMergeRequest.examples.ts new file mode 100644 index 0000000000..8bed461d25 --- /dev/null +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabMergeRequest.examples.ts @@ -0,0 +1,142 @@ +/* + * 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: 'Create a merge request with a specific assignee', + example: yaml.stringify({ + steps: [ + { + id: 'createMergeRequest', + action: 'publish:gitlab:merge-request', + name: 'Create a Merge Request', + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + title: 'Create my new MR', + description: 'This MR is really good', + sourcePath: './path/to/my/changes', + branchName: 'new-mr', + assignee: 'my-assignee', + }, + }, + ], + }), + }, + { + description: + 'Create a merge request with removal of source branch after merge', + example: yaml.stringify({ + steps: [ + { + id: 'createMergeRequest', + action: 'publish:gitlab:merge-request', + name: 'Create a Merge Request', + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + title: 'Create my new MR', + description: 'This MR is really good', + sourcePath: './path/to/my/changes', + branchName: 'new-mr', + removeSourceBranch: true, + }, + }, + ], + }), + }, + { + description: 'Create a merge request with a target branch', + example: yaml.stringify({ + steps: [ + { + id: 'createMergeRequest', + action: 'publish:gitlab:merge-request', + name: 'Create a Merge Request', + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + title: 'Create my new MR', + description: 'This MR is really good', + sourcePath: './path/to/my/changes', + branchName: 'new-mr', + targetBranchName: 'test', + targetPath: 'Subdirectory', + }, + }, + ], + }), + }, + { + description: 'Create a merge request with a commit action as create', + example: yaml.stringify({ + steps: [ + { + id: 'createMergeRequest', + action: 'publish:gitlab:merge-request', + name: 'Create a Merge Request', + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + title: 'Create my new MR', + branchName: 'new-mr', + description: 'MR description', + commitAction: 'create', + targetPath: 'source', + }, + }, + ], + }), + }, + { + description: 'Create a merge request with a commit action as delete', + example: yaml.stringify({ + steps: [ + { + id: 'createMergeRequest', + action: 'publish:gitlab:merge-request', + name: 'Create a Merge Request', + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + title: 'Create my new MR', + branchName: 'new-mr', + description: 'MR description', + commitAction: 'delete', + targetPath: 'source', + }, + }, + ], + }), + }, + { + description: 'Create a merge request with a commit action as update', + example: yaml.stringify({ + steps: [ + { + id: 'createMergeRequest', + action: 'publish:gitlab:merge-request', + name: 'Create a Merge Request', + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + title: 'Create my new MR', + branchName: 'new-mr', + description: 'MR description', + commitAction: 'update', + targetPath: 'source', + }, + }, + ], + }), + }, +]; diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabMergeRequest.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabMergeRequest.ts index 86f69ee357..c9b8ce68a9 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabMergeRequest.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabMergeRequest.ts @@ -25,6 +25,7 @@ import { ScmIntegrationRegistry } from '@backstage/integration'; import { InputError } from '@backstage/errors'; import { resolveSafeChildPath } from '@backstage/backend-common'; import { createGitlabApi } from './helpers'; +import { examples } from './gitlabMergeRequest.examples'; /** * Create a new action that creates a gitlab merge request. @@ -52,6 +53,7 @@ export const createPublishGitlabMergeRequestAction = (options: { assignee?: string; }>({ id: 'publish:gitlab:merge-request', + examples, schema: { input: { required: ['repoUrl', 'branchName'],