Skip to content

Commit

Permalink
Merge pull request #27 from cabinetoffice/IDP-442-add-call-to-submit-…
Browse files Browse the repository at this point in the history
…well-formatted-pr-object-to-terraform-repo

Idp 442 add call to submit well formatted pr object to terraform repo
  • Loading branch information
DanielMurray97 authored Oct 15, 2024
2 parents 77d5b73 + 2878548 commit 9160a8d
Show file tree
Hide file tree
Showing 7 changed files with 298 additions and 2 deletions.
27 changes: 26 additions & 1 deletion src/api-sdk/github/github.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ import {
GitHubMembersPerTeam,
GitHubReposPerTeam,
GitHubCollaboratorsPerRepo,
GitHubIssueRequest
GitHubIssueRequest,
GitHubPullRequest
} from './type';
import {
reposMapping,
Expand All @@ -16,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';

Expand Down Expand Up @@ -78,6 +80,29 @@ export class Github {
return this.responseHandler<any>(response);
}

public async postPullRequest (repoUrl: string, body: GitHubPullRequest): Promise<ApiResponse<any> | ApiErrorResponse> {
const shaResponse = await this.getData(getShaParams(repoUrl, body.baseBranch));
const baseSha = extractBaseShaHelper(shaResponse);

const { branchUrl, branchBody } = createBranchParams(repoUrl, body.branchName, baseSha);
await this.postData(branchUrl, branchBody);

const { blobUrl, blobBody } = createBlobParams(repoUrl, body.prContent);
const blobSha = extractShaHelper(await this.postData(blobUrl, blobBody));

const { treeUrl, treeBody } = createTreeParams(repoUrl, baseSha, body.filePath, blobSha);
const treeSha = extractShaHelper(await this.postData(treeUrl, treeBody));

const { commitUrl, commitBody } = createCommitParams(repoUrl, body.prTitle, treeSha, baseSha);
const commitSha = extractShaHelper(await this.postData(commitUrl, commitBody));

const { refUrl, refBody } = updateBranchReferenceParams(repoUrl, body.branchName, commitSha);
await this.postData(refUrl, refBody);

const { prUrl, prPostbody } = createPullRequestParams(repoUrl, body.prTitle, body.prBody, body.branchName, body.baseBranch);
return this.postData(prUrl, prPostbody);
}

private responseHandler<T>(
response: any,
responseMap?: (body: any) => T
Expand Down
9 changes: 9 additions & 0 deletions src/api-sdk/github/type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
85 changes: 85 additions & 0 deletions src/api-sdk/github/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { ApiResponse, ApiErrorResponse } from '../response';

const defaultBranch = 'main';

export const extractBaseShaHelper = (response: ApiResponse<any> | ApiErrorResponse) => {
if ('resource' in response && 'object' in response.resource){
return response.resource.object.sha;
}
throw new Error(`Error: ${JSON.stringify(response)}`);
};

export const extractShaHelper = (response: ApiResponse<any> | ApiErrorResponse) => {
if ('resource' in response){
return response.resource.sha;
}
throw new Error(`Error: ${JSON.stringify(response)}`);
};

export const getShaParams = (repoUrl: string, baseBranch: string = defaultBranch) => {
const shaUrl = `${repoUrl}/git/refs/heads/${baseBranch}`;
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 = defaultBranch) => {
const prUrl = `${repoUrl}/pulls`;
const prPostbody = {
title: prTitle,
body: prBody,
head: branchName,
base: baseBranch
};
return { prUrl, prPostbody };
};
45 changes: 45 additions & 0 deletions test/mock/data.mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -197,3 +197,48 @@ export const MOCK_POST_RESPONSE = {
httpStatusCode: 200,
resource: MOCK_POST
};

export const MOCK_SHA_RESPONSE = {
object: { sha: 'ABC12345678' }
};

export const MOCK_INVALID_SHA_RESPONSE = {
httpStatusCode: 404,
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 = {
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: 'ABC12345678' }]
};

export const MOCK_POST_COMMIT = {
message: 'commit message',
tree: 'ABC12345678',
parents: ['ABC12345678']
};

export const MOCK_POST_PR = {
title: 'PR Title',
body: 'PR Body',
head: 'test-branch',
base: 'main'
};

export const MOCK_PR_RESPONSE = {
branchName: 'new-feature',
prContent: 'some content',
filePath: 'file.txt',
prTitle: 'PR Title',
prBody: 'PR Body',
baseBranch: 'main'
};
15 changes: 15 additions & 0 deletions test/mock/text.mock.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
export const MOCK_REPO_URL = 'https://api.github.com/repos/test-repo';

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';

23 changes: 22 additions & 1 deletion test/unit/api-sdk/github/github.spec.ts
Original file line number Diff line number Diff line change
@@ -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_BASE_SHA, MOCK_BLOB_SHA, MOCK_TREE_SHA, MOCK_COMMIT_SHA } from '../../../mock/text.mock';
import {
MOCK_REPO_FETCH_RESPONSE,
MOCK_MEMBER_FETCH_RESPONSE,
Expand All @@ -27,7 +28,8 @@ import {
MOCK_GET,
MOCK_GET_RESPONSE,
MOCK_POST,
MOCK_POST_RESPONSE
MOCK_POST_RESPONSE,
MOCK_PR_RESPONSE
} from '../../../mock/data.mock';
import { HttpResponse } from '../../../../src/http-request/type';

Expand Down Expand Up @@ -163,6 +165,25 @@ describe('Github sdk module test suites', () => {
expect(result).toEqual(MOCK_POST_RESPONSE);
});

test('should successfully post a pull request and return the response', async () => {
httpRequestMock.httpGet.mockResolvedValue(createMockHttpResponse({ object: { sha: MOCK_BASE_SHA } }));
httpRequestMock.httpPost
.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 });

expect(httpRequestMock.httpGet).toHaveBeenCalledTimes(1);

expect(httpRequestMock.httpPost).toHaveBeenCalledTimes(6);
});

test('Should return an object with an error property', async () => {
httpRequestMock.httpGet.mockResolvedValue(createMockHttpResponse(MOCK_TEAMS, 500, MOCK_ERROR));

Expand Down
96 changes: 96 additions & 0 deletions test/unit/api-sdk/github/utils.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
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, MOCK_API_ERROR } 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', () => {
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', () => {
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', () => {
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', () => {
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);
expect(prUrl).toBe(`${MOCK_REPO_URL}/pulls`);
expect(prPostbody).toEqual(MOCK_POST_PR);
});
});

0 comments on commit 9160a8d

Please sign in to comment.