diff --git a/app/controllers/return-requirements.controller.js b/app/controllers/return-requirements.controller.js index 7d8c905ea8..9cf560ce60 100644 --- a/app/controllers/return-requirements.controller.js +++ b/app/controllers/return-requirements.controller.js @@ -9,6 +9,7 @@ const AddNoteService = require('../services/return-requirements/add-note.service const AbstractionPeriodService = require('../services/return-requirements/abstraction-period.service.js') const AgreementsExceptionsService = require('../services/return-requirements/agreements-exceptions.service.js') const CheckYourAnswersService = require('../services/return-requirements/check-your-answers.service.js') +const DeleteNoteService = require('../services/return-requirements/delete-note.service.js') const FrequencyCollectedService = require('../services/return-requirements/frequency-collected.service.js') const FrequencyReportedService = require('../services/return-requirements/frequency-reported.service.js') const NoReturnsRequiredService = require('../services/return-requirements/no-returns-required.service.js') @@ -77,13 +78,21 @@ async function approved (request, h) { async function checkYourAnswers (request, h) { const { sessionId } = request.params - const pageData = await CheckYourAnswersService.go(sessionId) + const pageData = await CheckYourAnswersService.go(sessionId, request.yar) return h.view('return-requirements/check-your-answers.njk', { ...pageData }) } +async function deleteNote (request, h) { + const { sessionId } = request.params + + await DeleteNoteService.go(sessionId, request.yar) + + return h.redirect(`/system/return-requirements/${sessionId}/check-your-answers`) +} + async function existing (request, h) { const { sessionId } = request.params @@ -215,7 +224,7 @@ async function submitAddNote (request, h) { const { sessionId } = request.params const { user } = request.auth.credentials - const pageData = await SubmitAddNoteService.go(sessionId, request.payload, user) + const pageData = await SubmitAddNoteService.go(sessionId, request.payload, user, request.yar) if (pageData.error) { return h.view('return-requirements/add-note.njk', pageData) @@ -415,6 +424,7 @@ module.exports = { agreementsExceptions, approved, checkYourAnswers, + deleteNote, existing, frequencyCollected, frequencyReported, diff --git a/app/routes/return-requirement.routes.js b/app/routes/return-requirement.routes.js index 193f4eeadb..a5472a2db2 100644 --- a/app/routes/return-requirement.routes.js +++ b/app/routes/return-requirement.routes.js @@ -120,6 +120,19 @@ const routes = [ description: 'Submit check your answers' } }, + { + method: 'GET', + path: '/return-requirements/{sessionId}/delete-note', + handler: ReturnRequirementsController.deleteNote, + options: { + auth: { + access: { + scope: ['billing'] + } + }, + description: 'Delete a note' + } + }, { method: 'GET', path: '/return-requirements/{sessionId}/existing', diff --git a/app/services/return-requirements/check-your-answers.service.js b/app/services/return-requirements/check-your-answers.service.js index 1d0f6b1756..b2952a6ecb 100644 --- a/app/services/return-requirements/check-your-answers.service.js +++ b/app/services/return-requirements/check-your-answers.service.js @@ -12,18 +12,21 @@ const SessionModel = require('../../models/session.model.js') * Orchestrates fetching and presenting the data for `/return-requirements/{sessionId}/check-your-answers` page * * @param {string} sessionId - The UUID for return requirement setup session record + * @param {Object} yar - The Hapi `request.yar` session manager passed on by the controller * * @returns {Promise} page data needed by the view template */ -async function go (sessionId) { +async function go (sessionId, yar) { const session = await SessionModel.query().findById(sessionId) + const notification = yar.flash('notification')[0] const formattedData = CheckYourAnswersPresenter.go(session) await _checkYourAnswersVisited(session) return { activeNavBar: 'search', + notification, licenceRef: session.data.licence.licenceRef, pageTitle: `Check the return requirements for ${session.data.licence.licenceHolder}`, ...formattedData diff --git a/app/services/return-requirements/delete-note.service.js b/app/services/return-requirements/delete-note.service.js new file mode 100644 index 0000000000..6127b1cc82 --- /dev/null +++ b/app/services/return-requirements/delete-note.service.js @@ -0,0 +1,43 @@ +'use strict' + +/** + * Orchestrates deleting the notes data for `/return-requirements/{sessionId}/check-your-answers` page + * @module DeleteNoteService + */ + +const SessionModel = require('../../models/session.model.js') + +/** + * + * It first retrieves the session instance for the returns requirements journey in progress. + * + * Then it removes the notes data from the session. + * + * @param {string} sessionId - The id of the current session + * @param {Object} yar - The Hapi `request.yar` session manager passed on by the controller + * + * @returns {Promise} A promise is returned but it does not resolve to anything we expect the caller to use + */ +async function go (sessionId, yar) { + const session = await SessionModel.query().findById(sessionId) + const notification = { + title: 'Removed', + text: 'Note removed' + } + + yar.flash('notification', notification) + + return _save(session) +} + +async function _save (session) { + const currentData = session.data + + delete currentData.note + + return session.$query().patch({ data: currentData }) +} + +module.exports = { + go +} diff --git a/app/services/return-requirements/submit-add-note.service.js b/app/services/return-requirements/submit-add-note.service.js index 4a5a80776e..1b805ef9f1 100644 --- a/app/services/return-requirements/submit-add-note.service.js +++ b/app/services/return-requirements/submit-add-note.service.js @@ -20,16 +20,22 @@ const SessionModel = require('../../models/session.model.js') * @param {string} sessionId - The id of the current session * @param {Object} payload - The submitted form data * @param {Object} user - The logged in user details + * @param {Object} yar - The Hapi `request.yar` session manager passed on by the controller * - * @returns {Promise} The page data for the no returns required page + * @returns {Promise} The page data for the check-your-answers page */ -async function go (sessionId, payload, user) { +async function go (sessionId, payload, user, yar) { const session = await SessionModel.query().findById(sessionId) const validationResult = _validate(payload) if (!validationResult) { + const notification = _notification(session, payload.note) await _save(session, payload, user) + if (notification) { + yar.flash('notification', notification) + } + return { journey: session.data.journey } @@ -45,6 +51,27 @@ async function go (sessionId, payload, user) { } } +function _notification (session, newNote) { + const { data: { note } } = session + const text = 'Changes made' + + if (!note && newNote) { + return { + text, + title: 'Added' + } + } + + if (note?.content !== newNote) { + return { + text, + title: 'Updated' + } + } + + return null +} + async function _save (session, payload, user) { const currentData = session.data diff --git a/app/views/return-requirements/check-your-answers.njk b/app/views/return-requirements/check-your-answers.njk index 6afb60446b..14e26163f8 100644 --- a/app/views/return-requirements/check-your-answers.njk +++ b/app/views/return-requirements/check-your-answers.njk @@ -1,5 +1,6 @@ {% extends 'layout.njk' %} {% from "govuk/components/button/macro.njk" import govukButton %} +{% from "govuk/components/notification-banner/macro.njk" import govukNotificationBanner %} {% from "govuk/components/summary-list/macro.njk" import govukSummaryList %} {% set baseURL = "/system/return-requirements/" + id %} @@ -45,6 +46,13 @@ {% endset %} {% block content %} + {% if notification %} + {{ govukNotificationBanner({ + titleText: notification.title, + text: notification.text + }) }} + {% endif %} + {# Main heading #}

@@ -120,7 +128,11 @@ { href: "add-note", text: "Change" if note else "Add a note" - } + }, + { + href: "delete-note", + text: "Delete" + } if note ] } } diff --git a/test/controllers/return-requirements.controller.test.js b/test/controllers/return-requirements.controller.test.js index c01b632f94..cd769815b5 100644 --- a/test/controllers/return-requirements.controller.test.js +++ b/test/controllers/return-requirements.controller.test.js @@ -13,6 +13,7 @@ const AbstractionPeriodService = require('../../app/services/return-requirements const AddNoteService = require('../../app/services/return-requirements/add-note.service.js') const AgreementsExceptionService = require('../../app/services/return-requirements/agreements-exceptions.service.js') const CheckYourAnswersService = require('../../app/services/return-requirements/check-your-answers.service.js') +const DeleteNoteService = require('../../app/services/return-requirements/delete-note.service.js') const FrequencyCollectedService = require('../../app/services/return-requirements/frequency-collected.service.js') const FrequencyReportedService = require('../../app/services/return-requirements/frequency-reported.service.js') const NoReturnsRequiredService = require('../../app/services/return-requirements/no-returns-required.service.js') @@ -26,6 +27,7 @@ const StartDateService = require('../../app/services/return-requirements/start-d // For running our service const { init } = require('../../app/server.js') +const sessionId = '64924759-8142-4a08-9d1e-1e902cd9d316' describe('Return requirements controller', () => { let server @@ -127,6 +129,22 @@ describe('Return requirements controller', () => { }) }) + describe('GET /return-requirements/{sessionId}/delete-note', () => { + beforeEach(async () => { + Sinon.stub(DeleteNoteService, 'go').resolves({ + title: 'Removed', + text: 'Note removed' + }) + }) + + it('redirects on success', async () => { + const result = await server.inject(_options('delete-note')) + + expect(result.statusCode).to.equal(302) + expect(result.headers.location).to.equal(`/system/return-requirements/${sessionId}/check-your-answers`) + }) + }) + describe('GET /return-requirements/{sessionId}/existing', () => { describe('when the request succeeds', () => { it('returns the page successfully', async () => { @@ -306,7 +324,7 @@ describe('Return requirements controller', () => { function _options (path) { return { method: 'GET', - url: `/return-requirements/64924759-8142-4a08-9d1e-1e902cd9d316/${path}`, + url: `/return-requirements/${sessionId}/${path}`, auth: { strategy: 'session', credentials: { scope: ['billing'] } diff --git a/test/presenters/return-requirements/check-your-answers.presenter.test.js b/test/presenters/return-requirements/check-your-answers.presenter.test.js index df4421dbdf..022439a343 100644 --- a/test/presenters/return-requirements/check-your-answers.presenter.test.js +++ b/test/presenters/return-requirements/check-your-answers.presenter.test.js @@ -27,6 +27,7 @@ describe('Check Your Answers presenter', () => { journey: 'no-returns-required', note: { content: 'Note attached to requirement', + status: 'Added', userEmail: 'carol.shaw@atari.com' }, reason: 'returns-exception', diff --git a/test/services/return-requirements/check-your-answers.service.test.js b/test/services/return-requirements/check-your-answers.service.test.js index 38ed96eeef..c075b807a5 100644 --- a/test/services/return-requirements/check-your-answers.service.test.js +++ b/test/services/return-requirements/check-your-answers.service.test.js @@ -3,8 +3,9 @@ // Test framework dependencies const Lab = require('@hapi/lab') const Code = require('@hapi/code') +const Sinon = require('sinon') -const { describe, it, beforeEach } = exports.lab = Lab.script() +const { describe, it, beforeEach, afterEach } = exports.lab = Lab.script() const { expect } = Code // Test helpers @@ -37,23 +38,29 @@ const sessionData = { describe('Check Your Answers service', () => { let session + let yarStub beforeEach(async () => { await DatabaseSupport.clean() session = await SessionHelper.add({ ...sessionData }) + yarStub = { flash: Sinon.stub().returns([]) } + }) + + afterEach(() => { + Sinon.restore() }) describe('when called', () => { it('fetches the current setup session record', async () => { - const result = await CheckYourAnswersService.go(session.id) + const result = await CheckYourAnswersService.go(session.id, yarStub) expect(result.id).to.equal(session.id) }) it('returns page data for the view', async () => { - const result = await CheckYourAnswersService.go(session.id) + const result = await CheckYourAnswersService.go(session.id, yarStub) expect(result).to.equal({ activeNavBar: 'search', @@ -62,6 +69,7 @@ describe('Check Your Answers service', () => { journey: 'no-returns-required', licenceRef: '01/ABC', note: 'Note attached to requirement', + notification: undefined, userEmail: 'carol.shaw@atari.com', reason: 'abstraction-below-100-cubic-metres-per-day', startDate: '8 February 2023' @@ -69,7 +77,7 @@ describe('Check Your Answers service', () => { }) it('updates the session record to indicate user has visited check-your-answers', async () => { - await CheckYourAnswersService.go(session.id) + await CheckYourAnswersService.go(session.id, yarStub) const updatedSession = await SessionModel.query().findById(session.id) expect(updatedSession.data.checkYourAnswersVisited).to.be.true() diff --git a/test/services/return-requirements/delete-note.service.test.js b/test/services/return-requirements/delete-note.service.test.js new file mode 100644 index 0000000000..8ea70d42bf --- /dev/null +++ b/test/services/return-requirements/delete-note.service.test.js @@ -0,0 +1,70 @@ +'use strict' + +// Test framework dependencies +const Lab = require('@hapi/lab') +const Code = require('@hapi/code') +const Sinon = require('sinon') + +const { describe, it, beforeEach } = exports.lab = Lab.script() +const { expect } = Code + +// Test helpers +const DatabaseSupport = require('../../support/database.js') +const SessionHelper = require('../../support/helpers/session.helper.js') + +// Thing under test +const DeleteNoteService = require('../../../app/services/return-requirements/delete-note.service.js') + +describe('Delete Note service', () => { + const sessionData = { + data: { + id: 'f1288f6c-8503-4dc1-b114-75c408a14bd0', + checkYourAnswersVisited: true, + licence: { + endDate: null, + licenceRef: '01/ABC', + licenceHolder: 'Astro Boy', + currentVersionStartDate: '2023-02-08T00:00:00.000Z' + }, + reason: 'abstraction-below-100-cubic-metres-per-day', + journey: 'no-returns-required', + note: { + content: 'Note attached to requirement', + userEmail: 'carol.shaw@atari.com' + }, + startDateOptions: 'licenceStartDate' + } + } + + let session + let yarStub + + beforeEach(async () => { + await DatabaseSupport.clean() + + session = await SessionHelper.add({ + ...sessionData + }) + + yarStub = { + flash: Sinon.stub() + } + }) + + it('deletes the note', async () => { + await DeleteNoteService.go(session.id, yarStub) + + const refreshedSession = await session.$query() + + expect(refreshedSession.data.note).to.be.undefined() + }) + + it("sets the notification message to 'Removed'", async () => { + await DeleteNoteService.go(session.id, yarStub) + + const [flashType, notification] = yarStub.flash.args[0] + + expect(flashType).to.equal('notification') + expect(notification).to.equal({ title: 'Removed', text: 'Note removed' }) + }) +}) diff --git a/test/services/return-requirements/submit-add-note.service.test.js b/test/services/return-requirements/submit-add-note.service.test.js index bcb5b2fdab..8ea3290d59 100644 --- a/test/services/return-requirements/submit-add-note.service.test.js +++ b/test/services/return-requirements/submit-add-note.service.test.js @@ -3,8 +3,9 @@ // Test framework dependencies const Lab = require('@hapi/lab') const Code = require('@hapi/code') +const Sinon = require('sinon') -const { describe, it, beforeEach } = exports.lab = Lab.script() +const { describe, it, beforeEach, afterEach } = exports.lab = Lab.script() const { expect } = Code // Test helpers @@ -17,6 +18,7 @@ const SubmitAddNoteService = require('../../../app/services/return-requirements/ describe('Submit Add Note service', () => { let payload let session + let yarStub const user = { username: 'carol.shaw@atari.com' } beforeEach(async () => { @@ -37,34 +39,76 @@ describe('Submit Add Note service', () => { returnsRequired: 'new-licence' } }) + + yarStub = { flash: Sinon.stub() } + }) + + afterEach(() => { + Sinon.restore() }) describe('when called', () => { describe('with a valid payload', () => { - beforeEach(() => { - payload = { - note: 'A note related to return requirement' - } - }) + describe('with a new note', () => { + beforeEach(() => { + payload = { + note: 'A note related to return requirement' + } + }) + + it('saves the submitted value', async () => { + await SubmitAddNoteService.go(session.id, payload, user, yarStub) + + const refreshedSession = await session.$query() + + expect(refreshedSession.data.note).to.equal({ + content: 'A note related to return requirement', + userEmail: 'carol.shaw@atari.com' + }) + }) + + it('returns the journey to redirect the page', async () => { + const result = await SubmitAddNoteService.go(session.id, payload, user, yarStub) + + expect(result).to.equal({ + journey: 'no-returns-required' + + }, { skip: ['id'] }) + }) - it('saves the submitted value', async () => { - await SubmitAddNoteService.go(session.id, payload, user) + it("sets the notification message to 'Added' for a new note", async () => { + await SubmitAddNoteService.go(session.id, payload, user, yarStub) - const refreshedSession = await session.$query() + const [flashType, notification] = yarStub.flash.args[0] - expect(refreshedSession.data.note).to.equal({ - content: 'A note related to return requirement', - userEmail: 'carol.shaw@atari.com' + expect(flashType).to.equal('notification') + expect(notification).to.equal({ title: 'Added', text: 'Changes made' }) }) }) - it('returns the journey to redirect the page', async () => { - const result = await SubmitAddNoteService.go(session.id, payload, user) + describe('with an updated note', () => { + beforeEach(async () => { + session = await SessionHelper.add({ + data: { + note: { + content: 'A old note related to return requirement', + userEmail: 'carol.shaw@atari.com' + } + } + }) + payload = { + note: 'A new note related to return requirement' + } + }) - expect(result).to.equal({ - journey: 'no-returns-required' + it("sets the notification message to 'Updated' for an updated note", async () => { + await SubmitAddNoteService.go(session.id, payload, user, yarStub) - }, { skip: ['id'] }) + const [flashType, notification] = yarStub.flash.args[0] + + expect(flashType).to.equal('notification') + expect(notification).to.equal({ title: 'Updated', text: 'Changes made' }) + }) }) }) @@ -74,7 +118,7 @@ describe('Submit Add Note service', () => { }) it('returns page data with an error', async () => { - const result = await SubmitAddNoteService.go(session.id, payload, user) + const result = await SubmitAddNoteService.go(session.id, payload, user, yarStub) expect(result).to.equal({ id: session.id,