import { test, expect } from "@playwright/test"; import { getTomorrowDateStr } from "./helpers/date"; import { API_URL, REGULAR_USER, ADMIN_USER, clearAuth, loginUser } from "./helpers/auth"; /** * Availability Page E2E Tests * * Tests for the admin availability management page. */ // Helper to get tomorrow's date in the format displayed on the page function getTomorrowDisplay(): string { const tomorrow = new Date(); tomorrow.setDate(tomorrow.getDate() + 1); return tomorrow.toLocaleDateString("en-US", { weekday: "short", month: "short", day: "numeric" }); } test.describe("Availability Page - Admin Access", () => { test.beforeEach(async ({ page }) => { await clearAuth(page); await loginUser(page, ADMIN_USER.email, ADMIN_USER.password); }); test("admin can access availability page and UI elements work", async ({ page }) => { // Test navigation link await page.goto("/admin/trades"); const availabilityLink = page.locator('a[href="/admin/availability"]'); await expect(availabilityLink).toBeVisible(); // Test page access and structure await page.goto("/admin/availability"); await expect(page).toHaveURL("/admin/availability"); await expect(page.getByRole("heading", { name: "Availability" })).toBeVisible(); await expect(page.getByText("Configure your available time slots")).toBeVisible(); // Test calendar grid const tomorrowText = getTomorrowDisplay(); await expect(page.getByText(tomorrowText)).toBeVisible(); await expect(page.getByText("No availability").first()).toBeVisible(); // Test edit modal await page.getByText(tomorrowText).click(); await expect(page.getByRole("heading", { name: /Edit Availability/ })).toBeVisible(); await expect(page.getByRole("button", { name: "Save" })).toBeVisible(); await expect(page.getByRole("button", { name: "Cancel" })).toBeVisible(); }); test("can add, clear, and add multiple availability slots", async ({ page }) => { await page.goto("/admin/availability"); // Wait for initial data load to complete await page.waitForLoadState("networkidle"); // Find a day card with "No availability" and click on it const dayCardWithNoAvailability = page .locator('[data-testid^="day-card-"]') .filter({ has: page.getByText("No availability"), }) .first(); // Get the testid so we can find the same card later const testId = await dayCardWithNoAvailability.getAttribute("data-testid"); const targetCard = page.locator(`[data-testid="${testId}"]`); // First add availability await dayCardWithNoAvailability.click(); await expect(page.getByRole("heading", { name: /Edit Availability/ })).toBeVisible(); // Set up listeners for both PUT and GET before clicking Save to avoid race condition const savePutPromise = page.waitForResponse( (resp) => resp.url().includes("/api/admin/availability") && resp.request().method() === "PUT" ); const saveGetPromise = page.waitForResponse( (resp) => resp.url().includes("/api/admin/availability") && resp.request().method() === "GET" ); await page.getByRole("button", { name: "Save" }).click(); await savePutPromise; await saveGetPromise; await expect(page.getByRole("heading", { name: /Edit Availability/ })).not.toBeVisible(); // Verify slot exists in the specific card we clicked await expect(targetCard.getByText("09:00 - 17:00")).toBeVisible(); // Now clear it - click on the same card using the testid await targetCard.click(); await expect(page.getByRole("heading", { name: /Edit Availability/ })).toBeVisible(); // Set up listeners for both PUT and GET before clicking Clear to avoid race condition const clearPutPromise = page.waitForResponse( (resp) => resp.url().includes("/api/admin/availability") && resp.request().method() === "PUT" ); const clearGetPromise = page.waitForResponse( (resp) => resp.url().includes("/api/admin/availability") && resp.request().method() === "GET" ); await page.getByRole("button", { name: "Clear All" }).click(); await clearPutPromise; await clearGetPromise; // Wait for modal to close await expect(page.getByRole("heading", { name: /Edit Availability/ })).not.toBeVisible(); // Slot should be gone from this specific card await expect(targetCard.getByText("09:00 - 17:00")).not.toBeVisible(); // Now test adding multiple slots - find another day card await page.waitForLoadState("networkidle"); const anotherDayCard = page .locator('[data-testid^="day-card-"]') .filter({ has: page.getByText("No availability"), }) .first(); const anotherTestId = await anotherDayCard.getAttribute("data-testid"); const anotherTargetCard = page.locator(`[data-testid="${anotherTestId}"]`); await anotherDayCard.click(); await expect(page.getByRole("heading", { name: /Edit Availability/ })).toBeVisible(); // First slot is 09:00-17:00 by default - change it to morning only const timeSelects = page.locator("select"); await timeSelects.nth(1).selectOption("12:00"); // Add another slot for afternoon await page.getByText("+ Add Time Range").click(); await timeSelects.nth(2).selectOption("14:00"); await timeSelects.nth(3).selectOption("17:00"); // Save multiple slots const putPromise = page.waitForResponse( (resp) => resp.url().includes("/api/admin/availability") && resp.request().method() === "PUT" ); const getPromise = page.waitForResponse( (resp) => resp.url().includes("/api/admin/availability") && resp.request().method() === "GET" ); await page.getByRole("button", { name: "Save" }).click(); await putPromise; await getPromise; await expect(page.getByRole("heading", { name: /Edit Availability/ })).not.toBeVisible(); // Should see both slots await expect(anotherTargetCard.getByText("09:00 - 12:00")).toBeVisible(); await expect(anotherTargetCard.getByText("14:00 - 17:00")).toBeVisible(); }); }); test.describe("Availability Page - Access Control", () => { test("regular user and unauthenticated user cannot access availability page", async ({ page, }) => { // Test unauthenticated access await clearAuth(page); await page.goto("/admin/availability"); await expect(page).toHaveURL("/login"); // Test regular user access await loginUser(page, REGULAR_USER.email, REGULAR_USER.password); await page.goto("/"); const availabilityLink = page.locator('a[href="/admin/availability"]'); await expect(availabilityLink).toHaveCount(0); await page.goto("/admin/availability"); await expect(page).not.toHaveURL("/admin/availability"); }); }); test.describe("Availability API", () => { test("admin can set availability via API, regular user cannot", async ({ page, request }) => { // Test admin API access await clearAuth(page); await loginUser(page, ADMIN_USER.email, ADMIN_USER.password); const cookies = await page.context().cookies(); const authCookie = cookies.find((c) => c.name === "auth_token"); if (authCookie) { const dateStr = getTomorrowDateStr(); const response = await request.put(`${API_URL}/api/admin/availability`, { headers: { Cookie: `auth_token=${authCookie.value}`, "Content-Type": "application/json", }, data: { date: dateStr, slots: [{ start_time: "10:00:00", end_time: "12:00:00" }], }, }); expect(response.status()).toBe(200); const data = await response.json(); expect(data.date).toBe(dateStr); expect(data.slots).toHaveLength(1); } // Test regular user API access await clearAuth(page); await loginUser(page, REGULAR_USER.email, REGULAR_USER.password); const regularCookies = await page.context().cookies(); const regularAuthCookie = regularCookies.find((c) => c.name === "auth_token"); if (regularAuthCookie) { const dateStr = getTomorrowDateStr(); const response = await request.get( `${API_URL}/api/admin/availability?from=${dateStr}&to=${dateStr}`, { headers: { Cookie: `auth_token=${regularAuthCookie.value}`, }, } ); expect(response.status()).toBe(403); } }); });