Files
EpicNext-Cms/e2e/ui/attachment-recovery.spec.ts
Simo 88790d1a0d
CI / check (push) Successful in 2m3s
CI / deploy (push) Successful in 1m31s
CI / publish-container (push) Successful in 1m41s
feat(cms): recover drafts and failed imports with verified UI workflows
2026-09-11 08:58:10 +02:00

110 lines
3.1 KiB
TypeScript

import { expect, test } from "@playwright/test";
const attachmentId = "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb";
const file = {
name: "fixture_chair.nitro",
mimeType: "application/octet-stream",
buffer: Buffer.from("isolated upload fixture"),
};
test.beforeEach(async ({ page }) => {
await page.goto("/admin/attachment-harness");
await page
.getByText("Attach original file and resume", { exact: true })
.click();
});
test("attachment upload and double-click resume send one retry and refresh history", async ({
page,
}) => {
let retries = 0;
await page.route("**/api/admin/studio/import-attachment", async (route) => {
expect(route.request().method()).toBe("POST");
expect(route.request().postData()).toContain('name="classname"');
expect(route.request().postData()).toContain("fixture_chair");
expect(route.request().postData()).toContain(
'filename="fixture_chair.nitro"',
);
await route.fulfill({ json: { ok: true, attachmentId } });
});
await page.route("**/api/admin/studio/import-jobs", async (route) => {
retries++;
expect(route.request().method()).toBe("PATCH");
expect(route.request().postDataJSON()).toEqual({
id: "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa",
action: "retry",
attachments: { fixture_chair: attachmentId },
});
await new Promise((resolve) => setTimeout(resolve, 150));
await route.fulfill({ json: { ok: true } });
});
await page.getByLabel("Choose the original .nitro file").setInputFiles(file);
await expect(
page.getByText("Matching original file attached", { exact: true }),
).toBeVisible();
await page
.getByRole("button", {
name: "Resume failed imports with this file",
exact: true,
})
.dblclick();
await expect(page.getByLabel("Resumed count")).toHaveText("1");
expect(retries).toBe(1);
await expect(
page.getByRole("button", {
name: "Resume failed imports with this file",
exact: true,
}),
).toHaveCount(0);
});
for (const scenario of [
{
name: "owner rejection",
status: 403,
code: "forbidden",
message: "Could not attach the file.",
},
{
name: "mismatched furniture",
status: 400,
code: "mismatchedFile",
message: "This file belongs to different furniture.",
},
{
name: "network failure",
status: 0,
code: "",
message: "Network connection failed",
},
])
test(`attachment ${scenario.name} shows readable error without enabling resume`, async ({
page,
}) => {
let retries = 0;
await page.route("**/api/admin/studio/import-jobs", async (route) => {
retries++;
await route.fulfill({ json: { ok: true } });
});
await page.route("**/api/admin/studio/import-attachment", (route) =>
scenario.status
? route.fulfill({
status: scenario.status,
json: { ok: false, code: scenario.code },
})
: route.abort("failed"),
);
await page
.getByLabel("Choose the original .nitro file")
.setInputFiles(file);
await expect(page.getByRole("alert")).toContainText(scenario.message);
await expect(
page.getByRole("button", {
name: "Resume failed imports with this file",
exact: true,
}),
).toHaveCount(0);
await expect(page.getByLabel("Resumed count")).toHaveText("0");
expect(retries).toBe(0);
});