import { expect, test } from "@playwright/test"; function seedAuthStorage(page: import("@playwright/test").Page, role = "admin") { return page.addInitScript((seedRole) => { localStorage.setItem("token", "test-token"); localStorage.setItem("userId", "1"); localStorage.setItem("role", seedRole); localStorage.setItem("username", "manager-user"); }, role); } async function mockConfig(page: import("@playwright/test").Page) { await page.route("**/config", async (route) => { await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify({ maxFileSizeMB: 20, maxImageDimension: 800, imageQuality: 85, }), }); }); } test("join household modal accepts invite links but rejects legacy invite codes", async ({ page }) => { await seedAuthStorage(page); await mockConfig(page); await page.route("**/households", async (route) => { await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify([]), }); }); await page.route("**/api/invite-links/approval-token", async (route) => { await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify({ link: { token: "approval-token", status: "ACTIVE", viewerStatus: null, active_policy: "APPROVAL_REQUIRED", group_name: "Approval Home", }, }), }); }); await page.goto("/manage"); await page.getByRole("button", { name: "Join Household" }).click(); await page.getByLabel("Invite Link").fill("HABC123"); await page.getByRole("button", { name: "Open Invite" }).click(); await expect(page.getByText("Use a household invite link like /invite/abcd1234.")).toBeVisible(); await page.getByLabel("Invite Link").fill("/invite/approval-token"); await page.getByRole("button", { name: "Open Invite" }).click(); await expect(page).toHaveURL(/\/invite\/approval-token$/); await expect(page.getByRole("heading", { name: "Join Approval Home" })).toBeVisible(); }); test("household management shows pending invite approvals and can approve them", async ({ page }) => { await seedAuthStorage(page); await mockConfig(page); let members = [ { id: 1, username: "manager-user", role: "owner" }, ]; let pendingRequests = [ { id: 41, user_id: 7, username: "pending-pal", name: "Pending Pal", display_name: "", created_at: "2026-03-31T12:00:00.000Z", status: "PENDING", }, ]; await page.route("**/households", async (route) => { await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify([{ id: 1, name: "Approval Home", role: "owner", invite_code: "ABCD1234" }]), }); }); await page.route("**/households/1/members", async (route) => { await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify(members), }); }); await page.route("**/api/groups/join-policy", async (route) => { const request = route.request(); if (request.method() === "GET") { await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify({ joinPolicy: "APPROVAL_REQUIRED" }), }); return; } await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify({ ok: true }), }); }); await page.route("**/api/groups/invites", async (route) => { const request = route.request(); if (request.method() === "GET") { await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify({ links: [ { id: 9, token: "invite-token-1234", policy: "APPROVAL_REQUIRED", single_use: false, expires_at: "2030-01-01T00:00:00.000Z", used_at: null, revoked_at: null, }, ], }), }); return; } await route.fulfill({ status: 201, contentType: "application/json", body: JSON.stringify({ link: { id: 10, token: "new-token" } }), }); }); await page.route("**/api/groups/join-requests", async (route) => { await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify({ requests: pendingRequests }), }); }); await page.route("**/api/groups/join-requests/decision", async (route) => { const body = route.request().postDataJSON() as { requestId?: number; decision?: string }; if (body.requestId === 41 && body.decision === "APPROVE") { pendingRequests = []; members = [ ...members, { id: 7, username: "pending-pal", role: "member" }, ]; } await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify({ request: { id: 41, user_id: 7, status: body.decision === "APPROVE" ? "APPROVED" : "DENIED", }, }), }); }); await page.goto("/manage?tab=household"); await expect(page.getByRole("heading", { name: "Invite Links" })).toBeVisible(); await expect(page.getByText("Pending Pal")).toBeVisible(); await expect(page.getByText("Invite Code")).toHaveCount(0); await page.getByRole("button", { name: "Approve" }).click(); await expect(page.locator(".action-toast.action-toast-success")).toContainText("Approved join request"); await expect(page.locator(".action-toast.action-toast-success")).toContainText("Pending Pal"); await expect(page.getByText("No pending join requests right now.")).toBeVisible(); await expect(page.getByText("Members (2)")).toBeVisible(); });