From 5dc99008c79cfdd7f4eb175c6ef9f5673169d20d Mon Sep 17 00:00:00 2001 From: Daniel Murray Date: Fri, 4 Oct 2024 01:25:09 +0100 Subject: [PATCH 1/5] add funtionality to create a branch, tree, commit, and pr --- src/api-sdk/github/github.ts | 112 ++++++++++++++++++++++++++++++++++- src/api-sdk/github/type.ts | 9 +++ 2 files changed, 120 insertions(+), 1 deletion(-) diff --git a/src/api-sdk/github/github.ts b/src/api-sdk/github/github.ts index a49b979..0203ad5 100644 --- a/src/api-sdk/github/github.ts +++ b/src/api-sdk/github/github.ts @@ -5,7 +5,8 @@ import { GitHubMembersPerTeam, GitHubReposPerTeam, GitHubCollaboratorsPerRepo, - GitHubIssueRequest + GitHubIssueRequest, + GitHubPullRequest } from './type'; import { reposMapping, @@ -78,6 +79,115 @@ export class Github { return this.responseHandler(response); } + // create pr functionality using generic get and post functions + + public async getSha(repoUrl: string) { + const url = `${repoUrl}/git/refs/heads/main`; + const response = await this.getData(url); + + if ('resource' in response && response.resource) { + return response.resource.object.sha; + } else { + return response; + } + } + + public async createBranch(repoUrl: string, branchName: string, baseSha: string) { + const url = `${repoUrl}/git/refs`; + const body = { + ref: `refs/heads/${branchName}`, + sha: baseSha + }; + const response = await this.postData(url, body); + return response; + } + + public async createBlob(repoUrl: string, content: string) { + const blobUrl = `${repoUrl}/git/blobs`; + const body = { + content: Buffer.from(content).toString('base64'), + encoding: 'base64', + }; + const response = await this.postData(blobUrl, body); + + if ('resource' in response && response.resource) { + return response.resource.sha; + } else { + return response; + } + } + + public async createTree(repoUrl: string, baseTreeSha: string, path: string, blobSha: string) { + const treeUrl = `${repoUrl}/git/trees`; + const body = { + base_tree: baseTreeSha, + tree: [ + { + path: path, + mode: '100644', + type: 'blob', + sha: blobSha, + } + ] + }; + const response = await this.postData(treeUrl, body); + + if ('resource' in response && response.resource) { + return response.resource.sha; + } else { + return response; + } + } + + public async createCommit(repoUrl: string, message: string, treeSha: string, parentSha: string) { + const commitUrl = `${repoUrl}/git/commits`; + const body = { + message: message, + tree: treeSha, + parents: [parentSha] + }; + const response = await this.postData(commitUrl, body); + + if ('resource' in response && response.resource) { + return response.resource.sha; + } else { + return response; + } + } + + public async updateBranchReferance(repoUrl: string, branch: string, commitSha: string) { + const refUrl = `${repoUrl}/git/refs/heads/${branch}`; + const body = { + sha: commitSha, + }; + const response = await this.postData(refUrl, body); + return response; + } + + public async createPullRequest(repoUrl: string, title: string, body: string, head: string, base: string): Promise | ApiErrorResponse> { + const prUrl = `${repoUrl}/pulls`; + const prBody = { + title: title, + body: body, + head: head, + base: base + }; + const response = await this.postData(prUrl, prBody); + return response; + } + + public async postPullRequest (repoUrl: string, body: GitHubPullRequest): Promise | ApiErrorResponse> { + const baseSha = await this.getSha(repoUrl); + await this.createBranch(repoUrl, body.branchName, baseSha); + const blobSha = await this.createBlob(repoUrl, body.prContent); + const treeSha = await this.createTree(repoUrl, baseSha, body.filePath, blobSha); + const commitSha = await this.createCommit(repoUrl, body.prTitle, treeSha, baseSha); + + await this.updateBranchReferance(repoUrl, body.branchName, commitSha); + const response = this.createPullRequest(repoUrl, body.prTitle, body.prBody, body.branchName, body.baseBranch); + return response; + } + private responseHandler( response: any, responseMap?: (body: any) => T diff --git a/src/api-sdk/github/type.ts b/src/api-sdk/github/type.ts index ba5986d..c2bae5b 100644 --- a/src/api-sdk/github/type.ts +++ b/src/api-sdk/github/type.ts @@ -47,3 +47,12 @@ export interface GitHubIssueRequest { assignees: string[], labels: string[] } + +export interface GitHubPullRequest { + prTitle: string, + prBody: string, + filePath: string, + prContent: string, + branchName: string, + baseBranch: string +} From fb8b5a0b4fc54c97eac1d7ff037d9b8152d9e5c1 Mon Sep 17 00:00:00 2001 From: Daniel Murray Date: Fri, 4 Oct 2024 01:25:44 +0100 Subject: [PATCH 2/5] add unit tests for create a branch, tree, commit, and pr --- test/mock/data.mock.ts | 37 ++++++ test/mock/text.mock.ts | 5 + test/unit/api-sdk/github/github.spec.ts | 160 +++++++++++++++++++++++- 3 files changed, 201 insertions(+), 1 deletion(-) create mode 100644 test/mock/text.mock.ts diff --git a/test/mock/data.mock.ts b/test/mock/data.mock.ts index 9cdc778..6729fbc 100644 --- a/test/mock/data.mock.ts +++ b/test/mock/data.mock.ts @@ -197,3 +197,40 @@ export const MOCK_POST_RESPONSE = { httpStatusCode: 200, resource: MOCK_POST }; + +export const MOCK_SHA_RESPONSE = { + object: { sha: 'ABC12345678' } +}; + +export const MOCK_POST_BRANCH = { ref: 'refs/heads/test-branch', sha: 'ABC12345678' }; + +export const MOCK_POST_BLOB = { + content: Buffer.from('test content').toString('base64'), + encoding: 'base64' +}; + +export const MOCK_POST_TREE = { + base_tree: 'ABC12345678', + tree: [{ path: 'terraform/account-1.tf', mode: '100644', type: 'blob', sha: 'mockBlobSha' }] +}; + +export const MOCK_POST_COMMIT = { + message: 'commit message', + tree: 'mockTreeSha', + parents: ['ABC12345678'] +}; + +export const MOCK_POST_PR = { + title: 'PR Title', + body: 'PR Body', + head: 'new-feature', + base: 'main' +}; +export const MOCK_PR_RESPONSE = { + branchName: 'new-feature', + prContent: 'some content', + filePath: 'file.txt', + prTitle: 'PR Title', + prBody: 'PR Body', + baseBranch: 'main' +}; diff --git a/test/mock/text.mock.ts b/test/mock/text.mock.ts new file mode 100644 index 0000000..eadbd99 --- /dev/null +++ b/test/mock/text.mock.ts @@ -0,0 +1,5 @@ +export const MOCK_REPO_URL = 'https://api.github.com/repos/test-repo'; + +export const MOCK_SHA = 'ABC12345678'; + +export const MOCK_BRANCH_NAME = 'test-branch'; diff --git a/test/unit/api-sdk/github/github.spec.ts b/test/unit/api-sdk/github/github.spec.ts index 24849fe..d3fe943 100644 --- a/test/unit/api-sdk/github/github.spec.ts +++ b/test/unit/api-sdk/github/github.spec.ts @@ -1,6 +1,7 @@ import { jest, beforeEach, afterEach, describe, test, expect } from '@jest/globals'; import { Github } from '../../../../src/api-sdk/github/github'; import { HttpRequest } from '../../../../src/http-request/index'; +import { MOCK_REPO_URL, MOCK_SHA, MOCK_BRANCH_NAME } from '../../../mock/text.mock'; import { MOCK_REPO_FETCH_RESPONSE, MOCK_MEMBER_FETCH_RESPONSE, @@ -27,7 +28,14 @@ import { MOCK_GET, MOCK_GET_RESPONSE, MOCK_POST, - MOCK_POST_RESPONSE + MOCK_POST_RESPONSE, + MOCK_SHA_RESPONSE, + MOCK_POST_BRANCH, + MOCK_POST_BLOB, + MOCK_POST_TREE, + MOCK_POST_COMMIT, + MOCK_POST_PR, + MOCK_PR_RESPONSE } from '../../../mock/data.mock'; import { HttpResponse } from '../../../../src/http-request/type'; @@ -163,6 +171,156 @@ describe('Github sdk module test suites', () => { expect(result).toEqual(MOCK_POST_RESPONSE); }); + test('Should retrieve the SHA of the main branch', async () => { + httpRequestMock.httpGet.mockResolvedValue(createMockHttpResponse(MOCK_SHA_RESPONSE)); + + const result = await github.getSha(MOCK_REPO_URL); + + expect(httpRequestMock.httpGet).toHaveBeenCalledWith(`${MOCK_REPO_URL}/git/refs/heads/main`); + expect(result).toEqual(MOCK_SHA); + }); + + test('Should create a new branch successfully', async () => { + httpRequestMock.httpPost.mockResolvedValue(createMockHttpResponse({})); + + const result = await github.createBranch(MOCK_REPO_URL, MOCK_BRANCH_NAME, MOCK_SHA); + expect(httpRequestMock.httpPost).toHaveBeenCalledWith( + `${MOCK_REPO_URL}/git/refs`, + JSON.stringify(MOCK_POST_BRANCH) + ); + expect(result.httpStatusCode).toEqual(200); + }); + + test('should create a blob and return the SHA', async () => { + httpRequestMock.httpPost.mockResolvedValue(createMockHttpResponse({ sha: MOCK_SHA })); + + const content = 'test content'; + const result = await github.createBlob(MOCK_REPO_URL, content); + + expect(httpRequestMock.httpPost).toHaveBeenCalledWith(`${MOCK_REPO_URL}/git/blobs`, JSON.stringify(MOCK_POST_BLOB)); + expect(result).toEqual(MOCK_SHA); + }); + + test('should create a tree and return the SHA', async () => { + httpRequestMock.httpPost.mockResolvedValue(createMockHttpResponse({ sha: MOCK_SHA })); + + const path = 'terraform/account-1.tf'; + const blobSha = 'mockBlobSha'; + + const result = await github.createTree(MOCK_REPO_URL, MOCK_SHA, path, blobSha); + + expect(httpRequestMock.httpPost).toHaveBeenCalledWith(`${MOCK_REPO_URL}/git/trees`, JSON.stringify(MOCK_POST_TREE)); + expect(result).toEqual(MOCK_SHA); + }); + + test('should create a commit and return the SHA', async () => { + const commitSha = 'mockCommitSha'; + httpRequestMock.httpPost.mockResolvedValue(createMockHttpResponse({ sha: commitSha })); + + const message = 'commit message'; + const treeSha = 'mockTreeSha'; + + const result = await github.createCommit(MOCK_REPO_URL, message, treeSha, MOCK_SHA); + + expect(httpRequestMock.httpPost).toHaveBeenCalledWith(`${MOCK_REPO_URL}/git/commits`, JSON.stringify(MOCK_POST_COMMIT)); + expect(result).toEqual(commitSha); + }); + + test('should update the branch reference with the commit SHA', async () => { + httpRequestMock.httpPost.mockResolvedValue(createMockHttpResponse({})); + + const result = await github.updateBranchReferance(MOCK_REPO_URL, MOCK_BRANCH_NAME, MOCK_SHA); + + expect(httpRequestMock.httpPost).toHaveBeenCalledWith(`${MOCK_REPO_URL}/git/refs/heads/${MOCK_BRANCH_NAME}`, JSON.stringify({ sha: MOCK_SHA })); + expect(result.httpStatusCode).toEqual(200); + }); + test('should create a pull request and return the response', async () => { + httpRequestMock.httpPost.mockResolvedValue(createMockHttpResponse(MOCK_PR_RESPONSE)); + + const title = 'PR Title'; + const body = 'PR Body'; + const head = 'new-feature'; + const base = 'main'; + + const result = await github.createPullRequest(MOCK_REPO_URL, title, body, head, base); + + expect(httpRequestMock.httpPost).toHaveBeenCalledWith(`${MOCK_REPO_URL}/pulls`, JSON.stringify(MOCK_POST_PR)); + expect(result).toEqual({ + httpStatusCode: 200, + resource: MOCK_PR_RESPONSE + }); + }); + + test('should successfully post a pull request and return the response', async () => { + httpRequestMock.httpGet.mockResolvedValue(createMockHttpResponse({ object: { sha: MOCK_SHA } })); + httpRequestMock.httpPost + .mockResolvedValueOnce(createMockHttpResponse({})) // branch creation + .mockResolvedValueOnce(createMockHttpResponse({ sha: 'mockBlobSha' })) + .mockResolvedValueOnce(createMockHttpResponse({ sha: 'mockTreeSha' })) + .mockResolvedValueOnce(createMockHttpResponse({ sha: 'mockCommitSha' })) + .mockResolvedValueOnce(createMockHttpResponse({})) // branch update + .mockResolvedValue(createMockHttpResponse(MOCK_PR_RESPONSE)); + + const result = await github.postPullRequest(MOCK_REPO_URL, MOCK_PR_RESPONSE); + + expect(result).toEqual({ + httpStatusCode: 200, + resource: MOCK_PR_RESPONSE + }); + }); + + test('Should return the error when SHA resource does not exist in getSha', async () => { + httpRequestMock.httpGet.mockResolvedValue(createMockHttpResponse({}, 404)); + + const result = await github.getSha(MOCK_REPO_URL); + + expect(httpRequestMock.httpGet).toHaveBeenCalledWith(`${MOCK_REPO_URL}/git/refs/heads/main`); + expect(result).toEqual({ + httpStatusCode: 404, + errors: [{}], + }); + }); + test('Should return the error when blob SHA resource does not exist in createBlob', async () => { + httpRequestMock.httpPost.mockResolvedValue(createMockHttpResponse({}, 404)); + + const content = 'test content'; + const result = await github.createBlob(MOCK_REPO_URL, content); + + expect(httpRequestMock.httpPost).toHaveBeenCalledWith(`${MOCK_REPO_URL}/git/blobs`, JSON.stringify(MOCK_POST_BLOB)); + expect(result).toEqual({ + httpStatusCode: 404, + errors: [{}], + }); + }); + + test('Should return the error when tree SHA resource does not exist in createTree', async () => { + httpRequestMock.httpPost.mockResolvedValue(createMockHttpResponse({}, 404)); + + const path = 'terraform/account-1.tf'; + const blobSha = 'mockBlobSha'; + const result = await github.createTree(MOCK_REPO_URL, MOCK_SHA, path, blobSha); + + expect(httpRequestMock.httpPost).toHaveBeenCalledWith(`${MOCK_REPO_URL}/git/trees`, JSON.stringify(MOCK_POST_TREE)); + expect(result).toEqual({ + httpStatusCode: 404, + errors: [{}], + }); + }); + + test('Should return the error when commit SHA resource does not exist in createCommit', async () => { + httpRequestMock.httpPost.mockResolvedValue(createMockHttpResponse({}, 404)); + + const message = 'commit message'; + const treeSha = 'mockTreeSha'; + const result = await github.createCommit(MOCK_REPO_URL, message, treeSha, MOCK_SHA); + + expect(httpRequestMock.httpPost).toHaveBeenCalledWith(`${MOCK_REPO_URL}/git/commits`, JSON.stringify(MOCK_POST_COMMIT)); + expect(result).toEqual({ + httpStatusCode: 404, + errors: [{}], + }); + }); + test('Should return an object with an error property', async () => { httpRequestMock.httpGet.mockResolvedValue(createMockHttpResponse(MOCK_TEAMS, 500, MOCK_ERROR)); From e91bc814eb8f48cb6c372442b87ae9c11e6c07cd Mon Sep 17 00:00:00 2001 From: Daniel Murray Date: Wed, 9 Oct 2024 10:18:54 +0100 Subject: [PATCH 3/5] assess card comments with seperation of github functions into utils file --- src/api-sdk/github/github.ts | 117 +++++------------------------------ src/api-sdk/github/utils.ts | 85 +++++++++++++++++++++++++ 2 files changed, 101 insertions(+), 101 deletions(-) create mode 100644 src/api-sdk/github/utils.ts diff --git a/src/api-sdk/github/github.ts b/src/api-sdk/github/github.ts index 0203ad5..7ed351f 100644 --- a/src/api-sdk/github/github.ts +++ b/src/api-sdk/github/github.ts @@ -17,6 +17,7 @@ import { collaboratorsPerRepoMapping } from './mapping'; +import { extractBaseShaHelper, extractShaHelper, getShaParams, createBranchParams, createBlobParams, createTreeParams, createCommitParams, updateBranchReferenceParams, createPullRequestParams } from './utils'; import { ApiResponse, ApiErrorResponse } from '../response'; import { HttpRequest } from '../../http-request'; @@ -79,113 +80,27 @@ export class Github { return this.responseHandler(response); } - // create pr functionality using generic get and post functions - - public async getSha(repoUrl: string) { - const url = `${repoUrl}/git/refs/heads/main`; - const response = await this.getData(url); - - if ('resource' in response && response.resource) { - return response.resource.object.sha; - } else { - return response; - } - } - - public async createBranch(repoUrl: string, branchName: string, baseSha: string) { - const url = `${repoUrl}/git/refs`; - const body = { - ref: `refs/heads/${branchName}`, - sha: baseSha - }; - const response = await this.postData(url, body); - return response; - } - - public async createBlob(repoUrl: string, content: string) { - const blobUrl = `${repoUrl}/git/blobs`; - const body = { - content: Buffer.from(content).toString('base64'), - encoding: 'base64', - }; - const response = await this.postData(blobUrl, body); - - if ('resource' in response && response.resource) { - return response.resource.sha; - } else { - return response; - } - } - - public async createTree(repoUrl: string, baseTreeSha: string, path: string, blobSha: string) { - const treeUrl = `${repoUrl}/git/trees`; - const body = { - base_tree: baseTreeSha, - tree: [ - { - path: path, - mode: '100644', - type: 'blob', - sha: blobSha, - } - ] - }; - const response = await this.postData(treeUrl, body); + public async postPullRequest (repoUrl: string, body: GitHubPullRequest): Promise | ApiErrorResponse> { + const shaResponse = await this.getData(getShaParams(repoUrl)); + const baseSha = extractBaseShaHelper(shaResponse); - if ('resource' in response && response.resource) { - return response.resource.sha; - } else { - return response; - } - } + const { branchUrl, branchBody } = createBranchParams(repoUrl, body.branchName, baseSha); + await this.postData(branchUrl, branchBody); - public async createCommit(repoUrl: string, message: string, treeSha: string, parentSha: string) { - const commitUrl = `${repoUrl}/git/commits`; - const body = { - message: message, - tree: treeSha, - parents: [parentSha] - }; - const response = await this.postData(commitUrl, body); + const { blobUrl, blobBody } = createBlobParams(repoUrl, body.prContent); + const blobSha = extractShaHelper(await this.postData(blobUrl, blobBody)); - if ('resource' in response && response.resource) { - return response.resource.sha; - } else { - return response; - } - } + const { treeUrl, treeBody } = createTreeParams(repoUrl, baseSha, body.filePath, blobSha); + const treeSha = extractShaHelper(await this.postData(treeUrl, treeBody)); - public async updateBranchReferance(repoUrl: string, branch: string, commitSha: string) { - const refUrl = `${repoUrl}/git/refs/heads/${branch}`; - const body = { - sha: commitSha, - }; - const response = await this.postData(refUrl, body); - return response; - } + const { commitUrl, commitBody } = createCommitParams(repoUrl, body.prTitle, treeSha, baseSha); + const commitSha = extractShaHelper(await this.postData(commitUrl, commitBody)); - public async createPullRequest(repoUrl: string, title: string, body: string, head: string, base: string): Promise | ApiErrorResponse> { - const prUrl = `${repoUrl}/pulls`; - const prBody = { - title: title, - body: body, - head: head, - base: base - }; - const response = await this.postData(prUrl, prBody); - return response; - } + const { refUrl, refBody } = updateBranchReferenceParams(repoUrl, body.branchName, commitSha); + await this.postData(refUrl, refBody); - public async postPullRequest (repoUrl: string, body: GitHubPullRequest): Promise | ApiErrorResponse> { - const baseSha = await this.getSha(repoUrl); - await this.createBranch(repoUrl, body.branchName, baseSha); - const blobSha = await this.createBlob(repoUrl, body.prContent); - const treeSha = await this.createTree(repoUrl, baseSha, body.filePath, blobSha); - const commitSha = await this.createCommit(repoUrl, body.prTitle, treeSha, baseSha); - - await this.updateBranchReferance(repoUrl, body.branchName, commitSha); - const response = this.createPullRequest(repoUrl, body.prTitle, body.prBody, body.branchName, body.baseBranch); - return response; + const { prUrl, prPostbody } = createPullRequestParams(repoUrl, body.prTitle, body.prBody, body.branchName, 'main'); + return this.postData(prUrl, prPostbody); } private responseHandler( diff --git a/src/api-sdk/github/utils.ts b/src/api-sdk/github/utils.ts new file mode 100644 index 0000000..689901f --- /dev/null +++ b/src/api-sdk/github/utils.ts @@ -0,0 +1,85 @@ +import { ApiResponse, ApiErrorResponse } from '../response'; + +export const extractBaseShaHelper = (response: ApiResponse | ApiErrorResponse) => { + if ('resource' in response && response.resource && 'object' in response.resource && 'sha' in response.resource.object) { + return response.resource.object.sha; + } else { + return response; + } +}; + +export const extractShaHelper = (response: ApiResponse | ApiErrorResponse) => { + if ('resource' in response && response.resource && 'sha' in response.resource) { + return response.resource.sha; + } else { + return response; + } +}; + +export const getShaParams = (repoUrl: string) => { + const shaUrl = `${repoUrl}/git/refs/heads/main`; + return shaUrl; +}; + +export const createBranchParams = (repoUrl: string, branchName: string, baseSha: string) => { + const branchUrl = `${repoUrl}/git/refs`; + const branchBody = { + ref: `refs/heads/${branchName}`, + sha: baseSha + }; + return { branchUrl, branchBody }; +}; + +export const createBlobParams = (repoUrl: string, content: string) => { + const blobUrl = `${repoUrl}/git/blobs`; + const blobBody = { + content: Buffer.from(content).toString('base64'), + encoding: 'base64' + }; + return { blobUrl, blobBody }; +}; + +export const createTreeParams = (repoUrl: string, baseTreeSha: string, path: string, blobSha: string) => { + const treeUrl = `${repoUrl}/git/trees`; + const treeBody = { + base_tree: baseTreeSha, + tree: [ + { + path: path, + mode: '100644', + type: 'blob', + sha: blobSha + } + ] + }; + return { treeUrl, treeBody }; +}; + +export const createCommitParams = (repoUrl: string, message: string, treeSha: string, parentSha: string) => { + const commitUrl = `${repoUrl}/git/commits`; + const commitBody = { + message: message, + tree: treeSha, + parents: [parentSha] + }; + return { commitUrl, commitBody }; +}; + +export const updateBranchReferenceParams = (repoUrl: string, branch: string, commitSha: string) => { + const refUrl = `${repoUrl}/git/refs/heads/${branch}`; + const refBody = { + sha: commitSha + }; + return { refUrl, refBody }; +}; + +export const createPullRequestParams = (repoUrl: string, prTitle: string, prBody: string, branchName: string, baseBranch: string) => { + const prUrl = `${repoUrl}/pulls`; + const prPostbody = { + title: prTitle, + body: prBody, + head: branchName, + base: baseBranch + }; + return { prUrl, prPostbody }; +}; From fd42d4131b1cf39f0bb0010de0b23ad95c467b49 Mon Sep 17 00:00:00 2001 From: Daniel Murray Date: Wed, 9 Oct 2024 10:19:23 +0100 Subject: [PATCH 4/5] update unit tests based on refactor of util functions --- test/mock/data.mock.ts | 12 +- test/mock/text.mock.ts | 12 +- test/unit/api-sdk/github/github.spec.ts | 157 ++---------------------- test/unit/api-sdk/github/utils.spec.ts | 94 ++++++++++++++ 4 files changed, 124 insertions(+), 151 deletions(-) create mode 100644 test/unit/api-sdk/github/utils.spec.ts diff --git a/test/mock/data.mock.ts b/test/mock/data.mock.ts index 6729fbc..121727f 100644 --- a/test/mock/data.mock.ts +++ b/test/mock/data.mock.ts @@ -202,6 +202,11 @@ export const MOCK_SHA_RESPONSE = { object: { sha: 'ABC12345678' } }; +export const MOCK_INVALID_SHA_RESPONSE = { + httpStatusCode: 404, + object: ['Resource not found'] +}; + export const MOCK_POST_BRANCH = { ref: 'refs/heads/test-branch', sha: 'ABC12345678' }; export const MOCK_POST_BLOB = { @@ -211,21 +216,22 @@ export const MOCK_POST_BLOB = { export const MOCK_POST_TREE = { base_tree: 'ABC12345678', - tree: [{ path: 'terraform/account-1.tf', mode: '100644', type: 'blob', sha: 'mockBlobSha' }] + tree: [{ path: 'terraform/account-1.tf', mode: '100644', type: 'blob', sha: 'ABC12345678' }] }; export const MOCK_POST_COMMIT = { message: 'commit message', - tree: 'mockTreeSha', + tree: 'ABC12345678', parents: ['ABC12345678'] }; export const MOCK_POST_PR = { title: 'PR Title', body: 'PR Body', - head: 'new-feature', + head: 'test-branch', base: 'main' }; + export const MOCK_PR_RESPONSE = { branchName: 'new-feature', prContent: 'some content', diff --git a/test/mock/text.mock.ts b/test/mock/text.mock.ts index eadbd99..b31351d 100644 --- a/test/mock/text.mock.ts +++ b/test/mock/text.mock.ts @@ -1,5 +1,15 @@ export const MOCK_REPO_URL = 'https://api.github.com/repos/test-repo'; -export const MOCK_SHA = 'ABC12345678'; +export const MOCK_BASE_SHA = 'ABC12345678'; +export const MOCK_BLOB_SHA = 'ABC12345678'; +export const MOCK_TREE_SHA = 'ABC12345678'; +export const MOCK_COMMIT_SHA = 'ABC12345678'; +export const MOCK_COMMIT_MESSAGE = 'commit message'; export const MOCK_BRANCH_NAME = 'test-branch'; + +export const MOCK_PATH = 'terraform/account-1.tf'; + +export const MOCK_PR_TITLE = 'PR Title'; +export const MOCK_PR_BODY = 'PR Body'; + diff --git a/test/unit/api-sdk/github/github.spec.ts b/test/unit/api-sdk/github/github.spec.ts index d3fe943..5837f03 100644 --- a/test/unit/api-sdk/github/github.spec.ts +++ b/test/unit/api-sdk/github/github.spec.ts @@ -1,7 +1,7 @@ import { jest, beforeEach, afterEach, describe, test, expect } from '@jest/globals'; import { Github } from '../../../../src/api-sdk/github/github'; import { HttpRequest } from '../../../../src/http-request/index'; -import { MOCK_REPO_URL, MOCK_SHA, MOCK_BRANCH_NAME } from '../../../mock/text.mock'; +import { MOCK_REPO_URL, MOCK_BASE_SHA, MOCK_BLOB_SHA, MOCK_TREE_SHA, MOCK_COMMIT_SHA } from '../../../mock/text.mock'; import { MOCK_REPO_FETCH_RESPONSE, MOCK_MEMBER_FETCH_RESPONSE, @@ -29,12 +29,6 @@ import { MOCK_GET_RESPONSE, MOCK_POST, MOCK_POST_RESPONSE, - MOCK_SHA_RESPONSE, - MOCK_POST_BRANCH, - MOCK_POST_BLOB, - MOCK_POST_TREE, - MOCK_POST_COMMIT, - MOCK_POST_PR, MOCK_PR_RESPONSE } from '../../../mock/data.mock'; import { HttpResponse } from '../../../../src/http-request/type'; @@ -171,154 +165,23 @@ describe('Github sdk module test suites', () => { expect(result).toEqual(MOCK_POST_RESPONSE); }); - test('Should retrieve the SHA of the main branch', async () => { - httpRequestMock.httpGet.mockResolvedValue(createMockHttpResponse(MOCK_SHA_RESPONSE)); - - const result = await github.getSha(MOCK_REPO_URL); - - expect(httpRequestMock.httpGet).toHaveBeenCalledWith(`${MOCK_REPO_URL}/git/refs/heads/main`); - expect(result).toEqual(MOCK_SHA); - }); - - test('Should create a new branch successfully', async () => { - httpRequestMock.httpPost.mockResolvedValue(createMockHttpResponse({})); - - const result = await github.createBranch(MOCK_REPO_URL, MOCK_BRANCH_NAME, MOCK_SHA); - expect(httpRequestMock.httpPost).toHaveBeenCalledWith( - `${MOCK_REPO_URL}/git/refs`, - JSON.stringify(MOCK_POST_BRANCH) - ); - expect(result.httpStatusCode).toEqual(200); - }); - - test('should create a blob and return the SHA', async () => { - httpRequestMock.httpPost.mockResolvedValue(createMockHttpResponse({ sha: MOCK_SHA })); - - const content = 'test content'; - const result = await github.createBlob(MOCK_REPO_URL, content); - - expect(httpRequestMock.httpPost).toHaveBeenCalledWith(`${MOCK_REPO_URL}/git/blobs`, JSON.stringify(MOCK_POST_BLOB)); - expect(result).toEqual(MOCK_SHA); - }); - - test('should create a tree and return the SHA', async () => { - httpRequestMock.httpPost.mockResolvedValue(createMockHttpResponse({ sha: MOCK_SHA })); - - const path = 'terraform/account-1.tf'; - const blobSha = 'mockBlobSha'; - - const result = await github.createTree(MOCK_REPO_URL, MOCK_SHA, path, blobSha); - - expect(httpRequestMock.httpPost).toHaveBeenCalledWith(`${MOCK_REPO_URL}/git/trees`, JSON.stringify(MOCK_POST_TREE)); - expect(result).toEqual(MOCK_SHA); - }); - - test('should create a commit and return the SHA', async () => { - const commitSha = 'mockCommitSha'; - httpRequestMock.httpPost.mockResolvedValue(createMockHttpResponse({ sha: commitSha })); - - const message = 'commit message'; - const treeSha = 'mockTreeSha'; - - const result = await github.createCommit(MOCK_REPO_URL, message, treeSha, MOCK_SHA); - - expect(httpRequestMock.httpPost).toHaveBeenCalledWith(`${MOCK_REPO_URL}/git/commits`, JSON.stringify(MOCK_POST_COMMIT)); - expect(result).toEqual(commitSha); - }); - - test('should update the branch reference with the commit SHA', async () => { - httpRequestMock.httpPost.mockResolvedValue(createMockHttpResponse({})); - - const result = await github.updateBranchReferance(MOCK_REPO_URL, MOCK_BRANCH_NAME, MOCK_SHA); - - expect(httpRequestMock.httpPost).toHaveBeenCalledWith(`${MOCK_REPO_URL}/git/refs/heads/${MOCK_BRANCH_NAME}`, JSON.stringify({ sha: MOCK_SHA })); - expect(result.httpStatusCode).toEqual(200); - }); - test('should create a pull request and return the response', async () => { - httpRequestMock.httpPost.mockResolvedValue(createMockHttpResponse(MOCK_PR_RESPONSE)); - - const title = 'PR Title'; - const body = 'PR Body'; - const head = 'new-feature'; - const base = 'main'; - - const result = await github.createPullRequest(MOCK_REPO_URL, title, body, head, base); - - expect(httpRequestMock.httpPost).toHaveBeenCalledWith(`${MOCK_REPO_URL}/pulls`, JSON.stringify(MOCK_POST_PR)); - expect(result).toEqual({ - httpStatusCode: 200, - resource: MOCK_PR_RESPONSE - }); - }); - test('should successfully post a pull request and return the response', async () => { - httpRequestMock.httpGet.mockResolvedValue(createMockHttpResponse({ object: { sha: MOCK_SHA } })); + httpRequestMock.httpGet.mockResolvedValue(createMockHttpResponse({ object: { sha: MOCK_BASE_SHA } })); httpRequestMock.httpPost - .mockResolvedValueOnce(createMockHttpResponse({})) // branch creation - .mockResolvedValueOnce(createMockHttpResponse({ sha: 'mockBlobSha' })) - .mockResolvedValueOnce(createMockHttpResponse({ sha: 'mockTreeSha' })) - .mockResolvedValueOnce(createMockHttpResponse({ sha: 'mockCommitSha' })) - .mockResolvedValueOnce(createMockHttpResponse({})) // branch update + .mockResolvedValueOnce(createMockHttpResponse({})) + .mockResolvedValueOnce(createMockHttpResponse({ sha: MOCK_BLOB_SHA })) + .mockResolvedValueOnce(createMockHttpResponse({ sha: MOCK_TREE_SHA })) + .mockResolvedValueOnce(createMockHttpResponse({ sha: MOCK_COMMIT_SHA })) + .mockResolvedValueOnce(createMockHttpResponse({})) .mockResolvedValue(createMockHttpResponse(MOCK_PR_RESPONSE)); const result = await github.postPullRequest(MOCK_REPO_URL, MOCK_PR_RESPONSE); - expect(result).toEqual({ - httpStatusCode: 200, - resource: MOCK_PR_RESPONSE - }); - }); - - test('Should return the error when SHA resource does not exist in getSha', async () => { - httpRequestMock.httpGet.mockResolvedValue(createMockHttpResponse({}, 404)); - - const result = await github.getSha(MOCK_REPO_URL); - - expect(httpRequestMock.httpGet).toHaveBeenCalledWith(`${MOCK_REPO_URL}/git/refs/heads/main`); - expect(result).toEqual({ - httpStatusCode: 404, - errors: [{}], - }); - }); - test('Should return the error when blob SHA resource does not exist in createBlob', async () => { - httpRequestMock.httpPost.mockResolvedValue(createMockHttpResponse({}, 404)); - - const content = 'test content'; - const result = await github.createBlob(MOCK_REPO_URL, content); - - expect(httpRequestMock.httpPost).toHaveBeenCalledWith(`${MOCK_REPO_URL}/git/blobs`, JSON.stringify(MOCK_POST_BLOB)); - expect(result).toEqual({ - httpStatusCode: 404, - errors: [{}], - }); - }); - - test('Should return the error when tree SHA resource does not exist in createTree', async () => { - httpRequestMock.httpPost.mockResolvedValue(createMockHttpResponse({}, 404)); - - const path = 'terraform/account-1.tf'; - const blobSha = 'mockBlobSha'; - const result = await github.createTree(MOCK_REPO_URL, MOCK_SHA, path, blobSha); + expect(result).toEqual({ httpStatusCode: 200, resource: MOCK_PR_RESPONSE }); - expect(httpRequestMock.httpPost).toHaveBeenCalledWith(`${MOCK_REPO_URL}/git/trees`, JSON.stringify(MOCK_POST_TREE)); - expect(result).toEqual({ - httpStatusCode: 404, - errors: [{}], - }); - }); - - test('Should return the error when commit SHA resource does not exist in createCommit', async () => { - httpRequestMock.httpPost.mockResolvedValue(createMockHttpResponse({}, 404)); + expect(httpRequestMock.httpGet).toHaveBeenCalledTimes(1); - const message = 'commit message'; - const treeSha = 'mockTreeSha'; - const result = await github.createCommit(MOCK_REPO_URL, message, treeSha, MOCK_SHA); - - expect(httpRequestMock.httpPost).toHaveBeenCalledWith(`${MOCK_REPO_URL}/git/commits`, JSON.stringify(MOCK_POST_COMMIT)); - expect(result).toEqual({ - httpStatusCode: 404, - errors: [{}], - }); + expect(httpRequestMock.httpPost).toHaveBeenCalledTimes(6); }); test('Should return an object with an error property', async () => { diff --git a/test/unit/api-sdk/github/utils.spec.ts b/test/unit/api-sdk/github/utils.spec.ts new file mode 100644 index 0000000..2f50970 --- /dev/null +++ b/test/unit/api-sdk/github/utils.spec.ts @@ -0,0 +1,94 @@ +import { jest, afterEach, describe, test, expect } from '@jest/globals'; + +import { + extractBaseShaHelper, + extractShaHelper, + getShaParams, + createBranchParams, + createBlobParams, + createTreeParams, + createCommitParams, + updateBranchReferenceParams, + createPullRequestParams +} from '../../../../src/api-sdk/github/utils'; +import { MOCK_REPO_URL, MOCK_BASE_SHA, MOCK_BLOB_SHA, MOCK_TREE_SHA, MOCK_COMMIT_SHA, MOCK_BRANCH_NAME, MOCK_PATH, MOCK_COMMIT_MESSAGE, MOCK_PR_TITLE, MOCK_PR_BODY } from '../../../mock/text.mock'; +import { MOCK_POST_BLOB, MOCK_INVALID_SHA_RESPONSE, MOCK_POST_BRANCH, MOCK_POST_TREE, MOCK_POST_COMMIT, MOCK_POST_PR } from '../../../mock/data.mock'; + +describe('Github utils test suites', () => { + + afterEach(() => { + jest.restoreAllMocks(); + }); + + test('extractBaseShaHelper should return sha if it exists', () => { + const mockBaseShaResponse = { + httpStatusCode: 200, + resource: { object: { sha: MOCK_BASE_SHA } } + }; + const result = extractBaseShaHelper(mockBaseShaResponse); + expect(result).toBe(MOCK_BASE_SHA); + }); + + test('extractBaseShaHelper should return response if resource does not exist', () => { + const result = extractBaseShaHelper(MOCK_INVALID_SHA_RESPONSE); + expect(result).toEqual(MOCK_INVALID_SHA_RESPONSE); + }); + + test('extractShaHelper should return sha if it exists', () => { + const mockShaResponse = { + httpStatusCode: 200, + resource: { sha: MOCK_BLOB_SHA } + }; + const result = extractShaHelper(mockShaResponse); + expect(result).toBe(MOCK_BLOB_SHA); + }); + + test('extractShaHelper should return response if sha does not exist', () => { + const result = extractShaHelper(MOCK_INVALID_SHA_RESPONSE); + expect(result).toEqual(MOCK_INVALID_SHA_RESPONSE); + }); + + test('getShaParams should return the correct sha URL', () => { + const result = getShaParams(MOCK_REPO_URL); + expect(result).toBe(`${MOCK_REPO_URL}/git/refs/heads/main`); + }); + + test('createBranchParams should return the correct branch URL and body', () => { + const { branchUrl, branchBody } = createBranchParams(MOCK_REPO_URL, MOCK_BRANCH_NAME, MOCK_BASE_SHA); + + expect(branchUrl).toBe(`${MOCK_REPO_URL}/git/refs`); + expect(branchBody).toEqual(MOCK_POST_BRANCH); + }); + + test('createBlobParams should return the correct blob URL and body', () => { + const { blobUrl, blobBody } = createBlobParams(MOCK_REPO_URL, 'test content'); + + expect(blobUrl).toBe(`${MOCK_REPO_URL}/git/blobs`); + expect(blobBody).toEqual(MOCK_POST_BLOB); + }); + + test('createTreeParams should return the correct tree URL and body', () => { + const { treeUrl, treeBody } = createTreeParams(MOCK_REPO_URL, MOCK_BASE_SHA, MOCK_PATH, MOCK_BLOB_SHA); + + expect(treeUrl).toBe(`${MOCK_REPO_URL}/git/trees`); + expect(treeBody).toEqual(MOCK_POST_TREE); + }); + + test('createCommitParams should return the correct commit URL and body', () => { + const { commitUrl, commitBody } = createCommitParams(MOCK_REPO_URL, MOCK_COMMIT_MESSAGE, MOCK_TREE_SHA, MOCK_BASE_SHA); + expect(commitUrl).toBe(`${MOCK_REPO_URL}/git/commits`); + expect(commitBody).toEqual(MOCK_POST_COMMIT); + }); + + test('updateBranchReferenceParams should return the correct ref URL and body', () => { + const { refUrl, refBody } = updateBranchReferenceParams(MOCK_REPO_URL, MOCK_BRANCH_NAME, MOCK_COMMIT_SHA); + expect(refUrl).toBe(`${MOCK_REPO_URL}/git/refs/heads/${MOCK_BRANCH_NAME}`); + expect(refBody).toEqual({ sha: MOCK_COMMIT_SHA }); + }); + + test('createPullRequestParams should return the correct PR URL and body', () => { + const { prUrl, prPostbody } = createPullRequestParams(MOCK_REPO_URL, MOCK_PR_TITLE, MOCK_PR_BODY, MOCK_BRANCH_NAME, 'main'); + expect(prUrl).toBe(`${MOCK_REPO_URL}/pulls`); + expect(prPostbody).toEqual(MOCK_POST_PR); + }); +}); From 287854899136b6f1d863d89f4d3cc92468c9718b Mon Sep 17 00:00:00 2001 From: Daniel Murray Date: Mon, 14 Oct 2024 16:17:38 +0100 Subject: [PATCH 5/5] assess pr comments --- src/api-sdk/github/github.ts | 4 ++-- src/api-sdk/github/utils.ts | 18 +++++++++--------- test/mock/data.mock.ts | 2 ++ test/unit/api-sdk/github/utils.spec.ts | 14 ++++++++------ 4 files changed, 21 insertions(+), 17 deletions(-) diff --git a/src/api-sdk/github/github.ts b/src/api-sdk/github/github.ts index 7ed351f..2283546 100644 --- a/src/api-sdk/github/github.ts +++ b/src/api-sdk/github/github.ts @@ -81,7 +81,7 @@ export class Github { } public async postPullRequest (repoUrl: string, body: GitHubPullRequest): Promise | ApiErrorResponse> { - const shaResponse = await this.getData(getShaParams(repoUrl)); + const shaResponse = await this.getData(getShaParams(repoUrl, body.baseBranch)); const baseSha = extractBaseShaHelper(shaResponse); const { branchUrl, branchBody } = createBranchParams(repoUrl, body.branchName, baseSha); @@ -99,7 +99,7 @@ export class Github { const { refUrl, refBody } = updateBranchReferenceParams(repoUrl, body.branchName, commitSha); await this.postData(refUrl, refBody); - const { prUrl, prPostbody } = createPullRequestParams(repoUrl, body.prTitle, body.prBody, body.branchName, 'main'); + const { prUrl, prPostbody } = createPullRequestParams(repoUrl, body.prTitle, body.prBody, body.branchName, body.baseBranch); return this.postData(prUrl, prPostbody); } diff --git a/src/api-sdk/github/utils.ts b/src/api-sdk/github/utils.ts index 689901f..93911be 100644 --- a/src/api-sdk/github/utils.ts +++ b/src/api-sdk/github/utils.ts @@ -1,23 +1,23 @@ import { ApiResponse, ApiErrorResponse } from '../response'; +const defaultBranch = 'main'; + export const extractBaseShaHelper = (response: ApiResponse | ApiErrorResponse) => { - if ('resource' in response && response.resource && 'object' in response.resource && 'sha' in response.resource.object) { + if ('resource' in response && 'object' in response.resource){ return response.resource.object.sha; - } else { - return response; } + throw new Error(`Error: ${JSON.stringify(response)}`); }; export const extractShaHelper = (response: ApiResponse | ApiErrorResponse) => { - if ('resource' in response && response.resource && 'sha' in response.resource) { + if ('resource' in response){ return response.resource.sha; - } else { - return response; } + throw new Error(`Error: ${JSON.stringify(response)}`); }; -export const getShaParams = (repoUrl: string) => { - const shaUrl = `${repoUrl}/git/refs/heads/main`; +export const getShaParams = (repoUrl: string, baseBranch: string = defaultBranch) => { + const shaUrl = `${repoUrl}/git/refs/heads/${baseBranch}`; return shaUrl; }; @@ -73,7 +73,7 @@ export const updateBranchReferenceParams = (repoUrl: string, branch: string, com return { refUrl, refBody }; }; -export const createPullRequestParams = (repoUrl: string, prTitle: string, prBody: string, branchName: string, baseBranch: string) => { +export const createPullRequestParams = (repoUrl: string, prTitle: string, prBody: string, branchName: string, baseBranch: string = defaultBranch) => { const prUrl = `${repoUrl}/pulls`; const prPostbody = { title: prTitle, diff --git a/test/mock/data.mock.ts b/test/mock/data.mock.ts index 121727f..c6b5229 100644 --- a/test/mock/data.mock.ts +++ b/test/mock/data.mock.ts @@ -207,6 +207,8 @@ export const MOCK_INVALID_SHA_RESPONSE = { object: ['Resource not found'] }; +export const MOCK_API_ERROR = new Error(`Error: ${JSON.stringify(MOCK_INVALID_SHA_RESPONSE)}`); + export const MOCK_POST_BRANCH = { ref: 'refs/heads/test-branch', sha: 'ABC12345678' }; export const MOCK_POST_BLOB = { diff --git a/test/unit/api-sdk/github/utils.spec.ts b/test/unit/api-sdk/github/utils.spec.ts index 2f50970..acf68a0 100644 --- a/test/unit/api-sdk/github/utils.spec.ts +++ b/test/unit/api-sdk/github/utils.spec.ts @@ -12,7 +12,7 @@ import { createPullRequestParams } from '../../../../src/api-sdk/github/utils'; import { MOCK_REPO_URL, MOCK_BASE_SHA, MOCK_BLOB_SHA, MOCK_TREE_SHA, MOCK_COMMIT_SHA, MOCK_BRANCH_NAME, MOCK_PATH, MOCK_COMMIT_MESSAGE, MOCK_PR_TITLE, MOCK_PR_BODY } from '../../../mock/text.mock'; -import { MOCK_POST_BLOB, MOCK_INVALID_SHA_RESPONSE, MOCK_POST_BRANCH, MOCK_POST_TREE, MOCK_POST_COMMIT, MOCK_POST_PR } from '../../../mock/data.mock'; +import { MOCK_POST_BLOB, MOCK_INVALID_SHA_RESPONSE, MOCK_POST_BRANCH, MOCK_POST_TREE, MOCK_POST_COMMIT, MOCK_POST_PR, MOCK_API_ERROR } from '../../../mock/data.mock'; describe('Github utils test suites', () => { @@ -30,8 +30,9 @@ describe('Github utils test suites', () => { }); test('extractBaseShaHelper should return response if resource does not exist', () => { - const result = extractBaseShaHelper(MOCK_INVALID_SHA_RESPONSE); - expect(result).toEqual(MOCK_INVALID_SHA_RESPONSE); + jest.spyOn(global, 'Error').mockImplementationOnce(() => MOCK_API_ERROR); + + expect(() => extractBaseShaHelper(MOCK_INVALID_SHA_RESPONSE)).toThrowError(MOCK_API_ERROR); }); test('extractShaHelper should return sha if it exists', () => { @@ -44,8 +45,9 @@ describe('Github utils test suites', () => { }); test('extractShaHelper should return response if sha does not exist', () => { - const result = extractShaHelper(MOCK_INVALID_SHA_RESPONSE); - expect(result).toEqual(MOCK_INVALID_SHA_RESPONSE); + jest.spyOn(global, 'Error').mockImplementationOnce(() => MOCK_API_ERROR); + + expect(() => extractShaHelper(MOCK_INVALID_SHA_RESPONSE)).toThrowError(MOCK_API_ERROR); }); test('getShaParams should return the correct sha URL', () => { @@ -87,7 +89,7 @@ describe('Github utils test suites', () => { }); test('createPullRequestParams should return the correct PR URL and body', () => { - const { prUrl, prPostbody } = createPullRequestParams(MOCK_REPO_URL, MOCK_PR_TITLE, MOCK_PR_BODY, MOCK_BRANCH_NAME, 'main'); + const { prUrl, prPostbody } = createPullRequestParams(MOCK_REPO_URL, MOCK_PR_TITLE, MOCK_PR_BODY, MOCK_BRANCH_NAME); expect(prUrl).toBe(`${MOCK_REPO_URL}/pulls`); expect(prPostbody).toEqual(MOCK_POST_PR); });