From 40ed53326044d1f1c86675d084ad5800c251df62 Mon Sep 17 00:00:00 2001 From: Beth Thomas Date: Tue, 4 Feb 2025 14:06:15 +0000 Subject: [PATCH 1/4] feat(DTFS2-7494): update amendment statuses --- libs/common/src/constants/amendments.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/libs/common/src/constants/amendments.ts b/libs/common/src/constants/amendments.ts index 18f3f555e5..dc28abd2da 100644 --- a/libs/common/src/constants/amendments.ts +++ b/libs/common/src/constants/amendments.ts @@ -19,8 +19,22 @@ export const PORTAL_AMENDMENT_STATUS = { * This is not displayed in the UI */ DRAFT: 'Draft', + /** + * Amendment has been submitted by the maker and is awaiting the checker's approval + */ + READY_FOR_CHECKERS_APPROVAL: "Ready for Checker's approval", + /** + * Checker has requested changes from the maker + */ + FURTHER_MAKERS_INPUT_REQUIRED: "Further Maker's input required", + /** + * Amendment has been acknowledged by the checker + */ + ACKNOWLEDGED: 'Acknowledged', } as const; +export const PORTAL_AMENDMENT_UNDERWAY_STATUSES = [PORTAL_AMENDMENT_STATUS.READY_FOR_CHECKERS_APPROVAL, PORTAL_AMENDMENT_STATUS.FURTHER_MAKERS_INPUT_REQUIRED]; + export const AMENDMENT_QUERIES = { LATEST_VALUE: 'latest-value', LATEST_COVER_END_DATE: 'latest-cover-end-date', From 7839b6feb80a8bd9dd9af71e3692d9843451800e Mon Sep 17 00:00:00 2001 From: Beth Thomas Date: Mon, 3 Feb 2025 16:01:47 +0000 Subject: [PATCH 2/4] feat(DTFS2-7494): add portal-api and gef-ui layer --- .../controllers/application-details/index.js | 7 +- .../application-details/index.test.js | 25 +++- gef-ui/server/services/__mocks__/api.js | 1 + gef-ui/server/services/__tests__/api.test.js | 35 ++++- gef-ui/server/services/api.js | 24 ++++ .../utils/facility-amendments.helper.ts | 4 +- .../v1/gef/amendments/amendment-urls.ts | 7 + .../amendmentsOnDeal.get.api-test.ts | 108 ++++++++++++++++ portal-api/src/v1/api.js | 23 ++++ .../get-amendments-on-deal.controller.ts | 45 +++++++ .../amendments/get-amendments-on-deal.test.ts | 120 ++++++++++++++++++ portal-api/src/v1/gef/routes.js | 3 + 12 files changed, 396 insertions(+), 6 deletions(-) create mode 100644 portal-api/api-tests/v1/gef/amendments/amendmentsOnDeal.get.api-test.ts create mode 100644 portal-api/src/v1/controllers/amendments/get-amendments-on-deal.controller.ts create mode 100644 portal-api/src/v1/controllers/amendments/get-amendments-on-deal.test.ts diff --git a/gef-ui/server/controllers/application-details/index.js b/gef-ui/server/controllers/application-details/index.js index 44dbd2c1c4..5e175cf275 100644 --- a/gef-ui/server/controllers/application-details/index.js +++ b/gef-ui/server/controllers/application-details/index.js @@ -1,5 +1,5 @@ const startCase = require('lodash/startCase'); -const { DEAL_TYPE, timeZoneConfig, DEAL_STATUS } = require('@ukef/dtfs2-common'); +const { DEAL_TYPE, timeZoneConfig, DEAL_STATUS, PORTAL_AMENDMENT_UNDERWAY_STATUSES } = require('@ukef/dtfs2-common'); const api = require('../../services/api'); const { canUpdateUnissuedFacilitiesCheck } = require('./canUpdateUnissuedFacilitiesCheck'); const { @@ -264,7 +264,10 @@ const applicationDetails = async (req, res, next) => { params.link += '/unissued-facilities'; } - params.canIssuedFacilitiesBeAmended = canUserAmendIssuedFacilities(application.submissionType, application.status, userRoles); + const amendmentsUnderwayOnDeal = await api.getAmendmentsOnDeal({ dealId, statuses: PORTAL_AMENDMENT_UNDERWAY_STATUSES, userToken }); + + params.canIssuedFacilitiesBeAmended = + canUserAmendIssuedFacilities(application.submissionType, application.status, userRoles) && !amendmentsUnderwayOnDeal.length; return res.render(`partials/${partial}.njk`, params); } catch (error) { diff --git a/gef-ui/server/controllers/application-details/index.test.js b/gef-ui/server/controllers/application-details/index.test.js index b8f9fcd852..a99690de98 100644 --- a/gef-ui/server/controllers/application-details/index.test.js +++ b/gef-ui/server/controllers/application-details/index.test.js @@ -1,5 +1,6 @@ import { cloneDeep } from 'lodash'; import { DEAL_STATUS, DEAL_SUBMISSION_TYPE, DEAL_TYPE, FACILITY_TYPE, isPortalFacilityAmendmentsFeatureFlagEnabled, ROLES } from '@ukef/dtfs2-common'; +import { aPortalFacilityAmendment } from '@ukef/dtfs2-common/mock-data-backend'; import { applicationDetails, postApplicationDetails } from '.'; import api from '../../services/api'; import { NON_MAKER_ROLES } from '../../../test-helpers/common-role-lists'; @@ -23,6 +24,7 @@ describe('controllers/application-details', () => { let mockFacilityResponse; let mockFacilitiesResponse; let mockUserResponse; + const mockGetAmendmentsOnDealResponse = []; beforeEach(() => { mockResponse = MOCKS.MockResponse(); @@ -35,6 +37,7 @@ describe('controllers/application-details', () => { api.getApplication.mockResolvedValue(mockApplicationResponse); api.getFacilities.mockResolvedValue(mockFacilitiesResponse); api.getUserDetails.mockResolvedValue(mockUserResponse); + api.getAmendmentsOnDeal.mockResolvedValue(mockGetAmendmentsOnDealResponse); mockRequest.flash = mockSuccessfulFlashResponse(); }); @@ -334,7 +337,7 @@ describe('controllers/application-details', () => { ); }); - it(`renders 'application-preview' with canIssuedFacilitiesBeAmended=true when the deal status is ${DEAL_STATUS.UKEF_ACKNOWLEDGED} and the submission type is valid`, async () => { + it(`renders 'application-preview' with canIssuedFacilitiesBeAmended=true when the deal status is ${DEAL_STATUS.UKEF_ACKNOWLEDGED}, the submission type is valid and there are no amendments underway on the deal`, async () => { api.getFacilities.mockResolvedValue(MOCKS.MockFacilityResponseNotChangedIssued); jest.mocked(isPortalFacilityAmendmentsFeatureFlagEnabled).mockReturnValueOnce(true); @@ -429,6 +432,26 @@ describe('controllers/application-details', () => { ); }); + it(`renders 'application-preview' with canIssuedFacilitiesBeAmended=false when there is an amendment already underway on the deal`, async () => { + api.getFacilities.mockResolvedValue(MOCKS.MockFacilityResponseNotChangedIssued); + jest.mocked(isPortalFacilityAmendmentsFeatureFlagEnabled).mockReturnValueOnce(true); + + api.getAmendmentsOnDeal.mockResolvedValueOnce([aPortalFacilityAmendment()]); + + mockApplicationResponse.status = DEAL_STATUS.UKEF_ACKNOWLEDGED; + mockApplicationResponse.submissionType = DEAL_SUBMISSION_TYPE.AIN; + api.getApplication.mockResolvedValueOnce(mockApplicationResponse); + + await applicationDetails(mockRequest, mockResponse); + + expect(mockResponse.render).toHaveBeenCalledWith( + 'partials/application-preview.njk', + expect.objectContaining({ + canIssuedFacilitiesBeAmended: false, + }), + ); + }); + it('renders `review-decision` when page requested is `review-decision`', async () => { mockApplicationResponse.status = CONSTANTS.DEAL_STATUS.UKEF_APPROVED_WITHOUT_CONDITIONS; api.getApplication.mockResolvedValueOnce(mockApplicationResponse); diff --git a/gef-ui/server/services/__mocks__/api.js b/gef-ui/server/services/__mocks__/api.js index c6be2598b4..61bd6d3ccd 100644 --- a/gef-ui/server/services/__mocks__/api.js +++ b/gef-ui/server/services/__mocks__/api.js @@ -16,4 +16,5 @@ module.exports = { getAmendment: jest.fn(), upsertAmendment: jest.fn(), updateAmendment: jest.fn(), + getAmendmentsOnDeal: jest.fn(), }; diff --git a/gef-ui/server/services/__tests__/api.test.js b/gef-ui/server/services/__tests__/api.test.js index d4d11fed2c..a2cdc78b04 100644 --- a/gef-ui/server/services/__tests__/api.test.js +++ b/gef-ui/server/services/__tests__/api.test.js @@ -1,5 +1,5 @@ import { AxiosError, HttpStatusCode } from 'axios'; -import { InvalidDealIdError, InvalidFacilityIdError, MOCK_COMPANY_REGISTRATION_NUMBERS } from '@ukef/dtfs2-common'; +import { InvalidDealIdError, InvalidFacilityIdError, MOCK_COMPANY_REGISTRATION_NUMBERS, PORTAL_AMENDMENT_STATUS } from '@ukef/dtfs2-common'; import Axios from '../axios'; import api from '../api'; import CONSTANTS from '../../constants'; @@ -494,3 +494,36 @@ describe('updateAmendment()', () => { await expect(returned).rejects.toThrow(InvalidFacilityIdError); }); }); + +describe('getAmendmentsOnDeal()', () => { + it(`should return the found amendments`, async () => { + // Arrange + const mockAmendment = new PortalFacilityAmendmentWithUkefIdMockBuilder().build(); + Axios.get.mockReturnValue(Promise.resolve({ data: [mockAmendment] })); + + // Act + const response = await api.getAmendmentsOnDeal({ dealId: validMongoId, userToken }); + + // Assert + expect(response).toEqual([mockAmendment]); + }); + + it('should throw an error if there is an api error', async () => { + // Arrange + Axios.get.mockReturnValue(Promise.reject(new AxiosError())); + + // Act + const returned = api.getAmendmentsOnDeal({ dealId: validMongoId, statuses: [PORTAL_AMENDMENT_STATUS.DRAFT], userToken }); + + // Assert + await expect(returned).rejects.toThrow(AxiosError); + }); + + it.each(invalidMongoIdTestCases)('should throw an error when given an invalid facility Id', async (invalidMongoId) => { + // Act + const returned = api.getAmendmentsOnDeal({ dealId: invalidMongoId, userToken }); + + // Assert + await expect(returned).rejects.toThrow(InvalidDealIdError); + }); +}); diff --git a/gef-ui/server/services/api.js b/gef-ui/server/services/api.js index 6136e97b82..c61aab5a54 100644 --- a/gef-ui/server/services/api.js +++ b/gef-ui/server/services/api.js @@ -383,6 +383,29 @@ const getAmendment = async ({ facilityId, amendmentId, userToken }) => { } }; +/** + * @param {Object} param + * @param {string} param.dealId + * @param {string} param.userToken + * @param {import('@ukef/dtfs2-common').PortalAmendmentStatus[] | undefined} param.statuses + * @returns {Promise<(import('@ukef/dtfs2-common').PortalFacilityAmendmentWithUkefId[])>}>} + */ +const getAmendmentsOnDeal = async ({ dealId, userToken, statuses }) => { + if (!isValidMongoId(dealId)) { + console.error('Invalid deal ID %s', dealId); + throw new InvalidDealIdError(dealId); + } + + try { + const response = await Axios.get(`/gef/deals/${dealId}/amendments`, { ...config(userToken), params: { statuses } }); + + return response.data; + } catch (error) { + console.error('Failed to get the amendments for facilities on deal with id %s: %o', dealId, error); + throw error; + } +}; + /** * @param {Object} param * @param {string} param.facilityId @@ -470,6 +493,7 @@ module.exports = { deleteFile, downloadFile, updateSupportingInformation, + getAmendmentsOnDeal, getAmendment, upsertAmendment, updateAmendment, diff --git a/gef-ui/server/utils/facility-amendments.helper.ts b/gef-ui/server/utils/facility-amendments.helper.ts index ad9bccb6df..8ceadb7c86 100644 --- a/gef-ui/server/utils/facility-amendments.helper.ts +++ b/gef-ui/server/utils/facility-amendments.helper.ts @@ -11,7 +11,7 @@ import { Deal } from '../types/deal'; import { Facility } from '../types/facility'; /** - * returns a boolean indicating whether the user can amend issued facilities based on the submission type, deal status and user roles. + * returns a boolean indicating whether the user can amend issued facilities in general for a deal based on the deal submission type, deal status and user roles. * @param submissionType - the submission type * @param dealStatus - the deal status * @param userRoles - a list of the user roles @@ -27,7 +27,7 @@ export const canUserAmendIssuedFacilities = (submissionType: DealSubmissionType, }; /** - * Determines if a user can amend a facility. + * Determines if a user can amend a given facility. * * @param facility - The facility to check. * @param deal - The deal associated with the facility. diff --git a/portal-api/api-tests/v1/gef/amendments/amendment-urls.ts b/portal-api/api-tests/v1/gef/amendments/amendment-urls.ts index 8b3f9566d1..2d367655ae 100644 --- a/portal-api/api-tests/v1/gef/amendments/amendment-urls.ts +++ b/portal-api/api-tests/v1/gef/amendments/amendment-urls.ts @@ -1,3 +1,5 @@ +import { PortalAmendmentStatus } from '@ukef/dtfs2-common'; + export const getAmendmentUrl = ({ facilityId, amendmentId }: { facilityId: string; amendmentId: string }) => `/v1/gef/facilities/${facilityId}/amendments/${amendmentId}`; @@ -5,3 +7,8 @@ export const patchAmendmentUrl = ({ facilityId, amendmentId }: { facilityId: str `/v1/gef/facilities/${facilityId}/amendments/${amendmentId}`; export const putAmendmentUrl = ({ facilityId }: { facilityId: string }) => `/v1/gef/facilities/${facilityId}/amendments`; + +export const getAmendmentsOnDealUrl = ({ dealId, statuses }: { dealId: string; statuses?: PortalAmendmentStatus[] }) => { + const statusFilterQuery = statuses ? `?statuses=${statuses.map((item) => item.replace(/ /g, '%20')).join(',')}` : ''; + return `/v1/gef/deals/${dealId}/amendments/${statusFilterQuery}`; +}; diff --git a/portal-api/api-tests/v1/gef/amendments/amendmentsOnDeal.get.api-test.ts b/portal-api/api-tests/v1/gef/amendments/amendmentsOnDeal.get.api-test.ts new file mode 100644 index 0000000000..2a5578d427 --- /dev/null +++ b/portal-api/api-tests/v1/gef/amendments/amendmentsOnDeal.get.api-test.ts @@ -0,0 +1,108 @@ +import { ObjectId } from 'mongodb'; +import { PORTAL_AMENDMENT_STATUS, AMENDMENT_TYPES, AnyObject, PortalFacilityAmendmentWithUkefId, Role } from '@ukef/dtfs2-common'; +import { HttpStatusCode } from 'axios'; +import app from '../../../../src/createApp'; +import testUserCache from '../../../api-test-users'; + +import ROLES, { MAKER } from '../../../../src/v1/roles/roles'; +import { getAmendmentsOnDealUrl } from './amendment-urls'; +import createApi from '../../../api'; +import { TestUser } from '../../../types/test-user'; +import { withRoleAuthorisationTests } from '../../../common-tests/role-authorisation-tests'; +import { withClientAuthenticationTests } from '../../../common-tests/client-authentication-tests'; + +const { as, get } = createApi(app); + +const getPortalFacilityAmendmentsOnDealMock = jest.fn() as jest.Mock>; + +jest.mock('../../../../src/v1/api', () => ({ + ...jest.requireActual('../../../../src/v1/api'), + getPortalFacilityAmendmentsOnDeal: () => getPortalFacilityAmendmentsOnDealMock(), +})); + +const validDealId = new ObjectId().toString(); +const statuses = [PORTAL_AMENDMENT_STATUS.DRAFT, PORTAL_AMENDMENT_STATUS.READY_FOR_CHECKERS_APPROVAL]; + +const invalidId = 'invalid-id'; + +describe('/v1/gef/deals/:dealId/amendments', () => { + let testUsers: Awaited>; + let aMaker: TestUser; + + describe('GET /v1/gef/deals/:dealId/amendments', () => { + beforeEach(() => { + jest.resetAllMocks(); + }); + + beforeAll(async () => { + testUsers = await testUserCache.initialise(app); + aMaker = testUsers().withRole(MAKER).one() as TestUser; + }); + + afterAll(() => { + jest.resetAllMocks(); + }); + + afterAll(() => { + jest.resetAllMocks(); + }); + + withClientAuthenticationTests({ + makeRequestWithoutAuthHeader: () => get(getAmendmentsOnDealUrl({ dealId: validDealId })), + makeRequestWithAuthHeader: (authHeader: string) => + get(getAmendmentsOnDealUrl({ dealId: validDealId, statuses }), { headers: { Authorization: authHeader } }), + }); + + withRoleAuthorisationTests({ + allowedRoles: Object.values(ROLES), + getUserWithRole: (role: Role) => testUsers().withRole(role).one() as TestUser, + makeRequestAsUser: (user: TestUser) => as(user).get(getAmendmentsOnDealUrl({ dealId: validDealId, statuses })), + successStatusCode: HttpStatusCode.Ok, + }); + + it(`should return a ${HttpStatusCode.BadRequest} response when deal id path param is invalid`, async () => { + // Arrange + const url = getAmendmentsOnDealUrl({ dealId: invalidId, statuses }); + + // Act + const response = await as(aMaker).get(url); + + // Assert + expect(response.status).toEqual(HttpStatusCode.BadRequest); + }); + + it(`should return a ${HttpStatusCode.Ok} response and the amendment for an authenticated user`, async () => { + const amendmentId = new ObjectId().toString(); + const facilityId = new ObjectId().toString(); + const dealId = new ObjectId().toString(); + + // Arrange + const amendment: PortalFacilityAmendmentWithUkefId = { + amendmentId, + facilityId, + dealId, + type: AMENDMENT_TYPES.PORTAL, + ukefFacilityId: '123', + createdAt: 1702061978881, + updatedAt: 1702061978881, + status: PORTAL_AMENDMENT_STATUS.DRAFT, + eligibilityCriteria: { version: 1, criteria: [] }, + createdBy: { + username: aMaker.username, + name: aMaker.firstname, + email: aMaker.email, + }, + }; + + jest.mocked(getPortalFacilityAmendmentsOnDealMock).mockResolvedValue([amendment]); + const url = getAmendmentsOnDealUrl({ dealId, statuses }); + + // Act + const response = await as(aMaker).get(url); + + // Assert + expect(response.status).toEqual(HttpStatusCode.Ok); + expect(response.body).toEqual([amendment]); + }); + }); +}); diff --git a/portal-api/src/v1/api.js b/portal-api/src/v1/api.js index 9ef53ddfc1..97c3f9f384 100644 --- a/portal-api/src/v1/api.js +++ b/portal-api/src/v1/api.js @@ -624,6 +624,28 @@ const getPortalFacilityAmendment = async (facilityId, amendmentId) => { } }; +/** + * Gets portal facility amendments on the deal filtered by status + * @param {string} dealId - id of the facility to amend + * @param {import('@ukef/dtfs2-common').PortalAmendmentStatus[] | undefined} statuses - an optional array of statuses to filter the amendments by + * @returns {Promise<(import('@ukef/dtfs2-common').PortalFacilityAmendmentWithUkefId[])>} - the amendments on the deal with a matching status + */ +const getPortalFacilityAmendmentsOnDeal = async (dealId, statuses) => { + try { + const response = await axios({ + method: 'get', + url: `${DTFS_CENTRAL_API_URL}/v1/portal/deals/${dealId}/amendments`, + params: { statuses }, + headers: headers.central, + }); + + return response.data; + } catch (error) { + console.error('Error getting portal facility amendments on deal with id %s: %o', dealId, error); + throw error; + } +}; + /** * Upserts a draft amendment for a portal facility in the database. * @param {Object} params @@ -751,6 +773,7 @@ module.exports = { saveFeeRecordCorrection, getFeeRecordCorrectionTransientFormData, getPortalFacilityAmendment, + getPortalFacilityAmendmentsOnDeal, putPortalFacilityAmendment, getFeeRecordCorrectionReview, patchPortalFacilityAmendment, diff --git a/portal-api/src/v1/controllers/amendments/get-amendments-on-deal.controller.ts b/portal-api/src/v1/controllers/amendments/get-amendments-on-deal.controller.ts new file mode 100644 index 0000000000..992cd16818 --- /dev/null +++ b/portal-api/src/v1/controllers/amendments/get-amendments-on-deal.controller.ts @@ -0,0 +1,45 @@ +import { HttpStatusCode } from 'axios'; +import { Response } from 'express'; +import { ApiError, CustomExpressRequest, PortalAmendmentStatus } from '@ukef/dtfs2-common'; +import api from '../../api'; + +export type GetFacilityAmendmentsOnDealRequest = CustomExpressRequest<{ + params: { + dealId: string; + }; + query: { + statuses: PortalAmendmentStatus[]; + }; +}>; + +/** + * Get the portal facility amendment + * @param req - The request object + * @param res - The response object + */ +export const getFacilityAmendmentsOnDeal = async (req: GetFacilityAmendmentsOnDealRequest, res: Response) => { + const { dealId } = req.params; + const { statuses } = req.query; + + try { + const amendments = await api.getPortalFacilityAmendmentsOnDeal(dealId, statuses); + + return res.status(HttpStatusCode.Ok).send(amendments); + } catch (error) { + const errorMessage = 'Failed to get the portal amendments for the given deal'; + console.error(errorMessage, error); + + if (error instanceof ApiError) { + return res.status(error.status).send({ + status: error.status, + message: `${errorMessage}: ${error.message}`, + code: error.code, + }); + } + + return res.status(HttpStatusCode.InternalServerError).send({ + status: HttpStatusCode.InternalServerError, + message: errorMessage, + }); + } +}; diff --git a/portal-api/src/v1/controllers/amendments/get-amendments-on-deal.test.ts b/portal-api/src/v1/controllers/amendments/get-amendments-on-deal.test.ts new file mode 100644 index 0000000000..d2da5bbfd1 --- /dev/null +++ b/portal-api/src/v1/controllers/amendments/get-amendments-on-deal.test.ts @@ -0,0 +1,120 @@ +import { ObjectId } from 'mongodb'; +import httpMocks from 'node-mocks-http'; +import { HttpStatusCode } from 'axios'; +import { PORTAL_AMENDMENT_STATUS, AMENDMENT_TYPES, PortalFacilityAmendmentWithUkefId, TestApiError, aPortalSessionUser } from '@ukef/dtfs2-common'; +import api from '../../api'; +import { getFacilityAmendmentsOnDeal, GetFacilityAmendmentsOnDealRequest } from './get-amendments-on-deal.controller'; + +jest.mock('../../api'); + +const dealId = new ObjectId().toString(); +const amendmentId = new ObjectId().toString(); +const facilityId = new ObjectId().toString(); + +const statuses = [PORTAL_AMENDMENT_STATUS.DRAFT, PORTAL_AMENDMENT_STATUS.ACKNOWLEDGED]; + +describe('controllers - facility amendment', () => { + beforeEach(() => { + jest.resetAllMocks(); + }); + + describe('GET - getAmendment', () => { + it('should call api.getPortalFacilityAmendmentsOnDeal with just the dealId if no statuses are queried', async () => { + // Arrange + const { req, res } = httpMocks.createMocks({ + params: { dealId }, + }); + + // Act + await getFacilityAmendmentsOnDeal(req, res); + + // Assert + expect(api.getPortalFacilityAmendmentsOnDeal).toHaveBeenCalledTimes(1); + expect(api.getPortalFacilityAmendmentsOnDeal).toHaveBeenCalledWith(dealId, undefined); + }); + + it('should call api.getPortalFacilityAmendmentsOnDeal with the dealId and statuses filter', async () => { + // Arrange + const { req, res } = httpMocks.createMocks({ + params: { dealId }, + query: { statuses }, + }); + + // Act + await getFacilityAmendmentsOnDeal(req, res); + + // Assert + expect(api.getPortalFacilityAmendmentsOnDeal).toHaveBeenCalledTimes(1); + expect(api.getPortalFacilityAmendmentsOnDeal).toHaveBeenCalledWith(dealId, statuses); + }); + + it(`should respond with ${HttpStatusCode.Ok} and return the amendments`, async () => { + // Arrange + const mockPortalAmendment: PortalFacilityAmendmentWithUkefId = { + amendmentId, + facilityId, + type: AMENDMENT_TYPES.PORTAL, + ukefFacilityId: '123', + dealId, + createdAt: 1702061978881, + updatedAt: 1702061978881, + status: PORTAL_AMENDMENT_STATUS.DRAFT, + eligibilityCriteria: { version: 1, criteria: [] }, + createdBy: { + username: aPortalSessionUser().username, + name: aPortalSessionUser().firstname, + email: aPortalSessionUser().email, + }, + }; + + jest.mocked(api.getPortalFacilityAmendmentsOnDeal).mockResolvedValue([mockPortalAmendment]); + const { req, res } = httpMocks.createMocks({ + params: { dealId }, + query: { statuses }, + }); + + // Act + await getFacilityAmendmentsOnDeal(req, res); + + // Assert + expect(res._getStatusCode()).toEqual(HttpStatusCode.Ok); + expect(res._getData()).toEqual([mockPortalAmendment]); + }); + + it('should return an error when there is an API error', async () => { + const testErrorStatus = HttpStatusCode.ExpectationFailed; + const testApiErrorMessage = 'test api error message'; + jest.mocked(api.getPortalFacilityAmendmentsOnDeal).mockRejectedValue(new TestApiError({ status: testErrorStatus, message: testApiErrorMessage })); + + // Arrange + const { req, res } = httpMocks.createMocks({ + params: { dealId }, + query: { statuses }, + }); + + // Act + await getFacilityAmendmentsOnDeal(req, res); + + // Assert + expect(res._getStatusCode()).toEqual(testErrorStatus); + expect(res._getData()).toEqual({ message: `Failed to get the portal amendments for the given deal: ${testApiErrorMessage}`, status: testErrorStatus }); + }); + + it('should return an error when there is a general error', async () => { + jest.mocked(api.getPortalFacilityAmendmentsOnDeal).mockRejectedValue(new Error('Some error')); + + // Arrange + const { req, res } = httpMocks.createMocks({ + params: { dealId }, + query: { statuses }, + }); + + // Act + await getFacilityAmendmentsOnDeal(req, res); + + // Assert + expect(res._getStatusCode()).toEqual(HttpStatusCode.InternalServerError); + expect(res._getData()).toEqual({ message: 'Failed to get the portal amendments for the given deal', status: HttpStatusCode.InternalServerError }); + }); + }); +}); diff --git a/portal-api/src/v1/gef/routes.js b/portal-api/src/v1/gef/routes.js index 3fe83e5ecd..d4be315802 100644 --- a/portal-api/src/v1/gef/routes.js +++ b/portal-api/src/v1/gef/routes.js @@ -16,6 +16,7 @@ const externalApi = require('./controllers/externalApi.controller'); const files = require('./controllers/files.controller'); const companies = require('../controllers/companies.controller'); const { getAmendment } = require('../controllers/amendments/get-amendment.controller'); +const { getFacilityAmendmentsOnDeal } = require('../controllers/amendments/get-amendments-on-deal.controller'); const { patchAmendment } = require('../controllers/amendments/patch-amendment.controller'); const { putAmendment } = require('../controllers/amendments/put-amendment.controller'); const { handleExpressValidatorResult } = require('../validation/route-validators/express-validator-result-handler'); @@ -142,4 +143,6 @@ router putAmendment, ); +router.route('/deals/:dealId/amendments').all(mongoIdValidation('dealId'), handleExpressValidatorResult).get(getFacilityAmendmentsOnDeal); + module.exports = router; From 60377d9395b0df93a19fab3e6ee4af733843666b Mon Sep 17 00:00:00 2001 From: Beth Thomas Date: Thu, 6 Feb 2025 10:53:15 +0000 Subject: [PATCH 3/4] feat(DTFS2-7494): review markups --- gef-ui/server/services/__tests__/api.test.js | 4 ++-- portal-api/api-tests/v1/gef/amendments/amendment-urls.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/gef-ui/server/services/__tests__/api.test.js b/gef-ui/server/services/__tests__/api.test.js index a2cdc78b04..1fde1c85cd 100644 --- a/gef-ui/server/services/__tests__/api.test.js +++ b/gef-ui/server/services/__tests__/api.test.js @@ -499,7 +499,7 @@ describe('getAmendmentsOnDeal()', () => { it(`should return the found amendments`, async () => { // Arrange const mockAmendment = new PortalFacilityAmendmentWithUkefIdMockBuilder().build(); - Axios.get.mockReturnValue(Promise.resolve({ data: [mockAmendment] })); + Axios.get.mockResolvedValueOnce({ data: [mockAmendment] }); // Act const response = await api.getAmendmentsOnDeal({ dealId: validMongoId, userToken }); @@ -510,7 +510,7 @@ describe('getAmendmentsOnDeal()', () => { it('should throw an error if there is an api error', async () => { // Arrange - Axios.get.mockReturnValue(Promise.reject(new AxiosError())); + Axios.get.mockRejectedValueOnce(new AxiosError()); // Act const returned = api.getAmendmentsOnDeal({ dealId: validMongoId, statuses: [PORTAL_AMENDMENT_STATUS.DRAFT], userToken }); diff --git a/portal-api/api-tests/v1/gef/amendments/amendment-urls.ts b/portal-api/api-tests/v1/gef/amendments/amendment-urls.ts index 2d367655ae..a7fe0fe1e4 100644 --- a/portal-api/api-tests/v1/gef/amendments/amendment-urls.ts +++ b/portal-api/api-tests/v1/gef/amendments/amendment-urls.ts @@ -9,6 +9,6 @@ export const patchAmendmentUrl = ({ facilityId, amendmentId }: { facilityId: str export const putAmendmentUrl = ({ facilityId }: { facilityId: string }) => `/v1/gef/facilities/${facilityId}/amendments`; export const getAmendmentsOnDealUrl = ({ dealId, statuses }: { dealId: string; statuses?: PortalAmendmentStatus[] }) => { - const statusFilterQuery = statuses ? `?statuses=${statuses.map((item) => item.replace(/ /g, '%20')).join(',')}` : ''; + const statusFilterQuery = statuses ? `?statuses=${statuses.map((item) => encodeURI(item)).join(',')}` : ''; return `/v1/gef/deals/${dealId}/amendments/${statusFilterQuery}`; }; From 5ab3d755bf1fccfb19da2f2907f44c6749b69f4e Mon Sep 17 00:00:00 2001 From: Beth Thomas Date: Fri, 7 Feb 2025 14:30:52 +0000 Subject: [PATCH 4/4] feat(DTFS2-7494): review markups --- gef-ui/server/services/__tests__/api.test.js | 12 +++++++++--- .../gef/amendments/amendmentsOnDeal.get.api-test.ts | 13 ++++++++++--- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/gef-ui/server/services/__tests__/api.test.js b/gef-ui/server/services/__tests__/api.test.js index 1fde1c85cd..056318478c 100644 --- a/gef-ui/server/services/__tests__/api.test.js +++ b/gef-ui/server/services/__tests__/api.test.js @@ -1,5 +1,11 @@ import { AxiosError, HttpStatusCode } from 'axios'; -import { InvalidDealIdError, InvalidFacilityIdError, MOCK_COMPANY_REGISTRATION_NUMBERS, PORTAL_AMENDMENT_STATUS } from '@ukef/dtfs2-common'; +import { + InvalidDealIdError, + InvalidFacilityIdError, + MOCK_COMPANY_REGISTRATION_NUMBERS, + PORTAL_AMENDMENT_STATUS, + PORTAL_AMENDMENT_UNDERWAY_STATUSES, +} from '@ukef/dtfs2-common'; import Axios from '../axios'; import api from '../api'; import CONSTANTS from '../../constants'; @@ -498,11 +504,11 @@ describe('updateAmendment()', () => { describe('getAmendmentsOnDeal()', () => { it(`should return the found amendments`, async () => { // Arrange - const mockAmendment = new PortalFacilityAmendmentWithUkefIdMockBuilder().build(); + const mockAmendment = { ...new PortalFacilityAmendmentWithUkefIdMockBuilder().build(), status: PORTAL_AMENDMENT_STATUS.READY_FOR_CHECKERS_APPROVAL }; Axios.get.mockResolvedValueOnce({ data: [mockAmendment] }); // Act - const response = await api.getAmendmentsOnDeal({ dealId: validMongoId, userToken }); + const response = await api.getAmendmentsOnDeal({ dealId: validMongoId, userToken, statuses: PORTAL_AMENDMENT_UNDERWAY_STATUSES }); // Assert expect(response).toEqual([mockAmendment]); diff --git a/portal-api/api-tests/v1/gef/amendments/amendmentsOnDeal.get.api-test.ts b/portal-api/api-tests/v1/gef/amendments/amendmentsOnDeal.get.api-test.ts index 2a5578d427..13bb0df817 100644 --- a/portal-api/api-tests/v1/gef/amendments/amendmentsOnDeal.get.api-test.ts +++ b/portal-api/api-tests/v1/gef/amendments/amendmentsOnDeal.get.api-test.ts @@ -1,5 +1,12 @@ import { ObjectId } from 'mongodb'; -import { PORTAL_AMENDMENT_STATUS, AMENDMENT_TYPES, AnyObject, PortalFacilityAmendmentWithUkefId, Role } from '@ukef/dtfs2-common'; +import { + PORTAL_AMENDMENT_STATUS, + AMENDMENT_TYPES, + AnyObject, + PortalFacilityAmendmentWithUkefId, + Role, + PORTAL_AMENDMENT_UNDERWAY_STATUSES, +} from '@ukef/dtfs2-common'; import { HttpStatusCode } from 'axios'; import app from '../../../../src/createApp'; import testUserCache from '../../../api-test-users'; @@ -21,7 +28,7 @@ jest.mock('../../../../src/v1/api', () => ({ })); const validDealId = new ObjectId().toString(); -const statuses = [PORTAL_AMENDMENT_STATUS.DRAFT, PORTAL_AMENDMENT_STATUS.READY_FOR_CHECKERS_APPROVAL]; +const statuses = PORTAL_AMENDMENT_UNDERWAY_STATUSES; const invalidId = 'invalid-id'; @@ -85,7 +92,7 @@ describe('/v1/gef/deals/:dealId/amendments', () => { ukefFacilityId: '123', createdAt: 1702061978881, updatedAt: 1702061978881, - status: PORTAL_AMENDMENT_STATUS.DRAFT, + status: PORTAL_AMENDMENT_STATUS.READY_FOR_CHECKERS_APPROVAL, eligibilityCriteria: { version: 1, criteria: [] }, createdBy: { username: aMaker.username,