Files
everything-is-suitable/everything-is-suitable-test/e2e/helpers/api-helper.ts
T
张翔 08ea5fbe98 feat(admin): 添加用户管理相关文件
添加用户管理视图、API和状态管理文件
2026-03-28 14:37:29 +08:00

309 lines
8.8 KiB
TypeScript

import { APIRequestContext, APIResponse } from '@playwright/test';
import { testLogger } from '../shared/utils/test-logger';
export interface APIRequestOptions {
headers?: Record<string, string>;
params?: Record<string, string | number>;
data?: any;
timeout?: number;
}
export interface APIResponseData<T = any> {
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<string, string> {
const headers: Record<string, string> = {
'Content-Type': 'application/json',
...options?.headers
};
if (this.token) {
headers['Authorization'] = `${this.tokenType} ${this.token}`;
}
return headers;
}
private buildURL(endpoint: string, params?: Record<string, string | number>): 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<string, string>)
).toString();
url += `?${queryString}`;
}
return url;
}
private async handleResponse<T>(response: APIResponse, endpoint: string): Promise<APIResponseData<T>> {
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<T = any>(endpoint: string, options?: APIRequestOptions): Promise<APIResponseData<T>> {
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<T>(response, endpoint);
}
async post<T = any>(endpoint: string, options?: APIRequestOptions): Promise<APIResponseData<T>> {
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<T>(response, endpoint);
}
async put<T = any>(endpoint: string, options?: APIRequestOptions): Promise<APIResponseData<T>> {
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<T>(response, endpoint);
}
async delete<T = any>(endpoint: string, options?: APIRequestOptions): Promise<APIResponseData<T>> {
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<T>(response, endpoint);
}
async patch<T = any>(endpoint: string, options?: APIRequestOptions): Promise<APIResponseData<T>> {
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<T>(response, endpoint);
}
async upload<T = any>(endpoint: string, formData: any, options?: APIRequestOptions): Promise<APIResponseData<T>> {
const url = this.buildURL(endpoint, options?.params);
const headers: Record<string, string> = {
...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<T>(response, endpoint);
}
async download(endpoint: string, options?: APIRequestOptions): Promise<Buffer> {
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<T = any>(method: string, endpoint: string, options?: APIRequestOptions): Promise<APIResponseData<T>> {
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<T>(response, endpoint);
}
async login(username: string, password: string): Promise<APIResponseData<{ token: string }>> {
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<APIResponseData<any>> {
testLogger.info('Logout');
const response = await this.post('/auth/logout');
this.clearToken();
testLogger.info('Logout successful');
return response;
}
async refresh(): Promise<APIResponseData<{ token: string }>> {
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;
}
}