import { test, expect, Page } from "@playwright/test"; import { getBackendUrl } from "./helpers/backend-url"; /** * Permission-based E2E tests * * These tests verify that: * 1. Regular users can access exchange and trades pages * 2. Admin users can access admin pages (trades, invites, availability) * 3. Users are properly redirected based on their permissions * 4. API calls respect permission boundaries */ // Test credentials - must match what's seeded in the database via seed.py // These come from environment variables DEV_USER_EMAIL/PASSWORD and DEV_ADMIN_EMAIL/PASSWORD // Tests will fail fast if these are not set function getRequiredEnv(name: string): string { const value = process.env[name]; if (!value) { throw new Error( `Required environment variable ${name} is not set. Run 'source .env' or set it in your environment.` ); } return value; } const REGULAR_USER = { email: getRequiredEnv("DEV_USER_EMAIL"), password: getRequiredEnv("DEV_USER_PASSWORD"), }; const ADMIN_USER = { email: getRequiredEnv("DEV_ADMIN_EMAIL"), password: getRequiredEnv("DEV_ADMIN_PASSWORD"), }; // Helper to clear auth cookies async function clearAuth(page: Page) { await page.context().clearCookies(); } // Helper to login a user async function loginUser(page: Page, email: string, password: string) { await page.goto("/login"); await page.fill('input[type="email"]', email); await page.fill('input[type="password"]', password); await page.click('button[type="submit"]'); // Wait for navigation away from login page await page.waitForURL((url) => !url.pathname.includes("/login"), { timeout: 10000 }); } // Setup: Users are pre-seeded via seed.py before e2e tests run // The seed script creates: // - A regular user (DEV_USER_EMAIL/PASSWORD) with "regular" role // - An admin user (DEV_ADMIN_EMAIL/PASSWORD) with "admin" role test.beforeAll(async () => { // No need to create users - they are seeded by scripts/e2e.sh }); test.describe("Regular User Access", () => { test.beforeEach(async ({ context, page }) => { await context.addInitScript(() => { window.localStorage.setItem("arbret-locale", "en"); }); await clearAuth(page); await loginUser(page, REGULAR_USER.email, REGULAR_USER.password); }); test("can access exchange and trades pages with correct navigation", async ({ page }) => { // Test redirect from home await page.goto("/"); await expect(page).toHaveURL("/exchange"); // Test exchange page access await page.goto("/exchange"); await expect(page).toHaveURL("/exchange"); await expect(page.getByText("Exchange Bitcoin")).toBeVisible(); // Test trades page access await page.goto("/trades"); await expect(page).toHaveURL("/trades"); await expect(page.getByRole("heading", { name: "My Trades" })).toBeVisible(); // Test navigation shows exchange and trades, but not admin links await expect(page.locator('a[href="/exchange"]').first()).toBeVisible(); const adminTradesLinks = page.locator('a[href="/admin/trades"]'); await expect(adminTradesLinks).toHaveCount(0); }); }); test.describe("Admin User Access", () => { test.beforeEach(async ({ context, page }) => { await context.addInitScript(() => { window.localStorage.setItem("arbret-locale", "en"); }); await clearAuth(page); await loginUser(page, ADMIN_USER.email, ADMIN_USER.password); }); test("can access admin pages with correct navigation", async ({ page }) => { // Test redirect from home await page.goto("/"); await expect(page).toHaveURL("/admin/trades"); // Test admin trades page await page.goto("/admin/trades"); await expect(page).toHaveURL("/admin/trades"); await expect(page.getByRole("heading", { name: "Trades" })).toBeVisible(); // Test admin availability page await page.goto("/admin/availability"); await expect(page).toHaveURL("/admin/availability"); await expect(page.getByRole("heading", { name: "Availability" })).toBeVisible(); // Test navigation shows admin links but not regular user links await page.goto("/admin/trades"); await expect(page.locator('a[href="/admin/invites"]')).toBeVisible(); await expect(page.locator('a[href="/admin/availability"]')).toBeVisible(); await expect(page.locator('a[href="/admin/trades"]')).toHaveCount(0); // Current page, shown as text not link const exchangeLinks = page.locator('a[href="/exchange"]'); await expect(exchangeLinks).toHaveCount(0); }); }); test.describe("Unauthenticated Access", () => { test.beforeEach(async ({ context, page }) => { await context.addInitScript(() => { window.localStorage.setItem("arbret-locale", "en"); }); await clearAuth(page); }); test("all protected pages redirect to login", async ({ page }) => { // Test home page redirect await page.goto("/"); await expect(page).toHaveURL("/login"); // Test exchange page redirect await page.goto("/exchange"); await expect(page).toHaveURL("/login"); // Test admin page redirect await page.goto("/admin/trades"); await expect(page).toHaveURL("/login"); }); }); test.describe("Permission Boundary via API", () => { test("API calls respect permission boundaries", async ({ page, request }) => { // Test regular user cannot access admin API await clearAuth(page); await loginUser(page, REGULAR_USER.email, REGULAR_USER.password); let cookies = await page.context().cookies(); let authCookie = cookies.find((c) => c.name === "auth_token"); if (authCookie) { const response = await request.get(`${getBackendUrl()}/api/admin/trades/upcoming`, { headers: { Cookie: `auth_token=${authCookie.value}`, }, }); expect(response.status()).toBe(403); } // Test admin cannot access regular user API await clearAuth(page); await loginUser(page, ADMIN_USER.email, ADMIN_USER.password); cookies = await page.context().cookies(); authCookie = cookies.find((c) => c.name === "auth_token"); if (authCookie) { const response = await request.get(`${getBackendUrl()}/api/exchange/price`, { headers: { Cookie: `auth_token=${authCookie.value}`, }, }); expect(response.status()).toBe(403); } }); }); test.describe("Session and Logout", () => { test.beforeEach(async ({ context }) => { await context.addInitScript(() => { window.localStorage.setItem("arbret-locale", "en"); }); }); test("logout clears permissions and tampered cookies are rejected", async ({ page, context }) => { // Test logout clears permissions await clearAuth(page); await loginUser(page, REGULAR_USER.email, REGULAR_USER.password); await expect(page).toHaveURL("/exchange"); await page.click("text=Sign out"); await expect(page).toHaveURL("/login"); await page.goto("/exchange"); await expect(page).toHaveURL("/login"); // Test tampered cookie is rejected await context.addCookies([ { name: "auth_token", value: "fake-token-that-should-not-work", domain: "localhost", path: "/", }, ]); await page.goto("/exchange"); await expect(page).toHaveURL("/login"); }); });