test: add OAuth2 authentication test files (TDD RED phase)
This commit is contained in:
234
tests/e2e/auth-session-persistence.spec.ts
Normal file
234
tests/e2e/auth-session-persistence.spec.ts
Normal file
@@ -0,0 +1,234 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
/**
|
||||
* E2E Test: Session Persistence Across Browser Restarts
|
||||
*
|
||||
* User Story: US4 - Session Persistence
|
||||
* Task: T011
|
||||
*
|
||||
* **Purpose**: Verify that authenticated users remain logged in after page reload
|
||||
*
|
||||
* **Acceptance Criteria**:
|
||||
* - AC-1: After login, user reloads page and remains on /dashboard (not redirected to /login)
|
||||
* - AC-2: After reload, user name is still visible in navbar
|
||||
* - AC-3: After reload, user can still access protected pages
|
||||
*
|
||||
* **Technical Implementation**:
|
||||
* - Tests rely on Pocketbase authStore persistence via localStorage
|
||||
* - initAuth() composable function restores session on mount via auth plugin
|
||||
* - Auth state is synchronized via pb.authStore.onChange listener
|
||||
*
|
||||
* **Test Flow**:
|
||||
* 1. Navigate to /login
|
||||
* 2. Authenticate via OAuth (mock provider)
|
||||
* 3. Verify redirect to /dashboard
|
||||
* 4. Verify user name visible in navbar
|
||||
* 5. Reload page
|
||||
* 6. Verify still on /dashboard (no redirect to /login)
|
||||
* 7. Verify user name still visible in navbar
|
||||
*
|
||||
* **Notes**:
|
||||
* - Requires Pocketbase OAuth mock provider configured for testing
|
||||
* - May need to configure test-specific OAuth provider or use Pocketbase test mode
|
||||
* - Session persistence depends on valid auth token not being expired
|
||||
*/
|
||||
|
||||
test.describe('Session Persistence', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
// Clear any existing auth state before each test
|
||||
await page.context().clearCookies();
|
||||
await page.evaluate(() => localStorage.clear());
|
||||
});
|
||||
|
||||
test('should maintain authentication after page reload', async ({ page }) => {
|
||||
// Step 1: Navigate to login page
|
||||
await page.goto('/login');
|
||||
await expect(page).toHaveURL('/login');
|
||||
|
||||
// Step 2: Authenticate via OAuth
|
||||
// NOTE: This requires a mock OAuth provider configured in Pocketbase for testing
|
||||
// The actual implementation will depend on the test environment setup
|
||||
// For now, this is a placeholder that documents the expected flow
|
||||
|
||||
// TODO: Replace with actual OAuth flow once test provider is configured
|
||||
// Example mock implementation:
|
||||
// await page.click('[data-testid="oauth-google"]');
|
||||
// await page.waitForURL('/dashboard');
|
||||
|
||||
// Placeholder: Simulate successful OAuth by setting auth state directly
|
||||
await page.evaluate(() => {
|
||||
// Mock Pocketbase authStore for testing
|
||||
const mockAuthData = {
|
||||
token: 'mock-jwt-token',
|
||||
record: {
|
||||
id: 'test-user-id',
|
||||
email: 'test@example.com',
|
||||
name: 'Test User',
|
||||
emailVisibility: false,
|
||||
verified: true,
|
||||
avatar: '',
|
||||
created: new Date().toISOString(),
|
||||
updated: new Date().toISOString()
|
||||
}
|
||||
};
|
||||
localStorage.setItem('pocketbase_auth', JSON.stringify(mockAuthData));
|
||||
});
|
||||
|
||||
// Navigate to dashboard to trigger auth restoration
|
||||
await page.goto('/dashboard');
|
||||
await expect(page).toHaveURL('/dashboard');
|
||||
|
||||
// Step 3: Verify user is authenticated - check for user name in navbar
|
||||
// NOTE: Adjust selector based on actual navbar implementation
|
||||
const userNameElement = page.locator('[data-testid="user-name"]').or(
|
||||
page.locator('text=Test User')
|
||||
);
|
||||
await expect(userNameElement).toBeVisible({ timeout: 5000 });
|
||||
|
||||
// Step 4: Reload the page
|
||||
await page.reload();
|
||||
|
||||
// Step 5: Verify still on /dashboard (session persisted)
|
||||
await expect(page).toHaveURL('/dashboard');
|
||||
|
||||
// Step 6: Verify user name still visible after reload
|
||||
await expect(userNameElement).toBeVisible({ timeout: 5000 });
|
||||
});
|
||||
|
||||
test('should persist session across browser context restarts', async ({ browser }) => {
|
||||
// Create initial context and authenticate
|
||||
const context1 = await browser.newContext();
|
||||
const page1 = await context1.newPage();
|
||||
|
||||
await page1.goto('/login');
|
||||
|
||||
// Simulate OAuth authentication
|
||||
await page1.evaluate(() => {
|
||||
const mockAuthData = {
|
||||
token: 'mock-jwt-token',
|
||||
record: {
|
||||
id: 'test-user-id',
|
||||
email: 'test@example.com',
|
||||
name: 'Test User',
|
||||
emailVisibility: false,
|
||||
verified: true,
|
||||
avatar: '',
|
||||
created: new Date().toISOString(),
|
||||
updated: new Date().toISOString()
|
||||
}
|
||||
};
|
||||
localStorage.setItem('pocketbase_auth', JSON.stringify(mockAuthData));
|
||||
});
|
||||
|
||||
await page1.goto('/dashboard');
|
||||
await expect(page1).toHaveURL('/dashboard');
|
||||
|
||||
// Extract storage state to simulate browser restart
|
||||
const storageState = await context1.storageState();
|
||||
await context1.close();
|
||||
|
||||
// Create new context with stored state (simulates browser restart)
|
||||
const context2 = await browser.newContext({ storageState });
|
||||
const page2 = await context2.newPage();
|
||||
|
||||
// Navigate directly to dashboard
|
||||
await page2.goto('/dashboard');
|
||||
|
||||
// Verify session persisted - should not redirect to /login
|
||||
await expect(page2).toHaveURL('/dashboard');
|
||||
|
||||
// Verify user info still available
|
||||
const userNameElement = page2.locator('[data-testid="user-name"]').or(
|
||||
page2.locator('text=Test User')
|
||||
);
|
||||
await expect(userNameElement).toBeVisible({ timeout: 5000 });
|
||||
|
||||
await context2.close();
|
||||
});
|
||||
|
||||
test('should redirect to login when accessing protected page without session', async ({ page }) => {
|
||||
// Ensure no auth state exists
|
||||
await page.goto('/');
|
||||
await page.evaluate(() => localStorage.clear());
|
||||
|
||||
// Try to access protected page
|
||||
await page.goto('/dashboard');
|
||||
|
||||
// Should redirect to login with redirect parameter
|
||||
await expect(page).toHaveURL(/\/login\?redirect=.*dashboard/);
|
||||
});
|
||||
|
||||
test('should restore session and allow access to other protected pages', async ({ page }) => {
|
||||
// Set up authenticated session
|
||||
await page.goto('/login');
|
||||
await page.evaluate(() => {
|
||||
const mockAuthData = {
|
||||
token: 'mock-jwt-token',
|
||||
record: {
|
||||
id: 'test-user-id',
|
||||
email: 'test@example.com',
|
||||
name: 'Test User',
|
||||
emailVisibility: false,
|
||||
verified: true,
|
||||
avatar: '',
|
||||
created: new Date().toISOString(),
|
||||
updated: new Date().toISOString()
|
||||
}
|
||||
};
|
||||
localStorage.setItem('pocketbase_auth', JSON.stringify(mockAuthData));
|
||||
});
|
||||
|
||||
await page.goto('/dashboard');
|
||||
await expect(page).toHaveURL('/dashboard');
|
||||
|
||||
// Reload to test session persistence
|
||||
await page.reload();
|
||||
await expect(page).toHaveURL('/dashboard');
|
||||
|
||||
// Navigate to other protected pages
|
||||
// NOTE: Adjust these based on actual application routes
|
||||
// await page.goto('/projects');
|
||||
// await expect(page).toHaveURL('/projects');
|
||||
|
||||
// await page.goto('/tasks');
|
||||
// await expect(page).toHaveURL('/tasks');
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* IMPLEMENTATION NOTES FOR TEST SETUP:
|
||||
*
|
||||
* 1. **Playwright Configuration Required**:
|
||||
* - Create `playwright.config.ts` in project root
|
||||
* - Configure base URL to match dev server (http://localhost:3000)
|
||||
* - Set up test fixtures for Pocketbase mock
|
||||
*
|
||||
* 2. **Pocketbase Test Provider Setup**:
|
||||
* Option A: Use Pocketbase test mode with mock OAuth provider
|
||||
* Option B: Create test-specific OAuth provider (e.g., test-provider)
|
||||
* Option C: Mock OAuth at network level using Playwright's route interception
|
||||
*
|
||||
* 3. **Mock OAuth Flow** (Recommended Approach):
|
||||
* ```typescript
|
||||
* await page.route('**/api/collections/users/auth-with-oauth2', async route => {
|
||||
* await route.fulfill({
|
||||
* status: 200,
|
||||
* body: JSON.stringify({
|
||||
* token: 'mock-jwt',
|
||||
* record: { ... }
|
||||
* })
|
||||
* });
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* 4. **Data Test IDs**:
|
||||
* Add the following to components:
|
||||
* - Navbar user name: `data-testid="user-name"`
|
||||
* - OAuth provider buttons: `data-testid="oauth-{provider}"`
|
||||
* - Logout button: `data-testid="logout-button"`
|
||||
*
|
||||
* 5. **Running Tests**:
|
||||
* - Install Playwright: `pnpm add -D @playwright/test`
|
||||
* - Add to package.json: `"test:e2e": "playwright test"`
|
||||
* - Run: `pnpm test:e2e`
|
||||
*/
|
||||
Reference in New Issue
Block a user