diff --git a/src/api-sdk/github/github.ts b/src/api-sdk/github/github.ts index d8dab5c..5e397a5 100644 --- a/src/api-sdk/github/github.ts +++ b/src/api-sdk/github/github.ts @@ -1,37 +1,75 @@ -import { GitHubRepos, GitHubMembers, GitHubTeams, GitHubMembersPerTeam, GitHubReposPerTeam } from './type'; +import { + GitHubRepos, + GitHubMembers, + GitHubTeams, + GitHubMembersPerTeam, + GitHubReposPerTeam, + GitHubCollaboratorsPerRepo, + GitHubIssueRequest +} from './type'; +import { + reposMapping, + membersMapping, + teamsMapping, + membersPerTeamMapping, + reposPerTeamMapping, + collaboratorsPerRepoMapping +} from './mapping'; + import { ApiResponse, ApiErrorResponse } from '../response'; -import { reposMapping, membersMapping, teamsMapping, membersPerTeamMapping, reposPerTeamMapping } from './mapping'; import { HttpRequest } from '../../http-request'; export class Github { - constructor(private readonly request: HttpRequest) { - /**/ + + constructor(private readonly request: HttpRequest) { /**/ } + + // Get info data. Could be repos/members/collaborator/teams ... + public async getGitHubInfo(url: string): Promise | ApiErrorResponse> { + const response = await this.request.httpGet(url); + return this.responseHandler(response); } + + // Create Issue on defined repo + public async postIssue (url: string, body: GitHubIssueRequest): Promise | ApiErrorResponse> { + const response = await this.request.httpPost(url, body); + return this.responseHandler(response); + } + + // Github data Mapped for IDC project public async getRepos(url: string): Promise | ApiErrorResponse> { - return await this.fetchData(url, reposMapping); + const response = await this.request.httpGet(url); + return this.responseHandler(response, reposMapping); } public async getMembers(url: string): Promise | ApiErrorResponse> { - return await this.fetchData(url, membersMapping); + const response = await this.request.httpGet(url); + return this.responseHandler(response, membersMapping); } public async getTeams(url: string): Promise | ApiErrorResponse> { - return await this.fetchData(url, teamsMapping); + const response = await this.request.httpGet(url); + return this.responseHandler(response, teamsMapping); } public async getMembersPerTeam(url: string): Promise | ApiErrorResponse> { - return await this.fetchData(url, membersPerTeamMapping); + const response = await this.request.httpGet(url); + return this.responseHandler(response, membersPerTeamMapping); } public async getReposPerTeam(url: string): Promise | ApiErrorResponse> { - return await this.fetchData(url, reposPerTeamMapping); + const response = await this.request.httpGet(url); + return this.responseHandler(response, reposPerTeamMapping); } - private async fetchData( - url: string, - mappingFunction: (body: any) => T - ): Promise | ApiErrorResponse> { + public async getCollaboratorsPerRepo(url: string): Promise | ApiErrorResponse> { const response = await this.request.httpGet(url); + return this.responseHandler(response, collaboratorsPerRepoMapping); + } + + private responseHandler( + response: any, + responseMap?: (body: any) => T + ): ApiResponse | ApiErrorResponse { const resource: ApiResponse & ApiErrorResponse = { httpStatusCode: response.status }; @@ -41,7 +79,7 @@ export class Github { } else if (response.status >= 400) { resource.errors = [response.body]; } else { - resource.resource = mappingFunction(response.body); + resource.resource = (responseMap) ? responseMap(response.body) : response.body; } return resource; diff --git a/src/api-sdk/github/mapping.ts b/src/api-sdk/github/mapping.ts index a64f289..3bec77b 100644 --- a/src/api-sdk/github/mapping.ts +++ b/src/api-sdk/github/mapping.ts @@ -1,4 +1,11 @@ -import { GitHubMembers, GitHubRepos, GitHubTeams, GitHubMembersPerTeam, GitHubReposPerTeam } from './type'; +import { + GitHubMembers, + GitHubRepos, + GitHubTeams, + GitHubMembersPerTeam, + GitHubReposPerTeam, + GitHubCollaboratorsPerRepo +} from './type'; export const reposMapping = (body: any[]): GitHubRepos[] => { return body.map((obj) => ({ @@ -41,3 +48,10 @@ export const reposPerTeamMapping = (body: any[]): GitHubReposPerTeam[] => { name: obj.name, })); }; + +export const collaboratorsPerRepoMapping = (body: any[]): GitHubCollaboratorsPerRepo[] => { + return body.map((obj) => ({ + login: obj.login, + permissions: { ...obj.permissions } + })); +}; diff --git a/src/api-sdk/github/type.ts b/src/api-sdk/github/type.ts index 0ad6539..bbea37e 100644 --- a/src/api-sdk/github/type.ts +++ b/src/api-sdk/github/type.ts @@ -29,3 +29,22 @@ export interface GitHubMembersPerTeam { export interface GitHubReposPerTeam { name: string; } + +export interface GitHubCollaboratorsPerRepo { + login: string; + permissions: { + pull: boolean; + triage: boolean; + push: boolean; + maintain: boolean; + admin: boolean; + } +} + +export interface GitHubIssueRequest { + title: string, + body: string, + milestone: number, + assignees: string[], + labels: string[] +} diff --git a/test/mock/data.mock.ts b/test/mock/data.mock.ts index 1b9e275..9423cda 100644 --- a/test/mock/data.mock.ts +++ b/test/mock/data.mock.ts @@ -29,7 +29,7 @@ export const MOCK_REPOS = [ } ]; -export const MOCK_UNMAPPED_ENTREE_REPO = [ +export const MOCK_UNMAPPED_RESPONSE_REPO = [ { name: "repo1", archived: false, @@ -54,6 +54,8 @@ export const MOCK_UNMAPPED_ENTREE_REPO = [ } ]; +export const MOCK_FULL_RESPONSE_REPO = [ ...MOCK_UNMAPPED_RESPONSE_REPO ]; + export const MOCK_REPO_FETCH_RESPONSE = { httpStatusCode: 200, resource: MOCK_REPOS @@ -74,6 +76,8 @@ export const MOCK_MEMBERS = [ } ]; +export const MOCK_FULL_RESPONSE_MEMBERS = [ ...MOCK_MEMBERS ]; + export const MOCK_MEMBER_FETCH_RESPONSE = { httpStatusCode: 200, resource: MOCK_MEMBERS.map(({ repos_url, ...rest }) => rest) @@ -131,6 +135,40 @@ export const MOCK_REPOS_PER_TEAM_RESPONSE = { resource: MOCK_REPOS_PER_TEAM }; +export const MOCK_COLLABORATORS_PER_REPO = [ + { + other: 'other', + login: 'name', + permissions: { pull: true, triage: true, push: true, maintain: true, admin: true } + }, + { + other: 'other2', + login: 'name2', + permissions: { pull: true, triage: false, push: false, maintain: false, admin: false } + }, +]; + +export const MOCK_COLLABORATORS_PER_REPO_RESPONSE = { + httpStatusCode: 200, + resource: MOCK_COLLABORATORS_PER_REPO.map(({ other, ...rest }) => rest) +}; + +export const MOCK_ISSUE_BODY = { + title: "Add member to the team", + body: "Add member Bob to the team Alice.", + assignees: ["IDC TEAM"], + milestone: 1, + labels: ["GIT"] +}; + +export const MOCK_ISSUE_RESPONSE = { + id: 1, + number: 1347, + state: "open", + title: "Add member to the team", + body: "Add member Bob to the team Alice." +}; + export const MOCK_ERROR = { error: 'Error: test error' }; diff --git a/test/unit/api-sdk/github/github.spec.ts b/test/unit/api-sdk/github/github.spec.ts index 4ee36fd..2f36a1d 100644 --- a/test/unit/api-sdk/github/github.spec.ts +++ b/test/unit/api-sdk/github/github.spec.ts @@ -11,20 +11,27 @@ import { MOCK_TEAMS, MOCK_ERROR_RESPONSE, MOCK_ERROR, - MOCK_UNMAPPED_ENTREE_REPO, + MOCK_UNMAPPED_RESPONSE_REPO, MOCK_MEMBERS_PER_TEAM, MOCK_MEMBERS_PER_TEAM_RESPONSE, MOCK_REPOS_PER_TEAM, MOCK_REPOS_PER_TEAM_RESPONSE, MOCK_403_ERROR_MSG, - MOCK_403_ERROR_RESPONSE + MOCK_403_ERROR_RESPONSE, + MOCK_COLLABORATORS_PER_REPO, + MOCK_COLLABORATORS_PER_REPO_RESPONSE, + MOCK_FULL_RESPONSE_REPO, + MOCK_FULL_RESPONSE_MEMBERS, + MOCK_ISSUE_RESPONSE, + MOCK_ISSUE_BODY } from '../../../mock/data.mock'; import { HttpResponse } from '../../../../src/http-request/type'; jest.mock('../../../../src/http-request/index', () => { return { HttpRequest: jest.fn().mockImplementation(() => ({ - httpGet: jest.fn() + httpGet: jest.fn(), + httpPost: jest.fn() })) }; }); @@ -57,6 +64,39 @@ describe('Github sdk module test suites', () => { expect(result).toEqual(MOCK_REPO_FETCH_RESPONSE); }); + test('Should return full repo data as an object', async () => { + httpRequestMock.httpGet.mockResolvedValue(createMockHttpResponse(MOCK_FULL_RESPONSE_REPO)); + + const url = 'https://api.github.com/org/test/repos'; + const result = await github.getGitHubInfo(url); + expect(result).toEqual({ + ...MOCK_REPO_FETCH_RESPONSE, + resource: MOCK_FULL_RESPONSE_REPO + }); + }); + + test('Should return full members data as an object', async () => { + httpRequestMock.httpGet.mockResolvedValue(createMockHttpResponse(MOCK_FULL_RESPONSE_MEMBERS)); + + const url = 'https://api.github.com/org/test/members'; + const result = await github.getGitHubInfo(url); + expect(result).toEqual({ + httpStatusCode: 200, + resource: MOCK_FULL_RESPONSE_MEMBERS + }); + }); + + test('Should post an issue and return the object data', async () => { + httpRequestMock.httpPost.mockResolvedValue(createMockHttpResponse(MOCK_ISSUE_RESPONSE)); + + const url = 'https://api.github.com/repos/org1/repo1/issues'; + const result = await github.postIssue(url, MOCK_ISSUE_BODY); + expect(result).toEqual({ + httpStatusCode: 200, + resource: MOCK_ISSUE_RESPONSE + }); + }); + test('Should return member data as an object', async () => { httpRequestMock.httpGet.mockResolvedValue(createMockHttpResponse(MOCK_MEMBERS)); @@ -89,6 +129,14 @@ describe('Github sdk module test suites', () => { expect(result).toEqual(MOCK_REPOS_PER_TEAM_RESPONSE); }); + test('Should return collaborators per repo data as an object', async () => { + httpRequestMock.httpGet.mockResolvedValue(createMockHttpResponse(MOCK_COLLABORATORS_PER_REPO)); + + const url = 'https://api.github.com/repos/organizations/repo/collaborators'; + const result = await github.getCollaboratorsPerRepo(url); + expect(result).toEqual(MOCK_COLLABORATORS_PER_REPO_RESPONSE); + }); + test('Should return an object with an error property', async () => { httpRequestMock.httpGet.mockResolvedValue(createMockHttpResponse(MOCK_TEAMS, 500, MOCK_ERROR)); @@ -97,8 +145,8 @@ describe('Github sdk module test suites', () => { expect(result).toEqual(MOCK_ERROR_RESPONSE); }); - test('Should ignore an unexpected entree in the object ', async () => { - httpRequestMock.httpGet.mockResolvedValue(createMockHttpResponse(MOCK_UNMAPPED_ENTREE_REPO)); + test('Should ignore an unexpected response fields in the object ', async () => { + httpRequestMock.httpGet.mockResolvedValue(createMockHttpResponse(MOCK_UNMAPPED_RESPONSE_REPO)); const url = 'https://api.github.com/users/test/repos'; const result = await github.getRepos(url);