Skip to content

Commit 95cf9a4

Browse files
committed
chore: e2e add supplementary obligation decrease
1 parent 4813b3d commit 95cf9a4

File tree

16 files changed

+1093
-145
lines changed

16 files changed

+1093
-145
lines changed

bciers/apps/compliance-e2e/poms/compliance-setup.ts

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { Page } from "@playwright/test";
22
import { pool } from "@bciers/e2e/utils/pool";
33

4+
export type InvoiceGenerationGateState = "open" | "closed";
5+
46
export class ComplianceSetupPOM {
57
readonly page: Page;
68

@@ -9,16 +11,17 @@ export class ComplianceSetupPOM {
911
}
1012

1113
/**
12-
* Force invoice_generation_date to be:
13-
* - yesterday → integration allowed
14-
* - far future → integration blocked
14+
* Prime the invoice-generation gate by setting invoice_generation_date to be:
15+
* - "open" → yesterday → integration allowed
16+
* - "closed" → far future → integration blocked
1517
*/
16-
async openInvoiceGenerationDate(isReached: boolean) {
18+
async primeInvoiceGenerationGate(state: InvoiceGenerationGateState) {
1719
const reportingYear = new Date().getFullYear() - 1;
1820

19-
const invoiceGenerationDate = isReached
20-
? new Date(Date.now() - 24 * 60 * 60 * 1000) // yesterday => reached
21-
: new Date(Date.now() + 50 * 365 * 24 * 60 * 60 * 1000); // far future => not reached
21+
const invoiceGenerationDate =
22+
state === "open"
23+
? new Date(Date.now() - 24 * 60 * 60 * 1000) // yesterday => reached
24+
: new Date(Date.now() + 50 * 365 * 24 * 60 * 60 * 1000); // far future => not reached
2225

2326
const res = await pool.query({
2427
text: `
@@ -34,7 +37,7 @@ export class ComplianceSetupPOM {
3437

3538
if (res.rowCount !== 1) {
3639
throw new Error(
37-
`openInvoiceGenerationDate: expected 1 row for reporting_year=${reportingYear}, got ${res.rowCount}`,
40+
`primeInvoiceGenerationGate: expected 1 row for reporting_year=${reportingYear}, got ${res.rowCount}`,
3841
);
3942
}
4043

bciers/apps/compliance-e2e/poms/compliance-summaries.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,24 @@ export class ComplianceSummariesPOM {
216216
return text;
217217
}
218218

219+
async assertRowCountForOperation(options: {
220+
operationName: string;
221+
expectedCount: number;
222+
}) {
223+
const { operationName, expectedCount } = options;
224+
225+
await this.waitForGridReady({ timeout: 30_000 });
226+
227+
await expect(async () => {
228+
const rows = this.grid()
229+
.getByRole("row")
230+
.filter({ hasText: operationName });
231+
232+
const count = await rows.count();
233+
expect(count).toBe(expectedCount);
234+
}).toPass({ timeout: 30_000 });
235+
}
236+
219237
async assertStatusForOperation(
220238
operationName: string,
221239
expectedStatus: string,
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import { Locator, Page, expect } from "@playwright/test";
2+
import {
3+
COMPLIANCE_INVOICE_API_BASE,
4+
ComplianceInvoiceType,
5+
GENERATE_INVOICE_BUTTON_TEXT,
6+
} from "@/compliance-e2e/utils/constants";
7+
8+
export class ReviewComplianceObligationPOM {
9+
private readonly page: Page;
10+
private readonly invoiceButton: Locator;
11+
12+
constructor(page: Page) {
13+
this.page = page;
14+
this.invoiceButton = this.page.getByRole("button", {
15+
name: GENERATE_INVOICE_BUTTON_TEXT,
16+
exact: true,
17+
});
18+
}
19+
20+
/**
21+
* Clicks "Generate Invoice" and returns the PDF buffer.
22+
* Throws if the response is JSON (error path) or not OK.
23+
*/
24+
25+
async generateInvoiceAndGetPdfBuffer(
26+
complianceReportVersionId: number | string,
27+
type: ComplianceInvoiceType,
28+
): Promise<Buffer> {
29+
const id = String(complianceReportVersionId).trim();
30+
const invoiceUrl = new RegExp(
31+
`${COMPLIANCE_INVOICE_API_BASE}/${id}/${type}(\\?|$)`,
32+
);
33+
const [response] = await Promise.all([
34+
this.page.waitForResponse((r) => invoiceUrl.test(r.url()), {
35+
timeout: 60_000,
36+
}),
37+
this.clickGenerateInvoice(),
38+
]);
39+
40+
const contentType = response.headers()["content-type"] ?? "";
41+
42+
expect(response.ok()).toBe(true);
43+
expect(contentType).not.toMatch(/application\/json/i);
44+
expect(contentType).toMatch(/application\/pdf|application\/octet-stream/i);
45+
46+
return Buffer.from(await response.body());
47+
}
48+
49+
private async clickGenerateInvoice(): Promise<void> {
50+
await expect(this.invoiceButton).toBeVisible({ timeout: 30_000 });
51+
await expect(this.invoiceButton).toBeEnabled({ timeout: 30_000 });
52+
await this.invoiceButton.click();
53+
}
54+
55+
private escapeRegex(value: string): string {
56+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
57+
}
58+
}

bciers/apps/compliance-e2e/tests/workflows/manage-obligation/report-compliance-obligation.spec.ts

Lines changed: 0 additions & 75 deletions
This file was deleted.
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
import { setupBeforeEachTest } from "@bciers/e2e/setupBeforeEach";
2+
import { UserRole } from "@bciers/e2e/utils/enums";
3+
import {
4+
ComplianceOperations,
5+
ComplianceDisplayStatus,
6+
GridActionText,
7+
} from "@/compliance-e2e/utils/enums";
8+
import { REVIEW_OBLIGATION_URL_PATTERN } from "@/compliance-e2e/utils/constants";
9+
10+
import { CurrentReportsPOM } from "@/reporting-e2e/poms/current-reports";
11+
import { ComplianceSummariesPOM } from "@/compliance-e2e/poms/compliance-summaries";
12+
import { ManageObligationTaskListPOM } from "@/compliance-e2e/poms/manage-obligation/tasklist";
13+
import { PaymentInstructionsPOM } from "@/compliance-e2e/poms/manage-obligation/payment-instructions";
14+
15+
import { ComplianceSetupPOM } from "@/compliance-e2e/poms/compliance-setup";
16+
import { takeStabilizedScreenshot } from "@bciers/e2e/utils/helpers";
17+
18+
// 👤 run test using the storageState for role UserRole.INDUSTRY_USER_ADMIN
19+
const test = setupBeforeEachTest(UserRole.INDUSTRY_USER_ADMIN);
20+
21+
test.describe.configure({ mode: "serial" });
22+
23+
test.describe("Test compliance report version manage obligation flow", () => {
24+
test("when invoice_generation_date is reached, compliance report version status is OBLIGATION_NOT_MET", async ({
25+
page,
26+
request,
27+
happoScreenshot,
28+
}) => {
29+
// PRECONDITIONS:
30+
// Prime invoice_generation_date DB state so integration is allowed
31+
const complianceSetupPOM = new ComplianceSetupPOM(page);
32+
await complianceSetupPOM.primeInvoiceGenerationGate("open");
33+
34+
// Submit obligation report
35+
const reports = new CurrentReportsPOM(page);
36+
await reports.submitReportObligation(false, request);
37+
38+
// Navigate to the compliance summaries grid
39+
const summaries = new ComplianceSummariesPOM(page);
40+
await summaries.route();
41+
42+
// Assert the gate open for invoice integration
43+
await summaries.assertStatusForOperation(
44+
ComplianceOperations.OBLIGATION_NOT_MET,
45+
ComplianceDisplayStatus.OBLIGATION_NOT_MET,
46+
);
47+
48+
// happo screenshot
49+
await takeStabilizedScreenshot(happoScreenshot, page, {
50+
component: "Compliance obligation not met",
51+
variant: "status: Obligation - not met",
52+
});
53+
54+
// Assert the record has an invoice number related to this obligation
55+
await summaries.openActionForOperation({
56+
operationName: ComplianceOperations.OBLIGATION_NOT_MET,
57+
linkName: GridActionText.MANAGE_OBLIGATION,
58+
urlPattern: REVIEW_OBLIGATION_URL_PATTERN,
59+
});
60+
61+
const manageObligationTaskList = new ManageObligationTaskListPOM(page);
62+
await manageObligationTaskList.clickDownloadPaymentInstructions();
63+
64+
const paymentInstructions = new PaymentInstructionsPOM(page);
65+
await paymentInstructions.assertHasInvoiceNumber();
66+
67+
// happo screenshot
68+
await takeStabilizedScreenshot(happoScreenshot, page, {
69+
component: "Compliance obligation not met",
70+
variant: "payment instructions invoice number",
71+
});
72+
});
73+
74+
test("when invoice_generation_date is NOT reached, compliance report version status is PENDING_INVOICE_CREATION", async ({
75+
page,
76+
request,
77+
happoScreenshot,
78+
}) => {
79+
// PRECONDITIONS:
80+
// Prime invoice_generation_date DB state so integration is blocked
81+
const complianceSetupPOM = new ComplianceSetupPOM(page);
82+
await complianceSetupPOM.primeInvoiceGenerationGate("closed");
83+
84+
// Submit obligation report
85+
const reports = new CurrentReportsPOM(page);
86+
await reports.submitReportObligation(false, request);
87+
88+
// Navigate to the compliance summaries grid
89+
const summaries = new ComplianceSummariesPOM(page);
90+
await summaries.route();
91+
92+
// Assert the gate blocked invoice integration
93+
await summaries.assertStatusForOperation(
94+
ComplianceOperations.OBLIGATION_NOT_MET,
95+
ComplianceDisplayStatus.PENDING_INVOICE_CREATION,
96+
);
97+
98+
// happo screenshot
99+
await takeStabilizedScreenshot(happoScreenshot, page, {
100+
component: "Compliance obligation not met",
101+
variant: "status: Obligation - pending invoice creation",
102+
});
103+
});
104+
});
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import { setupBeforeEachTest } from "@bciers/e2e/setupBeforeEach";
2+
import { UserRole } from "@bciers/e2e/utils/enums";
3+
import {
4+
ComplianceOperations,
5+
ComplianceDisplayStatus,
6+
GridActionText,
7+
} from "@/compliance-e2e/utils/enums";
8+
import { CurrentReportsPOM } from "@/reporting-e2e/poms/current-reports";
9+
import { ComplianceSummariesPOM } from "@/compliance-e2e/poms/compliance-summaries";
10+
import { ComplianceSetupPOM } from "@/compliance-e2e/poms/compliance-setup";
11+
import { ReportSetUpPOM } from "@/reporting-e2e/poms/report-setup";
12+
import { REVIEW_OBLIGATION_URL_PATTERN } from "@/compliance-e2e/utils/constants";
13+
14+
// 👤 run test using the storageState for role UserRole.INDUSTRY_USER_ADMIN
15+
const test = setupBeforeEachTest(UserRole.INDUSTRY_USER_ADMIN);
16+
17+
test.describe.configure({ mode: "serial" });
18+
test.describe("Test supplementary compliance report version flow", () => {
19+
test("submitting a supplementary obligation decrease does not create a new version and does not void the invoice", async ({
20+
page,
21+
request,
22+
}) => {
23+
test.slow();
24+
25+
// Preconditions
26+
const complianceSetupPOM = new ComplianceSetupPOM(page);
27+
await complianceSetupPOM.primeInvoiceGenerationGate("open");
28+
29+
const reportSetUpPOM = new ReportSetUpPOM(page);
30+
await reportSetUpPOM.primeReportingYear("open");
31+
32+
// Submit obligation
33+
const gridReportingReports = new CurrentReportsPOM(page);
34+
await gridReportingReports.submitReportObligation(false, request);
35+
36+
// Assert versions in summaries grid
37+
const gridComplianceSummaries = new ComplianceSummariesPOM(page);
38+
await gridComplianceSummaries.route();
39+
40+
await gridComplianceSummaries.assertRowCountForOperation({
41+
operationName: ComplianceOperations.OBLIGATION_NOT_MET,
42+
expectedCount: 1,
43+
});
44+
45+
// Assert BEFORE generate invoice
46+
// Open summary and generate invoice
47+
await gridComplianceSummaries.openActionForOperation({
48+
operationName: ComplianceOperations.OBLIGATION_NOT_MET,
49+
linkName: GridActionText.MANAGE_OBLIGATION,
50+
urlPattern: REVIEW_OBLIGATION_URL_PATTERN,
51+
});
52+
// const reviewObligationPOM = new ReviewComplianceObligationPOM(page);
53+
// Capture invoice pdf as buffer
54+
// const pdfBuffer = await reviewObligationPOM.generateInvoiceAndGetPdfBuffer(
55+
// 2,
56+
// "obligation",
57+
// );
58+
// // create invoice object from buffer
59+
// const invoice = await ObligationInvoicePOM.fromBuffer(pdfBuffer);
60+
61+
// invoice
62+
// .assertHasInvoiceNumber()
63+
// .assertHasFeesAndAdjustments()
64+
// .assertHasAdjustmentLine()
65+
// .assertNotVoid();
66+
67+
// Submit supplementary decrease
68+
await gridReportingReports.route();
69+
await gridReportingReports.supplementaryReportObligationDecrease(request);
70+
71+
// Assert versions in summaries grid
72+
await gridComplianceSummaries.route();
73+
await gridComplianceSummaries.assertRowCountForOperation({
74+
operationName: ComplianceOperations.OBLIGATION_NOT_MET,
75+
expectedCount: 1,
76+
});
77+
78+
await gridComplianceSummaries.assertStatusForOperation(
79+
ComplianceOperations.OBLIGATION_NOT_MET,
80+
ComplianceDisplayStatus.OBLIGATION_NOT_MET,
81+
);
82+
83+
// Assert AFTER generate invoice
84+
await gridComplianceSummaries.openActionForOperation({
85+
operationName: ComplianceOperations.OBLIGATION_NOT_MET,
86+
linkName: GridActionText.MANAGE_OBLIGATION,
87+
urlPattern: REVIEW_OBLIGATION_URL_PATTERN,
88+
});
89+
});
90+
});

0 commit comments

Comments
 (0)