Skip to content

Commit fc3a381

Browse files
committed
Merge branch 'test/middleware/add-cases-for-root-path-invoke' of github.com:jeremydaly/lambda-api into test/middleware/add-cases-for-root-path-invoke
2 parents d952612 + 1a805bf commit fc3a381

18 files changed

+8624
-5877
lines changed

.github/workflows/merge-to-main.yml renamed to .github/workflows/build.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
name: 'Merge to main'
1+
name: 'Build'
22

33
on:
44
push:
@@ -28,6 +28,8 @@ jobs:
2828
uses: actions/setup-node@v1
2929
with:
3030
node-version: ${{ matrix.node-version }}
31+
- name: Install NPM
32+
run: npm install -g npm@9
3133
- run: npm ci
3234
- run: npm run test-ci
3335
env:

.github/workflows/pull-request.yml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ jobs:
88
runs-on: ubuntu-latest
99
strategy:
1010
matrix:
11-
node: [14, 16, 18]
11+
node: [14, 16, 18, 20]
1212
name: Node ${{ matrix.node }}
1313
steps:
1414
- name: 'Checkout latest code'
@@ -19,6 +19,9 @@ jobs:
1919
uses: actions/setup-node@v3
2020
with:
2121
node-version: ${{ matrix.node }}
22+
cache: 'npm'
23+
- name: Install NPM
24+
run: npm install -g npm@9
2225
- name: Install dependencies
2326
run: npm ci
2427
- name: Run tests
@@ -36,6 +39,7 @@ jobs:
3639
uses: actions/setup-node@v3
3740
with:
3841
node-version: '16'
42+
cache: 'npm'
3943
- name: Install dependencies
4044
run: npm ci
4145
- name: Run ESLint

README.md

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[![Lambda API](https://www.jeremydaly.com/wp-content/uploads/2018/03/lambda-api-logo.svg)](https://serverless-api.com/)
22

3-
[![Build Status](https://github.com/jeremydaly/lambda-api/actions/workflows/build.yml/badge.svg)](https://github.com/jeremydaly/lambda-api/actions/workflows/build.yml)
3+
[![Build Status](https://github.com/jeremydaly/lambda-api/actions/workflows/build.yml/badge.svg?branch=main)](https://github.com/jeremydaly/lambda-api/actions/workflows/build.yml)
44
[![npm](https://img.shields.io/npm/v/lambda-api.svg)](https://www.npmjs.com/package/lambda-api)
55
[![npm](https://img.shields.io/npm/l/lambda-api.svg)](https://www.npmjs.com/package/lambda-api)
66
[![Coverage Status](https://coveralls.io/repos/github/jeremydaly/lambda-api/badge.svg?branch=main)](https://coveralls.io/github/jeremydaly/lambda-api?branch=main)
@@ -9,6 +9,11 @@
99

1010
Lambda API is a lightweight web framework for AWS Lambda using AWS API Gateway Lambda Proxy Integration or ALB Lambda Target Support. This closely mirrors (and is based on) other web frameworks like Express.js and Fastify, but is significantly stripped down to maximize performance with Lambda's stateless, single run executions.
1111

12+
## Using AWS SDK v2?
13+
14+
lambda-api@v1 is using AWS SDK v3.
15+
If you are using AWS SDK v2, please use lambda-api@v0.12.0.
16+
1217
## Simple Example
1318

1419
```javascript
@@ -147,6 +152,7 @@ Require the `lambda-api` module into your Lambda handler script and instantiate
147152
| serializer | `Function` | Optional object serializer function. This function receives the `body` of a response and must return a string. Defaults to `JSON.stringify` |
148153
| version | `String` | Version number accessible via the `REQUEST` object |
149154
| errorHeaderWhitelist | `Array` | Array of headers to maintain on errors |
155+
| s3Config | `Object` | Optional object to provide as config to S3 sdk. [S3ClientConfig](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/clients/client-s3/interfaces/s3clientconfig.html) |
150156

151157
```javascript
152158
// Require the framework and instantiate it with optional version and base parameters
@@ -481,21 +487,22 @@ res.sendStatus(403); // equivalent to res.status(403).send('Forbidden')
481487
The `header` method allows for you to set additional headers to return to the client. By default, just the `content-type` header is sent with `application/json` as the value. Headers can be added or overwritten by calling the `header()` method with two string arguments. The first is the name of the header and then second is the value. You can utilize multi-value headers by specifying an array with multiple values as the `value`, or you can use an optional third boolean parameter and append multiple headers.
482488

483489
```javascript
484-
api.get('/users', (req,res) => {
485-
res.header('content-type','text/html').send('<div>This is HTML</div>')
486-
})
490+
api.get('/users', (req, res) => {
491+
res.header('content-type', 'text/html').send('<div>This is HTML</div>');
492+
});
487493

488494
// Set multiple header values
489-
api.get('/users', (req,res) => {
490-
res.header('someHeader',['foo','bar').send({})
491-
})
495+
api.get('/users', (req, res) => {
496+
res.header('someHeader', ['foo', 'bar']).send({});
497+
});
492498

493499
// Set multiple header by adding to existing header
494-
api.get('/users', (req,res) => {
495-
res.header('someHeader','foo')
496-
.header('someHeader','bar',true) // append another value
497-
.send({})
498-
})
500+
api.get('/users', (req, res) => {
501+
res
502+
.header('someHeader', 'foo')
503+
.header('someHeader', 'bar', true) // append another value
504+
.send({});
505+
});
499506
```
500507

501508
**NOTE:** Header keys are converted and stored as lowercase in compliance with [rfc7540 8.1.2. HTTP Header Fields](https://tools.ietf.org/html/rfc7540) for HTTP/2. Header convenience methods (`getHeader`, `hasHeader`, and `removeHeader`) automatically ignore case.

__tests__/cookies.unit.js

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,39 @@ describe('Cookie Tests:', function() {
299299
})
300300
}) // end it
301301

302+
/**
303+
* There is no definitive standard on what the cookie value can contain.
304+
* The most restrictive definition I could find comes from Safari which only supports
305+
* the ASCII character set, excluding semi-colon, comma, backslash, and white space.
306+
*
307+
* The % character is also ambiguous, as it is used as part of the URL encoded scheme. For the purpose of this test, we will leave this character out.
308+
*
309+
* @see {@link https://stackoverflow.com/a/1969339 | This StackOverflow answer which provides more context regarding the cookie value}
310+
*/
311+
it('Parse cookie with the entire supported set of ASCII characters', async function() {
312+
let asciiCharacterSet = ' !"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~';
313+
314+
asciiCharacterSet =
315+
asciiCharacterSet.replace(' ', '')
316+
.replace(';', '')
317+
.replace(',', '')
318+
.replace('/', '')
319+
.replace('%', '');
320+
321+
let _event = Object.assign({},event,{
322+
path: '/cookieParse',
323+
multiValueHeaders: {
324+
cookie: [`test=${asciiCharacterSet}`]
325+
}
326+
})
327+
let result = await new Promise(r => api.run(_event,{},(e,res) => { r(res) }))
328+
expect(JSON.parse(result.body)).toEqual({
329+
cookies: {
330+
test: asciiCharacterSet,
331+
},
332+
})
333+
}) // end it
334+
302335
it('Parse & decode two cookies', async function() {
303336
let _event = Object.assign({},event,{
304337
path: '/cookieParse',
@@ -330,6 +363,31 @@ describe('Cookie Tests:', function() {
330363
})
331364
}) // end it
332365

366+
it('Parse & decode multiple cookies with the entire supported set of ASCII characters', async function() {
367+
let asciiCharacterSet = ' !"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~';
368+
369+
asciiCharacterSet =
370+
asciiCharacterSet.replace(' ', '')
371+
.replace(';', '')
372+
.replace(',', '')
373+
.replace('/', '')
374+
.replace('%', '');
375+
376+
let _event = Object.assign({},event,{
377+
path: '/cookieParse',
378+
multiValueHeaders: {
379+
cookie: [`test=${asciiCharacterSet}; test2=${asciiCharacterSet}`]
380+
}
381+
})
382+
let result = await new Promise(r => api.run(_event,{},(e,res) => { r(res) }))
383+
expect(JSON.parse(result.body)).toEqual({
384+
cookies: {
385+
test: asciiCharacterSet,
386+
test2: asciiCharacterSet,
387+
},
388+
})
389+
}) // end it
390+
333391
}) // end parse tests
334392

335393
describe("Clear", function() {

__tests__/download.unit.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ const fs = require('fs') // Require Node.js file system
77
// Require Sinon.js library
88
const sinon = require('sinon')
99

10-
const AWS = require('aws-sdk') // AWS SDK (automatically available in Lambda)
1110
const S3 = require('../lib/s3-service') // Init S3 Service
1211

1312
// Init API instance

__tests__/getLink.unit.js

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,6 @@ const delay = ms => new Promise(res => setTimeout(res, ms))
55
// Require Sinon.js library
66
const sinon = require('sinon')
77

8-
const AWS = require('aws-sdk') // AWS SDK (automatically available in Lambda)
9-
// AWS.config.credentials = new AWS.SharedIniFileCredentials({profile: 'madlucas'})
10-
118
const S3 = require('../lib/s3-service') // Init S3 Service
129

1310
// Init API instance

__tests__/headers.unit.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,13 @@ api.get('/hasHeader', function(req,res) {
9090
})
9191
})
9292

93+
api.get('/setHeader', function(req,res) {
94+
res.status(200).header('TestHeader','test').setHeader('NewHeader','test')
95+
res.json({
96+
headers: res.getHeaders()
97+
})
98+
});
99+
93100
api.get('/removeHeader', function(req,res) {
94101
res.status(200).header('TestHeader','test').header('NewHeader','test').removeHeader('testHeader')
95102
res.json({
@@ -243,6 +250,20 @@ describe('Header Tests:', function() {
243250
})
244251
}) // end it
245252

253+
it('Set Header', async function() {
254+
let _event = Object.assign({},event,{ path: '/setHeader'})
255+
let result = await new Promise(r => api.run(_event,{},(e,res) => { r(res) }))
256+
expect(result).toEqual({
257+
multiValueHeaders: {
258+
'content-type': ['application/json'],
259+
'testheader': ['test'],
260+
'newheader': ['test']
261+
}, statusCode: 200,
262+
body: "{\"headers\":{\"content-type\":[\"application/json\"],\"testheader\":[\"test\"],\"newheader\":[\"test\"]}}",
263+
isBase64Encoded: false
264+
})
265+
}) // end it
266+
246267
it('Remove Header', async function() {
247268
let _event = Object.assign({},event,{ path: '/removeHeader'})
248269
let result = await new Promise(r => api.run(_event,{},(e,res) => { r(res) }))

__tests__/responses.unit.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
'use strict';
22

33
const sinon = require('sinon') // Require Sinon.js library
4-
const AWS = require('aws-sdk') // AWS SDK (automatically available in Lambda)
54
const S3 = require('../lib/s3-service') // Init S3 Service
65
const { gzipSync, brotliCompressSync, deflateSync } = require('zlib')
76

__tests__/sendFile.unit.js

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,10 @@ const fs = require('fs') // Require Node.js file system
77
// Require Sinon.js library
88
const sinon = require('sinon')
99

10-
const AWS = require('aws-sdk') // AWS SDK (automatically available in Lambda)
11-
// AWS.config.credentials = new AWS.SharedIniFileCredentials({profile: 'madlucas'})
12-
1310
const S3 = require('../lib/s3-service'); // Init S3 Service
1411

1512
// Init API instance
16-
const api = require('../index')({ version: 'v1.0', mimeTypes: { test: 'text/test' } })
13+
const api = require('../index')({ version: 'v1.0', mimeTypes: { test: 'text/test' }})
1714

1815
let event = {
1916
httpMethod: 'get',
@@ -187,10 +184,12 @@ api.use(function(err,req,res,next) {
187184
let stub
188185

189186
describe('SendFile Tests:', function() {
187+
let setConfigSpy;
190188

191189
beforeEach(function() {
192190
// Stub getObjectAsync
193191
stub = sinon.stub(S3,'getObject')
192+
setConfigSpy = sinon.spy(S3, 'setConfig');
194193
})
195194

196195
it('Bad path', async function() {
@@ -348,6 +347,7 @@ describe('SendFile Tests:', function() {
348347
it('S3 file', async function() {
349348
let _event = Object.assign({},event,{ path: '/sendfile/s3' })
350349
let result = await new Promise(r => api.run(_event,{},(e,res) => { r(res) }))
350+
sinon.assert.notCalled(setConfigSpy);
351351
expect(result).toEqual({
352352
multiValueHeaders: {
353353
'content-type': ['text/plain'],
@@ -359,6 +359,14 @@ describe('SendFile Tests:', function() {
359359
})
360360
}) // end it
361361

362+
it('S3 file w/ custom config',async function() {
363+
const s3Config = {
364+
endpoint: "http://test"
365+
}
366+
const apiWithConfig = require('../index')({ version: 'v1.0', mimeTypes: { test: 'text/test' }, s3Config})
367+
sinon.assert.calledWith(setConfigSpy, s3Config);
368+
}) // end it
369+
362370
it('S3 file w/ nested path', async function() {
363371
let _event = Object.assign({},event,{ path: '/sendfile/s3path' })
364372
let result = await new Promise(r => api.run(_event,{},(e,res) => { r(res) }))
@@ -396,8 +404,10 @@ describe('SendFile Tests:', function() {
396404
})
397405
}) // end it
398406

407+
399408
afterEach(function() {
400409
stub.restore()
410+
setConfigSpy.restore();
401411
})
402412

403413
}) // end sendFile tests

__tests__/utils.unit.js

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"use strict";
22

33
const utils = require("../lib/utils");
4+
const { Readable } = require('stream');
45

56
/******************************************************************************/
67
/*** BEGIN TESTS ***/
@@ -489,5 +490,15 @@ describe("Utility Function Tests:", function () {
489490

490491
}); // end deepMerge tests
491492

492-
493+
describe("streamToBuffer:", function () {
494+
it("Should transform a given stream to a buffer", function () {
495+
let stream = new Readable();
496+
stream.push("test");
497+
stream.push(null);
498+
return utils.streamToBuffer(stream).then((buffer) => {
499+
expect(Buffer.isBuffer(buffer)).toBe(true);
500+
expect(buffer.toString()).toBe("test");
501+
});
502+
})
503+
})
493504
}); // end UTILITY tests

0 commit comments

Comments
 (0)