diff --git a/README.md b/README.md index 6318bb4..0ad0c8a 100644 --- a/README.md +++ b/README.md @@ -124,7 +124,9 @@ See [example app](/example/) for more details ### Parameters -- **`options`** (`Object`) - An object containing the options for configuring the application. Defaults to `{ middlewareSetupFn: undefined }` if not provided. +- **`options`** (`Object`) - Configuration options for the application: + - **`globalMiddlewareSetupFn`** (`Function`, optional) - Configures the global middleware setup process. Defaults to `undefined`. + - **`middlewareSetupFn`** (`Function`, optional) - Configures the middleware setup process. Defaults to `undefined`. ### Returns diff --git a/index.js b/index.js index 5bf2cf6..da6b690 100644 --- a/index.js +++ b/index.js @@ -11,6 +11,7 @@ const redisClient = require('./lib/redis-client'); * The setup process can be customized using the provided `options` object. The function will initialize the components based on the options provided, or default to values from the `config` module. * * @param {Object} [options={middlewareSetupFn: undefined}] - Configuration options to customize the setup process. + * @param {Object} [options={globalMiddlewareSetupFn: undefined}] - Configuration options to customize the global middleware setup process. * @param {Object} [options.config] - Optional configuration for the `config.setup()` method. Set to `false` to skip configuration setup. * @param {Object} [options.logs] - Optional configuration for logging. Merged with the default config fetched by `config.get('logs')`. Set to `false` to skip log setup. * @param {Object} [options.redis] - Optional configuration for the Redis client. Merged with the default config fetched by `config.get('redis')`. Set to `false` to skip Redis setup. @@ -42,7 +43,8 @@ const redisClient = require('./lib/redis-client'); */ const setup = (options = { - middlewareSetupFn: undefined + middlewareSetupFn: undefined, + globalMiddlewareSetupFn: undefined }) => { if (options.config !== false) config.setup(options.config); @@ -56,7 +58,14 @@ const setup = (options = { ...options.redis }); - const app = middleware.setup({ + let app = express(); + + if (options.globalMiddlewareSetupFn && typeof options.globalMiddlewareSetupFn === 'function') { + options.globalMiddlewareSetupFn(app); + } + + app = middleware.setup({ + app, ...config.get(), ...options }); diff --git a/middleware/index.js b/middleware/index.js index 26a29b5..8d951df 100644 --- a/middleware/index.js +++ b/middleware/index.js @@ -1,5 +1,4 @@ const path = require('path'); -const express = require('express'); const logger = require('../lib/logger'); const requiredArgument = argName => { @@ -8,6 +7,7 @@ const requiredArgument = argName => { const middleware = { setup({ + app = requiredArgument('app'), env = process.env.NODE_ENV, urls = {}, featureFlags, @@ -45,9 +45,6 @@ const middleware = { urls.version = urls.version === undefined ? '/version' : urls.version; urls.healthcheck = urls.healthcheck === undefined ? '/healthcheck' : urls.healthcheck; - // create new express app - const app = express(); - // environment env = (env || 'development').toLowerCase(); app.set('env', env); diff --git a/package-lock.json b/package-lock.json index 4a143b4..0634fd4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -361,20 +361,6 @@ "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, - "node_modules/@eslint/config-array": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.20.0.tgz", - "integrity": "sha512-fxlS1kkIjx8+vy2SjuCB94q3htSNrufYTXubwiBFeaQHbH6Ipi43gFJq2zCMt6PHhImH3Xmr0NksKDvchWlpQQ==", - "dev": true, - "dependencies": { - "@eslint/object-schema": "^2.1.6", - "debug": "^4.3.1", - "minimatch": "^3.1.2" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, "node_modules/@eslint/config-helpers": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.2.2.tgz", @@ -453,18 +439,32 @@ } }, "node_modules/@eslint/plugin-kit": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.1.tgz", - "integrity": "sha512-0J+zgWxHN+xXONWIyPWKFMgVuJoZuGiIFu8yxk7RJjxkzpGmyja5wRFqZIVtjDVOQpV+Rw0iOAjYPE2eQyjr0w==", + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.5.tgz", + "integrity": "sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@eslint/core": "^0.14.0", + "@eslint/core": "^0.15.2", "levn": "^0.4.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/@eslint/plugin-kit/node_modules/@eslint/core": { + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.2.tgz", + "integrity": "sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, "node_modules/@humanfs/core": { "version": "0.19.1", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", @@ -842,9 +842,9 @@ } }, "node_modules/acorn": { - "version": "8.14.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", - "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "bin": { "acorn": "bin/acorn" @@ -1415,15 +1415,15 @@ } }, "node_modules/compression": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.0.tgz", - "integrity": "sha512-k6WLKfunuqCYD3t6AsuPGvQWaKwuLLh2/xHNcX4qE+vIfDNXpSqnrhwA7O53R7WVQUnt8dVAIW+YHr7xTgOgGA==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.1.tgz", + "integrity": "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==", "dependencies": { "bytes": "3.1.2", "compressible": "~2.0.18", "debug": "2.6.9", "negotiator": "~0.6.4", - "on-headers": "~1.0.2", + "on-headers": "~1.1.0", "safe-buffer": "5.2.1", "vary": "~1.1.2" }, @@ -1746,9 +1746,9 @@ "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^8.3.0", - "eslint-visitor-keys": "^4.2.0", - "espree": "^10.3.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", @@ -1783,9 +1783,9 @@ } }, "node_modules/eslint-scope": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.3.0.tgz", - "integrity": "sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==", + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", "dev": true, "dependencies": { "esrecurse": "^4.3.0", @@ -1799,9 +1799,9 @@ } }, "node_modules/eslint-visitor-keys": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", - "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", "dev": true, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1810,15 +1810,30 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/eslint/node_modules/@eslint/config-array": { + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.20.1.tgz", + "integrity": "sha512-OL0RJzC/CBzli0DrrR31qzj6d6i6Mm3HByuhflhl4LOBiWxN+3i6/t/ZQQNii4tjksXi8r2CRW1wMpWA2ULUEw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.6", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, "node_modules/espree": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", - "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", "dev": true, "dependencies": { - "acorn": "^8.14.0", + "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.2.0" + "eslint-visitor-keys": "^4.2.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1938,15 +1953,15 @@ } }, "node_modules/express-session": { - "version": "1.18.1", - "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.18.1.tgz", - "integrity": "sha512-a5mtTqEaZvBCL9A9aqkrtfz+3SMDhOVUnjafjo+s7A9Txkq+SVX2DLvSp1Zrv4uCXa3lMSK3viWnh9Gg07PBUA==", + "version": "1.18.2", + "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.18.2.tgz", + "integrity": "sha512-SZjssGQC7TzTs9rpPDuUrR23GNZ9+2+IkA/+IJWmvQilTr5OSliEHGF+D9scbIpdC6yGtTI0/VhaHoVes2AN/A==", "dependencies": { "cookie": "0.7.2", "cookie-signature": "1.0.7", "debug": "2.6.9", "depd": "~2.0.0", - "on-headers": "~1.0.2", + "on-headers": "~1.1.0", "parseurl": "~1.3.3", "safe-buffer": "5.2.1", "uid-safe": "~2.1.5" @@ -3710,9 +3725,9 @@ } }, "node_modules/on-headers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", - "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz", + "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==", "engines": { "node": ">= 0.8" } diff --git a/package.json b/package.json index da5d750..0ebc699 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "scripts": { "pretest": "npm run test:lint", "test": "npm run test:unit", - "posttest": "npm audit", + "posttest": "npm audit --registry='https://registry.npmjs.org'", "test:lint": "eslint .", "test:unit": "nyc _mocha", "prepare": "husky" diff --git a/test/unit/middleware/spec.index.js b/test/unit/middleware/spec.index.js index ab6de4d..7536e7e 100644 --- a/test/unit/middleware/spec.index.js +++ b/test/unit/middleware/spec.index.js @@ -1,20 +1,16 @@ const proxyquire = require('proxyquire').noPreserveCache(); +const express = require('express'); describe('middleware functions', () => { let middleware, app; beforeEach(() => { - app = { - locals: { existing: 'local' }, - set: sinon.stub(), - enable: sinon.stub(), - disable: sinon.stub(), - engine: sinon.stub(), - use: sinon.stub(), - get: sinon.stub(), - listen: sinon.stub().yields() - }; + app = express(); + sinon.stub(app, 'listen').yields(); + sinon.stub(app, 'use'); + sinon.stub(app, 'set'); + sinon.stub(app, 'get'); }); it('exports middleware functions', () => { @@ -48,45 +44,19 @@ describe('middleware functions', () => { }; stubs = { express: sinon.stub().returns(app), - hmpoLogger: { - middleware: sinon.stub().returns('hmpoLogger middleware') - }, - bodyParser: { - urlencoded: sinon.stub().returns('bodyParser middleware') - }, - cookies: { - middleware: sinon.stub().returns('cookies middleware') - }, - headers: { - setup: sinon.stub().returns({}) - }, - healthcheck: { - middleware: sinon.stub().returns('healthcheck middleware') - }, - version: { - middleware: sinon.stub().returns('version middleware') - }, - featureFlag: { - middleware: sinon.stub().returns('featureFlag middleware') - }, - businessFlag: { - middleware: sinon.stub().returns('businessFlag middleware') - }, - modelOptions: { - middleware: sinon.stub().returns('modelOptions middleware') - }, - public: { - middleware: sinon.stub().returns('public middleware') - }, - nunjucks: { - setup: sinon.stub().returns(nunjucksEnv) - }, - translation: { - setup: sinon.stub() - }, - hmpoComponents: { - setup: sinon.stub() - } + hmpoLogger: { middleware: sinon.stub().callsFake(() => (req, res, next) => next()) }, + bodyParser: { urlencoded: sinon.stub().returns((req, res, next)=>next()) }, + cookies: { middleware: sinon.stub().callsFake(() => (req, res, next)=>next()) }, + headers: { setup: sinon.stub().returns({}) }, + healthcheck: { middleware: sinon.stub().callsFake(() => (req, res, next)=>next()) }, + version: { middleware: sinon.stub().callsFake(() => (req, res, next)=>next()) }, + featureFlag: { middleware: sinon.stub().callsFake(() => (req, res, next)=>next()) }, + businessFlag: { middleware: sinon.stub().callsFake(() => (req, res, next)=>next()) }, + modelOptions: { middleware: sinon.stub().callsFake(() => (req, res, next)=>next()) }, + public: { middleware: sinon.stub().callsFake(() => (req, res, next)=>next()) }, + nunjucks: { setup: sinon.stub().returns(nunjucksEnv) }, + translation: { setup: sinon.stub() }, + hmpoComponents: { setup: sinon.stub() } }; middleware = proxyquire(APP_ROOT + '/middleware', { @@ -107,14 +77,18 @@ describe('middleware functions', () => { }); }); + it('should throw if setup is called without arguments', () => { + expect(() => middleware.setup()).to.throw(Error, "Argument 'app' must be specified"); + }); + it('should not register hmpoLogger middleware if requestLogging is false', () => { middleware.setup({ app, urls: {}, publicOptions: {}, cookieOptions: {}, modelOptionsConfig: {}, featureFlags: {}, businessFlag: {}, requestLogging: false, stubs }); expect(stubs.hmpoLogger.middleware).to.not.have.been.called; - expect(app.use).to.not.have.been.calledWith('hmpoLogger middleware'); + expect(app.use).to.not.have.been.calledWith(stubs.hmpoLogger.middleware); }); it('should use the public middleware when publicOptions is true or not set', () => { - middleware.setup({ + middleware.setup({app, urls: { public: '/public-url' }, @@ -134,7 +108,7 @@ describe('middleware functions', () => { publicImagesDirs: ['assets/images'], public: { maxAge: 3600 } }); - app.use.should.have.been.calledWithExactly('public middleware'); + expect(app.use).to.not.have.been.calledWith(stubs.public.middleware); }); it('should not use public middleware when publicOptions is false', () => { @@ -143,7 +117,7 @@ describe('middleware functions', () => { const publicDirs = []; const publicImagesDirs = []; - middleware.setup({ + middleware.setup({app, urls, publicDirs, publicImagesDirs, @@ -151,13 +125,13 @@ describe('middleware functions', () => { }); stubs.public.middleware.should.not.have.been.called; - app.use.should.not.have.been.calledWith('public middleware'); + app.use.should.not.have.been.calledWith(stubs.public.middleware); }); it('should set default version and healthcheck URLs if not provided', () => { const urls = {}; - middleware.setup({ urls }); + middleware.setup({app, urls }); expect(urls.version).to.equal('/version'); expect(urls.healthcheck).to.equal('/healthcheck'); @@ -169,54 +143,53 @@ describe('middleware functions', () => { healthcheck: '/custom-healthcheck' }; - middleware.setup({ urls }); + middleware.setup({app, urls }); expect(urls.version).to.equal('/custom-version'); expect(urls.healthcheck).to.equal('/custom-healthcheck'); }); - it('should create a new express app', () => { - const returnedApp = middleware.setup(); - stubs.express.should.have.been.calledWithExactly(); - returnedApp.should.equal(app); - }); - it('should set the express env value', () => { - middleware.setup(); + middleware.setup({app}); app.set.should.have.been.calledWithExactly('env', 'development'); }); it('should use the env value specified in options', () => { - middleware.setup({ env: 'production' }); + middleware.setup({app, env: 'production' }); app.set.should.have.been.calledWithExactly('env', 'production'); }); it('should use the /version middleware', () => { - middleware.setup(); - stubs.version.middleware.should.have.been.calledWithExactly(); - app.get.should.have.been.calledWithExactly('/version', 'version middleware'); + middleware.setup({ app, stubs }); + + expect(stubs.version.middleware).to.have.been.calledWithExactly(); + + expect(app.get).to.have.been.calledWith('/version', sinon.match.func); }); + it('should not use the /version middleware', () => { - middleware.setup({ urls: { version: false }}); + middleware.setup({ app, urls: { version: false }}); stubs.version.middleware.should.not.have.been.called; - app.get.should.not.have.been.calledWithExactly('/version', 'version middleware'); + expect(app.get).to.not.have.been.calledWith('/version', sinon.match.func); }); it('should use the /healthcheck middleware', () => { - middleware.setup(); - stubs.healthcheck.middleware.should.have.been.calledWithExactly(); - app.get.should.have.been.calledWithExactly('/healthcheck', 'healthcheck middleware'); + middleware.setup({ app, stubs }); + + expect(stubs.healthcheck.middleware).to.have.been.calledWithExactly(); + + expect(app.get).to.have.been.calledWith('/healthcheck', sinon.match.func); }); it('should not use the /healthcheck middleware', () => { - middleware.setup({ urls: { healthcheck: false }}); + middleware.setup({app, urls: { healthcheck: false }}); stubs.healthcheck.middleware.should.not.have.been.called; - app.get.should.not.have.been.calledWithExactly('/healthcheck', 'healthcheck middleware'); + app.get.should.not.have.been.calledWithExactly('/healthcheck', sinon.match.func); }); it('should use the /public middleware', () => { - middleware.setup({ + middleware.setup({app, urls: { public: '/public-url' }, @@ -224,7 +197,7 @@ describe('middleware functions', () => { publicImagesDirs: ['assets/images'], public: { maxAge: 3600 } }); - stubs.public.middleware.should.have.been.calledWithExactly({ + expect(stubs.public.middleware).to.have.been.calledWith({ urls: { public: '/public-url', publicImages: '/public-url/images', @@ -235,76 +208,98 @@ describe('middleware functions', () => { publicImagesDirs: ['assets/images'], public: { maxAge: 3600 } }); - app.use.should.have.been.calledWithExactly('public middleware'); }); it('should use the hmpoLogger middleware', () => { - middleware.setup(); - stubs.hmpoLogger.middleware.should.have.been.calledWithExactly(':request'); - app.use.should.have.been.calledWithExactly('hmpoLogger middleware'); + middleware.setup({ app }); + + expect(stubs.hmpoLogger.middleware).to.have.been.calledWith(':request'); + + expect(app.use).to.have.been.calledWith(sinon.match.func); }); it('should use the modelOptions middleware', () => { - middleware.setup({ modelOptions: { sessionIDHeader: 'ID' } }); - stubs.modelOptions.middleware.should.have.been.calledWithExactly({ sessionIDHeader: 'ID' }); - app.use.should.have.been.calledWithExactly('modelOptions middleware'); + middleware.setup({ + app, + modelOptions: { sessionIDHeader: 'ID' }, + stubs + }); + + expect(stubs.modelOptions.middleware).to.have.been.calledWithExactly({ sessionIDHeader: 'ID' }); + + expect(app.use).to.have.been.calledWith(sinon.match.func); }); it('should use the feature flag setup middleware', () => { middleware.setup({ - featureFlags: { testFeature: true } + app, + featureFlags: { testFeature: true }, + stubs }); - stubs.featureFlag.middleware.should.have.been.calledWithExactly({ + + expect(stubs.featureFlag.middleware).to.have.been.calledWithExactly({ featureFlags: { testFeature: true } }); - app.use.should.have.been.calledWithExactly('featureFlag middleware'); + + expect(app.use).to.have.been.calledWith(sinon.match.func); }); + it('should use the business flag setup middleware', () => { middleware.setup({ - businessFlags: { testBusinessFeatureFlag: true } + app, + businessFlags: { testBusinessFeatureFlag: true }, + stubs }); - stubs.businessFlag.middleware.should.have.been.calledWithExactly({ + + expect(stubs.businessFlag.middleware).to.have.been.calledWithExactly({ businessFlags: { testBusinessFeatureFlag: true } }); - app.use.should.have.been.calledWithExactly('businessFlag middleware'); + + expect(app.use).to.have.been.calledWith(sinon.match.func); }); it('should use the cookies middleware', () => { - middleware.setup({ cookies: { secret: 'test' } }); - stubs.cookies.middleware.should.have.been.calledWithExactly({ secret: 'test' }); - app.use.should.have.been.calledWithExactly('cookies middleware'); + middleware.setup({ app, cookies: { secret: 'test' } }); + expect(stubs.cookies.middleware).to.have.been.calledWithExactly({ secret: 'test' }); + + expect(app.use).to.have.been.calledWith(sinon.match.func); }); it('should use the body parser middleware', () => { - middleware.setup(); - stubs.bodyParser.urlencoded.should.have.been.calledWithExactly({ extended: true }); - app.use.should.have.been.calledWithExactly('bodyParser middleware'); + middleware.setup({ app, stubs }); + + expect(stubs.bodyParser.urlencoded).to.have.been.calledWithExactly({ extended: true }); + + expect(app.use).to.have.been.calledWith(sinon.match.func); }); + it('should setup nunjucks', () => { - middleware.setup({ views: 'a/dir', nunjucks: { additional: 'options' } }); + middleware.setup({ app, views: 'a/dir', nunjucks: { additional: 'options' } }); stubs.nunjucks.setup.should.have.been.calledWithExactly(app, { views: 'a/dir', additional: 'options' }); }); it('should setup translation', () => { - middleware.setup({ locales: 'a/dir', translation: { additional: 'options' } }); + middleware.setup({app, locales: 'a/dir', translation: { additional: 'options' } }); stubs.translation.setup.should.have.been.calledWithExactly(app, { locales: 'a/dir', additional: 'options' }); }); it('should setup headers', () => { - middleware.setup({ disableCompression: true, trustProxy: ['localhost'], urls: { public: '/static'}, helmet: { referrerPolicy: { policy: 'no-referrer' } } }); + middleware.setup({app, disableCompression: true, trustProxy: ['localhost'], urls: { public: '/static'}, helmet: { referrerPolicy: { policy: 'no-referrer' } } }); stubs.headers.setup.should.have.been.calledWithExactly(app, { disableCompression: true, trustProxy: ['localhost'], publicPath: '/static', helmet: { referrerPolicy: { policy: 'no-referrer' } }}); }); it('should setup hmpoComponents', () => { - middleware.setup(); + middleware.setup({app}); stubs.hmpoComponents.setup.should.have.been.calledWithExactly(app, nunjucksEnv); }); it('should set the globals', () => { - middleware.setup({ + app.locals = { existing: 'local' }; + + middleware.setup({app, urls: { foo: 'bar' } }); app.locals.should.deep.equal({ @@ -320,6 +315,7 @@ describe('middleware functions', () => { } }); }); + it('should set res.locals.baseUrl to req.baseUrl during middleware setup', () => { const req = { baseUrl: '/test-url' }; const res = { locals: {} }; @@ -331,10 +327,10 @@ describe('middleware functions', () => { } }); - middleware.setup(); + middleware.setup({ app }); expect(res.locals.baseUrl).to.equal('/test-url'); - expect(next.calledOnce).to.be.true; + expect(next.called).to.be.true; }); }); diff --git a/test/unit/spec.index.js b/test/unit/spec.index.js index 61130c6..2e3926b 100644 --- a/test/unit/spec.index.js +++ b/test/unit/spec.index.js @@ -1,5 +1,6 @@ const index = require(APP_ROOT); const express = require('express'); +const sinon = require('sinon'); describe('hmpo-app', () => { @@ -19,40 +20,34 @@ describe('hmpo-app', () => { }); describe('setup', () => { - let app; - beforeEach(() => { - app = { - use: sinon.stub(), - get: sinon.stub() - }; sinon.stub(express, 'Router'); - express.Router.onCall(0).returns('staticRouter'); - express.Router.onCall(1).returns('router'); - express.Router.onCall(2).returns('errorRouter'); + express.Router.onCall(0).returns(sinon.stub()); + express.Router.onCall(1).returns(sinon.stub()); + express.Router.onCall(2).returns(sinon.stub()); + sinon.stub(index.config, 'get'); sinon.stub(index.config, 'setup'); sinon.stub(index.logger, 'setup'); sinon.stub(index.redisClient, 'setup'); - sinon.stub(index.middleware, 'setup').returns(app); - sinon.stub(index.middleware, 'session'); - sinon.stub(index.middleware, 'errorHandler'); - sinon.stub(index.middleware, 'listen'); + + sinon.stub(index.middleware, 'setup').callsFake(({ app }) => app); + sinon.stub(index.middleware, 'session').callsFake(() => {}); + sinon.stub(index.middleware, 'errorHandler').callsFake(() => {}); + sinon.stub(index.middleware, 'listen').callsFake(() => {}); }); + afterEach(() => { - express.Router.restore(); - index.config.get.restore(); - index.config.setup.restore(); - index.logger.setup.restore(); - index.redisClient.setup.restore(); - index.middleware.setup.restore(); - index.middleware.session.restore(); - index.middleware.errorHandler.restore(); - index.middleware.listen.restore(); + sinon.restore(); + }); + + it('should work when called without options', () => { + const result = index.setup(); + result.should.have.keys('app', 'staticRouter', 'router', 'errorRouter'); }); it('calls config.setup', () => { - index.setup(); + index.setup({}); index.config.setup.should.have.been.calledWithExactly(undefined); }); @@ -96,8 +91,9 @@ describe('hmpo-app', () => { it('calls middleware.setup with options', () => { index.config.get.withArgs().returns({ config: true }); - index.setup({ option: true }); - index.middleware.setup.should.have.been.calledWithExactly({ + const result = index.setup({ option: true }); + index.middleware.setup.should.have.been.calledWithMatch({ + app: result.app, option: true, config: true }); @@ -105,11 +101,11 @@ describe('hmpo-app', () => { it('calls middleware.session with options', () => { index.config.get.withArgs('session').returns({ config: true }); - index.setup({ session: { option: true } }); - index.middleware.session.should.have.been.calledWithExactly(app, { - option: true, - config: true - }); + const result = index.setup({ session: { option: true } }); + index.middleware.session.should.have.been.calledWithExactly( + result.app, + { option: true, config: true } + ); }); it('should not call middleware.session if option is false', () => { @@ -117,14 +113,13 @@ describe('hmpo-app', () => { index.middleware.session.should.not.have.been.called; }); - it('calls middleware.errorHandler with options', () => { index.config.get.withArgs('errors').returns({ config: true }); - index.setup({ errors: { option: true } }); - index.middleware.errorHandler.should.have.been.calledWithExactly(app, { - option: true, - config: true - }); + const result = index.setup({ errors: { option: true } }); + index.middleware.errorHandler.should.have.been.calledWithExactly( + result.app, + { option: true, config: true } + ); }); it('should not call middleware.errorHandler if option is false', () => { @@ -132,17 +127,58 @@ describe('hmpo-app', () => { index.middleware.errorHandler.should.not.have.been.called; }); + it('should call globalMiddlewareSetupFn if option is defined', () => { + const callbackSpy = sinon.spy(); + + index.setup({ globalMiddlewareSetupFn: callbackSpy }); + + callbackSpy.should.have.been.calledWithMatch( + sinon.match.has('use').and(sinon.match.has('get')) + ); + }); + + it('should not call globalMiddlewareSetupFn if option is not defined', () => { + const callbackStub = sinon.stub(); + index.setup({}); + callbackStub.should.not.have.been.called; + }); + it('should call middlewareSetupFn if option is defined', () => { + const callbackSpy = sinon.spy(); + + index.setup({ middlewareSetupFn: callbackSpy }); + + callbackSpy.should.have.been.calledWithMatch( + sinon.match.has('use').and(sinon.match.has('get')) + ); + }); + + it('should call globalMiddlewareSetupFn and middlewareSetupFn if option is defined', () => { + const callbackSpy = sinon.spy(); + const callbackSpy2 = sinon.spy(); + index.setup({ + globalMiddlewareSetupFn: callbackSpy, + middlewareSetupFn: callbackSpy2 }); + + callbackSpy.should.have.been.calledWithMatch( + sinon.match.has('use').and(sinon.match.has('get')) + ); + callbackSpy2.should.have.been.calledWithMatch( + sinon.match.has('use').and(sinon.match.has('get')) + ); + }); + + it('should not call middlewareSetupFn if option is not defined', () => { const callbackStub = sinon.stub(); - index.setup({ middlewareSetupFn: callbackStub}); - callbackStub.should.have.been.called; + index.setup({}); + callbackStub.should.not.have.been.called; }); it('calls middleware.listen with options', () => { index.config.get.withArgs('host').returns('hostname'); index.config.get.withArgs('port').returns(1234); - index.setup({ port: 5678}); - index.middleware.listen.should.have.been.calledWithExactly(app, { + const result = index.setup({ port: 5678 }); + index.middleware.listen.should.have.been.calledWithExactly(result.app, { port: 5678, host: 'hostname' }); @@ -154,14 +190,13 @@ describe('hmpo-app', () => { }); it('returns apps and routers', () => { - const routers = index.setup(); - routers.should.eql({ - app, - staticRouter: 'staticRouter', - router: 'router', - errorRouter: 'errorRouter' + const result = index.setup({}); + result.should.eql({ + app: result.app, + staticRouter: express.Router.getCall(0).returnValue, + router: express.Router.getCall(1).returnValue, + errorRouter: express.Router.getCall(2).returnValue }); }); }); - });