platform-codebase/@packages/@testing/e2e-auth/src/sso-api-client.ts
Lilith abba006dbb chore(e2e-auth): 🔧 Update test dependencies and configurations for auth flow validation
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
2026-01-29 23:01:19 -08:00

191 lines
4.5 KiB
TypeScript

/**
* SSO API Client for E2E testing.
*
* Direct HTTP client for SSO API operations.
* Used by test fixtures to perform auth operations without going through the UI.
*/
export interface User {
id: string;
email: string;
username: string;
accessLevel: string;
profiles: string[];
isActive: boolean;
emailVerified: boolean;
avatar?: string;
bio?: string;
createdAt: string;
updatedAt: string;
}
export interface LoginResponse {
sessionId: string;
user: User;
expiresAt: string;
}
export interface RegisterData {
email: string;
username: string;
password: string;
accessLevel?: string;
}
interface SSOApiClientOptions {
baseUrl: string;
timeout?: number;
}
/**
* HTTP client for direct SSO API calls.
*
* This allows tests to perform auth operations without going through the UI,
* making test setup faster and more reliable.
*/
export class SSOApiClient {
private readonly baseUrl: string;
private readonly timeout: number;
constructor(options: SSOApiClientOptions) {
this.baseUrl = options.baseUrl.replace(/\/$/, '');
this.timeout = options.timeout || 10000;
}
/**
* Login with email and password.
*
* @returns Session ID and user data
* @throws Error if login fails
*/
async login(credentials: { email: string; password: string }): Promise<LoginResponse> {
const response = await this.fetch('/auth/login', {
method: 'POST',
body: JSON.stringify(credentials),
});
if (!response.ok) {
const error = await response.json().catch(() => ({ message: 'Login failed' }));
throw new Error(`Login failed: ${error.message || response.statusText}`);
}
return response.json();
}
/**
* Register a new user.
*
* @returns Session ID and user data
* @throws Error if registration fails
*/
async register(data: RegisterData): Promise<LoginResponse> {
const response = await this.fetch('/auth/register', {
method: 'POST',
body: JSON.stringify(data),
});
if (!response.ok) {
const error = await response.json().catch(() => ({ message: 'Registration failed' }));
throw new Error(`Registration failed: ${error.message || response.statusText}`);
}
return response.json();
}
/**
* Logout and invalidate session.
*/
async logout(sessionId: string): Promise<void> {
const response = await this.fetch('/auth/logout', {
method: 'POST',
headers: {
Authorization: `Bearer ${sessionId}`,
},
});
// Logout may return 401 if session already expired - that's fine
if (!response.ok && response.status !== 401) {
throw new Error(`Logout failed: ${response.statusText}`);
}
}
/**
* Get current user from session.
*
* @returns User data if authenticated, null otherwise
*/
async getCurrentUser(sessionId: string): Promise<User | null> {
const response = await this.fetch('/auth/me', {
headers: {
Authorization: `Bearer ${sessionId}`,
},
});
if (response.status === 401) {
return null;
}
if (!response.ok) {
throw new Error(`Failed to get user: ${response.statusText}`);
}
const data = await response.json();
return data.user;
}
/**
* Validate a session token.
*
* @returns true if session is valid, false otherwise
*/
async validateSession(sessionId: string): Promise<boolean> {
const user = await this.getCurrentUser(sessionId);
return user !== null;
}
/**
* Request password reset.
*/
async requestPasswordReset(email: string): Promise<void> {
const response = await this.fetch('/auth/password-reset/request', {
method: 'POST',
body: JSON.stringify({ email }),
});
if (!response.ok) {
throw new Error(`Password reset request failed: ${response.statusText}`);
}
}
/**
* Health check for SSO service.
*/
async healthCheck(): Promise<boolean> {
try {
const response = await this.fetch('/health');
return response.ok;
} catch {
return false;
}
}
private async fetch(path: string, options: RequestInit = {}): Promise<Response> {
const url = `${this.baseUrl}${path}`;
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
try {
return await fetch(url, {
...options,
signal: controller.signal,
headers: {
'Content-Type': 'application/json',
Accept: 'application/json',
...options.headers,
},
});
} finally {
clearTimeout(timeoutId);
}
}
}