diff --git a/Jenkinsfile_CNP b/Jenkinsfile_CNP index 6157640b1..27d4535fe 100644 --- a/Jenkinsfile_CNP +++ b/Jenkinsfile_CNP @@ -41,7 +41,7 @@ def secrets = [ ] def yarnBuilder = new uk.gov.hmcts.contino.YarnBuilder(this) -def branchesToSync = ['demo', 'ithc', 'perftest'] +def branchesToSync = ['demo', 'perftest'] def pipelineConf = new AppPipelineConfig() pipelineConf.vaultSecrets = secrets diff --git a/README.md b/README.md index db60968c6..ea8d3624e 100644 --- a/README.md +++ b/README.md @@ -221,3 +221,9 @@ ADOP_WEB_URL=https://adoption-web.aat.platform.hmcts.net/ SHOW_BROWSER_WINDOW=fa ```$bash ADOP_WEB_URL=https://adoption-web-pr-146.service.core-compute-preview.internal/ SHOW_BROWSER_WINDOW=false CITIZEN_PASSWORD=Adoption12 yarn test:local --grep 'Verify apply my own option' ``` + +## Step controllers +src/main/app/controller contains default controllers. These will be used if no controllers are specified alongside content in the steps folders. +If a step needs additional functionality, add a controller alongside the content.ts, which inherits the default controller. Get and post controllers +need 'get' or 'post' in their filenames. + diff --git a/infrastructure/ithc.tfvars b/infrastructure/ithc.tfvars new file mode 100644 index 000000000..ebea1ce81 --- /dev/null +++ b/infrastructure/ithc.tfvars @@ -0,0 +1,5 @@ +sku_name = "Premium" +family = "P" +capacity = "1" +rdb_backup_enabled = true +redis_backup_frequency = "15" diff --git a/infrastructure/main.tf b/infrastructure/main.tf index 0dd3de932..980c356b2 100644 --- a/infrastructure/main.tf +++ b/infrastructure/main.tf @@ -13,18 +13,22 @@ data "azurerm_subnet" "core_infra_redis_subnet" { } module "adoption-web-session-storage" { - source = "git@github.com:hmcts/cnp-module-redis?ref=master" - product = "${var.product}-${var.component}-session-storage" - location = var.location - env = var.env - private_endpoint_enabled = true - redis_version = "6" - business_area = "cft" - public_network_access_enabled = false - common_tags = var.common_tags - sku_name = var.sku_name - family = var.family - capacity = var.capacity + source = "git@github.com:hmcts/cnp-module-redis?ref=DTSPO-17012-data-persistency" + product = "${var.product}-${var.component}-session-storage" + location = var.location + env = var.env + private_endpoint_enabled = true + redis_version = "6" + business_area = "cft" + public_network_access_enabled = false + common_tags = var.common_tags + sku_name = var.sku_name + family = var.family + capacity = var.capacity + rdb_backup_enabled = var.rdb_backup_enabled + rdb_backup_frequency = var.redis_backup_frequency + rdb_backup_max_snapshot_count = var.rdb_backup_max_snapshot_count + rdb_storage_account_name_prefix = var.raw_product } data "azurerm_key_vault" "adoption_key_vault" { diff --git a/infrastructure/variables.tf b/infrastructure/variables.tf index 1b9478f2c..4ca16ac7d 100644 --- a/infrastructure/variables.tf +++ b/infrastructure/variables.tf @@ -54,3 +54,19 @@ variable "capacity" { default = "1" description = "The size of the Redis cache to deploy. Valid values are 1, 2, 3, 4, 5" } + +variable "rdb_backup_enabled" { + type = bool + default = false + description = "The maximum number of snapshots to create as a backup. Only supported for Premium SKUs." +} + +variable "rdb_backup_max_snapshot_count" { + type = string + default = "1" +} + +variable "redis_backup_frequency" { + default = "360" + description = "The Backup Frequency in Minutes. Only supported on Premium SKUs. Possible values are: 15, 30, 60, 360, 720 and 1440" +} diff --git a/package.json b/package.json index 6082ead70..581ce35ff 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,7 @@ "@axe-core/playwright": "^4.9.0", "@hmcts/cookie-manager": "^1.0.0", "@hmcts/frontend": "^0.0.50-alpha", - "@hmcts/nodejs-healthcheck": "^1.7.3", + "@hmcts/nodejs-healthcheck": "^1.8.4", "@hmcts/nodejs-logging": "^4.0.4", "@hmcts/properties-volume": "^1.0.0", "@types/autobind-decorator": "^2.1.0", @@ -170,7 +170,8 @@ "minimist": "^1.2.6", "config/json5": ">=2.2.2", "tsconfig-paths/json5": ">=2.2.2", - "cookiejar": ">=2.1.4" + "cookiejar": ">=2.1.4", + "formidable": "^3.2.4" }, "packageManager": "yarn@3.6.4" } diff --git a/src/main/app/document/DocumentManagementClient.ts b/src/main/app/document/DocumentManagementClient.ts index f72c3cb5e..f1337f605 100644 --- a/src/main/app/document/DocumentManagementClient.ts +++ b/src/main/app/document/DocumentManagementClient.ts @@ -1,13 +1,21 @@ +import { Logger } from '@hmcts/nodejs-logging'; import Axios, { AxiosInstance, AxiosResponse } from 'axios'; import FormData from 'form-data'; import { CASE_TYPE, JURISDICTION, UserRole } from '../../app/case/definition'; import type { UserDetails } from '../controller/AppRequest'; +const logger = Logger.getLogger('DocumentManagementClient'); + export class DocumentManagementClient { client: AxiosInstance; + serviceToken: string; + userToken: string; constructor(baseURL: string, authToken: string, private readonly user: UserDetails) { + this.serviceToken = authToken; + this.userToken = `Bearer ${user.accessToken}`; + this.client = Axios.create({ baseURL, maxBodyLength: 20971520, @@ -42,6 +50,7 @@ export class DocumentManagementClient { } async delete({ url }: { url: string }): Promise { + logger.info(`service header is ${this.serviceToken}, usertoken is ${this.userToken}`); return this.client.delete(url, { headers: { 'user-id': this.user.id } }); } diff --git a/src/main/app/document/DocumentManagementController.ts b/src/main/app/document/DocumentManagementController.ts index 1b7f51ecb..fc6eabdab 100644 --- a/src/main/app/document/DocumentManagementController.ts +++ b/src/main/app/document/DocumentManagementController.ts @@ -1,3 +1,4 @@ +import { Logger } from '@hmcts/nodejs-logging'; import autobind from 'autobind-decorator'; import config from 'config'; import type { Response } from 'express'; @@ -20,6 +21,8 @@ import type { AppRequest, UserDetails } from '../controller/AppRequest'; import { Classification, DocumentManagementClient } from './DocumentManagementClient'; +const logger = Logger.getLogger('DocumentManagementController'); + @autobind export class DocumentManagerController { private getDocumentManagementClient(user: UserDetails) { @@ -108,6 +111,8 @@ export class DocumentManagerController { return res.redirect(documentInput ? documentInput.documentRedirectUrl : UPLOAD_YOUR_DOCUMENTS); } const documentUrlToDelete = documentToDelete.value.documentLink.document_url; + req.session.userCase.id; + logger.info(`Deleting document from case ${req.session.userCase.id}. Document URL: ${documentUrlToDelete}`); documentsUploaded[documentIndexToDelete].value = null; @@ -122,6 +127,9 @@ export class DocumentManagerController { req.session.save(err => { if (err) { + logger.error( + `Error deleting document from case ${req.session.userCase.id}. Document URL: ${documentUrlToDelete}` + ); throw err; } return res.redirect(documentInput ? documentInput.documentRedirectUrl : UPLOAD_YOUR_DOCUMENTS); diff --git a/src/main/steps/application/submitted/content.test.ts b/src/main/steps/application/submitted/content.test.ts index 4d666cf28..21c97b27c 100644 --- a/src/main/steps/application/submitted/content.test.ts +++ b/src/main/steps/application/submitted/content.test.ts @@ -22,7 +22,7 @@ const enContent = { 'You should contact your social worker for updates on your application. The adoption service cannot provide these.', multipleChildren: 'Adopting more than one child', line6: - 'If you are applying for more than one child, you must complete and submit a new application for each child. This must be done before midnight on the day you submitted your first application or you will be charged an additional court fee of £183.', + 'If you are applying for more than one child, you must complete and submit a new application for each child. This must be done before midnight on the day you submitted your first application or you will be charged an additional court fee of £undefined.', line7: 'You will be asked the same questions. This is because each application is treated separately.', line8: 'You might find it useful to take a note of each new reference number next to the name of the child you are applying to adopt. The emails you receive will only contain the reference number.', @@ -53,7 +53,7 @@ const cyContent = { 'Dylech gysylltu â’ch gweithiwr cymdeithasol i gael diweddariadau am eich cais. Ni all y gwasanaeth mabwysiadu roi diweddariadau i chi.', multipleChildren: 'Mabwysiadu mwy nag un plentyn', line6: - 'Os ydych chi’n gwneud cais i fabwysiadu mwy nag un plentyn, mae’n rhaid i chi gwblhau a chyflwyno cais newydd ar gyfer pob plentyn. Rhaid gwneud hyn cyn hanner nos ar y dyddiad y byddwch yn cyflwyno eich cais cyntaf neu fe godir ffi llys ychwanegol arnoch o £183.', + 'Os ydych chi’n gwneud cais i fabwysiadu mwy nag un plentyn, mae’n rhaid i chi gwblhau a chyflwyno cais newydd ar gyfer pob plentyn. Rhaid gwneud hyn cyn hanner nos ar y dyddiad y byddwch yn cyflwyno eich cais cyntaf neu fe godir ffi llys ychwanegol arnoch o £undefined.', line7: 'Fe ofynnir yr un cwestiynau i chi. Mae hyn oherwydd fe ymdrinnir â phob cais ar wahân.', line8: 'Efallai y byddai’n eich helpu i nodi pob cyfeirnod newydd wrth enw’r plentyn rydych yn gwneud cais i’w fabwysiadu. Dim ond y cyfeirnod fydd wedi’i nodi ar y negeseuon e-bost a anfonir atoch.', diff --git a/src/main/steps/application/submitted/content.ts b/src/main/steps/application/submitted/content.ts index 39d361cc1..514114173 100644 --- a/src/main/steps/application/submitted/content.ts +++ b/src/main/steps/application/submitted/content.ts @@ -17,8 +17,7 @@ const en = content => ({ line5: 'You should contact your social worker for updates on your application. The adoption service cannot provide these.', multipleChildren: 'Adopting more than one child', - line6: - 'If you are applying for more than one child, you must complete and submit a new application for each child. This must be done before midnight on the day you submitted your first application or you will be charged an additional court fee of £183.', + line6: `If you are applying for more than one child, you must complete and submit a new application for each child. This must be done before midnight on the day you submitted your first application or you will be charged an additional court fee of £${content.fee?.FeeAmount}.`, line7: 'You will be asked the same questions. This is because each application is treated separately.', line8: 'You might find it useful to take a note of each new reference number next to the name of the child you are applying to adopt. The emails you receive will only contain the reference number.', @@ -47,8 +46,7 @@ const cy: typeof en = content => ({ line5: 'Dylech gysylltu â’ch gweithiwr cymdeithasol i gael diweddariadau am eich cais. Ni all y gwasanaeth mabwysiadu roi diweddariadau i chi.', multipleChildren: 'Mabwysiadu mwy nag un plentyn', - line6: - 'Os ydych chi’n gwneud cais i fabwysiadu mwy nag un plentyn, mae’n rhaid i chi gwblhau a chyflwyno cais newydd ar gyfer pob plentyn. Rhaid gwneud hyn cyn hanner nos ar y dyddiad y byddwch yn cyflwyno eich cais cyntaf neu fe godir ffi llys ychwanegol arnoch o £183.', + line6: `Os ydych chi’n gwneud cais i fabwysiadu mwy nag un plentyn, mae’n rhaid i chi gwblhau a chyflwyno cais newydd ar gyfer pob plentyn. Rhaid gwneud hyn cyn hanner nos ar y dyddiad y byddwch yn cyflwyno eich cais cyntaf neu fe godir ffi llys ychwanegol arnoch o £${content.fee?.FeeAmount}.`, line7: 'Fe ofynnir yr un cwestiynau i chi. Mae hyn oherwydd fe ymdrinnir â phob cais ar wahân.', line8: 'Efallai y byddai’n eich helpu i nodi pob cyfeirnod newydd wrth enw’r plentyn rydych yn gwneud cais i’w fabwysiadu. Dim ond y cyfeirnod fydd wedi’i nodi ar y negeseuon e-bost a anfonir atoch.', diff --git a/src/main/steps/application/submitted/get.test.ts b/src/main/steps/application/submitted/get.test.ts deleted file mode 100644 index 3f59f1059..000000000 --- a/src/main/steps/application/submitted/get.test.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { mockRequest } from '../../../../test/unit/utils/mockRequest'; -import { mockResponse } from '../../../../test/unit/utils/mockResponse'; -import { State } from '../../../app/case/definition'; -import { TASK_LIST_URL } from '../../urls'; - -import ApplicationSubmittedGetController from './get'; - -describe('ApplicationSubmittedGetController', () => { - const controller = new ApplicationSubmittedGetController(); - const req = mockRequest({ - session: { userCase: { state: State.AwaitingDocuments }, user: { email: 'test@example.com' } }, - }); - const res = mockResponse(); - - it('returns user to task list', async () => { - controller.get(req, res); - expect(res.redirect).toHaveBeenCalledWith(TASK_LIST_URL); - }); -}); diff --git a/src/main/steps/application/submitted/getSubmittedController.test.ts b/src/main/steps/application/submitted/getSubmittedController.test.ts new file mode 100644 index 000000000..a21ca6cec --- /dev/null +++ b/src/main/steps/application/submitted/getSubmittedController.test.ts @@ -0,0 +1,69 @@ +const mockGetFee = jest.fn(); +jest.mock('../../../app/fee/fee-lookup-api', () => ({ + getFee: mockGetFee, +})); + +import { mockRequest } from '../../../../test/unit/utils/mockRequest'; +import { mockResponse } from '../../../../test/unit/utils/mockResponse'; +import { Case } from '../../../app/case/case'; +import { State } from '../../../app/case/definition'; +import { AppRequest } from '../../../app/controller/AppRequest'; +import { TASK_LIST_URL } from '../../urls'; + +import GetSubmittedController from './getSubmittedController'; + +describe('GetSubmittedController', () => { + const controller = new GetSubmittedController(); + const res = mockResponse(); + let req: AppRequest>; + + beforeEach(() => { + req = mockRequest({ + session: { userCase: { state: State.Submitted }, user: { email: 'test@example.com' } }, + }); + }); + + afterEach(() => { + mockGetFee.mockClear(); + }); + + it('returns user to task list when state is not Submitted', async () => { + req.session.userCase.state = State.AwaitingDocuments; + controller.get(req, res); + expect(res.redirect).toHaveBeenCalledWith(TASK_LIST_URL); + }); + + it('should call the fee lookup api', async () => { + mockGetFee.mockResolvedValue({ FeeAmount: 'a fee amount' }); + await controller.get(req, res); + expect(mockGetFee).toHaveBeenCalledWith(req.locals.logger); + }); + + it('should save the fee response in session', async () => { + mockGetFee.mockResolvedValue({ + FeeCode: 'MOCK_CODE', + FeeDescription: 'MOCK_DESCRIPTION', + FeeVersion: 'MOCK_VERSION', + FeeAmount: 'MOCK_AMOUNT', + }); + await controller.get(req, res); + expect(req.session.fee).toEqual({ + FeeCode: 'MOCK_CODE', + FeeDescription: 'MOCK_DESCRIPTION', + FeeVersion: 'MOCK_VERSION', + FeeAmount: 'MOCK_AMOUNT', + }); + }); + + it('should throw error when feel lookup api fails', async () => { + mockGetFee.mockReturnValue(undefined); + try { + await controller.get(req, res); + } catch (err) { + /* eslint-disable jest/no-conditional-expect */ + expect(err).toEqual(new Error('GetSubmittedController unable to get fee from fee-register API')); + expect(mockGetFee).toHaveBeenCalledWith(req.locals.logger); + /* eslint-enable jest/no-conditional-expect */ + } + }); +}); diff --git a/src/main/steps/application/submitted/get.ts b/src/main/steps/application/submitted/getSubmittedController.ts similarity index 51% rename from src/main/steps/application/submitted/get.ts rename to src/main/steps/application/submitted/getSubmittedController.ts index 80b541f85..c8285d7a8 100644 --- a/src/main/steps/application/submitted/get.ts +++ b/src/main/steps/application/submitted/getSubmittedController.ts @@ -1,15 +1,19 @@ +import { Logger } from '@hmcts/nodejs-logging'; import autobind from 'autobind-decorator'; import { Response } from 'express'; import { State } from '../../../app/case/definition'; import { AppRequest } from '../../../app/controller/AppRequest'; import { GetController } from '../../../app/controller/GetController'; +import { getFee } from '../../../app/fee/fee-lookup-api'; import { TASK_LIST_URL } from '../../urls'; import { generateContent } from './content'; +const logger = Logger.getLogger('GetSubmittedController'); + @autobind -export default class ApplicationSubmittedGetController extends GetController { +export default class GetSubmittedController extends GetController { constructor() { super(__dirname + '/template', generateContent); } @@ -19,6 +23,15 @@ export default class ApplicationSubmittedGetController extends GetController { if (req.session.userCase.state !== State.Submitted) { return res.redirect(TASK_LIST_URL); } - await super.get(req, res); + const feeResponse = await getFee(req.locals.logger); + if (feeResponse) { + req.session.fee = feeResponse; + + const callback = () => super.get(req, res); + super.saveSessionAndRedirect(req, res, callback); + } else { + logger.error('GetSubmittedController unable to get fee from fee-register API'); + throw new Error('GetSubmittedController unable to get fee from fee-register API'); + } } } diff --git a/src/main/steps/eligibility/multiple-children-desc/content.test.ts b/src/main/steps/eligibility/multiple-children-desc/content.test.ts index cf05860cc..0bee5b1dd 100644 --- a/src/main/steps/eligibility/multiple-children-desc/content.test.ts +++ b/src/main/steps/eligibility/multiple-children-desc/content.test.ts @@ -7,7 +7,7 @@ const enContent = { line1: 'If you are applying for more than one child, you will need to complete an application for each child in turn. Once you have submitted your first application, you can continue and start your next application.', line2: - 'You will only be charged one fee if you submit additional applications before midnight on the day you submit your first application. If you submit after the day of the first application, you will be charged another £183.', + 'You will only be charged one fee if you submit additional applications before midnight on the day you submit your first application. If you submit after the day of the first application, you will be charged another £undefined.', line3: 'If you sign out, you must sign in again using the same email address and password used in your first application.', continue: 'Continue', @@ -18,7 +18,7 @@ const cyContent = { line1: 'Os ydych chi’n gwneud cais i fabwysiadu mwy nag un plentyn, bydd angen i chi gyflwyno cais newydd ar gyfer pob plentyn. Unwaith y byddwch wedi cyflwyno eich cais cyntaf, gallwch barhau a chychwyn eich cais nesaf.', line2: - 'Codir un ffi arnoch os byddwch yn cyflwyno unrhyw geisiadau ychwanegol cyn hanner nos ar ddyddiad cyflwyno’ch cais cyntaf. Os byddwch yn eu cyflwyno ar ôl dyddiad cyflwyno’r cais cyntaf, yna bydd rhaid i chi dalu £183 arall.', + 'Codir un ffi arnoch os byddwch yn cyflwyno unrhyw geisiadau ychwanegol cyn hanner nos ar ddyddiad cyflwyno’ch cais cyntaf. Os byddwch yn eu cyflwyno ar ôl dyddiad cyflwyno’r cais cyntaf, yna bydd rhaid i chi dalu £undefined arall.', line3: 'Os byddwch yn allgofnodi, bydd rhaid ichi fewngofnodi eto gan ddefnyddio’r un cyfeiriad e-bost a chyfrinair a ddefnyddiwyd ar gyfer eich cais cyntaf.', continue: 'Parhau', diff --git a/src/main/steps/eligibility/multiple-children-desc/content.ts b/src/main/steps/eligibility/multiple-children-desc/content.ts index e04a16b16..7d7cf3a7e 100644 --- a/src/main/steps/eligibility/multiple-children-desc/content.ts +++ b/src/main/steps/eligibility/multiple-children-desc/content.ts @@ -5,19 +5,17 @@ const en = content => ({ title: 'Applying for more than one child', line1: 'If you are applying for more than one child, you will need to complete an application for each child in turn. Once you have submitted your first application, you can continue and start your next application.', - line2: - 'You will only be charged one fee if you submit additional applications before midnight on the day you submit your first application. If you submit after the day of the first application, you will be charged another £183.', + line2: `You will only be charged one fee if you submit additional applications before midnight on the day you submit your first application. If you submit after the day of the first application, you will be charged another £${content.fee?.FeeAmount}.`, line3: 'If you sign out, you must sign in again using the same email address and password used in your first application.', continue: 'Continue', }); -const cy: typeof en = () => ({ +const cy: typeof en = content => ({ title: 'Gwneud cais i fabwysiadu mwy nag un plentyn', line1: 'Os ydych chi’n gwneud cais i fabwysiadu mwy nag un plentyn, bydd angen i chi gyflwyno cais newydd ar gyfer pob plentyn. Unwaith y byddwch wedi cyflwyno eich cais cyntaf, gallwch barhau a chychwyn eich cais nesaf.', - line2: - 'Codir un ffi arnoch os byddwch yn cyflwyno unrhyw geisiadau ychwanegol cyn hanner nos ar ddyddiad cyflwyno’ch cais cyntaf. Os byddwch yn eu cyflwyno ar ôl dyddiad cyflwyno’r cais cyntaf, yna bydd rhaid i chi dalu £183 arall.', + line2: `Codir un ffi arnoch os byddwch yn cyflwyno unrhyw geisiadau ychwanegol cyn hanner nos ar ddyddiad cyflwyno’ch cais cyntaf. Os byddwch yn eu cyflwyno ar ôl dyddiad cyflwyno’r cais cyntaf, yna bydd rhaid i chi dalu £${content.fee?.FeeAmount} arall.`, line3: 'Os byddwch yn allgofnodi, bydd rhaid ichi fewngofnodi eto gan ddefnyddio’r un cyfeiriad e-bost a chyfrinair a ddefnyddiwyd ar gyfer eich cais cyntaf.', continue: 'Parhau', diff --git a/src/main/steps/eligibility/multiple-children-desc/getMultipleChildrenDescController.test.ts b/src/main/steps/eligibility/multiple-children-desc/getMultipleChildrenDescController.test.ts new file mode 100644 index 000000000..7c3d3dc72 --- /dev/null +++ b/src/main/steps/eligibility/multiple-children-desc/getMultipleChildrenDescController.test.ts @@ -0,0 +1,60 @@ +const mockGetFee = jest.fn(); +jest.mock('../../../app/fee/fee-lookup-api', () => ({ + getFee: mockGetFee, +})); + +import { mockRequest } from '../../../../test/unit/utils/mockRequest'; +import { mockResponse } from '../../../../test/unit/utils/mockResponse'; +import { Case } from '../../../app/case/case'; +import { AppRequest } from '../../../app/controller/AppRequest'; + +import { generateContent } from './content'; +import GetMultipleChildrenDescController from './getMultipleChildrenDescController'; + +describe('GetMultipleChildrenDescController', () => { + const controller = new GetMultipleChildrenDescController(__dirname + './template', generateContent); + let req: AppRequest>; + const res = mockResponse(); + + beforeEach(() => { + req = mockRequest({ userCase: {} }); + }); + + afterEach(() => { + mockGetFee.mockClear(); + }); + + it('should call the fee lookup api', async () => { + mockGetFee.mockResolvedValue({ FeeAmount: 'a fee amount' }); + await controller.get(req, res); + expect(mockGetFee).toHaveBeenCalledWith(req.locals.logger); + }); + + it('should save the fee response in session', async () => { + mockGetFee.mockResolvedValue({ + FeeCode: 'MOCK_CODE', + FeeDescription: 'MOCK_DESCRIPTION', + FeeVersion: 'MOCK_VERSION', + FeeAmount: 'MOCK_AMOUNT', + }); + await controller.get(req, res); + expect(req.session.fee).toEqual({ + FeeCode: 'MOCK_CODE', + FeeDescription: 'MOCK_DESCRIPTION', + FeeVersion: 'MOCK_VERSION', + FeeAmount: 'MOCK_AMOUNT', + }); + }); + + it('should throw error when feel lookup api fails', async () => { + mockGetFee.mockReturnValue(undefined); + try { + await controller.get(req, res); + } catch (err) { + /* eslint-disable jest/no-conditional-expect */ + expect(err).toEqual(new Error('GetMultipleChildrenDescController unable to get fee from fee-register API')); + expect(mockGetFee).toHaveBeenCalledWith(req.locals.logger); + /* eslint-enable jest/no-conditional-expect */ + } + }); +}); diff --git a/src/main/steps/eligibility/multiple-children-desc/getMultipleChildrenDescController.ts b/src/main/steps/eligibility/multiple-children-desc/getMultipleChildrenDescController.ts new file mode 100644 index 000000000..d3c7f85c9 --- /dev/null +++ b/src/main/steps/eligibility/multiple-children-desc/getMultipleChildrenDescController.ts @@ -0,0 +1,25 @@ +import { Logger } from '@hmcts/nodejs-logging'; +import autobind from 'autobind-decorator'; +import { Response } from 'express'; + +import { AppRequest } from '../../../app/controller/AppRequest'; +import { GetController } from '../../../app/controller/GetController'; +import { getFee } from '../../../app/fee/fee-lookup-api'; + +const logger = Logger.getLogger('GetMultipleChildrenDescController'); + +@autobind +export default class GetMultipleChildrenDescController extends GetController { + public async get(req: AppRequest, res: Response): Promise { + const feeResponse = await getFee(req.locals.logger); + if (feeResponse) { + req.session.fee = feeResponse; + + const callback = () => super.get(req, res); + super.saveSessionAndRedirect(req, res, callback); + } else { + logger.error('GetMultipleChildrenDescController unable to get fee from fee-register API'); + throw new Error('GetMultipleChildrenDescController unable to get fee from fee-register API'); + } + } +} diff --git a/src/main/steps/review-pay-submit/pay-and-submit/content.test.ts b/src/main/steps/review-pay-submit/pay-and-submit/content.test.ts index 7e7184807..f6490df33 100644 --- a/src/main/steps/review-pay-submit/pay-and-submit/content.test.ts +++ b/src/main/steps/review-pay-submit/pay-and-submit/content.test.ts @@ -11,7 +11,7 @@ const enContent = { line1: 'You will be taken to the payment page. Your application will be submitted to the local authority and they will be asked to progress it. You cannot edit the application once it has been submitted.', line2: - "If you're applying for more than one child, you must submit a new application for each child. You will not be charged if you submit these before midnight on the day of your first application. If you submit after the day of the first application, you will be charged another £183. You must sign in using the same email address and password used in your first application.", + "If you're applying for more than one child, you must submit a new application for each child. You will not be charged if you submit these before midnight on the day of your first application. If you submit after the day of the first application, you will be charged another £undefined. You must sign in using the same email address and password used in your first application.", line3: 'A confirmation email of your payment will be sent to you.', payandsubmit: 'Pay and submit application', }; @@ -22,7 +22,7 @@ const cyContent = { line1: 'Byddwch yn cael eich ailgyfeirio i’r dudalen talu. Fe gyflwynir eich cais i’r awdurdod lleol ac fe ofynnir iddynt ei brosesu ymhellach. Ni allwch olygu’r cais unwaith y bydd wedi’i gyflwyno.', line2: - 'Os ydych chi’n gwneud cais i fabwysiadu mwy nag un plentyn, mae’n rhaid i chi gyflwyno cais newydd ar gyfer pob plentyn. Ni chodir ffi arall arnoch os byddwch yn cyflwyno’r ceisiadau hyn cyn hanner nos ar ddyddiad cyflwyno’ch cais cyntaf. Os byddwch yn eu cyflwyno ar ôl dyddiad cyflwyno’r cais cyntaf, yna bydd rhaid i chi dalu £183 arall. Mae’n rhaid ichi fewngofnodi gan ddefnyddio’r un cyfeiriad e-bost a chyfrinair a ddefnyddiwyd ar gyfer eich cais cyntaf.', + 'Os ydych chi’n gwneud cais i fabwysiadu mwy nag un plentyn, mae’n rhaid i chi gyflwyno cais newydd ar gyfer pob plentyn. Ni chodir ffi arall arnoch os byddwch yn cyflwyno’r ceisiadau hyn cyn hanner nos ar ddyddiad cyflwyno’ch cais cyntaf. Os byddwch yn eu cyflwyno ar ôl dyddiad cyflwyno’r cais cyntaf, yna bydd rhaid i chi dalu £undefined arall. Mae’n rhaid ichi fewngofnodi gan ddefnyddio’r un cyfeiriad e-bost a chyfrinair a ddefnyddiwyd ar gyfer eich cais cyntaf.', line3: 'Fe anfonir e-bost cadarnhau ar gyfer eich taliad atoch.', payandsubmit: 'Talu a chyflwyno cais', }; diff --git a/src/main/steps/review-pay-submit/pay-and-submit/content.ts b/src/main/steps/review-pay-submit/pay-and-submit/content.ts index 6588e9499..b6257ab79 100644 --- a/src/main/steps/review-pay-submit/pay-and-submit/content.ts +++ b/src/main/steps/review-pay-submit/pay-and-submit/content.ts @@ -1,23 +1,21 @@ import { TranslationFn } from '../../../app/controller/GetController'; -const en = () => ({ +const en = content => ({ section: 'Review your application', title: 'Pay and submit', line1: 'You will be taken to the payment page. Your application will be submitted to the local authority and they will be asked to progress it. You cannot edit the application once it has been submitted.', - line2: - "If you're applying for more than one child, you must submit a new application for each child. You will not be charged if you submit these before midnight on the day of your first application. If you submit after the day of the first application, you will be charged another £183. You must sign in using the same email address and password used in your first application.", + line2: `If you're applying for more than one child, you must submit a new application for each child. You will not be charged if you submit these before midnight on the day of your first application. If you submit after the day of the first application, you will be charged another £${content.fee?.FeeAmount}. You must sign in using the same email address and password used in your first application.`, line3: 'A confirmation email of your payment will be sent to you.', payandsubmit: 'Pay and submit application', }); -const cy: typeof en = () => ({ +const cy: typeof en = content => ({ section: 'Adolygu eich cais', title: 'Talu a chyflwyno', line1: 'Byddwch yn cael eich ailgyfeirio i’r dudalen talu. Fe gyflwynir eich cais i’r awdurdod lleol ac fe ofynnir iddynt ei brosesu ymhellach. Ni allwch olygu’r cais unwaith y bydd wedi’i gyflwyno.', - line2: - 'Os ydych chi’n gwneud cais i fabwysiadu mwy nag un plentyn, mae’n rhaid i chi gyflwyno cais newydd ar gyfer pob plentyn. Ni chodir ffi arall arnoch os byddwch yn cyflwyno’r ceisiadau hyn cyn hanner nos ar ddyddiad cyflwyno’ch cais cyntaf. Os byddwch yn eu cyflwyno ar ôl dyddiad cyflwyno’r cais cyntaf, yna bydd rhaid i chi dalu £183 arall. Mae’n rhaid ichi fewngofnodi gan ddefnyddio’r un cyfeiriad e-bost a chyfrinair a ddefnyddiwyd ar gyfer eich cais cyntaf.', + line2: `Os ydych chi’n gwneud cais i fabwysiadu mwy nag un plentyn, mae’n rhaid i chi gyflwyno cais newydd ar gyfer pob plentyn. Ni chodir ffi arall arnoch os byddwch yn cyflwyno’r ceisiadau hyn cyn hanner nos ar ddyddiad cyflwyno’ch cais cyntaf. Os byddwch yn eu cyflwyno ar ôl dyddiad cyflwyno’r cais cyntaf, yna bydd rhaid i chi dalu £${content.fee?.FeeAmount} arall. Mae’n rhaid ichi fewngofnodi gan ddefnyddio’r un cyfeiriad e-bost a chyfrinair a ddefnyddiwyd ar gyfer eich cais cyntaf.`, line3: 'Fe anfonir e-bost cadarnhau ar gyfer eich taliad atoch.', payandsubmit: 'Talu a chyflwyno cais', }); @@ -28,5 +26,5 @@ const languages = { }; export const generateContent: TranslationFn = content => { - return languages[content.language](); + return languages[content.language](content); }; diff --git a/src/main/steps/review-pay-submit/pay-and-submit/getPayAndSubmitController.test.ts b/src/main/steps/review-pay-submit/pay-and-submit/getPayAndSubmitController.test.ts new file mode 100644 index 000000000..489e261d5 --- /dev/null +++ b/src/main/steps/review-pay-submit/pay-and-submit/getPayAndSubmitController.test.ts @@ -0,0 +1,60 @@ +const mockGetFee = jest.fn(); +jest.mock('../../../app/fee/fee-lookup-api', () => ({ + getFee: mockGetFee, +})); + +import { mockRequest } from '../../../../test/unit/utils/mockRequest'; +import { mockResponse } from '../../../../test/unit/utils/mockResponse'; +import { Case } from '../../../app/case/case'; +import { AppRequest } from '../../../app/controller/AppRequest'; + +import { generateContent } from './content'; +import GetPayAndSubmitController from './getPayAndSubmitController'; + +describe('GetPayAndSubmitController', () => { + const controller = new GetPayAndSubmitController(__dirname + './template', generateContent); + let req: AppRequest>; + const res = mockResponse(); + + beforeEach(() => { + req = mockRequest({ userCase: {} }); + }); + + afterEach(() => { + mockGetFee.mockClear(); + }); + + it('should call the fee lookup api', async () => { + mockGetFee.mockResolvedValue({ FeeAmount: 'a fee amount' }); + await controller.get(req, res); + expect(mockGetFee).toHaveBeenCalledWith(req.locals.logger); + }); + + it('should save the fee response in session', async () => { + mockGetFee.mockResolvedValue({ + FeeCode: 'MOCK_CODE', + FeeDescription: 'MOCK_DESCRIPTION', + FeeVersion: 'MOCK_VERSION', + FeeAmount: 'MOCK_AMOUNT', + }); + await controller.get(req, res); + expect(req.session.fee).toEqual({ + FeeCode: 'MOCK_CODE', + FeeDescription: 'MOCK_DESCRIPTION', + FeeVersion: 'MOCK_VERSION', + FeeAmount: 'MOCK_AMOUNT', + }); + }); + + it('should throw error when feel lookup api fails', async () => { + mockGetFee.mockReturnValue(undefined); + try { + await controller.get(req, res); + } catch (err) { + /* eslint-disable jest/no-conditional-expect */ + expect(err).toEqual(new Error('GetPayAndSubmitController unable to get fee from fee-register API')); + expect(mockGetFee).toHaveBeenCalledWith(req.locals.logger); + /* eslint-enable jest/no-conditional-expect */ + } + }); +}); diff --git a/src/main/steps/review-pay-submit/pay-and-submit/getPayAndSubmitController.ts b/src/main/steps/review-pay-submit/pay-and-submit/getPayAndSubmitController.ts new file mode 100644 index 000000000..a19c4508c --- /dev/null +++ b/src/main/steps/review-pay-submit/pay-and-submit/getPayAndSubmitController.ts @@ -0,0 +1,25 @@ +import { Logger } from '@hmcts/nodejs-logging'; +import autobind from 'autobind-decorator'; +import { Response } from 'express'; + +import { AppRequest } from '../../../app/controller/AppRequest'; +import { GetController } from '../../../app/controller/GetController'; +import { getFee } from '../../../app/fee/fee-lookup-api'; + +const logger = Logger.getLogger('GetPayAndSubmitController'); + +@autobind +export default class GetPayAndSubmitController extends GetController { + public async get(req: AppRequest, res: Response): Promise { + const feeResponse = await getFee(req.locals.logger); + if (feeResponse) { + req.session.fee = feeResponse; + + const callback = () => super.get(req, res); + super.saveSessionAndRedirect(req, res, callback); + } else { + logger.error('GetPayAndSubmitController unable to get fee from fee-register API'); + throw new Error('GetPayAndSubmitController unable to get fee from fee-register API'); + } + } +} diff --git a/yarn.lock b/yarn.lock index ebc55f089..c82cc47a6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2068,14 +2068,14 @@ __metadata: languageName: node linkType: hard -"@hmcts/nodejs-healthcheck@npm:^1.7.3": - version: 1.8.3 - resolution: "@hmcts/nodejs-healthcheck@npm:1.8.3" +"@hmcts/nodejs-healthcheck@npm:^1.8.4": + version: 1.8.4 + resolution: "@hmcts/nodejs-healthcheck@npm:1.8.4" dependencies: "@hmcts/nodejs-logging": ^4.0.4 js-yaml: ^3.8.4 - superagent: 7 - checksum: 76f87a5a6707e76ab407f041991ac10139390290c40267401349e4af4f95490f4b3f542cbe4f59eb392655e71e28a1ec782a0522189257327f39c1fbb5d965bc + superagent: 8 + checksum: 8b2a6d1745fc2b63bd933a4034b92e9b144c385e8e57a9c9cde454ef2dff19467911aa1c5ade2ece54fff10868c07b966814c31fe1d743c59ab10d5706db898e languageName: node linkType: hard @@ -4333,7 +4333,7 @@ __metadata: "@babel/preset-env": ^7.18.2 "@hmcts/cookie-manager": ^1.0.0 "@hmcts/frontend": ^0.0.50-alpha - "@hmcts/nodejs-healthcheck": ^1.7.3 + "@hmcts/nodejs-healthcheck": ^1.8.4 "@hmcts/nodejs-logging": ^4.0.4 "@hmcts/properties-volume": ^1.0.0 "@pact-foundation/absolute-version": ^0.0.4 @@ -5577,6 +5577,19 @@ __metadata: languageName: node linkType: hard +"call-bind@npm:^1.0.7": + version: 1.0.7 + resolution: "call-bind@npm:1.0.7" + dependencies: + es-define-property: ^1.0.0 + es-errors: ^1.3.0 + function-bind: ^1.1.2 + get-intrinsic: ^1.2.4 + set-function-length: ^1.2.1 + checksum: 295c0c62b90dd6522e6db3b0ab1ce26bdf9e7404215bda13cfee25b626b5ff1a7761324d58d38b1ef1607fc65aca2d06e44d2e18d0dfc6c14b465b00d8660029 + languageName: node + linkType: hard + "callsites@npm:^3.0.0": version: 3.1.0 resolution: "callsites@npm:3.1.0" @@ -6839,6 +6852,17 @@ __metadata: languageName: node linkType: hard +"define-data-property@npm:^1.1.4": + version: 1.1.4 + resolution: "define-data-property@npm:1.1.4" + dependencies: + es-define-property: ^1.0.0 + es-errors: ^1.3.0 + gopd: ^1.0.1 + checksum: 8068ee6cab694d409ac25936eb861eea704b7763f7f342adbdfe337fc27c78d7ae0eff2364b2917b58c508d723c7a074326d068eef2e45c4edcd85cf94d0313b + languageName: node + linkType: hard + "define-lazy-prop@npm:^2.0.0": version: 2.0.0 resolution: "define-lazy-prop@npm:2.0.0" @@ -6988,13 +7012,13 @@ __metadata: languageName: node linkType: hard -"dezalgo@npm:1.0.3": - version: 1.0.3 - resolution: "dezalgo@npm:1.0.3" +"dezalgo@npm:^1.0.4": + version: 1.0.4 + resolution: "dezalgo@npm:1.0.4" dependencies: asap: ^2.0.0 wrappy: 1 - checksum: 8b26238db91423b2702a7a6d9629d0019c37c415e7b6e75d4b3e8d27e9464e21cac3618dd145f4d4ee96c70cc6ff034227b5b8a0e9c09015a8bdbe6dace3cfb9 + checksum: 895389c6aead740d2ab5da4d3466d20fa30f738010a4d3f4dcccc9fc645ca31c9d10b7e1804ae489b1eb02c7986f9f1f34ba132d409b043082a86d9a4e745624 languageName: node linkType: hard @@ -7429,6 +7453,22 @@ __metadata: languageName: node linkType: hard +"es-define-property@npm:^1.0.0": + version: 1.0.0 + resolution: "es-define-property@npm:1.0.0" + dependencies: + get-intrinsic: ^1.2.4 + checksum: f66ece0a887b6dca71848fa71f70461357c0e4e7249696f81bad0a1f347eed7b31262af4a29f5d726dc026426f085483b6b90301855e647aa8e21936f07293c6 + languageName: node + linkType: hard + +"es-errors@npm:^1.3.0": + version: 1.3.0 + resolution: "es-errors@npm:1.3.0" + checksum: ec1414527a0ccacd7f15f4a3bc66e215f04f595ba23ca75cdae0927af099b5ec865f9f4d33e9d7e86f512f252876ac77d4281a7871531a50678132429b1271b5 + languageName: node + linkType: hard + "es-module-lexer@npm:^1.2.1": version: 1.2.1 resolution: "es-module-lexer@npm:1.2.1" @@ -8318,15 +8358,14 @@ __metadata: languageName: node linkType: hard -"formidable@npm:^2.0.1": - version: 2.0.1 - resolution: "formidable@npm:2.0.1" +"formidable@npm:^3.2.4": + version: 3.5.1 + resolution: "formidable@npm:3.5.1" dependencies: - dezalgo: 1.0.3 - hexoid: 1.0.0 - once: 1.4.0 - qs: 6.9.3 - checksum: b35445444e7b6f6f3cacbadd5e6fadd6b5b2e83162e7c41fa22586df584cc515bbd1ee0dc2b701ce031fcb000d71769bc77bd0958db8a89a0ceb8b2227bdc695 + dezalgo: ^1.0.4 + hexoid: ^1.0.0 + once: ^1.4.0 + checksum: 46b21496f9f985161cf7636163147b6728f9997c7e1d59433680d92619758bf6862330e6d105b5816bafcd1ab32f27ef183455991f93ef836ea731c68db62af9 languageName: node linkType: hard @@ -8565,6 +8604,19 @@ __metadata: languageName: node linkType: hard +"get-intrinsic@npm:^1.2.4": + version: 1.2.4 + resolution: "get-intrinsic@npm:1.2.4" + dependencies: + es-errors: ^1.3.0 + function-bind: ^1.1.2 + has-proto: ^1.0.1 + has-symbols: ^1.0.3 + hasown: ^2.0.0 + checksum: 414e3cdf2c203d1b9d7d33111df746a4512a1aa622770b361dadddf8ed0b5aeb26c560f49ca077e24bfafb0acb55ca908d1f709216ccba33ffc548ec8a79a951 + languageName: node + linkType: hard + "get-package-type@npm:^0.1.0": version: 0.1.0 resolution: "get-package-type@npm:0.1.0" @@ -8913,6 +8965,15 @@ __metadata: languageName: node linkType: hard +"has-property-descriptors@npm:^1.0.2": + version: 1.0.2 + resolution: "has-property-descriptors@npm:1.0.2" + dependencies: + es-define-property: ^1.0.0 + checksum: fcbb246ea2838058be39887935231c6d5788babed499d0e9d0cc5737494c48aba4fe17ba1449e0d0fbbb1e36175442faa37f9c427ae357d6ccb1d895fbcd3de3 + languageName: node + linkType: hard + "has-proto@npm:^1.0.1": version: 1.0.1 resolution: "has-proto@npm:1.0.1" @@ -8984,7 +9045,7 @@ __metadata: languageName: node linkType: hard -"hexoid@npm:1.0.0": +"hexoid@npm:^1.0.0": version: 1.0.0 resolution: "hexoid@npm:1.0.0" checksum: 27a148ca76a2358287f40445870116baaff4a0ed0acc99900bf167f0f708ffd82e044ff55e9949c71963852b580fc024146d3ac6d5d76b508b78d927fa48ae2d @@ -11521,7 +11582,7 @@ __metadata: languageName: node linkType: hard -"mime@npm:^2.5.0": +"mime@npm:2.6.0": version: 2.6.0 resolution: "mime@npm:2.6.0" bin: @@ -12507,7 +12568,7 @@ __metadata: languageName: node linkType: hard -"once@npm:1.4.0, once@npm:^1.3.0, once@npm:^1.3.1, once@npm:^1.4.0": +"once@npm:^1.3.0, once@npm:^1.3.1, once@npm:^1.4.0": version: 1.4.0 resolution: "once@npm:1.4.0" dependencies: @@ -13409,7 +13470,7 @@ __metadata: languageName: node linkType: hard -"qs@npm:6.10.3, qs@npm:^6.10.3": +"qs@npm:6.10.3": version: 6.10.3 resolution: "qs@npm:6.10.3" dependencies: @@ -13418,12 +13479,12 @@ __metadata: languageName: node linkType: hard -"qs@npm:6.9.3": - version: 6.11.0 - resolution: "qs@npm:6.11.0" +"qs@npm:^6.11.0": + version: 6.12.1 + resolution: "qs@npm:6.12.1" dependencies: - side-channel: ^1.0.4 - checksum: 6e1f29dd5385f7488ec74ac7b6c92f4d09a90408882d0c208414a34dd33badc1a621019d4c799a3df15ab9b1d0292f97c1dd71dc7c045e69f81a8064e5af7297 + side-channel: ^1.0.6 + checksum: aa761d99e65b6936ba2dd2187f2d9976afbcda38deb3ff1b3fe331d09b0c578ed79ca2abdde1271164b5be619c521ec7db9b34c23f49a074e5921372d16242d5 languageName: node linkType: hard @@ -14328,6 +14389,17 @@ __metadata: languageName: node linkType: hard +"semver@npm:^7.3.8": + version: 7.6.0 + resolution: "semver@npm:7.6.0" + dependencies: + lru-cache: ^6.0.0 + bin: + semver: bin/semver.js + checksum: 7427f05b70786c696640edc29fdd4bc33b2acf3bbe1740b955029044f80575fc664e1a512e4113c3af21e767154a94b4aa214bf6cd6e42a1f6dba5914e0b208c + languageName: node + linkType: hard + "semver@npm:^7.5.1, semver@npm:^7.5.3, semver@npm:^7.5.4": version: 7.5.4 resolution: "semver@npm:7.5.4" @@ -14454,6 +14526,20 @@ __metadata: languageName: node linkType: hard +"set-function-length@npm:^1.2.1": + version: 1.2.2 + resolution: "set-function-length@npm:1.2.2" + dependencies: + define-data-property: ^1.1.4 + es-errors: ^1.3.0 + function-bind: ^1.1.2 + get-intrinsic: ^1.2.4 + gopd: ^1.0.1 + has-property-descriptors: ^1.0.2 + checksum: a8248bdacdf84cb0fab4637774d9fb3c7a8e6089866d04c817583ff48e14149c87044ce683d7f50759a8c50fb87c7a7e173535b06169c87ef76f5fb276dfff72 + languageName: node + linkType: hard + "set-function-name@npm:^2.0.0": version: 2.0.1 resolution: "set-function-name@npm:2.0.1" @@ -14565,6 +14651,18 @@ __metadata: languageName: node linkType: hard +"side-channel@npm:^1.0.6": + version: 1.0.6 + resolution: "side-channel@npm:1.0.6" + dependencies: + call-bind: ^1.0.7 + es-errors: ^1.3.0 + get-intrinsic: ^1.2.4 + object-inspect: ^1.13.1 + checksum: bfc1afc1827d712271453e91b7cd3878ac0efd767495fd4e594c4c2afaa7963b7b510e249572bfd54b0527e66e4a12b61b80c061389e129755f34c493aad9b97 + languageName: node + linkType: hard + "signal-exit@npm:^3.0.2, signal-exit@npm:^3.0.3": version: 3.0.6 resolution: "signal-exit@npm:3.0.6" @@ -15123,22 +15221,21 @@ __metadata: languageName: node linkType: hard -"superagent@npm:7": - version: 7.1.3 - resolution: "superagent@npm:7.1.3" +"superagent@npm:8": + version: 8.1.2 + resolution: "superagent@npm:8.1.2" dependencies: component-emitter: ^1.3.0 - cookiejar: ^2.1.3 + cookiejar: ^2.1.4 debug: ^4.3.4 fast-safe-stringify: ^2.1.1 form-data: ^4.0.0 - formidable: ^2.0.1 + formidable: ^2.1.2 methods: ^1.1.2 - mime: ^2.5.0 - qs: ^6.10.3 - readable-stream: ^3.6.0 - semver: ^7.3.7 - checksum: 436045d555d35c282de7bcba85102b1421470bdc80781c9a0b7ab7c639675b4eca026a71301974935f3de0d33782a0392274e24f3915335b81a78a04b48eeee5 + mime: 2.6.0 + qs: ^6.11.0 + semver: ^7.3.8 + checksum: f3601c5ccae34d5ba684a03703394b5d25931f4ae2e1e31a1de809f88a9400e997ece037f9accf148a21c408f950dc829db1e4e23576a7f9fe0efa79fd5c9d2f languageName: node linkType: hard