195 lines
5.7 KiB
TypeScript
195 lines
5.7 KiB
TypeScript
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();
|
|
});
|