import { test, expect } from "@playwright/test"; import { ADMIN_EMAIL, ADMIN_PASSWORD, BASE, requireAuth, injectAuth, apiLogin } from "./helpers"; test.describe("Auth — public endpoints", () => { test("login page renders", async ({ page }) => { await page.goto("/login"); await expect(page).toHaveTitle(/.+/); const hasForm = await page.locator("form, input[type=password]").count(); expect(hasForm).toBeGreaterThan(0); }); test("protected routes reject unauthenticated requests", async ({ page }) => { const routes = ["/api/tree", "/api/leaderboard/weekly", "/api/notifications"]; for (const route of routes) { const res = await page.request.get(`${BASE}${route}`); expect(res.status(), `${route} should be 401`).toBe(401); } }); test("invalid credentials: bad captcha → 400, bad password → 401", async ({ page }) => { // Bad captcha answer returns 400 const r1 = await page.request.post(`${BASE}/api/auth/login`, { data: { username: "x", password: "x", challengeId: "fake", captchaAnswer: "wrong" }, }); expect([400, 429]).toContain(r1.status()); // Correct captcha, wrong password → 401 const auth = requireAuth(); const captchaRes = await page.request.get(`${BASE}/api/auth/captcha`); const { challengeId, answer } = await captchaRes.json(); const r2 = await page.request.post(`${BASE}/api/auth/login`, { data: { username: ADMIN_EMAIL, password: "definitely-wrong", challengeId, captchaAnswer: answer }, }); expect(r2.status()).toBe(401); }); }); test.describe("Auth — requires working CAPTCHA", () => { test("admin login via API succeeds and /me returns admin role", async ({ page }) => { const auth = requireAuth(); const res = await page.request.get(`${BASE}/api/auth/me`, { headers: { Authorization: `Bearer ${auth.accessToken}` }, }); expect(res.status()).toBe(200); const body = await res.json(); expect(body.user.role).toBe("admin"); }); test("logout invalidates refresh token", async ({ page }) => { requireAuth(); // Issue a fresh session for this test only (don't use the shared token) const captchaRes = await page.request.get(`${BASE}/api/auth/captcha`); const { challengeId, answer } = await captchaRes.json(); if (!answer) { test.skip(true, "CAPTCHA not solvable"); return; } const loginRes = await page.request.post(`${BASE}/api/auth/login`, { data: { username: ADMIN_EMAIL, password: ADMIN_PASSWORD, challengeId, captchaAnswer: answer }, }); const { accessToken, refreshToken } = await loginRes.json(); const logout = await page.request.post(`${BASE}/api/auth/logout`, { data: { refreshToken }, headers: { Authorization: `Bearer ${accessToken}` }, }); expect(logout.status()).toBe(200); const refresh = await page.request.post(`${BASE}/api/auth/refresh`, { data: { refreshToken }, }); expect(refresh.status()).toBe(401); }); test("cap page loads and stays off /login", async ({ page }) => { await injectAuth(page); await page.goto("/cap"); await page.waitForLoadState("networkidle", { timeout: 15_000 }); expect(page.url()).not.toContain("/login"); }); });