From 9fc9a76e162d7c5e5e11fca2cdd6b61019bf3937 Mon Sep 17 00:00:00 2001 From: Dave Sag Date: Thu, 18 Apr 2019 17:21:20 +0530 Subject: [PATCH 1/2] #22 added path-specific middleware option --- README.md | 32 +++++++++++++- src/connector.js | 12 ++--- src/extract/v2/extractPaths.js | 10 +++-- src/extract/v3/extractPaths.js | 10 +++-- src/normalise/normaliseMiddleware.js | 7 +++ test/fixtures/exampleV2.json | 3 +- test/fixtures/exampleV3.json | 3 +- test/unit/connector.test.js | 44 ++++++++++++++----- test/unit/extract/v2/extractPaths.test.js | 9 ++-- test/unit/extract/v3/basePath.test.js | 4 +- test/unit/extract/v3/extractPaths.test.js | 9 ++-- .../normalise/normaliseMiddleware.test.js | 35 +++++++++++++++ 12 files changed, 145 insertions(+), 33 deletions(-) create mode 100644 src/normalise/normaliseMiddleware.js create mode 100644 test/unit/normalise/normaliseMiddleware.test.js diff --git a/README.md b/README.md index 542419e..1234e02 100644 --- a/README.md +++ b/README.md @@ -304,6 +304,33 @@ async function correspondingMiddlewareFunction(req, res, next) { OpenAPI V3 allows you to define a global `security` definition as well as path specific ones. The global `security` block will be applied if there is no path specific one defined. +### Adding other path-level middleware + +You can add your own path specific middleware by passing in a `middleware` option + +```js +{ + middleware: { + myMiddleware: someMiddlewareFunction + } +} +``` + +and then in the path specification adding an `x-middleware` option + +```yml +paths: + /special + get: + summary: some special route + x-middleware: + - myMiddleware +``` + +The `someMiddlewareFunction` will be inserted **after** any auth middleware. + +This works for both Swagger v2 and OpenAPI v3 documents. + ### Adding hooks You can supply an `onCreateRoute` handler function with the options with signature @@ -367,9 +394,10 @@ If you don't pass in any options the defaults are: notFound: : require('./routes/notFound'), notImplemented: require('./routes/notImplemented'), onCreateRoute: undefined, - rootTag: 'root', // unused in OpenAPI v3 docs + rootTag: 'root', // only used in Swagger V2 docs security: {}, - variables: {}, // unused in Swagger V2 docs + variables: {}, // only used in OpenAPI v3 docs + middleware: {}, INVALID_VERSION: require('./errors').INVALID_VERSION } ``` diff --git a/src/connector.js b/src/connector.js index 07fb618..cf14112 100644 --- a/src/connector.js +++ b/src/connector.js @@ -18,13 +18,13 @@ const connectController = require('./connectors/connectController') * onCreateRoute * rootTag = 'root' // ignored if using OpenAPI v3 * security = {} - * variables = {} + * variables = {}, + * middleware = {}, * INVALID_VERSION = errors.INVALID_VERSION * } */ const connector = (api, apiDoc, options = {}) => { const { INVALID_VERSION = ERRORS.INVALID_VERSION, onCreateRoute } = options - const version = extractVersion(apiDoc) if (!version) throw new Error(INVALID_VERSION) @@ -33,13 +33,15 @@ const connector = (api, apiDoc, options = {}) => { const paths = extractPaths(apiDoc, options) return app => { - paths.forEach(({ method, route, operationId, security }) => { - const middleware = connectSecurity(security, options) + paths.forEach(({ method, route, operationId, security, middleware }) => { + const auth = connectSecurity(security, options) const controller = connectController(api, operationId, options) const descriptor = [route] - if (middleware) descriptor.push(middleware) + if (auth) descriptor.push(auth) + if (middleware.length) descriptor.push(...middleware) descriptor.push(controller) + app[method](...descriptor) if (typeof onCreateRoute === 'function') onCreateRoute(method, descriptor) }) diff --git a/src/extract/v2/extractPaths.js b/src/extract/v2/extractPaths.js index 3d9abf0..d5bb9aa 100644 --- a/src/extract/v2/extractPaths.js +++ b/src/extract/v2/extractPaths.js @@ -1,6 +1,7 @@ const { METHODS } = require('../../constants') const normaliseSecurity = require('../../normalise/v2/normaliseSecurity') const normaliseOperationId = require('../../normalise/normaliseOperationId') +const normaliseMiddleware = require('../../normalise/normaliseMiddleware') const normaliseRoute = require('../../normalise/normaliseRoute') /* @@ -11,7 +12,8 @@ const normaliseRoute = require('../../normalise/normaliseRoute') method, route, (normalised and inclues basePath if not a root route) operationId, - security + security, + middleware } ] @@ -19,7 +21,8 @@ const normaliseRoute = require('../../normalise/normaliseRoute') const extractPaths = ({ basePath, paths }, options = {}) => { const { apiSeparator, // What to swap for `/` in the swagger doc - rootTag = 'root' // The tag that tells us not to prepend the basePath + rootTag = 'root', // The tag that tells us not to prepend the basePath + middleware = {} } = options const reduceRoutes = (acc, elem) => { @@ -31,7 +34,8 @@ const extractPaths = ({ basePath, paths }, options = {}) => { method, route: normaliseRoute(`${isRoot ? '' : basePath}${elem}`), operationId: normaliseOperationId(op.operationId, apiSeparator), - security: normaliseSecurity(op.security) + security: normaliseSecurity(op.security), + middleware: normaliseMiddleware(middleware, op['x-middleware']) }) } }) diff --git a/src/extract/v3/extractPaths.js b/src/extract/v3/extractPaths.js index 3b4c28d..92bb850 100644 --- a/src/extract/v3/extractPaths.js +++ b/src/extract/v3/extractPaths.js @@ -1,6 +1,7 @@ const { METHODS } = require('../../constants') const normaliseSecurity = require('../../normalise/v3/normaliseSecurity') const normaliseOperationId = require('../../normalise/normaliseOperationId') +const normaliseMiddleware = require('../../normalise/normaliseMiddleware') const normaliseRoute = require('../../normalise/normaliseRoute') const basePath = require('./basePath') @@ -12,7 +13,8 @@ const basePath = require('./basePath') method, route, (normalised and inclues basePath if not a root route) operationId, - security + security, + middleware } ] @@ -20,7 +22,8 @@ const basePath = require('./basePath') const extractPaths = ({ security, servers, paths }, options = {}) => { const { apiSeparator, // What to swap for `/` in the swagger doc - variables = {} + variables = {}, + middleware = {} } = options const defaultBasePath = basePath(servers, variables) @@ -38,7 +41,8 @@ const extractPaths = ({ security, servers, paths }, options = {}) => { method, route: normaliseRoute(`${trimmedBase}${elem}`), operationId: normaliseOperationId(op.operationId, apiSeparator), - security: normaliseSecurity(op.security) || defaultSecurity + security: normaliseSecurity(op.security) || defaultSecurity, + middleware: normaliseMiddleware(middleware, op['x-middleware']) }) } }) diff --git a/src/normalise/normaliseMiddleware.js b/src/normalise/normaliseMiddleware.js new file mode 100644 index 0000000..4f7faf9 --- /dev/null +++ b/src/normalise/normaliseMiddleware.js @@ -0,0 +1,7 @@ +const normaliseMiddleware = (handlers = {}, names = []) => + names.reduce((acc, elem) => { + if (typeof handlers[elem] === 'function') acc.push(handlers[elem]) + return acc + }, []) + +module.exports = normaliseMiddleware diff --git a/test/fixtures/exampleV2.json b/test/fixtures/exampleV2.json index 1b92705..959e1eb 100644 --- a/test/fixtures/exampleV2.json +++ b/test/fixtures/exampleV2.json @@ -5,7 +5,7 @@ "version": "1.0.0", "title": "Example API" }, - "basePath": "/api/v2", + "basePath": "/api/v1", "schemes": ["https", "http"], "paths": { "/": { @@ -51,6 +51,7 @@ "summary": "Just a test", "description": "Returns 200 Okay if the path is accessed with the correct token", "operationId": "v2/test", + "x-middleware": ["middleTest"], "produces": ["application/json"], "responses": { "200": { diff --git a/test/fixtures/exampleV3.json b/test/fixtures/exampleV3.json index 5aa4841..8fd9816 100644 --- a/test/fixtures/exampleV3.json +++ b/test/fixtures/exampleV3.json @@ -65,6 +65,7 @@ "summary": "Just a test", "description": "Returns 200 Okay if the path is accessed with the correct token", "operationId": "v2/test", + "x-middleware": ["middleTest"], "responses": { "200": { "description": "success" @@ -180,7 +181,7 @@ }, "path": { "type": "string", - "example": "/api/v2" + "example": "/api/v1" } } }, diff --git a/test/unit/connector.test.js b/test/unit/connector.test.js index 1dff46a..1bffa15 100644 --- a/test/unit/connector.test.js +++ b/test/unit/connector.test.js @@ -1,5 +1,5 @@ const { expect } = require('chai') -const { stub, spy } = require('sinon') +const { stub, spy, resetHistory } = require('sinon') const connector = require('src') const ERRORS = require('src/errors') @@ -22,13 +22,8 @@ describe('src/connector', () => { const onCreateRoute = spy() - const resetStubs = () => { - mockApp.get.resetHistory() - onCreateRoute.resetHistory() - } - context('given an invalid document', () => { - after(resetStubs) + after(resetHistory) it('throws an error', () => expect(() => { @@ -46,7 +41,7 @@ describe('src/connector', () => { connect(mockApp) }) - after(resetStubs) + after(resetHistory) it('returned a function', () => { expect(connect).to.be.a('function') @@ -66,7 +61,7 @@ describe('src/connector', () => { connect(mockApp) }) - after(resetStubs) + after(resetHistory) it('called app.get for each route', () => { expect(mockApp.get.callCount).to.equal(4) @@ -82,7 +77,7 @@ describe('src/connector', () => { connect(mockApp) }) - after(resetStubs) + after(resetHistory) it('called app.get for each route', () => { expect(mockApp.get.callCount).to.equal(4) @@ -92,6 +87,35 @@ describe('src/connector', () => { expect(onCreateRoute.callCount).to.equal(4) }) }) + + context('with middleware', () => { + const middleTest = () => {} + before(() => { + const connect = connector(mockApi, doc, { + security: fakeSecurity, + middleware: { middleTest } + }) + connect(mockApp) + }) + + after(resetHistory) + + it('called app.get for each route', () => { + expect(mockApp.get.callCount).to.equal(4) + }) + + it("called get('/') with the versions handler", () => { + expect(mockApp.get).to.have.been.calledWith('/', mockApi.versions) + }) + + it("passed the middleware into the call to get('/api/v1/test') after the security middleware", () => { + expect(mockApp.get).to.have.been.calledWith( + '/api/v1/test', + fakeSecurity['admin,identity.basic,identity.email'], + middleTest + ) + }) + }) }) } diff --git a/test/unit/extract/v2/extractPaths.test.js b/test/unit/extract/v2/extractPaths.test.js index ab9e2ee..58ba83d 100644 --- a/test/unit/extract/v2/extractPaths.test.js +++ b/test/unit/extract/v2/extractPaths.test.js @@ -36,19 +36,22 @@ describe('src/extract/v2/extractPaths', () => { method: 'get', route: '/', operationId: 'versions', - security: undefined + security: undefined, + middleware: [] }, { method: 'get', route: '/ping', operationId: 'ping', - security: undefined + security: undefined, + middleware: [] }, { method: 'get', route: '/api/v1/test', operationId: 'v1_test', - security: 'admin,identity.basic,identity.email' + security: 'admin,identity.basic,identity.email', + middleware: [] } ] diff --git a/test/unit/extract/v3/basePath.test.js b/test/unit/extract/v3/basePath.test.js index 21b068c..7e15552 100644 --- a/test/unit/extract/v3/basePath.test.js +++ b/test/unit/extract/v3/basePath.test.js @@ -16,13 +16,13 @@ describe('src/extract/v3/basePath', () => { url: `${faker.internet.url()}/` }, { - url: '/{base}/v2' + url: '/{base}/v3' } ] const variables = { base: 'test' } - const expected = '/test/v2' + const expected = '/test/v3' it('returns the expected result', () => { expect(basePath(servers, variables)).to.equal(expected) diff --git a/test/unit/extract/v3/extractPaths.test.js b/test/unit/extract/v3/extractPaths.test.js index a14d9be..b613f0c 100644 --- a/test/unit/extract/v3/extractPaths.test.js +++ b/test/unit/extract/v3/extractPaths.test.js @@ -39,19 +39,22 @@ describe('src/extract/v3/extractPaths', () => { method: 'get', route: '/', operationId: 'versions', - security: undefined + security: undefined, + middleware: [] }, { method: 'get', route: '/ping', operationId: 'ping', - security: undefined + security: undefined, + middleware: [] }, { method: 'get', route: '/api/v1/test', operationId: 'v1_test', - security: 'admin,identity.basic,identity.email' + security: 'admin,identity.basic,identity.email', + middleware: [] } ] diff --git a/test/unit/normalise/normaliseMiddleware.test.js b/test/unit/normalise/normaliseMiddleware.test.js new file mode 100644 index 0000000..4baecf0 --- /dev/null +++ b/test/unit/normalise/normaliseMiddleware.test.js @@ -0,0 +1,35 @@ +const { expect } = require('chai') + +const normaliseMiddleware = require('src/normalise/normaliseMiddleware') + +describe('src/normalise/normaliseMiddleware', () => { + let result + + context('given nothing to normalise', () => { + before(() => { + result = normaliseMiddleware() + }) + + it('returns an empty array', () => { + expect(result).to.be.an('array') + expect(result).to.have.length(0) + }) + }) + + context('given something to normalise', () => { + const test1 = () => {} + const test2 = () => {} + + const handlers = { test1, test2 } + const names = ['test1', 'test2', 'test3'] + const expected = [test1, test2] + + before(() => { + result = normaliseMiddleware(handlers, names) + }) + + it('returns an array containing the named middleware', () => { + expect(result).to.deep.equal(expected) + }) + }) +}) From 78ba5b64ceca94774ae79b8e7eed7808bc807c43 Mon Sep 17 00:00:00 2001 From: Dave Sag Date: Thu, 18 Apr 2019 17:43:00 +0530 Subject: [PATCH 2/2] #22 added mutation testing --- .circleci/config.yml | 4 + .eslintrc.js | 2 +- README.md | 1 + package-lock.json | 283 ++++++++++++++++++++++++++ package.json | 7 +- src/connector.js | 3 +- stryker.conf.js | 22 ++ test/unit/extract/v3/basePath.test.js | 59 ++++-- 8 files changed, 365 insertions(+), 16 deletions(-) create mode 100644 stryker.conf.js diff --git a/.circleci/config.yml b/.circleci/config.yml index f1a0d04..249ec49 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -39,6 +39,10 @@ jobs: name: All Unit Tests with Code Coverage command: npm run test:coverage + - run: + name: Mutation Tests + command: npm run test:mutants + - run: name: Push any lockfile changes command: greenkeeper-lockfile-upload diff --git a/.eslintrc.js b/.eslintrc.js index 8e0a722..3562b12 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,6 +1,6 @@ module.exports = { extends: ['standard', 'prettier', 'prettier/standard'], - plugins: ['prettier', 'standard', 'import'], + plugins: ['prettier', 'standard', 'import', 'promise'], parserOptions: { sourceType: 'module' }, diff --git a/README.md b/README.md index 1234e02..df0a1cd 100644 --- a/README.md +++ b/README.md @@ -412,6 +412,7 @@ If you don't pass in any options the defaults are: - `npm test` — runs the unit tests. - `npm run test:coverage` - run the unit tests with coverage. +- `npm run test:mutants` - run mutation testing of the unit tests. ### Lint it diff --git a/package-lock.json b/package-lock.json index a3ef09c..da0437e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -165,6 +165,129 @@ "integrity": "sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==", "dev": true }, + "@stryker-mutator/api": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@stryker-mutator/api/-/api-1.2.0.tgz", + "integrity": "sha512-/4tx8cbnVe/fdl2WPcG89HZNK3CG1grG3ex2V1RYmMx7dhb5JclOq/oAhhsxLvZtpgL1ai9ui1rRFMWFdQR2Yg==", + "dev": true, + "requires": { + "mutation-testing-report-schema": "^1.0.0", + "tslib": "~1.9.3" + } + }, + "@stryker-mutator/core": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@stryker-mutator/core/-/core-1.2.0.tgz", + "integrity": "sha512-fODByHtYftDgDq4uIFgGAIi5ClWG/5WQM7N7eiPD5yeZnOiSOmBSrlLGdsVur28hdVLF9+L5ScaOE26kA12ELQ==", + "dev": true, + "requires": { + "@stryker-mutator/api": "^1.2.0", + "@stryker-mutator/util": "^1.2.0", + "chalk": "~2.4.1", + "commander": "~2.19.0", + "get-port": "~4.2.0", + "glob": "~7.1.2", + "inquirer": "~6.2.0", + "istanbul-lib-instrument": "~3.1.0", + "lodash": "~4.17.4", + "log4js": "~4.1.0", + "mkdirp": "~0.5.1", + "prettier": "~1.16.1", + "progress": "~2.0.0", + "rimraf": "~2.6.1", + "rxjs": "~6.3.0", + "source-map": "~0.6.1", + "surrial": "~1.0.0", + "tree-kill": "~1.2.0", + "tslib": "~1.9.3", + "typed-inject": "~1.0.0", + "typed-rest-client": "~1.2.0" + }, + "dependencies": { + "commander": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.19.0.tgz", + "integrity": "sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg==", + "dev": true + }, + "istanbul-lib-instrument": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-3.1.2.tgz", + "integrity": "sha512-5OCdsY81MIHduQqGs5k9JTkOioROzpl3r2PSdkh+1C3j5WxtRWhyhgxMv7wRuEsPsg4K0M9OVWtJ045lyBN73Q==", + "dev": true, + "requires": { + "@babel/generator": "^7.0.0", + "@babel/parser": "^7.0.0", + "@babel/template": "^7.0.0", + "@babel/traverse": "^7.0.0", + "@babel/types": "^7.0.0", + "istanbul-lib-coverage": "^2.0.4", + "semver": "^6.0.0" + } + }, + "prettier": { + "version": "1.16.4", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.16.4.tgz", + "integrity": "sha512-ZzWuos7TI5CKUeQAtFd6Zhm2s6EpAD/ZLApIhsF9pRvRtM1RFo61dM/4MSRUA0SuLugA/zgrZD8m0BaY46Og7g==", + "dev": true + }, + "rxjs": { + "version": "6.3.3", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.3.3.tgz", + "integrity": "sha512-JTWmoY9tWCs7zvIk/CvRjhjGaOd+OVBM987mxFo+OW66cGpdKjZcpmc74ES1sB//7Kl/PAe8+wEakuhG4pcgOw==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "@stryker-mutator/javascript-mutator": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@stryker-mutator/javascript-mutator/-/javascript-mutator-1.2.0.tgz", + "integrity": "sha512-Xxra18EebsbkMsN/w1WSE9otrt6+mtDrNy5TceXAFKw31wyVf6WzUCD6yvNR5DR0wheACTz651jXNE+FT9G2mA==", + "dev": true, + "requires": { + "@babel/generator": "~7.4.0", + "@babel/parser": "~7.4.0", + "@babel/traverse": "~7.4.0", + "@stryker-mutator/api": "^1.2.0", + "lodash": "~4.17.4", + "tslib": "~1.9.3" + } + }, + "@stryker-mutator/mocha-framework": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@stryker-mutator/mocha-framework/-/mocha-framework-1.2.0.tgz", + "integrity": "sha512-se3b0a+vTHi/YDsfOkGckvw4YjVJSwsgKYN7MiRrsiMvmGzUE4Sf2EMNxgZNBRF7cWi29mNnwZOb5DFl4vID8g==", + "dev": true, + "requires": { + "@stryker-mutator/api": "^1.2.0" + } + }, + "@stryker-mutator/mocha-runner": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@stryker-mutator/mocha-runner/-/mocha-runner-1.2.0.tgz", + "integrity": "sha512-emRm4YLYve2DnmZ/Vifw47d72CWXP3s8Q0NxUpLKjcewAW3C5/u+21nBHL35Ocdto+phqk+9DrtQoGzKMori2A==", + "dev": true, + "requires": { + "@stryker-mutator/api": "^1.2.0", + "multimatch": "~3.0.0", + "tslib": "~1.9.3" + } + }, + "@stryker-mutator/util": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@stryker-mutator/util/-/util-1.2.0.tgz", + "integrity": "sha512-EvoT0RZf1SMn4DwtveJO1BIJRC9vrWlOHvu/njDMo0M/4FJltOC0kjOSItoMQc0fxvct8osQKL2FL48xidEG0g==", + "dev": true + }, "acorn": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.1.1.tgz", @@ -264,6 +387,12 @@ "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", "dev": true }, + "array-differ": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-2.1.0.tgz", + "integrity": "sha512-KbUpJgx909ZscOc/7CLATBFam7P1Z1QRQInvgT0UztM9Q72aGKCunKASAl7WNW0tnPmPyEMeMhdsfWhfmW037w==", + "dev": true + }, "array-from": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/array-from/-/array-from-2.1.1.tgz", @@ -325,6 +454,15 @@ "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", "dev": true }, + "async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.2.tgz", + "integrity": "sha512-H1qVYh1MYhEEFLsP97cVKqCGo7KfCyTt6uEWqsTBr9SO84oK9Uwbyd/yCW+6rKJLHksBNUVWZDAjfS+Ccx0Bbg==", + "dev": true, + "requires": { + "lodash": "^4.17.11" + } + }, "atob": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", @@ -871,6 +1009,12 @@ "integrity": "sha512-hBSVCvSmWC+QypYObzwGOd9wqdDpOt+0wl0KbU+R+uuZBS1jN8VsD1ss3irQDknRj5NvxiTF6oj/nDRnN/UQNw==", "dev": true }, + "date-format": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/date-format/-/date-format-2.0.0.tgz", + "integrity": "sha512-M6UqVvZVgFYqZL1SfHsRGIQSz3ZL+qgbsV5Lp1Vj61LZVYuEwcMXYay7DRDtYs2HQQBK5hQtQ0fD9aEJ89V0LA==", + "dev": true + }, "debug": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", @@ -1774,6 +1918,17 @@ "map-cache": "^0.2.2" } }, + "fs-extra": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", + "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -1821,6 +1976,12 @@ "integrity": "sha512-CIJYJC4GGF06TakLg8z4GQKvDsx9EMspVxOYih7LerEL/WosUnFIww45CGfxfeKHqlg3twgUrYRT1O3WQqjGCg==", "dev": true }, + "get-port": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/get-port/-/get-port-4.2.0.tgz", + "integrity": "sha512-/b3jarXkH8KJoOMQc3uVGHASwGLPq3gSFJ7tgJm2diza+bydJPTGOibin2steecKeOylE8oY2JERlVWkAJO6yw==", + "dev": true + }, "get-stdin": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-6.0.0.tgz", @@ -2575,6 +2736,15 @@ "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", "dev": true }, + "jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.6" + } + }, "just-extend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.0.2.tgz", @@ -2836,6 +3006,19 @@ "wrap-ansi": "^3.0.1" } }, + "log4js": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log4js/-/log4js-4.1.0.tgz", + "integrity": "sha512-eDa+zZPeVEeK6QGJAePyXM6pg4P3n3TO5rX9iZMVY48JshsTyLJZLIL5HipI1kQ2qLsSyOpUqNND/C5H4WhhiA==", + "dev": true, + "requires": { + "date-format": "^2.0.0", + "debug": "^4.1.1", + "flatted": "^2.0.0", + "rfdc": "^1.1.2", + "streamroller": "^1.0.4" + } + }, "lolex": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/lolex/-/lolex-3.1.0.tgz", @@ -3151,6 +3334,24 @@ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", "dev": true }, + "multimatch": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/multimatch/-/multimatch-3.0.0.tgz", + "integrity": "sha512-22foS/gqQfANZ3o+W7ST2x25ueHDVNWl/b9OlGcLpy/iKxjCpvcNCM51YCenUi7Mt/jAjjqv8JwZRs8YP5sRjA==", + "dev": true, + "requires": { + "array-differ": "^2.0.3", + "array-union": "^1.0.2", + "arrify": "^1.0.1", + "minimatch": "^3.0.4" + } + }, + "mutation-testing-report-schema": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/mutation-testing-report-schema/-/mutation-testing-report-schema-1.0.5.tgz", + "integrity": "sha512-Vy0Z2RBO2YiFviZUzzipGfbyPkHmL+Wz4BxUUvmeSGcFSDLtLCxib5FEHlJKjjxqCwnhirs3agAW8zngWQx4zw==", + "dev": true + }, "mute-stream": { "version": "0.0.7", "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", @@ -3923,6 +4124,12 @@ "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", "dev": true }, + "rfdc": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.1.2.tgz", + "integrity": "sha512-92ktAgvZhBzYTIK0Mja9uen5q5J3NRVMoDkJL2VMwq6SXjVCgqvQeVP2XAaUY6HT+XpQYeLSjb3UoitBryKmdA==", + "dev": true + }, "rimraf": { "version": "2.6.3", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", @@ -4314,6 +4521,36 @@ } } }, + "streamroller": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-1.0.4.tgz", + "integrity": "sha512-Wc2Gm5ygjSX8ZpW9J7Y9FwiSzTlKSvcl0FTTMd3rn7RoxDXpBW+xD9TY5sWL2n0UR61COB0LG1BQvN6nTUQbLQ==", + "dev": true, + "requires": { + "async": "^2.6.1", + "date-format": "^2.0.0", + "debug": "^3.1.0", + "fs-extra": "^7.0.0", + "lodash": "^4.17.10" + }, + "dependencies": { + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + } + } + }, "string-argv": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.0.2.tgz", @@ -4377,6 +4614,12 @@ "has-flag": "^3.0.0" } }, + "surrial": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/surrial/-/surrial-1.0.0.tgz", + "integrity": "sha512-dkvhz3QvgraMeFWI9V+BinpNCNoaSNxKcxb0umRpkWeFlZ0WSbIfeTm9YtLA6a4kv/Q2pOMQOtMlcv/b5h6qpg==", + "dev": true + }, "symbol-observable": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz", @@ -4619,6 +4862,12 @@ "integrity": "sha1-riF2gXXRVZ1IvvNUILL0li8JwzA=", "dev": true }, + "tree-kill": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.1.tgz", + "integrity": "sha512-4hjqbObwlh2dLyW4tcz0Ymw0ggoaVDMveUB9w8kFSQScdRLo0gxO9J7WFcUBo+W3C1TLdFIEwNOWebgZZ0RH9Q==", + "dev": true + }, "trim-right": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz", @@ -4631,6 +4880,12 @@ "integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==", "dev": true }, + "tunnel": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.4.tgz", + "integrity": "sha1-LTeFoVjBdMmhbcLARuxfxfF0IhM=", + "dev": true + }, "type-check": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", @@ -4646,6 +4901,22 @@ "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", "dev": true }, + "typed-inject": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/typed-inject/-/typed-inject-1.0.0.tgz", + "integrity": "sha512-u1ZR7Zvu0NIIZjiLg8RSp4oOoSs8wUnsn8mQ3Pnl8fHmCNkA7Y/aOKTR/Stp4R9/bazwphSktuOEMP++DdlAKQ==", + "dev": true + }, + "typed-rest-client": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/typed-rest-client/-/typed-rest-client-1.2.0.tgz", + "integrity": "sha512-FrUshzZ1yxH8YwGR29PWWnfksLEILbWJydU7zfIRkyH7kAEzB62uMAl2WY6EyolWpLpVHeJGgQm45/MaruaHpw==", + "dev": true, + "requires": { + "tunnel": "0.0.4", + "underscore": "1.8.3" + } + }, "uglify-js": { "version": "3.5.4", "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.5.4.tgz", @@ -4666,6 +4937,12 @@ } } }, + "underscore": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.8.3.tgz", + "integrity": "sha1-Tz+1OxBuYJf8+ctBCfKl6b36UCI=", + "dev": true + }, "union-value": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.0.tgz", @@ -4701,6 +4978,12 @@ } } }, + "universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true + }, "unset-value": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", diff --git a/package.json b/package.json index 680b8cc..d4238e5 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,8 @@ "lint": "eslint .", "prettier": "prettier --write '**/*.{js,json,md}'", "test": "find ./test/unit -name '*.test.js' | NODE_PATH=. NODE_ENV=test xargs mocha --require ./test/unit/testHelper.js", - "test:coverage": "find ./test/unit -name '*.test.js' | NODE_PATH=. NODE_ENV=test xargs nyc mocha --require ./test/unit/testHelper.js" + "test:coverage": "find ./test/unit -name '*.test.js' | NODE_PATH=. NODE_ENV=test xargs nyc mocha --require ./test/unit/testHelper.js", + "test:mutants": "NODE_PATH=. NODE_ENV=test npx stryker run" }, "keywords": [ "express", @@ -36,6 +37,10 @@ "controllers" ], "devDependencies": { + "@stryker-mutator/core": "^1.2.0", + "@stryker-mutator/javascript-mutator": "^1.2.0", + "@stryker-mutator/mocha-framework": "^1.2.0", + "@stryker-mutator/mocha-runner": "^1.2.0", "ajv": "^6.10.0", "chai": "^4.2.0", "chai-as-promised": "^7.1.1", diff --git a/src/connector.js b/src/connector.js index cf14112..e5f220a 100644 --- a/src/connector.js +++ b/src/connector.js @@ -39,8 +39,7 @@ const connector = (api, apiDoc, options = {}) => { const descriptor = [route] if (auth) descriptor.push(auth) - if (middleware.length) descriptor.push(...middleware) - descriptor.push(controller) + descriptor.push(...middleware, controller) app[method](...descriptor) if (typeof onCreateRoute === 'function') onCreateRoute(method, descriptor) diff --git a/stryker.conf.js b/stryker.conf.js new file mode 100644 index 0000000..ac9b1f7 --- /dev/null +++ b/stryker.conf.js @@ -0,0 +1,22 @@ +module.exports = function(config) { + config.set({ + mutate: [ + 'src/**/*.js', + '!src/constants.js', + '!src/errors.js', + '!src/connectors/connectSecurity.js' + ], + mutator: 'javascript', + packageManager: 'npm', + reporters: ['clear-text', 'progress'], + testRunner: 'mocha', + mochaOptions: { + files: ['test/unit/**/*.test.js'], + require: ['test/unit/testHelper.js'] + }, + transpilers: [], + testFramework: 'mocha', + coverageAnalysis: 'perTest', + thresholds: { high: 80, low: 70, break: null } + }) +} diff --git a/test/unit/extract/v3/basePath.test.js b/test/unit/extract/v3/basePath.test.js index 7e15552..a22e0ce 100644 --- a/test/unit/extract/v3/basePath.test.js +++ b/test/unit/extract/v3/basePath.test.js @@ -11,21 +11,56 @@ describe('src/extract/v3/basePath', () => { }) context('given servers', () => { - const servers = [ - { - url: `${faker.internet.url()}/` - }, - { - url: '/{base}/v3' - } - ] + context('with an absolute base url', () => { + const servers = [ + { + url: `${faker.internet.url()}/` + } + ] - const variables = { base: 'test' } + const expected = '' - const expected = '/test/v3' + it('returns the expected result', () => { + expect(basePath(servers, {})).to.equal(expected) + }) + }) + + context('with a variable', () => { + const servers = [ + { + url: `${faker.internet.url()}/` + }, + { + url: '/{base}/v3' + } + ] + + const variables = { base: 'test' } + + const expected = '/test/v3' + + it('returns the expected result', () => { + expect(basePath(servers, variables)).to.equal(expected) + }) + }) + + context('with repeated variables', () => { + const servers = [ + { + url: `${faker.internet.url()}/` + }, + { + url: '/{base}/v3/{base}' + } + ] + + const variables = { base: 'test' } + + const expected = '/test/v3/test' - it('returns the expected result', () => { - expect(basePath(servers, variables)).to.equal(expected) + it('returns the expected result', () => { + expect(basePath(servers, variables)).to.equal(expected) + }) }) }) })