Browse Source

Merge pull request #8 from elastic-coders/add-unit-tests

Add unit tests
master
Nicola Peduzzi 8 years ago
committed by GitHub
parent
commit
ed74d8b426
  1. 1
      .gitignore
  2. 3
      .npmignore
  3. 2
      index.js
  4. 2
      lib/compile.js
  5. 2
      lib/run.js
  6. 25
      lib/serve.js
  7. 2
      lib/utils.js
  8. 12
      package.json
  9. 8
      tests/all.js
  10. 83
      tests/compile.test.js
  11. 20
      tests/express.mock.js
  12. 14
      tests/fs-extra.mock.js
  13. 221
      tests/run.test.js
  14. 418
      tests/serve.test.js
  15. 15
      tests/utils.mock.js
  16. 223
      tests/validate.test.js
  17. 34
      tests/webpack.mock.js

1
.gitignore

@ -1,3 +1,4 @@
node_modules node_modules
dist dist
.webpack .webpack
coverage

3
.npmignore

@ -1,2 +1,3 @@
node_modules node_modules
example examples
tests

2
index.js

@ -104,7 +104,7 @@ class ServerlessWebpack {
.then(this.validate) .then(this.validate)
.then(this.compile) .then(this.compile)
.then(this.run) .then(this.run)
.then(out => console.log(out)), .then(out => this.serverless.cli.consoleLog(out)),
'webpack:watch:watch': () => BbPromise.bind(this) 'webpack:watch:watch': () => BbPromise.bind(this)
.then(this.validate) .then(this.validate)

2
lib/compile.js

@ -12,7 +12,7 @@ module.exports = {
return BbPromise return BbPromise
.fromCallback(cb => compiler.run(cb)) .fromCallback(cb => compiler.run(cb))
.then(stats => { .then(stats => {
console.log(stats.toString({ this.serverless.cli.consoleLog(stats.toString({
colors: true, colors: true,
hash: false, hash: false,
version: false, version: false,

2
lib/run.js

@ -87,7 +87,7 @@ module.exports = {
if (err) { if (err) {
throw err; throw err;
} else { } else {
console.log(res); this.serverless.cli.consoleLog(res);
} }
} }
) )

25
lib/serve.js

@ -14,7 +14,7 @@ module.exports = {
const app = this._newExpressApp(funcConfs); const app = this._newExpressApp(funcConfs);
const port = this._getPort(); const port = this._getPort();
app.listen(port, () => { app.listen(port, () =>
compiler.watch({}, (err, stats) => { compiler.watch({}, (err, stats) => {
if (err) { if (err) {
throw err; throw err;
@ -28,8 +28,8 @@ module.exports = {
); );
loadedModules.push(funcConf.moduleName); loadedModules.push(funcConf.moduleName);
} }
}); })
}); );
return BbPromise.resolve(); return BbPromise.resolve();
}, },
@ -39,11 +39,11 @@ module.exports = {
app.use(bodyParser.json({ limit: '5mb' })); app.use(bodyParser.json({ limit: '5mb' }));
app.use((req, res, next) => { app.use(function optionsHandler(req, res, next) {
if(req.method !== 'OPTIONS') { if(req.method !== 'OPTIONS') {
next(); next();
} else { } else {
res.status(200).end(); res.sendStatus(200);
} }
}); });
@ -52,7 +52,7 @@ module.exports = {
const method = httpEvent.method.toLowerCase(); const method = httpEvent.method.toLowerCase();
const endpoint = `/${this.options.stage}/${httpEvent.path}`; const endpoint = `/${this.options.stage}/${httpEvent.path}`;
const path = endpoint.replace(/\{(.+?)\}/g, ':$1'); const path = endpoint.replace(/\{(.+?)\}/g, ':$1');
let handler = this._handerBase(funcConf); let handler = this._handlerBase(funcConf);
if (httpEvent.cors) { if (httpEvent.cors) {
handler = this._handlerAddCors(handler); handler = this._handlerAddCors(handler);
} }
@ -60,7 +60,7 @@ module.exports = {
path, path,
handler handler
); );
console.log(` ${method.toUpperCase()} - http://localhost:${this._getPort()}${endpoint}`); this.serverless.cli.consoleLog(` ${method.toUpperCase()} - http://localhost:${this._getPort()}${endpoint}`);
} }
} }
@ -94,14 +94,14 @@ module.exports = {
_handlerAddCors(handler) { _handlerAddCors(handler) {
return (req, res, next) => { return (req, res, next) => {
res.header('Access-Control-Allow-Origin', '*'); res.header('Access-Control-Allow-Origin', '*');
res.header( 'Access-Control-Allow-Methods', 'GET,PUT,HEAD,PATCH,POST,DELETE,OPTIONS'); res.header('Access-Control-Allow-Methods', 'GET,PUT,HEAD,PATCH,POST,DELETE,OPTIONS');
res.header( 'Access-Control-Allow-Headers', 'Authorization,Content-Type,x-amz-date,x-amz-security-token'); res.header('Access-Control-Allow-Headers', 'Authorization,Content-Type,x-amz-date,x-amz-security-token');
handler(req, res, next); handler(req, res, next);
}; };
}, },
_handerBase(funcConf) { _handlerBase(funcConf) {
return (req, res, next) => { return (req, res) => {
const func = funcConf.handlerFunc; const func = funcConf.handlerFunc;
const event = { const event = {
method: req.method, method: req.method,
@ -115,8 +115,7 @@ module.exports = {
const context = this.getContext(funcConf.id); const context = this.getContext(funcConf.id);
func(event, context, (err, resp) => { func(event, context, (err, resp) => {
if (err) { if (err) {
console.error(err); res.status(500).send(err);
res.sendStatus(500);
} else { } else {
res.status(200).send(resp); res.status(200).send(resp);
} }

2
lib/utils.js

@ -1,3 +1,5 @@
'use strict';
function guid() { function guid() {
function s4() { function s4() {
return Math.floor((1 + Math.random()) * 0x10000) return Math.floor((1 + Math.random()) * 0x10000)

12
package.json

@ -21,11 +21,23 @@
"url": "https://github.com/elastic-coders/serverless-webpack/issues" "url": "https://github.com/elastic-coders/serverless-webpack/issues"
}, },
"homepage": "https://github.com/elastic-coders/serverless-webpack#readme", "homepage": "https://github.com/elastic-coders/serverless-webpack#readme",
"scripts": {
"test": "istanbul cover _mocha tests/all -- -R spec --recursive"
},
"dependencies": { "dependencies": {
"bluebird": "^3.4.0", "bluebird": "^3.4.0",
"body-parser": "^1.15.2", "body-parser": "^1.15.2",
"express": "^4.14.0", "express": "^4.14.0",
"fs-extra": "^0.26.7", "fs-extra": "^0.26.7",
"webpack": "^1.13.1" "webpack": "^1.13.1"
},
"devDependencies": {
"chai": "^3.5.0",
"istanbul": "^0.4.4",
"mocha": "^3.0.2",
"mockery": "^1.7.0",
"serverless": "^1.0.0-beta.2",
"sinon": "^1.17.5",
"sinon-chai": "^2.8.0"
} }
} }

8
tests/all.js

@ -0,0 +1,8 @@
'use strict';
describe('serverless-webpack', () => {
require('./validate.test');
require('./compile.test');
require('./run.test');
require('./serve.test');
});

83
tests/compile.test.js

@ -0,0 +1,83 @@
'use strict';
const chai = require('chai');
const sinon = require('sinon');
const mockery = require('mockery');
const Serverless = require('serverless');
const makeWebpackMock = require('./webpack.mock');
chai.use(require('sinon-chai'));
const expect = chai.expect;
describe('compile', () => {
let webpackMock;
let baseModule;
let module;
let serverless;
before(() => {
mockery.enable({ warnOnUnregistered: false });
webpackMock = makeWebpackMock();
mockery.registerMock('webpack', webpackMock);
baseModule = require('../lib/compile');
Object.freeze(baseModule);
});
after(() => {
mockery.disable();
mockery.deregisterAll();
});
beforeEach(() => {
serverless = new Serverless();
serverless.cli = {
log: sinon.spy(),
consoleLog: sinon.spy(),
};
webpackMock._resetSpies();
module = Object.assign({
serverless,
options: {},
}, baseModule);
});
it('should expose a `compile` method', () => {
expect(module.compile).to.be.a('function');
});
it('should compile with webpack from a context configuration', () => {
const testWebpackConfig = 'testconfig';
module.webpackConfig = testWebpackConfig;
return module
.compile()
.then(() => {
expect(webpackMock).to.have.been.calledWith(testWebpackConfig);
expect(webpackMock.compilerMock.run).to.have.callCount(1);
});
});
it('should fail if there are compilation errors', () => {
module.webpackConfig = 'testconfig';
webpackMock.statsMock.compilation.errors = ['error'];
return module
.compile()
.catch((err) => {
expect(err.toString()).to.match(/compilation error/);
});
});
it('should set context `webpackOutputPath`, `originalServicePath`, `serverless.config.servicePath`', () => {
const testWebpackConfig = 'testconfig';
module.webpackConfig = testWebpackConfig;
const testServicePath = 'testServicePath';
module.serverless.config.servicePath = testServicePath;
const testOutputPath = 'testOutputPath';
webpackMock.statsMock.compilation.compiler.outputPath = testOutputPath;
return module
.compile()
.then(() => {
expect(module.webpackOutputPath).to.equal(testOutputPath);
expect(module.originalServicePath).to.equal(testServicePath);
expect(module.serverless.config.servicePath).to.equal(testOutputPath);
});
});
});

20
tests/express.mock.js

@ -0,0 +1,20 @@
const sinon = require('sinon');
const appMock = {
listen: sinon.spy(),
use: sinon.spy(),
get: sinon.spy(),
post: sinon.spy(),
};
const expressMock = sinon.stub().returns(appMock);
expressMock.appMock = appMock;
expressMock._resetSpies = () => {
expressMock.reset();
appMock.listen.reset();
appMock.use.reset();
appMock.get.reset();
appMock.post.reset();
};
module.exports = () => expressMock;

14
tests/fs-extra.mock.js

@ -0,0 +1,14 @@
'use strict';
const sinon = require('sinon');
module.exports = () => ({
_resetSpies() {
for (let p in this) {
if (this.hasOwnProperty(p) && p !== '_resetSpies') {
this[p].reset();
}
}
},
removeSync: sinon.spy(),
});

221
tests/run.test.js

@ -0,0 +1,221 @@
'use strict';
const chai = require('chai');
const sinon = require('sinon');
const mockery = require('mockery');
const Serverless = require('serverless');
const makeWebpackMock = require('./webpack.mock');
const makeUtilsMock = require('./utils.mock');
chai.use(require('sinon-chai'));
const expect = chai.expect;
describe('run', () => {
let webpackMock;
let utilsMock;
let baseModule;
let module;
let serverless;
before(() => {
mockery.enable({ warnOnUnregistered: false });
webpackMock = makeWebpackMock();
utilsMock = makeUtilsMock();
mockery.registerMock('webpack', webpackMock);
mockery.registerMock('./utils', utilsMock);
baseModule = require('../lib/run');
Object.freeze(baseModule);
});
after(() => {
mockery.disable();
mockery.deregisterAll();
});
beforeEach(() => {
serverless = new Serverless();
serverless.cli = {
log: sinon.spy(),
consoleLog: sinon.spy(),
};
webpackMock._resetSpies();
utilsMock._resetSpies();
module = Object.assign({
serverless,
options: {},
}, baseModule);
});
describe('utils', () => {
it('should expose utils methods', () => {
expect(module.loadHandler).to.be.a('function');
expect(module.getEvent).to.be.a('function');
expect(module.getContext).to.be.a('function');
});
describe('loadHandler', () => {
const testFunctionId = 'testFunctionId';
const testHandlerModuleName = 'testHandlerModule';
const testHandlerFunctionName = 'testHandlerFunction';
const testFunctionsConfig = {
[testFunctionId]: {
handler: `${testHandlerModuleName}.${testHandlerFunctionName}`,
}
};
const testPath = '/testpath';
const testFilename = `${testHandlerModuleName}.js`;
const testModuleFileName = `${testPath}/${testFilename}`;
const testStats = {
compilation: {
options: {
output: {
path: testPath,
filename: testFilename,
},
},
},
};
const testHandlerFunction = sinon.spy();
const testModule = {
[testHandlerFunctionName]: testHandlerFunction,
};
before(() => {
mockery.registerMock(testModuleFileName, testModule);
});
after(() => {
mockery.deregisterMock(testModuleFileName);
});
beforeEach(() => {
serverless.service.functions = testFunctionsConfig;
});
it('should require the handler module', () => {
const res = module.loadHandler(testStats, testFunctionId);
expect(res).to.equal(testHandlerFunction);
expect(utilsMock.purgeCache).to.have.callCount(0);
});
it('should purge the modules cache if required', () => {
const res = module.loadHandler(testStats, testFunctionId, true);
expect(utilsMock.purgeCache).to.have.been.calledWith(testModuleFileName);
});
});
it('should return a default event with `getEvent` and no option path', () => {
module.options.path = null;
const res = module.getEvent();
expect(res).to.equal(null);
});
it('should load an event object from disk with `getEvent`', () => {
const testPath = 'testPath';
module.options.path = testPath;
const testExampleObject = {};
module.serverless.utils.readFileSync = sinon.stub().returns(testExampleObject);
const res = module.getEvent();
expect(res).to.equal(testExampleObject);
});
it('should return an context object with `getContext`', () => {
const testFunctionName = 'testFunctionName';
const res = module.getContext(testFunctionName);
expect(res).to.eql({
awsRequestId: 'testguid',
functionName: testFunctionName,
functionVersion: '$LATEST',
invokeid: 'testguid',
isDefaultFunctionVersion: true,
logGroupName: `/aws/lambda/${testFunctionName}`,
logStreamName: '2016/02/14/[HEAD]13370a84ca4ed8b77c427af260',
memoryLimitInMB: '1024',
});
});
});
describe('run', () => {
const testEvent = {};
const testContext = {};
const testStats = {};
const testFunctionId = 'testFunctionId';
const testFunctionResult = 'testFunctionResult';
beforeEach(() => {
module.options['function'] = testFunctionId;
module.getEvent = sinon.stub().returns(testEvent);
module.getContext = sinon.stub().returns(testContext);
});
it('should execute the given function handler', () => {
const testHandlerFunc = sinon.spy((e, c, cb) => cb(null, testFunctionResult));
module.loadHandler = sinon.stub().returns(testHandlerFunc);
return module
.run(testStats)
.then((res) => {
expect(res).to.equal(testFunctionResult);
expect(testHandlerFunc).to.have.been.calledWith(
testEvent,
testContext
);
});
});
it('should fail if the function handler returns an error', () => {
const testError = 'testError';
const testHandlerFunc = sinon.spy((e, c, cb) => cb(testError));
module.loadHandler = sinon.stub().returns(testHandlerFunc);
return module
.run(testStats)
.catch((res) => {
expect(res).to.equal(testError);
});
});
});
describe('watch', () => {
const testEvent = {};
const testContext = {};
const testStats = {};
const testFunctionId = 'testFunctionId';
const testFunctionResult = 'testFunctionResult';
beforeEach(() => {
module.options['function'] = testFunctionId;
module.getEvent = sinon.stub().returns(testEvent);
module.getContext = sinon.stub().returns(testContext);
});
it('should throw if webpack watch fails', () => {
const testError = 'testError';
webpackMock.compilerMock.watch = sinon.spy((opt, cb) => cb(testError));
expect(module.watch.bind(module)).to.throw(testError);
});
it('should throw if function handler fails', () => {
const testError = 'testHandlerError';
const testHandlerFunc = sinon.spy((e, c, cb) => cb(testError));
module.loadHandler = sinon.stub().returns(testHandlerFunc);
let testCb;
webpackMock.compilerMock.watch = sinon.spy((opt, cb) => {
testCb = cb;
cb(null, webpackMock.statsMock);
});
expect(module.watch.bind(module)).to.throw(testError);
});
it('should call the handler every time a compilation occurs', () => {
const testHandlerFunc = sinon.spy((e, c, cb) => cb(null, testFunctionResult));
module.loadHandler = sinon.stub().returns(testHandlerFunc);
let testCb;
webpackMock.compilerMock.watch = sinon.spy((opt, cb) => {
testCb = cb;
cb(null, webpackMock.statsMock);
});
module.watch();
expect(testHandlerFunc).to.have.callCount(1);
testCb(null, webpackMock.statsMock);
expect(testHandlerFunc).to.have.callCount(2);
});
});
});

418
tests/serve.test.js

@ -0,0 +1,418 @@
'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 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);
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);
});
});
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';
}));
});
describe('OPTIONS handler', () => {
let optionsHandler;
beforeEach(() => {
const res = module._newExpressApp([]);
const optionsHandlers = res.use.getCalls().filter(c =>
typeof c.args[0] === 'function' &&
c.args[0].name === 'optionsHandler'
);
optionsHandler = optionsHandlers.length ? optionsHandlers[0].args[0] : null;
});
it('should add an OPTIONS request handler', () => {
expect(optionsHandler).to.exist;
});
it('should continue for non OPTIONS requests', () => {
const req = { method: 'GET' };
const next = sinon.spy();
optionsHandler(req, {}, next);
expect(next).to.have.callCount(1);
});
it('should send status 200 for OPTIONS requests', () => {
const req = { method: 'OPTIONS' };
const res = { sendStatus: sinon.spy() };
const next = sinon.spy();
optionsHandler(req, res, next);
expect(res.sendStatus).to.have.been.calledWith(200);
expect(next).to.have.callCount(0);
});
});
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';
module._handlerBase = sinon.stub().returns(testHandlerBase);
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}'
);
});
});
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
);
});
});
});

15
tests/utils.mock.js

@ -0,0 +1,15 @@
'use strict';
const sinon = require('sinon');
module.exports = () => ({
_resetSpies() {
for (let p in this) {
if (this.hasOwnProperty(p) && p !== '_resetSpies') {
this[p].reset();
}
}
},
guid: sinon.stub().returns('testguid'),
purgeCache: sinon.spy(),
});

223
tests/validate.test.js

@ -0,0 +1,223 @@
'use strict';
const chai = require('chai');
const sinon = require('sinon');
const mockery = require('mockery');
const Serverless = require('serverless');
const path = require('path');
const makeFsExtraMock = require('./fs-extra.mock');
chai.use(require('sinon-chai'));
const expect = chai.expect;
describe('validate', () => {
let fsExtraMock;
let baseModule;
let module;
let serverless;
before(() => {
mockery.enable({ warnOnUnregistered: false });
fsExtraMock = makeFsExtraMock();
mockery.registerMock('fs-extra', fsExtraMock);
baseModule = require('../lib/validate');
Object.freeze(baseModule);
});
after(() => {
mockery.disable();
mockery.deregisterAll();
});
beforeEach(() => {
serverless = new Serverless();
fsExtraMock._resetSpies();
module = Object.assign({
serverless,
options: {},
}, baseModule);
});
it('should expose a `validate` method', () => {
expect(module.validate).to.be.a('function');
});
it('should set `webpackConfig` in the context to `custom.webpack` option', () => {
const testConfig = {
entry: 'test',
context: 'testcontext',
output: {},
};
module.serverless.service.custom.webpack = testConfig;
return module
.validate()
.then(() => {
expect(module.webpackConfig).to.eql(testConfig);
});
});
it('should delete the output path', () => {
const testOutPath = 'test';
const testConfig = {
entry: 'test',
context: 'testcontext',
output: {
path: testOutPath,
},
};
module.serverless.service.custom.webpack = testConfig;
return module
.validate()
.then(() => {
expect(fsExtraMock.removeSync).to.have.been.calledWith(testOutPath);
});
});
it('should override the output path if `out` option is specified', () => {
const testConfig = {
entry: 'test',
context: 'testcontext',
output: {
path: 'originalpath',
filename: 'filename',
},
};
const testServicePath = 'testpath';
const testOptionsOut = 'testdir';
module.options.out = testOptionsOut;
module.serverless.config.servicePath = testServicePath;
module.serverless.service.custom.webpack = testConfig;
return module
.validate()
.then(() => {
expect(module.webpackConfig.output).to.eql({
path: `${testServicePath}/${testOptionsOut}`,
filename: 'filename',
});
});
});
it('should set a default `webpackConfig.context` if not present', () => {
const testConfig = {
entry: 'test',
output: {},
};
const testServicePath = 'testpath';
module.serverless.config.servicePath = testServicePath;
module.serverless.service.custom.webpack = testConfig;
return module
.validate()
.then(() => {
expect(module.webpackConfig.context).to.equal(testServicePath);
});
});
describe('default output', () => {
it('should set a default `webpackConfig.output` if not present', () => {
const testEntry = 'testentry';
const testConfig = {
entry: testEntry,
};
const testServicePath = 'testpath';
module.serverless.config.servicePath = testServicePath;
module.serverless.service.custom.webpack = testConfig;
return module
.validate()
.then(() => {
expect(module.webpackConfig.output).to.eql({
libraryTarget: 'commonjs',
path: `${testServicePath}/.webpack`,
filename: testEntry,
});
});
});
it('should set a default `webpackConfig.output.filename` if `entry` is an array', () => {
const testEntry = ['first', 'second', 'last'];
const testConfig = {
entry: testEntry,
};
const testServicePath = 'testpath';
module.serverless.config.servicePath = testServicePath;
module.serverless.service.custom.webpack = testConfig;
return module
.validate()
.then(() => {
expect(module.webpackConfig.output).to.eql({
libraryTarget: 'commonjs',
path: `${testServicePath}/.webpack`,
filename: 'last',
});
});
});
it('should set a default `webpackConfig.output.filename` if `entry` is not defined', () => {
const testConfig = {};
const testServicePath = 'testpath';
module.serverless.config.servicePath = testServicePath;
module.serverless.service.custom.webpack = testConfig;
return module
.validate()
.then(() => {
expect(module.webpackConfig.output).to.eql({
libraryTarget: 'commonjs',
path: `${testServicePath}/.webpack`,
filename: 'handler.js',
});
});
});
});
describe('config file load', () => {
it('should load a webpack config from file if `custom.webpack` is a string', () => {
const testConfig = 'testconfig'
const testServicePath = 'testpath';
const requiredPath = `${testServicePath}/${testConfig}`;
module.serverless.config.servicePath = testServicePath;
module.serverless.service.custom.webpack = testConfig;
serverless.utils.fileExistsSync = sinon.stub().returns(true);
const loadedConfig = {
entry: 'testentry',
};
mockery.registerMock(requiredPath, loadedConfig);
return module
.validate()
.then(() => {
expect(serverless.utils.fileExistsSync).to.have.been.calledWith(requiredPath);
expect(module.webpackConfig).to.eql(loadedConfig);
mockery.deregisterMock(requiredPath);
});
});
it('should throw if providing an invalid file', () => {
const testConfig = 'testconfig'
const testServicePath = 'testpath';
const requiredPath = `${testServicePath}/${testConfig}`;
module.serverless.config.servicePath = testServicePath;
module.serverless.service.custom.webpack = testConfig;
serverless.utils.fileExistsSync = sinon.stub().returns(false);
const loadedConfig = {
entry: 'testentry',
};
expect(module.validate.bind(module)).to.throw(/could not find/);
});
it('should load a default file if no custom config is provided', () => {
const testConfig = 'webpack.config.js';
const testServicePath = 'testpath';
const requiredPath = `${testServicePath}/${testConfig}`;
module.serverless.config.servicePath = testServicePath;
serverless.utils.fileExistsSync = sinon.stub().returns(true);
const loadedConfig = {
entry: 'testentry',
};
mockery.registerMock(requiredPath, loadedConfig);
return module
.validate()
.then(() => {
expect(serverless.utils.fileExistsSync).to.have.been.calledWith(requiredPath);
expect(module.webpackConfig).to.eql(loadedConfig);
mockery.deregisterMock(requiredPath);
});
});
});
});

34
tests/webpack.mock.js

@ -0,0 +1,34 @@
'use strict';
const sinon = require('sinon');
const statsMockBase = () => ({
compilation: {
errors: [],
compiler: {
outputPath: 'statsMock-outputPath',
},
},
toString: () => 'testStats',
});
const statsMock = {};
Object.assign(statsMock, statsMockBase());
const compilerMock = {
run: sinon.spy((cb) => cb(null, statsMock)),
watch: sinon.spy((opt, cb) => cb(null, statsMock)),
};
const webpackMock = sinon.stub().returns(compilerMock);
webpackMock.statsMock = statsMock;
webpackMock.compilerMock = compilerMock;
webpackMock._resetSpies = () => {
webpackMock.reset();
compilerMock.run.reset();
compilerMock.watch.reset();
Object.assign(statsMock, statsMockBase());
};
module.exports = () => webpackMock;
Loading…
Cancel
Save