import { APIRequestContext, APIResponse } from '@playwright/test'; import { testLogger } from '../shared/utils/test-logger'; export interface APIRequestOptions { headers?: Record; params?: Record; data?: any; timeout?: number; } export interface APIResponseData { success: boolean; code: string; message: string; data: T; timestamp: number; } export class APIHelper { private apiContext: APIRequestContext; private baseURL: string; private token: string | null = null; private tokenType: string = 'Bearer'; constructor(apiContext: APIRequestContext, baseURL: string) { this.apiContext = apiContext; this.baseURL = baseURL; testLogger.info(`APIHelper initialized with baseURL: ${baseURL}`); } setToken(token: string, tokenType: string = 'Bearer'): void { this.token = token; this.tokenType = tokenType; testLogger.info('Token set successfully'); } clearToken(): void { this.token = null; testLogger.info('Token cleared'); } private buildHeaders(options?: APIRequestOptions): Record { const headers: Record = { 'Content-Type': 'application/json', ...options?.headers }; if (this.token) { headers['Authorization'] = `${this.tokenType} ${this.token}`; } return headers; } private buildURL(endpoint: string, params?: Record): string { let url = `${this.baseURL}${endpoint}`; if (params && Object.keys(params).length > 0) { const queryString = new URLSearchParams( Object.entries(params).reduce((acc, [key, value]) => { acc[key] = String(value); return acc; }, {} as Record) ).toString(); url += `?${queryString}`; } return url; } private async handleResponse(response: APIResponse, endpoint: string): Promise> { const statusCode = response.status(); const contentType = response.headers()['content-type'] || ''; testLogger.debug(`API Response: ${endpoint} - Status: ${statusCode}, ContentType: ${contentType}`); if (!response.ok()) { const errorText = await response.text(); testLogger.error(`API Error: ${endpoint} - Status: ${statusCode}`, new Error(errorText)); throw new Error(`API request failed: ${statusCode} - ${errorText}`); } let responseData: any; if (contentType.includes('application/json')) { responseData = await response.json(); } else { responseData = await response.text(); } return responseData; } async get(endpoint: string, options?: APIRequestOptions): Promise> { const url = this.buildURL(endpoint, options?.params); const headers = this.buildHeaders(options); testLogger.info(`GET ${url}`); const startTime = Date.now(); const response = await this.apiContext.get(url, { headers, timeout: options?.timeout || 30000 }); const duration = Date.now() - startTime; testLogger.info(`GET ${url} - ${duration}ms`); return this.handleResponse(response, endpoint); } async post(endpoint: string, options?: APIRequestOptions): Promise> { const url = this.buildURL(endpoint, options?.params); const headers = this.buildHeaders(options); testLogger.info(`POST ${url}`); const startTime = Date.now(); const response = await this.apiContext.post(url, { headers, data: options?.data, timeout: options?.timeout || 30000 }); const duration = Date.now() - startTime; testLogger.info(`POST ${url} - ${duration}ms`); return this.handleResponse(response, endpoint); } async put(endpoint: string, options?: APIRequestOptions): Promise> { const url = this.buildURL(endpoint, options?.params); const headers = this.buildHeaders(options); testLogger.info(`PUT ${url}`); const startTime = Date.now(); const response = await this.apiContext.put(url, { headers, data: options?.data, timeout: options?.timeout || 30000 }); const duration = Date.now() - startTime; testLogger.info(`PUT ${url} - ${duration}ms`); return this.handleResponse(response, endpoint); } async delete(endpoint: string, options?: APIRequestOptions): Promise> { const url = this.buildURL(endpoint, options?.params); const headers = this.buildHeaders(options); testLogger.info(`DELETE ${url}`); const startTime = Date.now(); const response = await this.apiContext.delete(url, { headers, timeout: options?.timeout || 30000 }); const duration = Date.now() - startTime; testLogger.info(`DELETE ${url} - ${duration}ms`); return this.handleResponse(response, endpoint); } async patch(endpoint: string, options?: APIRequestOptions): Promise> { const url = this.buildURL(endpoint, options?.params); const headers = this.buildHeaders(options); testLogger.info(`PATCH ${url}`); const startTime = Date.now(); const response = await this.apiContext.fetch(url, { method: 'PATCH', headers, data: options?.data, timeout: options?.timeout || 30000 }); const duration = Date.now() - startTime; testLogger.info(`PATCH ${url} - ${duration}ms`); return this.handleResponse(response, endpoint); } async upload(endpoint: string, formData: any, options?: APIRequestOptions): Promise> { const url = this.buildURL(endpoint, options?.params); const headers: Record = { ...options?.headers }; if (this.token) { headers['Authorization'] = `${this.tokenType} ${this.token}`; } testLogger.info(`UPLOAD ${url}`); const startTime = Date.now(); const response = await this.apiContext.post(url, { headers, multipart: formData, timeout: options?.timeout || 60000 }); const duration = Date.now() - startTime; testLogger.info(`UPLOAD ${url} - ${duration}ms`); return this.handleResponse(response, endpoint); } async download(endpoint: string, options?: APIRequestOptions): Promise { const url = this.buildURL(endpoint, options?.params); const headers = this.buildHeaders(options); testLogger.info(`DOWNLOAD ${url}`); const startTime = Date.now(); const response = await this.apiContext.get(url, { headers, timeout: options?.timeout || 60000 }); const duration = Date.now() - startTime; testLogger.info(`DOWNLOAD ${url} - ${duration}ms`); if (!response.ok()) { throw new Error(`Download failed: ${response.status()}`); } return response.body(); } async request(method: string, endpoint: string, options?: APIRequestOptions): Promise> { const url = this.buildURL(endpoint, options?.params); const headers = this.buildHeaders(options); testLogger.info(`${method.toUpperCase()} ${url}`); const startTime = Date.now(); const response = await this.apiContext.fetch(url, { method, headers, data: options?.data, timeout: options?.timeout || 30000 }); const duration = Date.now() - startTime; testLogger.info(`${method.toUpperCase()} ${url} - ${duration}ms`); return this.handleResponse(response, endpoint); } async login(username: string, password: string): Promise> { testLogger.info(`Login attempt for user: ${username}`); const response = await this.post('/auth/login', { data: { username, password } }); if (response.success && response.data.token) { this.setToken(response.data.token); testLogger.info('Login successful'); } return response; } async logout(): Promise> { testLogger.info('Logout'); const response = await this.post('/auth/logout'); this.clearToken(); testLogger.info('Logout successful'); return response; } async refresh(): Promise> { testLogger.info('Token refresh'); if (!this.token) { throw new Error('No token to refresh'); } const response = await this.post('/auth/refresh'); if (response.success && response.data.token) { this.setToken(response.data.token); testLogger.info('Token refreshed successfully'); } return response; } verifyToken(): boolean { return this.token !== null && this.token.length > 0; } getToken(): string | null { return this.token; } getBaseURL(): string { return this.baseURL; } }