diff --git a/CHANGELOG.md b/CHANGELOG.md index d73a15d..c85d482 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,27 +3,12 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). -## [3.0.4] - 2020-05-19 -### Changed -- Added `error.message` into logged error -- Added integration test to confirm `application/x-www-form-urlencoded` requests work - -## [3.0.3] - 2020-05-19 -### Changed -- Incorporated user `mattdelsordo` to support binary response and abbreviate logging - -## [3.0.2] - 2020-05-19 +## [1.0.1] - 2021-11-17 ### Changed - Updated CircleCI and Greenkeeper badges in README.md -## [3.0.1] - 2020-05-19 +## [1.0.0] - 2021-11-17 ### Added -- Added `.codacy.yml` for Codacy analysis -- Added `.mocharc.yml` - -### Changed -- Migrated to using `nyc` from `istanbul` -- Migrated `husky` pre-push hooks -- Migrated to CircleCI v2 pipelines -- Updated `devDependencies` to latest versions -- Updated `package.json` to new Node.js and NPM version compatibility +- Forked from https://www.npmjs.com/package/claudia-local-api +- Added reuqestUUID forcing +- Added --stage argument diff --git a/README.md b/README.md index 89ae29c..fd3b172 100644 --- a/README.md +++ b/README.md @@ -1,26 +1,17 @@ -# claudia-local-api +# ts-claudia-local-api -[![CircleCI](https://circleci.com/gh/suddi/claudia-local-api.svg?style=svg)](https://circleci.com/gh/suddi/claudia-local-api) -[![codecov](https://codecov.io/gh/suddi/claudia-local-api/branch/master/graph/badge.svg)](https://codecov.io/gh/suddi/claudia-local-api) -[![Codacy Badge](https://api.codacy.com/project/badge/Grade/4aaafdcb86574c709f856f2e00d3a809)](https://www.codacy.com/app/Suddi/claudia-local-api) -[![npm](https://img.shields.io/npm/v/claudia-local-api.svg)](https://www.npmjs.com/package/claudia-local-api) -[![npm](https://img.shields.io/npm/dt/claudia-local-api.svg)](https://www.npmjs.com/package/claudia-local-api) -[![David](https://img.shields.io/david/suddi/claudia-local-api.svg)](https://david-dm.org/suddi/claudia-local-api) -[![David](https://img.shields.io/david/dev/suddi/claudia-local-api.svg)](https://david-dm.org/suddi/claudia-local-api?type=dev) -[![license](https://img.shields.io/github/license/suddi/claudia-local-api.svg)](https://github.com/suddi/claudia-local-api/blob/master/LICENSE) +Command line utility to launch Express local API for claudia-api-builder. Test drive your lambda functions before deployment (Based on Sudharshan Ravindran claudia-local-api) with the aws lambda function stage parameter. BE AWARE NOT INSTALL WITH claudia-local-api package! +Extends claudia-local-api with suport for AWS requestUUID and stages using --stage {stagename} provided as commandline argument -[![codecov](https://codecov.io/gh/suddi/claudia-local-api/branch/master/graphs/commits.svg)](https://codecov.io/gh/suddi/claudia-local-api) - -Command line utility to launch Express local API for claudia-api-builder. Test drive your lambda functions before deployment ```` -npm install --save-dev claudia-local-api +npm install --save-dev ts-claudia-local-api ```` To install globally: ```` -npm install --global claudia-local-api +npm install --global ts-claudia-local-api ```` ## Usage @@ -73,16 +64,16 @@ function bootstrap() { module.exports = bootstrap() ```` -You can install `claudia-local-api` and run the command line Express API to test out the lambda function locally: +You can install `ts-claudia-local-api` and run the command line Express API to test out the lambda function locally: ```` -claudia-local-api --api-module lib/app.js +claudia-local-api --api-module app.js ```` Or add into your `package.json`: ````json -"server": "claudia-local-api --api-module lib/app.js" +"server": "claudia-local-api --api-module app.js --stage develop" ```` This will start up a local Express server on port 3000 to proxy requests to your [`claudia-api-builder`](https://www.npmjs.com/package/claudia-api-builder) app. @@ -90,7 +81,7 @@ This will start up a local Express server on port 3000 to proxy requests to your You can also pipe it into [`bunyan`](https://www.npmjs.com/package/bunyan) to pretty print the log: ```` -claudia-local-api --api-module lib/app.js | bunyan --output short +claudia-local-api --api-module app.js --stage develop | bunyan --output short ```` --- diff --git a/bin/claudia-local-api b/bin/claudia-local-api index 31cd6d3..644104e 100755 --- a/bin/claudia-local-api +++ b/bin/claudia-local-api @@ -5,7 +5,7 @@ const path = require('path'); function execute() { const potentialPaths = [ - path.join(process.cwd(), 'node_modules/claudia-local-api/lib'), + path.join(process.cwd(), 'node_modules/claudia-local-api-with-lambda-version/lib'), path.join(__dirname, '../lib') ]; @@ -16,7 +16,7 @@ function execute() { if (!existingPaths.length) { throw new Error('claudia-local-api not found, is it installed correctly?'); } - + const program = require(existingPaths[0]); program.run(); } diff --git a/lib/index.js b/lib/index.js index 032ee01..2cf6d5a 100644 --- a/lib/index.js +++ b/lib/index.js @@ -6,10 +6,12 @@ const program = require('commander'); const path = require('path'); const pathParser = require('path-parser'); const packageJson = require('../package'); +const { v4: uuidv4 } = require('uuid'); function getDefaultConfig() { return { - port: 3000 + port: 3000, + lambdaVersion: 'develop' }; } @@ -82,19 +84,29 @@ function getPathParams(req, routes) { }; } -function getParams(req, routes) { +function getParams(req, routes, lambdaVersion = null) { + const uuid = uuidv4(); const pathParams = getPathParams(req, routes); - return { + let paramsObject = { requestContext: { resourcePath: pathParams.resourcePath, - httpMethod: req.method + httpMethod: req.method, + resourceId: uuid }, headers: req.headers, queryStringParameters: req.query, body: req.body, pathParameters: pathParams.pathParameters }; + + if(lambdaVersion){ + paramsObject['stageVariables'] = { + lambdaVersion: lambdaVersion + } + } + + return paramsObject; } // Determine whether to enable binary content handling @@ -133,7 +145,6 @@ function makeHandleResponse(logger, res, convertToBinary) { .status(response.statusCode || 200) .send(response.body ? Buffer.from(response.body, 'base64') : undefined); } - return res .set(response.headers || {}) .status(response.statusCode || 200) @@ -141,9 +152,9 @@ function makeHandleResponse(logger, res, convertToBinary) { }; } -function makeHandleRequest(logger, app, routes) { +function makeHandleRequest(logger, app, routes, lambdaVersion) { return function (req, res) { - const params = getParams(req, routes); + const params = getParams(req, routes, lambdaVersion); logJson(logger, params); const convertToBinary = parseContentHandling(params, app); @@ -157,7 +168,11 @@ function getRoutes(routesObj) { const routePaths = Object.keys(routesObj); return routePaths.map(function (routePath) { - const supportedMethods = Object.keys(routesObj[routePath] || {}); + let supportedMethods = Object.keys(routesObj[routePath] || {}); + if(supportedMethods == 'ANY'){ + supportedMethods = `GET,POST,PUT,PATCH,DELETE,HEAD,OPTIONS`; + } + const route = `/${routePath}`; return { resourcePath: route, @@ -168,7 +183,7 @@ function getRoutes(routesObj) { } function bootstrap(server, logger, claudiaApp, routes, options) { - const handleRequest = makeHandleRequest(logger, claudiaApp, routes); + const handleRequest = makeHandleRequest(logger, claudiaApp, routes, options.stage); server.all('*', handleRequest); const instance = server.listen(options.port); @@ -183,6 +198,7 @@ function runCmd(bootstrapFn) { .option('-a --api-module ', 'Specify claudia api path from project root') .option('-p --port [port]', `Specify port to use [${config.port}]`, config.port) .option('--abbrev [body length]', 'Specify the maximum logged length of a response/request body [no abbreviation]', -1) + .option('--stage', 'Specify the AWS lambda stage [develop, latest]', 'develop') .parse(process.argv); const apiPath = path.join(process.cwd(), program.apiModule); diff --git a/package-lock.json b/package-lock.json index dc10fe8..d8925f6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { - "name": "claudia-local-api", - "version": "3.0.5", + "name": "claudia-local-api-with-stage", + "version": "3.3.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 1cbfd5e..55a7e78 100644 --- a/package.json +++ b/package.json @@ -1,19 +1,19 @@ { - "name": "claudia-local-api", - "description": "Command line utility to launch Express local API for claudia-api-builder. Test drive your lambda functions before deployment", - "version": "3.0.5", - "homepage": "https://github.com/suddi/claudia-local-api", + "name": "ts-claudia-local-api", + "description": "Command line utility to launch Express local API for claudia-api-builder. Test drive your lambda functions before deployment (BASED on Sudharshan Ravindran claudia-local-api )", + "version": "1.0.2", + "homepage": "https://github.com/TantosvagoRepos/ts-claudia-local-api", "author": { - "name": "Sudharshan Ravindran", - "email": "mail@suddi.io", - "url": "https://suddi.io" + "name": "TantosvagoTeam", + "email": "it@tantosvago.it", + "url": "https://github.com/TantosvagoRepos" }, "repository": { "type": "git", - "url": "https://github.com/suddi/claudia-local-api" + "url": "https://github.com/TantosvagoRepos/ts-claudia-local-api" }, "bugs": { - "url": "https://github.com/suddi/claudia-local-api/issues" + "url": "https://github.com/TantosvagoRepos/ts-claudia-local-api/issues" }, "files": [ "bin", diff --git a/test/index.js b/test/index.js index 2bbe3f2..6ddb873 100644 --- a/test/index.js +++ b/test/index.js @@ -15,7 +15,7 @@ describe('Unit tests for lib/index', function () { const result = getDefaultConfig(); - expect(result).to.have.keys('port'); + expect(result).to.have.keys('port', 'lambdaVersion'); }); }); @@ -262,61 +262,76 @@ describe('Unit tests for lib/index', function () { context('Testing getParams', function () { it('CASE 1: Should make claudia-api-builder request params as expected', function () { - const getParams = localApi.__get__('getParams'); - const params = { - id: '42' - }; - const req = { - originalUrl: `http://www.example.com/test/${params.id}?test-value=42`, - _parsedUrl: { - pathname: `/test/${params.id}` - }, - method: 'PATCH', - headers: { - 'content-type': 'application/json', - 'user-agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.101 Safari/537.36' - }, - query: { - 'test-value': '42' - }, - body: { - a: { - b: { - c: [1, 2, 3], - d: 42 - } + try{ + const getParams = localApi.__get__('getParams'); + const params = { + id: '42' + }; + const req = { + originalUrl: `http://www.example.com/test/${params.id}?test-value=42`, + _parsedUrl: { + pathname: `/test/${params.id}` }, - e: 42 - } - }; - const resourcePath = '/test/{id}'; - const routes = [ - { - resourcePath, - supportedMethods: [ - 'GET', - 'POST', - 'PUT', - 'PATCH', - 'DELETE' - ], - path: pathParser.Path.createPath(resourcePath.replace(/{(.+?)}/g, ':$1')) - } - ]; - const expectedResult = { - requestContext: { - resourcePath: resourcePath, - httpMethod: req.method - }, - headers: req.headers, - queryStringParameters: req.query, - body: req.body, - pathParameters: params - }; - - const result = getParams(req, routes); - - expect(result).to.deep.eql(expectedResult); + method: 'PATCH', + headers: { + 'content-type': 'application/json', + 'user-agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.101 Safari/537.36' + }, + query: { + 'test-value': '42' + }, + body: { + a: { + b: { + c: [1, 2, 3], + d: 42 + } + }, + e: 42 + } + }; + const resourcePath = '/test/{id}'; + const routes = [ + { + resourcePath, + supportedMethods: [ + 'GET', + 'POST', + 'PUT', + 'PATCH', + 'DELETE', + 'OPTIONS' + ], + path: pathParser.Path.createPath(resourcePath.replace(/{(.+?)}/g, ':$1')) + } + ]; + const expectedResult = { + requestContext: { + resourcePath: resourcePath, + httpMethod: req.method + }, + headers: req.headers, + queryStringParameters: req.query, + body: req.body, + pathParameters: params + }; + + const result = getParams(req, routes); + + expect(expectedResult.requestContext.resourcePath).eql(result.requestContext.resourcePath); + expect(expectedResult.requestContext.httpMethod).eql(result.requestContext.httpMethod); + expect(typeof result.requestContext.resourceId).eql('string'); + expect(expectedResult.headers).eql(result.headers); + expect(expectedResult.queryStringParameters).eql(result.queryStringParameters); + expect(expectedResult.body).eql(result.body); + expect(expectedResult.pathParameters).eql(result.pathParameters); + + + //expect(result).to.deep.eql(expectedResult); + }catch(e){ + console.error(e); + expect(true).toBe(false); + } }); }); @@ -414,35 +429,43 @@ describe('Unit tests for lib/index', function () { } it('CASE 1: Should handle successful response', function () { - const makeHandleResponse = localApi.__get__('makeHandleResponse'); - const spy = sinon.spy(); - const logger = { - info: spy - }; - const response = { - headers: { - 'content-type': 'application/json', - 'user-agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.101 Safari/537.36' - }, - statusCode: 201, - body: { - a: { - b: { - c: [1, 2, 3], - d: 42 - } + try{ + const makeHandleResponse = localApi.__get__('makeHandleResponse'); + const spy = sinon.spy(); + const logger = { + info: spy + }; + const response = { + env:{ + lambdaVersion:'develop' }, - e: 42 - } - }; - const res = getRes(response.headers, response.statusCode, response.body); - const expectedResult = JSON.stringify(response, null, 4); - - const handleResponse = makeHandleResponse(logger, res); - handleResponse(null, response); - - expect(spy.calledOnce).to.be.eql(true); - expect(spy.calledWith(expectedResult)).to.be.eql(true); + headers: { + 'content-type': 'application/json', + 'user-agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.101 Safari/537.36' + }, + statusCode: 201, + body: { + a: { + b: { + c: [1, 2, 3], + d: 42 + } + }, + e: 42 + } + }; + const res = getRes(response.headers, response.statusCode, response.body); + const expectedResult = JSON.stringify(response, null, 4); + + const handleResponse = makeHandleResponse(logger, res); + handleResponse(null, response); + + expect(spy.calledOnce).to.be.eql(true); + expect(spy.calledWith(expectedResult)).to.be.eql(true); + }catch(e){ + console.error(e); + expect(true).toBe(false); + } }); it('CASE 2: Should handle successful response with default values', function () { @@ -527,78 +550,92 @@ describe('Unit tests for lib/index', function () { context('Testing makeHandleRequest', function () { it('CASE 1: Should handle request correctly', function () { - const makeHandleRequest = localApi.__get__('makeHandleRequest'); - const spy = sinon.spy(); - const logger = { - info: spy - }; - const params = { - id: '23423' - }; - const req = { - originalUrl: `http://www.example.com/test/${params.id}?test-value=42`, - _parsedUrl: { - pathname: `/test/${params.id}` - }, - method: 'PATCH', - headers: { - 'content-type': 'application/json', - 'user-agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.101 Safari/537.36' - }, - query: { - 'test-value': '42' - }, - body: { - a: { - b: { - c: [1, 2, 3], - d: 42 - } + + try{ + const makeHandleRequest = localApi.__get__('makeHandleRequest'); + const spy = sinon.spy(); + const logger = { + info: spy + }; + const params = { + id: '23423' + }; + const req = { + originalUrl: `http://www.example.com/test/${params.id}?test-value=42`, + _parsedUrl: { + pathname: `/test/${params.id}` }, - e: 42 - } - }; - const resourcePath = '/test/{id}'; - const routes = [ - { - resourcePath, - supportedMethods: [ - 'GET', - 'POST', - 'PUT', - 'PATCH', - 'DELETE' - ], - path: pathParser.Path.createPath(resourcePath.replace(/{(.+?)}/g, ':$1')) - } - ]; - const expectedParams = { - requestContext: { - resourcePath: resourcePath, - httpMethod: req.method - }, - headers: req.headers, - queryStringParameters: req.query, - body: req.body, - pathParameters: params - }; - const app = { - proxyRouter: function (receivedParams, controlFlow) { - expect(receivedParams).to.deep.eql(expectedParams); - expect(controlFlow).to.have.keys('done'); - expect(controlFlow.done).to.be.a('function'); - }, - apiConfig: function () { - return { - message: 'This function exists so that the rest of this test does not fail.' - }; - } - }; + method: 'PATCH', + headers: { + 'content-type': 'application/json', + 'user-agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.101 Safari/537.36' + }, + query: { + 'test-value': '42' + }, + body: { + a: { + b: { + c: [1, 2, 3], + d: 42 + } + }, + e: 42 + } + }; + const resourcePath = '/test/{id}'; + const routes = [ + { + resourcePath, + supportedMethods: [ + 'GET', + 'POST', + 'PUT', + 'PATCH', + 'DELETE' + ], + path: pathParser.Path.createPath(resourcePath.replace(/{(.+?)}/g, ':$1')) + } + ]; + const expectedParams = { + requestContext: { + resourcePath: resourcePath, + httpMethod: req.method + }, + headers: req.headers, + queryStringParameters: req.query, + body: req.body, + pathParameters: params + }; + const app = { + proxyRouter: function (receivedParams, controlFlow) { + //expect(receivedParams).to.deep.eql(expectedParams); + expect(expectedParams.requestContext.resourcePath).eql(receivedParams.requestContext.resourcePath); + expect(expectedParams.requestContext.httpMethod).eql(receivedParams.requestContext.httpMethod); + expect(typeof receivedParams.requestContext.resourceId).eql('string'); + expect(expectedParams.headers).eql(receivedParams.headers); + expect(expectedParams.queryStringParameters).eql(receivedParams.queryStringParameters); + expect(expectedParams.body).eql(receivedParams.body); + expect(expectedParams.pathParameters).eql(receivedParams.pathParameters); + expect(controlFlow).to.have.keys('done'); + expect(controlFlow.done).to.be.a('function'); + }, + apiConfig: function () { + return { + message: 'This function exists so that the rest of this test does not fail.' + }; + } + }; - const handleRequest = makeHandleRequest(logger, app, routes); - const result = handleRequest(req, {}); + const handleRequest = makeHandleRequest(logger, app, routes); + const result = handleRequest(req, {}); - expect(result).to.be.eql(undefined); + expect(result).to.be.eql(undefined); + + }catch(e){ + console.error(e); + expect(true).eql(false); + } }); }); @@ -765,6 +802,35 @@ describe('Unit tests for lib/index', function () { expect(result).to.be.eql(undefined); }); + + it('CASE 3: Should be able to set lambdaVersion', function () { + const runCmd = localApi.__get__('runCmd'); + const apiModule = 'test/claudia_app'; + const port = 3000; + const abbrev = -1; + const lambdaVersion = 'develop'; + process.argv = [ + 'node', + 'claudia-local-api', + '--api-module', + apiModule, + '--lambdaVersion', + 'develop' + ]; + const bootstrap = function (server, logger, claudiaApp, routes, options) { + expect(server).to.be.a('function'); + expect(logger).to.be.a('object'); + expect(claudiaApp).to.be.a('object'); + expect(options.apiModule).to.be.eql(apiModule); + expect(options.port).to.be.eql(port); + expect(options.abbrev).to.be.eql(abbrev); + expect(options.lambdaVersion).to.be.eql(lambdaVersion); + }; + + const result = runCmd(bootstrap); + + expect(result).to.be.eql(undefined); + }); }); });