Nicola Peduzzi
8 years ago
committed by
GitHub
17 changed files with 1068 additions and 17 deletions
@ -1,3 +1,4 @@ |
|||||
node_modules |
node_modules |
||||
dist |
dist |
||||
.webpack |
.webpack |
||||
|
coverage |
||||
|
@ -1,2 +1,3 @@ |
|||||
node_modules |
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