From cefac9293b30fe43061f0cdddd7ae8db3c210147 Mon Sep 17 00:00:00 2001 From: gpotter2 Date: Sun, 11 Sep 2016 19:40:07 +0200 Subject: [PATCH 01/22] Switched the callbackforce default state Function will now wait for callback by default for an easier usage --- bin/lambda-local | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bin/lambda-local b/bin/lambda-local index d451185..a22d014 100755 --- a/bin/lambda-local +++ b/bin/lambda-local @@ -13,7 +13,7 @@ .option('-e, --eventpath [event data file name]', 'Specify event data file name.') .option('-h, --handler [lambda-function handler name (optional)]', 'Lambda function handler name. Default is "handler".') .option('-t, --timeout [timeout seconds (optional)]', 'Seconds until lambda function timeout. Default is 3 seconds.') - .option('-c, --callbackforce (optional)', 'Force the function to stop after having called context.done/succeed/fail.') + .option('-n, --nocallbackforce (optional)', 'Force the function to stop after having called the handler function even if context.done/succeed/fail was not called.') .option('-p, --profile [aws file path (optional)]', 'Read the AWS profile to get the credentials') .parse(process.argv); @@ -33,9 +33,9 @@ //default callbackWaitsForEmptyEventLoop if(!callbackWaitsForEmptyEventLoop){ - callbackWaitsForEmptyEventLoop = true; - } else { callbackWaitsForEmptyEventLoop = false; + } else { + callbackWaitsForEmptyEventLoop = true; } // timeout milliseconds From a374db5f6135b9dfb519634789ab1719dd01cb2c Mon Sep 17 00:00:00 2001 From: gpotter2 Date: Sun, 11 Sep 2016 19:44:35 +0200 Subject: [PATCH 02/22] Update README.md --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index 3524683..d186e04 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ About * -e, --eventpath [event data file name] Specify event data file name. * -h, --handler [lambda-function handler name (optional)] Lambda function handler name. Default is "handler". * -t, --timeout [timeout seconds (optional)] Seconds until lambda function timeout. Default is 3 seconds. -* -c, --callbackforce (optional) Force the function to stop after having called context.done/succeed/fail. +* -n, --nocallbackforce (optional) Force the function to stop after having called the handler function even if context.done/succeed/fail was not called. * -p, --profile [aws file path (optional)] Read the AWS profile to get the credentials. ### Event data @@ -57,4 +57,3 @@ License ---------- This library is released under the MIT license. - From c1bd0693a5b54b12a8a2db0541c2e109ef43733c Mon Sep 17 00:00:00 2001 From: gpotter2 Date: Sun, 11 Sep 2016 19:48:07 +0200 Subject: [PATCH 03/22] Update lambda-local --- bin/lambda-local | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/lambda-local b/bin/lambda-local index a22d014..b1ba384 100755 --- a/bin/lambda-local +++ b/bin/lambda-local @@ -21,7 +21,7 @@ var lambdaPath = program.lambdapath; var lambdaHandler = program.handler; var profilePath = program.profile; - var callbackWaitsForEmptyEventLoop = program.callbackforce; + var callbackWaitsForEmptyEventLoop = program.nocallbackforce; if (!lambdaPath || !eventPath) { program.help(); } From 290c2972b4b793d00f1a925e610a7a81ee31c54a Mon Sep 17 00:00:00 2001 From: gpotter2 Date: Thu, 15 Sep 2016 21:45:04 +0200 Subject: [PATCH 04/22] Log Manager + New Unit Tests (and modified travis) + Windows bugs - Added a new log manager using Winston, to provide way better logs - Changed the test versions of travis: some are useless, only the most used one should be tested - Remade entirely the tests files to provide a way better organized function (no messy callbacks inside stuff) - Changed some indent.... Please use four spaces ! --- .travis.yml | 9 +- bin/lambda-local | 162 +++++++++++++++------------- lib/.tern-port | 1 - lib/context.js | 91 +++++++++------- lib/lambdalocal.js | 125 +++++++++++---------- lib/utils.js | 100 +++++++++-------- package.json | 4 +- test/events/test-event.js | 4 + test/functs/test-func-awsprofile.js | 7 ++ test/functs/test-func.js | 9 ++ test/{ => other}/debug.aws | 0 test/test-event.js | 6 -- test/test-func.js | 11 -- test/test.js | 24 ++--- 14 files changed, 284 insertions(+), 269 deletions(-) delete mode 100644 lib/.tern-port create mode 100644 test/events/test-event.js create mode 100644 test/functs/test-func-awsprofile.js create mode 100644 test/functs/test-func.js rename test/{ => other}/debug.aws (100%) delete mode 100644 test/test-event.js delete mode 100644 test/test-func.js diff --git a/.travis.yml b/.travis.yml index 0206b36..c5f3532 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,9 +1,10 @@ language: node_js node_js: - - "1.8" - - "2.5" - - "3.3" - - "4.4" + - "0.10" + - "0.12" + - "4" + - "6" + - "node" sudo: false #cache: # directories: diff --git a/bin/lambda-local b/bin/lambda-local index 13d8a08..effde78 100755 --- a/bin/lambda-local +++ b/bin/lambda-local @@ -6,91 +6,101 @@ * Local executor for Amazon Lambda function */ (function() { - var lambdaLocal = require('../lib/lambdalocal.js'), - utils = require('../lib/utils.js'); - // process opts - var program = require('commander'); - program - .option('-l, --lambda-path ', '(required) Lambda function file name.') - .option('-e, --event-path ', '(required) Event data file name.') - .option('-h, --handler ', - '(optional) Lambda function handler name. Default is \'handler\'.') - .option('-t, --timeout ', - '(optional) Seconds until lambda function timeout. Default is 3 seconds.') - .option('-n, --no-force-callback', - '(optional) Force the function to stop after having called the handler function' + - ' even if context.done/succeed/fail was not called.') - .option('-r, --region ', - '(optional) default set to us-east-1') - .option('-p, --profile ', - '(optional) Read the AWS profile to get the credentials from profile name') - .option('-P, --profile-path ', - '(optional) Read the specified AWS credentials file') - .parse(process.argv); + var winston = require('winston'); + var logger = new winston.Logger({ + level: 'info', + transports: [ + new (winston.transports.Console)({handleExceptions: true, json: false, colorize: true}) + ] + }); - var eventPath = program.eventPath, - lambdaPath = program.lambdaPath, - lambdaHandler = program.handler, - profilePath = program.profilePath, - profileName = program.profile, - region = program.region, - callbackWaitsForEmptyEventLoop = program.noForceCallback; + var lambdaLocal = require('../lib/lambdalocal.js'), + utils = require('../lib/utils.js'); + lambdaLocal.setLogger(logger); - if (!lambdaPath || !eventPath) { - program.help(); - } + // process opts + var program = require('commander'); + program + .option('-l, --lambda-path ', '(required) Lambda function file name.') + .option('-e, --event-path ', '(required) Event data file name.') + .option('-h, --handler ', + '(optional) Lambda function handler name. Default is \'handler\'.') + .option('-t, --timeout ', + '(optional) Seconds until lambda function timeout. Default is 3 seconds.') + .option('-n, --no-force-callback', + '(optional) Force the function to stop after having called the handler function' + + ' even if context.done/succeed/fail was not called.') + .option('-r, --region ', + '(optional) default set to us-east-1') + .option('-p, --profile ', + '(optional) Read the AWS profile to get the credentials from profile name') + .option('-P, --profile-path ', + '(optional) Read the specified AWS credentials file') + .parse(process.argv); - // default handler name - if (!lambdaHandler) { - lambdaHandler = 'handler'; - } + var eventPath = program.eventPath, + lambdaPath = program.lambdaPath, + lambdaHandler = program.handler, + profilePath = program.profilePath, + profileName = program.profile, + region = program.region, + callbackWaitsForEmptyEventLoop = program.noForceCallback; - //default callbackWaitsForEmptyEventLoop - if (!callbackWaitsForEmptyEventLoop) { - callbackWaitsForEmptyEventLoop = false; - } else { - callbackWaitsForEmptyEventLoop = true; - } + if (!lambdaPath || !eventPath) { + program.help(); + } - // timeout milliseconds - var timeoutMs; - if (program.timeout) { - timeoutMs = program.timeout * 1000; - } else { - timeoutMs = 3000; - } + // default handler name + if (!lambdaHandler) { + lambdaHandler = 'handler'; + } - var event = require(utils.getAbsolutePath(eventPath)); + //default callbackWaitsForEmptyEventLoop + if (!callbackWaitsForEmptyEventLoop) { + callbackWaitsForEmptyEventLoop = false; + } else { + callbackWaitsForEmptyEventLoop = true; + } - // execute - lambdaLocal.execute({ - event: event, - lambdaPath: lambdaPath, - lambdaHandler: lambdaHandler, - profilePath: profilePath, - profileName: profileName, - region: region, - callbackWaitsForEmptyEventLoop: callbackWaitsForEmptyEventLoop, - timeoutMs: timeoutMs, - callback: function(err /*, data */) { //data unused - console.log('-----'); - if (err === null) { - console.log('lambda-local successfully complete.'); - process.exit(0); - } else { - console.log('lambda-local failed.'); - // Finish the process - process.exit(1); - } + // timeout milliseconds + var timeoutMs; + if (program.timeout) { + timeoutMs = program.timeout * 1000; + } else { + timeoutMs = 3000; } - }); - // Handling timeout - setTimeout(function() { - console.log('-----'); - console.log('Task timed out after ' + (timeoutMs / 1000).toFixed(2) + ' seconds'); - process.exit(); - }, timeoutMs); + var event = require(utils.getAbsolutePath(eventPath)); + + // execute + lambdaLocal.execute({ + event: event, + lambdaPath: lambdaPath, + lambdaHandler: lambdaHandler, + profilePath: profilePath, + profileName: profileName, + region: region, + callbackWaitsForEmptyEventLoop: callbackWaitsForEmptyEventLoop, + timeoutMs: timeoutMs, + callback: function(err /*, data */) { //data unused + logger.log('info', '-----'); + if (err === null) { + logger.log('info', 'lambda-local successfully complete.'); + process.exit(0); + } else { + logger.log('error', 'lambda-local failed.'); + // Finish the process + process.exit(1); + } + } + }); + + // Handling timeout + setTimeout(function() { + logger.log('error', '-----'); + logger.log('error', 'Task timed out after ' + (timeoutMs / 1000).toFixed(2) + ' seconds'); + process.exit(); + }, timeoutMs); })(); diff --git a/lib/.tern-port b/lib/.tern-port deleted file mode 100644 index a9aa626..0000000 --- a/lib/.tern-port +++ /dev/null @@ -1 +0,0 @@ -52164 \ No newline at end of file diff --git a/lib/context.js b/lib/context.js index d2a5a7b..f776feb 100644 --- a/lib/context.js +++ b/lib/context.js @@ -8,6 +8,9 @@ var utils = require('./utils.js'); +var logger, + unmute; + /* * doneStatus & postDone were minimum; probably defined internally in Lambda. */ @@ -45,8 +48,9 @@ Context.clientContext = null; * callback function called after done */ Context.callback = function(result) { - console.log('default context callback'); - return result; + if(unmute != null) {unmute(); unmute = null;} + logger.log('info', 'This is the default context callback'); + return result; }; /* @@ -55,13 +59,13 @@ Context.callback = function(result) { * 8hex-4hex-4hex-4hex-12hex */ Context.createInvokeId = (function() { - return [ - utils.generateRandomHex(8), - utils.generateRandomHex(4), - utils.generateRandomHex(4), - utils.generateRandomHex(4), - utils.generateRandomHex(12) - ].join('-'); + return [ + utils.generateRandomHex(8), + utils.generateRandomHex(4), + utils.generateRandomHex(4), + utils.generateRandomHex(4), + utils.generateRandomHex(12) + ].join('-'); })(); /* @@ -69,60 +73,65 @@ Context.createInvokeId = (function() { * Called from lambda-local */ Context._initialize = function(options) { - /* set time */ - startTime = new Date().getTime(); - timeout = options.timeoutMs; - - /* set function name */ - Context.functionName = options.functionName; - - /* set requestid */ - Context.awsRequestId = options.awsRequestId; - - /* Set callbackWaitsForEmptyEventLoop */ - Context.callbackWaitsForEmptyEventLoop = options.callbackWaitsForEmptyEventLoop; - return; + /* set time */ + startTime = new Date().getTime(); + timeout = options.timeoutMs; + + logger = options.logger; + unmute = options.unmute; + + /* set function name */ + Context.functionName = options.functionName; + + /* set requestid */ + Context.awsRequestId = options.awsRequestId; + + /* Set callbackWaitsForEmptyEventLoop */ + Context.callbackWaitsForEmptyEventLoop = options.callbackWaitsForEmptyEventLoop; + return; }; /* * This `done` method is directly extracted from source. */ Context.done = function(err, message) { - console.log('END'); - console.log('\n'); - if (err !== null) { - console.log('Error'); - console.log('------'); - utils.outputJSON(err); - } else { - console.log('Message'); - console.log('------'); - utils.outputJSON(message); - } - if (!Context.callbackWaitsForEmptyEventLoop) { - Context.callback(true); - } + if(unmute != null) {unmute(); unmute = null;} + logger.log('info', 'END'); + if (err !== null) { + logger.log('error', 'Error'); + logger.log('error', '------'); + utils.outputJSON(err, logger); + } else { + logger.log('info', 'Message'); + logger.log('info', '------'); + utils.outputJSON(message, logger); + } + if (!Context.callbackWaitsForEmptyEventLoop) { + Context.callback(err, message); + } }; /* * `fail` method calls the `done` method */ Context.fail = function(err) { - console.log('FAILING!!'); - Context.done(err); + if(unmute != null) {unmute(); unmute = null;} + logger.log('error', 'FAILING!!'); + Context.done(err); }; /* * `succeed` method calls the `done` method */ Context.succeed = function(message) { - Context.done(null, message); + if(unmute != null) {unmute(); unmute = null;} + Context.done(null, message); }; /* * 'getRemainingTimeInMillis' method return time before task is killed */ Context.getRemainingTimeInMillis = function() { - var now = new Date().getTime(); - return (timeout + startTime - now); + var now = new Date().getTime(); + return (timeout + startTime - now); }; diff --git a/lib/lambdalocal.js b/lib/lambdalocal.js index f7ddc11..90ecd67 100755 --- a/lib/lambdalocal.js +++ b/lib/lambdalocal.js @@ -6,81 +6,76 @@ * https://docs.aws.amazon.com/en_us/lambda/latest/dg/nodejs-prog-model-context.html */ -const mute = require('mute'), - utils = require('./utils.js'), - _context = require('./context.js'); +var logger = require('winston'); +const mute = require('mute'), + utils = require('./utils.js'), + _context = require('./context.js'); + +var _setLogger = function(_logger){ + logger = _logger; +} + var _execute = function(opts) { - var event = opts.event, - lambdaPath = opts.lambdaPath, - lambdaHandler = opts.lambdaHandler || 'handler', - profilePath = opts.profilePath, - profileName = opts.profileName, - region = opts.region || 'us-east-1', - callbackWaitsForEmptyEventLoop = (typeof opts.callbackWaitsForEmptyEventLoop === 'undefined') ? - true : opts.callbackWaitsForEmptyEventLoop, - timeoutMs = (typeof opts.timeoutMs === 'undefined') ? 3000 : opts.timeoutMs, - muteLogs = opts.mute, - unmute = null, - callback = opts.callback; + var event = opts.event, + lambdaPath = opts.lambdaPath, + lambdaHandler = opts.lambdaHandler || 'handler', + profilePath = opts.profilePath, + profileName = opts.profileName, + region = opts.region || 'us-east-1', + callbackWaitsForEmptyEventLoop = (typeof opts.callbackWaitsForEmptyEventLoop === 'undefined') ? + true : opts.callbackWaitsForEmptyEventLoop, + timeoutMs = (typeof opts.timeoutMs === 'undefined') ? 3000 : opts.timeoutMs, + muteLogs = opts.mute, + unmute = null, + callback = opts.callback; - if(muteLogs) { - unmute = mute(); - } + if(muteLogs) { + unmute = mute(); + } + + //load profile + if (profilePath) { + utils.loadAWSCredentials(profilePath, profileName); + } - //load profile - if (profilePath) { - utils.loadAWSCredentials(profilePath, profileName); - } + // set region before the require + process.env['AWS_REGION'] = region; - // set region before the require - process.env['AWS_REGION'] = region; + // load lambda function + var lambdaFunc = require(utils.getAbsolutePath(lambdaPath)); - // load lambda function - var lambdaFunc = require(utils.getAbsolutePath(lambdaPath)); + // load event & context + var context = this.context; - // load event & context - var context = this.context; - - context._initialize({ - functionName: lambdaHandler, - awsRequestId: context.createInvokeId, - timeoutMs: timeoutMs, - callbackWaitsForEmptyEventLoop: callbackWaitsForEmptyEventLoop - }); - // export the LAMBDA_TASK_ROOT enviroment variable - process.env['LAMBDA_TASK_ROOT'] = process.cwd(); + context._initialize({ + functionName: lambdaHandler, + awsRequestId: context.createInvokeId, + timeoutMs: timeoutMs, + callbackWaitsForEmptyEventLoop: callbackWaitsForEmptyEventLoop, + unmute: unmute, + logger: logger + }); + // export the LAMBDA_TASK_ROOT enviroment variable + process.env['LAMBDA_TASK_ROOT'] = process.cwd(); - //setting common other vars environments - process.env['NODE_PATH'] = utils.getAbsolutePath('node_modules'); - process.env['LAMBDA_CONSOLE_SOCKET'] = 14; - process.env['LAMBDA_CONTROL_SOCKET'] = 11; - process.env['AWS_SESSION_TOKEN'] = context.awsRequestId; /*Just a random value...*/ + //setting common other vars environments + process.env['NODE_PATH'] = utils.getAbsolutePath('node_modules'); + process.env['LAMBDA_CONSOLE_SOCKET'] = 14; + process.env['LAMBDA_CONTROL_SOCKET'] = 11; + process.env['AWS_SESSION_TOKEN'] = context.awsRequestId; /*Just a random value...*/ - // execute lambda function - console.log('Logs'); - console.log('------'); - console.log('START RequestId: ' + context.awsRequestId); + // execute lambda function + logger.log('info', 'Logs'); + logger.log('info', '------'); + logger.log('info', 'START RequestId: ' + context.awsRequestId); - context.callback = callback; - if (callback) { - lambdaFunc[lambdaHandler](event, context, function (err, data) { - if (muteLogs) { - unmute(); - } - callback(err, data); - }); - } else { - lambdaFunc[lambdaHandler](event, context, function (err, message) { - if (muteLogs) { - unmute(); - } - context.done(err, message); - }); - } + if(callback) context.callback = callback; + lambdaFunc[lambdaHandler](event, context, context.done); }; module.exports = { - context: _context, - execute: _execute -}; + context: _context, + execute: _execute, + setLogger: _setLogger +}; \ No newline at end of file diff --git a/lib/utils.js b/lib/utils.js index 23cf9df..4a1f569 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -13,70 +13,76 @@ const join = require('path').join; const _hexChars = '0123456789abcdef'.split(''); var _generateRandomHex = function(length) { - var hexVal = ''; - for (var i = 0; i < length; i++) { - hexVal += _hexChars[Math.floor(Math.random() * _hexChars.length)]; - } - return hexVal; + var hexVal = ''; + for (var i = 0; i < length; i++) { + hexVal += _hexChars[Math.floor(Math.random() * _hexChars.length)]; + } + return hexVal; }; var _getAbsolutePath = function(path) { - var res = null, - homeDir = process.env.HOME || process.env.USERPROFILE; - - if (path.match(/^\//)) { - res = path; - } else { - if (path === '~') { - res = homeDir; - } else if (path.slice(0, 2) !== '~/') { - res = join(process.cwd(), path); + var res = null, + homeDir = process.env.HOME || process.env.USERPROFILE; + + var windowsRegex = /([A-Z|a-z]:\\[^*|"<>?\n]*)|(\\\\.*?\\.*)/; + + if (path.match(/^\//) || path.match(windowsRegex)) { + //On Windows and linux + res = path; } else { - res = join(homeDir, path.slice(2)); + if (path === '~') { + //On linux only + res = homeDir; + } else if (path.slice(0, 2) !== '~/') { + //On Windows and linux + res = join(process.cwd(), path); + } else { + //On linux only + res = join(homeDir, path.slice(2)); + } } - } - return res; + return res; }; -var _outputJSON = function(json) { - console.log(typeof json === 'object' ? - JSON.stringify(json, null, '\t') : json); +var _outputJSON = function(json, logger) { + logger.log('info', typeof json === 'object' ? + JSON.stringify(json, null, '\t') : json); }; //This will load aws credentials files //more infos: // - http://docs.aws.amazon.com/cli/latest/userguide/cli-chap-getting-started.html#cli-config-files var _loadAWSCredentials = function(path) { - //default parameter - var profileName = arguments.length <= 1 || - arguments[1] === undefined || - arguments[1] === null ? 'default' : arguments[1]; + //default parameter + var profileName = arguments.length <= 1 || + arguments[1] === undefined || + arguments[1] === null ? 'default' : arguments[1]; - var fs = require('fs'), - dataRaw = fs.readFileSync(_getAbsolutePath(path)), - data = dataRaw.toString(); + var fs = require('fs'), + dataRaw = fs.readFileSync(_getAbsolutePath(path)), + data = dataRaw.toString(); - var regex = new RegExp('\\[' + profileName + - '\\](.|\\n|\\r\\n)*?aws_secret_access_key( ?)+=( ?)+(.*)'), - match; - if ((match = regex.exec(data)) !== null) { - process.env['AWS_SECRET_ACCESS_KEY'] = match[4]; - } else { - console.log('WARNING: Couldn\'t find the \'aws_secret_access_key\' field inside the file.'); - } + var regex = new RegExp('\\[' + profileName + + '\\](.|\\n|\\r\\n)*?aws_secret_access_key( ?)+=( ?)+(.*)'), + match; + if ((match = regex.exec(data)) !== null) { + process.env['AWS_SECRET_ACCESS_KEY'] = match[4]; + } else { + console.log('WARNING: Couldn\'t find the \'aws_secret_access_key\' field inside the file.'); + } - regex = new RegExp('\\[' + profileName + '\\](.|\\n|\\r\\n)*?aws_access_key_id( ?)+=( ?)+(.*)'); - if ((match = regex.exec(data)) !== null) { - process.env['AWS_ACCESS_KEY_ID'] = match[4]; - } else { - console.log('WARNING: Couldn\'t find the \'aws_access_key_id\' field inside the file.'); - } + regex = new RegExp('\\[' + profileName + '\\](.|\\n|\\r\\n)*?aws_access_key_id( ?)+=( ?)+(.*)'); + if ((match = regex.exec(data)) !== null) { + process.env['AWS_ACCESS_KEY_ID'] = match[4]; + } else { + console.log('WARNING: Couldn\'t find the \'aws_access_key_id\' field inside the file.'); + } }; module.exports = { - hexChars: _hexChars, - generateRandomHex: _generateRandomHex, - getAbsolutePath: _getAbsolutePath, - outputJSON: _outputJSON, - loadAWSCredentials: _loadAWSCredentials + hexChars: _hexChars, + generateRandomHex: _generateRandomHex, + getAbsolutePath: _getAbsolutePath, + outputJSON: _outputJSON, + loadAWSCredentials: _loadAWSCredentials }; diff --git a/package.json b/package.json index c7de9d3..985ac11 100644 --- a/package.json +++ b/package.json @@ -16,9 +16,11 @@ }, "dependencies": { "aws-sdk": "^2.1.6", + "chai": "^3.5.0", "commander": "^2.6.0", "fs": "^0.0.2", - "mute": "^2.0.6" + "mute": "^2.0.6", + "winston": "^2.2.0" }, "devDependencies": { "mocha": "^2.4.5" diff --git a/test/events/test-event.js b/test/events/test-event.js new file mode 100644 index 0000000..2785985 --- /dev/null +++ b/test/events/test-event.js @@ -0,0 +1,4 @@ +module.exports = { + "key": "testvar", +}; + diff --git a/test/functs/test-func-awsprofile.js b/test/functs/test-func-awsprofile.js new file mode 100644 index 0000000..7d33876 --- /dev/null +++ b/test/functs/test-func-awsprofile.js @@ -0,0 +1,7 @@ +/* + * Lambda function used to test the AWS profiles. + */ +exports.handler = function(event, context) { + var result = {"secret": process.env['AWS_SECRET_ACCESS_KEY'], "key": process.env['AWS_ACCESS_KEY_ID']} + context.done(null, result); +}; diff --git a/test/functs/test-func.js b/test/functs/test-func.js new file mode 100644 index 0000000..2b9bc15 --- /dev/null +++ b/test/functs/test-func.js @@ -0,0 +1,9 @@ +/* + * Lambda function used for test. + */ +exports.handler = function(event, context) { + var answer = {"result": event.key, "context": context}; + context.done(null, answer); +}; + + diff --git a/test/debug.aws b/test/other/debug.aws similarity index 100% rename from test/debug.aws rename to test/other/debug.aws diff --git a/test/test-event.js b/test/test-event.js deleted file mode 100644 index a3ac241..0000000 --- a/test/test-event.js +++ /dev/null @@ -1,6 +0,0 @@ -module.exports = { - "key1": "value1", - "key2": "value2", - "key3": "value3" -}; - diff --git a/test/test-func.js b/test/test-func.js deleted file mode 100644 index 036855b..0000000 --- a/test/test-func.js +++ /dev/null @@ -1,11 +0,0 @@ -'use strict'; - -/* - * Lambda function used for test. - */ -exports.handler = function(event, context, callback) { - console.log('value1 = ' + event.key1); - console.log('value2 = ' + event.key2); - console.log('value3 = ' + event.key3); - callback(null, 'function done'); -}; diff --git a/test/test.js b/test/test.js index da6bbbe..10642d8 100644 --- a/test/test.js +++ b/test/test.js @@ -1,28 +1,15 @@ 'use strict'; -var assert = require('assert'); +var assert = require('chai').assert; var path = require('path'); +var fs = require('fs'); var functionName = 'handler'; var timeoutMs = 3000; -var lambdalocal = require('../lib/lambdalocal.js'); -var callbackFunc = function(err, data) { - describe('LambdaLocal', function() { - it('err should be null', function() { - assert.equal(err, null); - }); - it('data should not be null', function() { - assert.notEqual(data, null); - }); - }); +var winston = require("winston"); +winston.level="error"; - describe('Context object', function() { - it('should contain initialized functionName', function() { - assert.equal(lambdalocal.context.functionName, functionName); - }); - it('should contain initialized awsRequestId', function() { - assert.equal(lambdalocal.context.awsRequestId.length === 36, true); }); it('should contain initialized getRemainingTimeInMillis', function() { assert.equal((lambdalocal.context.getRemainingTimeInMillis() <= timeoutMs), true); @@ -39,3 +26,6 @@ lambdalocal.execute({ timeoutMs: timeoutMs, callback: callbackFunc }); + }); + }); +}); \ No newline at end of file From cae2a62400e56e60192823e07983093a9b55f2b0 Mon Sep 17 00:00:00 2001 From: gpotter2 Date: Thu, 15 Sep 2016 21:45:40 +0200 Subject: [PATCH 05/22] Added tests.js Forgot to add it... --- test/test.js | 99 +++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 86 insertions(+), 13 deletions(-) diff --git a/test/test.js b/test/test.js index 10642d8..008df11 100644 --- a/test/test.js +++ b/test/test.js @@ -10,22 +10,95 @@ var timeoutMs = 3000; var winston = require("winston"); winston.level="error"; +describe('- Testing utils.js', function () { + var utils = require("../lib/utils.js"); + describe('* getAbsolutePath', function () { + it('should return existing path file', function () { + var path = utils.getAbsolutePath("test.js"); + assert.doesNotThrow(function(){fs.accessSync(path, fs.F_OK)}); + }); }); - it('should contain initialized getRemainingTimeInMillis', function() { - assert.equal((lambdalocal.context.getRemainingTimeInMillis() <= timeoutMs), true); + describe('* generateRandomHex', function () { + it('should not return twice the same value', function () { + var first = utils.generateRandomHex(10); + var second = utils.generateRandomHex(10); + assert.notEqual(first, second); + }); }); - }); -}; - -lambdalocal.execute({ - event: require(path.join(__dirname, './test-event.js')), - lambdaPath: path.join(__dirname, './test-func.js'), - lambdaHandler: functionName, - profilePath: path.join(__dirname, './debug.aws'), - callbackWaitsForEmptyEventLoop: true, - timeoutMs: timeoutMs, - callback: callbackFunc }); +describe('- Testing lambdalocal.js', function () { + describe('* Basic Run', function () { + var done, err; + before(function (cb) { + var lambdalocal = require('../lib/lambdalocal.js'); + lambdalocal.setLogger(winston); + lambdalocal.execute({ + event: require(path.join(__dirname, './events/test-event.js')), + lambdaPath: path.join(__dirname, './functs/test-func.js'), + lambdaHandler: functionName, + callbackWaitsForEmptyEventLoop: false, + timeoutMs: timeoutMs, + callback: function (_err, _done) + { + err = _err; + done = _done; + cb(); + } + }); + }); + describe('# LambdaLocal', function () { + it('should return correct testvar', function () { + assert.equal(done.result, "testvar"); + }); + }); + + describe('# Context object', function () { + it('should contain initialized functionName', function () { + assert.equal(done.context.functionName, functionName); + }); + it('should contain initialized awsRequestId', function () { + assert.equal(done.context.awsRequestId.length, 36); + }); + it('should contain initialized getRemainingTimeInMillis', function () { + assert.isAtMost(done.context.getRemainingTimeInMillis(), timeoutMs); + }); + it('should contain done function', function () { + assert.isDefined(done.context.done); + }); + it('should contain succeed function', function () { + assert.isDefined(done.context.succeed); + }); + it('should contain fail function', function () { + assert.isDefined(done.context.fail); + }); + }); + }); + + describe('* AWS Profile Test Run', function () { + var done, err; + before(function (cb) { + var lambdalocal = require('../lib/lambdalocal.js'); + lambdalocal.setLogger(winston); + lambdalocal.execute({ + event: require(path.join(__dirname, './events/test-event.js')), + lambdaPath: path.join(__dirname, './functs/test-func-awsprofile.js'), + lambdaHandler: functionName, + profilePath: path.join(__dirname, './other/debug.aws'), + callbackWaitsForEmptyEventLoop: false, + timeoutMs: timeoutMs, + callback: function (_err, _done) + { + done = _done; + err = _err; + cb(); + } + }); + }); + describe('# AWS credentials', function () { + it('should return correct credentials', function () { + assert.equal(done.key, "AKIAIOSFODNN7EXAMPLE"); + assert.equal(done.secret, "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"); + }); }); }); }); \ No newline at end of file From 96fc93580f4782086300582d13d06c969d4634da Mon Sep 17 00:00:00 2001 From: gpotter2 Date: Thu, 15 Sep 2016 21:51:39 +0200 Subject: [PATCH 06/22] Fixed const bug Travis CI build would crash... --- lib/utils.js | 2 +- test/test.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/utils.js b/lib/utils.js index 4a1f569..4d0802b 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -1,4 +1,4 @@ -'use strict'; +'use strict' /** * Requires diff --git a/test/test.js b/test/test.js index 008df11..a51af27 100644 --- a/test/test.js +++ b/test/test.js @@ -1,4 +1,4 @@ -'use strict'; +'use strict' var assert = require('chai').assert; var path = require('path'); From f5562002c532b69f3d6a1a0a146501efab1c93f3 Mon Sep 17 00:00:00 2001 From: gpotter2 Date: Thu, 15 Sep 2016 21:55:10 +0200 Subject: [PATCH 07/22] Harmony fix Will now work on node 0.12- --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index c5f3532..f916742 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,5 +10,5 @@ sudo: false # directories: # - node_modules script: - - "cd test && ../node_modules/mocha/bin/mocha" + - "cd test && ../node_modules/mocha/bin/mocha --harmony-proxies" From ec6f5f05b4c36a5eccee0f82050fc3b82b6a8f56 Mon Sep 17 00:00:00 2001 From: gpotter2 Date: Thu, 15 Sep 2016 21:58:54 +0200 Subject: [PATCH 08/22] Abandonned old nodejs... ... --- .travis.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index f916742..34d0593 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,6 @@ language: node_js node_js: - - "0.10" - - "0.12" + - "1.8" - "4" - "6" - "node" @@ -10,5 +9,5 @@ sudo: false # directories: # - node_modules script: - - "cd test && ../node_modules/mocha/bin/mocha --harmony-proxies" + - "cd test && ../node_modules/mocha/bin/mocha" From e21672d908f9ecb600f6246cd552b16bdf065975 Mon Sep 17 00:00:00 2001 From: Gabriel Potter Date: Sun, 18 Sep 2016 17:22:30 +0200 Subject: [PATCH 09/22] Removed extra indents --- lib/lambdalocal.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/lambdalocal.js b/lib/lambdalocal.js index 90ecd67..e587ef7 100755 --- a/lib/lambdalocal.js +++ b/lib/lambdalocal.js @@ -26,8 +26,8 @@ var _execute = function(opts) { callbackWaitsForEmptyEventLoop = (typeof opts.callbackWaitsForEmptyEventLoop === 'undefined') ? true : opts.callbackWaitsForEmptyEventLoop, timeoutMs = (typeof opts.timeoutMs === 'undefined') ? 3000 : opts.timeoutMs, - muteLogs = opts.mute, - unmute = null, + muteLogs = opts.mute, + unmute = null, callback = opts.callback; if(muteLogs) { @@ -78,4 +78,4 @@ module.exports = { context: _context, execute: _execute, setLogger: _setLogger -}; \ No newline at end of file +}; From 801d377c7d6c2f4403f728a584ac9d4d01f2a1ba Mon Sep 17 00:00:00 2001 From: Gabriel Potter Date: Sun, 18 Sep 2016 17:22:59 +0200 Subject: [PATCH 10/22] Replaced tab by 4spaces --- lib/lambdalocal.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/lambdalocal.js b/lib/lambdalocal.js index e587ef7..b80faac 100755 --- a/lib/lambdalocal.js +++ b/lib/lambdalocal.js @@ -77,5 +77,5 @@ var _execute = function(opts) { module.exports = { context: _context, execute: _execute, - setLogger: _setLogger + setLogger: _setLogger }; From 4028af99d10822f6b27e5c825350ae217052e58e Mon Sep 17 00:00:00 2001 From: Gabriel Potter Date: Sun, 18 Sep 2016 17:48:58 +0200 Subject: [PATCH 11/22] Add logger type check --- lib/lambdalocal.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/lambdalocal.js b/lib/lambdalocal.js index b80faac..3121f51 100755 --- a/lib/lambdalocal.js +++ b/lib/lambdalocal.js @@ -13,7 +13,11 @@ const mute = require('mute'), _context = require('./context.js'); var _setLogger = function(_logger){ - logger = _logger; + if(_logger != null && typeof _logger.loggers != 'undefined'){ + logger = _logger; + } else { + throw new TypeError("The object must be a winston logger !"); + } } var _execute = function(opts) { From 8fc0cf828df4cbd1ef83b34972dabcdfb8efbdd1 Mon Sep 17 00:00:00 2001 From: Ahmad Shiina Date: Tue, 20 Sep 2016 10:27:09 +0900 Subject: [PATCH 12/22] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0f3a334..36e1603 100644 --- a/README.md +++ b/README.md @@ -85,7 +85,7 @@ http://docs.aws.amazon.com/cli/latest/userguide/cli-chap-getting-started.html#cl ## API -### LambdLocal +### LambdaLocal #### `execute(options)` From 4f4acd3dac404f1760080e0251d35dd93eb61ebd Mon Sep 17 00:00:00 2001 From: Ahmad Shiina Date: Tue, 20 Sep 2016 11:50:06 +0900 Subject: [PATCH 13/22] Update CHANGELOG.md --- CHANGELOG.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4c5a509..a6d24a3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,12 @@ # ChangeLog -## 1.0.0 (2016/6/xx) +## 1.1.0 (2016/9/15) + * The default behavior of lambda-local now does not forcefully call the callback function (`-c` option). + * Dropped Node.js v0.1, v0.12 suport + * Added AWS region option `-r`. Defaults to `us-east-1`. + * Added AWS profile name option `-p`. + +## 1.0.0 (2016/6/10) * lambda-local can now be imported as a node module, and be executed from other node.js programs ## 0.0.10 (2016/5/29) From f816f518155ed153a95298d3db0aeaf2f738af9a Mon Sep 17 00:00:00 2001 From: Gabriel Potter Date: Sat, 24 Sep 2016 00:00:34 +0200 Subject: [PATCH 14/22] Changed syntax of an ambiguous sentence Replaced ambiguous help sentence --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 36e1603..e45f31e 100644 --- a/README.md +++ b/README.md @@ -96,7 +96,7 @@ Executes a lambda given the `options` object where keys are: - `profileName` - optional aws profile name - `lambdaHandler` - optional handler name, default to `handler` - `region` - optional AWS region, default to `us-east-1` -- `callbackWaitsForEmptyEventLoop` - optional, default to `true` which forces the function to stop after having called the handler function even if context.done/succeed/fail was not called. +- `callbackWaitsForEmptyEventLoop` - optional, default to `true`. Setting it to `false` will force the function to stop after having called the handler function even if context.done/succeed/fail was not called. - `timeoutMs` - optional timeout, default to 3000 ms - `mute` - optional, allows to mute console.log calls in the lambda function, default false - `callback` - optional lambda third parameter [callback][3] From cab2ee008f7cd610adcb8df840a9cfc37219ba5a Mon Sep 17 00:00:00 2001 From: Yaswanthgandra Date: Thu, 29 Sep 2016 12:26:54 +0800 Subject: [PATCH 15/22] Update Readme --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index e45f31e..8973df9 100644 --- a/README.md +++ b/README.md @@ -60,8 +60,9 @@ lambdaLocal.execute({ * -h, --handler (optional) Lambda function handler name. Default is "handler". * -t, --timeout (optional) Seconds until lambda function timeout. Default is 3 seconds. * -n, --no-force-callback (optional) Force the function to stop after having called the handler function even if context.done/succeed/fail was not called. +* -r, --region (optional) Sets the AWS region, defaults to us-east-1. * -p, --profile (optional) Read the AWS profile to get the credentials from file name. -* -p, --profile-path (optional) Read the specified AWS credentials file. +* -P, --profile-path (optional) Read the specified AWS credentials file. ### Event data Event sample data are placed in `event-samples` folder - feel free to use the files in here, or create your own event data. From 3e7b0a120b0e2825adfc95569279dba0bc5504fe Mon Sep 17 00:00:00 2001 From: Gabriel Potter Date: Thu, 6 Oct 2016 20:47:21 +0200 Subject: [PATCH 16/22] Update README.md Moved the line -p under the depedency -P --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8973df9..15c87ae 100644 --- a/README.md +++ b/README.md @@ -61,8 +61,8 @@ lambdaLocal.execute({ * -t, --timeout (optional) Seconds until lambda function timeout. Default is 3 seconds. * -n, --no-force-callback (optional) Force the function to stop after having called the handler function even if context.done/succeed/fail was not called. * -r, --region (optional) Sets the AWS region, defaults to us-east-1. -* -p, --profile (optional) Read the AWS profile to get the credentials from file name. * -P, --profile-path (optional) Read the specified AWS credentials file. +* -p, --profile (optional) Use with **-P**: Read the AWS profile of the file. ### Event data Event sample data are placed in `event-samples` folder - feel free to use the files in here, or create your own event data. From d9b68340b33ebcbd4208a2463afb12cfd4914860 Mon Sep 17 00:00:00 2001 From: gpotter2 Date: Fri, 11 Nov 2016 13:48:27 +0100 Subject: [PATCH 17/22] Fixed some syntax + added mock function (+tests) --- lib/context.js | 7 +- lib/lambdalocal.js | 45 +++++----- package.json | 3 +- test/functs/test-func-mocking.js | 11 +++ test/functs/test-func.js | 2 +- test/test.js | 140 ++++++++++++++++++------------- 6 files changed, 127 insertions(+), 81 deletions(-) create mode 100644 test/functs/test-func-mocking.js diff --git a/lib/context.js b/lib/context.js index f776feb..cf76b5c 100644 --- a/lib/context.js +++ b/lib/context.js @@ -48,8 +48,6 @@ Context.clientContext = null; * callback function called after done */ Context.callback = function(result) { - if(unmute != null) {unmute(); unmute = null;} - logger.log('info', 'This is the default context callback'); return result; }; @@ -96,6 +94,9 @@ Context._initialize = function(options) { */ Context.done = function(err, message) { if(unmute != null) {unmute(); unmute = null;} + if (!Context.callbackWaitsForEmptyEventLoop) { + Context.callback(err, message); + } logger.log('info', 'END'); if (err !== null) { logger.log('error', 'Error'); @@ -106,7 +107,7 @@ Context.done = function(err, message) { logger.log('info', '------'); utils.outputJSON(message, logger); } - if (!Context.callbackWaitsForEmptyEventLoop) { + if (Context.callbackWaitsForEmptyEventLoop) { Context.callback(err, message); } }; diff --git a/lib/lambdalocal.js b/lib/lambdalocal.js index 3121f51..203560b 100755 --- a/lib/lambdalocal.js +++ b/lib/lambdalocal.js @@ -11,33 +11,38 @@ var logger = require('winston'); const mute = require('mute'), utils = require('./utils.js'), _context = require('./context.js'); - + var _setLogger = function(_logger){ - if(_logger != null && typeof _logger.loggers != 'undefined'){ - logger = _logger; - } else { - throw new TypeError("The object must be a winston logger !"); - } + if(_logger != null && typeof _logger.loggers != 'undefined'){ + logger = _logger; + } else { + throw new TypeError("The object must be a winston logger !"); + } } var _execute = function(opts) { var event = opts.event, + lambdaFunc = opts.lambdaFunc, lambdaPath = opts.lambdaPath, lambdaHandler = opts.lambdaHandler || 'handler', profilePath = opts.profilePath, profileName = opts.profileName, region = opts.region || 'us-east-1', - callbackWaitsForEmptyEventLoop = (typeof opts.callbackWaitsForEmptyEventLoop === 'undefined') ? - true : opts.callbackWaitsForEmptyEventLoop, - timeoutMs = (typeof opts.timeoutMs === 'undefined') ? 3000 : opts.timeoutMs, - muteLogs = opts.mute, - unmute = null, + callbackWaitsForEmptyEventLoop = opts.callbackWaitsForEmptyEventLoop || true, + timeoutMs = opts.timeoutMs || 3000, + muteLogs = opts.mute, + unmute = null, callback = opts.callback; - if(muteLogs) { - unmute = mute(); - } - + if (lambdaFunc && lambdaPath) { + throw new SyntaxError("Cannot specify both lambdaFunc and lambdaPath !"); + return; + } + + if(muteLogs) { + unmute = mute(); + } + //load profile if (profilePath) { utils.loadAWSCredentials(profilePath, profileName); @@ -46,8 +51,10 @@ var _execute = function(opts) { // set region before the require process.env['AWS_REGION'] = region; - // load lambda function - var lambdaFunc = require(utils.getAbsolutePath(lambdaPath)); + if (!(lambdaFunc)){ + // load lambda function + lambdaFunc = require(utils.getAbsolutePath(lambdaPath)); + } // load event & context var context = this.context; @@ -57,8 +64,8 @@ var _execute = function(opts) { awsRequestId: context.createInvokeId, timeoutMs: timeoutMs, callbackWaitsForEmptyEventLoop: callbackWaitsForEmptyEventLoop, - unmute: unmute, - logger: logger + unmute: unmute, + logger: logger }); // export the LAMBDA_TASK_ROOT enviroment variable process.env['LAMBDA_TASK_ROOT'] = process.cwd(); diff --git a/package.json b/package.json index 985ac11..8d12e77 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,8 @@ "winston": "^2.2.0" }, "devDependencies": { - "mocha": "^2.4.5" + "mocha": "^2.4.5", + "sinon": "^1.17.6" }, "scripts": { "test": "mocha" diff --git a/test/functs/test-func-mocking.js b/test/functs/test-func-mocking.js new file mode 100644 index 0000000..616dceb --- /dev/null +++ b/test/functs/test-func-mocking.js @@ -0,0 +1,11 @@ +/* + * Lambda function used to test mocking. + */ +exports.getData = function getData(){ + return "WrongData"; +} +exports.handler = function(event, context) { + context.done(null, exports.getData()); +}; + + diff --git a/test/functs/test-func.js b/test/functs/test-func.js index 2b9bc15..0403551 100644 --- a/test/functs/test-func.js +++ b/test/functs/test-func.js @@ -1,5 +1,5 @@ /* - * Lambda function used for test. + * Lambda function used for basic test. */ exports.handler = function(event, context) { var answer = {"result": event.key, "context": context}; diff --git a/test/test.js b/test/test.js index a51af27..54c4b36 100644 --- a/test/test.js +++ b/test/test.js @@ -1,101 +1,127 @@ -'use strict' +"use strict"; -var assert = require('chai').assert; -var path = require('path'); -var fs = require('fs'); +var assert = require("chai").assert; +var path = require("path"); +var fs = require("fs"); -var functionName = 'handler'; +var functionName = "handler"; var timeoutMs = 3000; +var sinon = require("sinon"); + var winston = require("winston"); -winston.level="error"; +winston.level = "error"; -describe('- Testing utils.js', function () { +describe("- Testing utils.js", function () { var utils = require("../lib/utils.js"); - describe('* getAbsolutePath', function () { - it('should return existing path file', function () { - var path = utils.getAbsolutePath("test.js"); - assert.doesNotThrow(function(){fs.accessSync(path, fs.F_OK)}); + describe("* getAbsolutePath", function () { + it("should return existing path file", function () { + var f_path = utils.getAbsolutePath("test.js"); + assert.doesNotThrow(function(){fs.accessSync(f_path, fs.F_OK)}); }); }); - describe('* generateRandomHex', function () { - it('should not return twice the same value', function () { + describe("* generateRandomHex", function () { + it("should not return twice the same value", function () { var first = utils.generateRandomHex(10); var second = utils.generateRandomHex(10); assert.notEqual(first, second); }); }); }); -describe('- Testing lambdalocal.js', function () { - describe('* Basic Run', function () { +describe("- Testing lambdalocal.js", function () { + describe("* Basic Run", function () { var done, err; before(function (cb) { - var lambdalocal = require('../lib/lambdalocal.js'); + var lambdalocal = require("../lib/lambdalocal.js"); lambdalocal.setLogger(winston); lambdalocal.execute({ - event: require(path.join(__dirname, './events/test-event.js')), - lambdaPath: path.join(__dirname, './functs/test-func.js'), - lambdaHandler: functionName, - callbackWaitsForEmptyEventLoop: false, - timeoutMs: timeoutMs, - callback: function (_err, _done) - { + event: require(path.join(__dirname, "./events/test-event.js")), + lambdaPath: path.join(__dirname, "./functs/test-func.js"), + lambdaHandler: functionName, + callbackWaitsForEmptyEventLoop: false, + timeoutMs: timeoutMs, + callback: function (_err, _done) { err = _err; done = _done; cb(); } }); }); - describe('# LambdaLocal', function () { - it('should return correct testvar', function () { - assert.equal(done.result, "testvar"); - }); + describe("# LambdaLocal", function () { + it("should return correct testvar", function () { + assert.equal(done.result, "testvar"); + }); + }); + + describe("# Context object", function () { + it("should contain initialized functionName", function () { + assert.equal(done.context.functionName, functionName); + }); + it("should contain initialized awsRequestId", function () { + assert.equal(done.context.awsRequestId.length, 36); + }); + it("should contain initialized getRemainingTimeInMillis", function () { + assert.isAtMost(done.context.getRemainingTimeInMillis(), timeoutMs); + }); + it("should contain done function", function () { + assert.isDefined(done.context.done); + }); + it("should contain succeed function", function () { + assert.isDefined(done.context.succeed); + }); + it("should contain fail function", function () { + assert.isDefined(done.context.fail); + }); }); - - describe('# Context object', function () { - it('should contain initialized functionName', function () { - assert.equal(done.context.functionName, functionName); - }); - it('should contain initialized awsRequestId', function () { - assert.equal(done.context.awsRequestId.length, 36); - }); - it('should contain initialized getRemainingTimeInMillis', function () { - assert.isAtMost(done.context.getRemainingTimeInMillis(), timeoutMs); - }); - it('should contain done function', function () { - assert.isDefined(done.context.done); - }); - it('should contain succeed function', function () { - assert.isDefined(done.context.succeed); - }); - it('should contain fail function', function () { - assert.isDefined(done.context.fail); - }); + describe("* Mocked function", function () { + var done, err; + before(function (cb) { + var lambdalocal = require("../lib/lambdalocal.js"); + lambdalocal.setLogger(winston); + var lambdaFunc = require("./functs/test-func-mocking.js"); + sinon.mock(lambdaFunc).expects("getData").returns("MockedData"); + lambdalocal.execute({ + event: require(path.join(__dirname, "./events/test-event.js")), + lambdaFunc: lambdaFunc, + lambdaHandler: functionName, + callbackWaitsForEmptyEventLoop: false, + timeoutMs: timeoutMs, + callback: function (_err, _done) { + err = _err; + done = _done; + cb(); + } + }); + }); + describe("# LambdaLocal", function () { + it("should return mocked value", function () { + assert.equal(done, "MockedData"); + }); + }); }); }); - - describe('* AWS Profile Test Run', function () { + + describe("* AWS Profile Test Run", function () { var done, err; before(function (cb) { - var lambdalocal = require('../lib/lambdalocal.js'); + var lambdalocal = require("../lib/lambdalocal.js"); lambdalocal.setLogger(winston); lambdalocal.execute({ - event: require(path.join(__dirname, './events/test-event.js')), - lambdaPath: path.join(__dirname, './functs/test-func-awsprofile.js'), + event: require(path.join(__dirname, "./events/test-event.js")), + lambdaPath: path.join(__dirname, "./functs/test-func-awsprofile.js"), lambdaHandler: functionName, - profilePath: path.join(__dirname, './other/debug.aws'), + profilePath: path.join(__dirname, "./other/debug.aws"), callbackWaitsForEmptyEventLoop: false, timeoutMs: timeoutMs, - callback: function (_err, _done) - { + callback: function (_err, _done) { done = _done; err = _err; cb(); } }); }); - describe('# AWS credentials', function () { - it('should return correct credentials', function () { + describe("# AWS credentials", function () { + it("should return correct credentials", function () { assert.equal(done.key, "AKIAIOSFODNN7EXAMPLE"); assert.equal(done.secret, "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"); }); From 3f37f5e6b690e1ccbded424b2da4435ea19cd895 Mon Sep 17 00:00:00 2001 From: gpotter2 Date: Fri, 11 Nov 2016 16:51:08 +0100 Subject: [PATCH 18/22] Edited Doc Added new API samples + corrected doc --- README.md | 52 ++++++++++------------------ REQUIRE_SAMPLES.md | 85 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 102 insertions(+), 35 deletions(-) create mode 100644 REQUIRE_SAMPLES.md diff --git a/README.md b/README.md index 15c87ae..74d41f5 100644 --- a/README.md +++ b/README.md @@ -25,32 +25,10 @@ lambda-local -l index.js -h handler -e event-samples/s3-put.js ### In another node.js script -You can also use Lambda local directly in a script. For instance, it is interesting in a [mocha][1] test suite in combination with [istanbull][2] in order to get test coverage. -```js -const lambdaLocal = require('lambda-local'); - -var jsonPayload = { - 'key1': 'value1', - 'key2': 'value2', - 'key3': 'value3' -} - -lambdaLocal.execute({ - event: jsonPayload, - lambdaPath: path.join(__dirname, 'path/to/index.js'), - profilePath: '~/.aws/credentials', - profileName: 'default', - timeoutMs: 3000, - callback: function(err, data) { - if (err) { - console.log(err); - } else { - console.log(data); - } - } -}); -``` +You can also use Lambda local directly in a script. For instance, it is interesting in a [MochaJS][1] test suite in order to get test coverage. + +See [API][#api] for more infos ## About @@ -93,20 +71,24 @@ http://docs.aws.amazon.com/cli/latest/userguide/cli-chap-getting-started.html#cl Executes a lambda given the `options` object where keys are: - `event` - requested event as a json object, - `lambdaPath` - requested path to the lambda function, -- `profilePath` - optional path to your AWS credentials file -- `profileName` - optional aws profile name +- `lambdaFunc` - pass the lambda function. You cannot use it at the same time as lambdaPath, +- `profilePath` - optional, path to your AWS credentials file +- `profileName` - optional, aws profile name. Must be used with - `lambdaHandler` - optional handler name, default to `handler` -- `region` - optional AWS region, default to `us-east-1` -- `callbackWaitsForEmptyEventLoop` - optional, default to `true`. Setting it to `false` will force the function to stop after having called the handler function even if context.done/succeed/fail was not called. -- `timeoutMs` - optional timeout, default to 3000 ms +- `region` - optional, AWS region, default to `us-east-1` +- `callbackWaitsForEmptyEventLoop` - optional, default to `true`. Setting it to `false` will call the callback when your code do, before finishing lambda-local. +- `timeoutMs` - optional, timeout, default to 3000 ms - `mute` - optional, allows to mute console.log calls in the lambda function, default false -- `callback` - optional lambda third parameter [callback][3] +- `callback` - optional, lambda third parameter [callback][1] + +#### `setLogger(logger)` + +If you are using [winston][https://www.npmjs.com/package/winston], this pass a winston logger instead of the console. + +#### [Samples][REQUIRE_SAMPLES.md] ## License This library is released under the MIT license. -[1]: https://mochajs.org/ -[2]: http://gotwarlost.github.io/istanbul/ -[3]: http://docs.aws.amazon.com/lambda/latest/dg/nodejs-prog-model-handler.html - +[1]: http://docs.aws.amazon.com/lambda/latest/dg/nodejs-prog-model-handler.html diff --git a/REQUIRE_SAMPLES.md b/REQUIRE_SAMPLES.md new file mode 100644 index 0000000..3e7ab1e --- /dev/null +++ b/REQUIRE_SAMPLES.md @@ -0,0 +1,85 @@ +## Samples for API + +#### Basic: In another node.js script + +```js +const lambdaLocal = require('lambda-local'); + +var jsonPayload = { + 'key': 1, + 'another_key': "Some text" +} + +lambdaLocal.execute({ + event: jsonPayload, + lambdaPath: path.join(__dirname, 'path_to_index.js'), + profilePath: '~/.aws/credentials', + profileName: 'default', + timeoutMs: 3000, + callback: function(err, data) { + if (err) { + console.log(err); + } else { + console.log(data); + } + } +}); +``` + +### Use lambda-local to Mock + +You can use Lambda local to mock your lambda then run it, using [MochaJS][1] and [SinonJS][2] + +In this sample, we assume that you got a test function like this: +```js +/* + * Lambda function used to test mocking. + */ +exports.getData = function getData(){ + return "WrongData"; +} +exports.handler = function(event, context) { + context.done(null, exports.getData()); +}; +``` + + + +```js + + //An empty event + var jsonPayload = { + + } + + var done, err; + before(function (cb) { + var lambdalocal = require('lambda-local'); + lambdalocal.setLogger(your_winston_logger); + var lambdaFunc = require("path_to_test-function.js"); + //For instance, this will replace the getData content + sinon.mock(lambdaFunc).expects("getData").returns("MockedData"); + //see on sinonjs page for more options + lambdalocal.execute({ + event: jsonPayload, + lambdaFunc: lambdaFunc, //We are directly passing the lambda function + lambdaHandler: "handler", + callbackWaitsForEmptyEventLoop: true, + timeoutMs: 3000, + callback: function (_err, _done) { //We are storing the results and finishing the before() call => one lambda local call for multiple tests + err = _err; + done = _done; + cb(); + } + }); + describe("Your first test", function () { + it("should return mocked value", function () { + assert.equal(done, "MockedData"); + }); + }); + ... Other tests + }); +``` + +[1]: https://mochajs.org/ +[2]: http://sinonjs.org/ From 71d4119955244b6a693f049c4823fb559da57841 Mon Sep 17 00:00:00 2001 From: gpotter2 Date: Fri, 11 Nov 2016 16:55:06 +0100 Subject: [PATCH 19/22] Fixed links --- README.md | 6 +++--- REQUIRE_SAMPLES.md | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 74d41f5..d96379f 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ lambda-local -l index.js -h handler -e event-samples/s3-put.js You can also use Lambda local directly in a script. For instance, it is interesting in a [MochaJS][1] test suite in order to get test coverage. -See [API][#api] for more infos +See [API](#api) for more infos ## About @@ -83,9 +83,9 @@ Executes a lambda given the `options` object where keys are: #### `setLogger(logger)` -If you are using [winston][https://www.npmjs.com/package/winston], this pass a winston logger instead of the console. +If you are using [winston](https://www.npmjs.com/package/winston), this pass a winston logger instead of the console. -#### [Samples][REQUIRE_SAMPLES.md] +#### [Samples](REQUIRE_SAMPLES.md) ## License diff --git a/REQUIRE_SAMPLES.md b/REQUIRE_SAMPLES.md index 3e7ab1e..dda253a 100644 --- a/REQUIRE_SAMPLES.md +++ b/REQUIRE_SAMPLES.md @@ -43,7 +43,7 @@ exports.handler = function(event, context) { }; ``` - +Then you will be able to use in your test.js mocha file, something like: ```js From 0e851fe966b4c45b49cf7ef0183a7e6e60711082 Mon Sep 17 00:00:00 2001 From: Ahmad Shiina Date: Wed, 23 Nov 2016 20:55:57 +0900 Subject: [PATCH 20/22] Update CHANGELOG.md --- CHANGELOG.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a6d24a3..e49616b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,11 @@ # ChangeLog +## 1.1.1 (2016/11/23) + * Added mock functionality + * Dropped Node.js v0.1, v0.12 suport + ## 1.1.0 (2016/9/15) * The default behavior of lambda-local now does not forcefully call the callback function (`-c` option). - * Dropped Node.js v0.1, v0.12 suport * Added AWS region option `-r`. Defaults to `us-east-1`. * Added AWS profile name option `-p`. From a325412ded320bda8b4840057e94dcf6124f4bf1 Mon Sep 17 00:00:00 2001 From: Ahmad Shiina Date: Wed, 23 Nov 2016 20:56:14 +0900 Subject: [PATCH 21/22] Update package.json --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8d12e77..3332b3f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "lambda-local", - "version": "1.1.0", + "version": "1.2.0", "description": "Commandline tool to run Lambda functions on your local machine.", "keywords": [ "lambda", From 95db7b07f81d9440ad7c57c91ec6b7ac9dde5166 Mon Sep 17 00:00:00 2001 From: Ahmad Shiina Date: Wed, 23 Nov 2016 20:56:30 +0900 Subject: [PATCH 22/22] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e49616b..99a567a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # ChangeLog -## 1.1.1 (2016/11/23) +## 1.2.0 (2016/11/23) * Added mock functionality * Dropped Node.js v0.1, v0.12 suport