test(app): cover Batch 1 functional-completion regressions
Add Playwright coverage for the fixes in this batch: vehicle search actually changes the rendered rows, booking pagination stays within 25 rows and page 2 differs from page 1, session survives a refresh, logout invalidates the server session, direct navigation without a session redirects to login, and Rental Employee is blocked from manager-only pages both in the UI (hidden nav, restricted message) and directly against the API (403).
This commit is contained in:
@@ -48,6 +48,28 @@ test("vehicles page: status filter and attention-only checkbox both work", async
|
||||
expect(attentionCells.every((c) => c.includes("Needs attention"))).toBeTruthy();
|
||||
});
|
||||
|
||||
test("vehicles page: free-text search actually filters the rendered rows", async ({ page }) => {
|
||||
await page.goto("/vehicles");
|
||||
await expect(page.locator(".data-table")).toBeVisible();
|
||||
const totalRows = await page.locator(".data-table tbody tr").count();
|
||||
expect(totalRows).toBeGreaterThan(1);
|
||||
|
||||
const searchBox = page.getByRole("form", { name: "Filter vehicles" }).getByLabel("Search");
|
||||
await searchBox.fill("MO-001");
|
||||
await expect(async () => {
|
||||
const rows = await page.locator(".data-table tbody tr").count();
|
||||
expect(rows).toBe(1);
|
||||
}).toPass({ timeout: 5000 });
|
||||
const refs = await page.locator(".data-table tbody tr th a").allTextContents();
|
||||
expect(refs).toEqual(["MO-001"]);
|
||||
|
||||
await searchBox.fill("");
|
||||
await expect(async () => {
|
||||
const rows = await page.locator(".data-table tbody tr").count();
|
||||
expect(rows).toBe(totalRows);
|
||||
}).toPass({ timeout: 5000 });
|
||||
});
|
||||
|
||||
test("vehicle detail: all tabs render distinct content", async ({ page }) => {
|
||||
await page.goto("/vehicles/MO-016");
|
||||
await expect(page.getByRole("heading", { name: /MO-016/ })).toBeVisible();
|
||||
@@ -66,6 +88,33 @@ test("bookings page: status filter works", async ({ page }) => {
|
||||
expect(statuses.every((s) => s.includes("returned"))).toBeTruthy();
|
||||
});
|
||||
|
||||
test("bookings page: pagination renders at most 25 rows and page 2 differs from page 1", async ({
|
||||
page,
|
||||
}) => {
|
||||
await page.goto("/bookings");
|
||||
await expect(page.locator(".data-table")).toBeVisible();
|
||||
|
||||
const page1Count = await page.locator(".data-table tbody tr").count();
|
||||
expect(page1Count).toBeLessThanOrEqual(25);
|
||||
const page1Refs = await page.locator(".data-table tbody tr th a").allTextContents();
|
||||
|
||||
const nextButton = page.getByRole("button", { name: "Next" });
|
||||
await expect(nextButton).toBeEnabled();
|
||||
await nextButton.click();
|
||||
|
||||
await expect(async () => {
|
||||
const page2Refs = await page.locator(".data-table tbody tr th a").allTextContents();
|
||||
expect(page2Refs.length).toBeGreaterThan(0);
|
||||
expect(page2Refs).not.toEqual(page1Refs);
|
||||
}).toPass({ timeout: 5000 });
|
||||
|
||||
const page2Count = await page.locator(".data-table tbody tr").count();
|
||||
expect(page2Count).toBeLessThanOrEqual(25);
|
||||
|
||||
const prevButton = page.getByRole("button", { name: "Previous" });
|
||||
await expect(prevButton).toBeEnabled();
|
||||
});
|
||||
|
||||
test("data quality page: status and rule-type filters work", async ({ page }) => {
|
||||
await page.goto("/data-quality");
|
||||
await expect(page.locator(".data-table")).toBeVisible();
|
||||
@@ -141,14 +190,78 @@ test("switch role button logs out and returns to login", async ({ page }) => {
|
||||
await expect(page).toHaveURL(/\/login$/);
|
||||
});
|
||||
|
||||
test("rental employee role sees restricted automation page and cannot access reset", async ({
|
||||
test("session survives a page refresh and restores the correct role", async ({ page }) => {
|
||||
await page.goto("/vehicles");
|
||||
await page.reload();
|
||||
await expect(page).toHaveURL(/\/vehicles$/);
|
||||
await expect(page.getByText("Operations manager")).toBeVisible();
|
||||
await expect(page.locator(".data-table")).toBeVisible();
|
||||
});
|
||||
|
||||
test("logout invalidates the server session so a refresh returns to login", async ({ page }) => {
|
||||
await page.goto("/dashboard");
|
||||
await page.getByRole("button", { name: "Switch role" }).click();
|
||||
await expect(page).toHaveURL(/\/login$/);
|
||||
|
||||
// Directly re-requesting a protected route after logout must not restore access from a
|
||||
// stale client cache; the server-side cookie is gone.
|
||||
await page.goto("/dashboard");
|
||||
await expect(page).toHaveURL(/\/login$/);
|
||||
});
|
||||
|
||||
test("direct navigation to a protected route without a session redirects to login", async ({
|
||||
page,
|
||||
context,
|
||||
}) => {
|
||||
await context.clearCookies();
|
||||
await page.goto("/vehicles");
|
||||
await expect(page).toHaveURL(/\/login$/);
|
||||
});
|
||||
|
||||
test("rental employee role has a restricted nav and cannot reach manager-only pages", async ({
|
||||
page,
|
||||
}) => {
|
||||
await page.getByRole("button", { name: "Switch role" }).click();
|
||||
await page.getByRole("button", { name: "Open as Rental Employee" }).click();
|
||||
await expect(page).toHaveURL(/\/dashboard$/);
|
||||
await page.getByRole("link", { name: "Integrations" }).click();
|
||||
|
||||
// Manager-only nav items are not shown at all, not merely disabled.
|
||||
await expect(page.getByRole("link", { name: "Data quality" })).toHaveCount(0);
|
||||
await expect(page.getByRole("link", { name: "Integrations" })).toHaveCount(0);
|
||||
await expect(page.getByRole("link", { name: "Audit trail" })).toHaveCount(0);
|
||||
|
||||
// Direct URL navigation is still blocked server-side and shows the same restricted
|
||||
// message as a defense-in-depth measure, not just a hidden button.
|
||||
await page.goto("/automation");
|
||||
await expect(
|
||||
page.getByText("Automation delivery status is visible to Operations Managers only."),
|
||||
).toBeVisible();
|
||||
|
||||
await page.goto("/data-quality");
|
||||
await expect(
|
||||
page.getByText("Data-quality evidence and resolutions are visible to Operations Managers only."),
|
||||
).toBeVisible();
|
||||
|
||||
await page.goto("/audit");
|
||||
await expect(page.getByText("Audit history is visible to Operations Managers only.")).toBeVisible();
|
||||
});
|
||||
|
||||
test("rental employee direct API access to manager-only endpoints is rejected", async ({
|
||||
page,
|
||||
request,
|
||||
}) => {
|
||||
await page.getByRole("button", { name: "Switch role" }).click();
|
||||
await page.getByRole("button", { name: "Open as Rental Employee" }).click();
|
||||
await expect(page).toHaveURL(/\/dashboard$/);
|
||||
|
||||
const cookies = await page.context().cookies();
|
||||
const sessionCookie = cookies.find((c) => c.name === "mobilityops_session");
|
||||
const cookieHeader = sessionCookie ? `${sessionCookie.name}=${sessionCookie.value}` : "";
|
||||
|
||||
for (const path of ["/api/v1/data-quality/issues", "/api/v1/audit", "/api/v1/workflows"]) {
|
||||
const response = await request.get(`http://localhost:8128${path}`, {
|
||||
headers: { Cookie: cookieHeader },
|
||||
});
|
||||
expect(response.status(), path).toBe(403);
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user