Phase 7: Final cleanup - Remove deprecated booking/appointment code
Deleted deprecated files: - backend/routes/booking.py - frontend/app/admin/appointments/, booking/, appointments/, sum/, audit/ - frontend/app/utils/appointment.ts - frontend/e2e/booking.spec.ts, appointments.spec.ts Updated references: - exchange/page.tsx: Use /api/exchange/slots instead of /api/booking/slots - useRequireAuth.ts: Redirect to /admin/trades and /exchange - profile.tsx, invites.tsx: Update fallback redirect - E2E tests: Update all /audit references to /admin/trades - profile.test.tsx: Update admin redirect test
This commit is contained in:
parent
9e8d0af435
commit
bbd9fae763
16 changed files with 29 additions and 2103 deletions
|
|
@ -12,7 +12,7 @@ async function loginAsAdmin(page: Page) {
|
|||
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");
|
||||
await expect(page).toHaveURL("/admin/trades");
|
||||
}
|
||||
|
||||
test.describe("Admin Invites Page", () => {
|
||||
|
|
|
|||
|
|
@ -1,230 +0,0 @@
|
|||
import { test, expect, Page } from "@playwright/test";
|
||||
import { getTomorrowDateStr } from "./helpers/date";
|
||||
import { API_URL, REGULAR_USER, ADMIN_USER, clearAuth, loginUser } from "./helpers/auth";
|
||||
|
||||
/**
|
||||
* Appointments Page E2E Tests
|
||||
*
|
||||
* Tests for viewing and cancelling user appointments.
|
||||
*/
|
||||
|
||||
// Set up availability and create a booking
|
||||
async function createTestBooking(page: Page) {
|
||||
const dateStr = getTomorrowDateStr();
|
||||
|
||||
// First login as admin to set availability
|
||||
await clearAuth(page);
|
||||
await loginUser(page, ADMIN_USER.email, ADMIN_USER.password);
|
||||
|
||||
const adminCookies = await page.context().cookies();
|
||||
const adminAuthCookie = adminCookies.find((c) => c.name === "auth_token");
|
||||
|
||||
if (!adminAuthCookie) throw new Error("No admin auth cookie");
|
||||
|
||||
await page.request.put(`${API_URL}/api/admin/availability`, {
|
||||
headers: {
|
||||
Cookie: `auth_token=${adminAuthCookie.value}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
data: {
|
||||
date: dateStr,
|
||||
slots: [{ start_time: "09:00:00", end_time: "12:00:00" }],
|
||||
},
|
||||
});
|
||||
|
||||
// Login as regular user
|
||||
await clearAuth(page);
|
||||
await loginUser(page, REGULAR_USER.email, REGULAR_USER.password);
|
||||
|
||||
const userCookies = await page.context().cookies();
|
||||
const userAuthCookie = userCookies.find((c) => c.name === "auth_token");
|
||||
|
||||
if (!userAuthCookie) throw new Error("No user auth cookie");
|
||||
|
||||
// Create booking - use a random minute to avoid conflicts with parallel tests
|
||||
const randomMinute = Math.floor(Math.random() * 11) * 15; // 0, 15, 30, 45 etc up to 165 min
|
||||
const hour = 9 + Math.floor(randomMinute / 60);
|
||||
const minute = randomMinute % 60;
|
||||
const timeStr = `${String(hour).padStart(2, "0")}:${String(minute).padStart(2, "0")}:00`;
|
||||
|
||||
const response = await page.request.post(`${API_URL}/api/booking`, {
|
||||
headers: {
|
||||
Cookie: `auth_token=${userAuthCookie.value}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
data: {
|
||||
slot_start: `${dateStr}T${timeStr}Z`,
|
||||
note: "Test appointment",
|
||||
},
|
||||
});
|
||||
|
||||
return response.json();
|
||||
}
|
||||
|
||||
test.describe("Appointments Page - Regular User Access", () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await clearAuth(page);
|
||||
await loginUser(page, REGULAR_USER.email, REGULAR_USER.password);
|
||||
});
|
||||
|
||||
test("regular user can access appointments page", async ({ page }) => {
|
||||
await page.goto("/appointments");
|
||||
|
||||
await expect(page).toHaveURL("/appointments");
|
||||
await expect(page.getByRole("heading", { name: "My Appointments" })).toBeVisible();
|
||||
});
|
||||
|
||||
test("regular user sees Appointments link in navigation", async ({ page }) => {
|
||||
await page.goto("/");
|
||||
|
||||
await expect(page.getByRole("link", { name: "Appointments" })).toBeVisible();
|
||||
});
|
||||
|
||||
test("shows empty state when no appointments", async ({ page }) => {
|
||||
await page.goto("/appointments");
|
||||
|
||||
await expect(page.getByText("don't have any appointments")).toBeVisible();
|
||||
await expect(page.getByRole("link", { name: "Book an appointment" })).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("Appointments Page - With Bookings", () => {
|
||||
test("shows user's appointments", async ({ page }) => {
|
||||
// Create a booking first
|
||||
await createTestBooking(page);
|
||||
|
||||
// Go to appointments page
|
||||
await page.goto("/appointments");
|
||||
|
||||
// Should see the appointment
|
||||
await expect(page.getByText("Test appointment")).toBeVisible();
|
||||
await expect(page.getByText("Booked", { exact: true })).toBeVisible();
|
||||
});
|
||||
|
||||
test("can cancel an appointment", async ({ page }) => {
|
||||
// Create a booking
|
||||
await createTestBooking(page);
|
||||
|
||||
// Go to appointments page
|
||||
await page.goto("/appointments");
|
||||
|
||||
// Click cancel button
|
||||
await page.getByRole("button", { name: "Cancel" }).first().click();
|
||||
|
||||
// Confirm cancellation
|
||||
await page.getByRole("button", { name: "Confirm" }).click();
|
||||
|
||||
// Should show cancelled status
|
||||
await expect(page.getByText("Cancelled by you")).toBeVisible();
|
||||
});
|
||||
|
||||
test("can abort cancellation", async ({ page }) => {
|
||||
// Create a booking
|
||||
await createTestBooking(page);
|
||||
|
||||
// Go to appointments page
|
||||
await page.goto("/appointments");
|
||||
|
||||
// Wait for appointments to load
|
||||
await expect(page.getByRole("heading", { name: /Upcoming/ })).toBeVisible({ timeout: 10000 });
|
||||
|
||||
// Click cancel button
|
||||
await page.getByRole("button", { name: "Cancel" }).first().click();
|
||||
|
||||
// Click No to abort
|
||||
await page.getByRole("button", { name: "No" }).click();
|
||||
|
||||
// Should still show as booked (use first() since there may be multiple bookings)
|
||||
await expect(page.getByText("Booked", { exact: true }).first()).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("Appointments Page - Access Control", () => {
|
||||
test("admin cannot access appointments page", async ({ page }) => {
|
||||
await clearAuth(page);
|
||||
await loginUser(page, ADMIN_USER.email, ADMIN_USER.password);
|
||||
|
||||
await page.goto("/appointments");
|
||||
|
||||
// Should be redirected
|
||||
await expect(page).not.toHaveURL("/appointments");
|
||||
});
|
||||
|
||||
test("admin does not see Appointments link", async ({ page }) => {
|
||||
await clearAuth(page);
|
||||
await loginUser(page, ADMIN_USER.email, ADMIN_USER.password);
|
||||
|
||||
await page.goto("/audit");
|
||||
|
||||
await expect(page.getByRole("link", { name: "Appointments" })).not.toBeVisible();
|
||||
});
|
||||
|
||||
test("unauthenticated user redirected to login", async ({ page }) => {
|
||||
await clearAuth(page);
|
||||
|
||||
await page.goto("/appointments");
|
||||
|
||||
await expect(page).toHaveURL("/login");
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("Appointments API", () => {
|
||||
test("regular user can view appointments via API", async ({ page }) => {
|
||||
await clearAuth(page);
|
||||
await loginUser(page, REGULAR_USER.email, REGULAR_USER.password);
|
||||
|
||||
const cookies = await page.context().cookies();
|
||||
const authCookie = cookies.find((c) => c.name === "auth_token");
|
||||
|
||||
if (authCookie) {
|
||||
const response = await page.request.get(`${API_URL}/api/appointments`, {
|
||||
headers: {
|
||||
Cookie: `auth_token=${authCookie.value}`,
|
||||
},
|
||||
});
|
||||
|
||||
expect(response.status()).toBe(200);
|
||||
expect(Array.isArray(await response.json())).toBe(true);
|
||||
}
|
||||
});
|
||||
|
||||
test("regular user can cancel appointment via API", async ({ page }) => {
|
||||
// Create a booking
|
||||
const booking = await createTestBooking(page);
|
||||
|
||||
const cookies = await page.context().cookies();
|
||||
const authCookie = cookies.find((c) => c.name === "auth_token");
|
||||
|
||||
if (authCookie && booking && booking.id) {
|
||||
const response = await page.request.post(`${API_URL}/api/appointments/${booking.id}/cancel`, {
|
||||
headers: {
|
||||
Cookie: `auth_token=${authCookie.value}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
data: {},
|
||||
});
|
||||
|
||||
expect(response.status()).toBe(200);
|
||||
const data = await response.json();
|
||||
expect(data.status).toBe("cancelled_by_user");
|
||||
}
|
||||
});
|
||||
|
||||
test("admin cannot view user appointments via API", async ({ page }) => {
|
||||
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 response = await page.request.get(`${API_URL}/api/appointments`, {
|
||||
headers: {
|
||||
Cookie: `auth_token=${authCookie.value}`,
|
||||
},
|
||||
});
|
||||
|
||||
expect(response.status()).toBe(403);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
@ -30,7 +30,7 @@ test.describe("Availability Page - Admin Access", () => {
|
|||
});
|
||||
|
||||
test("admin sees Availability link in nav", async ({ page }) => {
|
||||
await page.goto("/audit");
|
||||
await page.goto("/admin/trades");
|
||||
|
||||
const availabilityLink = page.locator('a[href="/admin/availability"]');
|
||||
await expect(availabilityLink).toBeVisible();
|
||||
|
|
|
|||
|
|
@ -1,393 +0,0 @@
|
|||
import { test, expect, Page } from "@playwright/test";
|
||||
import { getTomorrowDateStr } from "./helpers/date";
|
||||
import { API_URL, REGULAR_USER, ADMIN_USER, clearAuth, loginUser } from "./helpers/auth";
|
||||
|
||||
/**
|
||||
* Booking Page E2E Tests
|
||||
*
|
||||
* Tests for the user booking page.
|
||||
*/
|
||||
|
||||
// Set up availability for a date using the API with retry logic
|
||||
async function setAvailability(page: Page, dateStr: string, maxRetries = 3) {
|
||||
const cookies = await page.context().cookies();
|
||||
const authCookie = cookies.find((c) => c.name === "auth_token");
|
||||
|
||||
if (!authCookie) {
|
||||
throw new Error("No auth cookie found when trying to set availability");
|
||||
}
|
||||
|
||||
let lastError: Error | null = null;
|
||||
|
||||
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
||||
if (attempt > 0) {
|
||||
// Wait before retry
|
||||
await page.waitForTimeout(500);
|
||||
}
|
||||
|
||||
const response = await page.request.put(`${API_URL}/api/admin/availability`, {
|
||||
headers: {
|
||||
Cookie: `auth_token=${authCookie.value}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
data: {
|
||||
date: dateStr,
|
||||
slots: [{ start_time: "09:00:00", end_time: "12:00:00" }],
|
||||
},
|
||||
});
|
||||
|
||||
if (response.ok()) {
|
||||
return; // Success
|
||||
}
|
||||
|
||||
const body = await response.text();
|
||||
lastError = new Error(`Failed to set availability: ${response.status()} - ${body}`);
|
||||
|
||||
// Only retry on 500 errors
|
||||
if (response.status() !== 500) {
|
||||
throw lastError;
|
||||
}
|
||||
}
|
||||
|
||||
throw lastError;
|
||||
}
|
||||
|
||||
test.describe("Booking Page - Regular User Access", () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await clearAuth(page);
|
||||
await loginUser(page, REGULAR_USER.email, REGULAR_USER.password);
|
||||
});
|
||||
|
||||
test("regular user can access booking page", async ({ page }) => {
|
||||
await page.goto("/booking");
|
||||
|
||||
await expect(page).toHaveURL("/booking");
|
||||
await expect(page.getByRole("heading", { name: "Book an Appointment" })).toBeVisible();
|
||||
});
|
||||
|
||||
test("regular user sees Book link in navigation", async ({ page }) => {
|
||||
await page.goto("/");
|
||||
|
||||
await expect(page.getByRole("link", { name: "Book" })).toBeVisible();
|
||||
});
|
||||
|
||||
test("booking page shows date selection", async ({ page }) => {
|
||||
await page.goto("/booking");
|
||||
|
||||
await expect(page.getByRole("heading", { name: "Select a Date" })).toBeVisible();
|
||||
// Should see multiple date buttons
|
||||
const dateButtons = page
|
||||
.locator("button")
|
||||
.filter({ hasText: /^(Mon|Tue|Wed|Thu|Fri|Sat|Sun)/ });
|
||||
await expect(dateButtons.first()).toBeVisible();
|
||||
});
|
||||
|
||||
test("selecting date shows slots section", async ({ page }) => {
|
||||
// First set up availability for tomorrow so we have an enabled date
|
||||
await clearAuth(page);
|
||||
await loginUser(page, ADMIN_USER.email, ADMIN_USER.password);
|
||||
await setAvailability(page, getTomorrowDateStr());
|
||||
await clearAuth(page);
|
||||
await loginUser(page, REGULAR_USER.email, REGULAR_USER.password);
|
||||
|
||||
await page.goto("/booking");
|
||||
|
||||
// Wait for availability check to complete
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
// Find an enabled date button (one with availability)
|
||||
const dateButtons = page
|
||||
.locator("button")
|
||||
.filter({ hasText: /^(Mon|Tue|Wed|Thu|Fri|Sat|Sun)/ });
|
||||
let enabledButton = null;
|
||||
const buttonCount = await dateButtons.count();
|
||||
for (let i = 0; i < buttonCount; i++) {
|
||||
const button = dateButtons.nth(i);
|
||||
const isDisabled = await button.isDisabled().catch(() => true);
|
||||
if (!isDisabled) {
|
||||
enabledButton = button;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Should have at least one enabled date (tomorrow)
|
||||
expect(enabledButton).not.toBeNull();
|
||||
await enabledButton!.click();
|
||||
|
||||
// Should show Available Slots section (use heading to be specific)
|
||||
await expect(page.getByRole("heading", { name: /Available Slots for/ })).toBeVisible();
|
||||
});
|
||||
|
||||
test("shows no slots or message when no availability", async ({ page }) => {
|
||||
await page.goto("/booking");
|
||||
|
||||
// Wait for date buttons to load and availability check to complete
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
// Find an enabled date button (one that has availability or is still loading)
|
||||
// If all dates are disabled, we can't test clicking, so verify disabled state
|
||||
const dateButtons = page
|
||||
.locator("button")
|
||||
.filter({ hasText: /^(Mon|Tue|Wed|Thu|Fri|Sat|Sun)/ });
|
||||
const enabledButtons = dateButtons.filter({ hasNotText: /disabled/ });
|
||||
const enabledCount = await enabledButtons.count();
|
||||
|
||||
if (enabledCount > 0) {
|
||||
// Click the first enabled date button
|
||||
await enabledButtons.first().click();
|
||||
|
||||
// Wait for the section to appear
|
||||
await expect(page.getByRole("heading", { name: /Available Slots for/ })).toBeVisible();
|
||||
|
||||
// Should either show no slots message OR show no slot buttons
|
||||
// Wait a moment for API to return
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
// If no availability is set, we'll see the "No available slots" message
|
||||
const noSlotsMessage = page.getByText("No available slots for this date");
|
||||
const isNoSlotsVisible = await noSlotsMessage.isVisible().catch(() => false);
|
||||
|
||||
if (!isNoSlotsVisible) {
|
||||
// There might be some slots from shared state - just verify the section loads
|
||||
await expect(page.getByRole("heading", { name: /Available Slots for/ })).toBeVisible();
|
||||
}
|
||||
} else {
|
||||
// All dates are disabled - verify that disabled dates are shown
|
||||
const disabledButtons = dateButtons.filter({ hasText: /disabled/ });
|
||||
await expect(disabledButtons.first()).toBeDisabled();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("Booking Page - With Availability", () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await clearAuth(page);
|
||||
// Login as admin to set availability
|
||||
await loginUser(page, ADMIN_USER.email, ADMIN_USER.password);
|
||||
await setAvailability(page, getTomorrowDateStr());
|
||||
await clearAuth(page);
|
||||
// Login as regular user
|
||||
await loginUser(page, REGULAR_USER.email, REGULAR_USER.password);
|
||||
});
|
||||
|
||||
test("shows available slots when availability is set", async ({ page }) => {
|
||||
await page.goto("/booking");
|
||||
|
||||
// Get tomorrow's display name to click the correct button
|
||||
const tomorrow = new Date();
|
||||
tomorrow.setDate(tomorrow.getDate() + 1);
|
||||
const weekday = tomorrow.toLocaleDateString("en-US", { weekday: "short" });
|
||||
|
||||
// Click tomorrow's date using the weekday name
|
||||
const dateButton = page
|
||||
.locator("button")
|
||||
.filter({ hasText: new RegExp(`^${weekday}`) })
|
||||
.first();
|
||||
await dateButton.click();
|
||||
|
||||
// Wait for "Available Slots" section to appear
|
||||
await expect(page.getByRole("heading", { name: /Available Slots for/ })).toBeVisible();
|
||||
|
||||
// Wait for loading to finish (no "Loading slots..." text)
|
||||
await expect(page.getByText("Loading slots...")).not.toBeVisible({ timeout: 10000 });
|
||||
|
||||
// Should see some slot buttons (look for any button with time-like pattern)
|
||||
// The format might be "09:00" or "9:00 AM" depending on locale
|
||||
const slotButtons = page.locator("button").filter({ hasText: /^\d{1,2}:\d{2}/ });
|
||||
await expect(slotButtons.first()).toBeVisible({ timeout: 10000 });
|
||||
});
|
||||
|
||||
test("clicking slot shows confirmation form", async ({ page }) => {
|
||||
await page.goto("/booking");
|
||||
|
||||
// Get tomorrow's display name
|
||||
const tomorrow = new Date();
|
||||
tomorrow.setDate(tomorrow.getDate() + 1);
|
||||
const weekday = tomorrow.toLocaleDateString("en-US", { weekday: "short" });
|
||||
|
||||
// Click tomorrow's date
|
||||
const dateButton = page
|
||||
.locator("button")
|
||||
.filter({ hasText: new RegExp(`^${weekday}`) })
|
||||
.first();
|
||||
await dateButton.click();
|
||||
|
||||
// Wait for any slot to appear
|
||||
await expect(page.getByText("Loading slots...")).not.toBeVisible({ timeout: 10000 });
|
||||
const slotButtons = page.locator("button").filter({ hasText: /^\d{1,2}:\d{2}/ });
|
||||
await expect(slotButtons.first()).toBeVisible({ timeout: 10000 });
|
||||
|
||||
// Click first slot
|
||||
await slotButtons.first().click();
|
||||
|
||||
// Should show confirmation form
|
||||
await expect(page.getByText("Confirm Booking")).toBeVisible();
|
||||
await expect(page.getByRole("button", { name: "Book Appointment" })).toBeVisible();
|
||||
});
|
||||
|
||||
test("can book an appointment with note", async ({ page }) => {
|
||||
await page.goto("/booking");
|
||||
|
||||
// Get tomorrow's display name
|
||||
const tomorrow = new Date();
|
||||
tomorrow.setDate(tomorrow.getDate() + 1);
|
||||
const weekday = tomorrow.toLocaleDateString("en-US", { weekday: "short" });
|
||||
|
||||
// Click tomorrow's date
|
||||
const dateButton = page
|
||||
.locator("button")
|
||||
.filter({ hasText: new RegExp(`^${weekday}`) })
|
||||
.first();
|
||||
await dateButton.click();
|
||||
|
||||
// Wait for slots to load
|
||||
await expect(page.getByText("Loading slots...")).not.toBeVisible({ timeout: 10000 });
|
||||
const slotButtons = page.locator("button").filter({ hasText: /^\d{1,2}:\d{2}/ });
|
||||
await expect(slotButtons.first()).toBeVisible({ timeout: 10000 });
|
||||
|
||||
// Click second slot (to avoid booking same slot as other tests)
|
||||
await slotButtons.nth(1).click();
|
||||
|
||||
// Add a note
|
||||
await page.fill("textarea", "Test booking note");
|
||||
|
||||
// Book
|
||||
await page.getByRole("button", { name: "Book Appointment" }).click();
|
||||
|
||||
// Should show success message
|
||||
await expect(page.getByText(/Appointment booked/)).toBeVisible();
|
||||
});
|
||||
|
||||
test("booked slot disappears from available slots", async ({ page }) => {
|
||||
await page.goto("/booking");
|
||||
|
||||
// Get tomorrow's display name
|
||||
const tomorrow = new Date();
|
||||
tomorrow.setDate(tomorrow.getDate() + 1);
|
||||
const weekday = tomorrow.toLocaleDateString("en-US", { weekday: "short" });
|
||||
|
||||
// Click tomorrow's date
|
||||
const dateButton = page
|
||||
.locator("button")
|
||||
.filter({ hasText: new RegExp(`^${weekday}`) })
|
||||
.first();
|
||||
await dateButton.click();
|
||||
|
||||
// Wait for slots to load
|
||||
await expect(page.getByText("Loading slots...")).not.toBeVisible({ timeout: 10000 });
|
||||
const slotButtons = page.locator("button").filter({ hasText: /^\d{1,2}:\d{2}/ });
|
||||
await expect(slotButtons.first()).toBeVisible({ timeout: 10000 });
|
||||
|
||||
// Count initial slots
|
||||
const initialCount = await slotButtons.count();
|
||||
|
||||
// Click any slot (3rd to avoid conflicts)
|
||||
const slotToBook = slotButtons.nth(2);
|
||||
const _slotText = await slotToBook.textContent();
|
||||
await slotToBook.click();
|
||||
|
||||
// Book it
|
||||
await page.getByRole("button", { name: "Book Appointment" }).click();
|
||||
|
||||
// Wait for booking form to disappear (indicates booking completed)
|
||||
await expect(page.getByRole("button", { name: "Book Appointment" })).not.toBeVisible({
|
||||
timeout: 10000,
|
||||
});
|
||||
|
||||
// Wait for success message
|
||||
await expect(page.getByText(/Appointment booked/)).toBeVisible();
|
||||
|
||||
// Should have one less slot now
|
||||
const newCount = await slotButtons.count();
|
||||
expect(newCount).toBe(initialCount - 1);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("Booking Page - Access Control", () => {
|
||||
test("admin cannot access booking page", async ({ page }) => {
|
||||
await clearAuth(page);
|
||||
await loginUser(page, ADMIN_USER.email, ADMIN_USER.password);
|
||||
|
||||
await page.goto("/booking");
|
||||
|
||||
// Should be redirected away (to audit or home)
|
||||
await expect(page).not.toHaveURL("/booking");
|
||||
});
|
||||
|
||||
test("admin does not see Book link", async ({ page }) => {
|
||||
await clearAuth(page);
|
||||
await loginUser(page, ADMIN_USER.email, ADMIN_USER.password);
|
||||
|
||||
await page.goto("/audit");
|
||||
|
||||
await expect(page.getByRole("link", { name: "Book" })).not.toBeVisible();
|
||||
});
|
||||
|
||||
test("unauthenticated user redirected to login", async ({ page }) => {
|
||||
await clearAuth(page);
|
||||
|
||||
await page.goto("/booking");
|
||||
|
||||
await expect(page).toHaveURL("/login");
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("Booking API", () => {
|
||||
test("regular user can book via API", async ({ page, request }) => {
|
||||
await clearAuth(page);
|
||||
// Set up availability as admin
|
||||
await loginUser(page, ADMIN_USER.email, ADMIN_USER.password);
|
||||
const dateStr = getTomorrowDateStr();
|
||||
await setAvailability(page, dateStr);
|
||||
await clearAuth(page);
|
||||
|
||||
// Login as regular user
|
||||
await loginUser(page, REGULAR_USER.email, REGULAR_USER.password);
|
||||
|
||||
const cookies = await page.context().cookies();
|
||||
const authCookie = cookies.find((c) => c.name === "auth_token");
|
||||
|
||||
if (authCookie) {
|
||||
// Use 11:45 to avoid conflicts with other tests using 10:00
|
||||
const response = await request.post(`${API_URL}/api/booking`, {
|
||||
headers: {
|
||||
Cookie: `auth_token=${authCookie.value}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
data: {
|
||||
slot_start: `${dateStr}T11:45:00Z`,
|
||||
note: "API test booking",
|
||||
},
|
||||
});
|
||||
|
||||
expect(response.status()).toBe(200);
|
||||
const data = await response.json();
|
||||
expect(data.note).toBe("API test booking");
|
||||
expect(data.status).toBe("booked");
|
||||
}
|
||||
});
|
||||
|
||||
test("admin cannot book via API", async ({ page, request }) => {
|
||||
await clearAuth(page);
|
||||
await loginUser(page, ADMIN_USER.email, ADMIN_USER.password);
|
||||
const dateStr = getTomorrowDateStr();
|
||||
await setAvailability(page, dateStr);
|
||||
|
||||
const cookies = await page.context().cookies();
|
||||
const authCookie = cookies.find((c) => c.name === "auth_token");
|
||||
|
||||
if (authCookie) {
|
||||
const response = await request.post(`${API_URL}/api/booking`, {
|
||||
headers: {
|
||||
Cookie: `auth_token=${authCookie.value}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
data: {
|
||||
slot_start: `${dateStr}T10:15:00Z`,
|
||||
},
|
||||
});
|
||||
|
||||
expect(response.status()).toBe(403);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
@ -61,8 +61,8 @@ test.describe("Price History - E2E", () => {
|
|||
await clearAuth(page);
|
||||
await loginUser(page, ADMIN_USER.email, ADMIN_USER.password);
|
||||
|
||||
// Admin should be on audit page by default
|
||||
await expect(page).toHaveURL("/audit");
|
||||
// Admin should be on admin trades page by default
|
||||
await expect(page).toHaveURL("/admin/trades");
|
||||
|
||||
// Prices nav link should be visible
|
||||
await expect(page.getByRole("link", { name: "Prices" })).toBeVisible();
|
||||
|
|
|
|||
|
|
@ -75,8 +75,8 @@ test.describe("Profile - Regular User Access", () => {
|
|||
await loginUser(page, REGULAR_USER.email, REGULAR_USER.password);
|
||||
});
|
||||
|
||||
test("can navigate to profile page from counter", async ({ page }) => {
|
||||
await page.goto("/");
|
||||
test("can navigate to profile page from exchange", async ({ page }) => {
|
||||
await page.goto("/exchange");
|
||||
|
||||
// Should see My Profile link
|
||||
await expect(page.getByText("My Profile")).toBeVisible();
|
||||
|
|
@ -86,8 +86,8 @@ test.describe("Profile - Regular User Access", () => {
|
|||
await expect(page).toHaveURL("/profile");
|
||||
});
|
||||
|
||||
test("can navigate to profile page from sum", async ({ page }) => {
|
||||
await page.goto("/sum");
|
||||
test("can navigate to profile page from trades", async ({ page }) => {
|
||||
await page.goto("/trades");
|
||||
|
||||
// Should see My Profile link
|
||||
await expect(page.getByText("My Profile")).toBeVisible();
|
||||
|
|
@ -126,12 +126,12 @@ test.describe("Profile - Regular User Access", () => {
|
|||
await expect(loginEmailInput).toBeDisabled();
|
||||
});
|
||||
|
||||
test("navigation shows Counter, Sum, and My Profile", async ({ page }) => {
|
||||
test("navigation shows Exchange, My Trades, and My Profile", async ({ page }) => {
|
||||
await page.goto("/profile");
|
||||
|
||||
// Should see all nav items (Counter and Sum as links)
|
||||
await expect(page.locator('a[href="/"]')).toBeVisible();
|
||||
await expect(page.locator('a[href="/sum"]')).toBeVisible();
|
||||
// Should see all nav items (Exchange and My Trades as links)
|
||||
await expect(page.locator('a[href="/exchange"]')).toBeVisible();
|
||||
await expect(page.locator('a[href="/trades"]')).toBeVisible();
|
||||
// My Profile is the page title (h1) since we're on this page
|
||||
await expect(page.getByRole("heading", { name: "My Profile" })).toBeVisible();
|
||||
});
|
||||
|
|
@ -314,20 +314,20 @@ test.describe("Profile - Admin User Access", () => {
|
|||
});
|
||||
|
||||
test("admin does not see My Profile link", async ({ page }) => {
|
||||
await page.goto("/audit");
|
||||
await page.goto("/admin/trades");
|
||||
|
||||
// Should be on audit page
|
||||
await expect(page).toHaveURL("/audit");
|
||||
// Should be on admin trades page
|
||||
await expect(page).toHaveURL("/admin/trades");
|
||||
|
||||
// Should NOT see My Profile link
|
||||
await expect(page.locator('a[href="/profile"]')).toHaveCount(0);
|
||||
});
|
||||
|
||||
test("admin cannot access profile page - redirected to audit", async ({ page }) => {
|
||||
test("admin cannot access profile page - redirected to admin trades", async ({ page }) => {
|
||||
await page.goto("/profile");
|
||||
|
||||
// Should be redirected to audit
|
||||
await expect(page).toHaveURL("/audit");
|
||||
// Should be redirected to admin trades
|
||||
await expect(page).toHaveURL("/admin/trades");
|
||||
});
|
||||
|
||||
test("admin API call to profile returns 403", async ({ page, request }) => {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue