import { CodeCommitClient, CreatePullRequestApprovalRuleCommand, CreatePullRequestCommand, type CreatePullRequestOutput, DeleteCommentContentCommand, GetCommentsForPullRequestCommand, GetFileCommand, GetPullRequestCommand, type GetPullRequestOutput, GetRepositoryCommand, ListPullRequestsCommand, ListRepositoriesCommand, PostCommentForPullRequestCommand, PullRequestStatusEnum, UpdatePullRequestDescriptionCommand, UpdatePullRequestStatusCommand, UpdatePullRequestTitleCommand, } from '@aws-sdk/client-codecommit'; import { mockClient } from 'aws-sdk-client-mock'; import * as aws4 from 'aws4'; import { git, logger } from '~test/util.ts'; import { PLATFORM_BAD_CREDENTIALS, REPOSITORY_EMPTY, REPOSITORY_NOT_FOUND, } from '../../../constants/error-messages.ts'; import type { Platform } from '../types.ts'; import { getCodeCommitUrl } from './codecommit-client.ts'; import * as codeCommit from './index.ts'; const codeCommitClient = mockClient(CodeCommitClient); async function reInitRepo(codeCommit: Platform): Promise { codeCommitClient.on(GetRepositoryCommand).resolvesOnce({ repositoryMetadata: { defaultBranch: 'main', repositoryId: 'id', }, }); vi.stubEnv('AWS_ACCESS_KEY_ID', 'something'); vi.stubEnv('AWS_SECRET_ACCESS_KEY', 'something'); await codeCommit.initRepo({ repository: 'repositoryName' }); } describe('modules/platform/codecommit/index', () => { beforeAll(async () => { await codeCommit.initPlatform({ endpoint: 'https://git-codecommit.eu-central-1.amazonaws.com/', username: 'accessKeyId', password: 'SecretAccessKey', token: 'token', }); }); beforeEach(() => { vi.stubEnv('AWS_REGION', undefined); vi.stubEnv('AWS_ACCESS_KEY_ID', undefined); vi.stubEnv('AWS_SECRET_ACCESS_KEY', undefined); codeCommitClient.reset(); vi.useRealTimers(); }); describe('massageMarkdown', () => { it('validates massageMarkdown functionality', () => { const newStr = codeCommit.massageMarkdown( '
foobar
text
\n', ); expect(newStr).toBe( '**foo**bartext\n[//]: # ()', ); }); it('replaces pr links', () => { const body = '[#123](../pull/123) [#124](../pull/124) [#125](../pull/125)'; expect(codeCommit.massageMarkdown(body)).toBe( '[#123](../../pull-requests/123) [#124](../../pull-requests/124) [#125](../../pull-requests/125)', ); }); it('replaces issue links', () => { const body = '[#123](../issues/123) [#124](../issues/124) [#125](../issues/125)'; expect(codeCommit.massageMarkdown(body)).toBe( '[#123](#123) [#124](#124) [#125](#125)', ); }); }); it('maxBodyLength', () => { expect(codeCommit.maxBodyLength()).toBe(Infinity); }); describe('initPlatform()', () => { it('should init', async () => { await expect( codeCommit.initPlatform({ endpoint: 'https://git-codecommit.REGION.amazonaws.com/', username: 'abc', password: '123', }), ).resolves.toEqual({ endpoint: 'https://git-codecommit.REGION.amazonaws.com/', }); }); it('should init with env vars', async () => { vi.stubEnv('AWS_REGION', 'REGION'); await expect( codeCommit.initPlatform({ username: 'abc', password: '123', }), ).resolves.toEqual({ endpoint: 'https://git-codecommit.REGION.amazonaws.com/', }); }); it('should', async () => { await expect( codeCommit.initPlatform({ endpoint: 'non://parsable.url' }), ).resolves.toEqual({ endpoint: 'non://parsable.url', }); }); it('should as well', async () => { await expect(codeCommit.initPlatform({})).resolves.toEqual({ endpoint: 'https://git-codecommit.us-east-1.amazonaws.com/', }); }); }); describe('initRepos()', () => { it('fails to git.initRepo', async () => { git.initRepo.mockImplementationOnce(() => { throw new Error('any error'); }); codeCommitClient.on(GetRepositoryCommand).resolvesOnce({ repositoryMetadata: { defaultBranch: 'main', repositoryId: 'id', }, }); await expect( codeCommit.initRepo({ repository: 'repositoryName' }), ).rejects.toThrow(new Error(PLATFORM_BAD_CREDENTIALS)); }); it('fails on getRepositoryInfo', async () => { codeCommitClient .on(GetRepositoryCommand) .rejectsOnce(new Error('Could not find repository')); await expect( codeCommit.initRepo({ repository: 'repositoryName' }), ).rejects.toThrow(new Error(REPOSITORY_NOT_FOUND)); }); it('getRepositoryInfo returns bad results', async () => { git.initRepo.mockReturnValueOnce(Promise.resolve()); codeCommitClient.on(GetRepositoryCommand).resolvesOnce({}); await expect( codeCommit.initRepo({ repository: 'repositoryName' }), ).rejects.toThrow(new Error(REPOSITORY_NOT_FOUND)); }); it('getRepositoryInfo returns bad results 2', async () => { codeCommitClient.on(GetRepositoryCommand).resolvesOnce({ repositoryMetadata: { repositoryId: 'id', }, }); await expect( codeCommit.initRepo({ repository: 'repositoryName' }), ).rejects.toThrow(new Error(REPOSITORY_EMPTY)); }); it('initiates repo successfully', async () => { codeCommitClient.on(GetRepositoryCommand).resolvesOnce({ repositoryMetadata: { defaultBranch: 'main', repositoryId: 'id', }, }); vi.stubEnv('AWS_ACCESS_KEY_ID', 'something'); vi.stubEnv('AWS_SECRET_ACCESS_KEY', 'something'); await expect( codeCommit.initRepo({ repository: 'repositoryName' }), ).resolves.toEqual({ repoFingerprint: '1b375a58e9961e5c2c8cdc497c5975beb287d538b4692918a3306b92db181cd125c5bd0b1bf8e399cf31cd5f1d24b6dad5a80bdf061f6003cf1c33790525f9c6', defaultBranch: 'main', isFork: false, }); }); it('gets the right url', () => { vi.stubEnv('AWS_ACCESS_KEY_ID', ''); vi.stubEnv('AWS_SECRET_ACCESS_KEY', ''); expect( getCodeCommitUrl( { defaultBranch: 'main', repositoryId: 'id', cloneUrlHttp: 'https://git-codecommit.us-east-1.amazonaws.com/v1/repos/name', }, 'name', ), ).toBe('https://git-codecommit.us-east-1.amazonaws.com/v1/repos/name'); }); it('gets the eu-central-1 url', () => { vi.stubEnv('AWS_ACCESS_KEY_ID', ''); vi.stubEnv('AWS_SECRET_ACCESS_KEY', ''); vi.stubEnv('AWS_REGION', 'eu-central-1'); expect( getCodeCommitUrl( { defaultBranch: 'main', repositoryId: 'id', }, 'name', ), ).toBe('https://git-codecommit.eu-central-1.amazonaws.com/v1/repos/name'); }); it('gets url with username and token', () => { vi.useFakeTimers().setSystemTime(new Date('2020-01-01')); vi.stubEnv('AWS_ACCESS_KEY_ID', 'access-key-id'); vi.stubEnv('AWS_SECRET_ACCESS_KEY', 'secret-access-key'); vi.stubEnv('AWS_REGION', 'eu-central-1'); vi.stubEnv('AWS_SESSION_TOKEN', ''); const signer = new aws4.RequestSigner({ service: 'codecommit', host: 'git-codecommit.eu-central-1.amazonaws.com', method: 'GIT', path: 'v1/repos/name', }); const dateTime = signer.getDateTime(); const token = `${dateTime}Z${signer.signature()}`; expect( getCodeCommitUrl( { defaultBranch: 'main', repositoryId: 'id', }, 'name', ), ).toBe( `https://access-key-id:${token}@git-codecommit.eu-central-1.amazonaws.com/v1/repos/name`, ); }); }); describe('getRepos()', () => { it('returns repos', async () => { const result = { repositories: [ { repositoryId: 'id', repositoryName: 'repoName', }, ], }; codeCommitClient.on(ListRepositoriesCommand).resolvesOnce(result); const res = await codeCommit.getRepos(); expect(res).toEqual(['repoName']); }); it('returns empty if error', async () => { codeCommitClient .on(ListRepositoriesCommand) .rejectsOnce(new Error('something')); const res = await codeCommit.getRepos(); expect(res).toEqual([]); }); }); describe('getPrList()', () => { beforeAll(async () => { // reset state for tests await reInitRepo(codeCommit); }); it('gets PR list by author', async () => { codeCommitClient .on(ListPullRequestsCommand) .resolvesOnce({ pullRequestIds: ['1', '2'] }); const prRes: GetPullRequestOutput = { pullRequest: { title: 'someTitle', pullRequestStatus: 'OPEN', pullRequestTargets: [ { sourceReference: 'refs/heads/sourceBranch', destinationReference: 'refs/heads/targetBranch', }, ], }, }; codeCommitClient.on(GetPullRequestCommand).resolvesOnce({}); codeCommitClient.on(GetPullRequestCommand).resolvesOnce(prRes); const res = await codeCommit.getPrList(); expect(res).toMatchObject([ { sourceBranch: 'refs/heads/sourceBranch', targetBranch: 'refs/heads/targetBranch', state: 'open', number: 1, title: 'someTitle', }, ]); codeCommitClient .on(GetPullRequestCommand) .rejectsOnce(new Error('failed connection')); // test cache const res2 = await codeCommit.getPrList(); expect(res2).toMatchObject([ { sourceBranch: 'refs/heads/sourceBranch', targetBranch: 'refs/heads/targetBranch', state: 'open', number: 1, title: 'someTitle', }, ]); }); it('checks if nullcheck works for list prs', async () => { // This test expects a clean cache as we want to check the behaviour when an AWS call is made await reInitRepo(codeCommit); codeCommitClient.on(ListPullRequestsCommand).resolvesOnce({}); const res = await codeCommit.getPrList(); expect(res).toEqual([]); }); }); describe('findPr()', () => { beforeEach(async () => { // reset state for tests await reInitRepo(codeCommit); }); it('throws error on findPr', async () => { const err = new Error('failed'); codeCommitClient.on(ListPullRequestsCommand).rejectsOnce(err); const res = await codeCommit.findPr({ branchName: 'sourceBranch', prTitle: 'someTitle', state: 'open', }); expect(res).toBeNull(); expect(logger.logger.error).toHaveBeenCalledWith({ err }, 'findPr error'); }); it('finds pr', async () => { codeCommitClient .on(ListPullRequestsCommand) .resolvesOnce({ pullRequestIds: ['1'] }); const prRes: GetPullRequestOutput = { pullRequest: { title: 'someTitle', pullRequestStatus: 'OPEN', pullRequestTargets: [ { sourceReference: 'refs/heads/sourceBranch', destinationReference: 'refs/heads/targetBranch', }, ], }, }; codeCommitClient.on(GetPullRequestCommand).resolvesOnce(prRes); const res = await codeCommit.findPr({ branchName: 'sourceBranch', prTitle: 'someTitle', state: 'open', }); expect(res).toMatchObject({ sourceBranch: 'refs/heads/sourceBranch', targetBranch: 'refs/heads/targetBranch', state: 'open', number: 1, title: 'someTitle', }); }); it('finds any pr with that title in regardless of state', async () => { codeCommitClient .on(ListPullRequestsCommand) .resolvesOnce({ pullRequestIds: ['1'] }); const prRes: GetPullRequestOutput = { pullRequest: { title: 'someTitle', pullRequestStatus: 'OPEN', pullRequestTargets: [ { sourceReference: 'refs/heads/sourceBranch', destinationReference: 'refs/heads/targetBranch', }, ], }, }; codeCommitClient.on(GetPullRequestCommand).resolvesOnce(prRes); const res = await codeCommit.findPr({ branchName: 'sourceBranch', prTitle: 'someTitle', state: 'all', }); expect(res).toMatchObject({ sourceBranch: 'refs/heads/sourceBranch', targetBranch: 'refs/heads/targetBranch', state: 'open', number: 1, title: 'someTitle', }); }); it('finds closed/merged pr', async () => { codeCommitClient .on(ListPullRequestsCommand) .resolvesOnce({ pullRequestIds: ['1'] }); const prRes: GetPullRequestOutput = { pullRequest: { title: 'someTitle', pullRequestStatus: 'CLOSED', pullRequestTargets: [ { sourceReference: 'refs/heads/sourceBranch', destinationReference: 'refs/heads/targetBranch', }, ], }, }; codeCommitClient.on(GetPullRequestCommand).resolvesOnce(prRes); const res = await codeCommit.findPr({ branchName: 'sourceBranch', prTitle: 'someTitle', state: '!open', }); expect(res).toMatchObject({ sourceBranch: 'refs/heads/sourceBranch', targetBranch: 'refs/heads/targetBranch', state: 'closed', number: 1, title: 'someTitle', }); }); it('finds any pr', async () => { codeCommitClient .on(ListPullRequestsCommand) .resolvesOnce({ pullRequestIds: ['1'] }); const prRes: GetPullRequestOutput = { pullRequest: { title: 'someTitle', pullRequestStatus: 'CLOSED', pullRequestTargets: [ { sourceReference: 'refs/heads/sourceBranch', destinationReference: 'refs/heads/targetBranch', }, ], }, }; codeCommitClient.on(GetPullRequestCommand).resolvesOnce(prRes); const res = await codeCommit.findPr({ branchName: 'sourceBranch', prTitle: 'someTitle', }); expect(res).toMatchObject({ sourceBranch: 'refs/heads/sourceBranch', targetBranch: 'refs/heads/targetBranch', state: 'closed', number: 1, title: 'someTitle', }); }); it('returns empty list in case prs dont exist yet', async () => { const res = await codeCommit.findPr({ branchName: 'sourceBranch', prTitle: 'someTitle', state: 'open', }); expect(res).toBeNull(); }); }); describe('getBranchPr()', () => { it('codecommit find PR for branch', async () => { codeCommitClient .on(ListPullRequestsCommand) .resolvesOnce({ pullRequestIds: ['1'] }); const prRes: GetPullRequestOutput = { pullRequest: { title: 'someTitle', pullRequestStatus: 'OPEN', pullRequestTargets: [ { sourceReference: 'refs/heads/sourceBranch', destinationReference: 'refs/heads/targetBranch', }, ], }, }; codeCommitClient.on(GetPullRequestCommand).resolves(prRes); const res = await codeCommit.getBranchPr('sourceBranch'); expect(res).toMatchObject({ sourceBranch: 'refs/heads/sourceBranch', targetBranch: 'refs/heads/targetBranch', state: 'open', number: 1, title: 'someTitle', }); }); it('returns null if no PR for branch', async () => { codeCommitClient .on(ListPullRequestsCommand) .resolvesOnce({ pullRequestIds: ['1'] }); const prRes: GetPullRequestOutput = { pullRequest: { title: 'someTitle', pullRequestStatus: 'OPEN', pullRequestTargets: [ { sourceReference: 'refs/heads/sourceBranch', destinationReference: 'refs/heads/targetBranch', }, ], }, }; codeCommitClient.on(GetPullRequestCommand).resolvesOnce(prRes); const res = await codeCommit.getBranchPr('branch_without_pr'); expect(res).toBeNull(); }); }); describe('getPr()', () => { it('gets pr', async () => { const prRes: GetPullRequestOutput = { pullRequest: { title: 'someTitle', description: 'body', pullRequestStatus: 'OPEN', pullRequestTargets: [ { sourceReference: 'refs/heads/sourceBranch', destinationReference: 'refs/heads/targetBranch', }, ], }, }; codeCommitClient.on(GetPullRequestCommand).resolvesOnce(prRes); const res = await codeCommit.getPr(1); expect(res).toMatchObject({ sourceBranch: 'refs/heads/sourceBranch', targetBranch: 'refs/heads/targetBranch', state: 'open', number: 1, title: 'someTitle', }); }); it('gets closed pr', async () => { const prRes: GetPullRequestOutput = { pullRequest: { title: 'someTitle', pullRequestStatus: 'CLOSED', pullRequestTargets: [ { sourceReference: 'refs/heads/sourceBranch', destinationReference: 'refs/heads/targetBranch', }, ], }, }; codeCommitClient.on(GetPullRequestCommand).resolvesOnce(prRes); const res = await codeCommit.getPr(1); expect(res).toMatchObject({ sourceBranch: 'refs/heads/sourceBranch', targetBranch: 'refs/heads/targetBranch', state: 'closed', number: 1, title: 'someTitle', }); }); it('gets merged pr', async () => { const prRes: GetPullRequestOutput = { pullRequest: { title: 'someTitle', pullRequestStatus: 'OPEN', pullRequestTargets: [ { sourceReference: 'refs/heads/sourceBranch', destinationReference: 'refs/heads/targetBranch', mergeMetadata: { isMerged: true, }, }, ], }, }; codeCommitClient.on(GetPullRequestCommand).resolvesOnce(prRes); const res = await codeCommit.getPr(1); expect(res).toMatchObject({ sourceBranch: 'refs/heads/sourceBranch', targetBranch: 'refs/heads/targetBranch', state: 'merged', number: 1, title: 'someTitle', }); }); it('returns null in case input is null', async () => { codeCommitClient .on(GetPullRequestCommand) .rejectsOnce(new Error('bad creds')); const res = await codeCommit.getPr(1); expect(res).toBeNull(); }); }); describe('getJsonFile()', () => { it('returns file content', async () => { const data = { foo: 'bar' }; const uint8arrData = new Uint8Array(Buffer.from(JSON.stringify(data))); codeCommitClient .on(GetFileCommand) .resolvesOnce({ fileContent: uint8arrData }); const res = await codeCommit.getJsonFile('file.json'); expect(res).toEqual(data); }); it('returns file content in json5 format', async () => { const json5Data = ` { // json5 comment foo: 'bar' } `; const uint8arrData = new Uint8Array(Buffer.from(json5Data)); codeCommitClient .on(GetFileCommand) .resolvesOnce({ fileContent: uint8arrData }); const res = await codeCommit.getJsonFile('file.json'); expect(res).toEqual({ foo: 'bar' }); }); it('returns null', async () => { codeCommitClient .on(GetFileCommand) .resolvesOnce({ fileContent: undefined }); const res = await codeCommit.getJsonFile('file.json'); expect(res).toBeNull(); }); }); describe('getRawFile()', () => { it('returns file content', async () => { const data = { foo: 'bar' }; const uint8arrData = new Uint8Array(Buffer.from(JSON.stringify(data))); codeCommitClient .on(GetFileCommand) .resolvesOnce({ fileContent: uint8arrData }); const res = await codeCommit.getRawFile('file.json'); expect(res).toBe('{"foo":"bar"}'); }); it('returns null', async () => { codeCommitClient .on(GetFileCommand) .resolvesOnce({ fileContent: undefined }); const res = await codeCommit.getRawFile('file.json'); expect(res).toBeNull(); }); it('returns file content in json5 format', async () => { const json5Data = ` { // json5 comment foo: 'bar' } `; const uint8arrData = new Uint8Array(Buffer.from(json5Data)); codeCommitClient .on(GetFileCommand) .resolvesOnce({ fileContent: uint8arrData }); const res = await codeCommit.getRawFile('file.json'); expect(res).toBe(` { // json5 comment foo: 'bar' } `); }); }); describe('createPr()', () => { beforeAll(async () => { // reset state for tests await reInitRepo(codeCommit); }); it('posts PR', async () => { const prRes: CreatePullRequestOutput = { pullRequest: { pullRequestId: '1', pullRequestStatus: 'OPEN', title: 'someTitle', description: 'mybody', pullRequestTargets: [ { sourceCommit: '123', destinationCommit: '321', }, ], }, }; codeCommitClient.on(CreatePullRequestCommand).resolvesOnce(prRes); const pr = await codeCommit.createPr({ sourceBranch: 'sourceBranch', targetBranch: 'targetBranch', prTitle: 'mytitle', prBody: 'mybody', }); expect(pr).toMatchObject({ number: 1, state: 'open', title: 'someTitle', sourceBranch: 'sourceBranch', targetBranch: 'targetBranch', sourceRepo: 'repositoryName', body: 'mybody', }); }); it('doesnt return a title', async () => { const prRes: CreatePullRequestOutput = { pullRequest: { pullRequestId: '1', pullRequestStatus: 'OPEN', }, }; codeCommitClient.on(CreatePullRequestCommand).resolvesOnce(prRes); await expect( codeCommit.createPr({ sourceBranch: 'sourceBranch', targetBranch: 'targetBranch', prTitle: 'mytitle', prBody: 'mybody', }), ).rejects.toThrow(new Error('Could not create pr, missing PR info')); }); }); describe('updatePr()', () => { it('updates PR', async () => { codeCommitClient.on(UpdatePullRequestDescriptionCommand).resolvesOnce({}); codeCommitClient.on(UpdatePullRequestTitleCommand).resolvesOnce({}); codeCommitClient.on(UpdatePullRequestStatusCommand).resolvesOnce({}); await expect( codeCommit.updatePr({ number: 1, prTitle: 'title', prBody: 'body', state: 'open', }), ).toResolve(); }); it('updates PR body if cache is not the same', async () => { // Start with a clean cache await reInitRepo(codeCommit); codeCommitClient .on(ListPullRequestsCommand) .resolvesOnce({ pullRequestIds: ['1'] }); codeCommitClient.on(GetPullRequestCommand).resolvesOnce({ pullRequest: { pullRequestTargets: [ { destinationReference: 'targetBranch', sourceReference: 'sourceBranch', destinationCommit: '321', sourceCommit: '123', }, ], pullRequestStatus: PullRequestStatusEnum.OPEN, title: 'someTitle', description: 'some old description', }, }); const prList = await codeCommit.getPrList(); expect(prList).toHaveLength(1); codeCommitClient.on(UpdatePullRequestDescriptionCommand).resolvesOnce({}); codeCommitClient.on(UpdatePullRequestTitleCommand).resolvesOnce({}); codeCommitClient.on(UpdatePullRequestStatusCommand).resolvesOnce({}); await expect( codeCommit.updatePr({ number: 1, prTitle: 'title', prBody: 'new description', state: 'open', }), ).toResolve(); }); it('updates PR body does not update if cache is the same', async () => { // Start with a clean cache await reInitRepo(codeCommit); codeCommitClient .on(ListPullRequestsCommand) .resolvesOnce({ pullRequestIds: ['1'] }); codeCommitClient.on(GetPullRequestCommand).resolvesOnce({ pullRequest: { pullRequestTargets: [ { destinationReference: 'targetBranch', sourceReference: 'sourceBranch', destinationCommit: '321', sourceCommit: '123', }, ], pullRequestStatus: PullRequestStatusEnum.OPEN, title: 'someTitle', description: 'news description', }, }); const prList = await codeCommit.getPrList(); expect(prList).toHaveLength(1); codeCommitClient.on(UpdatePullRequestTitleCommand).resolvesOnce({}); codeCommitClient.on(UpdatePullRequestStatusCommand).resolvesOnce({}); await expect( codeCommit.updatePr({ number: 1, prTitle: 'title', prBody: 'new description', state: 'open', }), ).toResolve(); }); it('updates PR regardless of status failure', async () => { codeCommitClient.on(UpdatePullRequestDescriptionCommand).resolvesOnce({}); codeCommitClient.on(UpdatePullRequestTitleCommand).resolvesOnce({}); codeCommitClient .on(UpdatePullRequestStatusCommand) .rejectsOnce(new Error('update status failure')); await expect( codeCommit.updatePr({ number: 1, prTitle: 'title', prBody: 'body', state: 'open', }), ).toResolve(); }); it('updates PR with status closed', async () => { codeCommitClient.on(UpdatePullRequestDescriptionCommand).resolvesOnce({}); codeCommitClient.on(UpdatePullRequestTitleCommand).resolvesOnce({}); codeCommitClient.on(UpdatePullRequestStatusCommand).resolvesOnce({}); await expect( codeCommit.updatePr({ number: 1, prTitle: 'title', prBody: 'body', state: 'closed', }), ).toResolve(); }); }); describe('ensureComment', () => { beforeEach(async () => { // reset state for tests await reInitRepo(codeCommit); }); it('adds comment if missing', async () => { const commentsRes = { commentsForPullRequestData: [ { pullRequestId: '42', repositoryName: 'someRepo', beforeCommitId: 'beforeCommitId', afterCommitId: 'afterCommitId', comments: [ { commentId: '1', content: 'my comment content', }, ], }, ], }; codeCommitClient .on(GetCommentsForPullRequestCommand) .resolvesOnce(commentsRes); codeCommitClient .on(ListPullRequestsCommand) .resolvesOnce({ pullRequestIds: ['42'] }); const prRes: GetPullRequestOutput = { pullRequest: { pullRequestId: '42', title: 'someTitle', pullRequestStatus: 'OPEN', pullRequestTargets: [ { sourceReference: 'refs/heads/sourceBranch', destinationReference: 'refs/heads/targetBranch', sourceCommit: '123', destinationCommit: '321', }, ], }, }; codeCommitClient.on(GetPullRequestCommand).resolvesOnce(prRes); codeCommitClient.on(PostCommentForPullRequestCommand).resolvesOnce({}); const res = await codeCommit.ensureComment({ number: 42, topic: 'some-subject', content: 'some\ncontent', }); expect(res).toBeTrue(); expect(logger.logger.info).toHaveBeenCalledWith( { repository: 'repositoryName', prNo: 42, topic: 'some-subject' }, 'Comment added', ); }); it('updates comment if different content', async () => { const commentsRes = { commentsForPullRequestData: [ { pullRequestId: '42', repositoryName: 'someRepo', beforeCommitId: 'beforeCommitId', afterCommitId: 'afterCommitId', comments: [ { commentId: '1', content: '### some-subject\n\n - my comment content', }, ], }, ], }; codeCommitClient .on(GetCommentsForPullRequestCommand) .resolvesOnce(commentsRes); codeCommitClient.on(PostCommentForPullRequestCommand).resolvesOnce({}); const res = await codeCommit.ensureComment({ number: 42, topic: 'some-subject', content: 'some\ncontent', }); expect(res).toBeTrue(); expect(logger.logger.debug).toHaveBeenCalledWith( { repository: 'repositoryName', prNo: 42, topic: 'some-subject' }, 'Comment updated', ); }); it('does nothing if comment exists and is the same', async () => { const commentsRes = { commentsForPullRequestData: [ { pullRequestId: '42', repositoryName: 'someRepo', beforeCommitId: 'beforeCommitId', afterCommitId: 'afterCommitId', comments: [ { commentId: '1', content: '### some-subject\n\nmy comment content', }, ], }, ], }; codeCommitClient .on(GetCommentsForPullRequestCommand) .resolvesOnce(commentsRes); const res = await codeCommit.ensureComment({ number: 42, topic: 'some-subject', content: 'my comment content', }); expect(res).toBeTrue(); expect(logger.logger.debug).toHaveBeenCalledWith( { repository: 'repositoryName', prNo: 42, topic: 'some-subject' }, 'Comment is already up-to-date', ); }); it('does nothing if comment exists and is the same when there is no topic', async () => { const commentsRes = { commentsForPullRequestData: [ { pullRequestId: '42', repositoryName: 'someRepo', beforeCommitId: 'beforeCommitId', afterCommitId: 'afterCommitId', comments: [ { commentId: '1', content: 'my comment content', }, ], }, ], }; codeCommitClient .on(GetCommentsForPullRequestCommand) .resolvesOnce(commentsRes); const res = await codeCommit.ensureComment({ number: 42, topic: null, content: 'my comment content', }); expect(res).toBeTrue(); expect(logger.logger.debug).toHaveBeenCalledWith( { repository: 'repositoryName', prNo: 42, topic: null }, 'Comment is already up-to-date', ); }); it('throws an exception in case of api failed connection', async () => { const err = new Error('some error'); codeCommitClient.on(GetCommentsForPullRequestCommand).rejectsOnce(err); const res = await codeCommit.ensureComment({ number: 42, topic: null, content: 'my comment content', }); expect(res).toBeFalse(); expect(logger.logger.debug).toHaveBeenCalledWith( { err }, 'Unable to retrieve pr comments', ); }); it('fails at null check for response', async () => { codeCommitClient.on(GetCommentsForPullRequestCommand).resolvesOnce({}); const res = await codeCommit.ensureComment({ number: 42, topic: null, content: 'my comment content', }); expect(res).toBeFalse(); }); it('doesnt find comments obj and source or destination commit', async () => { const commentsRes = { commentsForPullRequestData: [ { pullRequestId: '42', repositoryName: 'someRepo', beforeCommitId: 'beforeCommitId', afterCommitId: 'afterCommitId', }, ], }; codeCommitClient .on(GetCommentsForPullRequestCommand) .resolvesOnce(commentsRes); codeCommitClient .on(ListPullRequestsCommand) .resolvesOnce({ pullRequestIds: ['42'] }); const prRes: GetPullRequestOutput = { pullRequest: { pullRequestId: '42', title: 'someTitle', pullRequestStatus: 'OPEN', pullRequestTargets: [ { sourceReference: 'refs/heads/sourceBranch', destinationReference: 'refs/heads/targetBranch', }, ], }, }; codeCommitClient.on(GetPullRequestCommand).resolvesOnce(prRes); const res = await codeCommit.ensureComment({ number: 42, topic: null, content: 'my comment content', }); expect(res).toBeFalse(); }); }); describe('ensureCommentRemoval', () => { it('deletes comment by topic if found', async () => { const commentsRes = { commentsForPullRequestData: [ { pullRequestId: '42', repositoryName: 'someRepo', beforeCommitId: 'beforeCommitId', afterCommitId: 'afterCommitId', comments: [ { commentId: '1', content: '### some-subject\n\nmy comment content', }, ], }, ], }; codeCommitClient .on(GetCommentsForPullRequestCommand) .resolvesOnce(commentsRes); codeCommitClient.on(DeleteCommentContentCommand).resolvesOnce({}); await codeCommit.ensureCommentRemoval({ type: 'by-topic', number: 42, topic: 'some-subject', }); expect(logger.logger.debug).toHaveBeenCalledWith( 'comment "some-subject" in PR #42 was removed', ); }); it('doesnt find commentsForPullRequestData', async () => { codeCommitClient.on(GetCommentsForPullRequestCommand).resolvesOnce({}); codeCommitClient.on(DeleteCommentContentCommand).resolvesOnce({}); await codeCommit.ensureCommentRemoval({ type: 'by-topic', number: 42, topic: 'some-subject', }); expect(logger.logger.debug).toHaveBeenCalledWith( 'commentsForPullRequestData not found', ); }); it('doesnt find comment obj', async () => { const commentsRes = { commentsForPullRequestData: [ { pullRequestId: '42', repositoryName: 'someRepo', beforeCommitId: 'beforeCommitId', afterCommitId: 'afterCommitId', }, ], }; codeCommitClient .on(GetCommentsForPullRequestCommand) .resolvesOnce(commentsRes); codeCommitClient.on(DeleteCommentContentCommand).resolvesOnce({}); await codeCommit.ensureCommentRemoval({ type: 'by-topic', number: 42, topic: 'some-subject', }); expect(logger.logger.debug).toHaveBeenCalledWith( 'comments object not found under commentsForPullRequestData', ); }); it('deletes comment by content if found', async () => { const commentsRes = { commentsForPullRequestData: [ { pullRequestId: '42', repositoryName: 'someRepo', beforeCommitId: 'beforeCommitId', afterCommitId: 'afterCommitId', comments: [ { commentId: '1', content: 'my comment content', }, ], }, ], }; codeCommitClient .on(GetCommentsForPullRequestCommand) .resolvesOnce(commentsRes); codeCommitClient.on(DeleteCommentContentCommand).resolvesOnce({}); await codeCommit.ensureCommentRemoval({ type: 'by-content', number: 42, content: 'my comment content', }); expect(logger.logger.debug).toHaveBeenCalledWith( 'comment "my comment content" in PR #42 was removed', ); }); it('throws exception in case failed api connection', async () => { const err = new Error('some error'); codeCommitClient.on(GetCommentsForPullRequestCommand).rejectsOnce(err); await codeCommit.ensureCommentRemoval({ type: 'by-content', number: 42, content: 'my comment content', }); expect(logger.logger.debug).toHaveBeenCalledWith( { err }, 'Unable to retrieve pr comments', ); }); }); describe('addReviewers', () => { it('checks that the function resolves', async () => { const res = { approvalRule: { approvalRuleName: 'Assignees By Renovate', lastModifiedDate: new Date(), ruleContentSha256: '7c44e6ebEXAMPLE', creationDate: new Date(), approvalRuleId: 'aac33506-EXAMPLE', approvalRuleContent: '{"Version": "2018-11-08","Statements": [{"Type": "Approvers","NumberOfApprovalsNeeded": 1,"ApprovalPoolMembers": ["arn:aws:iam::someUser:user/ReviewerUser"]}]}', lastModifiedUser: 'arn:aws:iam::someUser:user/ReviewerUser', }, }; codeCommitClient .on(CreatePullRequestApprovalRuleCommand) .resolvesOnce(res); await expect( codeCommit.addReviewers(13, [ 'arn:aws:iam::someUser:user/ReviewerUser', ]), ).toResolve(); expect(logger.logger.debug).toHaveBeenCalledWith( res, 'Approval Rule Added to PR #13:', ); }); }); });