2017-02-06 13:40:28 -05:00
|
|
|
// Imports
|
|
|
|
import {EventEmitter} from 'events';
|
|
|
|
import {ClientRequest, IncomingMessage} from 'http';
|
|
|
|
import * as https from 'https';
|
|
|
|
import {GithubApi} from '../../lib/common/github-api';
|
|
|
|
|
|
|
|
// Tests
|
|
|
|
describe('GithubApi', () => {
|
|
|
|
let api: GithubApi;
|
|
|
|
|
|
|
|
beforeEach(() => api = new GithubApi('repo/slug', '12345'));
|
|
|
|
|
|
|
|
|
|
|
|
describe('constructor()', () => {
|
|
|
|
|
2017-02-27 04:36:12 -05:00
|
|
|
it('should throw if \'repoSlug\' is not defined', () => {
|
2017-02-06 13:40:28 -05:00
|
|
|
expect(() => new GithubApi('', '12345')).toThrowError('Missing required parameter \'repoSlug\'!');
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
it('should log a warning if \'githubToken\' is not defined or empty', () => {
|
|
|
|
const warningMessage = 'No GitHub access-token specified. Requests will be unauthenticated.';
|
|
|
|
const consoleWarnSpy = spyOn(console, 'warn');
|
|
|
|
|
|
|
|
/* tslint:disable: no-unused-new */
|
|
|
|
new GithubApi('repo/slug');
|
|
|
|
new GithubApi('repo/slug', '');
|
|
|
|
/* tslint:enable: no-unused-new */
|
|
|
|
|
|
|
|
expect(consoleWarnSpy).toHaveBeenCalledTimes(2);
|
|
|
|
expect(consoleWarnSpy.calls.argsFor(0)[0]).toBe(warningMessage);
|
|
|
|
expect(consoleWarnSpy.calls.argsFor(1)[0]).toBe(warningMessage);
|
|
|
|
});
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
describe('get()', () => {
|
|
|
|
let apiBuildPathSpy: jasmine.Spy;
|
|
|
|
let apiRequestSpy: jasmine.Spy;
|
|
|
|
|
|
|
|
beforeEach(() => {
|
|
|
|
apiBuildPathSpy = spyOn(api as any, 'buildPath');
|
|
|
|
apiRequestSpy = spyOn(api as any, 'request');
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
it('should call \'buildPath()\' with the pathname and params', () => {
|
|
|
|
api.get('/foo', {bar: 'baz'});
|
|
|
|
|
|
|
|
expect(apiBuildPathSpy).toHaveBeenCalled();
|
|
|
|
expect(apiBuildPathSpy.calls.argsFor(0)).toEqual(['/foo', {bar: 'baz'}]);
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
it('should call \'request()\' with the correct method', () => {
|
|
|
|
api.get('/foo');
|
|
|
|
|
|
|
|
expect(apiRequestSpy).toHaveBeenCalled();
|
|
|
|
expect(apiRequestSpy.calls.argsFor(0)[0]).toBe('get');
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
it('should call \'request()\' with the correct path', () => {
|
|
|
|
apiBuildPathSpy.and.returnValue('/foo/bar');
|
|
|
|
api.get('foo');
|
|
|
|
|
|
|
|
expect(apiRequestSpy).toHaveBeenCalled();
|
|
|
|
expect(apiRequestSpy.calls.argsFor(0)[1]).toBe('/foo/bar');
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
it('should not pass data to \'request()\'', () => {
|
|
|
|
(api.get as Function)('foo', {}, {});
|
|
|
|
|
|
|
|
expect(apiRequestSpy).toHaveBeenCalled();
|
|
|
|
expect(apiRequestSpy.calls.argsFor(0)[2]).toBeUndefined();
|
|
|
|
});
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
describe('post()', () => {
|
|
|
|
let apiBuildPathSpy: jasmine.Spy;
|
|
|
|
let apiRequestSpy: jasmine.Spy;
|
|
|
|
|
|
|
|
beforeEach(() => {
|
|
|
|
apiBuildPathSpy = spyOn(api as any, 'buildPath');
|
|
|
|
apiRequestSpy = spyOn(api as any, 'request');
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
it('should call \'buildPath()\' with the pathname and params', () => {
|
|
|
|
api.post('/foo', {bar: 'baz'});
|
|
|
|
|
|
|
|
expect(apiBuildPathSpy).toHaveBeenCalled();
|
|
|
|
expect(apiBuildPathSpy.calls.argsFor(0)).toEqual(['/foo', {bar: 'baz'}]);
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
it('should call \'request()\' with the correct method', () => {
|
|
|
|
api.post('/foo');
|
|
|
|
|
|
|
|
expect(apiRequestSpy).toHaveBeenCalled();
|
|
|
|
expect(apiRequestSpy.calls.argsFor(0)[0]).toBe('post');
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
it('should call \'request()\' with the correct path', () => {
|
|
|
|
apiBuildPathSpy.and.returnValue('/foo/bar');
|
|
|
|
api.post('/foo');
|
|
|
|
|
|
|
|
expect(apiRequestSpy).toHaveBeenCalled();
|
|
|
|
expect(apiRequestSpy.calls.argsFor(0)[1]).toBe('/foo/bar');
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
it('should pass the data to \'request()\'', () => {
|
|
|
|
api.post('/foo', {}, {bar: 'baz'});
|
|
|
|
|
|
|
|
expect(apiRequestSpy).toHaveBeenCalled();
|
|
|
|
expect(apiRequestSpy.calls.argsFor(0)[2]).toEqual({bar: 'baz'});
|
|
|
|
});
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// Protected methods
|
|
|
|
|
|
|
|
describe('buildPath()', () => {
|
|
|
|
|
|
|
|
it('should return the pathname if no params', () => {
|
|
|
|
expect((api as any).buildPath('/foo')).toBe('/foo');
|
|
|
|
expect((api as any).buildPath('/foo', undefined)).toBe('/foo');
|
|
|
|
expect((api as any).buildPath('/foo', null)).toBe('/foo');
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
it('should append the params to the pathname', () => {
|
|
|
|
expect((api as any).buildPath('/foo', {bar: 'baz'})).toBe('/foo?bar=baz');
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
it('should join the params with \'&\'', () => {
|
|
|
|
expect((api as any).buildPath('/foo', {bar: 1, baz: 2})).toBe('/foo?bar=1&baz=2');
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
it('should ignore undefined/null params', () => {
|
|
|
|
expect((api as any).buildPath('/foo', {bar: undefined, baz: null})).toBe('/foo');
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
it('should encode param values as URI components', () => {
|
|
|
|
expect((api as any).buildPath('/foo', {bar: 'b a&z'})).toBe('/foo?bar=b%20a%26z');
|
|
|
|
});
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
describe('request()', () => {
|
|
|
|
let httpsRequestSpy: jasmine.Spy;
|
|
|
|
let latestRequest: ClientRequest;
|
|
|
|
|
|
|
|
beforeEach(() => {
|
|
|
|
const originalRequest = https.request;
|
|
|
|
|
|
|
|
httpsRequestSpy = spyOn(https, 'request').and.callFake((...args: any[]) => {
|
|
|
|
latestRequest = originalRequest.apply(https, args);
|
|
|
|
|
|
|
|
spyOn(latestRequest, 'on').and.callThrough();
|
|
|
|
spyOn(latestRequest, 'end');
|
|
|
|
|
|
|
|
return latestRequest;
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
it('should return a promise', () => {
|
|
|
|
expect((api as any).request()).toEqual(jasmine.any(Promise));
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
it('should call \'https.request()\' with the correct options', () => {
|
|
|
|
(api as any).request('method', 'path');
|
|
|
|
|
|
|
|
expect(httpsRequestSpy).toHaveBeenCalled();
|
|
|
|
expect(httpsRequestSpy.calls.argsFor(0)[0]).toEqual(jasmine.objectContaining({
|
|
|
|
headers: jasmine.objectContaining({
|
|
|
|
'User-Agent': `Node/${process.versions.node}`,
|
|
|
|
}),
|
|
|
|
host: 'api.github.com',
|
|
|
|
method: 'method',
|
|
|
|
path: 'path',
|
|
|
|
}));
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
it('should call specify an \'Authorization\' header if \'githubToken\' is present', () => {
|
|
|
|
(api as any).request('method', 'path');
|
|
|
|
|
|
|
|
expect(httpsRequestSpy).toHaveBeenCalled();
|
|
|
|
expect(httpsRequestSpy.calls.argsFor(0)[0].headers).toEqual(jasmine.objectContaining({
|
|
|
|
Authorization: 'token 12345',
|
|
|
|
}));
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
it('should reject on request error', done => {
|
|
|
|
(api as any).request('method', 'path').catch((err: any) => {
|
|
|
|
expect(err).toBe('Test');
|
|
|
|
done();
|
|
|
|
});
|
|
|
|
|
|
|
|
latestRequest.emit('error', 'Test');
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
it('should send the request (i.e. call \'end()\')', () => {
|
|
|
|
(api as any).request('method', 'path');
|
|
|
|
expect(latestRequest.end).toHaveBeenCalled();
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
it('should \'JSON.stringify\' and send the data along with the request', () => {
|
|
|
|
(api as any).request('method', 'path');
|
|
|
|
expect(latestRequest.end).toHaveBeenCalledWith(null);
|
|
|
|
|
|
|
|
(api as any).request('method', 'path', {key: 'value'});
|
|
|
|
expect(latestRequest.end).toHaveBeenCalledWith('{"key":"value"}');
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
describe('onResponse', () => {
|
|
|
|
let promise: Promise<void>;
|
|
|
|
let respond: (statusCode: number) => IncomingMessage;
|
|
|
|
|
|
|
|
beforeEach(() => {
|
|
|
|
promise = (api as any).request('method', 'path');
|
|
|
|
|
|
|
|
respond = (statusCode: number) => {
|
|
|
|
const mockResponse = new EventEmitter() as IncomingMessage;
|
|
|
|
mockResponse.statusCode = statusCode;
|
|
|
|
|
|
|
|
const onResponse = httpsRequestSpy.calls.argsFor(0)[1];
|
|
|
|
onResponse(mockResponse);
|
|
|
|
|
|
|
|
return mockResponse;
|
|
|
|
};
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
it('should reject on response error', done => {
|
|
|
|
promise.catch(err => {
|
|
|
|
expect(err).toBe('Test');
|
|
|
|
done();
|
|
|
|
});
|
|
|
|
|
|
|
|
const res = respond(200);
|
|
|
|
res.emit('error', 'Test');
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
it('should reject if returned statusCode is <200', done => {
|
|
|
|
promise.catch(err => {
|
|
|
|
expect(err).toContain('failed');
|
|
|
|
expect(err).toContain('status: 199');
|
|
|
|
done();
|
|
|
|
});
|
|
|
|
|
|
|
|
const res = respond(199);
|
|
|
|
res.emit('end');
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
it('should reject if returned statusCode is >=400', done => {
|
|
|
|
promise.catch(err => {
|
|
|
|
expect(err).toContain('failed');
|
|
|
|
expect(err).toContain('status: 400');
|
|
|
|
done();
|
|
|
|
});
|
|
|
|
|
|
|
|
const res = respond(400);
|
|
|
|
res.emit('end');
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
it('should include the response text in the rejection message', done => {
|
|
|
|
promise.catch(err => {
|
|
|
|
expect(err).toContain('Test');
|
|
|
|
done();
|
|
|
|
});
|
|
|
|
|
|
|
|
const res = respond(500);
|
|
|
|
res.emit('data', 'Test');
|
|
|
|
res.emit('end');
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
it('should resolve if returned statusCode is <=200 <400', done => {
|
|
|
|
promise.then(done);
|
|
|
|
|
|
|
|
const res = respond(200);
|
|
|
|
res.emit('data', '{}');
|
|
|
|
res.emit('end');
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
it('should resolve with the response text \'JSON.parsed\'', done => {
|
|
|
|
promise.then(data => {
|
|
|
|
expect(data).toEqual({foo: 'bar'});
|
|
|
|
done();
|
|
|
|
});
|
|
|
|
|
|
|
|
const res = respond(300);
|
|
|
|
res.emit('data', '{"foo":"bar"}');
|
|
|
|
res.emit('end');
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
it('should collect and concatenate the whole response text', done => {
|
|
|
|
promise.then(data => {
|
|
|
|
expect(data).toEqual({foo: 'bar', baz: 'qux'});
|
|
|
|
done();
|
|
|
|
});
|
|
|
|
|
|
|
|
const res = respond(300);
|
|
|
|
res.emit('data', '{"foo":');
|
|
|
|
res.emit('data', '"bar","baz"');
|
|
|
|
res.emit('data', ':"qux"}');
|
|
|
|
res.emit('end');
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
it('should reject if the response text is malformed JSON', done => {
|
|
|
|
promise.catch(err => {
|
|
|
|
expect(err).toEqual(jasmine.any(SyntaxError));
|
|
|
|
done();
|
|
|
|
});
|
|
|
|
|
|
|
|
const res = respond(300);
|
|
|
|
res.emit('data', '}');
|
|
|
|
res.emit('end');
|
|
|
|
});
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
});
|