- Install prettier - Configure .prettierrc.json and .prettierignore - Add npm scripts: format, format:check - Add Makefile target: format-frontend - Format all frontend files
167 lines
6.2 KiB
TypeScript
167 lines
6.2 KiB
TypeScript
import { test, expect, Page } from "@playwright/test";
|
|
|
|
// Admin credentials from seed data
|
|
const ADMIN_EMAIL = "admin@example.com";
|
|
const ADMIN_PASSWORD = "admin123";
|
|
|
|
// Regular user from seed data
|
|
const REGULAR_USER_EMAIL = "user@example.com";
|
|
|
|
async function loginAsAdmin(page: Page) {
|
|
await page.goto("/login");
|
|
await page.fill('input[type="email"]', ADMIN_EMAIL);
|
|
await page.fill('input[type="password"]', ADMIN_PASSWORD);
|
|
await page.click('button[type="submit"]');
|
|
await expect(page).toHaveURL("/audit");
|
|
}
|
|
|
|
test.describe("Admin Invites Page", () => {
|
|
test.beforeEach(async ({ page }) => {
|
|
await page.context().clearCookies();
|
|
await loginAsAdmin(page);
|
|
});
|
|
|
|
test("admin can access invites page", async ({ page }) => {
|
|
await page.goto("/admin/invites");
|
|
await expect(page.getByRole("heading", { name: "Create Invite" })).toBeVisible();
|
|
await expect(page.getByRole("heading", { name: "All Invites" })).toBeVisible();
|
|
});
|
|
|
|
test("godfather selection is a dropdown with users, not a number input", async ({ page }) => {
|
|
await page.goto("/admin/invites");
|
|
|
|
// The godfather selector should be a <select> element, not an <input type="number">
|
|
const selectElement = page.locator("select").first();
|
|
await expect(selectElement).toBeVisible();
|
|
|
|
// Wait for users to load by checking for a known user in the dropdown
|
|
await expect(selectElement).toContainText(REGULAR_USER_EMAIL);
|
|
|
|
// Verify it has user options (at least the seeded users)
|
|
const options = selectElement.locator("option");
|
|
const optionCount = await options.count();
|
|
|
|
// Should have at least 2 options: placeholder + at least one user
|
|
expect(optionCount).toBeGreaterThanOrEqual(2);
|
|
|
|
// There should NOT be a number input for godfather ID
|
|
const numberInput = page.locator('input[type="number"]');
|
|
await expect(numberInput).toHaveCount(0);
|
|
});
|
|
|
|
test("can create invite by selecting user from dropdown", async ({ page }) => {
|
|
await page.goto("/admin/invites");
|
|
|
|
// Wait for page to load
|
|
await page.waitForSelector("select");
|
|
|
|
// Select the regular user as godfather
|
|
const godfatherSelect = page.locator("select").first();
|
|
await godfatherSelect.selectOption({ label: REGULAR_USER_EMAIL });
|
|
|
|
// Click create invite
|
|
await page.click('button:has-text("Create Invite")');
|
|
|
|
// Wait for the invite to appear in the table
|
|
await expect(page.locator("table")).toContainText(REGULAR_USER_EMAIL);
|
|
|
|
// Verify an invite code appears (format: word-word-NN)
|
|
const inviteCodeCell = page.locator("td").first();
|
|
await expect(inviteCodeCell).toHaveText(/^[a-z]+-[a-z]+-\d{2}$/);
|
|
});
|
|
|
|
test("create button is disabled when no user selected", async ({ page }) => {
|
|
await page.goto("/admin/invites");
|
|
|
|
// Wait for page to load
|
|
await page.waitForSelector("select");
|
|
|
|
// The create button should be disabled initially (no user selected)
|
|
const createButton = page.locator('button:has-text("Create Invite")');
|
|
await expect(createButton).toBeDisabled();
|
|
|
|
// Select a user
|
|
const godfatherSelect = page.locator("select").first();
|
|
await godfatherSelect.selectOption({ label: REGULAR_USER_EMAIL });
|
|
|
|
// Now the button should be enabled
|
|
await expect(createButton).toBeEnabled();
|
|
});
|
|
|
|
test("can revoke a ready invite", async ({ page }) => {
|
|
await page.goto("/admin/invites");
|
|
await page.waitForSelector("select");
|
|
|
|
// Create an invite first
|
|
const godfatherSelect = page.locator("select").first();
|
|
await godfatherSelect.selectOption({ label: REGULAR_USER_EMAIL });
|
|
await page.click('button:has-text("Create Invite")');
|
|
|
|
// Wait for the new invite to appear and capture its code
|
|
// The new invite should be the first row with godfather = REGULAR_USER_EMAIL and status = ready
|
|
const newInviteRow = page
|
|
.locator("tr")
|
|
.filter({ hasText: REGULAR_USER_EMAIL })
|
|
.filter({ hasText: "ready" })
|
|
.first();
|
|
await expect(newInviteRow).toBeVisible();
|
|
|
|
// Get the invite code from this row (first cell)
|
|
const inviteCode = await newInviteRow.locator("td").first().textContent();
|
|
|
|
// Click revoke on this specific row
|
|
await newInviteRow.locator('button:has-text("Revoke")').click();
|
|
|
|
// Verify this specific invite now shows "revoked"
|
|
const revokedRow = page.locator("tr").filter({ hasText: inviteCode! });
|
|
await expect(revokedRow).toContainText("revoked");
|
|
});
|
|
|
|
test("status filter works", async ({ page }) => {
|
|
await page.goto("/admin/invites");
|
|
await page.waitForSelector("select");
|
|
|
|
// Create an invite
|
|
const godfatherSelect = page.locator("select").first();
|
|
await godfatherSelect.selectOption({ label: REGULAR_USER_EMAIL });
|
|
await page.click('button:has-text("Create Invite")');
|
|
await expect(page.locator("table")).toContainText("ready");
|
|
|
|
// Filter by "revoked" status - should show no ready invites
|
|
const statusFilter = page.locator("select").nth(1); // Second select is the status filter
|
|
await statusFilter.selectOption("revoked");
|
|
|
|
// Wait for the filter to apply
|
|
await page.waitForResponse((resp) => resp.url().includes("status=revoked"));
|
|
|
|
// Filter by "ready" status - should show our invite
|
|
await statusFilter.selectOption("ready");
|
|
await page.waitForResponse((resp) => resp.url().includes("status=ready"));
|
|
await expect(page.locator("table")).toContainText("ready");
|
|
});
|
|
});
|
|
|
|
test.describe("Admin Invites Access Control", () => {
|
|
test("regular user cannot access admin invites page", async ({ page }) => {
|
|
// Login as regular user
|
|
await page.goto("/login");
|
|
await page.fill('input[type="email"]', REGULAR_USER_EMAIL);
|
|
await page.fill('input[type="password"]', "user123");
|
|
await page.click('button[type="submit"]');
|
|
await expect(page).toHaveURL("/");
|
|
|
|
// Try to access admin invites page
|
|
await page.goto("/admin/invites");
|
|
|
|
// Should be redirected away (to home page based on fallbackRedirect)
|
|
await expect(page).not.toHaveURL("/admin/invites");
|
|
});
|
|
|
|
test("unauthenticated user cannot access admin invites page", async ({ page }) => {
|
|
await page.context().clearCookies();
|
|
await page.goto("/admin/invites");
|
|
|
|
// Should be redirected to login
|
|
await expect(page).toHaveURL("/login");
|
|
});
|
|
});
|