lambda-tester
Advanced tools
Comparing version
@@ -0,1 +1,12 @@ | ||
## 2.3.0 (TBD) | ||
New: | ||
* Automatically loads `.env` files when `lambda-tester` is loaded during `require()`. | ||
* Handlers can be loaded and cleaned up after execution | ||
Updated: | ||
* Leak detector moved to `lambda-leak` and included as a dependency. Still experimental status | ||
## 2.2.1 (2016-04-17) | ||
@@ -2,0 +13,0 @@ |
162
lib/index.js
'use strict'; | ||
const handleState = require( './handle_state' ); | ||
const lambdaLeak = require( 'lambda-leak' ); | ||
@@ -9,2 +9,3 @@ const DEFAULT_TIMEOUT = 3000; // s3 | ||
function createContext( tester ) { | ||
@@ -97,19 +98,2 @@ | ||
function addLegecyVerify( promise ) { | ||
promise.verify = function( done ) { | ||
return promise.then( | ||
function() { | ||
done(); | ||
}, | ||
function( err ) { | ||
done( err ); | ||
} | ||
); | ||
} | ||
} | ||
function verifyResult( tester, result, verifier, resolve, reject, savedHandleState ) { | ||
@@ -147,2 +131,70 @@ | ||
function resolveHandler( tester ) { | ||
return new Promise( function( resolve, reject ) { | ||
try { | ||
if( tester._loadHandler ) { | ||
tester._handler = tester._loadHandler() | ||
} | ||
if( !tester._handler ) { | ||
throw new Error( 'no handler specified or returned from loadHandler()' ); | ||
} | ||
resolve( tester._handler ); | ||
} | ||
catch( err ) { | ||
reject( err ); | ||
} | ||
}); | ||
} | ||
function addLegecyVerify( promise ) { | ||
promise.verify = function( done ) { | ||
return promise.then( | ||
function() { | ||
done(); | ||
}, | ||
function( err ) { | ||
done( err ); | ||
} | ||
); | ||
} | ||
} | ||
function addCleanup( promise, tester ) { | ||
let afterFunc = tester._afterFunc; | ||
if( !afterFunc ) { | ||
afterFunc = function() {}; | ||
} | ||
promise.then( | ||
function( result ) { | ||
afterFunc( result, true ); | ||
return result; | ||
}, | ||
function( err ) { | ||
afterFunc( err, false ); | ||
return Promise.reject( err ); | ||
} | ||
); | ||
} | ||
class LambdaTester { | ||
@@ -152,4 +204,7 @@ | ||
this._handler = handler; | ||
if( handler ) { | ||
this._handler = handler; | ||
} | ||
this._event = {}; | ||
@@ -160,2 +215,16 @@ | ||
loadHandler( loaderFunc ) { | ||
this._loadHandler = loaderFunc; | ||
return this; | ||
} | ||
after( afterFunc ) { | ||
this._afterFunc = afterFunc; | ||
return this; | ||
} | ||
event( evt ) { | ||
@@ -192,6 +261,6 @@ | ||
let succeedPromise = Promise.resolve() | ||
let promise = resolveHandler( this ) | ||
.then( function() { | ||
let savedHandleState = handleState.capture(); | ||
let savedHandleState = lambdaLeak.capture(); | ||
@@ -212,3 +281,3 @@ // need to do a nested promise because mocha injects a timer between the start of the | ||
var failError = new Error( 'encountered error but expected the handler to succeed' ); | ||
var failError = new Error( 'encountered error but expected the handler to succeed - cause: ' + err.message ); | ||
failError.cause = err; | ||
@@ -228,5 +297,7 @@ | ||
// support for v1 users | ||
addLegecyVerify( succeedPromise ); | ||
addLegecyVerify( promise ); | ||
return succeedPromise; | ||
addCleanup( promise, this ); | ||
return promise; | ||
} | ||
@@ -245,6 +316,6 @@ | ||
let failPromise = Promise.resolve() | ||
let promise = resolveHandler( this ) | ||
.then( function() { | ||
let savedHandleState = handleState.capture(); | ||
let savedHandleState = lambdaLeak.capture(); | ||
@@ -262,3 +333,3 @@ // need to do a nested promise because mocha injects a timer between the start of the | ||
var failError = new Error( 'encountered successful operation but expected failure' ); | ||
var failError = new Error( 'encountered successful operation but expected failure - result: ' + result ); | ||
failError.result = result; | ||
@@ -278,5 +349,7 @@ | ||
// support for v1 users | ||
addLegecyVerify( failPromise ); | ||
addLegecyVerify( promise ); | ||
return failPromise; | ||
addCleanup( promise, this ); | ||
return promise; | ||
} | ||
@@ -293,3 +366,3 @@ | ||
return Promise.resolve() | ||
let promise = resolveHandler( this ) | ||
.then( function() { | ||
@@ -299,3 +372,3 @@ | ||
// promise and .then() | ||
let savedHandleState = handleState.capture(); | ||
let savedHandleState = lambdaLeak.capture(); | ||
@@ -315,3 +388,3 @@ return new Promise( function( resolve, reject ) { | ||
let failError = new Error( 'expecting error' ); | ||
let failError = new Error( 'expecting error but got result: ' + result ); | ||
failError.result = result; | ||
@@ -325,2 +398,6 @@ | ||
}); | ||
addCleanup( promise, this ); | ||
return promise; | ||
} | ||
@@ -337,3 +414,3 @@ | ||
return Promise.resolve() | ||
let promise = resolveHandler( this ) | ||
.then( function() { | ||
@@ -343,3 +420,3 @@ | ||
// promise and .then() | ||
let savedHandleState = handleState.capture(); | ||
let savedHandleState = lambdaLeak.capture(); | ||
@@ -356,3 +433,3 @@ return new Promise( function( resolve, reject ) { | ||
let failError = new Error( 'expecting result' ); | ||
let failError = new Error( 'expecting result but error was thrown - cause: ' + err.message ); | ||
failError.cause = err; | ||
@@ -369,2 +446,6 @@ | ||
}); | ||
addCleanup( promise, this ); | ||
return promise; | ||
} | ||
@@ -375,7 +456,2 @@ } | ||
if( !handler ) { | ||
throw new Error( 'missing handler' ); | ||
} | ||
return new LambdaTester( handler ); | ||
@@ -392,2 +468,8 @@ } | ||
if( !process.env.LAMBDA_TESTER_NO_ENV ) { | ||
// configure env varaiables | ||
require( 'dotenv' ).config( { silent: true } ); | ||
} | ||
module.exports = LambdaTesterModule; |
{ | ||
"name": "lambda-tester", | ||
"version": "2.2.1", | ||
"version": "2.3.0", | ||
"description": "Unit/Integration tests for AWS Lambda handlers", | ||
"main": "index.js", | ||
"main": "lib/index.js", | ||
"keywords": [ | ||
@@ -16,3 +16,5 @@ "AWS", | ||
"integration testing", | ||
"serverless" | ||
"serverless", | ||
"environment", | ||
"variables" | ||
], | ||
@@ -34,2 +36,3 @@ "engines": { | ||
"chai": "^3.5.0", | ||
"freshy": "^1.0.2", | ||
"istanbul": "^0.4.2", | ||
@@ -40,4 +43,6 @@ "mocha": "^2.4.5", | ||
"dependencies": { | ||
"app-root-path": "^1.0.0" | ||
"app-root-path": "^1.0.0", | ||
"dotenv": "^2.0.0", | ||
"lambda-leak": "^1.0.0" | ||
} | ||
} |
275
README.md
@@ -13,4 +13,6 @@ [](https://travis-ci.org/vandium-io/lambda-tester) | ||
* Easily integrates with test frameworks | ||
* Handlers can be loaded and removed after execution | ||
* Lightweight and won't impact performance | ||
* Maps the environment variable `LAMBDA_TASK_ROOT` to the application's root | ||
* Automatically loads .env files | ||
* Works with Node 4.3.2+ | ||
@@ -76,249 +78,10 @@ | ||
## Documentation | ||
## Verifying Callbacks | ||
Complete documentation can be found in our [documentation](docs/main.md) page. | ||
## Projects Using `lambda-tester` | ||
To verify that `callback( null, result )` was called: | ||
* [vandium](https://github.com/vandium-io/vandium-node) - Secures and simplifies AWS Lambda handlers | ||
```js | ||
const LambdaTester = require( 'lambda-tester' ); | ||
// your favorite validation tool here | ||
const expect = require( 'chai' ).expect; | ||
const myHandler = require( '../index' ).handler; | ||
describe( 'handler', function() { | ||
it( 'test callback( null, result )', function() { | ||
return LambdaTester( myHandler ) | ||
.event( { name: 'Fred' } ) | ||
.expectResult( function( result ) { | ||
expect( result.userId ).to.exist; | ||
expect( result.user ).to.equal( 'fredsmith' ); | ||
}); | ||
}); | ||
}); | ||
``` | ||
To verify that `callback( err )` was called: | ||
```js | ||
const LambdaTester = require( 'lambda-tester' ); | ||
// your favorite validation tool here | ||
const expect = require( 'chai' ).expect; | ||
const myHandler = require( '../index' ).handler; | ||
describe( 'handler', function() { | ||
it( 'test callback( err )', function() { | ||
return LambdaTester( myHandler ) | ||
.event( { name: 'Unknown' } ) | ||
.expectError( function( err ) { | ||
expect( err.message ).to.equal( 'User not found' ); | ||
}); | ||
}); | ||
}); | ||
``` | ||
## Detecting Handlers than Run for Too Long | ||
For Lambda handlers that must run within a specific time period, you can specify a timeout value. This value will not stop execution of your code, but will detect an error condition. | ||
To use the timeout feature, specify a timeout value in seconds using `timeout()` as in the example below: | ||
```js | ||
const LambdaTester = require( 'lambda-tester' ); | ||
// your favorite validation tool here | ||
const expect = require( 'chai' ).expect; | ||
const myHandler = require( '../index' ).handler; | ||
describe( 'handler', function() { | ||
it( 'test callback( null, result )', function() { | ||
return LambdaTester( myHandler ) | ||
.event( { name: 'Fred' } ) | ||
.timeout( 1 /* fail if longer than 1 second */ ) | ||
.expectResult( function( result ) { | ||
expect( result.userId ).to.exist; | ||
expect( result.user ).to.equal( 'fredsmith' ); | ||
}); | ||
}); | ||
}); | ||
``` | ||
## Verifying `context.succeed()`, `context.fail` and `context.done()` | ||
On April 8, 2016 AWS Lambda introduced support for Lambda callbacks that replace the need to call `context.fail()` or `context.succeed()`. | ||
### Verifying `context.succeed()` | ||
When `expectSucceed()` is called, one can pass a function to perform additional validation. For example: | ||
```js | ||
const LambdaTester = require( 'lambda-tester' ); | ||
// your favorite validation tool here | ||
const expect = require( 'chai' ).expect; | ||
const myHandler = require( '../index' ).handler; | ||
describe( 'handler', function() { | ||
it( 'test success', function() { | ||
return LambdaTester( myHandler ) | ||
.event( { name: 'Fred' } ) | ||
.expectSucceed( function( result ) { | ||
expect( result.userId ).to.exist; | ||
expect( result.user ).to.equal( 'fredsmith' ); | ||
}); | ||
}); | ||
}); | ||
``` | ||
### Verifying `context.fail()` | ||
As with verifying success, `expectFail` has an optional parameter that can specify a function that will verify the error condition. For example: | ||
```js | ||
const LambdaTester = require( 'lambda-tester' ); | ||
// your favorite validation tool here | ||
const expect = require( 'chai' ).expect; | ||
const myHandler = require( '../index' ).handler; | ||
describe( 'handler', function() { | ||
it( 'test failure', function() { | ||
return LambdaTester( myHandler ) | ||
.event( { name: 'Unknown' } ) | ||
.expectFail( function( err ) { | ||
expect( err.message ).to.equal( 'User not found' ); | ||
}); | ||
}); | ||
}); | ||
``` | ||
### Verifying `context.done()` | ||
AWS Lambda routes `context.done()` to `context.succed()` and `context.fail()` for results or errors respectively, thus you can use the methods described above to verify those scenarios. | ||
## Resource Leak detection | ||
**Note**: This feature is experimental and disabled by default. | ||
Resource leaks (i.e. streams and other callback events like timers) can be detected and reported. Timers or streams than continue to be active post `callback()` will cause the Lambda handler to execute in the AWS environment until a timeout condition is reached. | ||
To enable leak detection: | ||
```js | ||
const LambdaTester = require( 'lambda-tester' ); | ||
LambdaTester.checkForResourceLeak( true ); | ||
``` | ||
When a leak is caught, it will cause an exception to be thrown on. For example, the following code will cause a timer to live past the callback: | ||
```js | ||
const LambdaTester = require( 'lambda-tester' ); | ||
const expect = require( 'chai' ).expect; | ||
const myHandler = require( '../index' ).handler; | ||
LambdaTester.checkForResourceLeak( true ); | ||
describe( 'handler', function() { | ||
it( 'test callback( null, result )', function() { | ||
return LambdaTester( function( event, context, callback ) { | ||
setTimeout( function() {}, 100 ); | ||
callback( null, 'ok' ); | ||
}) | ||
.expectResult( function( result ) { | ||
// will never get here | ||
}); | ||
}); | ||
}); | ||
``` | ||
Examining the exception thrown will indicate a leak was detected and the handles for the resources that are still open: | ||
```js | ||
{ [Error: Potential handle leakage detected] | ||
handles: | ||
[ Timer { | ||
'0': [Function: listOnTimeout], | ||
_idleNext: [Object], | ||
_idlePrev: [Object], | ||
msecs: 100 } ] } | ||
``` | ||
If you are adding leak detection as one of your unit tests, then the previous code should be changed to: | ||
```js | ||
const LambdaTester = require( 'lambda-tester' ); | ||
const expect = require( 'chai' ).expect; | ||
const myHandler = require( '../index' ).handler; | ||
LambdaTester.checkForResourceLeak( true ); | ||
describe( 'handler', function() { | ||
it( 'test callback( null, result )', function() { | ||
return LambdaTester( function( event, context, callback ) { | ||
setTimeout( function() {}, 100 ); | ||
callback( null, 'ok' ); | ||
}) | ||
.expectResult( function( result ) { | ||
throw new Error( 'should not produce a result' ); | ||
}) | ||
.catch( function( err ) { | ||
/* err will be: | ||
{ [Error: Potential handle leakage detected] | ||
handles: | ||
[ Timer { | ||
'0': [Function: listOnTimeout], | ||
_idleNext: [Object], | ||
_idlePrev: [Object], | ||
msecs: 100 } ] } | ||
*/ | ||
expect( err.message ).to.contain( 'Potential handle leakage detected' ); | ||
// TODO: add further validation here | ||
}); | ||
}); | ||
}); | ||
``` | ||
## Feedback | ||
@@ -328,3 +91,2 @@ | ||
## Compatibility | ||
@@ -338,26 +100,1 @@ | ||
[BSD-3-Clause](https://en.wikipedia.org/wiki/BSD_licenses) | ||
Copyright (c) 2016, Vandium Software Inc. | ||
All rights reserved. | ||
Redistribution and use in source and binary forms, with or without | ||
modification, are permitted provided that the following conditions are met: | ||
* Redistributions of source code must retain the above copyright | ||
notice, this list of conditions and the following disclaimer. | ||
* Redistributions in binary form must reproduce the above copyright | ||
notice, this list of conditions and the following disclaimer in the | ||
documentation and/or other materials provided with the distribution. | ||
* Neither the name of Vandium Software Inc. nor the | ||
names of its contributors may be used to endorse or promote products | ||
derived from this software without specific prior written permission. | ||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND | ||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY | ||
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | ||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | ||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | ||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | ||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
27370
12.62%3
200%5
25%7
-12.5%279
-0.71%97
-73.06%2
100%+ Added
+ Added
+ Added
+ Added