feat: consolidate shared UI and harden browser authority for release

This commit is contained in:
2026-09-08 01:35:05 +02:00
parent ac40774785
commit b75ca34295
143 changed files with 8664 additions and 752 deletions
@@ -0,0 +1,296 @@
import { expect, test, type Page } from "@playwright/test";
const sourceFile = {
id: "fixture-archive", tenant_id: "fixture-tenant", owner_type: "user", owner_id: "fixture-user",
display_path: "small-archive.zip", filename: "small-archive.zip", size_bytes: 280,
content_type: "application/zip", checksum_sha256: "a".repeat(64), version_id: "fixture-version-7",
created_at: "2026-01-01T12:00:00Z", updated_at: "2026-01-01T12:00:00Z", audit_relevant: false,
shares: [], metadata: {}, deleted_at: null
};
type RecordedCall = { path: string; body: Record<string, unknown>; contentType: string };
async function installFileFixtures(page: Page, options: { encrypted?: boolean; failPreviewOnce?: boolean; failConfirmation?: boolean; holdPreview?: Promise<void>; holdConfirmation?: Promise<void>; progress?: { current: Record<string, unknown> | null }; staged?: boolean } = {}) {
const calls: RecordedCall[] = [];
const forbiddenRequests: string[] = [];
const importedFiles: Record<string, unknown>[] = [];
const releasedStages: string[] = [];
let previewFailed = false;
// A broad **/api/** glob also matches Vite's /@fs/.../src/api/*.ts.
// Restrict interception to actual API URLs so real module code is rendered.
await page.route((url) => url.pathname.startsWith("/api/"), async (route) => {
const request = route.request();
const url = new URL(request.url());
const path = url.pathname;
if (request.method() === "DELETE" && path.startsWith("/api/v1/files/archive-staging/")) {
releasedStages.push(path.split("/").slice(-1)[0]);
await route.fulfill({ status: 204 });
return;
}
if (request.method() === "GET") {
if (path.startsWith("/api/v1/files/archive-progress/")) {
const operationId = path.split("/").slice(-1)[0];
const confirmation = calls.find((call) => call.path.endsWith("/archive-confirm") && call.body.operation_id === operationId);
if (confirmation && options.progress?.current) await route.fulfill({ json: options.progress.current });
else await route.fulfill({ status: 404, json: { detail: "No measured progress available" } });
return;
}
if (path === "/api/v1/files/spaces") {
await route.fulfill({ json: { spaces: [
{ id: "user:fixture-user", label: "My files", owner_type: "user", owner_id: "fixture-user", space_type: "managed" },
{ id: "group:fixture-team", label: "Team files", owner_type: "group", owner_id: "fixture-team", space_type: "managed" }
] } });
return;
}
const team = url.searchParams.get("owner_id") === "fixture-team";
if (path === "/api/v1/files/folders") {
await route.fulfill({ json: { folders: [{ id: team ? "team-output" : "personal-output", path: "extracted",
owner_type: team ? "group" : "user", owner_id: team ? "fixture-team" : "fixture-user",
name: "extracted", created_at: sourceFile.created_at, updated_at: sourceFile.updated_at }], total: 1, next_cursor: null } });
return;
}
if (path === "/api/v1/files") {
const files = team ? importedFiles : [sourceFile];
await route.fulfill({ json: { files, total: files.length, next_cursor: null } });
return;
}
}
if (request.method() === "POST" && (path.endsWith("/fixture-archive/archive-preview") || path.endsWith("/fixture-archive/archive-confirm") || path === "/api/v1/files/archive-preview" || path === "/api/v1/files/archive-confirm")) {
const contentType = request.headers()["content-type"] ?? "";
let body: Record<string, unknown>;
if (contentType.startsWith("application/json")) body = request.postDataJSON() as Record<string, unknown>;
else {
const form = await new Request(request.url(), { method: "POST", headers: { "content-type": contentType }, body: new Uint8Array(request.postDataBuffer()!) }).formData();
body = Object.fromEntries([...form.entries()].map(([key, value]) => [key, typeof value === "string" ? value : value.name]));
if (typeof body.selected_paths_json === "string") body.selected_paths = JSON.parse(body.selected_paths_json);
}
calls.push({ path, body, contentType: request.headers()["content-type"] ?? "" });
if (path.endsWith("/archive-preview")) {
if (options.holdPreview) await options.holdPreview;
if (options.failPreviewOnce && !previewFailed) {
previewFailed = true;
await route.fulfill({ status: 400, json: { detail: "Managed archive version changed; reload and preview again" } });
return;
}
await route.fulfill({ json: {
preview_token: `fixture-preview-${calls.length}`, archive_format: "zip", file_count: 2, directory_count: 1,
compressed_size_bytes: 280, expanded_size_bytes: 6, expires_at: "2099-01-01T12:00:00Z",
requires_password: Boolean(options.encrypted), password_verified: Boolean(body.password),
...(options.staged ? { staged_upload_id: "fixture-staged-archive" } : {}),
entries: [
{ path: "folder", kind: "directory", size_bytes: 0, encrypted: false },
{ path: "folder/one.txt", kind: "file", size_bytes: 3, compressed_size_bytes: 5, encrypted: Boolean(options.encrypted) },
{ path: "two.txt", kind: "file", size_bytes: 3, compressed_size_bytes: 5, encrypted: Boolean(options.encrypted) }
]
} });
return;
}
if (options.holdConfirmation) await options.holdConfirmation;
if (options.failConfirmation) {
await route.fulfill({ status: 400, json: { detail: "Target file already exists: extracted/folder/one.txt" } });
return;
}
for (const [index, selected] of (body.selected_paths as string[]).entries()) {
importedFiles.push({ ...sourceFile, id: `imported-${index}`, owner_type: body.owner_type, owner_id: body.owner_id,
display_path: `${body.path}/${selected}`, filename: selected.split("/").slice(-1)[0], version_id: `imported-version-${index}` });
}
await route.fulfill({ json: { files: importedFiles } });
return;
}
forbiddenRequests.push(`${request.method()} ${path}`);
await route.fulfill({ status: 403, json: { detail: "Unexpected fixture request; no real API access permitted" } });
});
return { calls, forbiddenRequests, importedFiles, releasedStages };
}
async function openExistingArchive(page: Page, language = "en", contextMenu = false) {
const name = language === "de" ? "Archiv entpacken" : "Unpack archive";
await page.goto(`/?managed-archive&language=${language}`);
const row = page.locator(".file-row").filter({ hasText: "small-archive.zip" });
await expect(row).toBeVisible();
await expect(page.getByRole("button", { name, exact: true })).toHaveCount(0);
await row.click({ button: contextMenu ? "right" : "left" });
if (contextMenu) await page.getByRole("menuitem", { name, exact: true }).click();
else await page.getByRole("button", { name, exact: true }).click();
const dialog = page.getByRole("dialog", { name, exact: true });
await expect(dialog).toBeVisible();
await expect(dialog).toContainText("small-archive.zip");
await expect(dialog.locator('input[type="file"]')).toHaveCount(0);
await dialog.locator("select").selectOption("group:fixture-team");
await dialog.getByRole("button", { name: "extracted", exact: true }).click();
return dialog;
}
for (const language of ["en", "de"]) {
test(`real Files page unpacks a selected managed archive without re-upload in ${language}`, async ({ page }) => {
let finish!: () => void;
const holdConfirmation = new Promise<void>((resolve) => { finish = resolve; });
const fixture = await installFileFixtures(page, { holdConfirmation });
const dialog = await openExistingArchive(page, language);
await dialog.getByRole("button", { name: language === "de" ? "Archivvorschau" : "Preview archive", exact: true }).click();
await expect(dialog.locator(".archive-entry-row")).toHaveCount(3);
await dialog.locator(".archive-entry-row").filter({ hasText: "two.txt" }).locator('input[type="checkbox"]').uncheck();
await dialog.getByRole("button", { name: "Import selected", exact: true }).click();
await expect.poll(() => fixture.calls.filter((call) => call.path.endsWith("/archive-confirm")).length).toBe(1);
await expect(dialog.getByRole("button", { name: "Import selected", exact: true, includeHidden: true })).toBeDisabled();
await expect(dialog.locator(".loading-frame-overlay")).toBeVisible();
await expect(dialog.locator(".loading-frame-overlay")).toHaveCSS("backdrop-filter", /blur/);
await expect(dialog.locator(".loading-envelope")).toHaveCount(0);
await expect(dialog.locator("[inert]")).toHaveCount(1);
await expect(dialog.locator(".dialog-close")).toBeDisabled();
await expect(dialog.getByRole("progressbar")).not.toHaveAttribute("value");
await page.keyboard.press("Escape");
await expect(dialog).toBeVisible();
finish();
await expect(dialog).toHaveCount(0);
await expect(page.locator(".file-row").filter({ hasText: "small-archive.zip" })).toBeVisible();
expect(fixture.calls).toHaveLength(2);
expect(fixture.calls.every((call) => call.contentType.startsWith("application/json"))).toBe(true);
expect(fixture.calls[0].body).toEqual({ source_version_id: "fixture-version-7", owner_type: "group", owner_id: "fixture-team", path: "extracted" });
expect(fixture.calls[1].body).toEqual({ ...fixture.calls[0].body, preview_token: "fixture-preview-1", selected_paths: ["folder/one.txt"], operation_id: expect.any(String) });
expect(fixture.importedFiles.map((file) => file.display_path)).toEqual(["extracted/folder/one.txt"]);
expect(fixture.forbiddenRequests).toEqual([]);
});
}
test("managed context-menu flow verifies a ZIP password in the shared dialog", async ({ page }) => {
const fixture = await installFileFixtures(page, { encrypted: true });
const dialog = await openExistingArchive(page, "en", true);
await dialog.getByRole("button", { name: "Preview archive", exact: true }).click();
await expect(dialog.getByRole("button", { name: "Import selected", exact: true })).toBeDisabled();
await dialog.locator('input[type="password"]').fill("fixture-only-password");
await dialog.getByRole("button", { name: "Verify password", exact: false }).click();
await expect.poll(() => fixture.calls.length).toBe(2);
await expect(dialog.getByRole("button", { name: "Import selected", exact: true })).toBeEnabled();
await dialog.getByRole("button", { name: "Import selected", exact: true }).click();
await expect(dialog).toHaveCount(0);
expect(fixture.calls[1].body.password).toBe("fixture-only-password");
expect(fixture.calls[2].body.password).toBe("fixture-only-password");
expect(fixture.calls[2].body.preview_token).toBe("fixture-preview-2");
expect(fixture.calls.every((call) => call.body.source_version_id === "fixture-version-7")).toBe(true);
expect(fixture.forbiddenRequests).toEqual([]);
});
test("cancelling an uploaded archive releases only its temporary stage without importing", async ({ page }) => {
const fixture = await installFileFixtures(page, { staged: true });
await page.goto("/?managed-archive&language=en");
await expect(page.locator(".file-row").filter({ hasText: "small-archive.zip" })).toBeVisible();
await page.getByRole("button", { name: "Upload", exact: true }).click();
const dialog = page.getByRole("dialog");
await dialog.getByRole("checkbox", { name: "Preview and unpack archive", exact: true }).focus();
await page.keyboard.press("Space");
await dialog.locator('input[type="file"]').setInputFiles({ name: "cancelled.zip", mimeType: "application/zip", buffer: Buffer.from("fixture archive") });
await expect(dialog.locator(".archive-entry-row")).toHaveCount(3);
await dialog.getByRole("button", { name: "Cancel", exact: true }).click();
await expect(dialog).toHaveCount(0);
await expect.poll(() => fixture.releasedStages).toEqual(["fixture-staged-archive"]);
expect(fixture.calls.filter((call) => call.path.endsWith("/archive-confirm"))).toHaveLength(0);
expect(fixture.importedFiles).toEqual([]);
expect(fixture.forbiddenRequests).toEqual([]);
});
test("managed preview and confirmation failures stay visible inside the real dialog", async ({ page }) => {
const fixture = await installFileFixtures(page, { failPreviewOnce: true, failConfirmation: true });
const dialog = await openExistingArchive(page);
await dialog.getByRole("button", { name: "Preview archive", exact: true }).click();
await expect(dialog.getByText(/Managed archive version changed/)).toBeVisible();
await expect(dialog).toContainText("small-archive.zip");
await dialog.getByRole("button", { name: "Preview archive", exact: true }).click();
await expect(dialog.locator(".archive-entry-row")).toHaveCount(3);
await dialog.getByRole("button", { name: "Import selected", exact: true }).click();
await expect(dialog.getByText(/Target file already exists/)).toBeVisible();
await expect(dialog.locator(".loading-frame-overlay")).toHaveCount(0);
await expect(dialog.locator("[inert]")).toHaveCount(0);
await expect(dialog.locator(".dialog-close")).toBeEnabled();
await expect(dialog.getByRole("button", { name: "Import selected", exact: true })).toBeEnabled();
await dialog.getByRole("button", { name: "Change destination", exact: true }).click();
await expect(dialog.getByRole("button", { name: "Preview archive", exact: true })).toBeVisible();
await expect(dialog.locator(".archive-entry-row")).toHaveCount(0);
expect(fixture.importedFiles).toEqual([]);
expect(fixture.forbiddenRequests).toEqual([]);
});
test("managed extraction is unavailable without the download permission", async ({ page }) => {
const fixture = await installFileFixtures(page);
await page.goto("/?managed-archive&language=en&no-download");
await page.locator(".file-row").filter({ hasText: "small-archive.zip" }).click();
await expect(page.getByRole("button", { name: "Unpack archive", exact: true })).toBeDisabled();
expect(fixture.calls).toEqual([]);
expect(fixture.forbiddenRequests).toEqual([]);
});
test("managed preview covers and blurs existing dialog controls without fabricating progress", async ({ page }) => {
let release!: () => void;
const holdPreview = new Promise<void>((resolve) => { release = resolve; });
const fixture = await installFileFixtures(page, { holdPreview });
const dialog = await openExistingArchive(page);
await dialog.getByRole("button", { name: "Preview archive", exact: true }).click();
const overlay = dialog.locator(".loading-frame-overlay");
await expect(overlay).toBeVisible();
await expect(overlay).toContainText("Inspecting the archive");
await expect(dialog.getByRole("progressbar")).not.toHaveAttribute("value");
await expect(dialog.locator(".loading-envelope")).toHaveCount(0);
await expect(dialog.locator(".dialog-close")).toBeDisabled();
await page.keyboard.press("Escape");
await expect(dialog).toBeVisible();
release();
await expect(overlay).toHaveCount(0);
await expect(dialog.locator(".archive-entry-row")).toHaveCount(3);
expect(fixture.forbiddenRequests).toEqual([]);
});
test("actual server counters update the blurred overlay and finalization never claims completion", async ({ page }) => {
let release!: () => void;
const holdConfirmation = new Promise<void>((resolve) => { release = resolve; });
const progress = { current: { phase: "extracting", completed_files: 1, total_files: 2, completed_bytes: 3, total_bytes: 6, status: "running" } as Record<string, unknown> };
const fixture = await installFileFixtures(page, { holdConfirmation, progress });
const dialog = await openExistingArchive(page);
await dialog.getByRole("button", { name: "Preview archive", exact: true }).click();
await expect(dialog.locator(".archive-entry-row")).toHaveCount(3);
await dialog.getByRole("button", { name: "Import selected", exact: true }).click();
await expect(dialog.getByRole("progressbar")).toHaveAttribute("value", "50");
await expect(dialog.locator(".loading-frame-overlay")).toContainText("1 of 2 files");
await expect(dialog.locator(".loading-frame-overlay")).toContainText("3 B of 6 B");
progress.current = { ...progress.current, phase: "finalizing", completed_files: 2, completed_bytes: 6 };
await expect(dialog.locator(".loading-frame-overlay")).toContainText("Finalizing and committing changes");
await expect(dialog.getByRole("progressbar")).not.toHaveAttribute("value");
await expect(dialog).toBeVisible();
release();
await expect(dialog).toHaveCount(0);
expect(fixture.calls.filter((call) => call.path.endsWith("/archive-confirm"))).toHaveLength(1);
expect(fixture.forbiddenRequests).toEqual([]);
});
test("new archive upload preview and confirmation use the same no-envelope progress overlay", async ({ page }) => {
let previewReady!: () => void;
let importReady!: () => void;
const holdPreview = new Promise<void>((resolve) => { previewReady = resolve; });
const holdConfirmation = new Promise<void>((resolve) => { importReady = resolve; });
const progress = { current: { phase: "storing", completed_files: 1, total_files: 2, completed_bytes: 3, total_bytes: 6, status: "running" } as Record<string, unknown> };
const fixture = await installFileFixtures(page, { holdPreview, holdConfirmation, progress, staged: true });
await page.goto("/?managed-archive&language=en");
await expect(page.locator(".file-row").filter({ hasText: "small-archive.zip" })).toBeVisible();
await page.getByRole("button", { name: "Upload", exact: true }).click();
const dialog = page.getByRole("dialog");
await dialog.getByRole("checkbox", { name: "Preview and unpack archive", exact: true }).focus();
await page.keyboard.press("Space");
await dialog.locator('input[type="file"]').setInputFiles({ name: "new-archive.zip", mimeType: "application/zip", buffer: Buffer.from("fixture ZIP bytes; backend is intercepted") });
await expect(dialog.locator(".loading-frame-overlay")).toBeVisible();
await expect(dialog.locator(".loading-envelope")).toHaveCount(0);
await expect(dialog.locator(".dialog-close")).toBeDisabled();
await expect(dialog.getByRole("progressbar")).not.toHaveAttribute("value", "100");
previewReady();
await expect(dialog.locator(".archive-entry-row")).toHaveCount(3);
await expect(dialog.locator(".loading-frame-overlay")).toHaveCount(0);
await dialog.getByRole("button", { name: "Import selected", exact: true }).click();
await expect(dialog.locator(".loading-frame-overlay")).toContainText("Storing extracted files");
await expect(dialog.getByRole("progressbar")).toHaveAttribute("value", "50");
await expect(dialog.locator(".loading-envelope")).toHaveCount(0);
importReady();
await expect(dialog).toHaveCount(0);
expect(fixture.calls.filter((call) => call.path === "/api/v1/files/archive-confirm")).toHaveLength(1);
const confirmation = fixture.calls.find((call) => call.path === "/api/v1/files/archive-confirm")!;
expect(confirmation.body.staged_upload_id).toBe("fixture-staged-archive");
expect(confirmation.body.file).toBeUndefined();
expect(fixture.forbiddenRequests).toEqual([]);
});