208 lines
6.9 KiB
TypeScript
208 lines
6.9 KiB
TypeScript
import { expect, test, type Page, type Route } from "@playwright/test";
|
|
|
|
import { apiPathPattern, mockApi, seedAuthenticatedState } from "./support/network.js";
|
|
|
|
// This route is sensitive to stale cached Vue modules when the PWA service worker is active in mobile runs.
|
|
test.use({ serviceWorkers: "block" });
|
|
|
|
const json = (body: unknown, status = 200) => ({
|
|
status,
|
|
contentType: "application/json",
|
|
body: JSON.stringify(body),
|
|
});
|
|
|
|
const permissionCatalog = {
|
|
"/orders": {
|
|
GET: {
|
|
list_orders: "List orders",
|
|
},
|
|
POST: {
|
|
add_order: "Create orders",
|
|
},
|
|
},
|
|
"/admin/bookings": {
|
|
POST: {
|
|
create_bookings: "Create bookings",
|
|
},
|
|
},
|
|
"/superuser/system/replication": {
|
|
GET: {
|
|
superuser_replication_view: "View replication status",
|
|
},
|
|
},
|
|
};
|
|
|
|
const departments = [
|
|
{
|
|
id: 1,
|
|
name: "Copenhagen",
|
|
},
|
|
{
|
|
id: 2,
|
|
name: "Odense",
|
|
},
|
|
];
|
|
|
|
type PermissionCall = {
|
|
action: "add" | "remove";
|
|
permission: string;
|
|
};
|
|
|
|
const openGroupTab = async (page: Page, label: RegExp) => {
|
|
await page.locator(".tabs").getByText(label).click();
|
|
};
|
|
|
|
const clickPermissionSwitch = async (page: Page, permission: string) => {
|
|
await page.locator(`label[for="role-permission-${permission}"]`).click();
|
|
};
|
|
|
|
const installRolePermissionRoutes = async (
|
|
page: Page,
|
|
options: {
|
|
failAction?: "add" | "remove";
|
|
failPermission?: string;
|
|
} = {}
|
|
) => {
|
|
let rolePermissions = ["user", "superuser", "department_access_1", "list_orders"];
|
|
const calls: PermissionCall[] = [];
|
|
|
|
const rolePayload = () => ({
|
|
id: 2,
|
|
name: "Operations manager",
|
|
description: "Can manage operations.",
|
|
created_at: "2026-07-06T08:00:00.000Z",
|
|
permissions: [...rolePermissions],
|
|
});
|
|
|
|
await page.route(apiPathPattern("/permissions"), async (route: Route) => {
|
|
await route.fulfill(json({ data: permissionCatalog }));
|
|
});
|
|
|
|
await page.route(apiPathPattern("/departments"), async (route: Route) => {
|
|
await route.fulfill(json({ data: departments }));
|
|
});
|
|
|
|
await page.route(apiPathPattern("/roles"), async (route: Route) => {
|
|
await route.fulfill(json({ data: rolePayload() }));
|
|
});
|
|
|
|
await page.route(apiPathPattern("/roles/permissions"), async (route: Route) => {
|
|
const request = route.request();
|
|
const method = request.method();
|
|
const url = new URL(request.url());
|
|
const body = method === "POST" ? request.postDataJSON() : {};
|
|
const permission =
|
|
method === "POST" ? String(body.permission_id || "") : String(url.searchParams.get("permission_id") || "");
|
|
const action = method === "POST" ? "add" : "remove";
|
|
|
|
calls.push({ action, permission });
|
|
|
|
if (options.failAction === action && options.failPermission === permission) {
|
|
await route.fulfill(json({ data: { message: "Mutation failed" } }, 500));
|
|
return;
|
|
}
|
|
|
|
if (action === "add" && !rolePermissions.includes(permission)) {
|
|
rolePermissions = [...rolePermissions, permission];
|
|
}
|
|
if (action === "remove") {
|
|
rolePermissions = rolePermissions.filter((value) => value !== permission);
|
|
}
|
|
|
|
await route.fulfill(json({ data: rolePayload() }));
|
|
});
|
|
|
|
return {
|
|
calls,
|
|
getRolePermissions: () => [...rolePermissions],
|
|
};
|
|
};
|
|
|
|
const bootRolePermissionsPage = async (page: Page, routeOptions = {}) => {
|
|
await page.addInitScript(() => {
|
|
window.localStorage.setItem("locale", "en");
|
|
});
|
|
await seedAuthenticatedState(page, "superuser-role-permissions-token");
|
|
await mockApi(page, {
|
|
authenticated: true,
|
|
permissions: [
|
|
"superuser",
|
|
"user",
|
|
"permissions_list",
|
|
"list_roles",
|
|
"add_role_permission",
|
|
"delete_role_permission",
|
|
],
|
|
sessionData: {
|
|
group_id: 1,
|
|
},
|
|
});
|
|
const harness = await installRolePermissionRoutes(page, routeOptions);
|
|
|
|
await page.goto("/superuser/roles/2", { waitUntil: "domcontentloaded" });
|
|
await expect(page.getByTestId("role-permissions-page")).toBeVisible();
|
|
await expect(page.getByTestId("role-permissions-loading")).toHaveCount(0);
|
|
|
|
return harness;
|
|
};
|
|
|
|
test.describe("Superuser role permissions", () => {
|
|
test("@smoke @pr loads grouped permissions, searches, and toggles without a blank page", async ({ page }) => {
|
|
const { calls } = await bootRolePermissionsPage(page);
|
|
|
|
await expect(page.getByText("Operations manager permissions")).toBeVisible();
|
|
await expect(page.getByTestId("role-permissions-selected-count")).toContainText("4 of 9 selected");
|
|
await expect(page.getByRole("heading", { name: "Access" })).toBeVisible();
|
|
|
|
await openGroupTab(page, /Orders and POS/);
|
|
await expect(page.getByTestId("role-permission-row-list_orders")).toContainText("List orders");
|
|
await clickPermissionSwitch(page, "list_orders");
|
|
|
|
await expect.poll(() => calls).toContainEqual({ action: "remove", permission: "list_orders" });
|
|
await expect(page.getByTestId("role-permissions-page")).toBeVisible();
|
|
await expect(page.locator("body")).not.toContainText(/white screen|TypeError|Unhandled/i);
|
|
|
|
await page.getByTestId("role-permissions-search").fill("booking");
|
|
await openGroupTab(page, /Bookings/);
|
|
await expect(page.getByTestId("role-permission-row-create_bookings")).toContainText("Create bookings");
|
|
await openGroupTab(page, /Orders and POS/);
|
|
await expect(page.getByTestId("role-permissions-empty-orders_pos")).toBeVisible();
|
|
});
|
|
|
|
test("selects and clears all permissions in a group without no-op requests", async ({ page }) => {
|
|
const { calls, getRolePermissions } = await bootRolePermissionsPage(page);
|
|
|
|
await openGroupTab(page, /Orders and POS/);
|
|
await page.getByTestId("role-permissions-clear-all-orders_pos").click();
|
|
await expect.poll(() => calls).toEqual([{ action: "remove", permission: "list_orders" }]);
|
|
expect(getRolePermissions()).not.toContain("list_orders");
|
|
expect(getRolePermissions()).not.toContain("add_order");
|
|
|
|
await page.getByTestId("role-permissions-select-all-orders_pos").click();
|
|
await expect
|
|
.poll(() => calls)
|
|
.toEqual([
|
|
{ action: "remove", permission: "list_orders" },
|
|
{ action: "add", permission: "add_order" },
|
|
{ action: "add", permission: "list_orders" },
|
|
]);
|
|
expect(getRolePermissions()).toEqual(expect.arrayContaining(["list_orders", "add_order"]));
|
|
});
|
|
|
|
test("keeps the page rendered and reloads role permissions when a mutation fails", async ({ page }) => {
|
|
const { calls, getRolePermissions } = await bootRolePermissionsPage(page, {
|
|
failAction: "add",
|
|
failPermission: "add_order",
|
|
});
|
|
|
|
await openGroupTab(page, /Orders and POS/);
|
|
await clickPermissionSwitch(page, "add_order");
|
|
|
|
await expect.poll(() => calls).toContainEqual({ action: "add", permission: "add_order" });
|
|
await expect(page.getByTestId("role-permission-error")).toBeVisible();
|
|
await expect(page.getByTestId("role-permissions-page")).toBeVisible();
|
|
await expect(page.getByTestId("role-permission-toggle-add_order")).not.toBeChecked();
|
|
expect(getRolePermissions()).not.toContain("add_order");
|
|
});
|
|
});
|