Nicola Peduzzi
8 years ago
committed by
GitHub
17 changed files with 1068 additions and 17 deletions
@ -1,3 +1,4 @@ |
|||
node_modules |
|||
dist |
|||
.webpack |
|||
coverage |
|||
|
@ -1,2 +1,3 @@ |
|||
node_modules |
|||
example |
|||
examples |
|||
tests |
|||
|
@ -0,0 +1,8 @@ |
|||
'use strict'; |
|||
|
|||
describe('serverless-webpack', () => { |
|||
require('./validate.test'); |
|||
require('./compile.test'); |
|||
require('./run.test'); |
|||
require('./serve.test'); |
|||
}); |
@ -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); |
|||
}); |
|||
}); |
|||
}); |
@ -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; |
@ -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(), |
|||
}); |
@ -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); |
|||
}); |
|||
}); |
|||
}); |
@ -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 |
|||
); |
|||
}); |
|||
}); |
|||
}); |
@ -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(), |
|||
}); |
@ -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); |
|||
}); |
|||
}); |
|||
}); |
|||
}); |
@ -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…
Reference in new issue