import { expect, test } from "@playwright/test"; test("production bootstrap supports submission and moderation", async ({ page, request }) => { test.skip( !process.env.BOOTSTRAP_API_URL || !process.env.BOOTSTRAP_ADMIN_TOKEN, "requires BOOTSTRAP_API_URL and BOOTSTRAP_ADMIN_TOKEN; run deploy/test-production-bootstrap.sh", ); const player = `Bootstrap Player ${Date.now()}`; const x = 7000 + Date.now() % 1000; const y = x + 1; await page.goto("/"); await expect(page.getByRole("heading", { name: "Выбирай место, пока клюёт." })).toBeVisible(); await page.goto("/report"); await page.getByLabel("Координата X *").fill(String(x)); await page.getByLabel("Координата Y *").fill(String(y)); await page.getByLabel("Вес, граммы *").fill("4321"); await page.getByLabel(/Я отправляю собственное наблюдение/).check(); await page.getByText("Дополнительные сведения").click(); await page.getByLabel("Ник игрока").fill(player); await page.getByRole("button", { name: "Отправить на проверку" }).click(); await expect(page.getByText("Улов отправлен на модерацию. Спасибо!")).toBeVisible(); const response = await request.get(`${process.env.BOOTSTRAP_API_URL}/api/v1/admin/catch-reports?status=pending`, { headers: { Authorization: `Bearer ${process.env.BOOTSTRAP_ADMIN_TOKEN}` }, }); expect(response.ok()).toBeTruthy(); expect(await response.text()).toContain(player); }); test("SSR pages use a per-response CSP nonce for JSON-LD", async ({ page, request }) => { for (const path of ["/", "/report", "/admin/"]) { const response = await page.goto(path); const policy = response?.headers()["content-security-policy"] ?? ""; expect(policy).toContain("script-src 'self' 'nonce-"); expect(policy).not.toContain("'unsafe-inline'"); expect(policy).toContain("style-src-attr 'none'"); const nonce = await page.locator('script[type="application/ld+json"]').evaluate((node: HTMLScriptElement) => node.nonce); expect(nonce).toMatch(/^[a-f0-9]{32}$/); expect(policy).toContain(`'nonce-${nonce}'`); } const image = await request.get("/og-rf4spotter.png"); expect(image.ok()).toBeTruthy(); expect(image.headers()["content-type"]).toContain("image/png"); });