diff --git a/.travis.yml b/.travis.yml index 0206b36..34d0593 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,9 +1,9 @@ language: node_js node_js: - "1.8" - - "2.5" - - "3.3" - - "4.4" + - "4" + - "6" + - "node" sudo: false #cache: # directories: diff --git a/CHANGELOG.md b/CHANGELOG.md index 4c5a509..99a567a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,15 @@ # ChangeLog -## 1.0.0 (2016/6/xx) +## 1.2.0 (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). + * 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) diff --git a/README.md b/README.md index 0f3a334..d96379f 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 @@ -60,8 +38,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. -* -p, --profile (optional) Read the AWS profile to get the credentials from file name. -* -p, --profile-path (optional) Read the specified AWS credentials file. +* -r, --region (optional) Sets the AWS region, defaults to us-east-1. +* -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. @@ -85,27 +64,31 @@ http://docs.aws.amazon.com/cli/latest/userguide/cli-chap-getting-started.html#cl ## API -### LambdLocal +### LambdaLocal #### `execute(options)` 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` which forces 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..dda253a --- /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()); +}; +``` + +Then you will be able to use in your test.js mocha file, something like: + +```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/ 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..cf76b5c 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,7 @@ Context.clientContext = null; * callback function called after done */ Context.callback = function(result) { - console.log('default context callback'); - return result; + return result; }; /* @@ -55,13 +57,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 +71,68 @@ 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;} + if (!Context.callbackWaitsForEmptyEventLoop) { + Context.callback(err, message); + } + 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..203560b 100755 --- a/lib/lambdalocal.js +++ b/lib/lambdalocal.js @@ -6,81 +6,87 @@ * 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){ + 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, - 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(); - } + 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 = opts.callbackWaitsForEmptyEventLoop || true, + timeoutMs = opts.timeoutMs || 3000, + muteLogs = opts.mute, + unmute = null, + callback = opts.callback; - //load profile - if (profilePath) { - utils.loadAWSCredentials(profilePath, profileName); - } + 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); + } - // 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)); + if (!(lambdaFunc)){ + // load lambda function + 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 }; diff --git a/lib/utils.js b/lib/utils.js index 23cf9df..4d0802b 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -1,4 +1,4 @@ -'use strict'; +'use strict' /** * Requires @@ -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..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", @@ -16,12 +16,15 @@ }, "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" + "mocha": "^2.4.5", + "sinon": "^1.17.6" }, "scripts": { "test": "mocha" 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-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 new file mode 100644 index 0000000..0403551 --- /dev/null +++ b/test/functs/test-func.js @@ -0,0 +1,9 @@ +/* + * Lambda function used for basic 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..54c4b36 100644 --- a/test/test.js +++ b/test/test.js @@ -1,41 +1,130 @@ -'use strict'; +"use strict"; -var assert = require('assert'); -var path = require('path'); +var assert = require("chai").assert; +var path = require("path"); +var fs = require("fs"); -var functionName = 'handler'; +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 sinon = require("sinon"); - describe('Context object', function() { - it('should contain initialized functionName', function() { - assert.equal(lambdalocal.context.functionName, functionName); +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 f_path = utils.getAbsolutePath("test.js"); + assert.doesNotThrow(function(){fs.accessSync(f_path, fs.F_OK)}); + }); }); - it('should contain initialized awsRequestId', function() { - assert.equal(lambdalocal.context.awsRequestId.length === 36, 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); + }); }); - it('should contain initialized getRemainingTimeInMillis', function() { - assert.equal((lambdalocal.context.getRemainingTimeInMillis() <= timeoutMs), true); +}); +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("* 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"); + }); + }); + }); }); - }); -}; -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("* 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