Skip to content

Commit d766304

Browse files
authored
#4390 - CAS Manual Intervention (e2e tests) (#4478)
### As a part of this PR, the following e2e tests have been written: - **CASInvoiceAESTController(e2e)-updateInvoiceToResolved** √ Should resolve an invoice in Manual intervention status for the provided invoice identifier. √ Should throw a HttpStatus Not Found (404) error when the CAS invoice to resolve doesn't exist. √ Should throw a HttpStatus Not Found (404) error when the CAS invoice to resolve does not have the expected Manual intervention status. √ Should throw a HttpStatus Forbidden (403) error when an unauthorized Ministry user tries to update the invoice. - **CASInvoiceAESTController(e2e)-getInvoices** √ Should be able to get invoices for the first page in a paginated result with a limit of two per page with Manual intervention status in the descending order. √ Should throw a HttpStatus Bad Request (400) error when the invoice status is invalid. √ Should throw a HttpStatus Bad Request (400) error when the sortField is invalid. √ Should throw a HttpStatus Forbidden (403) error when an unauthorized Ministry user tries to get the invoices. **Screenshots for the above e2e test execution:** <img width="1059" alt="image" src="https://github.com/user-attachments/assets/fb5ed187-aa04-46a3-87b7-63c4fd5d90d6" /> --------------------------------------------------------------------------------------------------------------------- <img width="1145" alt="image" src="https://github.com/user-attachments/assets/968e1bc4-8818-46fc-93c4-d59d682e1b02" />
1 parent d617d3c commit d766304

File tree

4 files changed

+499
-20
lines changed

4 files changed

+499
-20
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,257 @@
1+
import { HttpStatus, INestApplication } from "@nestjs/common";
2+
import * as request from "supertest";
3+
import {
4+
AESTGroups,
5+
BEARER_AUTH_TYPE,
6+
createTestingAppModule,
7+
getAESTToken,
8+
} from "../../../../testHelpers";
9+
import {
10+
createE2EDataSources,
11+
createFakeCASInvoiceBatch,
12+
createFakeDisbursementValue,
13+
E2EDataSources,
14+
saveFakeInvoiceFromDisbursementReceipt,
15+
saveFakeInvoiceIntoBatchWithInvoiceDetails,
16+
} from "@sims/test-utils";
17+
import { SystemUsersService } from "@sims/services";
18+
import {
19+
CASInvoiceStatus,
20+
DisbursementValueType,
21+
OfferingIntensity,
22+
SupplierStatus,
23+
} from "@sims/sims-db";
24+
import { CASInvoiceAPIOutDTO } from "apps/api/src/route-controllers/cas-invoice/models/cas-invoice.dto";
25+
import { addDays } from "@sims/utilities";
26+
import { Between, In } from "typeorm";
27+
28+
/**
29+
* Use a period that will never be reached to delete all existing invoice batches
30+
* and allow the retrieval of invoice batches to be tested.
31+
*/
32+
const CAS_INVOICE_STATUS_LAST_UPDATED_ON_START_DATE = new Date("2080-01-01");
33+
const CAS_INVOICE_STATUS_LAST_UPDATED_ON_END_DATE = new Date("2080-02-01");
34+
35+
describe("CASInvoiceAESTController(e2e)-getInvoices", () => {
36+
let app: INestApplication;
37+
let db: E2EDataSources;
38+
let systemUsersService: SystemUsersService;
39+
40+
beforeAll(async () => {
41+
const { nestApplication, dataSource } = await createTestingAppModule();
42+
app = nestApplication;
43+
systemUsersService = app.get(SystemUsersService);
44+
db = createE2EDataSources(dataSource);
45+
});
46+
47+
beforeEach(async () => {
48+
// Delete existing CAS invoices and invoice details created between the CAS_INVOICE_STATUS_LAST_UPDATED_ON_START_DATE and the CAS_INVOICE_STATUS_LAST_UPDATED_ON_END_DATE dates.
49+
const invoicesToDelete = await db.casInvoice.find({
50+
select: { id: true },
51+
where: {
52+
invoiceStatusUpdatedOn: Between(
53+
CAS_INVOICE_STATUS_LAST_UPDATED_ON_START_DATE,
54+
CAS_INVOICE_STATUS_LAST_UPDATED_ON_END_DATE,
55+
),
56+
},
57+
});
58+
const invoiceIDsToDelete = invoicesToDelete.map((invoice) => invoice.id);
59+
await db.casInvoiceDetail.delete({
60+
casInvoice: In(invoiceIDsToDelete),
61+
});
62+
await db.casInvoice.delete({
63+
invoiceStatusUpdatedOn: Between(
64+
CAS_INVOICE_STATUS_LAST_UPDATED_ON_START_DATE,
65+
CAS_INVOICE_STATUS_LAST_UPDATED_ON_END_DATE,
66+
),
67+
});
68+
});
69+
70+
it(`Should be able to get invoices for the first page in a paginated result with a limit of two per page with ${CASInvoiceStatus.ManualIntervention} status in the descending order.`, async () => {
71+
// Arrange
72+
// Create CAS invoice batch.
73+
const casInvoiceBatch = await db.casInvoiceBatch.save(
74+
createFakeCASInvoiceBatch({
75+
creator: systemUsersService.systemUser,
76+
}),
77+
);
78+
// Creates Part-time application with receipts, and invoices details.
79+
const casInvoice = await saveFakeInvoiceIntoBatchWithInvoiceDetails(
80+
db,
81+
{
82+
casInvoiceBatch,
83+
creator: systemUsersService.systemUser,
84+
// Part-time BC grants.
85+
disbursementValues: [
86+
createFakeDisbursementValue(
87+
DisbursementValueType.BCGrant,
88+
"BCAG",
89+
200,
90+
{
91+
effectiveAmount: 150,
92+
},
93+
),
94+
],
95+
},
96+
{
97+
offeringIntensity: OfferingIntensity.partTime,
98+
casSupplierInitialValues: {
99+
supplierStatus: SupplierStatus.VerifiedManually,
100+
supplierNumber: "222222",
101+
},
102+
casInvoiceInitialValues: {
103+
invoiceStatusUpdatedOn: addDays(
104+
5,
105+
CAS_INVOICE_STATUS_LAST_UPDATED_ON_START_DATE,
106+
),
107+
},
108+
},
109+
);
110+
const disbursementReceipt = casInvoice.disbursementReceipt;
111+
const casSupplier = casInvoice.casSupplier;
112+
await saveFakeInvoiceFromDisbursementReceipt(
113+
db,
114+
{
115+
casInvoiceBatch,
116+
creator: systemUsersService.systemUser,
117+
provincialDisbursementReceipt: disbursementReceipt,
118+
casSupplier,
119+
},
120+
{
121+
casInvoiceInitialValues: {
122+
invoiceStatus: CASInvoiceStatus.ManualIntervention,
123+
invoiceStatusUpdatedOn: addDays(
124+
2,
125+
CAS_INVOICE_STATUS_LAST_UPDATED_ON_START_DATE,
126+
),
127+
},
128+
},
129+
);
130+
const thirdInvoice = await saveFakeInvoiceFromDisbursementReceipt(
131+
db,
132+
{
133+
casInvoiceBatch,
134+
creator: systemUsersService.systemUser,
135+
provincialDisbursementReceipt: disbursementReceipt,
136+
casSupplier,
137+
},
138+
{
139+
casInvoiceInitialValues: {
140+
invoiceStatus: CASInvoiceStatus.ManualIntervention,
141+
invoiceStatusUpdatedOn: addDays(
142+
10,
143+
CAS_INVOICE_STATUS_LAST_UPDATED_ON_START_DATE,
144+
),
145+
errors: ["Error 1", "Error 2"],
146+
},
147+
},
148+
);
149+
const fourthInvoice = await saveFakeInvoiceFromDisbursementReceipt(
150+
db,
151+
{
152+
casInvoiceBatch,
153+
creator: systemUsersService.systemUser,
154+
provincialDisbursementReceipt: disbursementReceipt,
155+
casSupplier,
156+
},
157+
{
158+
casInvoiceInitialValues: {
159+
invoiceStatus: CASInvoiceStatus.ManualIntervention,
160+
invoiceStatusUpdatedOn: addDays(
161+
20,
162+
CAS_INVOICE_STATUS_LAST_UPDATED_ON_START_DATE,
163+
),
164+
},
165+
},
166+
);
167+
const endpoint = `/aest/cas-invoice?page=0&pageLimit=2&sortField=invoiceStatusUpdatedOn&sortOrder=DESC&invoiceStatusSearch=${CASInvoiceStatus.ManualIntervention}`;
168+
const token = await getAESTToken(AESTGroups.BusinessAdministrators);
169+
170+
// Act/Assert
171+
const response = await request(app.getHttpServer())
172+
.get(endpoint)
173+
.auth(token, BEARER_AUTH_TYPE)
174+
.expect(HttpStatus.OK);
175+
const invoices = response.body.results as CASInvoiceAPIOutDTO[];
176+
expect(invoices).toStrictEqual([
177+
{
178+
errors: null,
179+
id: fourthInvoice.id,
180+
invoiceBatchName: fourthInvoice.casInvoiceBatch.batchName,
181+
invoiceNumber: fourthInvoice.invoiceNumber,
182+
invoiceStatusUpdatedOn:
183+
fourthInvoice.invoiceStatusUpdatedOn.toISOString(),
184+
supplierNumber: fourthInvoice.casSupplier.supplierNumber,
185+
},
186+
{
187+
errors: thirdInvoice.errors,
188+
id: thirdInvoice.id,
189+
invoiceBatchName: thirdInvoice.casInvoiceBatch.batchName,
190+
invoiceNumber: thirdInvoice.invoiceNumber,
191+
invoiceStatusUpdatedOn:
192+
thirdInvoice.invoiceStatusUpdatedOn.toISOString(),
193+
supplierNumber: thirdInvoice.casSupplier.supplierNumber,
194+
},
195+
]);
196+
});
197+
198+
it("Should throw a HttpStatus Bad Request (400) error when the invoice status is invalid.", async () => {
199+
// Arrange
200+
const endpoint =
201+
"/aest/cas-invoice?page=0&pageLimit=10&sortField=invoiceStatusUpdatedOn&sortOrder=DESC&invoiceStatusSearch=SomeInvalidStatus";
202+
const token = await getAESTToken(AESTGroups.BusinessAdministrators);
203+
204+
// Act/Assert
205+
await request(app.getHttpServer())
206+
.get(endpoint)
207+
.auth(token, BEARER_AUTH_TYPE)
208+
.expect(HttpStatus.BAD_REQUEST)
209+
.expect({
210+
statusCode: HttpStatus.BAD_REQUEST,
211+
message: [
212+
"invoiceStatusSearch must be one of the following values: Pending, Sent, Manual intervention, Resolved",
213+
],
214+
error: "Bad Request",
215+
});
216+
});
217+
218+
it("Should throw a HttpStatus Bad Request (400) error when the sortField is invalid.", async () => {
219+
// Arrange
220+
const endpoint = `/aest/cas-invoice?page=0&pageLimit=10&sortField=SomeInvalidField&sortOrder=DESC&invoiceStatusSearch=Pending`;
221+
const token = await getAESTToken(AESTGroups.BusinessAdministrators);
222+
223+
// Act/Assert
224+
await request(app.getHttpServer())
225+
.get(endpoint)
226+
.auth(token, BEARER_AUTH_TYPE)
227+
.expect(HttpStatus.BAD_REQUEST)
228+
.expect({
229+
statusCode: HttpStatus.BAD_REQUEST,
230+
message: [
231+
"sortField must be one of the following values: invoiceStatusUpdatedOn",
232+
],
233+
error: "Bad Request",
234+
});
235+
});
236+
237+
it("Should throw a HttpStatus Forbidden (403) error when an unauthorized Ministry user tries to get the invoices.", async () => {
238+
// Arrange
239+
const endpoint = "/aest/cas-invoice";
240+
const token = await getAESTToken(AESTGroups.Operations);
241+
242+
// Act/Assert
243+
await request(app.getHttpServer())
244+
.get(endpoint)
245+
.auth(token, BEARER_AUTH_TYPE)
246+
.expect(HttpStatus.FORBIDDEN)
247+
.expect({
248+
statusCode: HttpStatus.FORBIDDEN,
249+
message: "Forbidden resource",
250+
error: "Forbidden",
251+
});
252+
});
253+
254+
afterAll(async () => {
255+
await app?.close();
256+
});
257+
});

0 commit comments

Comments
 (0)