diff --git a/.env.example b/.env.example index fe8c231..387312a 100644 --- a/.env.example +++ b/.env.example @@ -1,12 +1,16 @@ -PATH_SSL_PRIVATE_KEY=./infrastructure/host/test.key -PATH_SSL_CERTIFICATE=./infrastructure/host/test.cert - -PORT=3000 -NODE_ENV=development - BASE_URL=http://localhost:3000 CDN_HOST=test +FEATURE_FLAG_ENABLE_AUTH=true +HUMAN=true +LOG_LEVEL=info +NODE_ENV=development NODE_SSL_ENABLED=false +PATH_SSL_CERTIFICATE=./infrastructure/host/test.cert +PATH_SSL_PRIVATE_KEY=./infrastructure/host/test.key +PORT=3000 +SESSION_APP_KEY=git +SESSION_ID_NAME=connect.sid + + + -LOG_LEVEL=info -HUMAN=true \ No newline at end of file diff --git a/.eslintrc.json b/.eslintrc.json index 0c755ce..1bb3235 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,59 +1,63 @@ { - "env": { - "node": true, - "es2021": true - }, - "extends": [ - "eslint:recommended", - "plugin:@typescript-eslint/recommended" - ], - "parser": "@typescript-eslint/parser", - "parserOptions": { - "ecmaVersion": "latest" - }, - "plugins": [ - "@typescript-eslint" - ], - "rules": { - "@typescript-eslint/ban-types": "off", - "@typescript-eslint/explicit-module-boundary-types": "off", - "@typescript-eslint/no-explicit-any": "off", - "@typescript-eslint/no-inferrable-types": "off", - "@typescript-eslint/no-non-null-assertion": "off", - "@typescript-eslint/no-unused-vars": [ "error", { "argsIgnorePattern": "^_" } ], - "@typescript-eslint/semi": [ "error", "always" ], - "@typescript-eslint/type-annotation-spacing": "error", - "@typescript-eslint/quotes": [ "error", "single",{ "avoidEscape": true, "allowTemplateLiterals": true }], - "arrow-spacing": "error", - "brace-style": [ "error", "1tbs", { "allowSingleLine": true } ], - "comma-spacing": [ "error", { "before": false, "after": true } ], - "curly": "error", - "eqeqeq": "error", - "eol-last": ["warn", "always"], - "indent": [ "error", 4, - { - "FunctionExpression": { - "parameters": "first" - }, - "CallExpression": { - "arguments": "first" - }, - "outerIIFEBody": 2, - "SwitchCase": 1 - } - ], - "key-spacing": [ "error", { "afterColon": true } ], - "keyword-spacing": [ "error", { "before": true, "after": true } ], - "no-irregular-whitespace": "error", - "no-trailing-spaces": "error", - "no-multi-spaces": "error", - "no-multiple-empty-lines":["error", { "max": 1, "maxEOF": 1 }], - "no-underscore-dangle": [ "error", { "allowFunctionParams": true } ], - "no-unused-vars": "off", - "no-whitespace-before-property": "error", - "object-curly-spacing": [ "error", "always" ], - "require-await": "error", - "space-infix-ops": "error", - "spaced-comment": [ "error", "always", { "markers": [ "/", "*" ] } ] - } -} \ No newline at end of file + "env": { + "node": true, + "es2021": true + }, + "extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended"], + "parser": "@typescript-eslint/parser", + "parserOptions": { + "ecmaVersion": "latest" + }, + "plugins": ["@typescript-eslint"], + "rules": { + "@typescript-eslint/ban-types": "off", + "@typescript-eslint/explicit-module-boundary-types": "off", + "@typescript-eslint/no-explicit-any": "off", + "@typescript-eslint/no-inferrable-types": "off", + "@typescript-eslint/no-non-null-assertion": "off", + "@typescript-eslint/no-unused-vars": [ + "error", + { "argsIgnorePattern": "^_" } + ], + "@typescript-eslint/semi": ["error", "always"], + "@typescript-eslint/type-annotation-spacing": "error", + "@typescript-eslint/quotes": [ + "error", + "single", + { "avoidEscape": true, "allowTemplateLiterals": true } + ], + "arrow-spacing": "error", + "brace-style": ["error", "1tbs", { "allowSingleLine": true }], + "comma-spacing": ["error", { "before": false, "after": true }], + "curly": "error", + "eqeqeq": "error", + "eol-last": ["warn", "always"], + "indent": [ + "error", + 4, + { + "FunctionExpression": { + "parameters": "first" + }, + "CallExpression": { + "arguments": "first" + }, + "outerIIFEBody": 2, + "SwitchCase": 1 + } + ], + "key-spacing": ["error", { "afterColon": true }], + "keyword-spacing": ["error", { "before": true, "after": true }], + "no-irregular-whitespace": "error", + "no-trailing-spaces": "error", + "no-multi-spaces": "error", + "no-multiple-empty-lines": ["error", { "max": 1, "maxEOF": 1 }], + "no-underscore-dangle": ["error", { "allowFunctionParams": true }], + "no-unused-vars": "off", + "no-whitespace-before-property": "error", + "object-curly-spacing": ["error", "always"], + "require-await": "error", + "space-infix-ops": "error", + "spaced-comment": ["error", "always", { "markers": ["/", "*"] }] + } +} diff --git a/docker-compose.yml b/docker-compose.yml index c4c5401..f9be941 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -7,12 +7,23 @@ services: dockerfile: ./infrastructure/docker/${NODE_ENV}/Dockerfile ports: - "${PORT}:3000" + volumes: + - ./:/app environment: - - NODE_ENV=${NODE_ENV} - - PATH_SSL_PRIVATE_KEY=${PATH_SSL_PRIVATE_KEY} - - PATH_SSL_CERTIFICATE=${PATH_SSL_CERTIFICATE} - - CDN_HOST=${CDN_HOST} - - NODE_SSL_ENABLED=${NODE_SSL_ENABLED} + - AUTH_SIGN_IN_URL=${AUTH_SIGN_IN_URL} - BASE_URL=${BASE_URL} + - CDN_HOST=${CDN_HOST} + - COOKIE_ID_NAME=${COOKIE_ID_NAME} + - COOKIE_PARSER_SECRET=${COOKIE_PARSER_SECRET} + - COOKIE_SESSION_SECRET=${COOKIE_SESSION_SECRET} + - FEATURE_FLAG_ENABLE_AUTH=${FEATURE_FLAG_ENABLE_AUTH} - HUMAN=${HUMAN} - - LOG_LEVEL=${LOG_LEVEL} \ No newline at end of file + - LOG_LEVEL=${LOG_LEVEL} + - NODE_ENV=${NODE_ENV} + - NODE_SSL_ENABLED=${NODE_SSL_ENABLED} + - PATH_SSL_CERTIFICATE=${PATH_SSL_CERTIFICATE} + - PATH_SSL_PRIVATE_KEY=${PATH_SSL_PRIVATE_KEY} + - SESSION_APP_KEY=${SESSION_APP_KEY} + - SESSION_ID_NAME=${SESSION_ID_NAME} + - USER_POOL_CLIENT_ID=${USER_POOL_CLIENT_ID} + - USER_POOL_ID=${USER_POOL_ID} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 73af7d4..d1f7e03 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,11 +10,13 @@ "license": "MIT", "dependencies": { "@co-digital/logging": "^1.0.1", + "@co-digital/login": "^1.0.3", "cookie-parser": "^1.4.6", "cors": "^2.8.5", "crypto": "^1.0.1", "express": "^4.18.2", "express-rate-limit": "^7.3.1", + "express-validator": "^7.1.0", "govuk-frontend": "^4.8.0", "helmet": "^7.0.0", "nunjucks": "^3.2.4" @@ -736,9 +738,9 @@ "dev": true }, "node_modules/@co-digital/logging": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@co-digital/logging/-/logging-1.0.1.tgz", - "integrity": "sha512-lHJb7MRAPiZ4GaJzJXqsgEnpxm2rDE6ZMbMqtc+ZymD7BDswobZ6/BOBQCZOajD1zoZOm02fecKvnH7d2gONxw==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@co-digital/logging/-/logging-1.0.2.tgz", + "integrity": "sha512-/9e9uG3vPntblBac3x9jpSbbMujsnDBNu3unx1YJ2dBsyFRVEnZOVIfuJcmEwlnxXqpmwA+eB05W/7Kz7jdDGw==", "dependencies": { "luxon": "^3.4.3", "winston": "^3.11.0" @@ -748,6 +750,20 @@ "npm": ">=10.0.0" } }, + "node_modules/@co-digital/login": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@co-digital/login/-/login-1.0.3.tgz", + "integrity": "sha512-B8TPdsXg/UcQ27FBUFgPW8wrDq04qHBH56GdHOWhXnVA5PT46wUpGQ1yLVLMFg+88txdDmAGb0HViTYkqiv/tw==", + "dependencies": { + "@co-digital/logging": "^1.0.2", + "cookie-parser": "^1.4.6", + "jwt-decode": "^4.0.0" + }, + "engines": { + "node": ">=20.8.0", + "npm": ">=10.0.0" + } + }, "node_modules/@colors/colors": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.6.0.tgz", @@ -3131,6 +3147,18 @@ "express": "4 || 5 || ^5.0.0-beta.1" } }, + "node_modules/express-validator": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/express-validator/-/express-validator-7.1.0.tgz", + "integrity": "sha512-ePn6NXjHRZiZkwTiU1Rl2hy6aUqmi6Cb4/s8sfUsKH7j2yYl9azSpl8xEHcOj1grzzQ+UBEoLWtE1s6FDxW++g==", + "dependencies": { + "lodash": "^4.17.21", + "validator": "~13.12.0" + }, + "engines": { + "node": ">= 8.0.0" + } + }, "node_modules/express/node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -4511,6 +4539,14 @@ "node": ">=6" } }, + "node_modules/jwt-decode": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-4.0.0.tgz", + "integrity": "sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==", + "engines": { + "node": ">=18" + } + }, "node_modules/keyv": { "version": "4.5.3", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.3.tgz", @@ -4577,6 +4613,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, "node_modules/lodash.memoize": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", @@ -6201,6 +6242,14 @@ "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", "dev": true }, + "node_modules/validator": { + "version": "13.12.0", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.12.0.tgz", + "integrity": "sha512-c1Q0mCiPlgdTVVVIJIrBuxNicYE+t/7oKeI9MWLj3fh/uq2Pxh/3eeWbVZ4OcGW1TUf53At0njHw5SMdA3tmMg==", + "engines": { + "node": ">= 0.10" + } + }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", diff --git a/package.json b/package.json index 8d7d6a2..e050159 100644 --- a/package.json +++ b/package.json @@ -1,52 +1,54 @@ { - "name": "poc_node_protoype", - "version": "1.0.0", - "description": "POC Node Prototype", - "main": "dist/server.js", - "scripts": { - "build": "tsc && cp -r src/views dist/", - "start": "node dist/server.js", - "start:dev": "npm run build && nodemon", - "test": "jest", - "coverage": "jest --coverage", - "lint": "eslint '{src,test}/**/*.ts'", - "lint:fix": "eslint '{src,test}/**/*.ts' --fix", - "prepare": "husky install" - }, - "engines": { - "npm": ">=10.0.0", - "node": ">=20.0.0" - }, - "author": "X-CO Developers", - "license": "MIT", - "devDependencies": { - "@types/cookie-parser": "^1.4.4", - "@types/cors": "^2.8.14", - "@types/express": "^4.17.17", - "@types/jest": "^29.5.4", - "@types/node": "^20.6.2", - "@types/nunjucks": "^3.2.4", - "@types/supertest": "^2.0.14", - "@typescript-eslint/eslint-plugin": "^6.6.0", - "@typescript-eslint/parser": "^6.6.0", - "eslint": "^8.49.0", - "husky": "^8.0.0", - "jest": "^29.7.0", - "nodemon": "^3.0.3", - "supertest": "^7.0.0", - "ts-jest": "^29.1.1", - "ts-node": "^10.9.2", - "typescript": "^5.2.2" - }, - "dependencies": { - "@co-digital/logging": "^1.0.1", - "cookie-parser": "^1.4.6", - "cors": "^2.8.5", - "crypto": "^1.0.1", - "express": "^4.18.2", - "express-rate-limit": "^7.3.1", - "govuk-frontend": "^4.8.0", - "helmet": "^7.0.0", - "nunjucks": "^3.2.4" - } + "name": "poc_node_protoype", + "version": "1.0.0", + "description": "POC Node Prototype", + "main": "dist/server.js", + "scripts": { + "build": "tsc && cp -r src/views dist/", + "start": "node dist/server.js", + "start:dev": "npm run build && nodemon", + "test": "jest", + "coverage": "jest --coverage", + "lint": "eslint '{src,test}/**/*.ts'", + "lint:fix": "eslint '{src,test}/**/*.ts' --fix", + "prepare": "husky install" + }, + "engines": { + "npm": ">=10.0.0", + "node": ">=20.0.0" + }, + "author": "X-CO Developers", + "license": "MIT", + "devDependencies": { + "@types/cookie-parser": "^1.4.4", + "@types/cors": "^2.8.14", + "@types/express": "^4.17.17", + "@types/jest": "^29.5.4", + "@types/node": "^20.6.2", + "@types/nunjucks": "^3.2.4", + "@types/supertest": "^2.0.14", + "@typescript-eslint/eslint-plugin": "^6.6.0", + "@typescript-eslint/parser": "^6.6.0", + "eslint": "^8.49.0", + "husky": "^8.0.0", + "jest": "^29.7.0", + "nodemon": "^3.0.3", + "supertest": "^7.0.0", + "ts-jest": "^29.1.1", + "ts-node": "^10.9.2", + "typescript": "^5.2.2" + }, + "dependencies": { + "@co-digital/logging": "^1.0.1", + "@co-digital/login": "^1.0.3", + "cookie-parser": "^1.4.6", + "cors": "^2.8.5", + "crypto": "^1.0.1", + "express": "^4.18.2", + "express-rate-limit": "^7.3.1", + "express-validator": "^7.1.0", + "govuk-frontend": "^4.8.0", + "helmet": "^7.0.0", + "nunjucks": "^3.2.4" + } } diff --git a/src/config/index.ts b/src/config/index.ts index 03d13c7..0c3865f 100644 --- a/src/config/index.ts +++ b/src/config/index.ts @@ -4,9 +4,11 @@ export const PORT = getEnvironmentValue('PORT', '3000'); export const BASE_URL = getEnvironmentValue('BASE_URL', `http://localhost:${PORT}`); export const CDN_HOST = getEnvironmentValue('CDN_HOST'); export const NODE_SSL_ENABLED = getEnvironmentValue('NODE_SSL_ENABLED', 'false'); +export const SESSION_APP_KEY = getEnvironmentValue('SESSION_APP_KEY'); export const PATH_SSL_PRIVATE_KEY = getEnvironmentValue('PATH_SSL_PRIVATE_KEY', 'false'); export const PATH_SSL_CERTIFICATE = getEnvironmentValue('PATH_SSL_CERTIFICATE', 'false'); +export const NODE_ENV = getEnvironmentValue('NODE_ENV'); export const SERVICE_NAME = 'Node Prototype'; @@ -14,6 +16,7 @@ export const SERVICE_NAME = 'Node Prototype'; export const LANDING_PAGE = 'info'; export const NOT_FOUND = 'page-not-found'; export const ERROR_PAGE = 'error'; +export const NOT_AVAILABLE = 'not-available'; // Routing paths export const LANDING_URL = '/info'; @@ -21,3 +24,6 @@ export const LANDING_URL = '/info'; export const INFO_URL = '/info'; export const HEALTHCHECK_URL = '/healthcheck'; export const SERVICE_URL = `${BASE_URL}${LANDING_URL}`; + +// Feature Flags +export const FEATURE_FLAG_ENABLE_AUTH = getEnvironmentValue('FEATURE_FLAG_ENABLE_AUTH', 'false'); diff --git a/src/controller/error.controller.ts b/src/controller/error.controller.ts index 7b649a2..d387f7f 100644 --- a/src/controller/error.controller.ts +++ b/src/controller/error.controller.ts @@ -9,7 +9,7 @@ export const errorNotFound = (_req: Request, res: Response) => { export const errorHandler = (err: Error, _req: Request, res: Response, _next: NextFunction) => { const statusCode = !res.statusCode || res.statusCode === 200 ? 500 : res.statusCode; - const errorMessage = err.message || 'An error has occured. Re-routing to the error screen'; + const errorMessage = err.message || 'An error has occurred. Re-routing to the error screen'; log.error(`Error ${statusCode}: ${errorMessage}`); res.status(statusCode).render(config.ERROR_PAGE); diff --git a/src/middleware/authentication.middleware.ts b/src/middleware/authentication.middleware.ts new file mode 100644 index 0000000..fb59e07 --- /dev/null +++ b/src/middleware/authentication.middleware.ts @@ -0,0 +1,24 @@ +import { NextFunction, Request, Response } from 'express'; +import { log } from '../utils/logger'; +import { colaAuthenticationMiddleware } from '@co-digital/login'; + +import * as config from '../config'; + +export const authentication = ( + req: Request, + res: Response, + next: NextFunction +) => { + try { + if (config.NODE_ENV === 'production') { + log.infoRequest(req, 'Authenticating through COLA...'); + return colaAuthenticationMiddleware(req, res, next); + } + + log.infoRequest(req, 'sorry, auth service not available right now'); + next(); + } catch (err: any) { + log.errorRequest(req, err.message); + next(err); + } +}; diff --git a/src/routes/info.ts b/src/routes/info.ts index 17a802a..4127cf3 100644 --- a/src/routes/info.ts +++ b/src/routes/info.ts @@ -1,11 +1,11 @@ import { Router } from 'express'; - +import { authentication } from '../middleware/authentication.middleware'; import { get, post } from '../controller/info.controller'; import * as config from '../config'; const infoRouter = Router(); -infoRouter.get(config.INFO_URL, get); +infoRouter.get(config.INFO_URL, authentication, get); infoRouter.post(config.INFO_URL, post); export default infoRouter; diff --git a/src/utils/isFeatureEnabled.ts b/src/utils/isFeatureEnabled.ts new file mode 100644 index 0000000..bcbf072 --- /dev/null +++ b/src/utils/isFeatureEnabled.ts @@ -0,0 +1,3 @@ +export const isFeatureEnabled = (flag: string) => { + return flag === 'true'; +}; diff --git a/src/views/not-available.html b/src/views/not-available.html new file mode 100644 index 0000000..4e52823 --- /dev/null +++ b/src/views/not-available.html @@ -0,0 +1,9 @@ +{% extends "layout.html" %} + +{% block content %} +
You will be able to use the service at a later date.
+{% endblock %} diff --git a/test/setup.ts b/test/setup.ts index 484c925..4e9c36d 100644 --- a/test/setup.ts +++ b/test/setup.ts @@ -2,6 +2,13 @@ export default () => { process.env.LOG_LEVEL = 'info'; process.env.HUMAN = 'true'; process.env.TEST_KEY = 'test'; + process.env.SESSION_APP_KEY = 'test'; + process.env.AUTH_SIGN_IN_URL = 'test'; process.env.CDN_HOST = 'test'; + process.env.SESSION_ID_NAME = 'test'; + process.env.COOKIE_ID_NAME = 'secret'; + process.env.USER_POOL_ID = 'secret'; + process.env.USER_POOL_CLIENT_ID = 'secret'; + process.env.COOKIE_PARSER_SECRET = 'secret'; process.env.UNSANITISED_TEST_KEY = ' test '; }; diff --git a/test/unit/controller/error.controller.spec.ts b/test/unit/controller/error.controller.spec.ts index 50c7f9b..b4a23b3 100644 --- a/test/unit/controller/error.controller.spec.ts +++ b/test/unit/controller/error.controller.spec.ts @@ -1,15 +1,25 @@ -import { describe, beforeEach, afterEach, expect, test, jest } from '@jest/globals'; +import { + describe, + beforeEach, + afterEach, + expect, + test, + jest, +} from '@jest/globals'; import { Request, Response, NextFunction } from 'express'; -import { errorNotFound, errorHandler } from '../../../src/controller/error.controller'; +import { + errorNotFound, + errorHandler, +} from '../../../src/controller/error.controller'; import * as config from '../../../src/config'; import { MOCK_ERROR } from '../../mock/data'; import { log } from '../../../src/utils/logger'; jest.mock('../../../src/utils/logger', () => ({ log: { - error: jest.fn() - } + error: jest.fn(), + }, })); const req = {} as Request; @@ -83,7 +93,8 @@ describe('Error controller tests', () => { test('should log alternate error message', () => { const res = mockResponse(); MOCK_ERROR.message = ''; - const errorLogMessage = 'Error 500: An error has occured. Re-routing to the error screen'; + const errorLogMessage = + 'Error 500: An error has occurred. Re-routing to the error screen'; errorHandler(MOCK_ERROR, req, res, next); diff --git a/test/unit/middleware/authentication.middleware.spec.ts b/test/unit/middleware/authentication.middleware.spec.ts new file mode 100644 index 0000000..775cd83 --- /dev/null +++ b/test/unit/middleware/authentication.middleware.spec.ts @@ -0,0 +1,82 @@ +jest.mock('../../../src/utils/logger', () => ({ + log: { + infoRequest: jest.fn(), + errorRequest: jest.fn(), + }, +})); +jest.mock('../../../src/config/index.ts', () => ({ + __esModule: true, + NODE_ENV: null, +})); +jest.mock('@co-digital/login'); + +import { describe, expect, test, jest, afterEach } from '@jest/globals'; +import { Request, Response, NextFunction } from 'express'; + +import { authentication } from '../../../src/middleware/authentication.middleware'; +import { log } from '../../../src/utils/logger'; +import * as config from '../../../src/config'; + +import { mockRequest, mockResponse, mockNext } from '../../mock/express.mock'; +import { colaAuthenticationMiddleware } from '@co-digital/login'; +import { MOCK_ERROR } from '../../mock/data'; + +const configMock = config as { NODE_ENV: string }; +const logInfoRequestMock = log.infoRequest as jest.Mock; +const logErrorRequestMock = log.errorRequest as jest.Mock; +const colaAuthenticationMiddlewareMock = + colaAuthenticationMiddleware as jest.Mock; + +describe('Authentication Middleware test suites', () => { + let req: Request; + let res: Response; + let next: NextFunction; + + beforeEach(() => { + req = mockRequest(); + res = mockResponse(); + next = mockNext; + }); + + afterEach(() => { + jest.resetAllMocks(); + }); + + test('Should call next if auth feature is enabled', () => { + configMock.NODE_ENV = 'production'; + + authentication(req, res, next); + + expect(logInfoRequestMock).toHaveBeenCalledWith( + req, + 'Authenticating through COLA...' + ); + expect(colaAuthenticationMiddlewareMock).toHaveBeenCalledTimes(1); + }); + + test('should call res.render with not-available view if auth feature is disabled', () => { + configMock.NODE_ENV = 'development'; + + authentication(req, res, next); + + expect(logInfoRequestMock).toHaveBeenCalledTimes(1); + expect(logInfoRequestMock).toHaveBeenCalledWith( + req, + 'sorry, auth service not available right now' + ); + }); + + test('should call next with error object if error is thrown', () => { + configMock.NODE_ENV = 'production'; + + colaAuthenticationMiddlewareMock.mockImplementationOnce(() => { + throw new Error(MOCK_ERROR.message); + }); + + authentication(req, res, next); + + expect(logErrorRequestMock).toHaveBeenCalledTimes(1); + expect(logErrorRequestMock).toHaveBeenCalledWith(req, MOCK_ERROR.message); + expect(next).toHaveBeenCalledTimes(1); + }); +}); diff --git a/test/unit/utils/isFeatureEnabled.spec.ts b/test/unit/utils/isFeatureEnabled.spec.ts new file mode 100644 index 0000000..5c36ef4 --- /dev/null +++ b/test/unit/utils/isFeatureEnabled.spec.ts @@ -0,0 +1,17 @@ +import { describe, test, expect } from '@jest/globals'; + +import { isFeatureEnabled } from '../../../src/utils/isFeatureEnabled'; + +describe('isFeaturedEnabled test suites', () => { + + test('it should return true boolean if flag passed in is the string "true"', () => { + + expect(isFeatureEnabled('true')).toBe(true); + }); + + test('it should return false if the flag passed in is not the string "true"', () => { + + expect(isFeatureEnabled('no')).toBe(false); + }); + +});