From 27061205311c618020e9622c4f690ca1c7f258af Mon Sep 17 00:00:00 2001 From: Stuart Adair <43574728+StuAA78@users.noreply.github.com> Date: Thu, 15 Dec 2022 14:52:49 +0000 Subject: [PATCH] Add Create Sroc Bill Run endpoint (#51) https://eaflood.atlassian.net/browse/WATER-3854 We need to create an endpoint for the UI to hit in order to create an sroc supplementary bill run. This PR is to create our initial endpoint and apply validation. Creating the billing batch will be done in a future PR. Note that we expect to receive the bill run type (ie. `supplementary`) in the payload as `type`, and the object it returns also contains this as `type`. This is in contrast to the `water-abstraction-service` which refers to this as `batchType`. We chose not to use `batchType` as we aim to be consistent within this repo as far as possible, and therefore we don't refer to batches if we can help it. It will therefore be necessary when updating the service to ensure that we send and receive it as `type`. Since this is the first time we're validating incoming data, we needed to decide on how we will implement validation. The solution we have come up with is to create `app/validators`, with validators sitting in an appropriate subfolder. In this case, since the validation relates to bill runs and specifically the create bill run endpoint, we create the file `app/validators/bill-runs/create-bill-run.validator.js` As part of this we also updated `ErrorPagesPlugin` to accept a `plainOutput` setting, which when set `true` in an endpoint's route will not attempt to change the response to be an HTML page. We did this so that our endpoint will return a standard JSON error response. --- app/controllers/bill-runs.controller.js | 35 ++ app/plugins/error-pages.plugin.js | 9 +- app/plugins/router.plugin.js | 2 + app/routes/bill-runs.routes.js | 21 + .../bill-runs/create-bill-run.validator.js | 25 ++ package-lock.json | 370 +++++------------- package.json | 2 + test/controllers/bill-runs.controller.test.js | 62 +++ .../bill-runs/create-bill-run.validator.js | 132 +++++++ 9 files changed, 374 insertions(+), 284 deletions(-) create mode 100644 app/controllers/bill-runs.controller.js create mode 100644 app/routes/bill-runs.routes.js create mode 100644 app/validators/bill-runs/create-bill-run.validator.js create mode 100644 test/controllers/bill-runs.controller.test.js create mode 100644 test/validators/bill-runs/create-bill-run.validator.js diff --git a/app/controllers/bill-runs.controller.js b/app/controllers/bill-runs.controller.js new file mode 100644 index 0000000000..58f661a05f --- /dev/null +++ b/app/controllers/bill-runs.controller.js @@ -0,0 +1,35 @@ +'use strict' + +/** + * Controller for /bill-runs endpoints + * @module BillRunsController + */ + +const Boom = require('@hapi/boom') + +const CreateBillRunValidator = require('../validators/bill-runs/create-bill-run.validator') + +async function createBillRun (request, _h) { + const validatedData = CreateBillRunValidator.go(request.payload) + + if (validatedData.error) { + return _formattedValidationError(validatedData.error) + } + + return { + id: 'DUMMY_SROC_BATCH', + region: validatedData.value.region, + scheme: validatedData.value.scheme, + type: validatedData.value.type, + status: 'ready' + } +} + +// Takes an error from a validator and returns a suitable Boom error +function _formattedValidationError (e) { + return Boom.badRequest(e.details[0].message) +} + +module.exports = { + createBillRun +} diff --git a/app/plugins/error-pages.plugin.js b/app/plugins/error-pages.plugin.js index 5bd7f302cf..e943be5473 100644 --- a/app/plugins/error-pages.plugin.js +++ b/app/plugins/error-pages.plugin.js @@ -1,7 +1,10 @@ 'use strict' /** - * Add an `onPreResponse` listener to return error pages + * Add an `onPreResponse` listener to return HTML error pages for Boom errors. + * + * The plugin is configured in the route's `plugins.errorPages` object. If `plainOutput` is set to `true` then the + * output will not be put into an HTML template and will simply be returned as-is. * * The bulk of this is taken from https://github.com/DEFRA/hapi-web-boilerplate and tweaked to fit how we organise our * code. For now we have removed Google Analytics (which would have been added to the `context` option) as we can @@ -17,7 +20,9 @@ const ErrorPagesPlugin = { server.ext('onPreResponse', (request, h) => { const { response } = request - if (response.isBoom) { + const { errorPages: pluginSettings } = request.route.settings.plugins + + if (response.isBoom && !pluginSettings.plainOutput) { const { statusCode } = response.output if (statusCode === 404) { diff --git a/app/plugins/router.plugin.js b/app/plugins/router.plugin.js index 650b7d1323..db50925614 100644 --- a/app/plugins/router.plugin.js +++ b/app/plugins/router.plugin.js @@ -13,6 +13,7 @@ const AirbrakeRoutes = require('../routes/airbrake.routes.js') const AssetRoutes = require('../routes/assets.routes.js') +const BillRunRoutes = require('../routes/bill-runs.routes') const DatabaseRoutes = require('../routes/database.routes.js') const FilterRoutesService = require('../services/plugins/filter-routes.service.js') const RootRoutes = require('../routes/root.routes.js') @@ -24,6 +25,7 @@ const routes = [ ...RootRoutes, ...AirbrakeRoutes, ...AssetRoutes, + ...BillRunRoutes, ...DatabaseRoutes, ...TestRoutes ] diff --git a/app/routes/bill-runs.routes.js b/app/routes/bill-runs.routes.js new file mode 100644 index 0000000000..39624d3810 --- /dev/null +++ b/app/routes/bill-runs.routes.js @@ -0,0 +1,21 @@ +'use strict' + +const BillRunsController = require('../controllers/bill-runs.controller.js') + +const routes = [ + { + method: 'POST', + path: '/bill-runs', + handler: BillRunsController.createBillRun, + options: { + description: 'Used to create a bill run', + plugins: { + errorPages: { + plainOutput: true + } + } + } + } +] + +module.exports = routes diff --git a/app/validators/bill-runs/create-bill-run.validator.js b/app/validators/bill-runs/create-bill-run.validator.js new file mode 100644 index 0000000000..76bdc098d3 --- /dev/null +++ b/app/validators/bill-runs/create-bill-run.validator.js @@ -0,0 +1,25 @@ +'use strict' + +/** + * @module CreateBillRunValidator + */ + +const Joi = require('joi') + +/** + * Checks that the payload of a `create bill run` request is valid +*/ +function go (data) { + const schema = Joi.object({ + type: Joi.string().valid('supplementary').required(), + scheme: Joi.string().valid('sroc').required(), + region: Joi.string().guid().required(), + previousBillRunId: Joi.string().guid().optional() + }) + + return schema.validate(data) +} + +module.exports = { + go +} diff --git a/package-lock.json b/package-lock.json index 2257c14434..d063cb3fb2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "license": "OGL-UK-3.0", "dependencies": { "@airbrake/node": "^2.1.8", + "@hapi/boom": "^10.0.0", "@hapi/hapi": "^21.1.0", "@hapi/inert": "^7.0.0", "@hapi/vision": "^7.0.0", @@ -19,6 +20,7 @@ "got": "^12.5.3", "govuk-frontend": "^4.4.0", "hapi-pino": "^11.0.1", + "joi": "^17.7.0", "knex": "^2.3.0", "nunjucks": "^3.2.3", "objection": "^3.0.1", @@ -455,14 +457,6 @@ "@hapi/hoek": "^10.0.0" } }, - "node_modules/@hapi/accept/node_modules/@hapi/boom": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/@hapi/boom/-/boom-10.0.0.tgz", - "integrity": "sha512-1YVs9tLHhypBqqinKQRqh7FUERIolarQApO37OWkzD+z6y6USi871Sv746zBPKcIOBuI6g6y4FrwX87mmJ90Gg==", - "dependencies": { - "@hapi/hoek": "10.x.x" - } - }, "node_modules/@hapi/ammo": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/@hapi/ammo/-/ammo-6.0.0.tgz", @@ -485,18 +479,13 @@ "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==" }, "node_modules/@hapi/boom": { - "version": "9.1.4", - "resolved": "https://registry.npmjs.org/@hapi/boom/-/boom-9.1.4.tgz", - "integrity": "sha512-Ls1oH8jaN1vNsqcaHVYJrKmgMcKsC1wcp8bujvXrHaAqD2iDYq3HoOwsxwo09Cuda5R5nC0o0IxlrlTuvPuzSw==", + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@hapi/boom/-/boom-10.0.0.tgz", + "integrity": "sha512-1YVs9tLHhypBqqinKQRqh7FUERIolarQApO37OWkzD+z6y6USi871Sv746zBPKcIOBuI6g6y4FrwX87mmJ90Gg==", "dependencies": { - "@hapi/hoek": "9.x.x" + "@hapi/hoek": "10.x.x" } }, - "node_modules/@hapi/boom/node_modules/@hapi/hoek": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", - "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==" - }, "node_modules/@hapi/bossy": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/@hapi/bossy/-/bossy-6.0.0.tgz", @@ -510,15 +499,6 @@ "@hapi/validate": "^2.0.0" } }, - "node_modules/@hapi/bossy/node_modules/@hapi/boom": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/@hapi/boom/-/boom-10.0.0.tgz", - "integrity": "sha512-1YVs9tLHhypBqqinKQRqh7FUERIolarQApO37OWkzD+z6y6USi871Sv746zBPKcIOBuI6g6y4FrwX87mmJ90Gg==", - "dev": true, - "dependencies": { - "@hapi/hoek": "10.x.x" - } - }, "node_modules/@hapi/bossy/node_modules/@hapi/topo": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-6.0.0.tgz", @@ -547,14 +527,6 @@ "@hapi/hoek": "^10.0.0" } }, - "node_modules/@hapi/bounce/node_modules/@hapi/boom": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/@hapi/boom/-/boom-10.0.0.tgz", - "integrity": "sha512-1YVs9tLHhypBqqinKQRqh7FUERIolarQApO37OWkzD+z6y6USi871Sv746zBPKcIOBuI6g6y4FrwX87mmJ90Gg==", - "dependencies": { - "@hapi/hoek": "10.x.x" - } - }, "node_modules/@hapi/bourne": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@hapi/bourne/-/bourne-3.0.0.tgz", @@ -569,14 +541,6 @@ "@hapi/hoek": "^10.0.0" } }, - "node_modules/@hapi/call/node_modules/@hapi/boom": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/@hapi/boom/-/boom-10.0.0.tgz", - "integrity": "sha512-1YVs9tLHhypBqqinKQRqh7FUERIolarQApO37OWkzD+z6y6USi871Sv746zBPKcIOBuI6g6y4FrwX87mmJ90Gg==", - "dependencies": { - "@hapi/hoek": "10.x.x" - } - }, "node_modules/@hapi/catbox": { "version": "12.1.0", "resolved": "https://registry.npmjs.org/@hapi/catbox/-/catbox-12.1.0.tgz", @@ -597,22 +561,6 @@ "@hapi/hoek": "^10.0.0" } }, - "node_modules/@hapi/catbox-memory/node_modules/@hapi/boom": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/@hapi/boom/-/boom-10.0.0.tgz", - "integrity": "sha512-1YVs9tLHhypBqqinKQRqh7FUERIolarQApO37OWkzD+z6y6USi871Sv746zBPKcIOBuI6g6y4FrwX87mmJ90Gg==", - "dependencies": { - "@hapi/hoek": "10.x.x" - } - }, - "node_modules/@hapi/catbox/node_modules/@hapi/boom": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/@hapi/boom/-/boom-10.0.0.tgz", - "integrity": "sha512-1YVs9tLHhypBqqinKQRqh7FUERIolarQApO37OWkzD+z6y6USi871Sv746zBPKcIOBuI6g6y4FrwX87mmJ90Gg==", - "dependencies": { - "@hapi/hoek": "10.x.x" - } - }, "node_modules/@hapi/catbox/node_modules/@hapi/podium": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/@hapi/podium/-/podium-5.0.0.tgz", @@ -665,14 +613,6 @@ "@hapi/boom": "^10.0.0" } }, - "node_modules/@hapi/content/node_modules/@hapi/boom": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/@hapi/boom/-/boom-10.0.0.tgz", - "integrity": "sha512-1YVs9tLHhypBqqinKQRqh7FUERIolarQApO37OWkzD+z6y6USi871Sv746zBPKcIOBuI6g6y4FrwX87mmJ90Gg==", - "dependencies": { - "@hapi/hoek": "10.x.x" - } - }, "node_modules/@hapi/cryptiles": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/@hapi/cryptiles/-/cryptiles-5.1.0.tgz", @@ -684,6 +624,19 @@ "node": ">=12.0.0" } }, + "node_modules/@hapi/cryptiles/node_modules/@hapi/boom": { + "version": "9.1.4", + "resolved": "https://registry.npmjs.org/@hapi/boom/-/boom-9.1.4.tgz", + "integrity": "sha512-Ls1oH8jaN1vNsqcaHVYJrKmgMcKsC1wcp8bujvXrHaAqD2iDYq3HoOwsxwo09Cuda5R5nC0o0IxlrlTuvPuzSw==", + "dependencies": { + "@hapi/hoek": "9.x.x" + } + }, + "node_modules/@hapi/cryptiles/node_modules/@hapi/hoek": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", + "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==" + }, "node_modules/@hapi/eslint-plugin": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/@hapi/eslint-plugin/-/eslint-plugin-6.0.0.tgz", @@ -735,14 +688,6 @@ "node": ">=14.15.0" } }, - "node_modules/@hapi/hapi/node_modules/@hapi/boom": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/@hapi/boom/-/boom-10.0.0.tgz", - "integrity": "sha512-1YVs9tLHhypBqqinKQRqh7FUERIolarQApO37OWkzD+z6y6USi871Sv746zBPKcIOBuI6g6y4FrwX87mmJ90Gg==", - "dependencies": { - "@hapi/hoek": "10.x.x" - } - }, "node_modules/@hapi/hapi/node_modules/@hapi/podium": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/@hapi/podium/-/podium-5.0.0.tgz", @@ -788,14 +733,6 @@ "@hapi/validate": "^2.0.0" } }, - "node_modules/@hapi/heavy/node_modules/@hapi/boom": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/@hapi/boom/-/boom-10.0.0.tgz", - "integrity": "sha512-1YVs9tLHhypBqqinKQRqh7FUERIolarQApO37OWkzD+z6y6USi871Sv746zBPKcIOBuI6g6y4FrwX87mmJ90Gg==", - "dependencies": { - "@hapi/hoek": "10.x.x" - } - }, "node_modules/@hapi/heavy/node_modules/@hapi/topo": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-6.0.0.tgz", @@ -831,14 +768,6 @@ "lru-cache": "^7.10.2" } }, - "node_modules/@hapi/inert/node_modules/@hapi/boom": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/@hapi/boom/-/boom-10.0.0.tgz", - "integrity": "sha512-1YVs9tLHhypBqqinKQRqh7FUERIolarQApO37OWkzD+z6y6USi871Sv746zBPKcIOBuI6g6y4FrwX87mmJ90Gg==", - "dependencies": { - "@hapi/hoek": "10.x.x" - } - }, "node_modules/@hapi/inert/node_modules/@hapi/topo": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-6.0.0.tgz", @@ -876,6 +805,14 @@ "@hapi/hoek": "9.x.x" } }, + "node_modules/@hapi/iron/node_modules/@hapi/boom": { + "version": "9.1.4", + "resolved": "https://registry.npmjs.org/@hapi/boom/-/boom-9.1.4.tgz", + "integrity": "sha512-Ls1oH8jaN1vNsqcaHVYJrKmgMcKsC1wcp8bujvXrHaAqD2iDYq3HoOwsxwo09Cuda5R5nC0o0IxlrlTuvPuzSw==", + "dependencies": { + "@hapi/hoek": "9.x.x" + } + }, "node_modules/@hapi/iron/node_modules/@hapi/bourne": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/@hapi/bourne/-/bourne-2.1.0.tgz", @@ -963,14 +900,6 @@ "@hapi/hoek": "^10.0.0" } }, - "node_modules/@hapi/pez/node_modules/@hapi/boom": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/@hapi/boom/-/boom-10.0.0.tgz", - "integrity": "sha512-1YVs9tLHhypBqqinKQRqh7FUERIolarQApO37OWkzD+z6y6USi871Sv746zBPKcIOBuI6g6y4FrwX87mmJ90Gg==", - "dependencies": { - "@hapi/hoek": "10.x.x" - } - }, "node_modules/@hapi/podium": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/@hapi/podium/-/podium-4.1.3.tgz", @@ -1048,14 +977,6 @@ "@hapi/hoek": "^10.0.0" } }, - "node_modules/@hapi/statehood/node_modules/@hapi/boom": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/@hapi/boom/-/boom-10.0.0.tgz", - "integrity": "sha512-1YVs9tLHhypBqqinKQRqh7FUERIolarQApO37OWkzD+z6y6USi871Sv746zBPKcIOBuI6g6y4FrwX87mmJ90Gg==", - "dependencies": { - "@hapi/hoek": "10.x.x" - } - }, "node_modules/@hapi/statehood/node_modules/@hapi/cryptiles": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/@hapi/cryptiles/-/cryptiles-6.0.0.tgz", @@ -1110,14 +1031,6 @@ "@hapi/wreck": "^18.0.0" } }, - "node_modules/@hapi/subtext/node_modules/@hapi/boom": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/@hapi/boom/-/boom-10.0.0.tgz", - "integrity": "sha512-1YVs9tLHhypBqqinKQRqh7FUERIolarQApO37OWkzD+z6y6USi871Sv746zBPKcIOBuI6g6y4FrwX87mmJ90Gg==", - "dependencies": { - "@hapi/hoek": "10.x.x" - } - }, "node_modules/@hapi/teamwork": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/@hapi/teamwork/-/teamwork-5.1.1.tgz", @@ -1172,14 +1085,6 @@ "@hapi/validate": "^2.0.0" } }, - "node_modules/@hapi/vision/node_modules/@hapi/boom": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/@hapi/boom/-/boom-10.0.0.tgz", - "integrity": "sha512-1YVs9tLHhypBqqinKQRqh7FUERIolarQApO37OWkzD+z6y6USi871Sv746zBPKcIOBuI6g6y4FrwX87mmJ90Gg==", - "dependencies": { - "@hapi/hoek": "10.x.x" - } - }, "node_modules/@hapi/vision/node_modules/@hapi/topo": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-6.0.0.tgz", @@ -1207,14 +1112,6 @@ "@hapi/hoek": "^10.0.0" } }, - "node_modules/@hapi/wreck/node_modules/@hapi/boom": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/@hapi/boom/-/boom-10.0.0.tgz", - "integrity": "sha512-1YVs9tLHhypBqqinKQRqh7FUERIolarQApO37OWkzD+z6y6USi871Sv746zBPKcIOBuI6g6y4FrwX87mmJ90Gg==", - "dependencies": { - "@hapi/hoek": "10.x.x" - } - }, "node_modules/@humanwhocodes/config-array": { "version": "0.10.7", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.10.7.tgz", @@ -1463,6 +1360,19 @@ "joi": "^17.3.0" } }, + "node_modules/@types/hapi__hapi/node_modules/@hapi/boom": { + "version": "9.1.4", + "resolved": "https://registry.npmjs.org/@hapi/boom/-/boom-9.1.4.tgz", + "integrity": "sha512-Ls1oH8jaN1vNsqcaHVYJrKmgMcKsC1wcp8bujvXrHaAqD2iDYq3HoOwsxwo09Cuda5R5nC0o0IxlrlTuvPuzSw==", + "dependencies": { + "@hapi/hoek": "9.x.x" + } + }, + "node_modules/@types/hapi__hapi/node_modules/@hapi/hoek": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", + "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==" + }, "node_modules/@types/hapi__mimos": { "version": "4.1.4", "resolved": "https://registry.npmjs.org/@types/hapi__mimos/-/hapi__mimos-4.1.4.tgz", @@ -3990,9 +3900,9 @@ "dev": true }, "node_modules/joi": { - "version": "17.6.2", - "resolved": "https://registry.npmjs.org/joi/-/joi-17.6.2.tgz", - "integrity": "sha512-+gqqdh1xc1wb+Lor0J9toqgeReyDOCqOdG8QSdRcEvwrcRiFQZneUCGKjFjuyBWUb3uaFOgY56yMaZ5FIc+H4w==", + "version": "17.7.0", + "resolved": "https://registry.npmjs.org/joi/-/joi-17.7.0.tgz", + "integrity": "sha512-1/ugc8djfn93rTE3WRKdCzGGt/EtiYKxITMO4Wiv6q5JL1gl9ePt4kBsl1S499nbosspfctIQTpYIhSmHA3WAg==", "dependencies": { "@hapi/hoek": "^9.0.0", "@hapi/topo": "^5.0.0", @@ -6440,16 +6350,6 @@ "requires": { "@hapi/boom": "^10.0.0", "@hapi/hoek": "^10.0.0" - }, - "dependencies": { - "@hapi/boom": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/@hapi/boom/-/boom-10.0.0.tgz", - "integrity": "sha512-1YVs9tLHhypBqqinKQRqh7FUERIolarQApO37OWkzD+z6y6USi871Sv746zBPKcIOBuI6g6y4FrwX87mmJ90Gg==", - "requires": { - "@hapi/hoek": "10.x.x" - } - } } }, "@hapi/ammo": { @@ -6476,18 +6376,11 @@ } }, "@hapi/boom": { - "version": "9.1.4", - "resolved": "https://registry.npmjs.org/@hapi/boom/-/boom-9.1.4.tgz", - "integrity": "sha512-Ls1oH8jaN1vNsqcaHVYJrKmgMcKsC1wcp8bujvXrHaAqD2iDYq3HoOwsxwo09Cuda5R5nC0o0IxlrlTuvPuzSw==", + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@hapi/boom/-/boom-10.0.0.tgz", + "integrity": "sha512-1YVs9tLHhypBqqinKQRqh7FUERIolarQApO37OWkzD+z6y6USi871Sv746zBPKcIOBuI6g6y4FrwX87mmJ90Gg==", "requires": { - "@hapi/hoek": "9.x.x" - }, - "dependencies": { - "@hapi/hoek": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", - "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==" - } + "@hapi/hoek": "10.x.x" } }, "@hapi/bossy": { @@ -6503,15 +6396,6 @@ "@hapi/validate": "^2.0.0" }, "dependencies": { - "@hapi/boom": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/@hapi/boom/-/boom-10.0.0.tgz", - "integrity": "sha512-1YVs9tLHhypBqqinKQRqh7FUERIolarQApO37OWkzD+z6y6USi871Sv746zBPKcIOBuI6g6y4FrwX87mmJ90Gg==", - "dev": true, - "requires": { - "@hapi/hoek": "10.x.x" - } - }, "@hapi/topo": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-6.0.0.tgz", @@ -6540,16 +6424,6 @@ "requires": { "@hapi/boom": "^10.0.0", "@hapi/hoek": "^10.0.0" - }, - "dependencies": { - "@hapi/boom": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/@hapi/boom/-/boom-10.0.0.tgz", - "integrity": "sha512-1YVs9tLHhypBqqinKQRqh7FUERIolarQApO37OWkzD+z6y6USi871Sv746zBPKcIOBuI6g6y4FrwX87mmJ90Gg==", - "requires": { - "@hapi/hoek": "10.x.x" - } - } } }, "@hapi/bourne": { @@ -6564,16 +6438,6 @@ "requires": { "@hapi/boom": "^10.0.0", "@hapi/hoek": "^10.0.0" - }, - "dependencies": { - "@hapi/boom": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/@hapi/boom/-/boom-10.0.0.tgz", - "integrity": "sha512-1YVs9tLHhypBqqinKQRqh7FUERIolarQApO37OWkzD+z6y6USi871Sv746zBPKcIOBuI6g6y4FrwX87mmJ90Gg==", - "requires": { - "@hapi/hoek": "10.x.x" - } - } } }, "@hapi/catbox": { @@ -6587,14 +6451,6 @@ "@hapi/validate": "^2.0.0" }, "dependencies": { - "@hapi/boom": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/@hapi/boom/-/boom-10.0.0.tgz", - "integrity": "sha512-1YVs9tLHhypBqqinKQRqh7FUERIolarQApO37OWkzD+z6y6USi871Sv746zBPKcIOBuI6g6y4FrwX87mmJ90Gg==", - "requires": { - "@hapi/hoek": "10.x.x" - } - }, "@hapi/podium": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/@hapi/podium/-/podium-5.0.0.tgz", @@ -6636,16 +6492,6 @@ "requires": { "@hapi/boom": "^10.0.0", "@hapi/hoek": "^10.0.0" - }, - "dependencies": { - "@hapi/boom": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/@hapi/boom/-/boom-10.0.0.tgz", - "integrity": "sha512-1YVs9tLHhypBqqinKQRqh7FUERIolarQApO37OWkzD+z6y6USi871Sv746zBPKcIOBuI6g6y4FrwX87mmJ90Gg==", - "requires": { - "@hapi/hoek": "10.x.x" - } - } } }, "@hapi/code": { @@ -6663,16 +6509,6 @@ "integrity": "sha512-CEhs7j+H0iQffKfe5Htdak5LBOz/Qc8TRh51cF+BFv0qnuph3Em4pjGVzJMkI2gfTDdlJKWJISGWS1rK34POGA==", "requires": { "@hapi/boom": "^10.0.0" - }, - "dependencies": { - "@hapi/boom": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/@hapi/boom/-/boom-10.0.0.tgz", - "integrity": "sha512-1YVs9tLHhypBqqinKQRqh7FUERIolarQApO37OWkzD+z6y6USi871Sv746zBPKcIOBuI6g6y4FrwX87mmJ90Gg==", - "requires": { - "@hapi/hoek": "10.x.x" - } - } } }, "@hapi/cryptiles": { @@ -6681,6 +6517,21 @@ "integrity": "sha512-fo9+d1Ba5/FIoMySfMqPBR/7Pa29J2RsiPrl7bkwo5W5o+AN1dAYQRi4SPrPwwVxVGKjgLOEWrsvt1BonJSfLA==", "requires": { "@hapi/boom": "9.x.x" + }, + "dependencies": { + "@hapi/boom": { + "version": "9.1.4", + "resolved": "https://registry.npmjs.org/@hapi/boom/-/boom-9.1.4.tgz", + "integrity": "sha512-Ls1oH8jaN1vNsqcaHVYJrKmgMcKsC1wcp8bujvXrHaAqD2iDYq3HoOwsxwo09Cuda5R5nC0o0IxlrlTuvPuzSw==", + "requires": { + "@hapi/hoek": "9.x.x" + } + }, + "@hapi/hoek": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", + "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==" + } } }, "@hapi/eslint-plugin": { @@ -6720,14 +6571,6 @@ "@hapi/validate": "^2.0.0" }, "dependencies": { - "@hapi/boom": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/@hapi/boom/-/boom-10.0.0.tgz", - "integrity": "sha512-1YVs9tLHhypBqqinKQRqh7FUERIolarQApO37OWkzD+z6y6USi871Sv746zBPKcIOBuI6g6y4FrwX87mmJ90Gg==", - "requires": { - "@hapi/hoek": "10.x.x" - } - }, "@hapi/podium": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/@hapi/podium/-/podium-5.0.0.tgz", @@ -6772,14 +6615,6 @@ "@hapi/validate": "^2.0.0" }, "dependencies": { - "@hapi/boom": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/@hapi/boom/-/boom-10.0.0.tgz", - "integrity": "sha512-1YVs9tLHhypBqqinKQRqh7FUERIolarQApO37OWkzD+z6y6USi871Sv746zBPKcIOBuI6g6y4FrwX87mmJ90Gg==", - "requires": { - "@hapi/hoek": "10.x.x" - } - }, "@hapi/topo": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-6.0.0.tgz", @@ -6817,14 +6652,6 @@ "lru-cache": "^7.10.2" }, "dependencies": { - "@hapi/boom": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/@hapi/boom/-/boom-10.0.0.tgz", - "integrity": "sha512-1YVs9tLHhypBqqinKQRqh7FUERIolarQApO37OWkzD+z6y6USi871Sv746zBPKcIOBuI6g6y4FrwX87mmJ90Gg==", - "requires": { - "@hapi/hoek": "10.x.x" - } - }, "@hapi/topo": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-6.0.0.tgz", @@ -6861,6 +6688,14 @@ "@hapi/hoek": "9.x.x" }, "dependencies": { + "@hapi/boom": { + "version": "9.1.4", + "resolved": "https://registry.npmjs.org/@hapi/boom/-/boom-9.1.4.tgz", + "integrity": "sha512-Ls1oH8jaN1vNsqcaHVYJrKmgMcKsC1wcp8bujvXrHaAqD2iDYq3HoOwsxwo09Cuda5R5nC0o0IxlrlTuvPuzSw==", + "requires": { + "@hapi/hoek": "9.x.x" + } + }, "@hapi/bourne": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/@hapi/bourne/-/bourne-2.1.0.tgz", @@ -6934,14 +6769,6 @@ "requires": { "@hapi/hoek": "^10.0.0" } - }, - "@hapi/boom": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/@hapi/boom/-/boom-10.0.0.tgz", - "integrity": "sha512-1YVs9tLHhypBqqinKQRqh7FUERIolarQApO37OWkzD+z6y6USi871Sv746zBPKcIOBuI6g6y4FrwX87mmJ90Gg==", - "requires": { - "@hapi/hoek": "10.x.x" - } } } }, @@ -7028,14 +6855,6 @@ "@hapi/hoek": "^10.0.0" } }, - "@hapi/boom": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/@hapi/boom/-/boom-10.0.0.tgz", - "integrity": "sha512-1YVs9tLHhypBqqinKQRqh7FUERIolarQApO37OWkzD+z6y6USi871Sv746zBPKcIOBuI6g6y4FrwX87mmJ90Gg==", - "requires": { - "@hapi/hoek": "10.x.x" - } - }, "@hapi/cryptiles": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/@hapi/cryptiles/-/cryptiles-6.0.0.tgz", @@ -7087,16 +6906,6 @@ "@hapi/hoek": "^10.0.0", "@hapi/pez": "^6.0.0", "@hapi/wreck": "^18.0.0" - }, - "dependencies": { - "@hapi/boom": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/@hapi/boom/-/boom-10.0.0.tgz", - "integrity": "sha512-1YVs9tLHhypBqqinKQRqh7FUERIolarQApO37OWkzD+z6y6USi871Sv746zBPKcIOBuI6g6y4FrwX87mmJ90Gg==", - "requires": { - "@hapi/hoek": "10.x.x" - } - } } }, "@hapi/teamwork": { @@ -7154,14 +6963,6 @@ "@hapi/validate": "^2.0.0" }, "dependencies": { - "@hapi/boom": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/@hapi/boom/-/boom-10.0.0.tgz", - "integrity": "sha512-1YVs9tLHhypBqqinKQRqh7FUERIolarQApO37OWkzD+z6y6USi871Sv746zBPKcIOBuI6g6y4FrwX87mmJ90Gg==", - "requires": { - "@hapi/hoek": "10.x.x" - } - }, "@hapi/topo": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-6.0.0.tgz", @@ -7189,16 +6990,6 @@ "@hapi/boom": "^10.0.0", "@hapi/bourne": "^3.0.0", "@hapi/hoek": "^10.0.0" - }, - "dependencies": { - "@hapi/boom": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/@hapi/boom/-/boom-10.0.0.tgz", - "integrity": "sha512-1YVs9tLHhypBqqinKQRqh7FUERIolarQApO37OWkzD+z6y6USi871Sv746zBPKcIOBuI6g6y4FrwX87mmJ90Gg==", - "requires": { - "@hapi/hoek": "10.x.x" - } - } } }, "@humanwhocodes/config-array": { @@ -7410,6 +7201,21 @@ "@types/hapi__shot": "*", "@types/node": "*", "joi": "^17.3.0" + }, + "dependencies": { + "@hapi/boom": { + "version": "9.1.4", + "resolved": "https://registry.npmjs.org/@hapi/boom/-/boom-9.1.4.tgz", + "integrity": "sha512-Ls1oH8jaN1vNsqcaHVYJrKmgMcKsC1wcp8bujvXrHaAqD2iDYq3HoOwsxwo09Cuda5R5nC0o0IxlrlTuvPuzSw==", + "requires": { + "@hapi/hoek": "9.x.x" + } + }, + "@hapi/hoek": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", + "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==" + } } }, "@types/hapi__mimos": { @@ -9245,9 +9051,9 @@ "dev": true }, "joi": { - "version": "17.6.2", - "resolved": "https://registry.npmjs.org/joi/-/joi-17.6.2.tgz", - "integrity": "sha512-+gqqdh1xc1wb+Lor0J9toqgeReyDOCqOdG8QSdRcEvwrcRiFQZneUCGKjFjuyBWUb3uaFOgY56yMaZ5FIc+H4w==", + "version": "17.7.0", + "resolved": "https://registry.npmjs.org/joi/-/joi-17.7.0.tgz", + "integrity": "sha512-1/ugc8djfn93rTE3WRKdCzGGt/EtiYKxITMO4Wiv6q5JL1gl9ePt4kBsl1S499nbosspfctIQTpYIhSmHA3WAg==", "requires": { "@hapi/hoek": "^9.0.0", "@hapi/topo": "^5.0.0", diff --git a/package.json b/package.json index b18e31a620..057fdc9e13 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "license": "OGL-UK-3.0", "dependencies": { "@airbrake/node": "^2.1.8", + "@hapi/boom": "^10.0.0", "@hapi/hapi": "^21.1.0", "@hapi/inert": "^7.0.0", "@hapi/vision": "^7.0.0", @@ -32,6 +33,7 @@ "got": "^12.5.3", "govuk-frontend": "^4.4.0", "hapi-pino": "^11.0.1", + "joi": "^17.7.0", "knex": "^2.3.0", "nunjucks": "^3.2.3", "objection": "^3.0.1", diff --git a/test/controllers/bill-runs.controller.test.js b/test/controllers/bill-runs.controller.test.js new file mode 100644 index 0000000000..ce231fb1c0 --- /dev/null +++ b/test/controllers/bill-runs.controller.test.js @@ -0,0 +1,62 @@ +'use strict' + +// Test framework dependencies +const Lab = require('@hapi/lab') +const Code = require('@hapi/code') + +const { describe, it, beforeEach } = exports.lab = Lab.script() +const { expect } = Code + +// For running our service +const { init } = require('../../app/server.js') + +describe('Bill Runs controller:', () => { + let server + + // Create server before each test + beforeEach(async () => { + server = await init() + }) + + describe('POST /bill-runs', () => { + describe('when a valid request is sent', () => { + it('returns a dummy response', async () => { + const options = { + method: 'POST', + url: '/bill-runs', + payload: { + type: 'supplementary', + scheme: 'sroc', + region: '07ae7f3a-2677-4102-b352-cc006828948c' + } + } + + const response = await server.inject(options) + const payload = JSON.parse(response.payload) + + expect(response.statusCode).to.equal(200) + expect(payload.type).to.equal('supplementary') + }) + }) + + describe('when an invalid request is sent', () => { + it('returns an error response', async () => { + const options = { + method: 'POST', + url: '/bill-runs', + payload: { + type: 'supplementary', + scheme: 'INVALID', + region: '07ae7f3a-2677-4102-b352-cc006828948c' + } + } + + const response = await server.inject(options) + const payload = JSON.parse(response.payload) + + expect(response.statusCode).to.equal(400) + expect(payload.message).to.startWith('"scheme" must be') + }) + }) + }) +}) diff --git a/test/validators/bill-runs/create-bill-run.validator.js b/test/validators/bill-runs/create-bill-run.validator.js new file mode 100644 index 0000000000..3ee420c23f --- /dev/null +++ b/test/validators/bill-runs/create-bill-run.validator.js @@ -0,0 +1,132 @@ +'use strict' + +// Test framework dependencies +const Lab = require('@hapi/lab') +const Code = require('@hapi/code') + +const { describe, it } = exports.lab = Lab.script() +const { expect } = Code + +// Thing under test +const CreateBillRunValidator = require('../../../app/validators/bill-runs/create-bill-run.validator.js') + +describe('Create Bill Run validator', () => { + describe('when valid data is provided', () => { + it('returns validated data', async () => { + const validData = { + type: 'supplementary', + scheme: 'sroc', + region: '07ae7f3a-2677-4102-b352-cc006828948c', + previousBillRunId: '28a5fc2e-bdc9-4b48-96e7-5ee7b2f5d603' + } + + const result = await CreateBillRunValidator.go(validData) + + expect(result.value).to.equal({ + type: 'supplementary', + scheme: 'sroc', + region: '07ae7f3a-2677-4102-b352-cc006828948c', + previousBillRunId: '28a5fc2e-bdc9-4b48-96e7-5ee7b2f5d603' + }) + }) + + describe('which does not include `previousBillRunId`', () => { + it('returns validated data', async () => { + const validData = { + type: 'supplementary', + scheme: 'sroc', + region: '07ae7f3a-2677-4102-b352-cc006828948c' + } + + const result = await CreateBillRunValidator.go(validData) + + expect(result.value).to.equal({ + type: 'supplementary', + scheme: 'sroc', + region: '07ae7f3a-2677-4102-b352-cc006828948c' + }) + }) + }) + }) + + describe('when invalid data is provided', () => { + describe('because `type` is missing', () => { + it('returns an error', async () => { + const invalidData = { + scheme: 'sroc', + region: '07ae7f3a-2677-4102-b352-cc006828948c' + } + + const result = await CreateBillRunValidator.go(invalidData) + + expect(result.error).to.not.be.empty() + }) + }) + + describe('because `scheme` is missing', () => { + it('returns an error', async () => { + const invalidData = { + type: 'supplementary', + region: '07ae7f3a-2677-4102-b352-cc006828948c' + } + + const result = await CreateBillRunValidator.go(invalidData) + + expect(result.error).to.not.be.empty() + }) + }) + + describe('because `region` is missing', () => { + it('returns an error', async () => { + const invalidData = { + type: 'supplementary', + scheme: 'sroc' + } + + const result = await CreateBillRunValidator.go(invalidData) + + expect(result.error).to.not.be.empty() + }) + }) + + describe('because `type` has an invalid value', () => { + it('returns an error', async () => { + const invalidData = { + type: 'INVALID', + scheme: 'sroc' + } + + const result = await CreateBillRunValidator.go(invalidData) + + expect(result.error).to.not.be.empty() + }) + }) + + describe('because `scheme` has an invalid value', () => { + it('returns an error', async () => { + const invalidData = { + type: 'supplementary', + scheme: 'INVALID' + } + + const result = await CreateBillRunValidator.go(invalidData) + + expect(result.error).to.not.be.empty() + }) + }) + + describe('because `region` has an invalid value', () => { + it('returns an error', async () => { + const invalidData = { + type: 'supplementary', + scheme: 'sroc', + region: 'INVALID' + } + + const result = await CreateBillRunValidator.go(invalidData) + + expect(result.error).to.not.be.empty() + }) + }) + }) +})