210 lines
7.1 KiB
TypeScript
210 lines
7.1 KiB
TypeScript
/**
|
|
* Epic 1: Authentication & User Management
|
|
* Acceptance Test: User Logout
|
|
*
|
|
* User Story: As a logged-in user, I want to log out so that I can secure my account
|
|
*
|
|
* Acceptance Criteria:
|
|
* - Logout button in navigation
|
|
* - Session cleared on logout
|
|
* - Redirect to home page
|
|
*/
|
|
|
|
import { test, expect } from '@playwright/test';
|
|
import { prisma } from '@/lib/prisma';
|
|
const BASE_URL = process.env.CI ? 'https://euchre-ci.notsosm.art' : 'http://localhost:3000';
|
|
|
|
|
|
function getTestCredentials() {
|
|
const timestamp = Date.now();
|
|
return {
|
|
email: `logout-test-${timestamp}@example.com`,
|
|
password: 'TestPassword1234!',
|
|
name: `Logout Test User ${timestamp}`
|
|
};
|
|
}
|
|
|
|
test.describe.serial('Epic 1: User Logout', () => {
|
|
let testEmail: string;
|
|
let testPassword: string;
|
|
let testName: string;
|
|
|
|
test.beforeAll(async () => {
|
|
const credentials = getTestCredentials();
|
|
testEmail = credentials.email;
|
|
testPassword = credentials.password;
|
|
testName = credentials.name;
|
|
|
|
// Create test user via API with proper origin header
|
|
const response = await fetch(`${BASE_URL}/api/auth/sign-up/email`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Origin': BASE_URL,
|
|
'X-Requested-With': 'XMLHttpRequest'
|
|
},
|
|
body: JSON.stringify({
|
|
email: testEmail,
|
|
password: testPassword,
|
|
name: testName
|
|
})
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const errorText = await response.text();
|
|
console.error('Failed to create user:', response.status, errorText);
|
|
throw new Error(`Failed to create user: ${response.status}`);
|
|
}
|
|
|
|
const data = await response.json();
|
|
console.log('User created via API:', data.user?.email);
|
|
|
|
// Wait for the database hook to complete (creating Player record)
|
|
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
|
|
// Verify the user was created in the database
|
|
const user = await prisma.user.findUnique({
|
|
where: { email: testEmail }
|
|
});
|
|
|
|
if (!user) {
|
|
throw new Error(`User ${testEmail} was not created in database`);
|
|
}
|
|
|
|
if (!user.playerId) {
|
|
throw new Error(`User ${testEmail} does not have a playerId (database hook may not have run)`);
|
|
}
|
|
|
|
console.log(`User ${testEmail} created with playerId: ${user.playerId}`);
|
|
});
|
|
|
|
test.afterAll(async () => {
|
|
try {
|
|
const user = await prisma.user.findUnique({
|
|
where: { email: testEmail }
|
|
});
|
|
if (user) {
|
|
await prisma.user.delete({ where: { id: user.id } });
|
|
}
|
|
} catch (error) {
|
|
console.error('Cleanup error:', error);
|
|
}
|
|
});
|
|
|
|
test('Logout button appears in navigation when logged in', async ({ page }) => {
|
|
// Login first
|
|
await page.goto('/auth/login');
|
|
|
|
// Wait for page to load
|
|
await page.waitForLoadState('domcontentloaded');
|
|
await page.waitForSelector('form');
|
|
await page.waitForSelector('button[type="submit"]:not([disabled])');
|
|
|
|
await page.fill('input[name="email"]', testEmail);
|
|
await page.fill('input[name="password"]', testPassword);
|
|
|
|
// Wait for redirect to admin or player profile after clicking submit
|
|
const [response] = await Promise.all([
|
|
page.waitForResponse(response =>
|
|
response.url().includes('/api/auth/sign-in/email') && response.status() === 200,
|
|
{ timeout: 10000 }
|
|
),
|
|
page.click('button[type="submit"]')
|
|
]);
|
|
|
|
console.log('Login response:', response.status());
|
|
|
|
// Wait for redirect
|
|
await page.waitForURL(/\/(admin|players)/, { timeout: 10000 });
|
|
|
|
console.log('After login URL:', page.url());
|
|
|
|
// Check cookies
|
|
const cookies = await page.context().cookies();
|
|
console.log('Cookies after login:', cookies.map(c => `${c.name}=${c.value.substring(0, 20)}...`).join(', '));
|
|
|
|
// Check if better-auth.session_token exists
|
|
const sessionCookie = cookies.find(c => c.name.includes('session'));
|
|
console.log('Session cookie:', sessionCookie ? `${sessionCookie.name}=${sessionCookie.value.substring(0, 20)}...` : 'NOT FOUND');
|
|
|
|
// Navigate to home page to check navigation (session should persist)
|
|
// Use reload to ensure session is read from cookies
|
|
await page.reload();
|
|
await page.waitForLoadState('domcontentloaded');
|
|
|
|
// Wait a moment for the navigation component to update
|
|
await page.waitForTimeout(1000);
|
|
|
|
// Debug: Check what's on the page
|
|
const pageContent = await page.content();
|
|
console.log('Page content contains Sign out:', pageContent.includes('Sign out'));
|
|
console.log('Page content contains Sign in:', pageContent.includes('Sign in'));
|
|
console.log('Page content length:', pageContent.length);
|
|
console.log('Current URL:', page.url());
|
|
console.log('Page HTML snippet:', pageContent.substring(0, 500));
|
|
|
|
// Wait for the Navigation component to load and render
|
|
await page.waitForSelector('nav', { timeout: 5000 });
|
|
|
|
// Wait for the session to be loaded (check for logout button directly)
|
|
await page.waitForSelector('text=Sign out', { timeout: 10000 });
|
|
|
|
// Check for logout button
|
|
const logoutButton = await page.locator('text=Sign out').count();
|
|
console.log('Logout button count:', logoutButton);
|
|
expect(logoutButton).toBeGreaterThan(0);
|
|
});
|
|
|
|
test('Logout clears session and redirects to home', async ({ page }) => {
|
|
// Login first
|
|
await page.goto('/auth/login');
|
|
await page.fill('input[name="email"]', testEmail);
|
|
await page.fill('input[name="password"]', testPassword);
|
|
await page.click('button[type="submit"]');
|
|
|
|
// Wait for redirect to admin or player profile (indicates successful login)
|
|
await page.waitForURL(/\/(admin|players)/, { timeout: 10000 });
|
|
|
|
// Navigate to home using reload to ensure session is loaded
|
|
await page.reload();
|
|
await page.waitForLoadState('domcontentloaded');
|
|
|
|
// Wait for logout button to appear
|
|
await page.waitForSelector('text=Sign out', { timeout: 10000 });
|
|
|
|
// Click logout
|
|
await page.click('text=Sign out');
|
|
|
|
// Wait for redirect to login page
|
|
await page.waitForURL('**/auth/login**', { timeout: 10000 });
|
|
});
|
|
|
|
test('After logout, protected pages redirect to login', async ({ page }) => {
|
|
// Login first
|
|
await page.goto('/auth/login');
|
|
await page.fill('input[name="email"]', testEmail);
|
|
await page.fill('input[name="password"]', testPassword);
|
|
await page.click('button[type="submit"]');
|
|
|
|
// Wait for redirect to admin or player profile (indicates successful login)
|
|
await page.waitForURL(/\/(admin|players)/, { timeout: 10000 });
|
|
|
|
// Navigate to home using reload to ensure session is loaded
|
|
await page.reload();
|
|
await page.waitForLoadState('domcontentloaded');
|
|
|
|
// Wait for logout button to appear
|
|
await page.waitForSelector('text=Sign out', { timeout: 10000 });
|
|
|
|
// Logout
|
|
await page.click('text=Sign out');
|
|
await page.waitForURL('**/auth/login**', { timeout: 10000 });
|
|
|
|
// Try to access admin page
|
|
await page.goto('/admin');
|
|
|
|
// Should redirect to login
|
|
await expect(page).toHaveURL(/.*auth\/login.*/);
|
|
});
|
|
});
|