import { mkdirSync } from "node:fs"; import { resolve } from "node:path"; import { expect, test, type Page } from "@playwright/test"; import { createServer, type ViteDevServer } from "vite"; let vite: ViteDevServer; let webUrl: string; const png = Buffer.from("iVBORw0KGgoAAAANSUhEUgAAAAQAAAADCAYAAAC09K7GAAAACXBIWXMAAAPoAAAD6AG1e1JrAAAAEklEQVQImWO4E6XxHxkzEBQAANIxHF3ECQOzAAAAAElFTkSuQmCC", "base64"); const adminSession = { csrf_token: "csrf-wp5-05-admin-000000000000000000000000000000000" }; const projectId = "00000000-0000-4000-8000-000000001405"; const userSession = { audience: "user", authenticated: true, credits: { available_balance: 10, reserved_balance: 0 }, csrf_token: "csrf-wp5-05-user-0000000000000000000000000000000000", expires_at: "2026-09-03T12:00:00.000Z", user: { creator_name: "Sticker User", role: "user", social_id: "@sticker", status: "active", user_id: projectId }, }; function asset(stableId = "STK1408") { return { enabled: true, file_state: "committed", height: 3, mime: "image/png", mime_type: "image/png", order: 184, original_byte_size: png.byteLength, original_filename: `${stableId}.png`, original_reference: `/api/v1/assets/public/asset-20260803.1/${stableId}`, origin: "admin_uploaded", part: 25, relative_path: `managed-assets/stickers/original/${stableId}.png`, resource_version: "asset-20260803.1", sha256: "0".repeat(64), stable_id: stableId, thumbnail_byte_size: 75, thumbnail_reference: { media: "thumbnail", resource_id: stableId, resource_version: "asset-20260803.1", url: `/api/v1/assets/public/asset-20260803.1/${stableId}?variant=thumbnail` }, width: 4, }; } function assetsResponse(status: "active" | "full" | "unavailable" = "active", items = [asset()]) { return { count: items.length, items, release_version: items.length ? "asset-20260803.1" : null, storage: { capacity_notice_level: status === "active" ? "normal" : "critical", hard_limit_bytes: 5_368_709_120, managed_content_bytes: status === "full" ? 5_368_709_120 : 150, storage_status: status }, }; } test.beforeAll(async () => { vite = await createServer({ configFile: resolve("apps/web/vite.config.ts"), root: resolve("apps/web"), server: { host: "127.0.0.1", port: 0 } }); await vite.listen(); const address = vite.httpServer?.address(); if (!address || typeof address === "string") throw new Error("Vite did not expose a test port."); webUrl = `http://127.0.0.1:${address.port}`; }); test.afterAll(async () => vite.close()); async function routeAdminSession(page: Page) { await page.route("**/api/v1/admin-auth/session", (route) => route.fulfill({ body: JSON.stringify(adminSession), contentType: "application/json", status: 200 })); await page.route("**/api/v1/assets/public/**", (route) => route.fulfill({ body: png, contentType: "image/png", status: 200 })); } test("TDD-WP5-UPL-001-upload-metering uploads and publishes with stable metadata controls", async ({ page }) => { await routeAdminSession(page); let uploaded = false; let multipartBody = ""; await page.route("**/api/v1/admin/assets/static-stickers", async (route) => { if (route.request().method() === "POST") { multipartBody = route.request().postDataBuffer()?.toString("latin1") ?? ""; uploaded = true; return route.fulfill({ body: JSON.stringify({ created: true, item: asset(), release_version: "asset-20260803.1" }), contentType: "application/json", status: 201 }); } return route.fulfill({ body: JSON.stringify(assetsResponse("active", uploaded ? [asset()] : [])), contentType: "application/json", status: 200 }); }); await page.goto(`${webUrl}/admin/assets`); await expect(page.getByRole("heading", { name: "普通贴纸" })).toBeVisible(); await expect(page.getByText("当前没有后台上传的普通贴纸。")).toBeVisible(); await page.getByLabel("贴纸文件").setInputFiles({ buffer: png, mimeType: "image/png", name: "STK1408.png" }); await page.getByRole("button", { name: "上传并发布" }).click(); await expect(page.getByRole("rowheader", { name: /STK1408/ })).toBeVisible(); expect(multipartBody).toContain("STK1408"); expect(multipartBody).toContain("image/png"); expect(multipartBody).toContain("original_sha256"); const evidenceRoot = process.env.DADA_EVIDENCE_DIR_WP5_UPL; if (evidenceRoot) { const directory = resolve(evidenceRoot, "screenshots"); mkdirSync(directory, { recursive: true }); await page.screenshot({ fullPage: true, path: resolve(directory, "admin-assets-uploaded.png") }); } }); test("storage full disables every upload control and load failure exposes retry", async ({ page }) => { await page.setViewportSize({ height: 844, width: 390 }); await routeAdminSession(page); await page.route("**/api/v1/admin/assets/static-stickers", (route) => route.fulfill({ body: JSON.stringify(assetsResponse("full")), contentType: "application/json", status: 200 })); await page.goto(`${webUrl}/admin/assets`); await expect(page.getByText("当前存储状态禁止新增原图和缩略图。")).toBeVisible(); await expect(page.getByLabel("贴纸文件")).toBeDisabled(); await expect(page.getByRole("button", { name: "上传并发布" })).toBeDisabled(); const evidenceRoot = process.env.DADA_EVIDENCE_DIR_WP5_UPL; if (evidenceRoot) { const directory = resolve(evidenceRoot, "screenshots"); mkdirSync(directory, { recursive: true }); await page.screenshot({ fullPage: true, path: resolve(directory, "admin-assets-full-mobile.png") }); } await page.unroute("**/api/v1/admin/assets/static-stickers"); await page.route("**/api/v1/admin/assets/static-stickers", (route) => route.fulfill({ status: 503 })); await page.reload(); await expect(page.getByRole("alert")).toContainText("素材状态暂时无法读取"); await expect(page.getByRole("button", { name: "重试" })).toBeVisible(); }); test("the editor merges the current uploaded release and saves its resource version", async ({ page }) => { let savedResourceVersion = ""; let originalRequests = 0; await page.route("**/api/v1/auth/session", (route) => route.fulfill({ body: JSON.stringify(userSession), contentType: "application/json", status: 200 })); await page.route("**/api/v1/static-stickers/current", (route) => route.fulfill({ body: JSON.stringify({ count: 1, items: [asset()], release_version: "asset-20260803.1" }), contentType: "application/json", status: 200 })); await page.route(`**/api/v1/projects/${projectId}`, (route) => route.fulfill({ body: JSON.stringify({ canvas_state: { background: { adjustments: { brightness: 0, contrast: 0, crop: null, filter: "none", fit: "fill", saturation: 0, sharpness: 0, temperature: 0 }, asset_id: null }, elements: [], pixel_height: 1440, pixel_width: 1080, ratio: "3:4", schema_version: 1 }, created_at: "2026-08-03T12:00:00.000Z", current_image_id: null, images: [], name: "上传贴纸", project_id: projectId, ratio: "3:4", state_version: 1, }), contentType: "application/json", status: 200 })); await page.route(`**/api/v1/projects/${projectId}/state`, async (route) => { const body = route.request().postDataJSON() as { canvas_state: { elements: Array<{ resource_version: string }> } }; savedResourceVersion = body.canvas_state.elements[0]?.resource_version ?? ""; await route.fulfill({ body: JSON.stringify({ save_status: "saved", state_version: 2 }), contentType: "application/json", status: 200 }); }); await page.route("**/api/v1/assets/recent?asset_kind=text_template", (route) => route.fulfill({ body: JSON.stringify({ items: [] }), contentType: "application/json", status: 200 })); await page.route("**/api/v1/assets/public/**", async (route) => { if (!new URL(route.request().url()).searchParams.has("variant")) originalRequests += 1; await route.fulfill({ body: png, contentType: "image/png", status: 200 }); }); await page.goto(`${webUrl}/app/projects/${projectId}/editor`); await page.getByRole("button", { name: "普通贴纸", exact: true }).click(); await expect(page.getByText("共 1,408 张", { exact: true })).toBeVisible(); const list = page.getByTestId("static-sticker-list"); await list.evaluate((element) => { element.scrollTop = element.scrollHeight; element.dispatchEvent(new Event("scroll")); }); await list.getByRole("button", { name: "添加贴纸 STK1408" }).click(); await expect.poll(() => savedResourceVersion).toBe("asset-20260803.1"); await expect.poll(() => originalRequests).toBeGreaterThan(0); });