/** * 统一 API 请求封装 * 提供 GET / POST / PUT / DELETE 方法,自动处理认证和错误 */ import { request, APIRequestContext } from '@playwright/test'; const BASE_URL = process.env.BACKEND_URL || 'http://127.0.0.1:3000'; let apiContext: APIRequestContext; let authToken: string | null = null; let testUserId: string | null = null; /** 初始化 API 客户端 */ export async function initApiClient() { apiContext = await request.newContext({ baseURL: BASE_URL }); } /** 释放 API 客户端 */ export async function disposeApiClient() { if (apiContext) await apiContext.dispose(); } /** 设置认证 token */ export function setAuthToken(token: string, userId?: string) { authToken = token; if (userId) testUserId = userId; } /** 获取当前 token */ export function getAuthToken(): string | null { return authToken; } /** 获取当前测试用户 ID */ export function getTestUserId(): string { return testUserId || '1'; } /** 通用请求选项 */ function getOptions(method: string, data?: any): Record { const options: Record = { method, headers: getAuthHeaders(), }; if (data) { options.data = data; } return options; } /** 提取公共请求头(Authorization + Content-Type) */ function getAuthHeaders(): Record { const headers: Record = { 'Content-Type': 'application/json' }; if (authToken) { headers['Authorization'] = `Bearer ${authToken}`; } return headers; } /** API 响应类型 */ export interface ApiResponse { code: number; message: string; data: any; } /** 解析 API 响应,自动兼容多种响应格式 */ function parseApiResponse(body: any, res: any): ApiResponse { // 标准格式: { code: 0, message: '...', data: ... } if (body && typeof body === 'object' && body.code !== undefined) { return { code: body.code, message: body.message ?? '', data: body.data }; } // 兼容格式: { success: true, data: ... } -> code = 0 if (body && typeof body === 'object' && body.success === true) { return { code: 0, message: '', data: body.data }; } // 降级: 使用 HTTP 状态码 return { code: res.status(), message: '', data: null }; } /** 安全解析 JSON,避免非 JSON 响应崩溃 */ async function safeJson(res: any): Promise { const text = await res.text(); if (!text || text === 'Not Found' || text === 'Method Not Allowed') { return { _raw: text }; } try { return JSON.parse(text); } catch { return { _raw: text }; } } /** GET 请求 */ export async function apiGet(path: string, params?: Record): Promise { let url = path; if (params) { const qs = new URLSearchParams(params).toString(); url = `${path}?${qs}`; } // 必须传 headers:之前这里漏掉 headers 导致 GET 请求永远没带 Authorization, // optionalAuth 中间件 fall back 到 TEST_USER,所有需要 owner 校验的接口都 404。 const res = await apiContext.get(url, { headers: getAuthHeaders() }); const body = await safeJson(res); return parseApiResponse(body, res); } /** POST 请求 */ export async function apiPost(path: string, data?: any): Promise { const res = await apiContext.post(path, getOptions('POST', data)); const body = await safeJson(res); return parseApiResponse(body, res); } /** PUT 请求 */ export async function apiPut(path: string, data?: any): Promise { const res = await apiContext.put(path, getOptions('PUT', data)); const body = await safeJson(res); return parseApiResponse(body, res); } /** DELETE 请求 */ export async function apiDelete(path: string): Promise { const res = await apiContext.delete(path, getOptions('DELETE')); const body = await safeJson(res); return parseApiResponse(body, res); } /** 获取原始响应(用于检查状态码等) */ export async function apiRaw(method: string, path: string, data?: any) { return apiContext.fetch(path, getOptions(method, data)); }