nextjs-rewrite (#5)
Reviewed-on: #5 Co-authored-by: David Gwilliam <dhgwilliam@gmail.com> Co-committed-by: David Gwilliam <dhgwilliam@gmail.com>
This commit was merged in pull request #5.
This commit is contained in:
@@ -0,0 +1,209 @@
|
||||
/**
|
||||
* 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 { PrismaClient } from '@prisma/client';
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
function getTestCredentials() {
|
||||
const timestamp = Date.now();
|
||||
return {
|
||||
email: `logout-test-${timestamp}@example.com`,
|
||||
password: 'TestPassword123!',
|
||||
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('http://localhost:3000/api/auth/sign-up/email', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Origin': 'http://localhost:3000',
|
||||
'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('http://localhost:3000/auth/login');
|
||||
|
||||
// Wait for JavaScript to be ready
|
||||
await page.waitForLoadState('networkidle');
|
||||
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('networkidle');
|
||||
|
||||
// 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('http://localhost:3000/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('networkidle');
|
||||
|
||||
// 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('http://localhost:3000/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('networkidle');
|
||||
|
||||
// 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('http://localhost:3000/admin');
|
||||
|
||||
// Should redirect to login
|
||||
await expect(page).toHaveURL(/.*auth\/login.*/);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user