api-client.ts 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124
  1. /**
  2. * 统一 API 请求封装
  3. * 提供 GET / POST / PUT / DELETE 方法,自动处理认证和错误
  4. */
  5. import { request, APIRequestContext } from '@playwright/test';
  6. const BASE_URL = 'http://localhost:3000';
  7. let apiContext: APIRequestContext;
  8. let authToken: string | null = null;
  9. let testUserId: string | null = null;
  10. /** 初始化 API 客户端 */
  11. export async function initApiClient() {
  12. apiContext = await request.newContext({ baseURL: BASE_URL });
  13. }
  14. /** 释放 API 客户端 */
  15. export async function disposeApiClient() {
  16. if (apiContext) await apiContext.dispose();
  17. }
  18. /** 设置认证 token */
  19. export function setAuthToken(token: string, userId?: string) {
  20. authToken = token;
  21. if (userId) testUserId = userId;
  22. }
  23. /** 获取当前 token */
  24. export function getAuthToken(): string | null {
  25. return authToken;
  26. }
  27. /** 获取当前测试用户 ID */
  28. export function getTestUserId(): string {
  29. return testUserId || '1';
  30. }
  31. /** 通用请求选项 */
  32. function getOptions(method: string, data?: any): Record<string, any> {
  33. const options: Record<string, any> = {
  34. method,
  35. headers: { 'Content-Type': 'application/json' },
  36. };
  37. if (authToken) {
  38. options.headers['Authorization'] = `Bearer ${authToken}`;
  39. }
  40. if (data) {
  41. options.data = data;
  42. }
  43. return options;
  44. }
  45. /** API 响应类型 */
  46. export interface ApiResponse {
  47. code: number;
  48. message: string;
  49. data: any;
  50. }
  51. /** 解析 API 响应,自动兼容多种响应格式 */
  52. function parseApiResponse(body: any, res: any): ApiResponse {
  53. // 标准格式: { code: 0, message: '...', data: ... }
  54. if (body && typeof body === 'object' && body.code !== undefined) {
  55. return { code: body.code, message: body.message ?? '', data: body.data };
  56. }
  57. // 兼容格式: { success: true, data: ... } -> code = 0
  58. if (body && typeof body === 'object' && body.success === true) {
  59. return { code: 0, message: '', data: body.data };
  60. }
  61. // 降级: 使用 HTTP 状态码
  62. return { code: res.status(), message: '', data: null };
  63. }
  64. /** 安全解析 JSON,避免非 JSON 响应崩溃 */
  65. async function safeJson(res: any): Promise<any> {
  66. const text = await res.text();
  67. if (!text || text === 'Not Found' || text === 'Method Not Allowed') {
  68. return { _raw: text };
  69. }
  70. try {
  71. return JSON.parse(text);
  72. } catch {
  73. return { _raw: text };
  74. }
  75. }
  76. /** GET 请求 */
  77. export async function apiGet(path: string, params?: Record<string, string>): Promise<ApiResponse> {
  78. let url = path;
  79. if (params) {
  80. const qs = new URLSearchParams(params).toString();
  81. url = `${path}?${qs}`;
  82. }
  83. const res = await apiContext.get(url);
  84. const body = await safeJson(res);
  85. return parseApiResponse(body, res);
  86. }
  87. /** POST 请求 */
  88. export async function apiPost(path: string, data?: any): Promise<ApiResponse> {
  89. const res = await apiContext.post(path, getOptions('POST', data));
  90. const body = await safeJson(res);
  91. return parseApiResponse(body, res);
  92. }
  93. /** PUT 请求 */
  94. export async function apiPut(path: string, data?: any): Promise<ApiResponse> {
  95. const res = await apiContext.put(path, getOptions('PUT', data));
  96. const body = await safeJson(res);
  97. return parseApiResponse(body, res);
  98. }
  99. /** DELETE 请求 */
  100. export async function apiDelete(path: string): Promise<ApiResponse> {
  101. const res = await apiContext.delete(path, getOptions('DELETE'));
  102. const body = await safeJson(res);
  103. return parseApiResponse(body, res);
  104. }
  105. /** 获取原始响应(用于检查状态码等) */
  106. export async function apiRaw(method: string, path: string, data?: any) {
  107. return apiContext.fetch(path, getOptions(method, data));
  108. }