diff --git a/docs/features/software-templates/writing-tests-for-actions.md b/docs/features/software-templates/writing-tests-for-actions.md new file mode 100644 index 0000000000..7a75250479 --- /dev/null +++ b/docs/features/software-templates/writing-tests-for-actions.md @@ -0,0 +1,57 @@ +--- +id: writing-tests-for-actions +title: Writing Tests For Actions +description: How to write tests for actions +--- + +Once you created a new action, your own custom one, or you would like to contribute new actions, you have to cover it with +Unit tests to be sure that your actions do what they suppose to do. + +Make sure that you cover the most of scenario's, which could happen with the action. +One of indispensable part of the test is to supply the context to a handler of action for the execution. +We encourage you to use a utility method for that, so your tests are immune to structural changes of context. +What is inevitably going to happen during the time. + +Example how to use it: + +```typescript +import { createMockActionContext } from '@backstage/scaffolder-test-utils'; + +const mockContext = createMockActionContext({ + input: { repoUrl: 'dev.azure.com?repo=repo&owner=owner&organization=org' }, +}); + +await action.handler(mockContext); + +expect(mockContext.output).toHaveBeenCalledWith( + 'remoteUrl', + 'https://dev.azure.com/organization/project/_git/repo', +); +``` + +One thing to be aware about: if you would like to call `createMockActionContext` inside `it`, +you have to provide a `workspacePath`. By default, `createMockActionContext` uses +`import { createMockDirectory } from '@backstage/backend-test-utils';` to create it for you. +This implementation contains a hook inside which creates this limitation. So in this case you can do then: + +```typescript +describe('github:autolinks:create', async () => { + const workspacePath = createMockDirectory().resolve('workspace'); + // ... + + it('should call the githubApis for creating alphanumeric autolink reference', async () => { + // ... + await action.handler( + createMockActionContext({ + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + keyPrefix: 'TICKET-', + urlTemplate: 'https://example.com/TICKET?query=', + }, + workspacePath, + }), + ); + //... + }); +}); +``` diff --git a/plugins/scaffolder-backend-module-github/src/actions/github.ts b/plugins/scaffolder-backend-module-github/src/actions/github.ts index f32b65bc9a..3a1beae8e8 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/github.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/github.ts @@ -213,79 +213,97 @@ export function createPublishGithubAction(options: { requiredCommitSigning = false, } = ctx.input; - const octokitOptions = await getOctokitOptions({ - integrations, - credentialsProvider: githubCredentialsProvider, - token: providedToken, - repoUrl: repoUrl, - }); - const client = new Octokit(octokitOptions); + const { _commitHash, _remoteUrl, _repoContentsUrl } = + await ctx.checkpoint?.( + 'repo.create', + async (): Promise<{ + _commitHash: string; + _remoteUrl: string; + _repoContentsUrl: string; + }> => { + const octokitOptions = await getOctokitOptions({ + integrations, + credentialsProvider: githubCredentialsProvider, + token: providedToken, + repoUrl: repoUrl, + }); + const client = new Octokit(octokitOptions); - const { owner, repo } = parseRepoUrl(repoUrl, integrations); + const { owner, repo } = parseRepoUrl(repoUrl, integrations); - if (!owner) { - throw new InputError('Invalid repository owner provided in repoUrl'); - } + if (!owner) { + throw new InputError( + 'Invalid repository owner provided in repoUrl', + ); + } - const newRepo = await createGithubRepoWithCollaboratorsAndTopics( - client, - repo, - owner, - repoVisibility, - description, - homepage, - deleteBranchOnMerge, - allowMergeCommit, - allowSquashMerge, - squashMergeCommitTitle, - squashMergeCommitMessage, - allowRebaseMerge, - allowAutoMerge, - access, - collaborators, - hasProjects, - hasWiki, - hasIssues, - topics, - repoVariables, - secrets, - oidcCustomization, - ctx.logger, - ); + const newRepo = await createGithubRepoWithCollaboratorsAndTopics( + client, + repo, + owner, + repoVisibility, + description, + homepage, + deleteBranchOnMerge, + allowMergeCommit, + allowSquashMerge, + squashMergeCommitTitle, + squashMergeCommitMessage, + allowRebaseMerge, + allowAutoMerge, + access, + collaborators, + hasProjects, + hasWiki, + hasIssues, + topics, + repoVariables, + secrets, + oidcCustomization, + ctx.logger, + ); - const remoteUrl = newRepo.clone_url; - const repoContentsUrl = `${newRepo.html_url}/blob/${defaultBranch}`; + const remoteUrl = newRepo.clone_url; + const repoContentsUrl = `${newRepo.html_url}/blob/${defaultBranch}`; - const commitResult = await initRepoPushAndProtect( - remoteUrl, - octokitOptions.auth, - ctx.workspacePath, - ctx.input.sourcePath, - defaultBranch, - protectDefaultBranch, - protectEnforceAdmins, - owner, - client, - repo, - requireCodeOwnerReviews, - bypassPullRequestAllowances, - requiredApprovingReviewCount, - restrictions, - requiredStatusCheckContexts, - requireBranchesToBeUpToDate, - requiredConversationResolution, - config, - ctx.logger, - gitCommitMessage, - gitAuthorName, - gitAuthorEmail, - dismissStaleReviews, - requiredCommitSigning, - ); + const commitResult = await initRepoPushAndProtect( + remoteUrl, + octokitOptions.auth, + ctx.workspacePath, + ctx.input.sourcePath, + defaultBranch, + protectDefaultBranch, + protectEnforceAdmins, + owner, + client, + repo, + requireCodeOwnerReviews, + bypassPullRequestAllowances, + requiredApprovingReviewCount, + restrictions, + requiredStatusCheckContexts, + requireBranchesToBeUpToDate, + requiredConversationResolution, + config, + ctx.logger, + gitCommitMessage, + gitAuthorName, + gitAuthorEmail, + dismissStaleReviews, + requiredCommitSigning, + ); - ctx.output('commitHash', commitResult?.commitHash); - ctx.output('remoteUrl', remoteUrl); - ctx.output('repoContentsUrl', repoContentsUrl); + return { + _commitHash: commitResult?.commitHash, + _remoteUrl: remoteUrl, + _repoContentsUrl: repoContentsUrl, + }; + }, + )!!; + + ctx.output('commitHash', _commitHash); + ctx.output('remoteUrl', _remoteUrl); + ctx.output('repoContentsUrl', _repoContentsUrl); }, }); }