From acac5e4485598894dfc5076b1b8b23e32a24308d Mon Sep 17 00:00:00 2001 From: Beth Thomas Date: Fri, 17 Jan 2025 15:36:10 +0000 Subject: [PATCH 01/19] feat(DTFS2-7498): add eligibility criteria to initial draft amendment --- .../eligibility-criteria-amendments.repo.ts | 29 +++++++++++++++++++ .../portal/facility-amendment.service.ts | 7 +++++ .../amendments/eligibility.component-test.ts | 7 +++-- .../eligibility-criteria/get-eligibility.ts | 8 ++++- .../amendments/eligibility-view-model.ts | 10 ++----- .../src/constants/mongo-db-collections.ts | 1 + .../eligibility-criteria-not-found.error.ts | 11 +++++++ libs/common/src/errors/index.ts | 1 + libs/common/src/schemas/portal-amendment.ts | 13 +++++++++ .../amendments-eligibility-criteria.ts | 21 ++++++++++++++ .../common/src/types/mongo-db-models/index.ts | 1 + .../types/mongo-db-models/tfm-facilities.ts | 11 +++++++ 12 files changed, 108 insertions(+), 12 deletions(-) create mode 100644 dtfs-central-api/src/repositories/portal/eligibility-criteria-amendments.repo.ts create mode 100644 libs/common/src/errors/eligibility-criteria-not-found.error.ts create mode 100644 libs/common/src/types/mongo-db-models/amendments-eligibility-criteria.ts diff --git a/dtfs-central-api/src/repositories/portal/eligibility-criteria-amendments.repo.ts b/dtfs-central-api/src/repositories/portal/eligibility-criteria-amendments.repo.ts new file mode 100644 index 0000000000..bf9890416e --- /dev/null +++ b/dtfs-central-api/src/repositories/portal/eligibility-criteria-amendments.repo.ts @@ -0,0 +1,29 @@ +import { Collection, WithoutId } from 'mongodb'; +import { MONGO_DB_COLLECTIONS, AmendmentsEligibilityCriteria, EligibilityCriteriaNotFoundError, FacilityType } from '@ukef/dtfs2-common'; +import { mongoDbClient } from '../../drivers/db-client'; + +export class EligibilityCriteriaAmendmentsRepo { + private static async getCollection(): Promise>> { + return await mongoDbClient.getCollection(MONGO_DB_COLLECTIONS.ELIGIBILITY_CRITERIA_AMENDMENTS); + } + + /** + * @param facilityType the facility type + * @returns The latest portal amendments eligibility criteria for the given facility type + */ + public static async findLatestEligibilityCriteria(facilityType: FacilityType): Promise { + const collection = await this.getCollection(); + + const [latestEligibilityCriteria] = await collection + .find({ $and: [{ facilityType }, { isInDraft: { $eq: false } }] }) + .sort({ version: -1 }) + .limit(1) + .toArray(); + + if (!latestEligibilityCriteria) { + throw new EligibilityCriteriaNotFoundError(); + } + + return latestEligibilityCriteria; + } +} diff --git a/dtfs-central-api/src/services/portal/facility-amendment.service.ts b/dtfs-central-api/src/services/portal/facility-amendment.service.ts index de8b8f4ff9..6c24d3242a 100644 --- a/dtfs-central-api/src/services/portal/facility-amendment.service.ts +++ b/dtfs-central-api/src/services/portal/facility-amendment.service.ts @@ -11,6 +11,8 @@ import { import { ObjectId } from 'mongodb'; import { findOneUser } from '../../v1/controllers/user/get-user.controller'; import { TfmFacilitiesRepo } from '../../repositories/tfm-facilities-repo'; +import { EligibilityCriteriaAmendmentsRepo } from '../../repositories/portal/eligibility-criteria-amendments.repo'; +import { findOneFacility } from '../../v1/controllers/portal/facility/get-facility.controller'; export class PortalFacilityAmendmentService { /** @@ -39,6 +41,10 @@ export class PortalFacilityAmendmentService { throw new InvalidAuditDetailsError(`Supplied auditDetails 'id' ${auditDetails.id.toString()} does not correspond to a valid user`); } + const { type: facilityType } = await findOneFacility(facilityId); + + const { version, criteria } = await EligibilityCriteriaAmendmentsRepo.findLatestEligibilityCriteria(facilityType); + const amendmentToInsert: PortalFacilityAmendment = { ...amendment, dealId: new ObjectId(dealId), @@ -48,6 +54,7 @@ export class PortalFacilityAmendmentService { status: AMENDMENT_STATUS.IN_PROGRESS, createdAt: getUnixTimestampSeconds(new Date()), updatedAt: getUnixTimestampSeconds(new Date()), + eligibilityCriteria: { version, criteria }, createdBy: { username: user.username, name: `${user.firstname} ${user.surname}`, diff --git a/gef-ui/component-tests/partials/amendments/eligibility.component-test.ts b/gef-ui/component-tests/partials/amendments/eligibility.component-test.ts index 9aca3a1bad..8961bee8b4 100644 --- a/gef-ui/component-tests/partials/amendments/eligibility.component-test.ts +++ b/gef-ui/component-tests/partials/amendments/eligibility.component-test.ts @@ -1,6 +1,7 @@ +import { AmendmentsEligibilityCriterion } from '@ukef/dtfs2-common'; import { validationErrorHandler } from '../../../server/utils/helpers'; import pageRenderer from '../../pageRenderer'; -import { EligibilityCriterion, EligibilityViewModel } from '../../../server/types/view-models/amendments/eligibility-view-model.ts'; +import { EligibilityViewModel } from '../../../server/types/view-models/amendments/eligibility-view-model.ts'; const page = 'partials/amendments/eligibility.njk'; const render = pageRenderer(page); @@ -8,7 +9,7 @@ const render = pageRenderer(page); describe(page, () => { const previousPage = 'previousPage'; const cancelUrl = 'cancelUrl'; - const criteria: EligibilityCriterion[] = [ + const criteria: AmendmentsEligibilityCriterion[] = [ { id: 1, text: 'Test first criteria', textList: ['criterion 1 bullet point 1', 'criterion 1 bullet point 2'] }, { id: 2, text: 'Test second criteria' }, { id: 3, text: 'Test third criteria', textList: ['criterion 3 bullet point 1', 'criterion 3 bullet point 2', 'criterion 3 bullet point 3'] }, @@ -103,7 +104,7 @@ describe(page, () => { }); it('should render the radio boxes checked as per the passed in answers', () => { - const criteriaWithAnswers: EligibilityCriterion[] = [ + const criteriaWithAnswers: AmendmentsEligibilityCriterion[] = [ { id: 1, text: 'Test first criteria', answer: true }, { id: 2, text: 'Test second criteria', answer: false }, { id: 3, text: 'Test third criteria' }, diff --git a/gef-ui/server/controllers/amendments/eligibility-criteria/get-eligibility.ts b/gef-ui/server/controllers/amendments/eligibility-criteria/get-eligibility.ts index 6b9e1d19b9..228d638871 100644 --- a/gef-ui/server/controllers/amendments/eligibility-criteria/get-eligibility.ts +++ b/gef-ui/server/controllers/amendments/eligibility-criteria/get-eligibility.ts @@ -41,12 +41,18 @@ export const getEligibility = async (req: GetEligibilityRequest, res: Response) return res.redirect('/not-found'); } - // TODO 7765: Fetch criteria using GET endpoint from database and add to viewModel + if (!amendment.eligibilityCriteria) { + console.error('Eligibility criteria was not found on facility %s', facilityId); + return res.redirect('/not-found'); + } + + const { criteria } = amendment.eligibilityCriteria; const viewModel: EligibilityViewModel = { exporterName: deal.exporter.companyName, cancelUrl: `/gef/application-details/${dealId}/facilities/${facilityId}/amendments/${amendmentId}/cancel`, previousPage: getPreviousPage(PORTAL_AMENDMENT_PAGES.ELIGIBILITY, amendment), + criteria, }; return res.render('partials/amendments/eligibility.njk', viewModel); diff --git a/gef-ui/server/types/view-models/amendments/eligibility-view-model.ts b/gef-ui/server/types/view-models/amendments/eligibility-view-model.ts index 3674252333..cd18e249b8 100644 --- a/gef-ui/server/types/view-models/amendments/eligibility-view-model.ts +++ b/gef-ui/server/types/view-models/amendments/eligibility-view-model.ts @@ -1,16 +1,10 @@ +import { AmendmentsEligibilityCriterion } from '@ukef/dtfs2-common'; import { ViewModelErrors } from '../view-model-errors'; -export type EligibilityCriterion = { - id: number; - text: string; - textList?: string[]; - answer?: boolean; -}; - export type EligibilityViewModel = { exporterName: string; previousPage: string; cancelUrl: string; - criteria?: EligibilityCriterion[]; // TODO 7765: Make required property when fetching this from database + criteria?: AmendmentsEligibilityCriterion[]; errors?: ViewModelErrors | null; }; diff --git a/libs/common/src/constants/mongo-db-collections.ts b/libs/common/src/constants/mongo-db-collections.ts index dd28130b7a..12b99c3667 100644 --- a/libs/common/src/constants/mongo-db-collections.ts +++ b/libs/common/src/constants/mongo-db-collections.ts @@ -4,6 +4,7 @@ export const MONGO_DB_COLLECTIONS = { DEALS: 'deals', DURABLE_FUNCTIONS_LOG: 'durable-functions-log', ELIGIBILITY_CRITERIA: 'eligibilityCriteria', + ELIGIBILITY_CRITERIA_AMENDMENTS: 'eligibilityCriteriaAmendments', FACILITIES: 'facilities', GEF_ELIGIBILITY_CRITERIA: 'gef-eligibilityCriteria', GEF_MANDATORY_CRITERIA_VERSIONED: 'gef-mandatoryCriteriaVersioned', diff --git a/libs/common/src/errors/eligibility-criteria-not-found.error.ts b/libs/common/src/errors/eligibility-criteria-not-found.error.ts new file mode 100644 index 0000000000..9784945a7f --- /dev/null +++ b/libs/common/src/errors/eligibility-criteria-not-found.error.ts @@ -0,0 +1,11 @@ +import { HttpStatusCode } from 'axios'; +import { ApiError } from './api.error'; + +export class EligibilityCriteriaNotFoundError extends ApiError { + constructor() { + const message = 'Latest Eligibility Criteria not found'; + super({ message, status: HttpStatusCode.NotFound }); + + this.name = 'EligibilityCriteriaNotFoundError'; + } +} diff --git a/libs/common/src/errors/index.ts b/libs/common/src/errors/index.ts index 8417403ba9..5e797408e7 100644 --- a/libs/common/src/errors/index.ts +++ b/libs/common/src/errors/index.ts @@ -19,3 +19,4 @@ export * from './user-session-not-defined.error'; export * from './user-token-not-defined.error'; export * from './multiple-users-found.error'; export * from './amendment-not-found.error'; +export * from './eligibility-criteria-not-found.error'; diff --git a/libs/common/src/schemas/portal-amendment.ts b/libs/common/src/schemas/portal-amendment.ts index c0aa3ac86b..2f14239cbf 100644 --- a/libs/common/src/schemas/portal-amendment.ts +++ b/libs/common/src/schemas/portal-amendment.ts @@ -30,6 +30,19 @@ export const PORTAL_FACILITY_AMENDMENT_USER_VALUES = z currency: z.enum(Object.values(CURRENCY) as [Currency] & Currency[]).optional(), ukefExposure: z.number().optional(), coveredPercentage: z.number().optional(), + eligibilityCriteria: z + .object({ + version: z.number(), + criteria: z.array( + z.object({ + id: z.number(), + text: z.string(), + textList: z.array(z.string()).optional(), + answer: z.boolean().optional(), + }), + ), + }) + .optional(), }) .strict(); diff --git a/libs/common/src/types/mongo-db-models/amendments-eligibility-criteria.ts b/libs/common/src/types/mongo-db-models/amendments-eligibility-criteria.ts new file mode 100644 index 0000000000..79d2b37171 --- /dev/null +++ b/libs/common/src/types/mongo-db-models/amendments-eligibility-criteria.ts @@ -0,0 +1,21 @@ +import { ObjectId } from 'mongodb'; +import { DealType } from '../deal-type'; +import { FacilityType } from '../facility-type'; +import { AuditDatabaseRecord } from '../audit-database-record'; +import { UnixTimestampMilliseconds } from '../date'; + +export type EligibilityCriterion = { id: number; text: string; textList: string[] }; + +/** + * Type of the mongo db "eligibilityCriteriaAmendments" collection + */ +export type AmendmentsEligibilityCriteria = { + _id: ObjectId; + version: number; + product: DealType; + facilityType: FacilityType[]; + isInDraft: boolean; + createdAt: UnixTimestampMilliseconds; + criteria: EligibilityCriterion[]; + auditRecord?: AuditDatabaseRecord; +}; diff --git a/libs/common/src/types/mongo-db-models/index.ts b/libs/common/src/types/mongo-db-models/index.ts index 68d41b07e8..f5ee2a9c5f 100644 --- a/libs/common/src/types/mongo-db-models/index.ts +++ b/libs/common/src/types/mongo-db-models/index.ts @@ -12,3 +12,4 @@ export * from './tfm-deals'; export * from './deals'; export * from './tfm-deal-cancellation'; export * from './tfm-activity'; +export * from './amendments-eligibility-criteria'; diff --git a/libs/common/src/types/mongo-db-models/tfm-facilities.ts b/libs/common/src/types/mongo-db-models/tfm-facilities.ts index ba9eafda7c..3938594541 100644 --- a/libs/common/src/types/mongo-db-models/tfm-facilities.ts +++ b/libs/common/src/types/mongo-db-models/tfm-facilities.ts @@ -120,11 +120,22 @@ export interface TfmFacilityAmendment extends BaseAmendment { }; } +export type AmendmentsEligibilityCriterion = { + id: number; + text: string; + textList?: string[]; + answer?: boolean; +}; + /** * Amendments created in Portal */ export interface PortalFacilityAmendment extends BaseAmendment { type: typeof AMENDMENT_TYPES.PORTAL; + eligibilityCriteria?: { + version: number; + criteria: AmendmentsEligibilityCriterion[]; + }; } /** From 27a7e25f8f63c4175c98180ab705e67309d9d6d0 Mon Sep 17 00:00:00 2001 From: Beth Thomas Date: Fri, 17 Jan 2025 16:02:19 +0000 Subject: [PATCH 02/19] feat(DTFS2-7498): add null answer field when creating draft amendment --- .../portal/facility-amendment.service.ts | 4 +++- libs/common/src/schemas/portal-amendment.ts | 17 ++++++++++++++++- .../amendments-eligibility-criteria.ts | 2 +- .../src/types/mongo-db-models/tfm-facilities.ts | 2 +- 4 files changed, 21 insertions(+), 4 deletions(-) diff --git a/dtfs-central-api/src/services/portal/facility-amendment.service.ts b/dtfs-central-api/src/services/portal/facility-amendment.service.ts index 6c24d3242a..f8aee3d1e6 100644 --- a/dtfs-central-api/src/services/portal/facility-amendment.service.ts +++ b/dtfs-central-api/src/services/portal/facility-amendment.service.ts @@ -45,6 +45,8 @@ export class PortalFacilityAmendmentService { const { version, criteria } = await EligibilityCriteriaAmendmentsRepo.findLatestEligibilityCriteria(facilityType); + const updatedCriteria = criteria.map((criterion) => ({ ...criterion, answer: null })); + const amendmentToInsert: PortalFacilityAmendment = { ...amendment, dealId: new ObjectId(dealId), @@ -54,7 +56,7 @@ export class PortalFacilityAmendmentService { status: AMENDMENT_STATUS.IN_PROGRESS, createdAt: getUnixTimestampSeconds(new Date()), updatedAt: getUnixTimestampSeconds(new Date()), - eligibilityCriteria: { version, criteria }, + eligibilityCriteria: { version, criteria: updatedCriteria }, createdBy: { username: user.username, name: `${user.firstname} ${user.surname}`, diff --git a/libs/common/src/schemas/portal-amendment.ts b/libs/common/src/schemas/portal-amendment.ts index 2f14239cbf..7d512ae066 100644 --- a/libs/common/src/schemas/portal-amendment.ts +++ b/libs/common/src/schemas/portal-amendment.ts @@ -38,7 +38,8 @@ export const PORTAL_FACILITY_AMENDMENT_USER_VALUES = z id: z.number(), text: z.string(), textList: z.array(z.string()).optional(), - answer: z.boolean().optional(), + /* When eligibilityCriteria is updated through a patch request with user values, all answers are required */ + answer: z.boolean(), }), ), }) @@ -67,6 +68,20 @@ export const PORTAL_FACILITY_AMENDMENT = PORTAL_FACILITY_AMENDMENT_USER_VALUES.m email: z.string().email(), }) .optional(), + eligibilityCriteria: z + .object({ + version: z.number(), + criteria: z.array( + z.object({ + id: z.number(), + text: z.string(), + textList: z.array(z.string()).optional(), + /* When eligibilityCriteria is fetched from the database all the `answer` fields may be null: this is the case before the user has submitted their eligibility responses. */ + answer: z.boolean().nullable(), + }), + ), + }) + .optional(), }), ); diff --git a/libs/common/src/types/mongo-db-models/amendments-eligibility-criteria.ts b/libs/common/src/types/mongo-db-models/amendments-eligibility-criteria.ts index 79d2b37171..d039425490 100644 --- a/libs/common/src/types/mongo-db-models/amendments-eligibility-criteria.ts +++ b/libs/common/src/types/mongo-db-models/amendments-eligibility-criteria.ts @@ -4,7 +4,7 @@ import { FacilityType } from '../facility-type'; import { AuditDatabaseRecord } from '../audit-database-record'; import { UnixTimestampMilliseconds } from '../date'; -export type EligibilityCriterion = { id: number; text: string; textList: string[] }; +type EligibilityCriterion = { id: number; text: string; textList: string[] }; /** * Type of the mongo db "eligibilityCriteriaAmendments" collection diff --git a/libs/common/src/types/mongo-db-models/tfm-facilities.ts b/libs/common/src/types/mongo-db-models/tfm-facilities.ts index 3938594541..bd19a9e9da 100644 --- a/libs/common/src/types/mongo-db-models/tfm-facilities.ts +++ b/libs/common/src/types/mongo-db-models/tfm-facilities.ts @@ -124,7 +124,7 @@ export type AmendmentsEligibilityCriterion = { id: number; text: string; textList?: string[]; - answer?: boolean; + answer: boolean | null; }; /** From 7b3f731b3991ca9ba9d39fc11d710067340b73ae Mon Sep 17 00:00:00 2001 From: Beth Thomas Date: Fri, 17 Jan 2025 16:34:20 +0000 Subject: [PATCH 03/19] feat(DTFS2-7498): add tests --- .../amendments/eligibility.component-test.ts | 8 +-- .../get-eligibility.test.ts | 12 +++- gef-ui/test-helpers/mock-amendment.ts | 20 +++++- ...igibility-criteria-not-found.error.test.ts | 36 +++++++++++ .../eligibility-criteria-not-found.error.ts | 6 +- .../src/schemas/portal-amendment.test.ts | 63 +++++++++++++++++++ .../types/mongo-db-models/tfm-facilities.ts | 2 +- 7 files changed, 139 insertions(+), 8 deletions(-) create mode 100644 libs/common/src/errors/eligibility-criteria-not-found.error.test.ts diff --git a/gef-ui/component-tests/partials/amendments/eligibility.component-test.ts b/gef-ui/component-tests/partials/amendments/eligibility.component-test.ts index 8961bee8b4..feebb34ef3 100644 --- a/gef-ui/component-tests/partials/amendments/eligibility.component-test.ts +++ b/gef-ui/component-tests/partials/amendments/eligibility.component-test.ts @@ -10,9 +10,9 @@ describe(page, () => { const previousPage = 'previousPage'; const cancelUrl = 'cancelUrl'; const criteria: AmendmentsEligibilityCriterion[] = [ - { id: 1, text: 'Test first criteria', textList: ['criterion 1 bullet point 1', 'criterion 1 bullet point 2'] }, - { id: 2, text: 'Test second criteria' }, - { id: 3, text: 'Test third criteria', textList: ['criterion 3 bullet point 1', 'criterion 3 bullet point 2', 'criterion 3 bullet point 3'] }, + { id: 1, text: 'Test first criteria', textList: ['criterion 1 bullet point 1', 'criterion 1 bullet point 2'], answer: null }, + { id: 2, text: 'Test second criteria', answer: null }, + { id: 3, text: 'Test third criteria', textList: ['criterion 3 bullet point 1', 'criterion 3 bullet point 2', 'criterion 3 bullet point 3'], answer: null }, ]; const exporterName = 'exporterName'; @@ -107,7 +107,7 @@ describe(page, () => { const criteriaWithAnswers: AmendmentsEligibilityCriterion[] = [ { id: 1, text: 'Test first criteria', answer: true }, { id: 2, text: 'Test second criteria', answer: false }, - { id: 3, text: 'Test third criteria' }, + { id: 3, text: 'Test third criteria', answer: null }, ]; const wrapper = render({ ...params, criteria: criteriaWithAnswers }); diff --git a/gef-ui/server/controllers/amendments/eligibility-criteria/get-eligibility.test.ts b/gef-ui/server/controllers/amendments/eligibility-criteria/get-eligibility.test.ts index 97cc6788ea..1885061d4b 100644 --- a/gef-ui/server/controllers/amendments/eligibility-criteria/get-eligibility.test.ts +++ b/gef-ui/server/controllers/amendments/eligibility-criteria/get-eligibility.test.ts @@ -39,6 +39,10 @@ const getHttpMocks = () => }); const mockDeal = { ...MOCK_BASIC_DEAL, submissionType: DEAL_SUBMISSION_TYPE.AIN, status: DEAL_STATUS.UKEF_ACKNOWLEDGED }; +const criteria = [ + { id: 1, text: 'test criteria 1', answer: true }, + { id: 2, text: 'test criteria 2', answer: false }, +]; describe('getEligibilityCriteria', () => { let amendment: PortalFacilityAmendmentWithUkefId; @@ -50,7 +54,12 @@ describe('getEligibilityCriteria', () => { jest.spyOn(dtfsCommon, 'isPortalFacilityAmendmentsFeatureFlagEnabled').mockReturnValue(true); jest.spyOn(console, 'error'); - amendment = new PortalFacilityAmendmentWithUkefIdMockBuilder().withDealId(dealId).withFacilityId(facilityId).withAmendmentId(amendmentId).build(); + amendment = new PortalFacilityAmendmentWithUkefIdMockBuilder() + .withDealId(dealId) + .withFacilityId(facilityId) + .withAmendmentId(amendmentId) + .withCriteria(criteria) + .build(); getApplicationMock.mockResolvedValue(mockDeal); getFacilityMock.mockResolvedValue(MOCK_ISSUED_FACILITY); @@ -109,6 +118,7 @@ describe('getEligibilityCriteria', () => { exporterName: MOCK_BASIC_DEAL.exporter.companyName, cancelUrl: `/gef/application-details/${dealId}/facilities/${facilityId}/amendments/${amendmentId}/cancel`, previousPage: getPreviousPage(PORTAL_AMENDMENT_PAGES.ELIGIBILITY, amendment), + criteria, }; expect(res._getStatusCode()).toEqual(HttpStatusCode.Ok); diff --git a/gef-ui/test-helpers/mock-amendment.ts b/gef-ui/test-helpers/mock-amendment.ts index 6687ad1c59..9049e61ecd 100644 --- a/gef-ui/test-helpers/mock-amendment.ts +++ b/gef-ui/test-helpers/mock-amendment.ts @@ -1,4 +1,4 @@ -import { AMENDMENT_STATUS, AMENDMENT_TYPES, PortalFacilityAmendmentWithUkefId } from '@ukef/dtfs2-common'; +import { AMENDMENT_STATUS, AMENDMENT_TYPES, AmendmentsEligibilityCriterion, PortalFacilityAmendmentWithUkefId } from '@ukef/dtfs2-common'; import { getUnixTime } from 'date-fns'; export class PortalFacilityAmendmentWithUkefIdMockBuilder { @@ -19,6 +19,16 @@ export class PortalFacilityAmendmentWithUkefIdMockBuilder { email: 'maker1@ukexportfinance.gov.uk', }, ukefFacilityId: '0041282190', + eligibilityCriteria: { + version: 1, + criteria: [ + { + id: 1, + text: 'Criteria 1', + answer: null, + }, + ], + }, }; } @@ -37,6 +47,14 @@ export class PortalFacilityAmendmentWithUkefIdMockBuilder { return this; } + public withCriteria(criteria: AmendmentsEligibilityCriterion[]) { + this.amendment.eligibilityCriteria = { + version: 1, + criteria, + }; + return this; + } + public withChangeCoverEndDate(changeCoverEndDate: boolean) { this.amendment.changeCoverEndDate = changeCoverEndDate; return this; diff --git a/libs/common/src/errors/eligibility-criteria-not-found.error.test.ts b/libs/common/src/errors/eligibility-criteria-not-found.error.test.ts new file mode 100644 index 0000000000..f6b58e4562 --- /dev/null +++ b/libs/common/src/errors/eligibility-criteria-not-found.error.test.ts @@ -0,0 +1,36 @@ +import { ApiError } from './api.error'; +import { EligibilityCriteriaNotFoundError } from './eligibility-criteria-not-found.error'; + +describe('EligibilityCriteriaNotFoundError', () => { + it('should expose the message the error was created with', () => { + // Act + const exception = new EligibilityCriteriaNotFoundError(); + + // Assert + expect(exception.message).toEqual('Latest eligibility criteria not found'); + }); + + it('should be an instance of EligibilityCriteriaNotFoundError', () => { + // Act + const exception = new EligibilityCriteriaNotFoundError(); + + // Assert + expect(exception).toBeInstanceOf(EligibilityCriteriaNotFoundError); + }); + + it('should be an instance of ApiError', () => { + // Act + const exception = new EligibilityCriteriaNotFoundError(); + + // Assert + expect(exception).toBeInstanceOf(ApiError); + }); + + it('should expose the name of the exception', () => { + // Act + const exception = new EligibilityCriteriaNotFoundError(); + + // Assert + expect(exception.name).toEqual('EligibilityCriteriaNotFoundError'); + }); +}); diff --git a/libs/common/src/errors/eligibility-criteria-not-found.error.ts b/libs/common/src/errors/eligibility-criteria-not-found.error.ts index 9784945a7f..bea1e6a93f 100644 --- a/libs/common/src/errors/eligibility-criteria-not-found.error.ts +++ b/libs/common/src/errors/eligibility-criteria-not-found.error.ts @@ -1,9 +1,13 @@ import { HttpStatusCode } from 'axios'; import { ApiError } from './api.error'; +/** + * Error thrown when eligibility criteria is not found, to be handled as an ApiError in the controller + * and return a 404 status code + */ export class EligibilityCriteriaNotFoundError extends ApiError { constructor() { - const message = 'Latest Eligibility Criteria not found'; + const message = 'Latest eligibility criteria not found'; super({ message, status: HttpStatusCode.NotFound }); this.name = 'EligibilityCriteriaNotFoundError'; diff --git a/libs/common/src/schemas/portal-amendment.test.ts b/libs/common/src/schemas/portal-amendment.test.ts index 268ee49ab9..3f462bbb09 100644 --- a/libs/common/src/schemas/portal-amendment.test.ts +++ b/libs/common/src/schemas/portal-amendment.test.ts @@ -158,6 +158,69 @@ describe('PORTAL_FACILITY_AMENDMENT_USER_VALUES', () => { coveredPercentage: 'not a number', }), }, + { + description: 'eligibilityCriteria contains a non numerical version value', + aTestCase: () => ({ + ...aValidPayload(), + eligibilityCriteria: { version: '1', criteria: [] }, + }), + }, + { + description: 'eligibilityCriteria is missing version', + aTestCase: () => ({ + ...aValidPayload(), + eligibilityCriteria: { criteria: [] }, + }), + }, + { + description: 'eligibilityCriteria is missing criteria', + aTestCase: () => ({ + ...aValidPayload(), + eligibilityCriteria: { version: 1 }, + }), + }, + { + description: 'eligibility has a non numerical id', + aTestCase: () => ({ + ...aValidPayload(), + eligibilityCriteria: { version: 1, eligibility: { id: '1', text: 'test-text', answer: true } }, + }), + }, + { + description: 'eligibility is missing id', + aTestCase: () => ({ + ...aValidPayload(), + eligibilityCriteria: { version: 1, eligibility: { text: 'test-text', answer: true } }, + }), + }, + { + description: 'eligibility is missing text', + aTestCase: () => ({ + ...aValidPayload(), + eligibilityCriteria: { version: 1, eligibility: { id: 1, answer: true } }, + }), + }, + { + description: 'eligibility is missing answer', + aTestCase: () => ({ + ...aValidPayload(), + eligibilityCriteria: { version: 1, eligibility: { id: 1, text: 'test-text' } }, + }), + }, + { + description: 'eligibility has a non boolean answer', + aTestCase: () => ({ + ...aValidPayload(), + eligibilityCriteria: { version: 1, eligibility: { id: 1, text: 'test-text', answer: 'true' } }, + }), + }, + { + description: 'eligibility has a null answer', + aTestCase: () => ({ + ...aValidPayload(), + eligibilityCriteria: { version: 1, eligibility: { id: 1, text: 'test-text', answer: null } }, + }), + }, ]; } diff --git a/libs/common/src/types/mongo-db-models/tfm-facilities.ts b/libs/common/src/types/mongo-db-models/tfm-facilities.ts index bd19a9e9da..ec1c13b0b8 100644 --- a/libs/common/src/types/mongo-db-models/tfm-facilities.ts +++ b/libs/common/src/types/mongo-db-models/tfm-facilities.ts @@ -132,7 +132,7 @@ export type AmendmentsEligibilityCriterion = { */ export interface PortalFacilityAmendment extends BaseAmendment { type: typeof AMENDMENT_TYPES.PORTAL; - eligibilityCriteria?: { + eligibilityCriteria: { version: number; criteria: AmendmentsEligibilityCriterion[]; }; From b1ef1d352c86ec86e4caa06fa6de210b8ead6b2c Mon Sep 17 00:00:00 2001 From: Beth Thomas Date: Mon, 20 Jan 2025 10:47:16 +0000 Subject: [PATCH 04/19] feat(DTFS2-7498): add dtfs-central tests --- ...igibility-criteria-amendments.repo.test.ts | 59 +++++++++++++++ ...rtalFacilityAmendmentDraft.service.test.ts | 74 +++++++++++++++++-- .../eligibility-criteria-amendments.ts | 17 +++++ ...igibility-criteria-not-found.error.test.ts | 2 +- .../eligibility-criteria-not-found.error.ts | 2 +- .../portal-facility-amendment.ts | 4 + .../amendments-eligibility-criteria.ts | 2 +- 7 files changed, 150 insertions(+), 10 deletions(-) create mode 100644 dtfs-central-api/src/repositories/portal/eligibility-criteria-amendments.repo.test.ts create mode 100644 dtfs-central-api/test-helpers/test-data/eligibility-criteria-amendments.ts diff --git a/dtfs-central-api/src/repositories/portal/eligibility-criteria-amendments.repo.test.ts b/dtfs-central-api/src/repositories/portal/eligibility-criteria-amendments.repo.test.ts new file mode 100644 index 0000000000..245b558d06 --- /dev/null +++ b/dtfs-central-api/src/repositories/portal/eligibility-criteria-amendments.repo.test.ts @@ -0,0 +1,59 @@ +import { AmendmentsEligibilityCriteria, FACILITY_TYPE, MONGO_DB_COLLECTIONS } from '@ukef/dtfs2-common'; +import { mongoDbClient } from '../../drivers/db-client'; +import { EligibilityCriteriaAmendmentsRepo } from './eligibility-criteria-amendments.repo'; +import { anAmendmentsEligibilityCriteria } from '../../../test-helpers/test-data/eligibility-criteria-amendments'; + +describe('EligibilityCriteriaAmendmentsRepo', () => { + const findToArrayMock = jest.fn(); + const findMock = jest.fn(); + const getCollectionMock = jest.fn(); + + afterEach(() => { + jest.resetAllMocks(); + }); + + describe('findLatestEligibilityCriteria', () => { + const eligibilityCriteria: AmendmentsEligibilityCriteria = anAmendmentsEligibilityCriteria(); + + beforeEach(() => { + findToArrayMock.mockResolvedValue([eligibilityCriteria]); + findMock.mockReturnValue({ sort: () => ({ limit: () => ({ toArray: findToArrayMock }) }) }); + + getCollectionMock.mockResolvedValue({ + find: findMock, + }); + + jest.spyOn(mongoDbClient, 'getCollection').mockImplementation(getCollectionMock); + }); + + it(`calls getCollection with ${MONGO_DB_COLLECTIONS.ELIGIBILITY_CRITERIA_AMENDMENTS}`, async () => { + // Act + await EligibilityCriteriaAmendmentsRepo.findLatestEligibilityCriteria(FACILITY_TYPE.CASH); + + // Assert + expect(getCollectionMock).toHaveBeenCalledTimes(1); + expect(getCollectionMock).toHaveBeenCalledWith(MONGO_DB_COLLECTIONS.ELIGIBILITY_CRITERIA_AMENDMENTS); + }); + + it('calls find with the expected parameters', async () => { + // Act + const facilityType = FACILITY_TYPE.CASH; + await EligibilityCriteriaAmendmentsRepo.findLatestEligibilityCriteria(facilityType); + + // Assert + const expectedFilter = { $and: [{ facilityType }, { isInDraft: { $eq: false } }] }; + + expect(findMock).toHaveBeenCalledTimes(1); + expect(findMock).toHaveBeenCalledWith(expectedFilter); + }); + + it('returns the found latest eligibility criteria', async () => { + // Act + const facilityType = FACILITY_TYPE.CASH; + const result = await EligibilityCriteriaAmendmentsRepo.findLatestEligibilityCriteria(facilityType); + + // Assert + expect(result).toEqual(eligibilityCriteria); + }); + }); +}); diff --git a/dtfs-central-api/src/services/portal/facility-amendment.upsertPortalFacilityAmendmentDraft.service.test.ts b/dtfs-central-api/src/services/portal/facility-amendment.upsertPortalFacilityAmendmentDraft.service.test.ts index 67f077b92e..52e8bce284 100644 --- a/dtfs-central-api/src/services/portal/facility-amendment.upsertPortalFacilityAmendmentDraft.service.test.ts +++ b/dtfs-central-api/src/services/portal/facility-amendment.upsertPortalFacilityAmendmentDraft.service.test.ts @@ -1,26 +1,42 @@ /* eslint-disable import/first */ -const mockFindOneUser = jest.fn(); +import { AMENDMENT_STATUS, AMENDMENT_TYPES, AmendmentsEligibilityCriteria, InvalidAuditDetailsError } from '@ukef/dtfs2-common'; -jest.mock('../../v1/controllers/user/get-user.controller', () => ({ - findOneUser: mockFindOneUser, -})); +const mockFindOneUser = jest.fn(); +const mockFindOneFacility = jest.fn(); +const mockFindLatestEligibilityCriteria = jest.fn() as jest.Mock>; import { ObjectId } from 'mongodb'; import { HttpStatusCode } from 'axios'; import { getUnixTime } from 'date-fns'; import { generatePortalAuditDetails } from '@ukef/dtfs2-common/change-stream'; -import { AMENDMENT_STATUS, AMENDMENT_TYPES, InvalidAuditDetailsError } from '@ukef/dtfs2-common'; import { aPortalFacilityAmendmentUserValues } from '@ukef/dtfs2-common/mock-data-backend'; +import { anAmendmentsEligibilityCriteria } from '../../../test-helpers/test-data/eligibility-criteria-amendments'; import { PortalFacilityAmendmentService } from './facility-amendment.service'; -import { aPortalUser } from '../../../test-helpers'; +import { aFacility, aPortalUser } from '../../../test-helpers'; import { TfmFacilitiesRepo } from '../../repositories/tfm-facilities-repo'; +jest.mock('../../v1/controllers/user/get-user.controller', () => ({ + findOneUser: mockFindOneUser, +})); + +jest.mock('../../v1/controllers/portal/facility/get-facility.controller', () => ({ + findOneFacility: mockFindOneFacility, +})); + +jest.mock('../../repositories/portal/eligibility-criteria-amendments.repo', () => ({ + EligibilityCriteriaAmendmentsRepo: { + findLatestEligibilityCriteria: (facilityType: string) => mockFindLatestEligibilityCriteria(facilityType), + }, +})); + const mockUpsertPortalFacilityAmendmentDraft = jest.fn(); const dealId = new ObjectId().toString(); const facilityId = new ObjectId().toString(); const amendment = aPortalFacilityAmendmentUserValues(); const auditDetails = generatePortalAuditDetails(aPortalUser()._id); +const facility = aFacility(); +const eligibilityCriteria = anAmendmentsEligibilityCriteria(); describe('PortalFacilityAmendmentService', () => { beforeAll(() => { @@ -33,7 +49,9 @@ describe('PortalFacilityAmendmentService', () => { jest.spyOn(TfmFacilitiesRepo, 'upsertPortalFacilityAmendmentDraft').mockImplementation(mockUpsertPortalFacilityAmendmentDraft); mockFindOneUser.mockResolvedValue(aPortalUser()); - mockUpsertPortalFacilityAmendmentDraft.mockResolvedValue({}); + mockFindOneFacility.mockResolvedValue(facility); + mockFindOneFacility.mockResolvedValue(facility); + mockFindLatestEligibilityCriteria.mockResolvedValue(eligibilityCriteria); }); afterAll(() => { @@ -70,6 +88,34 @@ describe('PortalFacilityAmendmentService', () => { ).rejects.toThrow(InvalidAuditDetailsError); }); + it('should call findOneFacility with the facility id', async () => { + // Act + await PortalFacilityAmendmentService.upsertPortalFacilityAmendmentDraft({ + dealId, + facilityId, + amendment, + auditDetails, + }); + + // Assert + expect(mockFindOneFacility).toHaveBeenCalledTimes(1); + expect(mockFindOneFacility).toHaveBeenCalledWith(facilityId); + }); + + it('should call findLatestEligibilityCriteria with the facility type', async () => { + // Act + await PortalFacilityAmendmentService.upsertPortalFacilityAmendmentDraft({ + dealId, + facilityId, + amendment, + auditDetails, + }); + + // Assert + expect(mockFindLatestEligibilityCriteria).toHaveBeenCalledTimes(1); + expect(mockFindLatestEligibilityCriteria).toHaveBeenCalledWith(facility.type); + }); + it('should call TfmFacilitiesRepo.upsertPortalFacilityAmendmentDraft with correct params', async () => { // Act await PortalFacilityAmendmentService.upsertPortalFacilityAmendmentDraft({ @@ -89,6 +135,13 @@ describe('PortalFacilityAmendmentService', () => { status: AMENDMENT_STATUS.IN_PROGRESS, createdAt: getUnixTime(new Date()), updatedAt: getUnixTime(new Date()), + eligibilityCriteria: { + version: eligibilityCriteria.version, + criteria: [ + { ...eligibilityCriteria.criteria[0], answer: null }, + { ...eligibilityCriteria.criteria[1], answer: null }, + ], + }, createdBy: { username: aPortalUser().username, name: `${aPortalUser().firstname} ${aPortalUser().surname}`, @@ -119,6 +172,13 @@ describe('PortalFacilityAmendmentService', () => { status: AMENDMENT_STATUS.IN_PROGRESS, createdAt: getUnixTime(new Date()), updatedAt: getUnixTime(new Date()), + eligibilityCriteria: { + version: eligibilityCriteria.version, + criteria: [ + { ...eligibilityCriteria.criteria[0], answer: null }, + { ...eligibilityCriteria.criteria[1], answer: null }, + ], + }, createdBy: { username: aPortalUser().username, name: `${aPortalUser().firstname} ${aPortalUser().surname}`, diff --git a/dtfs-central-api/test-helpers/test-data/eligibility-criteria-amendments.ts b/dtfs-central-api/test-helpers/test-data/eligibility-criteria-amendments.ts new file mode 100644 index 0000000000..fe472530e8 --- /dev/null +++ b/dtfs-central-api/test-helpers/test-data/eligibility-criteria-amendments.ts @@ -0,0 +1,17 @@ +import { ObjectId } from 'mongodb'; +import { AmendmentsEligibilityCriteria, DEAL_TYPE, FACILITY_TYPE } from '@ukef/dtfs2-common'; +import { generateMockPortalUserAuditDatabaseRecord } from '@ukef/dtfs2-common/change-stream/test-helpers'; + +export const anAmendmentsEligibilityCriteria = (): AmendmentsEligibilityCriteria => ({ + _id: new ObjectId(), + version: 1, + product: DEAL_TYPE.GEF, + facilityType: [FACILITY_TYPE.CASH, FACILITY_TYPE.CONTINGENT], + isInDraft: false, + createdAt: new Date().getTime(), + criteria: [ + { id: 1, text: 'item 1', textList: ['item 1'] }, + { id: 2, text: 'item 2' }, + ], + auditRecord: generateMockPortalUserAuditDatabaseRecord(new ObjectId()), +}); diff --git a/libs/common/src/errors/eligibility-criteria-not-found.error.test.ts b/libs/common/src/errors/eligibility-criteria-not-found.error.test.ts index f6b58e4562..6b124e0c0a 100644 --- a/libs/common/src/errors/eligibility-criteria-not-found.error.test.ts +++ b/libs/common/src/errors/eligibility-criteria-not-found.error.test.ts @@ -7,7 +7,7 @@ describe('EligibilityCriteriaNotFoundError', () => { const exception = new EligibilityCriteriaNotFoundError(); // Assert - expect(exception.message).toEqual('Latest eligibility criteria not found'); + expect(exception.message).toEqual('Eligibility criteria not found'); }); it('should be an instance of EligibilityCriteriaNotFoundError', () => { diff --git a/libs/common/src/errors/eligibility-criteria-not-found.error.ts b/libs/common/src/errors/eligibility-criteria-not-found.error.ts index bea1e6a93f..6ab38fc600 100644 --- a/libs/common/src/errors/eligibility-criteria-not-found.error.ts +++ b/libs/common/src/errors/eligibility-criteria-not-found.error.ts @@ -7,7 +7,7 @@ import { ApiError } from './api.error'; */ export class EligibilityCriteriaNotFoundError extends ApiError { constructor() { - const message = 'Latest eligibility criteria not found'; + const message = 'Eligibility criteria not found'; super({ message, status: HttpStatusCode.NotFound }); this.name = 'EligibilityCriteriaNotFoundError'; diff --git a/libs/common/src/test-helpers/mock-data-backend/portal-facility-amendment.ts b/libs/common/src/test-helpers/mock-data-backend/portal-facility-amendment.ts index 549c781221..24feefb271 100644 --- a/libs/common/src/test-helpers/mock-data-backend/portal-facility-amendment.ts +++ b/libs/common/src/test-helpers/mock-data-backend/portal-facility-amendment.ts @@ -28,4 +28,8 @@ export const aPortalFacilityAmendment = (): PortalFacilityAmendment => ({ createdAt: getUnixTime(new Date()), updatedAt: getUnixTime(new Date()), status: AMENDMENT_STATUS.IN_PROGRESS, + eligibilityCriteria: { + criteria: [{ id: 1, text: 'item 1', answer: null }], + version: 1, + }, }); diff --git a/libs/common/src/types/mongo-db-models/amendments-eligibility-criteria.ts b/libs/common/src/types/mongo-db-models/amendments-eligibility-criteria.ts index d039425490..b112c4223d 100644 --- a/libs/common/src/types/mongo-db-models/amendments-eligibility-criteria.ts +++ b/libs/common/src/types/mongo-db-models/amendments-eligibility-criteria.ts @@ -4,7 +4,7 @@ import { FacilityType } from '../facility-type'; import { AuditDatabaseRecord } from '../audit-database-record'; import { UnixTimestampMilliseconds } from '../date'; -type EligibilityCriterion = { id: number; text: string; textList: string[] }; +type EligibilityCriterion = { id: number; text: string; textList?: string[] }; /** * Type of the mongo db "eligibilityCriteriaAmendments" collection From 895768a38637418072bc7150afbd4e15fc657e67 Mon Sep 17 00:00:00 2001 From: Beth Thomas Date: Mon, 20 Jan 2025 10:57:39 +0000 Subject: [PATCH 05/19] feat(DTFS2-7498): fix test and type names --- .../amendments/eligibility.component-test.ts | 6 ++-- .../amendments/eligibility-view-model.ts | 4 +-- gef-ui/test-helpers/mock-amendment.ts | 4 +-- .../src/schemas/portal-amendment.test.ts | 30 +++++++++++-------- .../amendments-eligibility-criteria.ts | 4 +-- .../types/mongo-db-models/tfm-facilities.ts | 8 ++--- 6 files changed, 30 insertions(+), 26 deletions(-) diff --git a/gef-ui/component-tests/partials/amendments/eligibility.component-test.ts b/gef-ui/component-tests/partials/amendments/eligibility.component-test.ts index feebb34ef3..db0331db92 100644 --- a/gef-ui/component-tests/partials/amendments/eligibility.component-test.ts +++ b/gef-ui/component-tests/partials/amendments/eligibility.component-test.ts @@ -1,4 +1,4 @@ -import { AmendmentsEligibilityCriterion } from '@ukef/dtfs2-common'; +import { AmendmentsEligibilityCriterionWithAnswer } from '@ukef/dtfs2-common'; import { validationErrorHandler } from '../../../server/utils/helpers'; import pageRenderer from '../../pageRenderer'; import { EligibilityViewModel } from '../../../server/types/view-models/amendments/eligibility-view-model.ts'; @@ -9,7 +9,7 @@ const render = pageRenderer(page); describe(page, () => { const previousPage = 'previousPage'; const cancelUrl = 'cancelUrl'; - const criteria: AmendmentsEligibilityCriterion[] = [ + const criteria: AmendmentsEligibilityCriterionWithAnswer[] = [ { id: 1, text: 'Test first criteria', textList: ['criterion 1 bullet point 1', 'criterion 1 bullet point 2'], answer: null }, { id: 2, text: 'Test second criteria', answer: null }, { id: 3, text: 'Test third criteria', textList: ['criterion 3 bullet point 1', 'criterion 3 bullet point 2', 'criterion 3 bullet point 3'], answer: null }, @@ -104,7 +104,7 @@ describe(page, () => { }); it('should render the radio boxes checked as per the passed in answers', () => { - const criteriaWithAnswers: AmendmentsEligibilityCriterion[] = [ + const criteriaWithAnswers: AmendmentsEligibilityCriterionWithAnswer[] = [ { id: 1, text: 'Test first criteria', answer: true }, { id: 2, text: 'Test second criteria', answer: false }, { id: 3, text: 'Test third criteria', answer: null }, diff --git a/gef-ui/server/types/view-models/amendments/eligibility-view-model.ts b/gef-ui/server/types/view-models/amendments/eligibility-view-model.ts index cd18e249b8..654c764fb9 100644 --- a/gef-ui/server/types/view-models/amendments/eligibility-view-model.ts +++ b/gef-ui/server/types/view-models/amendments/eligibility-view-model.ts @@ -1,10 +1,10 @@ -import { AmendmentsEligibilityCriterion } from '@ukef/dtfs2-common'; +import { AmendmentsEligibilityCriterionWithAnswer } from '@ukef/dtfs2-common'; import { ViewModelErrors } from '../view-model-errors'; export type EligibilityViewModel = { exporterName: string; previousPage: string; cancelUrl: string; - criteria?: AmendmentsEligibilityCriterion[]; + criteria?: AmendmentsEligibilityCriterionWithAnswer[]; errors?: ViewModelErrors | null; }; diff --git a/gef-ui/test-helpers/mock-amendment.ts b/gef-ui/test-helpers/mock-amendment.ts index 9049e61ecd..16503d0a3a 100644 --- a/gef-ui/test-helpers/mock-amendment.ts +++ b/gef-ui/test-helpers/mock-amendment.ts @@ -1,4 +1,4 @@ -import { AMENDMENT_STATUS, AMENDMENT_TYPES, AmendmentsEligibilityCriterion, PortalFacilityAmendmentWithUkefId } from '@ukef/dtfs2-common'; +import { AMENDMENT_STATUS, AMENDMENT_TYPES, AmendmentsEligibilityCriterionWithAnswer, PortalFacilityAmendmentWithUkefId } from '@ukef/dtfs2-common'; import { getUnixTime } from 'date-fns'; export class PortalFacilityAmendmentWithUkefIdMockBuilder { @@ -47,7 +47,7 @@ export class PortalFacilityAmendmentWithUkefIdMockBuilder { return this; } - public withCriteria(criteria: AmendmentsEligibilityCriterion[]) { + public withCriteria(criteria: AmendmentsEligibilityCriterionWithAnswer[]) { this.amendment.eligibilityCriteria = { version: 1, criteria, diff --git a/libs/common/src/schemas/portal-amendment.test.ts b/libs/common/src/schemas/portal-amendment.test.ts index 3f462bbb09..06ec592543 100644 --- a/libs/common/src/schemas/portal-amendment.test.ts +++ b/libs/common/src/schemas/portal-amendment.test.ts @@ -180,45 +180,51 @@ describe('PORTAL_FACILITY_AMENDMENT_USER_VALUES', () => { }), }, { - description: 'eligibility has a non numerical id', + description: 'criteria has a non numerical id', aTestCase: () => ({ ...aValidPayload(), - eligibilityCriteria: { version: 1, eligibility: { id: '1', text: 'test-text', answer: true } }, + eligibilityCriteria: { version: 1, criteria: [{ id: '1', text: 'test-text', answer: true }] }, }), }, { - description: 'eligibility is missing id', + description: 'criteria is missing id', aTestCase: () => ({ ...aValidPayload(), - eligibilityCriteria: { version: 1, eligibility: { text: 'test-text', answer: true } }, + eligibilityCriteria: { version: 1, criteria: [{ text: 'test-text', answer: true }] }, }), }, { - description: 'eligibility is missing text', + description: 'criteria is missing text', aTestCase: () => ({ ...aValidPayload(), - eligibilityCriteria: { version: 1, eligibility: { id: 1, answer: true } }, + eligibilityCriteria: { + version: 1, + criteria: [ + { id: 1, answer: true }, + { id: 2, answer: false, text: 'test-text' }, + ], + }, }), }, { - description: 'eligibility is missing answer', + description: 'criteria is missing answer', aTestCase: () => ({ ...aValidPayload(), - eligibilityCriteria: { version: 1, eligibility: { id: 1, text: 'test-text' } }, + eligibilityCriteria: { version: 1, criteria: [{ id: 1, text: 'test-text' }] }, }), }, { - description: 'eligibility has a non boolean answer', + description: 'criteria has a non boolean answer', aTestCase: () => ({ ...aValidPayload(), - eligibilityCriteria: { version: 1, eligibility: { id: 1, text: 'test-text', answer: 'true' } }, + eligibilityCriteria: { version: 1, criteria: [{ id: 1, text: 'test-text', answer: 'true' }] }, }), }, { - description: 'eligibility has a null answer', + description: 'criteria has a null answer', aTestCase: () => ({ ...aValidPayload(), - eligibilityCriteria: { version: 1, eligibility: { id: 1, text: 'test-text', answer: null } }, + eligibilityCriteria: { version: 1, criteria: [{ id: 1, text: 'test-text', answer: null }] }, }), }, ]; diff --git a/libs/common/src/types/mongo-db-models/amendments-eligibility-criteria.ts b/libs/common/src/types/mongo-db-models/amendments-eligibility-criteria.ts index b112c4223d..6d8d6b11ed 100644 --- a/libs/common/src/types/mongo-db-models/amendments-eligibility-criteria.ts +++ b/libs/common/src/types/mongo-db-models/amendments-eligibility-criteria.ts @@ -4,7 +4,7 @@ import { FacilityType } from '../facility-type'; import { AuditDatabaseRecord } from '../audit-database-record'; import { UnixTimestampMilliseconds } from '../date'; -type EligibilityCriterion = { id: number; text: string; textList?: string[] }; +export type AmendmentsEligibilityCriterion = { id: number; text: string; textList?: string[] }; /** * Type of the mongo db "eligibilityCriteriaAmendments" collection @@ -16,6 +16,6 @@ export type AmendmentsEligibilityCriteria = { facilityType: FacilityType[]; isInDraft: boolean; createdAt: UnixTimestampMilliseconds; - criteria: EligibilityCriterion[]; + criteria: AmendmentsEligibilityCriterion[]; auditRecord?: AuditDatabaseRecord; }; diff --git a/libs/common/src/types/mongo-db-models/tfm-facilities.ts b/libs/common/src/types/mongo-db-models/tfm-facilities.ts index ec1c13b0b8..bf86876ae7 100644 --- a/libs/common/src/types/mongo-db-models/tfm-facilities.ts +++ b/libs/common/src/types/mongo-db-models/tfm-facilities.ts @@ -6,6 +6,7 @@ import { Facility } from './facility'; import { AnyObject } from '../any-object'; import { AuditDatabaseRecord } from '../audit-database-record'; import { AMENDMENT_TYPES } from '../../constants'; +import { AmendmentsEligibilityCriterion } from './amendments-eligibility-criteria'; type SubmittedByUser = { _id: ObjectId; @@ -120,10 +121,7 @@ export interface TfmFacilityAmendment extends BaseAmendment { }; } -export type AmendmentsEligibilityCriterion = { - id: number; - text: string; - textList?: string[]; +export type AmendmentsEligibilityCriterionWithAnswer = AmendmentsEligibilityCriterion & { answer: boolean | null; }; @@ -134,7 +132,7 @@ export interface PortalFacilityAmendment extends BaseAmendment { type: typeof AMENDMENT_TYPES.PORTAL; eligibilityCriteria: { version: number; - criteria: AmendmentsEligibilityCriterion[]; + criteria: AmendmentsEligibilityCriterionWithAnswer[]; }; } From bec8c351d431321d8c6859429a19c08a9bfa78fc Mon Sep 17 00:00:00 2001 From: Beth Thomas Date: Mon, 20 Jan 2025 10:58:16 +0000 Subject: [PATCH 06/19] feat(DTFS2-7498): update field to be non-optional --- libs/common/src/schemas/portal-amendment.ts | 26 ++++++++++----------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/libs/common/src/schemas/portal-amendment.ts b/libs/common/src/schemas/portal-amendment.ts index 7d512ae066..056f6ba232 100644 --- a/libs/common/src/schemas/portal-amendment.ts +++ b/libs/common/src/schemas/portal-amendment.ts @@ -68,20 +68,18 @@ export const PORTAL_FACILITY_AMENDMENT = PORTAL_FACILITY_AMENDMENT_USER_VALUES.m email: z.string().email(), }) .optional(), - eligibilityCriteria: z - .object({ - version: z.number(), - criteria: z.array( - z.object({ - id: z.number(), - text: z.string(), - textList: z.array(z.string()).optional(), - /* When eligibilityCriteria is fetched from the database all the `answer` fields may be null: this is the case before the user has submitted their eligibility responses. */ - answer: z.boolean().nullable(), - }), - ), - }) - .optional(), + eligibilityCriteria: z.object({ + version: z.number(), + criteria: z.array( + z.object({ + id: z.number(), + text: z.string(), + textList: z.array(z.string()).optional(), + /* When eligibilityCriteria is fetched from the database all the `answer` fields may be null: this is the case before the user has submitted their eligibility responses. */ + answer: z.boolean().nullable(), + }), + ), + }), }), ); From 442d12b70e7091ab1dea716fb54b8719f053501a Mon Sep 17 00:00:00 2001 From: Beth Thomas Date: Mon, 20 Jan 2025 11:54:19 +0000 Subject: [PATCH 07/19] feat(DTFS2-7498): add test to ec not found error --- .../errors/eligibility-criteria-not-found.error.test.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/libs/common/src/errors/eligibility-criteria-not-found.error.test.ts b/libs/common/src/errors/eligibility-criteria-not-found.error.test.ts index 6b124e0c0a..c09d244e7b 100644 --- a/libs/common/src/errors/eligibility-criteria-not-found.error.test.ts +++ b/libs/common/src/errors/eligibility-criteria-not-found.error.test.ts @@ -1,3 +1,4 @@ +import { HttpStatusCode } from 'axios'; import { ApiError } from './api.error'; import { EligibilityCriteriaNotFoundError } from './eligibility-criteria-not-found.error'; @@ -10,6 +11,14 @@ describe('EligibilityCriteriaNotFoundError', () => { expect(exception.message).toEqual('Eligibility criteria not found'); }); + it('exposes the 404 (Not Found) status code', () => { + // Act + const exception = new EligibilityCriteriaNotFoundError(); + + // Assert + expect(exception.status).toEqual(HttpStatusCode.NotFound); + }); + it('should be an instance of EligibilityCriteriaNotFoundError', () => { // Act const exception = new EligibilityCriteriaNotFoundError(); From 21729b1e444aa926925300dc7f669025ee37b114 Mon Sep 17 00:00:00 2001 From: Beth Thomas Date: Mon, 20 Jan 2025 11:57:42 +0000 Subject: [PATCH 08/19] feat(DTFS2-7498): remove unnecessary check from controller --- .../amendments/eligibility-criteria/get-eligibility.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/gef-ui/server/controllers/amendments/eligibility-criteria/get-eligibility.ts b/gef-ui/server/controllers/amendments/eligibility-criteria/get-eligibility.ts index 228d638871..2fe8912dca 100644 --- a/gef-ui/server/controllers/amendments/eligibility-criteria/get-eligibility.ts +++ b/gef-ui/server/controllers/amendments/eligibility-criteria/get-eligibility.ts @@ -41,11 +41,6 @@ export const getEligibility = async (req: GetEligibilityRequest, res: Response) return res.redirect('/not-found'); } - if (!amendment.eligibilityCriteria) { - console.error('Eligibility criteria was not found on facility %s', facilityId); - return res.redirect('/not-found'); - } - const { criteria } = amendment.eligibilityCriteria; const viewModel: EligibilityViewModel = { From 277a8636e9b947dd984ddd01ca8924bf5ae3d35c Mon Sep 17 00:00:00 2001 From: Beth Thomas Date: Mon, 20 Jan 2025 13:46:03 +0000 Subject: [PATCH 09/19] feat(DTFS2-7498): update objects in tests --- .../api-tests/v1/gef/amendments/amendments.get.api-test.ts | 1 + .../api-tests/v1/gef/amendments/amendments.patch.api-test.ts | 1 + .../api-tests/v1/gef/amendments/amendments.put.api-test.ts | 1 + portal-api/src/v1/controllers/amendments/get-amendment.test.ts | 1 + portal-api/src/v1/controllers/amendments/patch-amendment.test.ts | 1 + portal-api/src/v1/controllers/amendments/put-amendment.test.ts | 1 + 6 files changed, 6 insertions(+) diff --git a/portal-api/api-tests/v1/gef/amendments/amendments.get.api-test.ts b/portal-api/api-tests/v1/gef/amendments/amendments.get.api-test.ts index 1ce4630ad4..a53785a2aa 100644 --- a/portal-api/api-tests/v1/gef/amendments/amendments.get.api-test.ts +++ b/portal-api/api-tests/v1/gef/amendments/amendments.get.api-test.ts @@ -122,6 +122,7 @@ describe('/v1/gef/facilities/:facilityId/amendments/:amendmentId', () => { createdAt: 1702061978881, updatedAt: 1702061978881, status: PORTAL_AMENDMENT_STATUS.DRAFT, + eligibilityCriteria: { version: 1, criteria: [] }, }; jest.mocked(getPortalFacilityAmendmentMock).mockResolvedValue(amendment); diff --git a/portal-api/api-tests/v1/gef/amendments/amendments.patch.api-test.ts b/portal-api/api-tests/v1/gef/amendments/amendments.patch.api-test.ts index bede50fca8..88c61a472c 100644 --- a/portal-api/api-tests/v1/gef/amendments/amendments.patch.api-test.ts +++ b/portal-api/api-tests/v1/gef/amendments/amendments.patch.api-test.ts @@ -160,6 +160,7 @@ describe('/v1/gef/facilities/:facilityId/amendments/:amendmentId', () => { createdAt: 1702061978881, updatedAt: 1702061978881, status: PORTAL_AMENDMENT_STATUS.DRAFT, + eligibilityCriteria: { version: 1, criteria: [] }, }; jest.mocked(patchPortalFacilityAmendmentMock).mockResolvedValue(amendment); diff --git a/portal-api/api-tests/v1/gef/amendments/amendments.put.api-test.ts b/portal-api/api-tests/v1/gef/amendments/amendments.put.api-test.ts index 6b41b25737..098bee9f88 100644 --- a/portal-api/api-tests/v1/gef/amendments/amendments.put.api-test.ts +++ b/portal-api/api-tests/v1/gef/amendments/amendments.put.api-test.ts @@ -141,6 +141,7 @@ describe('/v1/gef/facilities/:facilityId/amendments', () => { createdAt: 1702061978881, updatedAt: 1702061978881, status: PORTAL_AMENDMENT_STATUS.DRAFT, + eligibilityCriteria: { version: 1, criteria: [] }, }; jest.mocked(putPortalFacilityAmendmentMock).mockResolvedValue(amendment); diff --git a/portal-api/src/v1/controllers/amendments/get-amendment.test.ts b/portal-api/src/v1/controllers/amendments/get-amendment.test.ts index a35054ab70..779abce7f7 100644 --- a/portal-api/src/v1/controllers/amendments/get-amendment.test.ts +++ b/portal-api/src/v1/controllers/amendments/get-amendment.test.ts @@ -42,6 +42,7 @@ describe('controllers - facility amendment', () => { createdAt: 1702061978881, updatedAt: 1702061978881, status: PORTAL_AMENDMENT_STATUS.DRAFT, + eligibilityCriteria: { version: 1, criteria: [] }, }; jest.mocked(api.getPortalFacilityAmendment).mockResolvedValue(mockPortalAmendmentResponse); diff --git a/portal-api/src/v1/controllers/amendments/patch-amendment.test.ts b/portal-api/src/v1/controllers/amendments/patch-amendment.test.ts index e92d334d06..43e0ed010d 100644 --- a/portal-api/src/v1/controllers/amendments/patch-amendment.test.ts +++ b/portal-api/src/v1/controllers/amendments/patch-amendment.test.ts @@ -55,6 +55,7 @@ describe('controllers - facility amendment', () => { createdAt: 1702061978881, updatedAt: 1702061978881, status: PORTAL_AMENDMENT_STATUS.DRAFT, + eligibilityCriteria: { version: 1, criteria: [] }, }; jest.mocked(api.patchPortalFacilityAmendment).mockResolvedValue(mockPortalAmendmentResponse); diff --git a/portal-api/src/v1/controllers/amendments/put-amendment.test.ts b/portal-api/src/v1/controllers/amendments/put-amendment.test.ts index ae89019238..92fa3ed9f9 100644 --- a/portal-api/src/v1/controllers/amendments/put-amendment.test.ts +++ b/portal-api/src/v1/controllers/amendments/put-amendment.test.ts @@ -53,6 +53,7 @@ describe('controllers - facility amendment', () => { createdAt: 1702061978881, updatedAt: 1702061978881, status: PORTAL_AMENDMENT_STATUS.DRAFT, + eligibilityCriteria: { version: 1, criteria: [] }, }; jest.mocked(api.putPortalFacilityAmendment).mockResolvedValue(mockPortalAmendmentResponse); From da899ad6030b18ecea368fd389cb84be2352960e Mon Sep 17 00:00:00 2001 From: Beth Thomas Date: Tue, 21 Jan 2025 09:51:51 +0000 Subject: [PATCH 10/19] feat(DTFS2-7621): add eligibility criteria to mock data loader --- .../mock-data-loader/clean-all-tables-gef.js | 11 +++++ .../gef/eligibilityCriteriaAmendments.js | 43 +++++++++++++++++++ utils/mock-data-loader/gef/index.js | 2 + utils/mock-data-loader/insert-mocks-gef.js | 6 +++ 4 files changed, 62 insertions(+) create mode 100644 utils/mock-data-loader/gef/eligibilityCriteriaAmendments.js diff --git a/utils/mock-data-loader/clean-all-tables-gef.js b/utils/mock-data-loader/clean-all-tables-gef.js index c7c58042c2..cb804c6eea 100644 --- a/utils/mock-data-loader/clean-all-tables-gef.js +++ b/utils/mock-data-loader/clean-all-tables-gef.js @@ -1,4 +1,6 @@ +const { MONGO_DB_COLLECTIONS } = require('@ukef/dtfs2-common'); const api = require('./gef/api'); +const { mongoDbClient } = require('../drivers/db-client'); const cleanEligibilityCriteria = async (token) => { console.info('cleaning GEF tables'); @@ -8,6 +10,14 @@ const cleanEligibilityCriteria = async (token) => { } }; +const cleanEligibilityCriteriaAmendments = async () => { + console.info('cleaning GEF tables'); + console.info('cleaning GEF eligibilityCriteriaAmendments'); + + const eligibilityCriteriaAmendmentsCollection = await mongoDbClient.getCollection(MONGO_DB_COLLECTIONS.ELIGIBILITY_CRITERIA_AMENDMENTS); + await eligibilityCriteriaAmendmentsCollection.deleteMany({}); +}; + const cleanMandatoryCriteriaVersioned = async (token) => { console.info('cleaning GEF mandatory-criteria-versioned'); @@ -28,6 +38,7 @@ const deleteCronJobs = async (token) => { const cleanAllTables = async (token) => { await cleanEligibilityCriteria(token); + await cleanEligibilityCriteriaAmendments(token); await cleanMandatoryCriteriaVersioned(token); await cleanDurableFunctions(token); await deleteCronJobs(token); diff --git a/utils/mock-data-loader/gef/eligibilityCriteriaAmendments.js b/utils/mock-data-loader/gef/eligibilityCriteriaAmendments.js new file mode 100644 index 0000000000..6b711462a7 --- /dev/null +++ b/utils/mock-data-loader/gef/eligibilityCriteriaAmendments.js @@ -0,0 +1,43 @@ +const { FACILITY_TYPE } = require('@ukef/dtfs2-common'); + +const ELIGIBILITY_CRITERIA_AMENDMENTS = [ + { + version: 1, + product: 'GEF', + isInDraft: false, + facilityType: [FACILITY_TYPE.CASH, FACILITY_TYPE.CONTINGENT], + createdAt: 1649876028968, + criteria: [ + { + id: 1, + text: 'The Facility is not an Affected Facility', + }, + { + id: 2, + text: 'Neither the Exporter, not its UK Parent Obligor is an Affected Person', + }, + { + id: 3, + text: 'The Cover Period of the Facility is within the Facility Maximum Cover Period', + }, + { + id: 4, + text: 'The Covered Facility Limit (converted for this purpose into the Master Guarantee Base Currency) of the Facility is not more than the lesser of (i) the Available Master Guarantee Limit; and the Available Obligor(s) Limit', + }, + { + id: 5, + text: 'The Bank has completed its Bank Due Diligence to its satisfaction in accordance with its policies and procedures without having to escalate any issue raised during its Bank Due Diligence internally to any Relevant Person for approval as part of its usual Bank Due Diligence', + }, + { + id: 6, + text: 'The Bank is the sole and legal beneficial owner of, and has good title to, the Facility and any Utilisation thereunder.', + }, + { + id: 7, + text: "The Bank's right, title and interest in and to the Facility and any Utilisation thereunder (including any indebtedness, obligation of liability of each Obligor) is free and clear of any Security or Quasi-Security (other than Permitted Security)", + }, + ], + }, +]; + +module.exports = ELIGIBILITY_CRITERIA_AMENDMENTS; diff --git a/utils/mock-data-loader/gef/index.js b/utils/mock-data-loader/gef/index.js index 179cda982f..b1ff9c077b 100644 --- a/utils/mock-data-loader/gef/index.js +++ b/utils/mock-data-loader/gef/index.js @@ -1,5 +1,6 @@ const MANDATORY_CRITERIA_VERSIONED = require('./mandatoryCriteriaVersioned'); const ELIGIBILITY_CRITERIA = require('./eligibilityCriteria'); +const ELIGIBILITY_CRITERIA_AMENDMENTS = require('./eligibilityCriteriaAmendments'); const APPLICATION = require('./application'); const EXPORTER = require('./exporter'); const FACILITIES = require('./facilities'); @@ -7,6 +8,7 @@ const FACILITIES = require('./facilities'); const MOCKS = { MANDATORY_CRITERIA_VERSIONED, ELIGIBILITY_CRITERIA, + ELIGIBILITY_CRITERIA_AMENDMENTS, EXPORTER, FACILITIES, APPLICATION, diff --git a/utils/mock-data-loader/insert-mocks-gef.js b/utils/mock-data-loader/insert-mocks-gef.js index 5b42f3c64f..0880a4cea7 100644 --- a/utils/mock-data-loader/insert-mocks-gef.js +++ b/utils/mock-data-loader/insert-mocks-gef.js @@ -1,7 +1,9 @@ +const { MONGO_DB_COLLECTIONS } = require('@ukef/dtfs2-common'); const portalApi = require('./api'); const api = require('./gef/api'); const MOCKS = require('./gef'); const { BANK1_MAKER1 } = require('./portal-users'); +const { mongoDbClient } = require('../drivers/db-client'); const insertMocks = async (token) => { console.info('inserting GEF mocks'); @@ -15,6 +17,10 @@ const insertMocks = async (token) => { await api.createEligibilityCriteria(item, token); } + console.info('inserting GEF eligibility-criteria-amendments'); + const eligibilityCriteriaAmendmentsCollection = await mongoDbClient.getCollection(MONGO_DB_COLLECTIONS.ELIGIBILITY_CRITERIA_AMENDMENTS); + await eligibilityCriteriaAmendmentsCollection.insertMany(MOCKS.ELIGIBILITY_CRITERIA_AMENDMENTS); + console.info('inserting GEF deals'); const allUsers = await portalApi.listUsers(token); From fb08f0da2c0e614a5bfd32a473a1e9a5815a9bed Mon Sep 17 00:00:00 2001 From: Beth Thomas Date: Tue, 21 Jan 2025 09:53:03 +0000 Subject: [PATCH 11/19] feat(DTFS2-7621): fix test namings --- .../portal/eligibility-criteria-amendments.repo.test.ts | 6 +++--- .../src/errors/eligibility-criteria-not-found.error.test.ts | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/dtfs-central-api/src/repositories/portal/eligibility-criteria-amendments.repo.test.ts b/dtfs-central-api/src/repositories/portal/eligibility-criteria-amendments.repo.test.ts index 245b558d06..7a778535e9 100644 --- a/dtfs-central-api/src/repositories/portal/eligibility-criteria-amendments.repo.test.ts +++ b/dtfs-central-api/src/repositories/portal/eligibility-criteria-amendments.repo.test.ts @@ -26,7 +26,7 @@ describe('EligibilityCriteriaAmendmentsRepo', () => { jest.spyOn(mongoDbClient, 'getCollection').mockImplementation(getCollectionMock); }); - it(`calls getCollection with ${MONGO_DB_COLLECTIONS.ELIGIBILITY_CRITERIA_AMENDMENTS}`, async () => { + it(`should call getCollection with ${MONGO_DB_COLLECTIONS.ELIGIBILITY_CRITERIA_AMENDMENTS}`, async () => { // Act await EligibilityCriteriaAmendmentsRepo.findLatestEligibilityCriteria(FACILITY_TYPE.CASH); @@ -35,7 +35,7 @@ describe('EligibilityCriteriaAmendmentsRepo', () => { expect(getCollectionMock).toHaveBeenCalledWith(MONGO_DB_COLLECTIONS.ELIGIBILITY_CRITERIA_AMENDMENTS); }); - it('calls find with the expected parameters', async () => { + it('should call find with the expected parameters', async () => { // Act const facilityType = FACILITY_TYPE.CASH; await EligibilityCriteriaAmendmentsRepo.findLatestEligibilityCriteria(facilityType); @@ -47,7 +47,7 @@ describe('EligibilityCriteriaAmendmentsRepo', () => { expect(findMock).toHaveBeenCalledWith(expectedFilter); }); - it('returns the found latest eligibility criteria', async () => { + it('should return the found latest eligibility criteria', async () => { // Act const facilityType = FACILITY_TYPE.CASH; const result = await EligibilityCriteriaAmendmentsRepo.findLatestEligibilityCriteria(facilityType); diff --git a/libs/common/src/errors/eligibility-criteria-not-found.error.test.ts b/libs/common/src/errors/eligibility-criteria-not-found.error.test.ts index c09d244e7b..dd71b62369 100644 --- a/libs/common/src/errors/eligibility-criteria-not-found.error.test.ts +++ b/libs/common/src/errors/eligibility-criteria-not-found.error.test.ts @@ -11,7 +11,7 @@ describe('EligibilityCriteriaNotFoundError', () => { expect(exception.message).toEqual('Eligibility criteria not found'); }); - it('exposes the 404 (Not Found) status code', () => { + it('should expose the 404 (Not Found) status code', () => { // Act const exception = new EligibilityCriteriaNotFoundError(); From 13ba88f8f44a3d5651105974bf79f09c9aff607f Mon Sep 17 00:00:00 2001 From: Beth Thomas Date: Wed, 22 Jan 2025 11:12:50 +0000 Subject: [PATCH 12/19] feat(DTFS2-7498): renamings and docstrings --- .../portal/eligibility-criteria-amendments.repo.test.ts | 4 ++-- .../portal/eligibility-criteria-amendments.repo.ts | 5 +++++ ...ndment.upsertPortalFacilityAmendmentDraft.service.test.ts | 4 ++-- .../test-data/eligibility-criteria-amendments.ts | 5 ++++- 4 files changed, 13 insertions(+), 5 deletions(-) diff --git a/dtfs-central-api/src/repositories/portal/eligibility-criteria-amendments.repo.test.ts b/dtfs-central-api/src/repositories/portal/eligibility-criteria-amendments.repo.test.ts index 7a778535e9..ec730c9857 100644 --- a/dtfs-central-api/src/repositories/portal/eligibility-criteria-amendments.repo.test.ts +++ b/dtfs-central-api/src/repositories/portal/eligibility-criteria-amendments.repo.test.ts @@ -1,7 +1,7 @@ import { AmendmentsEligibilityCriteria, FACILITY_TYPE, MONGO_DB_COLLECTIONS } from '@ukef/dtfs2-common'; import { mongoDbClient } from '../../drivers/db-client'; import { EligibilityCriteriaAmendmentsRepo } from './eligibility-criteria-amendments.repo'; -import { anAmendmentsEligibilityCriteria } from '../../../test-helpers/test-data/eligibility-criteria-amendments'; +import { amendmentsEligibilityCriteria } from '../../../test-helpers/test-data/eligibility-criteria-amendments'; describe('EligibilityCriteriaAmendmentsRepo', () => { const findToArrayMock = jest.fn(); @@ -13,7 +13,7 @@ describe('EligibilityCriteriaAmendmentsRepo', () => { }); describe('findLatestEligibilityCriteria', () => { - const eligibilityCriteria: AmendmentsEligibilityCriteria = anAmendmentsEligibilityCriteria(); + const eligibilityCriteria: AmendmentsEligibilityCriteria = amendmentsEligibilityCriteria(); beforeEach(() => { findToArrayMock.mockResolvedValue([eligibilityCriteria]); diff --git a/dtfs-central-api/src/repositories/portal/eligibility-criteria-amendments.repo.ts b/dtfs-central-api/src/repositories/portal/eligibility-criteria-amendments.repo.ts index bf9890416e..12a1318de4 100644 --- a/dtfs-central-api/src/repositories/portal/eligibility-criteria-amendments.repo.ts +++ b/dtfs-central-api/src/repositories/portal/eligibility-criteria-amendments.repo.ts @@ -2,12 +2,16 @@ import { Collection, WithoutId } from 'mongodb'; import { MONGO_DB_COLLECTIONS, AmendmentsEligibilityCriteria, EligibilityCriteriaNotFoundError, FacilityType } from '@ukef/dtfs2-common'; import { mongoDbClient } from '../../drivers/db-client'; +/** + * Repository to handle database operations for amendments eligibility criteria + */ export class EligibilityCriteriaAmendmentsRepo { private static async getCollection(): Promise>> { return await mongoDbClient.getCollection(MONGO_DB_COLLECTIONS.ELIGIBILITY_CRITERIA_AMENDMENTS); } /** + * Finds the portal amendments eligibility criteria for the given facility type with the latest version number * @param facilityType the facility type * @returns The latest portal amendments eligibility criteria for the given facility type */ @@ -21,6 +25,7 @@ export class EligibilityCriteriaAmendmentsRepo { .toArray(); if (!latestEligibilityCriteria) { + console.error('Unable to find latest eligibility criteria for facility type %s', facilityType); throw new EligibilityCriteriaNotFoundError(); } diff --git a/dtfs-central-api/src/services/portal/facility-amendment.upsertPortalFacilityAmendmentDraft.service.test.ts b/dtfs-central-api/src/services/portal/facility-amendment.upsertPortalFacilityAmendmentDraft.service.test.ts index 87989a8b07..e54df92030 100644 --- a/dtfs-central-api/src/services/portal/facility-amendment.upsertPortalFacilityAmendmentDraft.service.test.ts +++ b/dtfs-central-api/src/services/portal/facility-amendment.upsertPortalFacilityAmendmentDraft.service.test.ts @@ -10,7 +10,7 @@ import { HttpStatusCode } from 'axios'; import { getUnixTime } from 'date-fns'; import { generatePortalAuditDetails } from '@ukef/dtfs2-common/change-stream'; import { aPortalFacilityAmendmentUserValues } from '@ukef/dtfs2-common/mock-data-backend'; -import { anAmendmentsEligibilityCriteria } from '../../../test-helpers/test-data/eligibility-criteria-amendments'; +import { amendmentsEligibilityCriteria } from '../../../test-helpers/test-data/eligibility-criteria-amendments'; import { PortalFacilityAmendmentService } from './facility-amendment.service'; import { aFacility, aPortalUser } from '../../../test-helpers'; import { TfmFacilitiesRepo } from '../../repositories/tfm-facilities-repo'; @@ -36,7 +36,7 @@ const facilityId = new ObjectId().toString(); const amendment = aPortalFacilityAmendmentUserValues(); const auditDetails = generatePortalAuditDetails(aPortalUser()._id); const facility = aFacility(); -const eligibilityCriteria = anAmendmentsEligibilityCriteria(); +const eligibilityCriteria = amendmentsEligibilityCriteria(); describe('PortalFacilityAmendmentService', () => { beforeAll(() => { diff --git a/dtfs-central-api/test-helpers/test-data/eligibility-criteria-amendments.ts b/dtfs-central-api/test-helpers/test-data/eligibility-criteria-amendments.ts index fe472530e8..8a9fff2aa6 100644 --- a/dtfs-central-api/test-helpers/test-data/eligibility-criteria-amendments.ts +++ b/dtfs-central-api/test-helpers/test-data/eligibility-criteria-amendments.ts @@ -2,7 +2,10 @@ import { ObjectId } from 'mongodb'; import { AmendmentsEligibilityCriteria, DEAL_TYPE, FACILITY_TYPE } from '@ukef/dtfs2-common'; import { generateMockPortalUserAuditDatabaseRecord } from '@ukef/dtfs2-common/change-stream/test-helpers'; -export const anAmendmentsEligibilityCriteria = (): AmendmentsEligibilityCriteria => ({ +/** + * Instantiates an AmendmentsEligibilityCriteria object + */ +export const amendmentsEligibilityCriteria = (): AmendmentsEligibilityCriteria => ({ _id: new ObjectId(), version: 1, product: DEAL_TYPE.GEF, From 9cda99305b77566a701a1a543e57a689bcc6da94 Mon Sep 17 00:00:00 2001 From: Beth Thomas Date: Wed, 22 Jan 2025 11:15:26 +0000 Subject: [PATCH 13/19] feat(DTFS2-7498): remove audit record and product from eligibility criteria type --- .../test-data/eligibility-criteria-amendments.ts | 5 +---- .../types/mongo-db-models/amendments-eligibility-criteria.ts | 4 ---- utils/mock-data-loader/gef/eligibilityCriteriaAmendments.js | 1 - 3 files changed, 1 insertion(+), 9 deletions(-) diff --git a/dtfs-central-api/test-helpers/test-data/eligibility-criteria-amendments.ts b/dtfs-central-api/test-helpers/test-data/eligibility-criteria-amendments.ts index 8a9fff2aa6..85248c8c3b 100644 --- a/dtfs-central-api/test-helpers/test-data/eligibility-criteria-amendments.ts +++ b/dtfs-central-api/test-helpers/test-data/eligibility-criteria-amendments.ts @@ -1,6 +1,5 @@ import { ObjectId } from 'mongodb'; -import { AmendmentsEligibilityCriteria, DEAL_TYPE, FACILITY_TYPE } from '@ukef/dtfs2-common'; -import { generateMockPortalUserAuditDatabaseRecord } from '@ukef/dtfs2-common/change-stream/test-helpers'; +import { AmendmentsEligibilityCriteria, FACILITY_TYPE } from '@ukef/dtfs2-common'; /** * Instantiates an AmendmentsEligibilityCriteria object @@ -8,7 +7,6 @@ import { generateMockPortalUserAuditDatabaseRecord } from '@ukef/dtfs2-common/ch export const amendmentsEligibilityCriteria = (): AmendmentsEligibilityCriteria => ({ _id: new ObjectId(), version: 1, - product: DEAL_TYPE.GEF, facilityType: [FACILITY_TYPE.CASH, FACILITY_TYPE.CONTINGENT], isInDraft: false, createdAt: new Date().getTime(), @@ -16,5 +14,4 @@ export const amendmentsEligibilityCriteria = (): AmendmentsEligibilityCriteria = { id: 1, text: 'item 1', textList: ['item 1'] }, { id: 2, text: 'item 2' }, ], - auditRecord: generateMockPortalUserAuditDatabaseRecord(new ObjectId()), }); diff --git a/libs/common/src/types/mongo-db-models/amendments-eligibility-criteria.ts b/libs/common/src/types/mongo-db-models/amendments-eligibility-criteria.ts index 6d8d6b11ed..445d8a0135 100644 --- a/libs/common/src/types/mongo-db-models/amendments-eligibility-criteria.ts +++ b/libs/common/src/types/mongo-db-models/amendments-eligibility-criteria.ts @@ -1,7 +1,5 @@ import { ObjectId } from 'mongodb'; -import { DealType } from '../deal-type'; import { FacilityType } from '../facility-type'; -import { AuditDatabaseRecord } from '../audit-database-record'; import { UnixTimestampMilliseconds } from '../date'; export type AmendmentsEligibilityCriterion = { id: number; text: string; textList?: string[] }; @@ -12,10 +10,8 @@ export type AmendmentsEligibilityCriterion = { id: number; text: string; textLis export type AmendmentsEligibilityCriteria = { _id: ObjectId; version: number; - product: DealType; facilityType: FacilityType[]; isInDraft: boolean; createdAt: UnixTimestampMilliseconds; criteria: AmendmentsEligibilityCriterion[]; - auditRecord?: AuditDatabaseRecord; }; diff --git a/utils/mock-data-loader/gef/eligibilityCriteriaAmendments.js b/utils/mock-data-loader/gef/eligibilityCriteriaAmendments.js index 6b711462a7..6de9ca40be 100644 --- a/utils/mock-data-loader/gef/eligibilityCriteriaAmendments.js +++ b/utils/mock-data-loader/gef/eligibilityCriteriaAmendments.js @@ -3,7 +3,6 @@ const { FACILITY_TYPE } = require('@ukef/dtfs2-common'); const ELIGIBILITY_CRITERIA_AMENDMENTS = [ { version: 1, - product: 'GEF', isInDraft: false, facilityType: [FACILITY_TYPE.CASH, FACILITY_TYPE.CONTINGENT], createdAt: 1649876028968, From 2b387cc897b59621cc3255f7c674577f2e7bd600 Mon Sep 17 00:00:00 2001 From: Beth Thomas Date: Wed, 22 Jan 2025 11:32:05 +0000 Subject: [PATCH 14/19] feat(DTFS2-7498): more review mark ups --- utils/mock-data-loader/clean-all-tables-gef.js | 3 +++ utils/mock-data-loader/insert-mocks-gef.js | 5 ++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/utils/mock-data-loader/clean-all-tables-gef.js b/utils/mock-data-loader/clean-all-tables-gef.js index cb804c6eea..bc0e023d69 100644 --- a/utils/mock-data-loader/clean-all-tables-gef.js +++ b/utils/mock-data-loader/clean-all-tables-gef.js @@ -10,6 +10,9 @@ const cleanEligibilityCriteria = async (token) => { } }; +/** + * Deletes all entries in the eligibilityCriteriaAmendments collection + */ const cleanEligibilityCriteriaAmendments = async () => { console.info('cleaning GEF tables'); console.info('cleaning GEF eligibilityCriteriaAmendments'); diff --git a/utils/mock-data-loader/insert-mocks-gef.js b/utils/mock-data-loader/insert-mocks-gef.js index 0880a4cea7..8a4bdcf7e0 100644 --- a/utils/mock-data-loader/insert-mocks-gef.js +++ b/utils/mock-data-loader/insert-mocks-gef.js @@ -17,7 +17,10 @@ const insertMocks = async (token) => { await api.createEligibilityCriteria(item, token); } - console.info('inserting GEF eligibility-criteria-amendments'); + /** + * Inserts mock entries into the eligibilityCriteriaAmendments collection + */ + console.info('Inserting GEF eligibility criteria amendments'); const eligibilityCriteriaAmendmentsCollection = await mongoDbClient.getCollection(MONGO_DB_COLLECTIONS.ELIGIBILITY_CRITERIA_AMENDMENTS); await eligibilityCriteriaAmendmentsCollection.insertMany(MOCKS.ELIGIBILITY_CRITERIA_AMENDMENTS); From e04f956b14eeb9c52ab85b4f6b87a243b0ce1da2 Mon Sep 17 00:00:00 2001 From: Beth Thomas Date: Wed, 22 Jan 2025 13:49:12 +0000 Subject: [PATCH 15/19] feat(DTFS2-7498): more review mark ups --- .../amendment-put.api-test.ts | 68 ++++++++++++++++++- ...igibility-criteria-amendments.repo.test.ts | 19 +++++- .../portal/facility-amendment.service.ts | 4 +- .../eligibility-criteria-amendments.ts | 16 +++-- 4 files changed, 96 insertions(+), 11 deletions(-) diff --git a/dtfs-central-api/api-tests/v1/portal-facility-amendments/amendment-put.api-test.ts b/dtfs-central-api/api-tests/v1/portal-facility-amendments/amendment-put.api-test.ts index 0281c8bc30..200d278e0b 100644 --- a/dtfs-central-api/api-tests/v1/portal-facility-amendments/amendment-put.api-test.ts +++ b/dtfs-central-api/api-tests/v1/portal-facility-amendments/amendment-put.api-test.ts @@ -11,6 +11,8 @@ import aDeal from '../deal-builder'; import { aPortalUser } from '../../mocks/test-users/portal-user'; import { createPortalUser } from '../../helpers/create-portal-user'; import { createPortalFacilityAmendment } from '../../helpers/create-portal-facility-amendment'; +import { amendmentsEligibilityCriteria } from '../../../test-helpers/test-data/eligibility-criteria-amendments'; +import { mongoDbClient as db } from '../../../src/drivers/db-client'; const originalEnv = { ...process.env }; @@ -22,18 +24,29 @@ const generateUrl = (facilityId: string): string => { return `/v1/portal/facilities/${facilityId}/amendments/`; }; +console.error = jest.fn(); + const newDeal = aDeal({ dealType: DEAL_TYPE.GEF, submissionType: DEAL_SUBMISSION_TYPE.AIN, }) as AnyObject; +const draftCashEligibilityCriteria = amendmentsEligibilityCriteria(1.5, [FACILITY_TYPE.CASH, FACILITY_TYPE.CONTINGENT], true); +const latestCashEligibilityCriteria = amendmentsEligibilityCriteria(1.2, [FACILITY_TYPE.CASH, FACILITY_TYPE.CONTINGENT]); +const legacyCashEligibilityCriteria = amendmentsEligibilityCriteria(1, [FACILITY_TYPE.CASH, FACILITY_TYPE.CONTINGENT]); +const otherEligibilityCriteria = amendmentsEligibilityCriteria(2, [FACILITY_TYPE.LOAN]); + +const eligibilityCriteriaList = [draftCashEligibilityCriteria, legacyCashEligibilityCriteria, latestCashEligibilityCriteria, otherEligibilityCriteria]; + describe('PUT /v1/portal/facilities/:facilityId/amendments/', () => { let dealId: string; let facilityId: string; + let portalUserId: string; beforeAll(async () => { - await wipeDB.wipe([MONGO_DB_COLLECTIONS.FACILITIES, MONGO_DB_COLLECTIONS.TFM_FACILITIES]); + await wipeDB.wipe([MONGO_DB_COLLECTIONS.FACILITIES, MONGO_DB_COLLECTIONS.TFM_FACILITIES, MONGO_DB_COLLECTIONS.ELIGIBILITY_CRITERIA_AMENDMENTS]); + await db.getCollection(MONGO_DB_COLLECTIONS.ELIGIBILITY_CRITERIA_AMENDMENTS).then((c) => c.insertMany(eligibilityCriteriaList)); portalUserId = (await createPortalUser())._id; }); @@ -124,7 +137,13 @@ describe('PUT /v1/portal/facilities/:facilityId/amendments/', () => { // Assert expect(status).toEqual(HttpStatusCode.Ok); - expect(body).toEqual(expect.objectContaining({ amendmentId: expect.any(String) as string, facilityId, dealId })); + expect(body).toEqual( + expect.objectContaining({ + amendmentId: expect.any(String) as string, + facilityId, + dealId, + }), + ); }); it('should overwrite any existing amendment', async () => { @@ -150,5 +169,50 @@ describe('PUT /v1/portal/facilities/:facilityId/amendments/', () => { message: `Amendment not found: ${existingAmendmentId} on facility: ${facilityId}`, }); }); + + it('should return the new amendment with the latest non-draft eligibility criteria for the facility type', async () => { + // Act + const { body, status } = (await testApi + .put({ dealId, amendment: aPortalFacilityAmendmentUserValues(), auditDetails: generatePortalAuditDetails(portalUserId) }) + .to(generateUrl(facilityId))) as FacilityAmendmentResponse; + + // Assert + const latestCriteriaForFacilityType = latestCashEligibilityCriteria.criteria.map((criterion) => ({ ...criterion, answer: null })); + const latestVersionForFacilityType = latestCashEligibilityCriteria.version; + + expect(status).toEqual(HttpStatusCode.Ok); + expect(body).toEqual( + expect.objectContaining({ + amendmentId: expect.any(String) as string, + facilityId, + dealId, + eligibilityCriteria: { version: latestVersionForFacilityType, criteria: latestCriteriaForFacilityType }, + }), + ); + }); + + it('should throw an error if no eligibility criteria exists in the db for the given facility type', async () => { + // Arrange + await wipeDB.wipe([MONGO_DB_COLLECTIONS.ELIGIBILITY_CRITERIA_AMENDMENTS]); + const eligibilityCriteriaNoCash = [ + amendmentsEligibilityCriteria(1, [FACILITY_TYPE.CONTINGENT]), + amendmentsEligibilityCriteria(2, [FACILITY_TYPE.CASH], true), + amendmentsEligibilityCriteria(1.5, [FACILITY_TYPE.BOND, FACILITY_TYPE.LOAN]), + ]; + await db.getCollection(MONGO_DB_COLLECTIONS.ELIGIBILITY_CRITERIA_AMENDMENTS).then((c) => c.insertMany(eligibilityCriteriaNoCash)); + + // Act + const { body, status } = (await testApi + .put({ dealId, amendment: aPortalFacilityAmendmentUserValues(), auditDetails: generatePortalAuditDetails(portalUserId) }) + .to(generateUrl(facilityId))) as FacilityAmendmentResponse; + + // Assert + expect(status).toEqual(HttpStatusCode.NotFound); + expect(body).toEqual({ + status: HttpStatusCode.NotFound, + message: 'Eligibility criteria not found', + }); + expect(console.error).toHaveBeenCalledWith('Unable to find latest eligibility criteria for facility type %s', FACILITY_TYPE.CASH); + }); }); }); diff --git a/dtfs-central-api/src/repositories/portal/eligibility-criteria-amendments.repo.test.ts b/dtfs-central-api/src/repositories/portal/eligibility-criteria-amendments.repo.test.ts index ec730c9857..3cfe956f50 100644 --- a/dtfs-central-api/src/repositories/portal/eligibility-criteria-amendments.repo.test.ts +++ b/dtfs-central-api/src/repositories/portal/eligibility-criteria-amendments.repo.test.ts @@ -18,7 +18,6 @@ describe('EligibilityCriteriaAmendmentsRepo', () => { beforeEach(() => { findToArrayMock.mockResolvedValue([eligibilityCriteria]); findMock.mockReturnValue({ sort: () => ({ limit: () => ({ toArray: findToArrayMock }) }) }); - getCollectionMock.mockResolvedValue({ find: findMock, }); @@ -47,7 +46,23 @@ describe('EligibilityCriteriaAmendmentsRepo', () => { expect(findMock).toHaveBeenCalledWith(expectedFilter); }); - it('should return the found latest eligibility criteria', async () => { + it.only('should return the found latest eligibility criteria if one exists matching the facility type', async () => { + // Arrange + const latestCashEligibilityCriteria = amendmentsEligibilityCriteria(1.2, [FACILITY_TYPE.CASH, FACILITY_TYPE.CONTINGENT]); + const legacyCashEligibilityCriteria = amendmentsEligibilityCriteria(1.7, [FACILITY_TYPE.CASH, FACILITY_TYPE.CONTINGENT]); + const otherEligibilityCriteria = amendmentsEligibilityCriteria(2, [FACILITY_TYPE.BOND]); + + findToArrayMock.mockResolvedValueOnce([latestCashEligibilityCriteria, legacyCashEligibilityCriteria, otherEligibilityCriteria]); + + // Act + const facilityType = FACILITY_TYPE.CASH; + const result = await EligibilityCriteriaAmendmentsRepo.findLatestEligibilityCriteria(facilityType); + + // Assert + expect(result).toEqual(legacyCashEligibilityCriteria); + }); + + it('should throw an error if the ', async () => { // Act const facilityType = FACILITY_TYPE.CASH; const result = await EligibilityCriteriaAmendmentsRepo.findLatestEligibilityCriteria(facilityType); diff --git a/dtfs-central-api/src/services/portal/facility-amendment.service.ts b/dtfs-central-api/src/services/portal/facility-amendment.service.ts index e7d02fbc43..3f9f48e6ec 100644 --- a/dtfs-central-api/src/services/portal/facility-amendment.service.ts +++ b/dtfs-central-api/src/services/portal/facility-amendment.service.ts @@ -43,7 +43,9 @@ export class PortalFacilityAmendmentService { const { type: facilityType } = await findOneFacility(facilityId); - const { version, criteria } = await EligibilityCriteriaAmendmentsRepo.findLatestEligibilityCriteria(facilityType); + const eligibilityCriteria = await EligibilityCriteriaAmendmentsRepo.findLatestEligibilityCriteria(facilityType); + + const { version, criteria } = eligibilityCriteria; const updatedCriteria = criteria.map((criterion) => ({ ...criterion, answer: null })); diff --git a/dtfs-central-api/test-helpers/test-data/eligibility-criteria-amendments.ts b/dtfs-central-api/test-helpers/test-data/eligibility-criteria-amendments.ts index 85248c8c3b..75582aacc1 100644 --- a/dtfs-central-api/test-helpers/test-data/eligibility-criteria-amendments.ts +++ b/dtfs-central-api/test-helpers/test-data/eligibility-criteria-amendments.ts @@ -1,15 +1,19 @@ import { ObjectId } from 'mongodb'; -import { AmendmentsEligibilityCriteria, FACILITY_TYPE } from '@ukef/dtfs2-common'; +import { AmendmentsEligibilityCriteria, FACILITY_TYPE, FacilityType, getEpochMs } from '@ukef/dtfs2-common'; /** * Instantiates an AmendmentsEligibilityCriteria object */ -export const amendmentsEligibilityCriteria = (): AmendmentsEligibilityCriteria => ({ +export const amendmentsEligibilityCriteria = ( + version: number = 1, + facilityType: FacilityType[] = [FACILITY_TYPE.CASH, FACILITY_TYPE.CONTINGENT], + isInDraft: boolean = false, +): AmendmentsEligibilityCriteria => ({ _id: new ObjectId(), - version: 1, - facilityType: [FACILITY_TYPE.CASH, FACILITY_TYPE.CONTINGENT], - isInDraft: false, - createdAt: new Date().getTime(), + version, + facilityType, + isInDraft, + createdAt: getEpochMs(), criteria: [ { id: 1, text: 'item 1', textList: ['item 1'] }, { id: 2, text: 'item 2' }, From ae7564fb9e648475d4d478fd1c076c37651f6aff Mon Sep 17 00:00:00 2001 From: Beth Thomas Date: Wed, 22 Jan 2025 15:32:59 +0000 Subject: [PATCH 16/19] feat(DTFS2-7621): revert accidental test change --- ...igibility-criteria-amendments.repo.test.ts | 19 ++----------------- .../portal/facility-amendment.service.ts | 4 +--- 2 files changed, 3 insertions(+), 20 deletions(-) diff --git a/dtfs-central-api/src/repositories/portal/eligibility-criteria-amendments.repo.test.ts b/dtfs-central-api/src/repositories/portal/eligibility-criteria-amendments.repo.test.ts index 3cfe956f50..ec730c9857 100644 --- a/dtfs-central-api/src/repositories/portal/eligibility-criteria-amendments.repo.test.ts +++ b/dtfs-central-api/src/repositories/portal/eligibility-criteria-amendments.repo.test.ts @@ -18,6 +18,7 @@ describe('EligibilityCriteriaAmendmentsRepo', () => { beforeEach(() => { findToArrayMock.mockResolvedValue([eligibilityCriteria]); findMock.mockReturnValue({ sort: () => ({ limit: () => ({ toArray: findToArrayMock }) }) }); + getCollectionMock.mockResolvedValue({ find: findMock, }); @@ -46,23 +47,7 @@ describe('EligibilityCriteriaAmendmentsRepo', () => { expect(findMock).toHaveBeenCalledWith(expectedFilter); }); - it.only('should return the found latest eligibility criteria if one exists matching the facility type', async () => { - // Arrange - const latestCashEligibilityCriteria = amendmentsEligibilityCriteria(1.2, [FACILITY_TYPE.CASH, FACILITY_TYPE.CONTINGENT]); - const legacyCashEligibilityCriteria = amendmentsEligibilityCriteria(1.7, [FACILITY_TYPE.CASH, FACILITY_TYPE.CONTINGENT]); - const otherEligibilityCriteria = amendmentsEligibilityCriteria(2, [FACILITY_TYPE.BOND]); - - findToArrayMock.mockResolvedValueOnce([latestCashEligibilityCriteria, legacyCashEligibilityCriteria, otherEligibilityCriteria]); - - // Act - const facilityType = FACILITY_TYPE.CASH; - const result = await EligibilityCriteriaAmendmentsRepo.findLatestEligibilityCriteria(facilityType); - - // Assert - expect(result).toEqual(legacyCashEligibilityCriteria); - }); - - it('should throw an error if the ', async () => { + it('should return the found latest eligibility criteria', async () => { // Act const facilityType = FACILITY_TYPE.CASH; const result = await EligibilityCriteriaAmendmentsRepo.findLatestEligibilityCriteria(facilityType); diff --git a/dtfs-central-api/src/services/portal/facility-amendment.service.ts b/dtfs-central-api/src/services/portal/facility-amendment.service.ts index 3f9f48e6ec..e7d02fbc43 100644 --- a/dtfs-central-api/src/services/portal/facility-amendment.service.ts +++ b/dtfs-central-api/src/services/portal/facility-amendment.service.ts @@ -43,9 +43,7 @@ export class PortalFacilityAmendmentService { const { type: facilityType } = await findOneFacility(facilityId); - const eligibilityCriteria = await EligibilityCriteriaAmendmentsRepo.findLatestEligibilityCriteria(facilityType); - - const { version, criteria } = eligibilityCriteria; + const { version, criteria } = await EligibilityCriteriaAmendmentsRepo.findLatestEligibilityCriteria(facilityType); const updatedCriteria = criteria.map((criterion) => ({ ...criterion, answer: null })); From 45fee29c10ae8122498dd7c9d29580576a92ca38 Mon Sep 17 00:00:00 2001 From: Beth Thomas Date: Wed, 22 Jan 2025 16:03:34 +0000 Subject: [PATCH 17/19] feat(DTFS2-7621): change types to interface --- .../mongo-db-models/amendments-eligibility-criteria.ts | 10 +++++++--- .../common/src/types/mongo-db-models/tfm-facilities.ts | 4 ++-- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/libs/common/src/types/mongo-db-models/amendments-eligibility-criteria.ts b/libs/common/src/types/mongo-db-models/amendments-eligibility-criteria.ts index 445d8a0135..c2c6fdcdb2 100644 --- a/libs/common/src/types/mongo-db-models/amendments-eligibility-criteria.ts +++ b/libs/common/src/types/mongo-db-models/amendments-eligibility-criteria.ts @@ -2,16 +2,20 @@ import { ObjectId } from 'mongodb'; import { FacilityType } from '../facility-type'; import { UnixTimestampMilliseconds } from '../date'; -export type AmendmentsEligibilityCriterion = { id: number; text: string; textList?: string[] }; +export interface AmendmentsEligibilityCriterion { + id: number; + text: string; + textList?: string[]; +} /** * Type of the mongo db "eligibilityCriteriaAmendments" collection */ -export type AmendmentsEligibilityCriteria = { +export interface AmendmentsEligibilityCriteria { _id: ObjectId; version: number; facilityType: FacilityType[]; isInDraft: boolean; createdAt: UnixTimestampMilliseconds; criteria: AmendmentsEligibilityCriterion[]; -}; +} diff --git a/libs/common/src/types/mongo-db-models/tfm-facilities.ts b/libs/common/src/types/mongo-db-models/tfm-facilities.ts index 7190a4484d..96748a686d 100644 --- a/libs/common/src/types/mongo-db-models/tfm-facilities.ts +++ b/libs/common/src/types/mongo-db-models/tfm-facilities.ts @@ -121,9 +121,9 @@ export interface TfmFacilityAmendment extends BaseAmendment { }; } -export type AmendmentsEligibilityCriterionWithAnswer = AmendmentsEligibilityCriterion & { +export interface AmendmentsEligibilityCriterionWithAnswer extends AmendmentsEligibilityCriterion { answer: boolean | null; -}; +} /** * Amendments created in Portal From d78a3cd22c0d7867a6f3c08ac37c22deee3ecad6 Mon Sep 17 00:00:00 2001 From: Beth Thomas Date: Wed, 22 Jan 2025 17:14:39 +0000 Subject: [PATCH 18/19] feat(DTFS2-7621): fix api tests --- .../portal-facility-amendments/amendment-get.api-test.ts | 7 ++++++- .../portal-facility-amendments/amendment-patch.api-test.ts | 7 ++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/dtfs-central-api/api-tests/v1/portal-facility-amendments/amendment-get.api-test.ts b/dtfs-central-api/api-tests/v1/portal-facility-amendments/amendment-get.api-test.ts index 0cae87a260..adcee83196 100644 --- a/dtfs-central-api/api-tests/v1/portal-facility-amendments/amendment-get.api-test.ts +++ b/dtfs-central-api/api-tests/v1/portal-facility-amendments/amendment-get.api-test.ts @@ -18,6 +18,8 @@ import aDeal from '../deal-builder'; import { aPortalUser } from '../../mocks/test-users/portal-user'; import { createPortalUser } from '../../helpers/create-portal-user'; import { createPortalFacilityAmendment } from '../../helpers/create-portal-facility-amendment'; +import { mongoDbClient as db } from '../../../src/drivers/db-client'; +import { amendmentsEligibilityCriteria } from '../../../test-helpers/test-data/eligibility-criteria-amendments'; const originalEnv = { ...process.env }; @@ -40,7 +42,10 @@ describe('GET /v1/portal/facilities/:facilityId/amendments/:amendmentId', () => let portalUserId: string; beforeAll(async () => { - await wipeDB.wipe([MONGO_DB_COLLECTIONS.FACILITIES, MONGO_DB_COLLECTIONS.TFM_FACILITIES]); + await wipeDB.wipe([MONGO_DB_COLLECTIONS.FACILITIES, MONGO_DB_COLLECTIONS.TFM_FACILITIES, MONGO_DB_COLLECTIONS.ELIGIBILITY_CRITERIA_AMENDMENTS]); + await db + .getCollection(MONGO_DB_COLLECTIONS.ELIGIBILITY_CRITERIA_AMENDMENTS) + .then((c) => c.insertOne(amendmentsEligibilityCriteria(1, [FACILITY_TYPE.CASH, FACILITY_TYPE.CONTINGENT]))); portalUserId = (await createPortalUser())._id; }); diff --git a/dtfs-central-api/api-tests/v1/portal-facility-amendments/amendment-patch.api-test.ts b/dtfs-central-api/api-tests/v1/portal-facility-amendments/amendment-patch.api-test.ts index 4ae2ece8c3..f414ff0a15 100644 --- a/dtfs-central-api/api-tests/v1/portal-facility-amendments/amendment-patch.api-test.ts +++ b/dtfs-central-api/api-tests/v1/portal-facility-amendments/amendment-patch.api-test.ts @@ -11,6 +11,8 @@ import aDeal from '../deal-builder'; import { aPortalUser } from '../../mocks/test-users/portal-user'; import { createPortalUser } from '../../helpers/create-portal-user'; import { createPortalFacilityAmendment } from '../../helpers/create-portal-facility-amendment'; +import { mongoDbClient as db } from '../../../src/drivers/db-client'; +import { amendmentsEligibilityCriteria } from '../../../test-helpers/test-data/eligibility-criteria-amendments'; const originalEnv = { ...process.env }; @@ -33,7 +35,10 @@ describe('PATCH /v1/portal/facilities/:facilityId/amendments/', () => { let portalUserId: string; beforeAll(async () => { - await wipeDB.wipe([MONGO_DB_COLLECTIONS.FACILITIES, MONGO_DB_COLLECTIONS.TFM_FACILITIES]); + await wipeDB.wipe([MONGO_DB_COLLECTIONS.FACILITIES, MONGO_DB_COLLECTIONS.TFM_FACILITIES, MONGO_DB_COLLECTIONS.ELIGIBILITY_CRITERIA_AMENDMENTS]); + await db + .getCollection(MONGO_DB_COLLECTIONS.ELIGIBILITY_CRITERIA_AMENDMENTS) + .then((c) => c.insertOne(amendmentsEligibilityCriteria(1, [FACILITY_TYPE.CASH, FACILITY_TYPE.CONTINGENT]))); portalUserId = (await createPortalUser())._id; }); From d745a4c7b06440c33a2404538d3af0fd864353a1 Mon Sep 17 00:00:00 2001 From: Beth Thomas Date: Thu, 23 Jan 2025 08:59:41 +0000 Subject: [PATCH 19/19] feat(DTFS2-7621): tiny renaming --- .../v1/portal-facility-amendments/amendment-get.api-test.ts | 2 +- .../v1/portal-facility-amendments/amendment-patch.api-test.ts | 2 +- .../v1/portal-facility-amendments/amendment-put.api-test.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/dtfs-central-api/api-tests/v1/portal-facility-amendments/amendment-get.api-test.ts b/dtfs-central-api/api-tests/v1/portal-facility-amendments/amendment-get.api-test.ts index adcee83196..ba44d4851b 100644 --- a/dtfs-central-api/api-tests/v1/portal-facility-amendments/amendment-get.api-test.ts +++ b/dtfs-central-api/api-tests/v1/portal-facility-amendments/amendment-get.api-test.ts @@ -45,7 +45,7 @@ describe('GET /v1/portal/facilities/:facilityId/amendments/:amendmentId', () => await wipeDB.wipe([MONGO_DB_COLLECTIONS.FACILITIES, MONGO_DB_COLLECTIONS.TFM_FACILITIES, MONGO_DB_COLLECTIONS.ELIGIBILITY_CRITERIA_AMENDMENTS]); await db .getCollection(MONGO_DB_COLLECTIONS.ELIGIBILITY_CRITERIA_AMENDMENTS) - .then((c) => c.insertOne(amendmentsEligibilityCriteria(1, [FACILITY_TYPE.CASH, FACILITY_TYPE.CONTINGENT]))); + .then((collection) => collection.insertOne(amendmentsEligibilityCriteria(1, [FACILITY_TYPE.CASH, FACILITY_TYPE.CONTINGENT]))); portalUserId = (await createPortalUser())._id; }); diff --git a/dtfs-central-api/api-tests/v1/portal-facility-amendments/amendment-patch.api-test.ts b/dtfs-central-api/api-tests/v1/portal-facility-amendments/amendment-patch.api-test.ts index f414ff0a15..262ac5e51e 100644 --- a/dtfs-central-api/api-tests/v1/portal-facility-amendments/amendment-patch.api-test.ts +++ b/dtfs-central-api/api-tests/v1/portal-facility-amendments/amendment-patch.api-test.ts @@ -38,7 +38,7 @@ describe('PATCH /v1/portal/facilities/:facilityId/amendments/', () => { await wipeDB.wipe([MONGO_DB_COLLECTIONS.FACILITIES, MONGO_DB_COLLECTIONS.TFM_FACILITIES, MONGO_DB_COLLECTIONS.ELIGIBILITY_CRITERIA_AMENDMENTS]); await db .getCollection(MONGO_DB_COLLECTIONS.ELIGIBILITY_CRITERIA_AMENDMENTS) - .then((c) => c.insertOne(amendmentsEligibilityCriteria(1, [FACILITY_TYPE.CASH, FACILITY_TYPE.CONTINGENT]))); + .then((collection) => collection.insertOne(amendmentsEligibilityCriteria(1, [FACILITY_TYPE.CASH, FACILITY_TYPE.CONTINGENT]))); portalUserId = (await createPortalUser())._id; }); diff --git a/dtfs-central-api/api-tests/v1/portal-facility-amendments/amendment-put.api-test.ts b/dtfs-central-api/api-tests/v1/portal-facility-amendments/amendment-put.api-test.ts index 200d278e0b..490bf4ddb0 100644 --- a/dtfs-central-api/api-tests/v1/portal-facility-amendments/amendment-put.api-test.ts +++ b/dtfs-central-api/api-tests/v1/portal-facility-amendments/amendment-put.api-test.ts @@ -46,7 +46,7 @@ describe('PUT /v1/portal/facilities/:facilityId/amendments/', () => { beforeAll(async () => { await wipeDB.wipe([MONGO_DB_COLLECTIONS.FACILITIES, MONGO_DB_COLLECTIONS.TFM_FACILITIES, MONGO_DB_COLLECTIONS.ELIGIBILITY_CRITERIA_AMENDMENTS]); - await db.getCollection(MONGO_DB_COLLECTIONS.ELIGIBILITY_CRITERIA_AMENDMENTS).then((c) => c.insertMany(eligibilityCriteriaList)); + await db.getCollection(MONGO_DB_COLLECTIONS.ELIGIBILITY_CRITERIA_AMENDMENTS).then((collection) => collection.insertMany(eligibilityCriteriaList)); portalUserId = (await createPortalUser())._id; });