/** * 集成测试 HTTP 客户端 * 使用 axios 替代 Playwright 的 APIRequestContext,确保可在 Vitest 中运行 * * FIX-01: Node.js 22 下 localhost 解析为 IPv6 ::1,但后端只绑 IPv4 0.0.0.0 * 强制使用 127.0.0.1 (避免被环境变量 BASE_URL=/ 干扰) */ import axios, { AxiosInstance, AxiosResponse } from 'axios'; // 强制 127.0.0.1,不依赖环境变量(避免被 BASE_URL=/ 等干扰) const BASE_URL = 'http://127.0.0.1:3000'; let httpClient: AxiosInstance | null = null; let authToken: string | null = null; /** 初始化 HTTP 客户端 */ export function initHttpClient(): void { httpClient = axios.create({ baseURL: BASE_URL, timeout: 30000, headers: { 'Content-Type': 'application/json' }, }); // 防御:如果多次 init,需要重新创建 if (!httpClient) { httpClient = axios.create({ baseURL: BASE_URL, timeout: 30000, headers: { 'Content-Type': 'application/json' }, }); } } /** 释放 HTTP 客户端 */ export function disposeHttpClient(): void { httpClient = null; } /** 设置认证 token */ export function setAuthToken(token: string): void { authToken = token; } /** 获取当前 token */ export function getAuthToken(): string | null { return authToken; } /** 清除认证 token */ export function clearAuthToken(): void { authToken = null; } /** API 响应类型 */ export interface ApiResponse { code: number; message: string; data: any; } /** 解析 API 响应 */ function parseResponse(response: AxiosResponse): ApiResponse { const body = response.data; if (body && typeof body === 'object' && body.code !== undefined) { return { code: body.code, message: body.message ?? '', data: body.data }; } if (body && typeof body === 'object' && body.success === true) { return { code: 0, message: '', data: body.data }; } return { code: response.status, message: '', data: body }; } /** 确保 httpClient 已初始化 */ function ensureClient(): AxiosInstance { if (!httpClient) { initHttpClient(); } if (!httpClient) { throw new Error('httpClient 初始化失败'); } return httpClient; } /** GET 请求 */ export async function apiGet(path: string, params?: Record): Promise { const client = ensureClient(); const config: any = {}; if (authToken) { config.headers = { Authorization: `Bearer ${authToken}` }; } if (params) { config.params = params; } try { const response = await client.get(path, config); return parseResponse(response); } catch (error: any) { if (error.response) { return parseResponse(error.response); } return { code: -1, message: error.message || String(error), data: null }; } } /** POST 请求 */ export async function apiPost(path: string, data?: any): Promise { const client = ensureClient(); const config: any = {}; if (authToken) { config.headers = { Authorization: `Bearer ${authToken}` }; } try { const response = await client.post(path, data, config); return parseResponse(response); } catch (error: any) { if (error.response) { return parseResponse(error.response); } return { code: -1, message: error.message || String(error), data: null }; } } /** PUT 请求 */ export async function apiPut(path: string, data?: any): Promise { const client = ensureClient(); const config: any = {}; if (authToken) { config.headers = { Authorization: `Bearer ${authToken}` }; } try { const response = await client.put(path, data, config); return parseResponse(response); } catch (error: any) { if (error.response) { return parseResponse(error.response); } return { code: -1, message: error.message || String(error), data: null }; } } /** DELETE 请求 */ export async function apiDelete(path: string): Promise { const client = ensureClient(); const config: any = {}; if (authToken) { config.headers = { Authorization: `Bearer ${authToken}` }; } try { const response = await client.delete(path, config); return parseResponse(response); } catch (error: any) { if (error.response) { return parseResponse(error.response); } return { code: -1, message: error.message || String(error), data: null }; } } /** 创建测试用户(集成测试专用) */ export async function createTestUser(): Promise<{ token: string; userId: string }> { initHttpClient(); const phone = `13${Math.floor(Math.random() * 1_000_000_000).toString().padStart(9, '0')}`; await apiPost('/api/auth/send-code', { phone }); const loginRes = await apiPost('/api/auth/login', { phone, code: '123456' }); if (loginRes.code !== 0 || !loginRes.data?.token) { throw new Error('创建测试用户失败'); } setAuthToken(loginRes.data.token); return { token: loginRes.data.token, userId: loginRes.data.userId || '1' }; }