You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
448 lines
13 KiB
448 lines
13 KiB
'use strict';
|
|
|
|
const chai = require('chai');
|
|
const sinon = require('sinon');
|
|
const mockery = require('mockery');
|
|
const Serverless = require('serverless');
|
|
const makeWebpackMock = require('./webpack.mock');
|
|
const makeExpressMock = require('./express.mock');
|
|
chai.use(require('sinon-chai'));
|
|
const expect = chai.expect;
|
|
|
|
describe('serve', () => {
|
|
let webpackMock;
|
|
let expressMock;
|
|
let baseModule;
|
|
let module;
|
|
let serverless;
|
|
|
|
before(() => {
|
|
mockery.enable({ warnOnUnregistered: false });
|
|
webpackMock = makeWebpackMock();
|
|
expressMock = makeExpressMock();
|
|
mockery.registerMock('webpack', webpackMock);
|
|
mockery.registerMock('express', expressMock);
|
|
baseModule = require('../lib/serve');
|
|
Object.freeze(baseModule);
|
|
});
|
|
|
|
after(() => {
|
|
mockery.disable();
|
|
mockery.deregisterAll();
|
|
});
|
|
|
|
beforeEach(() => {
|
|
serverless = new Serverless();
|
|
serverless.cli = {
|
|
log: sinon.spy(),
|
|
consoleLog: sinon.spy(),
|
|
};
|
|
webpackMock._resetSpies();
|
|
expressMock._resetSpies();
|
|
module = Object.assign({
|
|
serverless,
|
|
options: {},
|
|
}, baseModule);
|
|
});
|
|
|
|
describe('_handlerBase', () => {
|
|
it('should return an express handler', () => {
|
|
const testFuncConf = {
|
|
id: 'testFuncId',
|
|
handerFunc: sinon.spy(),
|
|
};
|
|
const handler = module._handlerBase(testFuncConf);
|
|
expect(handler).to.be.a('function');
|
|
});
|
|
|
|
it('should send a 200 express response if successful', () => {
|
|
const testHandlerResp = 'testHandlerResp';
|
|
const testHandlerFunc = sinon.spy((ev, ct, cb) => {
|
|
cb(null, testHandlerResp);
|
|
});
|
|
const testFuncConf = {
|
|
id: 'testFuncId',
|
|
handlerFunc: testHandlerFunc,
|
|
};
|
|
const testHttpEvent = {
|
|
integration: 'lambda'
|
|
}
|
|
const testReq = {
|
|
method: 'testmethod',
|
|
headers: 'testheaders',
|
|
body: 'testbody',
|
|
params: 'testparams',
|
|
query: 'testquery',
|
|
};
|
|
const testRes = {
|
|
send: sinon.spy(),
|
|
};
|
|
testRes.status = sinon.stub().returns(testRes);
|
|
module.getContext = sinon.stub().returns('testContext');
|
|
const handler = module._handlerBase(testFuncConf, testHttpEvent);
|
|
handler(testReq, testRes);
|
|
expect(testRes.status).to.have.been.calledWith(200);
|
|
expect(testRes.send).to.have.been.calledWith(testHandlerResp);
|
|
expect(testHandlerFunc).to.have.been.calledWith(
|
|
{
|
|
body: 'testbody',
|
|
headers: 'testheaders',
|
|
method: 'testmethod',
|
|
path: 'testparams',
|
|
query: 'testquery',
|
|
},
|
|
'testContext'
|
|
);
|
|
});
|
|
|
|
it('should send a 500 express response if fails', () => {
|
|
const testHandlerErr = 'testHandlerErr';
|
|
const testHandlerFunc = sinon.spy((ev, ct, cb) => {
|
|
cb(testHandlerErr);
|
|
});
|
|
const testFuncConf = {
|
|
id: 'testFuncId',
|
|
handlerFunc: testHandlerFunc,
|
|
};
|
|
const testRes = {
|
|
send: sinon.spy(),
|
|
};
|
|
testRes.status = sinon.stub().returns(testRes);
|
|
module.getContext = sinon.stub().returns('testContext');
|
|
const handler = module._handlerBase(testFuncConf);
|
|
handler({}, testRes);
|
|
expect(testRes.status).to.have.been.calledWith(500);
|
|
expect(testRes.send).to.have.been.calledWith(testHandlerErr);
|
|
});
|
|
|
|
it('handles lambda-proxy integration for request and response', () => {
|
|
const testHandlerResp = { statusCode: 200, body: 'testHandlerResp' };
|
|
const testHandlerFunc = sinon.spy((ev, ct, cb) => {
|
|
cb(null, testHandlerResp);
|
|
});
|
|
const testFuncConf = {
|
|
id: 'testFuncId',
|
|
handlerFunc: testHandlerFunc,
|
|
};
|
|
const testHttpEvent = {}
|
|
const testReq = {
|
|
method: 'testmethod',
|
|
headers: 'testheaders',
|
|
body: 'testbody',
|
|
params: 'testparams',
|
|
query: 'testquery',
|
|
};
|
|
const testRes = {
|
|
send: sinon.spy(),
|
|
};
|
|
testRes.status = sinon.stub().returns(testRes);
|
|
module.getContext = sinon.stub().returns('testContext');
|
|
const handler = module._handlerBase(testFuncConf, testHttpEvent);
|
|
handler(testReq, testRes);
|
|
expect(testRes.status).to.have.been.calledWith(testHandlerResp.statusCode);
|
|
expect(testRes.send).to.have.been.calledWith(testHandlerResp.body);
|
|
expect(testHandlerFunc).to.have.been.calledWith(
|
|
{
|
|
body: 'testbody',
|
|
headers: 'testheaders',
|
|
method: 'testmethod',
|
|
pathParameters: 'testparams',
|
|
queryStringParameters: 'testquery',
|
|
},
|
|
'testContext'
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('_optionsHandler', () => {
|
|
it('should send a 200 express response', () => {
|
|
const testRes = {
|
|
sendStatus: sinon.spy(),
|
|
};
|
|
const handler = module._optionsHandler;
|
|
handler({}, testRes);
|
|
expect(testRes.sendStatus).to.have.been.calledWith(200);
|
|
});
|
|
});
|
|
|
|
describe('_handlerAddCors', () => {
|
|
it('should retun an express handler', () => {
|
|
const res = module._handlerAddCors();
|
|
expect(res).to.be.a('function');
|
|
});
|
|
|
|
it('should call the given handler when called adding CORS headers', () => {
|
|
const testHandler = sinon.spy();
|
|
const res = {
|
|
header: sinon.spy(),
|
|
};
|
|
const req = {};
|
|
const next = () => {};
|
|
module._handlerAddCors(testHandler)(req, res, next);
|
|
expect(testHandler).to.have.been.calledWith(req, res, next);
|
|
expect(res.header).to.have.been.calledWith(
|
|
'Access-Control-Allow-Origin',
|
|
'*'
|
|
);
|
|
expect(res.header).to.have.been.calledWith(
|
|
'Access-Control-Allow-Methods',
|
|
'GET,PUT,HEAD,PATCH,POST,DELETE,OPTIONS'
|
|
);
|
|
expect(res.header).to.have.been.calledWith(
|
|
'Access-Control-Allow-Headers',
|
|
'Authorization,Content-Type,x-amz-date,x-amz-security-token'
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('_getPort', () => {
|
|
it('should return a default port', () => {
|
|
const port = module._getPort();
|
|
expect(port).to.equal(8000);
|
|
});
|
|
|
|
it('should return the input option port if specified', () => {
|
|
module.options.port = 1234;
|
|
const port = module._getPort();
|
|
expect(port).to.equal(1234);
|
|
});
|
|
});
|
|
|
|
describe('_getFuncConfig', () => {
|
|
const testFunctionsConfig = {
|
|
func1: {
|
|
handler: 'module1.func1handler',
|
|
events: [{
|
|
http: {
|
|
method: 'get',
|
|
path: 'func1path',
|
|
},
|
|
}],
|
|
},
|
|
func2: {
|
|
handler: 'module2.func2handler',
|
|
events: [{
|
|
http: {
|
|
method: 'POST',
|
|
path: 'func2path',
|
|
},
|
|
}, {
|
|
nonhttp: 'non-http',
|
|
}],
|
|
},
|
|
func3: {
|
|
handler: 'module2.func3handler',
|
|
events: [{
|
|
nonhttp: 'non-http',
|
|
}],
|
|
},
|
|
};
|
|
|
|
beforeEach(() => {
|
|
serverless.service.functions = testFunctionsConfig;
|
|
});
|
|
|
|
it('should return a list of normalized functions configurations', () => {
|
|
const res = module._getFuncConfigs();
|
|
expect(res).to.eql([
|
|
{
|
|
'events': [
|
|
{
|
|
'method': 'get',
|
|
'path': 'func1path',
|
|
}
|
|
],
|
|
'handler': 'module1.func1handler',
|
|
'handlerFunc': null,
|
|
'id': 'func1',
|
|
'moduleName': 'module1',
|
|
},
|
|
{
|
|
'events': [
|
|
{
|
|
'method': 'POST',
|
|
'path': 'func2path',
|
|
}
|
|
],
|
|
'handler': 'module2.func2handler',
|
|
'handlerFunc': null,
|
|
'id': 'func2',
|
|
'moduleName': 'module2',
|
|
},
|
|
]);
|
|
});
|
|
});
|
|
|
|
describe('_newExpressApp', () => {
|
|
it('should return an express app', () => {
|
|
const res = module._newExpressApp([]);
|
|
expect(res).to.equal(expressMock.appMock);
|
|
});
|
|
|
|
it('should add a body-parser to the app', () => {
|
|
const res = module._newExpressApp([]);
|
|
expect(res.use).to.have.been.calledWith(sinon.match(value => {
|
|
return typeof value === 'function' && value.name === 'jsonParser';
|
|
}));
|
|
});
|
|
|
|
it('should create express handlers for all functions http event', () => {
|
|
const testFuncsConfs = [
|
|
{
|
|
'events': [
|
|
{
|
|
'method': 'get',
|
|
'path': 'func1path',
|
|
'cors': true,
|
|
}
|
|
],
|
|
'handler': 'module1.func1handler',
|
|
'handlerFunc': null,
|
|
'id': 'func1',
|
|
'moduleName': 'module1',
|
|
},
|
|
{
|
|
'events': [
|
|
{
|
|
'method': 'POST',
|
|
'path': 'func2path/{testParam}',
|
|
}
|
|
],
|
|
'handler': 'module2.func2handler',
|
|
'handlerFunc': null,
|
|
'id': 'func2',
|
|
'moduleName': 'module2',
|
|
},
|
|
];
|
|
const testStage = 'test';
|
|
module.options.stage = testStage;
|
|
const testHandlerBase = 'testHandlerBase';
|
|
const testHandlerCors = 'testHandlerCors';
|
|
const testHandlerOptions = 'testHandlerOptions';
|
|
module._handlerBase = sinon.stub().returns(testHandlerBase);
|
|
module._optionsHandler = testHandlerOptions;
|
|
module._handlerAddCors = sinon.stub().returns(testHandlerCors);
|
|
const app = module._newExpressApp(testFuncsConfs);
|
|
expect(app.get).to.have.callCount(1);
|
|
expect(app.get).to.have.been.calledWith(
|
|
'/test/func1path',
|
|
testHandlerCors
|
|
);
|
|
expect(module.serverless.cli.consoleLog).to.have.been.calledWith(
|
|
' GET - http://localhost:8000/test/func1path'
|
|
);
|
|
expect(app.post).to.have.callCount(1);
|
|
expect(app.post).to.have.been.calledWith(
|
|
'/test/func2path/:testParam',
|
|
testHandlerBase
|
|
);
|
|
expect(module.serverless.cli.consoleLog).to.have.been.calledWith(
|
|
' POST - http://localhost:8000/test/func2path/{testParam}'
|
|
);
|
|
expect(app.options).to.have.callCount(2);
|
|
expect(app.options.firstCall).to.have.been.calledWith(
|
|
'/test/func1path',
|
|
testHandlerCors
|
|
);
|
|
expect(app.options.secondCall).to.have.been.calledWith(
|
|
'/test/func2path/:testParam',
|
|
testHandlerOptions
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('serve method', () => {
|
|
let serve;
|
|
let listenerCb;
|
|
|
|
beforeEach(() => {
|
|
serve = module.serve();
|
|
listenerCb = expressMock.appMock.listen.firstCall.args[1];
|
|
});
|
|
|
|
it('should start an express app listener', () => {
|
|
expect(expressMock.appMock.listen).to.have.callCount(1);
|
|
});
|
|
|
|
it('should start a webpack watcher', () => {
|
|
listenerCb.bind(module)();
|
|
expect(webpackMock.compilerMock.watch).to.have.callCount(1);
|
|
});
|
|
|
|
it('should throw if compiler fails', () => {
|
|
listenerCb.bind(module)();
|
|
const compileCb = webpackMock.compilerMock.watch.firstCall.args[1];
|
|
const testError = 'testError';
|
|
expect(compileCb.bind(module, testError)).to.throw(testError);
|
|
});
|
|
|
|
it('should reload all function handlers on compilation', () => {
|
|
const testFuncsConfs = [
|
|
{
|
|
'events': [
|
|
{
|
|
'method': 'get',
|
|
'path': 'func1path',
|
|
'cors': true,
|
|
}
|
|
],
|
|
'handler': 'module1.func1handler',
|
|
'handlerFunc': null,
|
|
'id': 'func1',
|
|
'moduleName': 'module1',
|
|
},
|
|
{
|
|
'events': [
|
|
{
|
|
'method': 'POST',
|
|
'path': 'func2path/{testParam}',
|
|
}
|
|
],
|
|
'handler': 'module2.func2handler',
|
|
'handlerFunc': null,
|
|
'id': 'func2',
|
|
'moduleName': 'module2',
|
|
},
|
|
{
|
|
'events': [
|
|
{
|
|
'method': 'GET',
|
|
'path': 'func3path',
|
|
}
|
|
],
|
|
'handler': 'module2.func2handler',
|
|
'handlerFunc': null,
|
|
'id': 'func3',
|
|
'moduleName': 'module2',
|
|
},
|
|
];
|
|
module._getFuncConfigs = sinon.stub().returns(testFuncsConfs);
|
|
module.loadHandler = sinon.spy();
|
|
expressMock._resetSpies();
|
|
webpackMock._resetSpies();
|
|
serve = module.serve();
|
|
listenerCb = expressMock.appMock.listen.firstCall.args[1];
|
|
listenerCb.bind(module)();
|
|
const compileCb = webpackMock.compilerMock.watch.firstCall.args[1];
|
|
const testStats = {};
|
|
module.loadHandler.reset();
|
|
compileCb.bind(module)(null, testStats);
|
|
expect(module.loadHandler).to.have.callCount(3);
|
|
expect(module.loadHandler).to.have.been.calledWith(
|
|
testStats,
|
|
'func1',
|
|
true
|
|
);
|
|
expect(module.loadHandler).to.have.been.calledWith(
|
|
testStats,
|
|
'func2',
|
|
true
|
|
);
|
|
expect(module.loadHandler).to.have.been.calledWith(
|
|
testStats,
|
|
'func3',
|
|
false
|
|
);
|
|
});
|
|
});
|
|
});
|
|
|