Production-ready test infrastructure with Page Object Model pattern, reusable fixtures for auth/DB/test-data, and example tests covering login flow, dashboard, accounts CRUD API, and visual regression. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
125 lines
3.9 KiB
TypeScript
125 lines
3.9 KiB
TypeScript
/**
|
|
* API regression tests for authentication endpoints.
|
|
*
|
|
* Tests the NestJS auth controller directly via HTTP.
|
|
* No browser needed — uses Playwright's request context.
|
|
*/
|
|
|
|
import { test, expect } from '@playwright/test';
|
|
import { TEST_USERS } from '../fixtures/test-data';
|
|
|
|
const API_BASE = process.env.API_BASE_URL || 'http://localhost/api';
|
|
|
|
test.describe('POST /api/auth/login', () => {
|
|
test('should return access token for valid credentials', async ({ request }) => {
|
|
const response = await request.post(`${API_BASE}/auth/login`, {
|
|
data: {
|
|
email: TEST_USERS.treasurer.email,
|
|
password: TEST_USERS.treasurer.password,
|
|
},
|
|
});
|
|
|
|
expect(response.status()).toBe(201);
|
|
|
|
const body = await response.json();
|
|
expect(body).toHaveProperty('accessToken');
|
|
expect(body).toHaveProperty('user');
|
|
expect(body.user).toHaveProperty('email', TEST_USERS.treasurer.email);
|
|
expect(body).toHaveProperty('organizations');
|
|
expect(Array.isArray(body.organizations)).toBe(true);
|
|
});
|
|
|
|
test('should return 401 for invalid password', async ({ request }) => {
|
|
const response = await request.post(`${API_BASE}/auth/login`, {
|
|
data: {
|
|
email: TEST_USERS.treasurer.email,
|
|
password: 'WrongPassword123!',
|
|
},
|
|
});
|
|
|
|
expect(response.status()).toBe(401);
|
|
});
|
|
|
|
test('should return 401 for non-existent user', async ({ request }) => {
|
|
const response = await request.post(`${API_BASE}/auth/login`, {
|
|
data: {
|
|
email: 'nonexistent@example.com',
|
|
password: 'AnyPassword123!',
|
|
},
|
|
});
|
|
|
|
expect(response.status()).toBe(401);
|
|
});
|
|
|
|
test('should set httpOnly refresh cookie on success', async ({ request }) => {
|
|
const response = await request.post(`${API_BASE}/auth/login`, {
|
|
data: {
|
|
email: TEST_USERS.treasurer.email,
|
|
password: TEST_USERS.treasurer.password,
|
|
},
|
|
});
|
|
|
|
expect(response.status()).toBe(201);
|
|
|
|
// Check for Set-Cookie header with the refresh token
|
|
const setCookie = response.headers()['set-cookie'] || '';
|
|
expect(setCookie).toContain('ledgeriq_rt');
|
|
expect(setCookie).toContain('HttpOnly');
|
|
});
|
|
});
|
|
|
|
test.describe('GET /api/auth/profile', () => {
|
|
test('should return 401 without auth header', async ({ request }) => {
|
|
const response = await request.get(`${API_BASE}/auth/profile`);
|
|
expect(response.status()).toBe(401);
|
|
});
|
|
|
|
test('should return user profile with valid token', async ({ request }) => {
|
|
// Login first
|
|
const loginResponse = await request.post(`${API_BASE}/auth/login`, {
|
|
data: {
|
|
email: TEST_USERS.treasurer.email,
|
|
password: TEST_USERS.treasurer.password,
|
|
},
|
|
});
|
|
const { accessToken } = await loginResponse.json();
|
|
|
|
// Fetch profile
|
|
const response = await request.get(`${API_BASE}/auth/profile`, {
|
|
headers: { Authorization: `Bearer ${accessToken}` },
|
|
});
|
|
|
|
expect(response.status()).toBe(200);
|
|
const profile = await response.json();
|
|
expect(profile).toHaveProperty('email', TEST_USERS.treasurer.email);
|
|
});
|
|
});
|
|
|
|
test.describe('POST /api/auth/logout', () => {
|
|
test('should return success and clear cookie', async ({ request }) => {
|
|
// Login first to get the cookie
|
|
await request.post(`${API_BASE}/auth/login`, {
|
|
data: {
|
|
email: TEST_USERS.treasurer.email,
|
|
password: TEST_USERS.treasurer.password,
|
|
},
|
|
});
|
|
|
|
// Logout
|
|
const response = await request.post(`${API_BASE}/auth/logout`);
|
|
expect(response.status()).toBe(200);
|
|
|
|
const body = await response.json();
|
|
expect(body).toEqual({ success: true });
|
|
});
|
|
});
|
|
|
|
test.describe('POST /api/auth/refresh', () => {
|
|
test('should return 400 without refresh cookie', async ({ request }) => {
|
|
// Create a fresh context without cookies
|
|
const response = await request.post(`${API_BASE}/auth/refresh`);
|
|
// Should fail — no refresh token cookie
|
|
expect([400, 401]).toContain(response.status());
|
|
});
|
|
});
|