import { expect, test, type Browser, type Page } from "@playwright/test"; // Persona helpers: the two dev personas used across the sweep. // testuser = admin, sees everything. // rls-phone = restricted user; the Audiobooks library is HIDDEN from // them (seeded by the rollout; see the RLS plan §7.5). // // Sessions are established ONCE per persona and reused via // storageState — the login endpoint rate-limits rapid attempts, and a // sweep should not look like a brute-force run. const PERSONAS = { admin: { login: "testuser", password: "Test@Pass123!" }, restricted: { login: "rls-phone", password: "Test@Pass123!" }, } as const; type Persona = keyof typeof PERSONAS; const stateCache: Partial> = {}; async function authedPage(browser: Browser, persona: Persona): Promise { if (!stateCache[persona]) { const ctx = await browser.newContext(); const page = await ctx.newPage(); await page.goto("/login"); await page.fill('input[name="login"]', PERSONAS[persona].login); await page.fill('input[name="password"]', PERSONAS[persona].password); await page.click('button[type="submit"]'); await page.waitForLoadState("networkidle"); stateCache[persona] = await ctx.storageState(); await ctx.close(); } const ctx = await browser.newContext({ storageState: stateCache[persona] }); return ctx.newPage(); } // The restricted persona must never see Audiobooks content anywhere. const HIDDEN = "Audiobooks"; test.describe("authentication", () => { test("wrong password is rejected without a session", async ({ page }) => { await page.goto("/login"); await page.fill('input[name="login"]', PERSONAS.admin.login); await page.fill('input[name="password"]', "definitely-wrong"); await page.click('button[type="submit"]'); await page.waitForTimeout(1_500); const cookies = await page.context().cookies(); expect(cookies.find((c) => c.name === "token" && c.value.length > 10)).toBeUndefined(); }); }); test.describe("admin persona sees everything", () => { let page: Page; test.beforeAll(async ({ browser }) => { page = await authedPage(browser, "admin"); }); test("dashboard sections render with library content", async () => { await page.goto("/dashboard"); const body = (await page.textContent("body")) ?? ""; expect(body).toContain("Recently Added"); expect(body).toContain("Not Started"); expect(body).toContain(HIDDEN); // admin sees the hidden-from-others library }); test("search returns hidden-library titles for admin", async () => { const resp = await page.goto("/api/media-items/search?query=Witcher"); expect(resp?.status()).toBe(200); const body = (await page.textContent("body")) ?? ""; expect(body).toContain("Witcher"); }); }); test.describe("restricted persona: hidden means hidden", () => { let page: Page; test.beforeAll(async ({ browser }) => { page = await authedPage(browser, "restricted"); }); test("dashboard never shows the hidden library or its items", async () => { await page.goto("/dashboard"); expect((await page.textContent("body")) ?? "").toContain("Recently Added"); // real dashboard, not the landing page const body = (await page.textContent("body")) ?? ""; expect(body).not.toContain(HIDDEN); }); test("search results exclude hidden-library content", async () => { const resp = await page.goto("/api/media-items/search?query=Witcher"); expect(resp?.status()).toBe(200); const body = (await page.textContent("body")) ?? ""; // The Witcher audiobooks live in the hidden library — none may leak. expect(body).not.toContain("Witcher"); }); test("media payloads exclude hidden-library items", async () => { const resp = await page.goto("/api/media-items?limit=200"); expect(resp?.status()).toBe(200); const body = (await page.textContent("body")) ?? ""; expect(body).not.toContain(HIDDEN); }); });