angular-cn/aio/aio-builds-setup/dockerbuild/scripts-js/test/upload-server/upload-server-factory.spec.ts

263 lines
8.5 KiB
TypeScript

// Imports
import * as express from 'express';
import * as http from 'http';
import * as supertest from 'supertest';
import {BuildCreator} from '../../lib/upload-server/build-creator';
import {CreatedBuildEvent} from '../../lib/upload-server/build-events';
import {uploadServerFactory as usf} from '../../lib/upload-server/upload-server-factory';
// Tests
describe('uploadServerFactory', () => {
describe('create()', () => {
let usfCreateMiddlewareSpy: jasmine.Spy;
beforeEach(() => {
usfCreateMiddlewareSpy = spyOn(usf as any, 'createMiddleware').and.callThrough();
});
it('should throw if \'buildsDir\' is empty', () => {
expect(() => usf.create('')).toThrowError('Missing or empty required parameter \'buildsDir\'!');
});
it('should return an http.Server', () => {
const httpCreateServerSpy = spyOn(http, 'createServer').and.callThrough();
const server = usf.create('builds/dir');
expect(server).toBe(httpCreateServerSpy.calls.mostRecent().returnValue);
});
it('should create and use an appropriate middleware', () => {
const httpCreateServerSpy = spyOn(http, 'createServer').and.callThrough();
usf.create('builds/dir');
const middleware: express.Express = usfCreateMiddlewareSpy.calls.mostRecent().returnValue;
expect(usfCreateMiddlewareSpy).toHaveBeenCalledWith(jasmine.any(BuildCreator));
expect(httpCreateServerSpy).toHaveBeenCalledWith(middleware);
});
it('should pass \'buildsDir\' to the created BuildCreator', () => {
usf.create('builds/dir');
const buildCreator: BuildCreator = usfCreateMiddlewareSpy.calls.argsFor(0)[0];
expect((buildCreator as any).buildsDir).toBe('builds/dir');
});
it('should pass CreatedBuildEvents emitted on BuildCreator through to the server', done => {
const server = usf.create('builds/dir');
const buildCreator: BuildCreator = usfCreateMiddlewareSpy.calls.argsFor(0)[0];
const evt = new CreatedBuildEvent(42, 'foo');
server.on(CreatedBuildEvent.type, (data: CreatedBuildEvent) => {
expect(data).toBe(evt);
done();
});
buildCreator.emit(CreatedBuildEvent.type, evt);
});
it('should log the server address info on \'listening\'', () => {
const consoleInfoSpy = spyOn(console, 'info');
const server = usf.create('builds/dir');
server.address = () => ({address: 'foo', family: '', port: 1337});
expect(consoleInfoSpy).not.toHaveBeenCalled();
server.emit('listening');
expect(consoleInfoSpy).toHaveBeenCalledWith('Up and running (and listening on foo:1337)...');
});
});
// Protected methods
describe('createMiddleware()', () => {
let buildCreator: BuildCreator;
let agent: supertest.SuperTest<supertest.Test>;
// Helpers
const promisifyRequest = (req: supertest.Request) =>
new Promise((resolve, reject) => req.end(err => err ? reject(err) : resolve()));
const verifyRequests = (reqs: supertest.Request[], done: jasmine.DoneFn) =>
Promise.all(reqs.map(promisifyRequest)).then(done, done.fail);
beforeEach(() => {
buildCreator = new BuildCreator('builds/dir');
agent = supertest.agent((usf as any).createMiddleware(buildCreator));
spyOn(console, 'error');
});
describe('GET /create-build/<pr>/<sha>', () => {
const pr = '9';
const sha = '9'.repeat(40);
let buildCreatorCreateSpy: jasmine.Spy;
let deferred: {resolve: Function, reject: Function};
beforeEach(() => {
const promise = new Promise((resolve, reject) => deferred = {resolve, reject});
promise.catch(() => null); // Avoid "unhandled rejection" warnings.
buildCreatorCreateSpy = spyOn(buildCreator, 'create').and.returnValue(promise);
});
it('should respond with 405 for non-GET requests', done => {
verifyRequests([
agent.put(`/create-build/${pr}/${sha}`).expect(405),
agent.post(`/create-build/${pr}/${sha}`).expect(405),
agent.patch(`/create-build/${pr}/${sha}`).expect(405),
agent.delete(`/create-build/${pr}/${sha}`).expect(405),
], done);
});
it('should respond with 400 for requests without an \'X-FILE\' header', done => {
const url = `/create-build/${pr}/${sha}`;
const responseBody = `Missing or empty 'X-FILE' header in request: GET ${url}`;
verifyRequests([
agent.get(url).expect(400, responseBody),
agent.get(url).field('X-FILE', '').expect(400, responseBody),
], done);
});
it('should respond with 404 for unknown paths', done => {
verifyRequests([
agent.get(`/foo/create-build/${pr}/${sha}`).expect(404),
agent.get(`/foo-create-build/${pr}/${sha}`).expect(404),
agent.get(`/fooncreate-build/${pr}/${sha}`).expect(404),
agent.get(`/create-build/foo/${pr}/${sha}`).expect(404),
agent.get(`/create-build-foo/${pr}/${sha}`).expect(404),
agent.get(`/create-buildnfoo/${pr}/${sha}`).expect(404),
agent.get(`/create-build/pr${pr}/${sha}`).expect(404),
agent.get(`/create-build/${pr}/${sha}42`).expect(404),
], done);
});
it('should propagate errors from BuildCreator', done => {
const req = agent.
get(`/create-build/${pr}/${sha}`).
set('X-FILE', 'foo').
expect(500, 'Test');
verifyRequests([req], done);
deferred.reject('Test');
});
it('should respond with 201 on successful upload', done => {
const req = agent.
get(`/create-build/${pr}/${sha}`).
set('X-FILE', 'foo').
expect(201, http.STATUS_CODES[201]);
verifyRequests([req], done);
deferred.resolve();
});
it('should call \'BuildCreator#create()\' with appropriate arguments', done => {
promisifyRequest(agent.get(`/create-build/${pr}/${sha}`).set('X-FILE', 'foo').expect(201)).
then(() => expect(buildCreatorCreateSpy).toHaveBeenCalledWith(pr, sha, 'foo')).
then(done, done.fail);
deferred.resolve();
});
it('should reject PRs with leading zeros', done => {
verifyRequests([agent.get(`/create-build/0${pr}/${sha}`).expect(404)], done);
});
it('should accept SHAs with leading zeros (but not ignore them)', done => {
const sha40 = '0'.repeat(40);
const sha41 = `0${sha40}`;
Promise.all([
promisifyRequest(agent.get(`/create-build/${pr}/${sha41}`).expect(404)),
promisifyRequest(agent.get(`/create-build/${pr}/${sha40}`).set('X-FILE', 'foo')).
then(() => expect(buildCreatorCreateSpy).toHaveBeenCalledWith(pr, sha40, 'foo')),
]).then(done, done.fail);
deferred.resolve();
});
});
describe('GET /health-check', () => {
it('should respond with 200', done => {
verifyRequests([
agent.get('/health-check').expect(200),
agent.get('/health-check/').expect(200),
], done);
});
it('should respond with 405 for non-GET requests', done => {
verifyRequests([
agent.put('/health-check').expect(405),
agent.post('/health-check').expect(405),
agent.patch('/health-check').expect(405),
agent.delete('/health-check').expect(405),
], done);
});
it('should respond with 404 if the path does not match exactly', done => {
verifyRequests([
agent.get('/health-check/foo').expect(404),
agent.get('/health-check-foo').expect(404),
agent.get('/health-checknfoo').expect(404),
agent.get('/foo/health-check').expect(404),
agent.get('/foo-health-check').expect(404),
agent.get('/foonhealth-check').expect(404),
], done);
});
});
describe('GET *', () => {
it('should respond with 404', done => {
const responseBody = 'Unknown resource in request: GET /some/url';
verifyRequests([agent.get('/some/url').expect(404, responseBody)], done);
});
});
describe('ALL *', () => {
it('should respond with 405', done => {
const responseFor = (method: string) => `Unsupported method in request: ${method.toUpperCase()} /some/url`;
verifyRequests([
agent.put('/some/url').expect(405, responseFor('put')),
agent.post('/some/url').expect(405, responseFor('post')),
agent.patch('/some/url').expect(405, responseFor('patch')),
agent.delete('/some/url').expect(405, responseFor('delete')),
], done);
});
});
});
});