Skip to content

Commit 88ee288

Browse files
authored
feat(FN-3611): browser back button behaviour after correction request submitted (#4164)
1 parent fe2faaf commit 88ee288

File tree

26 files changed

+443
-67
lines changed

26 files changed

+443
-67
lines changed

dtfs-central-api/src/v1/controllers/utilisation-report-service/fee-record-correction/get-fee-record-correction-request-review.controller/index.test.ts

Lines changed: 54 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import {
66
RECORD_CORRECTION_REASON,
77
TestApiError,
88
UtilisationReportEntityMockBuilder,
9+
FEE_RECORD_STATUS,
10+
ERROR_KEY,
911
} from '@ukef/dtfs2-common';
1012
import { FeeRecordRepo } from '../../../../../repositories/fee-record-repo';
1113
import { aBank } from '../../../../../../test-helpers';
@@ -22,6 +24,12 @@ describe('get-fee-record-correction-request-review.controller', () => {
2224
const reportId = 3;
2325
const feeRecordId = 14;
2426
const userId = '123';
27+
const bankId = '12356';
28+
29+
const commonFeeRecord = FeeRecordEntityMockBuilder.forReport(new UtilisationReportEntityMockBuilder().withBankId(bankId).build())
30+
.withFacilityId('0011223344')
31+
.withExporter('Test company')
32+
.build();
2533

2634
const getHttpMocks = () =>
2735
httpMocks.createMocks<GetFeeRecordCorrectionRequestReviewRequest>({
@@ -32,6 +40,10 @@ describe('get-fee-record-correction-request-review.controller', () => {
3240
const findFormDataSpy = jest.spyOn(FeeRecordCorrectionRequestTransientFormDataRepo, 'findByUserIdAndFeeRecordId');
3341
const findBankSpy = jest.spyOn(BanksRepo, 'getBankById');
3442

43+
beforeEach(() => {
44+
findFeeRecordSpy.mockResolvedValue(commonFeeRecord);
45+
});
46+
3547
afterEach(() => {
3648
jest.resetAllMocks();
3749
});
@@ -76,22 +88,24 @@ describe('get-fee-record-correction-request-review.controller', () => {
7688

7789
it(`should respond with a ${HttpStatusCode.NotFound} when the bank cannot be found`, async () => {
7890
// Arrange
79-
const bankId = '1234567';
91+
const incorrectBankId = '1234567';
8092
const { req, res } = getHttpMocks();
8193

8294
findFormDataSpy.mockResolvedValue(new FeeRecordCorrectionRequestTransientFormDataEntityMockBuilder().build());
83-
findFeeRecordSpy.mockResolvedValue(FeeRecordEntityMockBuilder.forReport(new UtilisationReportEntityMockBuilder().withBankId(bankId).build()).build());
95+
findFeeRecordSpy.mockResolvedValue(
96+
FeeRecordEntityMockBuilder.forReport(new UtilisationReportEntityMockBuilder().withBankId(incorrectBankId).build()).build(),
97+
);
8498
findBankSpy.mockResolvedValue(null);
8599

86100
// Act
87101
await getFeeRecordCorrectionRequestReview(req, res);
88102

89103
// Assert
90104
expect(res._getStatusCode()).toEqual(HttpStatusCode.NotFound);
91-
const expectedErrorMessage = `Failed to find bank with id: ${bankId}`;
105+
const expectedErrorMessage = `Failed to find bank with id: ${incorrectBankId}`;
92106
expect(res._getData()).toEqual(`Failed to get fee record correction request review: ${expectedErrorMessage}`);
93107
expect(findBankSpy).toHaveBeenCalledTimes(1);
94-
expect(findBankSpy).toHaveBeenCalledWith(bankId);
108+
expect(findBankSpy).toHaveBeenCalledWith(incorrectBankId);
95109
});
96110

97111
it("should respond with the specific error status if fetching the review throws an 'ApiError'", async () => {
@@ -149,8 +163,6 @@ describe('get-fee-record-correction-request-review.controller', () => {
149163
});
150164

151165
describe('when the request is successful', () => {
152-
const bankId = '12356';
153-
154166
const formData = new FeeRecordCorrectionRequestTransientFormDataEntityMockBuilder()
155167
.withFormData({
156168
reasons: [RECORD_CORRECTION_REASON.FACILITY_ID_INCORRECT],
@@ -249,6 +261,42 @@ describe('get-fee-record-correction-request-review.controller', () => {
249261
expect(findBankSpy).toHaveBeenCalledTimes(1);
250262
expect(findBankSpy).toHaveBeenCalledWith(bankId);
251263
});
264+
265+
describe('when a record correction request is already submitted', () => {
266+
const pendingCorrectionFeeRecord = FeeRecordEntityMockBuilder.forReport(new UtilisationReportEntityMockBuilder().withBankId(bankId).build())
267+
.withFacilityId('0011223344')
268+
.withExporter('Test company')
269+
.withStatus(FEE_RECORD_STATUS.PENDING_CORRECTION)
270+
.build();
271+
272+
beforeEach(() => {
273+
findFormDataSpy.mockResolvedValue(formData);
274+
findFeeRecordSpy.mockResolvedValue(pendingCorrectionFeeRecord);
275+
findBankSpy.mockResolvedValue(bank);
276+
});
277+
278+
it(`should respond with a ${HttpStatusCode.Ok}`, async () => {
279+
// Arrange
280+
const { req, res } = getHttpMocks();
281+
282+
// Act
283+
await getFeeRecordCorrectionRequestReview(req, res);
284+
285+
// Assert
286+
expect(res._getStatusCode()).toEqual(HttpStatusCode.Ok);
287+
});
288+
289+
it(`should respond with an error key set as ${ERROR_KEY.INVALID_STATUS}`, async () => {
290+
// Arrange
291+
const { req, res } = getHttpMocks();
292+
293+
// Act
294+
await getFeeRecordCorrectionRequestReview(req, res);
295+
296+
// Assert
297+
expect(res._getData()).toEqual({ errorKey: ERROR_KEY.INVALID_STATUS });
298+
});
299+
});
252300
});
253301
});
254302
});

dtfs-central-api/src/v1/controllers/utilisation-report-service/fee-record-correction/get-fee-record-correction-request-review.controller/index.ts

Lines changed: 34 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { ApiError, CustomExpressRequest, RecordCorrectionReason, ReportPeriod, SessionBank } from '@ukef/dtfs2-common';
1+
import { ApiError, CustomExpressRequest, RecordCorrectionReason, ReportPeriod, SessionBank, FEE_RECORD_STATUS, ERROR_KEY } from '@ukef/dtfs2-common';
22
import { Response } from 'express';
33
import { HttpStatusCode } from 'axios';
44
import { getBankById } from '../../../../../repositories/banks-repo';
@@ -14,20 +14,26 @@ export type GetFeeRecordCorrectionRequestReviewRequest = CustomExpressRequest<{
1414
};
1515
}>;
1616

17+
const { INVALID_STATUS } = ERROR_KEY;
18+
19+
export type InvalidStatusType = typeof INVALID_STATUS;
20+
1721
/**
1822
* Response body type for the GET fee record correction request review route.
1923
*/
20-
export type GetFeeRecordCorrectionRequestReviewResponseBody = {
21-
bank: SessionBank;
22-
reportPeriod: ReportPeriod;
23-
correctionRequestDetails: {
24-
facilityId: string;
25-
exporter: string;
26-
reasons: RecordCorrectionReason[];
27-
additionalInfo: string;
28-
contactEmailAddresses: string[];
29-
};
30-
};
24+
export type GetFeeRecordCorrectionRequestReviewResponseBody =
25+
| {
26+
bank: SessionBank;
27+
reportPeriod: ReportPeriod;
28+
correctionRequestDetails: {
29+
facilityId: string;
30+
exporter: string;
31+
reasons: RecordCorrectionReason[];
32+
additionalInfo: string;
33+
contactEmailAddresses: string[];
34+
};
35+
}
36+
| { errorKey: InvalidStatusType };
3137

3238
type GetFeeRecordCorrectionRequestReviewResponse = Response<GetFeeRecordCorrectionRequestReviewResponseBody | string>;
3339

@@ -51,18 +57,28 @@ export const getFeeRecordCorrectionRequestReview = async (
5157
const reportId = Number(reportIdString);
5258
const feeRecordId = Number(feeRecordIdString);
5359

54-
const formDataEntity = await FeeRecordCorrectionRequestTransientFormDataRepo.findByUserIdAndFeeRecordId(userId, feeRecordId);
55-
56-
if (!formDataEntity) {
57-
throw new NotFoundError(`Failed to find fee record correction request transient form data with userId: ${userId} and feeRecordId: ${feeRecordId}`);
58-
}
59-
6060
const feeRecord = await FeeRecordRepo.findOneByIdAndReportIdWithReport(feeRecordId, reportId);
6161

6262
if (!feeRecord) {
6363
throw new NotFoundError(`Failed to find fee record with id: ${feeRecordId} and reportId: ${reportId}`);
6464
}
6565

66+
/**
67+
* if fee record status us PENDING_CORRECTION then it means record correction request has been submitted
68+
* should return errorKey if so
69+
*/
70+
if (feeRecord.status === FEE_RECORD_STATUS.PENDING_CORRECTION) {
71+
return res.status(HttpStatusCode.Ok).send({
72+
errorKey: ERROR_KEY.INVALID_STATUS,
73+
});
74+
}
75+
76+
const formDataEntity = await FeeRecordCorrectionRequestTransientFormDataRepo.findByUserIdAndFeeRecordId(userId, feeRecordId);
77+
78+
if (!formDataEntity) {
79+
throw new NotFoundError(`Failed to find fee record correction request transient form data with userId: ${userId} and feeRecordId: ${feeRecordId}`);
80+
}
81+
6682
const { report } = feeRecord;
6783
const { bankId } = report;
6884

dtfs-central-api/src/v1/routes/utilisation-reports-routes.js

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1091,8 +1091,13 @@ utilisationReportsRouter
10911091
* content:
10921092
* application/json:
10931093
* schema:
1094-
* type: object
1095-
* $ref: '#/definitions/FeeRecordCorrectionRequestReview'
1094+
* oneOf:
1095+
* - $ref: '#/definitions/FeeRecordCorrectionRequestReview'
1096+
* - type: object
1097+
* properties:
1098+
* errorKey:
1099+
* type: string
1100+
* description: Error key if the correction request has already been submitted
10961101
* 400:
10971102
* description: Bad request
10981103
* 404:
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import {
2+
FEE_RECORD_STATUS,
3+
FeeRecordEntityMockBuilder,
4+
PENDING_RECONCILIATION,
5+
UtilisationReportEntityMockBuilder,
6+
RECORD_CORRECTION_REASON,
7+
} from '@ukef/dtfs2-common';
8+
import pages from '../../../../pages';
9+
import USERS from '../../../../../fixtures/users';
10+
import { NODE_TASKS } from '../../../../../../../e2e-fixtures';
11+
import { getMatchingTfmFacilitiesForFeeRecords } from '../../../../../support/utils/getMatchingTfmFacilitiesForFeeRecords';
12+
import { mainHeading } from '../../../../partials';
13+
import relative from '../../../../relativeURL';
14+
15+
const bankId = '961';
16+
const reportId = 1;
17+
18+
const report = UtilisationReportEntityMockBuilder.forStatus(PENDING_RECONCILIATION).withId(reportId).withBankId(bankId).build();
19+
20+
const feeRecord = FeeRecordEntityMockBuilder.forReport(report).withId(1).withStatus(FEE_RECORD_STATUS.TO_DO).build();
21+
22+
const matchingTfmFacilities = getMatchingTfmFacilitiesForFeeRecords([feeRecord]);
23+
24+
const { utilisationReportPageNotFoundPage } = pages;
25+
26+
const additionalInfoUserInput = 'Some additional info';
27+
28+
context('Pressing the back button after a record correction request is submitted', () => {
29+
before(() => {
30+
cy.task(NODE_TASKS.DELETE_ALL_FROM_SQL_DB);
31+
cy.task(NODE_TASKS.REINSERT_ZERO_THRESHOLD_PAYMENT_MATCHING_TOLERANCES);
32+
33+
cy.task(NODE_TASKS.INSERT_TFM_FACILITIES_INTO_DB, matchingTfmFacilities);
34+
35+
cy.task(NODE_TASKS.REMOVE_ALL_UTILISATION_REPORTS_FROM_DB);
36+
cy.task(NODE_TASKS.REMOVE_ALL_FEE_RECORD_CORRECTION_TRANSIENT_FORM_DATA_FROM_DB);
37+
38+
cy.task(NODE_TASKS.INSERT_UTILISATION_REPORTS_INTO_DB, [report]);
39+
cy.task(NODE_TASKS.INSERT_FEE_RECORDS_INTO_DB, [feeRecord]);
40+
});
41+
42+
beforeEach(() => {
43+
pages.landingPage.visit();
44+
cy.login(USERS.PDC_RECONCILE);
45+
46+
// submit a record correction request
47+
cy.createAndSubmitFeeRecordCorrectionRequestForm({
48+
feeRecord,
49+
reportId,
50+
additionalInfoUserInput,
51+
reasons: [RECORD_CORRECTION_REASON.FACILITY_ID_INCORRECT, RECORD_CORRECTION_REASON.OTHER],
52+
});
53+
54+
// press browser back button
55+
cy.go('back');
56+
});
57+
58+
after(() => {
59+
cy.task(NODE_TASKS.DELETE_ALL_FROM_SQL_DB);
60+
cy.task(NODE_TASKS.REINSERT_ZERO_THRESHOLD_PAYMENT_MATCHING_TOLERANCES);
61+
});
62+
63+
it('should show the "Page not found" page when pressing the browser back button after a record correction request is submitted', () => {
64+
cy.assertText(mainHeading(), 'Page not found');
65+
cy.assertText(
66+
utilisationReportPageNotFoundPage.reason(),
67+
'The record correction request has been sent to the bank. You cannot make any changes to the request',
68+
);
69+
cy.assertText(utilisationReportPageNotFoundPage.returnToPremiumPaymentsButton(), 'Return to premium payments');
70+
71+
utilisationReportPageNotFoundPage.returnToPremiumPaymentsButton().click();
72+
73+
cy.url().should('eq', relative(`/utilisation-reports/${reportId}`));
74+
});
75+
});

e2e-tests/tfm/cypress/e2e/pages/utilisation-reports/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { utilisationReportAddToAnExistingPaymentPage } from './utilisationReport
99
import { createFeeRecordCorrectionRequestPage } from './fee-record-correction/createFeeRecordCorrectionRequestPage';
1010
import { checkFeeRecordCorrectionRequestPage } from './fee-record-correction/checkFeeRecordCorrectionRequestPage';
1111
import { feeRecordCorrectionRequestSentPage } from './fee-record-correction/feeRecordCorrectionRequestSentPage';
12+
import utilisationReportPageNotFoundPage from './pageNotFoundPage';
1213

1314
export const utilisationReportPages = {
1415
utilisationReportsSummaryPage,
@@ -23,4 +24,5 @@ export const utilisationReportPages = {
2324
createFeeRecordCorrectionRequestPage,
2425
checkFeeRecordCorrectionRequestPage,
2526
feeRecordCorrectionRequestSentPage,
27+
utilisationReportPageNotFoundPage,
2628
};
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
const pageNotFoundPage = {
2+
reason: () => cy.get(`[data-cy="reason"]`),
3+
returnToPremiumPaymentsButton: () => cy.get(`[data-cy="return-to-premium-payments-button"]`),
4+
};
5+
6+
export default pageNotFoundPage;

libs/common/src/constants/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,5 +33,6 @@ export * from './facility-fee-frequency';
3333
export * from './facility-day-count-basis';
3434
export * from './e2e-fixtures';
3535
export * from './regex';
36+
export * from './status';
3637

3738
export * as PAYLOAD_VERIFICATION from './payload-verification';

libs/common/src/constants/status.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export const ERROR_KEY = {
2+
INVALID_STATUS: 'INVALID_STATUS',
3+
};

libs/common/src/middleware/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
export * from './feature-flags';
22
export * from './create-validation-middleware-for-schema';
3+
export * from './set-no-store-cache-control';
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import request from 'supertest';
2+
import express, { Request, Response, NextFunction } from 'express';
3+
import { HttpStatusCode } from 'axios';
4+
import { setNoStoreCacheControl } from './set-no-store-cache-control';
5+
6+
const app = express();
7+
app.use(setNoStoreCacheControl);
8+
9+
afterEach(() => {
10+
jest.resetAllMocks();
11+
});
12+
13+
describe('set-no-store-cache-control', () => {
14+
it('should set Cache-Control headers correctly', async () => {
15+
const middleware = (req: Request, res: Response, next: NextFunction) => {
16+
setNoStoreCacheControl(req, res, next);
17+
};
18+
19+
const next = jest.fn((_req: Request, res: Response) => res.send());
20+
21+
const testApp = express();
22+
testApp.use(middleware);
23+
testApp.get('/', next);
24+
25+
const response = await request(testApp).get('/');
26+
27+
expect(response.headers['cache-control']).toEqual('no-cache, no-store, must-revalidate, max-age=0');
28+
expect(response.status).toEqual(HttpStatusCode.Ok);
29+
expect(next).toHaveBeenCalled();
30+
});
31+
});
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { Request, Response, NextFunction } from 'express';
2+
3+
/**
4+
* middleware to set Cache-Control to no-store, no-cache, must-revalidate and max-age=0
5+
* stops cached version of a page being rendered when going back to a page (using the browser back button)
6+
* ensures the get controller for the page is called
7+
* @param _req
8+
* @param res
9+
* @param next
10+
*/
11+
export const setNoStoreCacheControl = (_req: Request, res: Response, next: NextFunction) => {
12+
res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate, max-age=0');
13+
14+
next();
15+
};
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { RECORD_CORRECTION_REASON } from '../../constants';
2+
3+
export const feeRecordCorrectionRequestReviewResponseBodyMock = {
4+
bank: { id: '123', name: 'Test bank' },
5+
reportPeriod: {
6+
start: { month: 1, year: 2024 },
7+
end: { month: 1, year: 2024 },
8+
},
9+
correctionRequestDetails: {
10+
facilityId: '0012345678',
11+
exporter: 'A sample exporter',
12+
reasons: [RECORD_CORRECTION_REASON.FACILITY_ID_INCORRECT],
13+
additionalInfo: 'this is the reason',
14+
contactEmailAddresses: ['test@test.com'],
15+
},
16+
};

libs/common/src/test-helpers/mock-data/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,5 @@ export * from './upsert-tfm-user-request';
1414
export * from './record-correction-mock';
1515
export * from './fee-record-correction-request-transient-form-data.entity.mock-builder';
1616
export * from './fee-record-correction-transient-form-data.entity.mock-builder';
17+
export * from './fee-record-correction-request-review-response-body-mock';
1718
export * from './record-correction-values';

0 commit comments

Comments
 (0)