@dxdeveloperexperience/hygie
Version:
Hygie is an easy-to-use Open-Source REST API allowing you to interact with GIT events. This NestJS API expose a set of customizable rules to automate your project's life cycle.
806 lines (679 loc) • 25.2 kB
text/typescript
import { Test, TestingModule } from '@nestjs/testing';
import { GithubService } from '../github/github.service';
import { HttpService } from '@nestjs/common';
import {
MockHttpService,
MockObservable,
MockDataAccessService,
} from '../__mocks__/mocks';
import { GitCommitStatusInfos } from '../git/gitCommitStatusInfos';
import { CommitStatusEnum } from '../webhook/utils.enum';
import {
GitIssueInfos,
IssuePRStateEnum,
GitIssuePRSearch,
IssueSortEnum,
} from '../git/gitIssueInfos';
import {
GitPRInfos,
GitCommentPRInfos,
GitMergePRInfos,
PRMethodsEnum,
} from '../git/gitPRInfos';
import { Observable, of } from 'rxjs';
import { GitFileInfos } from '../git/gitFileInfos';
import { DataAccessService } from '../data_access/dataAccess.service';
import { GitRelease } from '../git/gitRelease';
import { GitCommit } from '../git/gitCommit';
import { GitRef } from '../git/gitRef';
import { GitTag } from '../git/gitTag';
import { GitBranchCommit } from '../git/gitBranchSha';
import { logger } from '../logger/logger.service';
import { Utils } from '../utils/utils';
describe('Github Service', () => {
let app: TestingModule;
let githubService: GithubService;
let httpService: HttpService;
let dataAccessService: DataAccessService;
let gitCommitStatusInfos: GitCommitStatusInfos;
let expectedConfig;
beforeAll(async () => {
app = await Test.createTestingModule({
providers: [
{ provide: HttpService, useClass: MockHttpService },
{ provide: Observable, useClass: MockObservable },
{ provide: DataAccessService, useClass: MockDataAccessService },
GithubService,
],
}).compile();
githubService = app.get(GithubService);
httpService = app.get(HttpService);
dataAccessService = app.get(DataAccessService);
githubService.setToken('0123456789abcdef');
githubService.setUrlApi('https://api.github.com');
githubService.setRepositoryFullName('bastienterrier/test');
githubService.setConfigGitHub({
headers: {
Authorization: `token 0123456789abcdef`,
},
});
expectedConfig = {
headers: {
Authorization: `token 0123456789abcdef`,
},
};
});
beforeEach(() => {
jest.clearAllMocks();
});
describe('updateCommitStatus', () => {
it('should emit a POST request with specific params', () => {
gitCommitStatusInfos = new GitCommitStatusInfos();
gitCommitStatusInfos.commitSha = '1';
gitCommitStatusInfos.commitStatus = CommitStatusEnum.Success;
gitCommitStatusInfos.descriptionMessage = 'Well done';
gitCommitStatusInfos.targetUrl = 'https://www.zenika.com';
githubService.updateCommitStatus(gitCommitStatusInfos);
const expectedUrl = `https://api.github.com/repos/bastienterrier/test/statuses/1`;
const expectedData = {
context: process.env.APPLICATION_NAME + '/commit',
state: 'success',
target_url: 'https://www.zenika.com',
description: 'Well done',
};
expect(httpService.post).toBeCalledWith(
expectedUrl,
expectedData,
expectedConfig,
);
});
});
describe('addIssueComment', () => {
it('should emit a POST request with specific params', () => {
const gitIssueInfos = new GitIssueInfos();
gitIssueInfos.number = '1';
gitIssueInfos.comment = 'my comment';
githubService.addIssueComment(gitIssueInfos);
const expectedUrl = `https://api.github.com/repos/bastienterrier/test/issues/1/comments`;
const expectedData = {
body: 'my comment',
};
expect(httpService.post).toBeCalledWith(
expectedUrl,
expectedData,
expectedConfig,
);
});
});
describe('updateIssue', () => {
it('should emit a PATCH request with specific params', () => {
const gitIssueInfos = new GitIssueInfos();
gitIssueInfos.number = '1';
gitIssueInfos.state = IssuePRStateEnum.Close;
githubService.updateIssue(gitIssueInfos);
const expectedUrl = `https://api.github.com/repos/bastienterrier/test/issues/1`;
const expectedData = {
state: 'closed',
};
expect(httpService.patch).toBeCalledWith(
expectedUrl,
expectedData,
expectedConfig,
);
});
it('should emit a PATCH request with specific params', () => {
const gitIssueInfos = new GitIssueInfos();
gitIssueInfos.number = '1';
gitIssueInfos.state = IssuePRStateEnum.Open;
githubService.updateIssue(gitIssueInfos);
const expectedUrl = `https://api.github.com/repos/bastienterrier/test/issues/1`;
const expectedData = {
state: 'open',
};
expect(httpService.patch).toBeCalledWith(
expectedUrl,
expectedData,
expectedConfig,
);
});
});
describe('addPRComment', () => {
it('should emit a POST request with specific params', () => {
const gitCommentPRInfos = new GitCommentPRInfos();
gitCommentPRInfos.number = '1';
gitCommentPRInfos.comment = 'my comment';
githubService.addPRComment(gitCommentPRInfos);
const expectedUrl = `https://api.github.com/repos/bastienterrier/test/issues/1/comments`;
const expectedData = {
body: 'my comment',
};
expect(httpService.post).toBeCalledWith(
expectedUrl,
expectedData,
expectedConfig,
);
});
});
describe('createPullRequest', () => {
it('should emit a POST request with specific params', () => {
const gitCreatePRInfos = new GitPRInfos();
gitCreatePRInfos.title = 'my PR';
gitCreatePRInfos.description = 'my desc';
gitCreatePRInfos.source = 'develop';
gitCreatePRInfos.target = 'master';
githubService.createPullRequest(gitCreatePRInfos);
const expectedUrl = `https://api.github.com/repos/bastienterrier/test/pulls`;
const expectedData = {
title: 'my PR',
body: 'my desc',
head: 'develop',
base: 'master',
};
expect(httpService.post).toBeCalledWith(
expectedUrl,
expectedData,
expectedConfig,
);
// expect(observable.subscribe).toBeCalled();
});
});
describe('createIssue', () => {
it('should emit a POST request with specific params', async () => {
const gitIssueInfos = new GitIssueInfos();
gitIssueInfos.title = 'my new issue';
gitIssueInfos.description = 'my desc';
gitIssueInfos.assignees = ['bastienterrier'];
gitIssueInfos.labels = ['good first issue'];
httpService.post = jest.fn().mockImplementationOnce((...args) => {
return of({ data: { number: 1 } });
});
await githubService.createIssue(gitIssueInfos);
const expectedUrl = `https://api.github.com/repos/bastienterrier/test/issues`;
const expectedData = {
title: 'my new issue',
body: 'my desc',
assignees: ['bastienterrier'],
labels: ['good first issue'],
};
expect(httpService.post).toBeCalledWith(
expectedUrl,
expectedData,
expectedConfig,
);
// Restore mock
httpService.post = jest.fn().mockImplementation(() => {
return of([]);
});
});
});
describe('deleteFile', () => {
it('should emit GET and POST requests with specific params', () => {
const gitFileInfos = new GitFileInfos();
gitFileInfos.fileBranch = 'master';
gitFileInfos.filePath = 'file/to/remove.txt';
gitFileInfos.commitMessage = 'remove file/to/remove.txt';
// const firstGetReturned = Observable.create(observer => {
// observer.next({
// sha: 'e69de29bb2d1d6434b8b29ae775ad8c2e48c5391',
// });
// observer.complete();
// });
httpService.get = jest.fn().mockImplementationOnce(() => {
return of({
data: {
sha: 'tee69de29bb2d1d6434b8b29ae775ad8c2e48c5391st',
},
});
});
githubService.deleteFile(gitFileInfos).catch(err => logger.error(err));
const expectedUrl1 = `https://api.github.com/repos/bastienterrier/test/contents/file/to/remove.txt`;
const expectedUrl2 = `https://api.github.com/repos/bastienterrier/test/contents/file/to/remove.txt`;
const expectedConfig2 = JSON.parse(JSON.stringify(expectedConfig));
expectedConfig2.params = {
branch: 'master',
message: 'remove file/to/remove.txt',
sha: 'tee69de29bb2d1d6434b8b29ae775ad8c2e48c5391st',
};
expect(httpService.get).toBeCalledWith(expectedUrl1, expectedConfig);
expect(httpService.delete).toBeCalledWith(expectedUrl2, expectedConfig2);
});
});
describe('deleteBranch', () => {
it('should emit a DELETE request with specific params', () => {
githubService.deleteBranch('feature/test');
const expectedUrl = `https://api.github.com/repos/bastienterrier/test/git/refs/heads/feature%2Ftest`;
expect(httpService.delete).toBeCalledWith(expectedUrl, expectedConfig);
});
});
describe('mergePullRequest', () => {
it('should emit a PUT request with specific params', () => {
const gitMergePRInfos = new GitMergePRInfos();
gitMergePRInfos.number = 42;
gitMergePRInfos.commitTitle = 'commit title';
gitMergePRInfos.commitMessage = 'commit message';
gitMergePRInfos.method = PRMethodsEnum.Merge;
githubService.mergePullRequest(gitMergePRInfos);
const expectedUrl = `https://api.github.com/repos/bastienterrier/test/pulls/42/merge`;
const expectedData = {
commit_title: 'commit title',
commit_message: 'commit message',
merge_method: 'merge',
};
expect(httpService.put).toBeCalledWith(
expectedUrl,
expectedData,
expectedConfig,
);
});
});
describe('updatePullRequest', () => {
it('should emit a PATCH request with specific params', () => {
const gitPRInfos = new GitPRInfos();
gitPRInfos.number = 42;
gitPRInfos.title = 'pr title';
gitPRInfos.description = 'pr description';
gitPRInfos.target = 'master';
gitPRInfos.state = IssuePRStateEnum.Close;
githubService.updatePullRequest(gitPRInfos);
const expectedUrl = `https://api.github.com/repos/bastienterrier/test/pulls/42`;
const expectedData = {
title: 'pr title',
body: 'pr description',
state: 'closed',
base: 'master',
};
expect(httpService.patch).toBeCalledWith(
expectedUrl,
expectedData,
expectedConfig,
);
});
});
describe('createWebhook', () => {
it('should emit a POST request with specific params', () => {
const webhookURL: string = 'https://some.url.com';
githubService.createWebhook(webhookURL);
const expectedUrl = `https://api.github.com/repos/bastienterrier/test/hooks`;
const expectedData = {
active: true,
config: { content_type: 'json', url: 'https://some.url.com' },
events: ['*'],
name: 'web',
};
expect(httpService.post).toBeCalledWith(
expectedUrl,
expectedData,
expectedConfig,
);
});
});
describe('getIssues', () => {
it('should emit a GET request with specific params', () => {
const gitIssueSearch: GitIssuePRSearch = new GitIssuePRSearch();
gitIssueSearch.state = IssuePRStateEnum.Open;
gitIssueSearch.sort = IssueSortEnum.Asc;
httpService.get = jest.fn().mockImplementationOnce((...args) => {
return new Observable(observer => observer.next({ data: [] }));
});
githubService.getIssues(gitIssueSearch).catch(err => logger.error(err));
const expectedUrl = `https://api.github.com/repos/bastienterrier/test/issues`;
const expectedConfig2 = JSON.parse(JSON.stringify(expectedConfig));
expectedConfig2.params = {
direction: 'asc',
state: 'open',
};
expect(httpService.get).toBeCalledWith(expectedUrl, expectedConfig2);
});
});
describe('getPullRequests', () => {
it('should emit a GET request with specific params', () => {
const gitIssueSearch: GitIssuePRSearch = new GitIssuePRSearch();
gitIssueSearch.state = IssuePRStateEnum.Open;
gitIssueSearch.sort = IssueSortEnum.Asc;
httpService.get = jest.fn().mockImplementationOnce((...args) => {
return new Observable(observer => observer.next({ data: [] }));
});
githubService
.getPullRequests(gitIssueSearch)
.catch(err => logger.error(err));
const expectedUrl = `https://api.github.com/repos/bastienterrier/test/pulls`;
const expectedConfig2 = JSON.parse(JSON.stringify(expectedConfig));
expectedConfig2.params = {
direction: 'asc',
state: 'open',
};
expect(httpService.get).toBeCalledWith(expectedUrl, expectedConfig2);
});
});
describe('createRelease', () => {
it('should emit a POST request with specific params', () => {
const gitRelease = new GitRelease();
gitRelease.name = 'v0.0.1';
gitRelease.tag = 'v0.0.1';
gitRelease.description = 'this is the first release';
githubService.createRelease(gitRelease);
const expectedUrl = `https://api.github.com/repos/bastienterrier/test/releases`;
const expectedData = {
body: 'this is the first release',
tag_name: 'v0.0.1',
name: 'v0.0.1',
};
expect(httpService.post).toBeCalledWith(
expectedUrl,
expectedData,
expectedConfig,
);
});
});
describe('getTree', () => {
it('should emit a GET request with specific params', () => {
httpService.get = jest.fn().mockImplementationOnce(() => {
return of({
data: [{ name: 'path', sha: 'sha' }],
});
});
githubService.getTree('your/folder/path').catch(err => logger.error(err));
const customConfig = JSON.parse(JSON.stringify(expectedConfig));
customConfig.params = { ref: 'master' };
const expectedUrl = `https://api.github.com/repos/bastienterrier/test/contents/your/folder`;
expect(httpService.get).toBeCalledWith(expectedUrl, customConfig);
});
});
describe('getLastCommit', () => {
it('should emit a GET request with specific params', async () => {
httpService.get = jest.fn().mockImplementationOnce((...args) => {
return of({ data: { object: { sha: '0123456789abcdef' } } });
});
await githubService.getLastCommit('develop');
const expectedUrl = `https://api.github.com/repos/bastienterrier/test/git/refs/heads/develop`;
expect(httpService.get).toBeCalledWith(expectedUrl, expectedConfig);
});
});
describe('createCommit', () => {
it('should emit a POST request with specific params', () => {
const gitCommit = new GitCommit();
gitCommit.message = 'commit message';
gitCommit.parents = ['1'];
gitCommit.tree = 'treesha';
httpService.post = jest.fn().mockImplementationOnce((...args) => {
return new Observable(observer => observer.next({ data: [] }));
});
githubService.createCommit(gitCommit).catch(err => logger.error(err));
const expectedUrl = `https://api.github.com/repos/bastienterrier/test/git/commits`;
const expectedData = {
message: 'commit message',
parents: ['1'],
tree: 'treesha',
};
expect(httpService.post).toBeCalledWith(
expectedUrl,
expectedData,
expectedConfig,
);
});
});
describe('updateRef', () => {
it('should emit a PATCH request with specific params', () => {
const gitRef = new GitRef();
gitRef.refName = 'refs/heads/develop';
gitRef.sha = 'sha';
githubService.updateRef(gitRef);
const expectedUrl = `https://api.github.com/repos/bastienterrier/test/git/refs/heads/develop`;
const expectedData = {
sha: 'sha',
};
expect(httpService.patch).toBeCalledWith(
expectedUrl,
expectedData,
expectedConfig,
);
});
});
describe('createRef', () => {
it('should emit a POST request with specific params', () => {
const gitRef = new GitRef();
gitRef.refName = 'refs/heads/develop';
gitRef.sha = 'sha';
httpService.post = jest.fn().mockImplementationOnce((...args) => {
return new Observable(observer => observer.next({ data: [] }));
});
githubService.createRef(gitRef);
const expectedUrl = `https://api.github.com/repos/bastienterrier/test/git/refs`;
const expectedData = {
sha: 'sha',
ref: 'refs/heads/develop',
};
expect(httpService.post).toBeCalledWith(
expectedUrl,
expectedData,
expectedConfig,
);
});
});
describe('createTag', () => {
it('should emit a POST request with specific params', () => {
httpService.post = jest.fn().mockImplementationOnce((...args) => {
return of({ data: { sha: 'sha' } });
});
const gitTag = new GitTag();
gitTag.sha = '1';
gitTag.tag = 'v0.0.1';
gitTag.type = 'commit';
gitTag.message = 'new tag';
githubService.createTag(gitTag).catch(err => logger.error(err));
const expectedUrl = `https://api.github.com/repos/bastienterrier/test/git/tags`;
const expectedData = {
tag: gitTag.tag,
message: gitTag.message,
object: gitTag.sha,
type: gitTag.type,
};
expect(httpService.post).toBeCalledWith(
expectedUrl,
expectedData,
expectedConfig,
);
});
});
describe('getLastBranchesCommitSha', () => {
it('should emit a GET request with specific params', async () => {
httpService.get = jest.fn().mockImplementationOnce((...args) => {
return of({
data: [
{
name: 'develop',
commit: {
sha: '9c036a26fc5c26b68025d2b7d7fcf757d4dec36e',
url:
'https://api.github.com/repos/zenika-open-source/hygie/commits/9c036a26fc5c26b68025d2b7d7fcf757d4dec36e',
},
protected: false,
protection: {
enabled: false,
required_status_checks: {
enforcement_level: 'off',
contexts: [],
},
},
protection_url:
'https://api.github.com/repos/zenika-open-source/hygie/branches/develop/protection',
},
{
name: 'gh-pages',
commit: {
sha: 'e82fef381b423754a2ca783e18dfb1eeac6aeec3',
url:
'https://api.github.com/repos/zenika-open-source/hygie/commits/e82fef381b423754a2ca783e18dfb1eeac6aeec3',
},
protected: false,
protection: {
enabled: false,
required_status_checks: {
enforcement_level: 'off',
contexts: [],
},
},
protection_url:
'https://api.github.com/repos/zenika-open-source/hygie/branches/gh-pages/protection',
},
{
name: 'master',
commit: {
sha: 'eeaa4df1b847cd53378c0fcb457b83bad5224738',
url:
'https://api.github.com/repos/zenika-open-source/hygie/commits/eeaa4df1b847cd53378c0fcb457b83bad5224738',
},
protected: false,
protection: {
enabled: false,
required_status_checks: {
enforcement_level: 'off',
contexts: [],
},
},
protection_url:
'https://api.github.com/repos/zenika-open-source/hygie/branches/master/protection',
},
{
name: 'renovate/express-4.x',
commit: {
sha: '31ed1b43b7f80c4a58a9b20f173f72b2b369c19f',
url:
'https://api.github.com/repos/zenika-open-source/hygie/commits/31ed1b43b7f80c4a58a9b20f173f72b2b369c19f',
},
protected: false,
protection: {
enabled: false,
required_status_checks: {
enforcement_level: 'off',
contexts: [],
},
},
protection_url:
'https://api.github.com/repos/zenika-open-source/hygie/branches/renovate/express-4.x/protection',
},
{
name: 'renovate/googleapis-40.x',
commit: {
sha: 'c93f1779441198daef276a50a274d16a5a83dd4e',
url:
'https://api.github.com/repos/zenika-open-source/hygie/commits/c93f1779441198daef276a50a274d16a5a83dd4e',
},
protected: false,
protection: {
enabled: false,
required_status_checks: {
enforcement_level: 'off',
contexts: [],
},
},
protection_url:
'https://api.github.com/repos/zenika-open-source/hygie/branches/renovate/googleapis-40.x/protection',
},
{
name: 'renovate/nest-monorepo',
commit: {
sha: '9d968cc538b9796ec61e78f4055814028e816858',
url:
'https://api.github.com/repos/zenika-open-source/hygie/commits/9d968cc538b9796ec61e78f4055814028e816858',
},
protected: false,
protection: {
enabled: false,
required_status_checks: {
enforcement_level: 'off',
contexts: [],
},
},
protection_url:
'https://api.github.com/repos/zenika-open-source/hygie/branches/renovate/nest-monorepo/protection',
},
],
});
});
const result: GitBranchCommit[] = await githubService.getLastBranchesCommitSha();
const expectedUrl = `https://api.github.com/repos/bastienterrier/test/branches`;
expect(httpService.get).toBeCalledWith(expectedUrl, expectedConfig);
expect(result).toEqual([
{
commitSha: '9c036a26fc5c26b68025d2b7d7fcf757d4dec36e',
branch: 'develop',
},
{
commitSha: 'e82fef381b423754a2ca783e18dfb1eeac6aeec3',
branch: 'gh-pages',
},
{
commitSha: 'eeaa4df1b847cd53378c0fcb457b83bad5224738',
branch: 'master',
},
{
commitSha: '31ed1b43b7f80c4a58a9b20f173f72b2b369c19f',
branch: 'renovate/express-4.x',
},
{
commitSha: 'c93f1779441198daef276a50a274d16a5a83dd4e',
branch: 'renovate/googleapis-40.x',
},
{
commitSha: '9d968cc538b9796ec61e78f4055814028e816858',
branch: 'renovate/nest-monorepo',
},
]);
});
});
// TESTS BEFORE
describe('setToken', () => {
it('should set the token', () => {
githubService.setToken('azertyuiop');
expect(githubService.token).toBe('azertyuiop');
});
});
describe('setUrlApi', () => {
it('should set the url of the API', () => {
githubService.setUrlApi('https://githubapi.com');
expect(githubService.urlApi).toBe('https://githubapi.com');
});
});
describe('setRepositoryFullName', () => {
it('should set the repositoryFullName', () => {
githubService.setRepositoryFullName('some/repo');
expect(githubService.repositoryFullName).toBe('some/repo');
});
});
describe('setConfigGitHub', () => {
it('should set the config header', () => {
githubService.setConfigGitHub({
headers: {
Authorization: 'token azertyuiop',
},
});
expect(githubService.configGitHub).toEqual({
headers: {
Authorization: 'token azertyuiop',
},
});
});
});
describe('setEnvironmentVariables', () => {
it('should set the token and urlApi', async () => {
process.env.ENCRYPTION_KEY = 'somekey';
dataAccessService.readEnv = jest.fn().mockResolvedValue({
gitApi: 'https://mygithubapi.com',
gitToken: Utils.encryptValue('githubToken'),
});
githubService.setToken = jest.fn().mockName('setTokenGithub');
githubService.setUrlApi = jest.fn().mockName('setUrlApiGithub');
githubService.setRepositoryFullName = jest
.fn()
.mockName('setRepositoryFullNameGithub');
await githubService.setEnvironmentVariables(
dataAccessService,
'some/repo',
);
expect(githubService.setToken).toBeCalledWith('githubToken');
expect(githubService.setUrlApi).toBeCalledWith('https://mygithubapi.com');
expect(githubService.setRepositoryFullName).toBeCalledWith('some/repo');
});
});
});