e2e/stock-session-recovery.spec.ts
Rama main · réplica de lectura del repositorio original.
import { navigateWorkspace } from "./workspace-controls";
import { expect, test, type Page } from "@playwright/test";
async function signIn(page: Page) {
await page.getByLabel("Workspace password").fill("demo-password-please-change");
const login = page.waitForResponse(response => response.request().method() === "POST" && new URL(response.url()).pathname === "/api/v1/auth/login");
await page.getByRole("button", { name: "Sign in", exact: true }).click();
expect((await login).ok()).toBe(true);
}
test("a first commissioning auth rejection remains editable after a post-login version conflict", async ({ page }) => {
await page.setViewportSize({ width: 1440, height: 900 });
const pageErrors: string[] = [];
page.on("pageerror", error => pageErrors.push(error.message));
let item = {
id: "synthetic-session-definitive", name: "Synthetic editable recovery board", kind: "electronic",
quantity: 10, availableQuantity: 0, allocatedQuantity: 0, unit: "each", location: "Synthetic drawer", tags: [], links: [],
evidence: { state: "delivered_uncounted", source: "Synthetic delivery", observedAt: "2026-10-03T08:00:00Z" },
createdAt: "2026-10-03T08:00:00Z", updatedAt: "2026-10-03T08:00:00Z", version: 3
};
await page.route("**/api/v1/inventory?**", route => route.fulfill({ json: { data: [item], total: 1, limit: 25 } }));
let exactReads = 0;
await page.route(`**/api/v1/inventory/${item.id}`, route => { exactReads++; return route.fulfill({ json: item }); });
await page.route(`**/api/v1/inventory/${item.id}/images`, route => route.fulfill({ json: { itemId: item.id, version: 0, images: [] } }));
const writes: { key: string | undefined; version: string | undefined; body: string | null }[] = [];
await page.route(`**/api/v1/inventory/${item.id}/commission`, async route => {
writes.push({ key: route.request().headers()["idempotency-key"], version: route.request().headers()["if-match"], body: route.request().postData() });
if (writes.length === 1) {
// This request was definitively rejected; another observation changed the
// record before sign-in. There has been no lost acknowledgement.
item = { ...item, quantity: 11, version: 4 };
return route.fulfill({ status: 401, json: { error: { code: "unauthenticated", message: "Synthetic sign-in required before commissioning." } } });
}
return route.fulfill({ status: 409, json: { error: { code: "version_conflict", message: "Synthetic item changed; review the latest stock." } } });
});
await page.goto("/"); await signIn(page);
await expect(page.getByRole("heading", { name: "Projects", exact: true })).toBeVisible();
await navigateWorkspace(page, "Settings");
await page.getByRole("switch", { name: "Technical details", exact: true }).check();
await navigateWorkspace(page, "Inventory");
const row = page.locator(".inventory-table tbody tr").filter({ hasText: item.name });
await row.focus(); await row.press("Enter");
const drawer = page.getByRole("dialog");
await drawer.getByLabel("Observed quantity", { exact: true }).fill("7");
await drawer.getByLabel("Source", { exact: true }).fill("Synthetic original observation");
await drawer.getByLabel("Observed", { exact: true }).fill("2026-10-03T09:17");
await drawer.getByRole("button", { name: "Review commissioning", exact: true }).click();
const review = page.getByRole("alertdialog", { name: "Review stock commissioning", exact: true });
const reviewedSummary = await review.locator(".inventory-selection-summary").innerText();
await review.getByRole("button", { name: "Commission stock", exact: true }).click();
await expect(page.getByLabel("Workspace password")).toBeVisible();
await expect(page.locator("body")).not.toContainText(item.name);
expect(writes).toHaveLength(1);
const readsBeforeSignIn = exactReads;
await signIn(page);
await expect.poll(() => exactReads).toBeGreaterThan(readsBeforeSignIn);
await expect(review).toBeVisible();
expect(await review.locator(".inventory-selection-summary").innerText()).toBe(reviewedSummary);
await expect(review.getByRole("button", { name: "Back to item", exact: true })).toBeEnabled();
expect(writes).toHaveLength(1);
await expect(review.getByRole("alert")).toContainText("This observation was not saved. Review it before retrying.");
await review.getByRole("button", { name: "Retry observation", exact: true }).click();
await expect(review.getByRole("alert")).toContainText("Synthetic item changed");
expect(writes).toHaveLength(2);
expect(writes[1]!.version).toBe("3");
expect(writes[1]!.body).toBe(writes[0]!.body);
expect(writes[1]!.key).toBeTruthy();
await expect(review.getByRole("button", { name: "Back to item", exact: true })).toBeEnabled();
await expect(review.getByRole("button", { name: "Retry unchanged observation", exact: true })).toHaveCount(0);
await review.getByRole("button", { name: "Back to item", exact: true }).click();
await expect(review).toHaveCount(0);
await expect(drawer.getByLabel("Observed quantity", { exact: true })).toHaveValue("7");
await drawer.getByLabel("Observed quantity", { exact: true }).fill("8");
await drawer.getByLabel("Source", { exact: true }).fill("Synthetic revised observation");
await drawer.getByRole("button", { name: "Close item details", exact: true }).click();
const guard = page.getByRole("alertdialog");
await expect(guard.getByRole("button", { name: "Discard changes and leave", exact: true })).toBeEnabled();
await guard.getByRole("button", { name: "Discard changes and leave", exact: true }).click();
await expect(page.getByRole("dialog")).toHaveCount(0);
await expect(page.getByRole("alertdialog")).toHaveCount(0);
await navigateWorkspace(page, "Projects");
await expect(page.getByRole("heading", { name: "Projects", exact: true })).toBeVisible();
expect(writes).toHaveLength(2);
expect(pageErrors).toEqual([]);
});
test("a definitively rejected observation can be discarded when its item is gone after sign-in", async ({ page }) => {
await page.setViewportSize({ width: 1440, height: 900 });
const pageErrors: string[] = [];
page.on("pageerror", error => pageErrors.push(error.message));
const item = {
id: "synthetic-session-removed", name: "Synthetic removed recovery board", kind: "electronic",
quantity: 10, availableQuantity: 0, allocatedQuantity: 0, unit: "each", location: "Synthetic drawer", tags: [], links: [],
evidence: { state: "delivered_uncounted", source: "Synthetic delivery", observedAt: "2026-10-03T08:00:00Z" },
createdAt: "2026-10-03T08:00:00Z", updatedAt: "2026-10-03T08:00:00Z", version: 3
};
let removed = false, writes = 0, exactReads = 0;
await page.route("**/api/v1/inventory?**", route => route.fulfill({ json: { data: removed ? [] : [item], total: removed ? 0 : 1, limit: 25 } }));
await page.route(`**/api/v1/inventory/${item.id}`, route => {
exactReads++;
return removed
? route.fulfill({ status: 404, json: { error: { code: "not_found", message: "Synthetic inventory item was removed." } } })
: route.fulfill({ json: item });
});
await page.route(`**/api/v1/inventory/${item.id}/images`, route => route.fulfill({ json: { itemId: item.id, version: 0, images: [] } }));
await page.route(`**/api/v1/inventory/${item.id}/commission`, route => {
writes++;
removed = true;
return route.fulfill({ status: 401, json: { error: { code: "unauthenticated", message: "Synthetic session expired before the observation was saved." } } });
});
await page.goto("/"); await signIn(page);
await expect(page.getByRole("heading", { name: "Projects", exact: true })).toBeVisible();
await navigateWorkspace(page, "Settings");
await page.getByRole("switch", { name: "Technical details", exact: true }).check();
await navigateWorkspace(page, "Inventory");
const row = page.locator(".inventory-table tbody tr").filter({ hasText: item.name });
await row.focus(); await row.press("Enter");
const drawer = page.getByRole("dialog");
await drawer.getByLabel("Observed quantity", { exact: true }).fill("7");
await drawer.getByLabel("Source", { exact: true }).fill("Synthetic physical observation");
await drawer.getByLabel("Observed", { exact: true }).fill("2026-10-03T09:17");
await drawer.getByRole("button", { name: "Review commissioning", exact: true }).click();
await page.getByRole("alertdialog", { name: "Review stock commissioning", exact: true }).getByRole("button", { name: "Commission stock", exact: true }).click();
await expect(page.getByLabel("Workspace password")).toBeVisible();
await expect(page.locator("body")).not.toContainText(item.name);
expect(writes).toBe(1);
const readsBeforeSignIn = exactReads;
await signIn(page);
await expect(page.getByRole("heading", { name: "Cannot open workspace", exact: true })).toBeVisible();
await expect.poll(() => exactReads).toBeGreaterThan(readsBeforeSignIn);
await expect(page.getByRole("dialog")).toHaveCount(0);
await expect(page.getByRole("alertdialog")).toHaveCount(0);
expect(writes).toBe(1);
const readsBeforeDiscard = exactReads;
await page.getByRole("button", { name: "Discard observation and reload", exact: true }).click();
await expect(page.getByRole("heading", { name: "Cannot open workspace", exact: true })).toHaveCount(0);
await navigateWorkspace(page, "Projects");
await expect(page.getByRole("heading", { name: "Projects", exact: true })).toBeVisible();
await expect(page.getByRole("dialog")).toHaveCount(0);
await expect(page.getByRole("alertdialog")).toHaveCount(0);
expect(writes).toBe(1);
expect(exactReads).toBe(readsBeforeDiscard);
expect(pageErrors).toEqual([]);
});
for (const scenario of [
{ operation: "count", status: 401, code: "unauthenticated", width: 390 },
{ operation: "commission", status: 403, code: "csrf", width: 1440 }
] as const) {
test(`${scenario.operation} resumes its exact unresolved observation after ${scenario.code} sign-in`, async ({ page }) => {
await page.setViewportSize({ width: scenario.width, height: 900 });
const pageErrors: string[] = [];
page.on("pageerror", error => pageErrors.push(error.message));
let item = {
id: `synthetic-session-${scenario.operation}`, name: `Synthetic ${scenario.operation} recovery board`, kind: "electronic",
quantity: 10, availableQuantity: 0, allocatedQuantity: 0, unit: "each", location: "Synthetic drawer", tags: [], links: [],
evidence: { state: "delivered_uncounted", source: "Synthetic delivery", observedAt: "2026-10-03T08:00:00Z" },
createdAt: "2026-10-03T08:00:00Z", updatedAt: "2026-10-03T08:00:00Z", version: 3
};
await page.route("**/api/v1/inventory?**", route => route.fulfill({ json: { data: [item], total: 1, limit: 25 } }));
let exactReads = 0;
await page.route(`**/api/v1/inventory/${item.id}`, route => { exactReads++; return route.fulfill({ json: item }); });
await page.route(`**/api/v1/inventory/${item.id}/images`, route => route.fulfill({ json: { itemId: item.id, version: 0, images: [] } }));
const writes: { key: string | undefined; version: string | undefined; body: string | null }[] = [];
await page.route(`**/api/v1/inventory/${item.id}/${scenario.operation}`, async route => {
writes.push({ key: route.request().headers()["idempotency-key"], version: route.request().headers()["if-match"], body: route.request().postData() });
if (writes.length === 1) {
// Simulate a committed observation whose response was lost. Reauthentication
// loads this newer state, but must not rewrite the original reviewed command.
item = { ...item, quantity: 7, availableQuantity: 7, version: 4, evidence: { ...item.evidence, state: scenario.operation === "count" ? "physically_counted" : "commissioned" } };
return route.abort("failed");
}
if (writes.length === 2) return route.fulfill({ status: scenario.status, json: { error: { code: scenario.code, message: "Synthetic session needs sign-in." } } });
return route.fulfill({ json: { data: { item, event: { id: `synthetic-${scenario.operation}-event` } } } });
});
await page.goto("/"); await signIn(page);
await expect(page.getByRole("heading", { name: "Projects", exact: true })).toBeVisible();
if (scenario.operation === "commission") {
await navigateWorkspace(page, "Settings");
await page.getByRole("switch", { name: "Technical details", exact: true }).check();
}
await navigateWorkspace(page, "Inventory");
const row = page.locator(".inventory-table tbody tr").filter({ hasText: item.name });
await row.focus(); await row.press("Enter");
const drawer = page.getByRole("dialog");
await expect(drawer.getByRole("heading", { name: item.name, exact: true })).toBeVisible();
let expectedObservedAt: string | undefined;
if (scenario.operation === "count") {
await drawer.getByLabel("Counted quantity").fill("7");
await drawer.getByRole("button", { name: "Review physical count", exact: true }).click();
} else {
await drawer.getByLabel("Observed quantity", { exact: true }).fill("7");
await drawer.getByLabel("Source", { exact: true }).fill("Synthetic physical inspection");
await drawer.getByLabel("Source ID (optional)", { exact: true }).fill("synthetic-observation-reference");
await drawer.getByLabel("Observed", { exact: true }).fill("2026-10-03T09:17");
await drawer.getByLabel("Note (optional)", { exact: true }).fill("Seven intact boards physically observed.");
expectedObservedAt = await page.evaluate(() => new Date("2026-10-03T09:17").toISOString());
await drawer.getByRole("button", { name: "Review commissioning", exact: true }).click();
}
const review = page.getByRole("alertdialog", { name: scenario.operation === "count" ? "Review physical count" : "Review stock commissioning", exact: true });
const reviewedSummary = await review.locator(".inventory-selection-summary").innerText();
await review.getByRole("button", { name: scenario.operation === "count" ? "Confirm physical count" : "Commission stock", exact: true }).click();
await expect(review.getByRole("button", { name: "Retry unchanged observation", exact: true })).toBeEnabled();
expect(writes).toHaveLength(1);
await review.getByRole("button", { name: "Retry unchanged observation", exact: true }).click();
await expect(page.getByLabel("Workspace password")).toBeVisible();
await expect(page.getByRole("dialog")).toHaveCount(0);
await expect(page.getByRole("alertdialog")).toHaveCount(0);
await expect(page.locator(".inventory-table")).toHaveCount(0);
await expect(page.locator("body")).not.toContainText(item.name);
expect(writes).toHaveLength(2);
expect(writes[1]).toEqual(writes[0]);
const readsBeforeSignIn = exactReads;
await signIn(page);
await expect.poll(() => exactReads).toBeGreaterThan(readsBeforeSignIn);
await expect(review).toBeVisible();
await expect(review.getByRole("button", { name: "Retry unchanged observation", exact: true })).toBeEnabled();
await expect(review.getByRole("button", { name: "Back to item", exact: true })).toBeDisabled();
// Restoration displays the original observation, not a new review based on
// the version-4 item loaded after sign-in, and never sends it automatically.
expect(await review.locator(".inventory-selection-summary").innerText()).toBe(reviewedSummary);
await page.keyboard.press("Escape"); await expect(review).toBeVisible();
expect(writes).toHaveLength(2);
await review.getByRole("button", { name: "Retry unchanged observation", exact: true }).click();
await expect(review).toHaveCount(0);
await expect(drawer.getByRole("heading", { name: item.name, exact: true })).toBeVisible();
expect(writes).toHaveLength(3);
expect(writes[0]!.key).toBeTruthy(); expect(writes[2]).toEqual(writes[0]);
if (scenario.operation === "count") {
expect(JSON.parse(writes[2]!.body!)).toEqual({ quantity: 7 });
await expect(drawer.getByLabel("Counted quantity")).toHaveCount(0);
await expect(drawer.getByRole("status")).toContainText("Confirmed 7");
} else {
expect(writes[2]!.version).toBe("3");
expect(JSON.parse(writes[2]!.body!)).toEqual({ quantity: 7, unit: "each", evidence: {
state: "commissioned", source: "Synthetic physical inspection", sourceId: "synthetic-observation-reference",
observedAt: expectedObservedAt, note: "Seven intact boards physically observed."
} });
}
expect(pageErrors).toEqual([]);
});
}