Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Idp 442 add call to submit well formatted pr object to terraform repo #27

Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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));
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, 'main');
Mouhajer-CO marked this conversation as resolved.
Show resolved Hide resolved
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';

export const extractBaseShaHelper = (response: ApiResponse<any> | ApiErrorResponse) => {
if ('resource' in response && response.resource && 'object' in response.resource && 'sha' in response.resource.object) {
return response.resource.object.sha;
} else {
return response;
Mouhajer-CO marked this conversation as resolved.
Show resolved Hide resolved
}
};

export const extractShaHelper = (response: ApiResponse<any> | 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`;
Mouhajer-CO marked this conversation as resolved.
Show resolved Hide resolved
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 };
};
43 changes: 43 additions & 0 deletions test/mock/data.mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -197,3 +197,46 @@ 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_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
94 changes: 94 additions & 0 deletions test/unit/api-sdk/github/utils.spec.ts
Original file line number Diff line number Diff line change
@@ -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);
});
});
Loading