- Introduced `.prettierrc.json` to enforce consistent code formatting across the project. - Updated unit and e2e test files to address formatting issues, improve readability, and ensure alignment with the new Prettier configuration.
498 lines
13 KiB
JavaScript
498 lines
13 KiB
JavaScript
import { expect, test } from "@playwright/test";
|
|
import { mockApi } from "./support/network.js";
|
|
|
|
function json(body, status = 200) {
|
|
return {
|
|
status,
|
|
contentType: "application/json",
|
|
body: JSON.stringify(body),
|
|
};
|
|
}
|
|
|
|
function apiMessage(message, status = 422) {
|
|
return json(
|
|
{
|
|
data: {
|
|
message,
|
|
},
|
|
},
|
|
status
|
|
);
|
|
}
|
|
|
|
async function suppressVueDevtoolsOverlay(page) {
|
|
await page.addInitScript(() => {
|
|
const STYLE_ID = "__e2e-hide-vue-devtools";
|
|
|
|
const apply = () => {
|
|
if (!document.getElementById(STYLE_ID)) {
|
|
const style = document.createElement("style");
|
|
style.id = STYLE_ID;
|
|
style.textContent =
|
|
"#__vue-devtools-container__, .vue-devtools__anchor-btn, .vue-devtools__panel-content { display: none !important; visibility: hidden !important; pointer-events: none !important; }";
|
|
(document.head || document.documentElement).appendChild(style);
|
|
}
|
|
|
|
const container = document.getElementById("__vue-devtools-container__");
|
|
if (container) {
|
|
container.style.display = "none";
|
|
container.style.pointerEvents = "none";
|
|
}
|
|
};
|
|
|
|
apply();
|
|
const observer = new MutationObserver(apply);
|
|
observer.observe(document.documentElement, { childList: true, subtree: true });
|
|
});
|
|
}
|
|
|
|
async function openHarness(page) {
|
|
await page.goto("/__e2e/economic-queue");
|
|
await expect(page.getByTestId("economic-queue-harness-page")).toBeVisible();
|
|
}
|
|
|
|
test.describe("Economic queue async export workflow", () => {
|
|
test.beforeEach(async ({ page }) => {
|
|
await suppressVueDevtoolsOverlay(page);
|
|
await mockApi(page);
|
|
});
|
|
|
|
test("invoice export enqueues, polls, completes, and stops polling in terminal state", async ({ page }) => {
|
|
let statusCalls = 0;
|
|
|
|
await page.route("**/economic/invoice/export", async (route) => {
|
|
if (route.request().method() !== "POST") {
|
|
await route.fallback();
|
|
return;
|
|
}
|
|
|
|
await route.fulfill(
|
|
json(
|
|
{
|
|
data: {
|
|
job: {
|
|
id: 1101,
|
|
status: "QUEUED",
|
|
progress_percent: 0,
|
|
progress_message: "Queued export",
|
|
},
|
|
},
|
|
},
|
|
202
|
|
)
|
|
);
|
|
});
|
|
|
|
await page.route("**/economic/invoice/export/status**", async (route) => {
|
|
if (route.request().method() !== "GET") {
|
|
await route.fallback();
|
|
return;
|
|
}
|
|
|
|
statusCalls += 1;
|
|
if (statusCalls === 1) {
|
|
await route.fulfill(
|
|
json({
|
|
data: {
|
|
id: 1101,
|
|
status: "PROCESSING",
|
|
progress_percent: 40,
|
|
progress_message: "Preparing invoice payload",
|
|
},
|
|
})
|
|
);
|
|
return;
|
|
}
|
|
|
|
await route.fulfill(
|
|
json({
|
|
data: {
|
|
id: 1101,
|
|
status: "COMPLETED",
|
|
progress_percent: 100,
|
|
progress_message: "Completed",
|
|
result: {
|
|
message: "Invoice export completed by queue worker.",
|
|
},
|
|
},
|
|
})
|
|
);
|
|
});
|
|
|
|
await openHarness(page);
|
|
|
|
const submitButton = page.getByTestId("economic-invoice-export-submit");
|
|
await submitButton.click();
|
|
await expect(submitButton).toBeDisabled();
|
|
await expect(page.getByTestId("economic-invoice-export-progress")).toContainText("Preparing invoice payload");
|
|
|
|
await expect(page.getByTestId("economic-invoice-export-completed")).toContainText(
|
|
"Invoice export completed by queue worker."
|
|
);
|
|
await expect(page.getByTestId("economic-invoice-export-completed")).toBeVisible();
|
|
|
|
const terminalCallCount = statusCalls;
|
|
await page.waitForTimeout(2300);
|
|
expect(statusCalls).toBe(terminalCallCount);
|
|
});
|
|
|
|
test("invoice export shows queue failure message and supports retry to completion", async ({ page }) => {
|
|
let phase = "initial";
|
|
let retryStatusCalls = 0;
|
|
|
|
await page.route("**/economic/invoice/export", async (route) => {
|
|
if (route.request().method() !== "POST") {
|
|
await route.fallback();
|
|
return;
|
|
}
|
|
|
|
await route.fulfill(
|
|
json(
|
|
{
|
|
data: {
|
|
job: {
|
|
id: 1201,
|
|
status: "QUEUED",
|
|
progress_percent: 0,
|
|
progress_message: "Queued export",
|
|
},
|
|
},
|
|
},
|
|
202
|
|
)
|
|
);
|
|
});
|
|
|
|
await page.route("**/economic/invoice/export/retry", async (route) => {
|
|
if (route.request().method() !== "POST") {
|
|
await route.fallback();
|
|
return;
|
|
}
|
|
|
|
phase = "retry";
|
|
await route.fulfill(
|
|
json(
|
|
{
|
|
data: {
|
|
job: {
|
|
id: 1201,
|
|
status: "QUEUED",
|
|
progress_percent: 0,
|
|
progress_message: "Retry queued",
|
|
},
|
|
},
|
|
},
|
|
200
|
|
)
|
|
);
|
|
});
|
|
|
|
await page.route("**/economic/invoice/export/status**", async (route) => {
|
|
if (route.request().method() !== "GET") {
|
|
await route.fallback();
|
|
return;
|
|
}
|
|
|
|
if (phase === "initial") {
|
|
await route.fulfill(
|
|
json({
|
|
data: {
|
|
id: 1201,
|
|
status: "FAILED",
|
|
progress_percent: 10,
|
|
progress_message: "Failed",
|
|
error_message: "no billable order items after zero-price/zero-quantity skips",
|
|
},
|
|
})
|
|
);
|
|
return;
|
|
}
|
|
|
|
retryStatusCalls += 1;
|
|
if (retryStatusCalls === 1) {
|
|
await route.fulfill(
|
|
json({
|
|
data: {
|
|
id: 1201,
|
|
status: "PROCESSING",
|
|
progress_percent: 70,
|
|
progress_message: "Retry processing",
|
|
},
|
|
})
|
|
);
|
|
return;
|
|
}
|
|
|
|
await route.fulfill(
|
|
json({
|
|
data: {
|
|
id: 1201,
|
|
status: "COMPLETED",
|
|
progress_percent: 100,
|
|
progress_message: "Retry completed",
|
|
result: {
|
|
message: "Invoice export completed after retry.",
|
|
},
|
|
},
|
|
})
|
|
);
|
|
});
|
|
|
|
await openHarness(page);
|
|
|
|
await page.getByTestId("economic-invoice-export-submit").click();
|
|
await expect(page.getByTestId("economic-invoice-export-failed")).toContainText(
|
|
"no billable order items after zero-price/zero-quantity skips"
|
|
);
|
|
|
|
await page.getByTestId("economic-invoice-export-retry").click();
|
|
await expect(page.getByTestId("economic-invoice-export-progress")).toContainText("Retry processing");
|
|
await expect(page.getByTestId("economic-invoice-export-completed")).toContainText(
|
|
"Invoice export completed after retry."
|
|
);
|
|
});
|
|
|
|
test("draft export surfaces API transport errors separately from queue failure state", async ({ page }) => {
|
|
await page.route("**/economic/invoice/draft/export", async (route) => {
|
|
if (route.request().method() !== "POST") {
|
|
await route.fallback();
|
|
return;
|
|
}
|
|
|
|
await route.fulfill(apiMessage("order_id is invalid", 422));
|
|
});
|
|
|
|
await openHarness(page);
|
|
|
|
await page.getByTestId("economic-draft-export-submit").click();
|
|
await expect(page.getByTestId("economic-draft-export-transport-error")).toContainText("order_id is invalid");
|
|
await expect(page.getByTestId("economic-draft-export-failed")).toHaveCount(0);
|
|
});
|
|
|
|
test("collected invoice create action enqueues and completes with returned result", async ({ page }) => {
|
|
let statusCalls = 0;
|
|
|
|
await page.route("**/collected-invoices/economic", async (route) => {
|
|
if (route.request().method() !== "POST") {
|
|
await route.fallback();
|
|
return;
|
|
}
|
|
|
|
const payload = route.request().postDataJSON();
|
|
expect(payload).toMatchObject({
|
|
id: 6001,
|
|
send_as_is: false,
|
|
});
|
|
|
|
await route.fulfill(
|
|
json(
|
|
{
|
|
data: {
|
|
job: {
|
|
id: 1301,
|
|
status: "QUEUED",
|
|
progress_percent: 0,
|
|
progress_message: "Collected invoice queued",
|
|
},
|
|
},
|
|
},
|
|
202
|
|
)
|
|
);
|
|
});
|
|
|
|
await page.route("**/collected-invoices/economic/queue/status**", async (route) => {
|
|
if (route.request().method() !== "GET") {
|
|
await route.fallback();
|
|
return;
|
|
}
|
|
|
|
statusCalls += 1;
|
|
if (statusCalls === 1) {
|
|
await route.fulfill(
|
|
json({
|
|
data: {
|
|
id: 1301,
|
|
status: "PROCESSING",
|
|
progress_percent: 55,
|
|
progress_message: "Building e-conomic payload",
|
|
},
|
|
})
|
|
);
|
|
return;
|
|
}
|
|
|
|
await route.fulfill(
|
|
json({
|
|
data: {
|
|
id: 1301,
|
|
status: "COMPLETED",
|
|
progress_percent: 100,
|
|
progress_message: "Completed",
|
|
result: {
|
|
message: "Collected invoice exported.",
|
|
},
|
|
},
|
|
})
|
|
);
|
|
});
|
|
|
|
await openHarness(page);
|
|
|
|
await page.getByTestId("collected-economic-create-invoice").click();
|
|
await expect(page.getByTestId("collected-economic-progress")).toContainText("Building e-conomic payload");
|
|
await expect(page.getByTestId("collected-economic-completed")).toContainText("Collected invoice exported.");
|
|
});
|
|
|
|
test("collected invoice create accepts enqueue responses with data.job_id alias", async ({ page }) => {
|
|
let statusCalls = 0;
|
|
|
|
await page.route("**/collected-invoices/economic", async (route) => {
|
|
if (route.request().method() !== "POST") {
|
|
await route.fallback();
|
|
return;
|
|
}
|
|
|
|
await route.fulfill(
|
|
json(
|
|
{
|
|
data: {
|
|
message: "Queued collected invoice export",
|
|
job_id: 1302,
|
|
job: {
|
|
status: "QUEUED",
|
|
progress_percent: 0,
|
|
progress_message: "Collected invoice queued",
|
|
},
|
|
},
|
|
},
|
|
202
|
|
)
|
|
);
|
|
});
|
|
|
|
await page.route("**/collected-invoices/economic/queue/status**", async (route) => {
|
|
if (route.request().method() !== "GET") {
|
|
await route.fallback();
|
|
return;
|
|
}
|
|
|
|
statusCalls += 1;
|
|
if (statusCalls === 1) {
|
|
await route.fulfill(
|
|
json({
|
|
data: {
|
|
id: 1302,
|
|
status: "PROCESSING",
|
|
progress_percent: 65,
|
|
progress_message: "Creating draft invoice",
|
|
},
|
|
})
|
|
);
|
|
return;
|
|
}
|
|
|
|
await route.fulfill(
|
|
json({
|
|
data: {
|
|
id: 1302,
|
|
status: "COMPLETED",
|
|
progress_percent: 100,
|
|
progress_message: "Completed",
|
|
result: {
|
|
message: "Collected invoice exported from alias response.",
|
|
},
|
|
},
|
|
})
|
|
);
|
|
});
|
|
|
|
await openHarness(page);
|
|
|
|
await page.getByTestId("collected-economic-create-invoice").click();
|
|
await expect(page.getByTestId("collected-economic-progress")).toContainText("Creating draft invoice");
|
|
await expect(page.getByTestId("collected-economic-completed")).toContainText(
|
|
"Collected invoice exported from alias response."
|
|
);
|
|
});
|
|
|
|
test("invalid boolean send_as_is is blocked and shown as validation error", async ({ page }) => {
|
|
let enqueueCalls = 0;
|
|
|
|
await page.route("**/collected-invoices/economic", async (route) => {
|
|
if (route.request().method() !== "POST") {
|
|
await route.fallback();
|
|
return;
|
|
}
|
|
|
|
enqueueCalls += 1;
|
|
await route.fulfill(apiMessage("This endpoint should not be called for invalid boolean.", 500));
|
|
});
|
|
|
|
await openHarness(page);
|
|
|
|
await page.getByTestId("economic-queue-harness-invalid-boolean-trigger").click();
|
|
await expect(page.getByTestId("economic-queue-harness-invalid-boolean-error")).toContainText(
|
|
"send_as_is must be a boolean."
|
|
);
|
|
expect(enqueueCalls).toBe(0);
|
|
});
|
|
|
|
test("invoice export polling is cancelled when component unmounts", async ({ page }) => {
|
|
let statusCalls = 0;
|
|
|
|
await page.route("**/economic/invoice/export", async (route) => {
|
|
if (route.request().method() !== "POST") {
|
|
await route.fallback();
|
|
return;
|
|
}
|
|
|
|
await route.fulfill(
|
|
json(
|
|
{
|
|
data: {
|
|
job: {
|
|
id: 1401,
|
|
status: "QUEUED",
|
|
progress_percent: 0,
|
|
progress_message: "Queued export",
|
|
},
|
|
},
|
|
},
|
|
202
|
|
)
|
|
);
|
|
});
|
|
|
|
await page.route("**/economic/invoice/export/status**", async (route) => {
|
|
if (route.request().method() !== "GET") {
|
|
await route.fallback();
|
|
return;
|
|
}
|
|
|
|
statusCalls += 1;
|
|
await route.fulfill(
|
|
json({
|
|
data: {
|
|
id: 1401,
|
|
status: "PROCESSING",
|
|
progress_percent: 25,
|
|
progress_message: "Still processing",
|
|
},
|
|
})
|
|
);
|
|
});
|
|
|
|
await openHarness(page);
|
|
|
|
await page.getByTestId("economic-invoice-export-submit").click();
|
|
await expect.poll(() => statusCalls).toBeGreaterThanOrEqual(1);
|
|
|
|
await page.getByTestId("economic-queue-harness-toggle-invoice-export").click();
|
|
const statusCallsAtUnmount = statusCalls;
|
|
|
|
await page.waitForTimeout(2600);
|
|
expect(statusCalls).toBe(statusCallsAtUnmount);
|
|
});
|
|
});
|