diff --git a/lib/plugins/aws/invokeLocal/fixture/handlerWithSuccess.js b/lib/plugins/aws/invokeLocal/fixture/handlerWithSuccess.js new file mode 100644 index 000000000..22b7c6de9 --- /dev/null +++ b/lib/plugins/aws/invokeLocal/fixture/handlerWithSuccess.js @@ -0,0 +1,22 @@ +'use strict'; + +module.exports.withErrorByDone = (event, context) => { + context.done(new Error('failed')); +}; + +module.exports.withMessageByDone = (event, context) => { + context.done(null, 'Succeed'); +}; + +module.exports.withMessageByLambdaProxy = (event, context) => { + context.done(null, { + statusCode: 200, + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + result: true, + message: 'Whatever', + }), + }); +}; diff --git a/lib/plugins/aws/invokeLocal/index.js b/lib/plugins/aws/invokeLocal/index.js index 21ba37e2e..29b58f529 100644 --- a/lib/plugins/aws/invokeLocal/index.js +++ b/lib/plugins/aws/invokeLocal/index.js @@ -42,7 +42,15 @@ class AwsInvokeLocal { if (!this.serverless.utils.fileExistsSync(absolutePath)) { throw new this.serverless.classes.Error('The file you provided does not exist.'); } - this.options.data = this.serverless.utils.readFileSync(absolutePath); + // + + if (absolutePath.endsWith('.js')) { + // to support js - export as an input data + this.options.data = require(absolutePath); // eslint-disable-line global-require + } else { + this.options.data = this.serverless.utils.readFileSync(absolutePath); + } + resolve(); } else { try { @@ -143,8 +151,15 @@ class AwsInvokeLocal { * we need require() here to load the handler from the file system * which the user has to supply by passing the function name */ - lambda = require(path // eslint-disable-line global-require - .join(this.serverless.config.servicePath, handlerPath))[handlerName]; + + const handlersContainer = require( // eslint-disable-line global-require + path.join( + this.serverless.config.servicePath, + this.options.extraServicePath || '', + handlerPath + ) + ); + lambda = handlersContainer[handlerName]; } catch (error) { this.serverless.cli.consoleLog(error); process.exit(0); @@ -167,10 +182,24 @@ class AwsInvokeLocal { this.serverless.cli.consoleLog(chalk.red(JSON.stringify(errorResult, null, 4))); process.exitCode = 1; } else if (result) { + if (result.headers && result.headers['Content-Type'] === 'application/json') { + if (result.body) { + try { + Object.assign(result, { + body: JSON.parse(result.body), + }); + } catch (e) { + throw new Error('Content-Type of response is application/json but body is not json'); + } + } + } + this.serverless.cli.consoleLog(JSON.stringify(result, null, 4)); } }; + + const startTime = new Date(); const context = { awsRequestId: 'id', invokeid: 'id', @@ -188,11 +217,11 @@ class AwsInvokeLocal { fail(error) { return callback(error); }, - done() { - return callback(); + done(error, result) { + return callback(error, result); }, getRemainingTimeInMillis() { - return 5000; + return (new Date()).valueOf() - startTime.valueOf(); }, }; diff --git a/lib/plugins/aws/invokeLocal/index.test.js b/lib/plugins/aws/invokeLocal/index.test.js index 830210855..938375bb4 100644 --- a/lib/plugins/aws/invokeLocal/index.test.js +++ b/lib/plugins/aws/invokeLocal/index.test.js @@ -150,6 +150,28 @@ describe('AwsInvokeLocal', () => { }); }); + it('it should require a js file if file path is provided', () => { + serverless.config.servicePath = testUtils.getTmpDirPath(); + const jsContent = [ + 'module.exports = {', + ' headers: { "Content-Type" : "application/json" },', + ' body: JSON.stringify([100, 200]),', + '}', + ].join('\n'); + + serverless.utils.writeFileSync(path + .join(serverless.config.servicePath, 'data.js'), jsContent); + awsInvokeLocal.options.path = 'data.js'; + + return awsInvokeLocal.extendedValidate().then(() => { + expect(awsInvokeLocal.options.data).to.deep.equal({ + headers: { 'Content-Type': 'application/json' }, + body: '[100,200]', + }); + }); + }); + + it('it should throw error if service path is not set', () => { serverless.config.servicePath = false; expect(() => awsInvokeLocal.extendedValidate()).to.throw(Error); @@ -308,6 +330,45 @@ describe('AwsInvokeLocal', () => { serverless.cli.consoleLog.restore(); }); + describe('with done method', () => { + it('should exit with error exit code', () => { + awsInvokeLocal.serverless.config.servicePath = __dirname; + + awsInvokeLocal.invokeLocalNodeJs('fixture/handlerWithSuccess', 'withErrorByDone'); + + expect(process.exitCode).to.be.equal(1); + }); + + it('should succeed if succeed', () => { + awsInvokeLocal.serverless.config.servicePath = __dirname; + + awsInvokeLocal.invokeLocalNodeJs('fixture/handlerWithSuccess', 'withMessageByDone'); + + expect(serverless.cli.consoleLog.lastCall.args[0]).to.contain('"Succeed"'); + }); + }); + + describe('with Lambda Proxy with application/json response', () => { + it('should succeed if succeed', () => { + awsInvokeLocal.serverless.config.servicePath = __dirname; + + awsInvokeLocal.invokeLocalNodeJs('fixture/handlerWithSuccess', 'withMessageByLambdaProxy'); + + expect(serverless.cli.consoleLog.lastCall.args[0]).to.contain('{\n "statusCode": 200,\n "headers": {\n "Content-Type": "application/json"\n },\n "body": {\n "result": true,\n "message": "Whatever"\n }\n}'); // eslint-disable-line + }); + }); + + describe('with extraServicePath', () => { + it('should succeed if succeed', () => { + awsInvokeLocal.serverless.config.servicePath = __dirname; + awsInvokeLocal.options.extraServicePath = 'fixture'; + + awsInvokeLocal.invokeLocalNodeJs('handlerWithSuccess', 'withMessageByLambdaProxy'); + + expect(serverless.cli.consoleLog.lastCall.args[0]).to.contain('{\n "statusCode": 200,\n "headers": {\n "Content-Type": "application/json"\n },\n "body": {\n "result": true,\n "message": "Whatever"\n }\n}'); // eslint-disable-line + }); + }); + it('should exit with error exit code', () => { awsInvokeLocal.serverless.config.servicePath = __dirname;