feat(admin): 添加用户管理相关文件
添加用户管理视图、API和状态管理文件
This commit is contained in:
@@ -0,0 +1,308 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user