diff --git a/app/controllers/return-logs-setup.controller.js b/app/controllers/return-logs-setup.controller.js index 5c29f9f3b2..8ec52a4e96 100644 --- a/app/controllers/return-logs-setup.controller.js +++ b/app/controllers/return-logs-setup.controller.js @@ -6,6 +6,15 @@ */ const InitiateSessionService = require('../services/return-logs/setup/initiate-session.service.js') +const ReceivedService = require('../services/return-logs/setup/received.service.js') +const SubmitReceivedService = require('../services/return-logs/setup/submit-received.service.js') + +async function received(request, h) { + const { sessionId } = request.params + const pageData = await ReceivedService.go(sessionId) + + return h.view('return-logs/setup/received.njk', { ...pageData }) +} async function setup(request, h) { const { returnLogId } = request.query @@ -14,6 +23,23 @@ async function setup(request, h) { return h.redirect(`/system/return-logs/setup/${session.id}/how-to-edit`) } +async function submitReceived(request, h) { + const { + params: { sessionId }, + payload + } = request + + const pageData = await SubmitReceivedService.go(sessionId, payload) + + if (pageData.error) { + return h.view('return-logs/setup/received.njk', pageData) + } + + return h.redirect(`/system/return-logs/setup/${sessionId}/reported`) +} + module.exports = { - setup + received, + setup, + submitReceived } diff --git a/app/presenters/return-logs/setup/received.presenter.js b/app/presenters/return-logs/setup/received.presenter.js new file mode 100644 index 0000000000..40ddaba5f1 --- /dev/null +++ b/app/presenters/return-logs/setup/received.presenter.js @@ -0,0 +1,38 @@ +'use strict' + +/** + * Format data for the `/return-log/setup/{sessionId}/received` page + * @module ReceivedPresenter + */ + +/** + * Format data for the `/return-log/setup/{sessionId}/received` page + * + * @param {module:SessionModel} session - The return log setup session instance + * + * @returns {object} page data needed by the view template + */ +function go(session) { + const { + id: sessionId, + data: { returnReference }, + receivedDateOptions, + receivedDateDay, + receivedDateMonth, + receivedDateYear + } = session + + return { + receivedDateOption: receivedDateOptions ?? null, + receivedDateDay: receivedDateDay ?? null, + receivedDateMonth: receivedDateMonth ?? null, + receivedDateYear: receivedDateYear ?? null, + sessionId, + returnReference, + backLink: `/system/return-logs/setup/${session.id}/start` + } +} + +module.exports = { + go +} diff --git a/app/routes/return-logs-setup.routes.js b/app/routes/return-logs-setup.routes.js index 210669a87a..47895e5548 100644 --- a/app/routes/return-logs-setup.routes.js +++ b/app/routes/return-logs-setup.routes.js @@ -14,6 +14,30 @@ const routes = [ } } } + }, + { + method: 'GET', + path: '/return-logs/setup/{sessionId}/received', + options: { + handler: ReturnLogsSetupController.received, + auth: { + access: { + scope: ['billing'] + } + } + } + }, + { + method: 'POST', + path: '/return-logs/setup/{sessionId}/received', + options: { + handler: ReturnLogsSetupController.submitReceived, + auth: { + access: { + scope: ['billing'] + } + } + } } ] diff --git a/app/services/return-logs/setup/received.service.js b/app/services/return-logs/setup/received.service.js new file mode 100644 index 0000000000..040be7fe4b --- /dev/null +++ b/app/services/return-logs/setup/received.service.js @@ -0,0 +1,35 @@ +'use strict' + +/** + * Orchestrates fetching and presenting the data for `/return-logs/setup/{sessionId}/received` page + * @module ReceivedService + */ + +const ReceivedPresenter = require('../../../presenters/return-logs/setup/received.presenter.js') +const SessionModel = require('../../../models/session.model.js') + +/** + * Orchestrates fetching and presenting the data for `/return-logs/setup/{sessionId}/received` page + * + * Supports generating the data needed for the received page in the return log setup journey. It fetches the + * current session record and formats the data needed for the page. + * + * @param {string} sessionId - The UUID of the current session + * + * @returns {Promise} The view data for the received page + */ +async function go(sessionId) { + const session = await SessionModel.query().findById(sessionId) + + const formattedData = ReceivedPresenter.go(session) + + return { + pageTitle: 'When was the return received?', + activeNavBar: 'search', + ...formattedData + } +} + +module.exports = { + go +} diff --git a/app/services/return-logs/setup/submit-received.service.js b/app/services/return-logs/setup/submit-received.service.js new file mode 100644 index 0000000000..58c0b69b1e --- /dev/null +++ b/app/services/return-logs/setup/submit-received.service.js @@ -0,0 +1,106 @@ +'use strict' + +/** + * Orchestrates validating the data for `/return-logs/setup/{sessionId}/received` page + * @module SubmitReceivedService + */ + +const ReceivedDateValidator = require('../../../validators/return-logs/setup/received-date.validator.js') +const ReceivedPresenter = require('../../../presenters/return-logs/setup/received.presenter.js') +const SessionModel = require('../../../models/session.model.js') + +/** + * Orchestrates validating the data for `/return-logs/setup/{sessionId}/received` page + * + * It first retrieves the session instance for the return log setup session in progress. The session has details about + * the return log that are needed to validate that the chosen date is valid. + * + * The validation result is then combined with the output of the presenter to generate the page data needed by the view. + * If there was a validation error the controller will re-render the page so needs this information. If all is well the + * controller will redirect to the next page in the journey. + * + * @param {string} sessionId - The UUID of the current session + * @param {object} payload - The submitted form data + * + * @returns {Promise} If no errors the page data for the received page else the validation error details + */ +async function go(sessionId, payload) { + const session = await SessionModel.query().findById(sessionId) + + const { startDate } = session + const validationResult = _validate(payload, startDate) + + if (!validationResult) { + await _save(session, payload) + + return {} + } + + const formattedData = _submittedSessionData(session, payload) + + return { + pageTitle: 'When was the return received?', + activeNavBar: 'search', + error: validationResult, + ...formattedData + } +} + +async function _save(session, payload) { + const selectedOption = payload['received-date-options'] + session.receivedDateOptions = selectedOption + const todaysDate = new Date(new Date().setHours(0, 0, 0, 0)) + + if (selectedOption === 'today') { + session.receivedDate = todaysDate + } else if (selectedOption === 'yesterday') { + // The setDate method updates the date object in place and returns a timestamp, + // not the updated date object itself. To ensure we store the correct date, + // we first modify the 'yesterday' variable and then assign it to session.receivedDate. + todaysDate.setDate(todaysDate.getDate() - 1) + session.receivedDate = todaysDate + } else { + session.receivedDateDay = payload['received-date-day'] + session.receivedDateMonth = payload['received-date-month'] + session.receivedDateYear = payload['received-date-year'] + session.receivedDate = new Date( + `${payload['received-date-year']}-${payload['received-date-month']}-${payload['received-date-day']}` + ) + } + + return session.$update() +} + +function _submittedSessionData(session, payload) { + session.receivedDateDay = payload['received-date-day'] ?? null + session.receivedDateMonth = payload['received-date-month'] ?? null + session.receivedDateYear = payload['received-date-year'] ?? null + session.receivedDateOptions = payload['received-date-options'] ?? null + + const data = ReceivedPresenter.go(session) + return data +} + +function _validate(payload, startDate) { + const validation = ReceivedDateValidator.go(payload, startDate) + + if (!validation.error) { + return null + } + + const { message, type } = validation.error.details[0] + + // There are only two possible error scenarios: either a radio button has not been selected, in which case the date + // isn't visible so there cannot be an "invalid date" error; or an invalid date has been entered, in which case the + // date *is* visible so there cannot be a "radio button not selected" error. We identify the former by checking if the + // error type is `any.required`; and so if an error is present which isn't of this type, it must be a date error. + return { + message, + radioFormElement: type === 'any.required' ? { text: message } : null, + dateInputFormElement: type === 'any.required' ? null : { text: message } + } +} + +module.exports = { + go +} diff --git a/app/validators/return-logs/setup/received-date.validator.js b/app/validators/return-logs/setup/received-date.validator.js new file mode 100644 index 0000000000..4ebacb0b73 --- /dev/null +++ b/app/validators/return-logs/setup/received-date.validator.js @@ -0,0 +1,80 @@ +'use strict' + +/** + * Validates data submitted for the `/return-logs/setup/{sessionId}/received` page + * @module ReceivedDateValidator + */ + +const Joi = require('joi').extend(require('@joi/date')) + +const { leftPadZeroes } = require('../../../presenters/base.presenter.js') + +/** + * Validates data submitted for the `/return-logs/setup/{sessionId}/received` page + * + * When entering a return log users must specify a received date for when the return was received by. The page + * allows them to select todays date, yesterdays date or enter a custom date. + * + * The custom date uses a {@link https://design-system.service.gov.uk/components/date-input/ | GOV.UK date input} + * which is 3 text fields for day, month and year. Users can enter what they like or omit a value completely which is + * why date validation can become quite complex. + * + * Also, the date they enter cannot be before the start date of the return or a date in the future. + * + * Finally, we also need to validate that the user selected one of the options; todays, yesterday or a custom + * date. + * + * @param {object} payload - The payload from the request to be validated + * @param {string} startDate - The date the return log starts from + * + * @returns {object} the result from calling Joi's schema.validate(). It will be an object with a `value:` property. If + * any errors are found the `error:` property will also exist detailing what the issues were + */ +function go(payload, startDate) { + const { 'received-date-options': selectedOption } = payload + + if (selectedOption === 'custom-date') { + payload.fullDate = _fullDate(payload) + + return _validateCustomReceivedDate(payload, startDate) + } + return _validateReceivedDate(payload) +} + +function _fullDate(payload) { + const { 'received-date-day': day, 'received-date-month': month, 'received-date-year': year } = payload + + const paddedMonth = month ? leftPadZeroes(month, 2) : '' + const paddedDay = day ? leftPadZeroes(day, 2) : '' + + return `${year}-${paddedMonth}-${paddedDay}` +} + +function _validateCustomReceivedDate(payload, startDate) { + const schema = Joi.object({ + fullDate: Joi.date().format(['YYYY-MM-DD']).required().min(startDate).less('now').messages({ + 'date.base': 'Enter a return received date', + 'date.format': 'Enter a real received date', + 'date.min': 'Received date must be the return period start date or after it', + 'date.less': "Received date must be either today's date or in the past" + }), + otherwise: Joi.forbidden() + }) + + return schema.validate(payload, { abortEarly: false, allowUnknown: true }) +} + +function _validateReceivedDate(payload) { + const schema = Joi.object({ + 'received-date-options': Joi.string().required().messages({ + 'any.required': 'Select the return received date', + 'string.empty': 'Select the return received date' + }) + }) + + return schema.validate(payload, { abortEarly: false, allowUnknown: true }) +} + +module.exports = { + go +} diff --git a/app/views/return-logs/setup/received.njk b/app/views/return-logs/setup/received.njk new file mode 100644 index 0000000000..ac1d43b968 --- /dev/null +++ b/app/views/return-logs/setup/received.njk @@ -0,0 +1,111 @@ +{% extends 'layout.njk' %} +{% from "govuk/components/back-link/macro.njk" import govukBackLink %} +{% from "govuk/components/button/macro.njk" import govukButton %} +{% from "govuk/components/error-message/macro.njk" import govukErrorMessage %} +{% from "govuk/components/error-summary/macro.njk" import govukErrorSummary %} +{% from "govuk/components/radios/macro.njk" import govukRadios %} +{% from 'govuk/components/date-input/macro.njk' import govukDateInput %} + +{% block breadcrumbs %} + {# Back link #} + {{ + govukBackLink({ + text: 'Back', + href: backLink + }) + }} +{% endblock %} + +{% block content %} + {# Error summary #} + {% if error %} + {{ govukErrorSummary({ + titleText: "There is a problem", + errorList: [ + { + text: error.message, + href: "#receivedDate-error" + } + ] + }) }} + {%endif%} + + {# Main heading #} +
+ Return reference {{ returnReference }} +

{{ pageTitle }}

+
+
+
+ + + {% set dateInputHTML %} + {% if error %} + {% set errorClass = 'govuk-input--error' %} + {% else %} + {% set errorClass = '' %} + {% endif %} + + {{ govukDateInput({ + id: 'date', + namePrefix: 'received-date', + errorMessage: error.dateInputFormElement, + fieldset: { + legend: { + text: 'When was the return received?' + } + }, + items: [ + { + classes: 'govuk-input--width-2 ' + errorClass, + name: 'day', + value: receivedDateDay + }, + { + classes: 'govuk-input--width-2 ' + errorClass, + name: 'month', + value: receivedDateMonth + }, + { + classes: 'govuk-input--width-4 ' + errorClass, + name: 'year', + value: receivedDateYear + } + ] + }) }} + {% endset %} + + {{ govukRadios({ + name: "received-date-options", + errorMessage: error.radioFormElement, + fieldset: { + legend: { + classes: "govuk-fieldset__legend--l" + } + }, + items: [ + { + value: "today", + text: "Today", + checked: receivedDateOption == 'today' + }, + { + value: "yesterday", + text: "Yesterday", + checked: receivedDateOption == 'yesterday' + }, + { + value: "custom-date", + text: "Custom date", + checked: receivedDateOption == 'custom-date', + conditional: { + html: dateInputHTML + } + } + ] + }) }} + + {{ govukButton({ text: "Continue", preventDoubleClick: true }) }} +
+
+{% endblock %} diff --git a/test/controllers/return-logs-setup.controller.test.js b/test/controllers/return-logs-setup.controller.test.js index 8cdea3fd62..17142b2ac9 100644 --- a/test/controllers/return-logs-setup.controller.test.js +++ b/test/controllers/return-logs-setup.controller.test.js @@ -8,8 +8,13 @@ const Sinon = require('sinon') const { describe, it, before, beforeEach, afterEach } = (exports.lab = Lab.script()) const { expect } = Code +// Test helpers +const { postRequestOptions } = require('../support/general.js') + // Things we need to stub const InitiateSessionService = require('../../app/services/return-logs/setup/initiate-session.service.js') +const ReceivedService = require('../../app/services/return-logs/setup/received.service.js') +const SubmitReceivedService = require('../../app/services/return-logs/setup/submit-received.service.js') // For running our service const { init } = require('../../app/server.js') @@ -23,7 +28,7 @@ describe('Return Logs Setup controller', () => { server = await init() }) - beforeEach(async () => { + beforeEach(() => { // We silence any calls to server.logger.error made in the plugin to try and keep the test output as clean as // possible Sinon.stub(server.logger, 'error') @@ -40,7 +45,7 @@ describe('Return Logs Setup controller', () => { describe('GET', () => { const session = { id: 'e0c77b74-7326-493d-be5e-0d1ad41594b5', data: {} } - beforeEach(async () => { + beforeEach(() => { options = { method: 'GET', url: '/return-logs/setup?returnLogId=v1:1:123:10021668:2022-04-01:2023-03-31', @@ -52,7 +57,7 @@ describe('Return Logs Setup controller', () => { }) describe('when a request is valid', () => { - beforeEach(async () => { + beforeEach(() => { Sinon.stub(InitiateSessionService, 'go').resolves(session) }) @@ -65,4 +70,83 @@ describe('Return Logs Setup controller', () => { }) }) }) + + describe('return-logs/setup/{sessionId}/received', () => { + describe('GET', () => { + beforeEach(() => { + options = { + method: 'GET', + url: '/return-logs/setup/e0c77b74-7326-493d-be5e-0d1ad41594b5/received', + auth: { + strategy: 'session', + credentials: { scope: ['billing'] } + } + } + }) + + describe('when a request is valid', () => { + beforeEach(() => { + Sinon.stub(ReceivedService, 'go').resolves({ + sessionId: 'e0c77b74-7326-493d-be5e-0d1ad41594b5', + licenceId: '3154ea03-e232-4c66-a711-a72956b7de61', + pageTitle: 'When was the return received?' + }) + }) + + it('returns the page successfully', async () => { + const response = await server.inject(options) + + expect(response.statusCode).to.equal(200) + expect(response.payload).to.contain('When was the return received?') + }) + }) + }) + + describe('POST', () => { + describe('when a request is valid', () => { + beforeEach(() => { + options = _postOptions('received', {}) + }) + + describe('and the received date is entered', () => { + beforeEach(() => { + Sinon.stub(SubmitReceivedService, 'go').resolves({}) + }) + + it('redirects to the "reported" page', async () => { + const response = await server.inject(options) + + expect(response.statusCode).to.equal(302) + expect(response.headers.location).to.equal( + '/system/return-logs/setup/e0c77b74-7326-493d-be5e-0d1ad41594b5/reported' + ) + }) + }) + }) + + describe('when a request is invalid', () => { + beforeEach(() => { + options = _postOptions('received') + + Sinon.stub(SubmitReceivedService, 'go').resolves({ + error: { message: 'Enter a real received date' }, + pageTitle: 'When was the return received?', + sessionId: 'e0c77b74-7326-493d-be5e-0d1ad41594b5' + }) + }) + + it('re-renders the page with an error message', async () => { + const response = await server.inject(options) + + expect(response.statusCode).to.equal(200) + expect(response.payload).to.contain('Enter a real received date') + expect(response.payload).to.contain('There is a problem') + }) + }) + }) + }) }) + +function _postOptions(path, payload) { + return postRequestOptions(`/return-logs/setup/e0c77b74-7326-493d-be5e-0d1ad41594b5/${path}`, payload) +} diff --git a/test/presenters/return-logs/setup/received.presenter.test.js b/test/presenters/return-logs/setup/received.presenter.test.js new file mode 100644 index 0000000000..4b8211b48d --- /dev/null +++ b/test/presenters/return-logs/setup/received.presenter.test.js @@ -0,0 +1,96 @@ +'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 + +// Thing under test +const ReceivedPresenter = require('../../../../app/presenters/return-logs/setup/received.presenter.js') + +describe('Return Logs Setup - Received presenter', () => { + let session + + beforeEach(() => { + session = { + id: '61e07498-f309-4829-96a9-72084a54996d', + data: { + returnReference: '012345' + } + } + }) + + describe('when provided with a session', () => { + it('correctly presents the data', () => { + const result = ReceivedPresenter.go(session) + + expect(result).to.equal({ + sessionId: '61e07498-f309-4829-96a9-72084a54996d', + returnReference: '012345', + receivedDateOption: null, + receivedDateDay: null, + receivedDateMonth: null, + receivedDateYear: null, + backLink: '/system/return-logs/setup/61e07498-f309-4829-96a9-72084a54996d/start' + }) + }) + }) + + describe('the "receivedDate" properties', () => { + describe('when the user has previously selected todays date as the received date', () => { + beforeEach(() => { + session.receivedDateOptions = 'today' + }) + + it('returns the "receivedDateOption" property populated to re-select the option', () => { + const result = ReceivedPresenter.go(session) + + const { receivedDateOption, receivedDateDay, receivedDateMonth, receivedDateYear } = result + + expect(receivedDateDay).to.be.null() + expect(receivedDateMonth).to.be.null() + expect(receivedDateYear).to.be.null() + expect(receivedDateOption).to.equal('today') + }) + }) + + describe('when the user has previously selected yesterdays date as the received date', () => { + beforeEach(() => { + session.receivedDateOptions = 'yesterday' + }) + + it('returns the "receivedDateOption" property populated to re-select the option', () => { + const result = ReceivedPresenter.go(session) + + const { receivedDateOption, receivedDateDay, receivedDateMonth, receivedDateYear } = result + + expect(receivedDateDay).to.be.null() + expect(receivedDateMonth).to.be.null() + expect(receivedDateYear).to.be.null() + expect(receivedDateOption).to.equal('yesterday') + }) + }) + + describe('when the user has previously selected custom date as the received date', () => { + beforeEach(() => { + session.receivedDateDay = '26' + session.receivedDateMonth = '11' + session.receivedDateYear = '2023' + session.receivedDateOptions = 'custom-date' + }) + + it('returns the properties needed to re-populate the fields', () => { + const result = ReceivedPresenter.go(session) + + const { receivedDateOption, receivedDateDay, receivedDateMonth, receivedDateYear } = result + + expect(receivedDateDay).to.equal('26') + expect(receivedDateMonth).to.equal('11') + expect(receivedDateYear).to.equal('2023') + expect(receivedDateOption).to.equal('custom-date') + }) + }) + }) +}) diff --git a/test/services/return-logs/setup/received.service.test.js b/test/services/return-logs/setup/received.service.test.js new file mode 100644 index 0000000000..b82ef289ab --- /dev/null +++ b/test/services/return-logs/setup/received.service.test.js @@ -0,0 +1,54 @@ +'use strict' + +// Test framework dependencies +const Lab = require('@hapi/lab') +const Code = require('@hapi/code') + +const { describe, it, before } = (exports.lab = Lab.script()) +const { expect } = Code + +// Test helpers +const SessionHelper = require('../../../support/helpers/session.helper.js') + +// Thing under test +const ReceivedService = require('../../../../app/services/return-logs/setup/received.service.js') + +describe('Return Logs Setup - Received service', () => { + let session + + before(async () => { + session = await SessionHelper.add({ + data: { + licenceId: '736144f1-203d-46bb-9968-5137ae06a7bd', + returnReference: '012345' + }, + id: 'd958333a-4acd-4add-9e2b-09e14c6b72f3' + }) + }) + + describe('when called', () => { + it('fetches the current setup session record', async () => { + const result = await ReceivedService.go(session.id) + + expect(result.sessionId).to.equal(session.id) + }) + + it('returns page data for the view', async () => { + const result = await ReceivedService.go(session.id) + + expect(result).to.equal( + { + activeNavBar: 'search', + pageTitle: 'When was the return received?', + returnReference: '012345', + backLink: `/system/return-logs/setup/${session.id}/start`, + receivedDateOption: null, + receivedDateDay: null, + receivedDateMonth: null, + receivedDateYear: null + }, + { skip: ['sessionId'] } + ) + }) + }) +}) diff --git a/test/services/return-logs/setup/submit-received.service.test.js b/test/services/return-logs/setup/submit-received.service.test.js new file mode 100644 index 0000000000..047428ac3e --- /dev/null +++ b/test/services/return-logs/setup/submit-received.service.test.js @@ -0,0 +1,168 @@ +'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 + +// Test helpers +const SessionHelper = require('../../../support/helpers/session.helper.js') + +// Thing under test +const SubmitReceivedService = require('../../../../app/services/return-logs/setup/submit-received.service.js') + +describe('Return Logs Setup - Submit Received service', () => { + let payload + let session + let sessionData + let testDate + + beforeEach(async () => { + sessionData = { + data: { + returnReference: '12345', + startDate: '2023-04-01T00:00:00.000Z' + } + } + + session = await SessionHelper.add(sessionData) + }) + + describe('when called', () => { + describe('with a valid payload (todays date)', () => { + beforeEach(async () => { + testDate = new Date() + testDate.setHours(0, 0, 0, 0) + payload = { + 'received-date-options': 'today' + } + }) + + it('saves the submitted option', async () => { + await SubmitReceivedService.go(session.id, payload) + + const refreshedSession = await session.$query() + + expect(refreshedSession.receivedDateOptions).to.equal('today') + expect(new Date(refreshedSession.receivedDate)).to.equal(testDate) + }) + }) + + describe('with a valid payload (yesterdays date)', () => { + beforeEach(async () => { + testDate = new Date() + testDate.setDate(testDate.getDate() - 1) + testDate.setHours(0, 0, 0, 0) + payload = { + 'received-date-options': 'yesterday' + } + }) + + it('saves the submitted option', async () => { + await SubmitReceivedService.go(session.id, payload) + + const refreshedSession = await session.$query() + + expect(refreshedSession.receivedDateOptions).to.equal('yesterday') + expect(new Date(refreshedSession.receivedDate)).to.equal(testDate) + }) + }) + + describe('with a valid payload (custom received date)', () => { + beforeEach(async () => { + payload = { + 'received-date-options': 'custom-date', + 'received-date-day': '26', + 'received-date-month': '11', + 'received-date-year': '2023' + } + }) + + it('saves the submitted values', async () => { + await SubmitReceivedService.go(session.id, payload) + + const refreshedSession = await session.$query() + + expect(refreshedSession.receivedDateOptions).to.equal('custom-date') + expect(refreshedSession.receivedDateDay).to.equal('26') + expect(refreshedSession.receivedDateMonth).to.equal('11') + expect(refreshedSession.receivedDateYear).to.equal('2023') + expect(new Date(refreshedSession.receivedDate)).to.equal(new Date('2023-11-26')) + }) + + it('returns the correct details the controller needs to redirect the journey', async () => { + const result = await SubmitReceivedService.go(session.id, payload) + + expect(result).to.equal({}) + }) + }) + + describe('with an invalid payload', () => { + beforeEach(async () => { + payload = {} + }) + + it('returns the page data for the view', async () => { + const result = await SubmitReceivedService.go(session.id, payload) + + expect(result).to.equal( + { + activeNavBar: 'search', + pageTitle: 'When was the return received?', + receivedDateDay: null, + receivedDateMonth: null, + receivedDateYear: null, + receivedDateOption: null, + backLink: `/system/return-logs/setup/${session.id}/start`, + returnReference: '12345' + }, + { skip: ['sessionId', 'error'] } + ) + }) + + describe('because the user has not selected anything', () => { + it('includes an error for the radio form element', async () => { + const result = await SubmitReceivedService.go(session.id, payload) + + expect(result.error).to.equal({ + message: 'Select the return received date', + radioFormElement: { text: 'Select the return received date' }, + dateInputFormElement: null + }) + }) + }) + + describe('because the user has selected custom received date and entered invalid data', () => { + beforeEach(async () => { + payload = { + 'received-date-options': 'custom-date', + 'received-date-day': 'a', + 'received-date-month': 'b', + 'received-date-year': 'c' + } + }) + + it('includes an error for the date input element', async () => { + const result = await SubmitReceivedService.go(session.id, payload) + + expect(result.error).to.equal({ + message: 'Enter a real received date', + radioFormElement: null, + dateInputFormElement: { text: 'Enter a real received date' } + }) + }) + + it('includes what was submitted', async () => { + const result = await SubmitReceivedService.go(session.id, payload) + + expect(result.receivedDateDay).to.equal('a') + expect(result.receivedDateMonth).to.equal('b') + expect(result.receivedDateYear).to.equal('c') + expect(result.receivedDateOption).to.equal('custom-date') + }) + }) + }) + }) +}) diff --git a/test/validators/return-logs/setup/received-date.validator.test.js b/test/validators/return-logs/setup/received-date.validator.test.js new file mode 100644 index 0000000000..fce3f7eb81 --- /dev/null +++ b/test/validators/return-logs/setup/received-date.validator.test.js @@ -0,0 +1,197 @@ +'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 + +// Thing under test +const ReceivedDateValidator = require('../../../../app/validators/return-logs/setup/received-date.validator.js') + +describe('Return Logs Setup - Received Date validator', () => { + const returnStartDate = '2023-01-01T00:00:00.000Z' + + let payload + + describe('when a valid payload is provided', () => { + describe('because the user selected the todays date option', () => { + beforeEach(() => { + payload = { + 'received-date-options': 'today' + } + }) + + it('confirms the payload is valid', () => { + const result = ReceivedDateValidator.go(payload, returnStartDate) + + expect(result.error).not.to.exist() + }) + }) + + describe('because the user selected the yesterdays date option', () => { + beforeEach(() => { + payload = { + 'received-date-options': 'yesterday' + } + }) + + it('confirms the payload is valid', () => { + const result = ReceivedDateValidator.go(payload, returnStartDate) + + expect(result.error).not.to.exist() + }) + }) + + describe('because the user entered a valid custom date', () => { + beforeEach(() => { + payload = { + 'received-date-options': 'custom-date', + 'received-date-day': '26', + 'received-date-month': '11', + 'received-date-year': '2023' + } + }) + + it('confirms the payload is valid', () => { + const result = ReceivedDateValidator.go(payload, returnStartDate) + + expect(result.error).not.to.exist() + }) + }) + }) + + describe('when an invalid payload is provided', () => { + describe('because the user did not select an option', () => { + beforeEach(() => { + payload = {} + }) + + it('fails validation with the message "Select the return received date"', () => { + const result = ReceivedDateValidator.go(payload, returnStartDate) + + expect(result.error).to.exist() + expect(result.error.details[0].message).to.equal('Select the return received date') + }) + }) + + describe('because the user selected "Custom date"', () => { + beforeEach(() => { + payload = { 'received-date-options': 'custom-date' } + }) + + describe('but then entered no values', () => { + beforeEach(() => { + payload['received-date-day'] = null + payload['received-date-month'] = null + payload['received-date-year'] = null + }) + + it('fails validation with the message "Enter a real received date"', () => { + const result = ReceivedDateValidator.go(payload, returnStartDate) + + expect(result.error).to.exist() + expect(result.error.details[0].message).to.equal('Enter a real received date') + }) + }) + + describe('but only entered some values', () => { + beforeEach(() => { + payload['received-date-day'] = '6' + payload['received-date-month'] = '4' + payload['received-date-year'] = null + }) + + it('fails validation with the message "Enter a real received date"', () => { + const result = ReceivedDateValidator.go(payload, returnStartDate) + + expect(result.error).to.exist() + expect(result.error.details[0].message).to.equal('Enter a real received date') + }) + }) + + describe('but entered text', () => { + beforeEach(() => { + payload['received-date-day'] = 'TT' + payload['received-date-month'] = 'ZZ' + payload['received-date-year'] = 'LLLL' + }) + + it('fails validation with the message "Enter a real received date"', () => { + const result = ReceivedDateValidator.go(payload, returnStartDate) + + expect(result.error).to.exist() + expect(result.error.details[0].message).to.equal('Enter a real received date') + }) + }) + + describe('but entered invalid numbers, for example, 13 for the month', () => { + beforeEach(() => { + payload['received-date-day'] = '6' + payload['received-date-month'] = '13' + payload['received-date-year'] = '2023' + }) + + it('fails validation with the message "Enter a real received date"', () => { + const result = ReceivedDateValidator.go(payload, returnStartDate) + + expect(result.error).to.exist() + expect(result.error.details[0].message).to.equal('Enter a real received date') + }) + }) + + describe('but entered an invalid date, for example, 2023-02-29', () => { + beforeEach(() => { + payload['received-date-day'] = '29' + payload['received-date-month'] = '2' + payload['received-date-year'] = '2023' + }) + + it('fails validation with the message "Enter a real received date"', () => { + const result = ReceivedDateValidator.go(payload, returnStartDate) + + expect(result.error).to.exist() + expect(result.error.details[0].message).to.equal('Enter a real received date') + }) + }) + + describe('but entered a date before the return logs start date', () => { + beforeEach(() => { + payload['received-date-day'] = '31' + payload['received-date-month'] = '12' + payload['received-date-year'] = '2022' + }) + + it('fails validation with the message "Received date must be the return period start date or after it"', () => { + const result = ReceivedDateValidator.go(payload, returnStartDate) + + expect(result.error).to.exist() + expect(result.error.details[0].message).to.equal( + 'Received date must be the return period start date or after it' + ) + }) + }) + + describe('but entered a future date', () => { + beforeEach(() => { + // Get today's date and add 1 day + const futureDate = new Date() + futureDate.setDate(futureDate.getDate() + 1) // Set to tomorrow + + // Update the payload with the future date values + payload['received-date-day'] = String(futureDate.getDate()) + payload['received-date-month'] = String(futureDate.getMonth() + 1) // Month (0-based index) + payload['received-date-year'] = String(futureDate.getFullYear()) + }) + + it('fails validation with the message "Received date must be either todays date or in the past"', () => { + const result = ReceivedDateValidator.go(payload, returnStartDate) + + expect(result.error).to.exist() + expect(result.error.details[0].message).to.equal("Received date must be either today's date or in the past") + }) + }) + }) + }) +})