import { test, expect, Page, APIRequestContext } from "@playwright/test"; // Helper to generate unique email for each test function uniqueEmail(): string { return `test-${Date.now()}-${Math.random().toString(36).substring(7)}@example.com`; } // Helper to clear auth cookies async function clearAuth(page: Page) { await page.context().clearCookies(); } // Admin credentials from seed data const ADMIN_EMAIL = "admin@example.com"; const ADMIN_PASSWORD = "admin123"; // Helper to create an invite via the API const API_BASE = process.env.NEXT_PUBLIC_API_URL || "http://localhost:8000"; async function createInvite(request: APIRequestContext): Promise { // Login as admin const loginResp = await request.post(`${API_BASE}/api/auth/login`, { data: { email: ADMIN_EMAIL, password: ADMIN_PASSWORD }, }); const cookies = loginResp.headers()["set-cookie"]; // Get admin user ID (we'll use admin as godfather for simplicity) const meResp = await request.get(`${API_BASE}/api/auth/me`, { headers: { Cookie: cookies }, }); const admin = await meResp.json(); // Create invite const inviteResp = await request.post(`${API_BASE}/api/admin/invites`, { data: { godfather_id: admin.id }, headers: { Cookie: cookies }, }); const invite = await inviteResp.json(); return invite.identifier; } test.describe("Authentication Flow", () => { test.beforeEach(async ({ page }) => { await clearAuth(page); }); test("redirects to login when not authenticated and auth pages have correct UI", async ({ page, }) => { // Test redirect await page.goto("/"); await expect(page).toHaveURL("/login"); // Test login page UI await page.goto("/login"); await expect(page.locator("h1")).toHaveText("Welcome back"); await expect(page.locator('input[type="email"]')).toBeVisible(); await expect(page.locator('input[type="password"]')).toBeVisible(); await expect(page.locator('button[type="submit"]')).toHaveText("Sign in"); await expect(page.locator('a[href="/signup"]')).toBeVisible(); // Test navigation to signup await page.click('a[href="/signup"]'); await expect(page).toHaveURL("/signup"); // Test signup page UI await expect(page.locator("h1")).toHaveText("Join with Invite"); await expect(page.locator("input#inviteCode")).toBeVisible(); await expect(page.locator('button[type="submit"]')).toHaveText("Continue"); await expect(page.locator('a[href="/login"]')).toBeVisible(); // Test navigation back to login await page.click('a[href="/login"]'); await expect(page).toHaveURL("/login"); }); }); test.describe("Signup with Invite", () => { test.beforeEach(async ({ page }) => { await clearAuth(page); }); test("can create account with valid invite via form and direct URL, and logged-in users are redirected", async ({ page, request, }) => { // Test signup via form const email1 = uniqueEmail(); const inviteCode1 = await createInvite(request); await page.goto("/signup"); await page.fill("input#inviteCode", inviteCode1); await page.click('button[type="submit"]'); await expect(page.locator("h1")).toHaveText("Create account"); await page.fill("input#email", email1); await page.fill("input#password", "password123"); await page.fill("input#confirmPassword", "password123"); await page.click('button[type="submit"]'); await expect(page).toHaveURL("/exchange"); await expect(page.getByRole("heading", { name: "Exchange Bitcoin" })).toBeVisible(); // Test logged-in user visiting invite URL - should redirect to exchange const anotherInvite = await createInvite(request); await page.goto(`/signup/${anotherInvite}`); await expect(page).toHaveURL("/exchange"); // Test logged-in user visiting signup page - should redirect to exchange await page.goto("/signup"); await expect(page).toHaveURL("/exchange"); // Test signup via direct URL (new session) await clearAuth(page); const email2 = uniqueEmail(); const inviteCode2 = await createInvite(request); await page.goto(`/signup/${inviteCode2}`); await page.waitForURL(/\/signup\?code=/); await expect(page.locator("h1")).toHaveText("Create account"); await page.fill("input#email", email2); await page.fill("input#password", "password123"); await page.fill("input#confirmPassword", "password123"); await page.click('button[type="submit"]'); await expect(page).toHaveURL("/exchange"); }); test("shows errors for invalid invite code and password validation", async ({ page, request, }) => { // Test invalid invite code await page.goto("/signup"); await page.fill("input#inviteCode", "fake-code-99"); await page.click('button[type="submit"]'); await expect(page.getByText(/not found/i)).toBeVisible(); // Test password validation with valid invite const inviteCode = await createInvite(request); await page.goto("/signup"); await page.fill("input#inviteCode", inviteCode); await page.click('button[type="submit"]'); await expect(page.locator("h1")).toHaveText("Create account"); // Test password mismatch await page.fill("input#email", uniqueEmail()); await page.fill("input#password", "password123"); await page.fill("input#confirmPassword", "differentpassword"); await page.click('button[type="submit"]'); await expect(page.getByText("Passwords do not match")).toBeVisible(); // Test short password await page.fill("input#email", uniqueEmail()); await page.fill("input#password", "short"); await page.fill("input#confirmPassword", "short"); await page.click('button[type="submit"]'); await expect(page.getByText("Password must be at least 6 characters")).toBeVisible(); }); }); test.describe("Login", () => { let testEmail: string; const testPassword = "testpassword123"; test.beforeAll(async ({ request }) => { // Create a test user with invite testEmail = uniqueEmail(); const inviteCode = await createInvite(request); // Register the test user via backend API await request.post(`${API_BASE}/api/auth/register`, { data: { email: testEmail, password: testPassword, invite_identifier: inviteCode, }, }); }); test.beforeEach(async ({ page }) => { await clearAuth(page); }); test("can login with valid credentials and shows loading state", async ({ page }) => { // Test loading state await page.goto("/login"); await page.fill('input[type="email"]', testEmail); await page.fill('input[type="password"]', testPassword); const submitPromise = page.click('button[type="submit"]'); await expect(page.locator('button[type="submit"]')).toHaveText("Signing in..."); await submitPromise; // Regular user redirects to exchange await expect(page).toHaveURL("/exchange"); await expect(page.getByRole("heading", { name: "Exchange Bitcoin" })).toBeVisible(); }); test("shows error for wrong password and non-existent user", async ({ page }) => { // Test wrong password await page.goto("/login"); await page.fill('input[type="email"]', testEmail); await page.fill('input[type="password"]', "wrongpassword"); await page.click('button[type="submit"]'); await expect(page.getByText("Incorrect email or password")).toBeVisible(); // Test non-existent user await page.goto("/login"); await page.fill('input[type="email"]', "nonexistent@example.com"); await page.fill('input[type="password"]', "password123"); await page.click('button[type="submit"]'); await expect(page.getByText("Incorrect email or password")).toBeVisible(); }); }); test.describe("Logout", () => { test("can logout and cannot access protected pages after logout", async ({ page, request }) => { const email = uniqueEmail(); const inviteCode = await createInvite(request); // Sign up await page.goto("/signup"); await page.fill("input#inviteCode", inviteCode); await page.click('button[type="submit"]'); await expect(page.locator("h1")).toHaveText("Create account"); await page.fill("input#email", email); await page.fill("input#password", "password123"); await page.fill("input#confirmPassword", "password123"); await page.click('button[type="submit"]'); await expect(page).toHaveURL("/exchange"); // Click logout await page.click("text=Sign out"); await expect(page).toHaveURL("/login"); // Try to access exchange (protected page) await page.goto("/exchange"); await expect(page).toHaveURL("/login"); }); }); test.describe("Session Persistence", () => { test("session persists after page reload and cookies are managed correctly", async ({ page, request, }) => { const email = uniqueEmail(); const inviteCode = await createInvite(request); // Sign up await page.goto("/signup"); await page.fill("input#inviteCode", inviteCode); await page.click('button[type="submit"]'); await expect(page.locator("h1")).toHaveText("Create account"); await page.fill("input#email", email); await page.fill("input#password", "password123"); await page.fill("input#confirmPassword", "password123"); await page.click('button[type="submit"]'); await expect(page).toHaveURL("/exchange"); await expect(page.getByRole("heading", { name: "Exchange Bitcoin" })).toBeVisible(); // Check cookies are set after signup let cookies = await page.context().cookies(); let authCookie = cookies.find((c) => c.name === "auth_token"); expect(authCookie).toBeTruthy(); expect(authCookie!.httpOnly).toBe(true); // Reload page - session should persist await page.reload(); await expect(page).toHaveURL("/exchange"); await expect(page.getByRole("heading", { name: "Exchange Bitcoin" })).toBeVisible(); // Logout and verify cookie is cleared await page.click("text=Sign out"); await expect(page).toHaveURL("/login"); cookies = await page.context().cookies(); authCookie = cookies.find((c) => c.name === "auth_token"); expect(!authCookie || authCookie.value === "").toBe(true); }); });