test-utils.ts 2.2 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677
  1. /**
  2. * Actions API(/api/actions/[...route])的“进程内”调用工具
  3. *
  4. * 目标:
  5. * - 不启动 next dev / next start
  6. * - 不走真实网络端口(更稳定、更快、适合 CI)
  7. * - 仍然以 HTTP 语义(Request/Response)进行断言
  8. *
  9. * 适用范围:
  10. * - OpenAPI 文档(/api/actions/openapi.json)
  11. * - 文档 UI(/api/actions/docs /api/actions/scalar)
  12. * - 健康检查(/api/actions/health)
  13. * - 以及需要校验“缺少 Cookie 直接 401”的端点
  14. */
  15. import { Request as UndiciRequest } from "undici";
  16. import { GET, POST } from "@/app/api/actions/[...route]/route";
  17. export type ActionsRouteCallOptions = {
  18. method: "GET" | "POST";
  19. /**
  20. * 形如:/api/actions/openapi.json 或 /api/actions/users/getUsers
  21. */
  22. pathname: string;
  23. /**
  24. * 写入 Cookie: auth-token=...
  25. */
  26. authToken?: string;
  27. headers?: Record<string, string>;
  28. /**
  29. * POST 请求体,会自动 JSON.stringify
  30. */
  31. body?: unknown;
  32. };
  33. export async function callActionsRoute(options: ActionsRouteCallOptions): Promise<{
  34. response: Response;
  35. /**
  36. * 当响应为 application/json 时解析后的对象,否则为 undefined
  37. */
  38. json?: unknown;
  39. /**
  40. * 当响应不是 JSON 时返回文本,否则为 undefined
  41. */
  42. text?: string;
  43. }> {
  44. const url = new URL(options.pathname, "http://localhost");
  45. const headers: Record<string, string> = {
  46. ...(options.headers ?? {}),
  47. };
  48. if (options.authToken) {
  49. const existing = headers.Cookie ? `${headers.Cookie}; ` : "";
  50. headers.Cookie = `${existing}auth-token=${options.authToken}`;
  51. headers.Authorization = headers.Authorization ?? `Bearer ${options.authToken}`;
  52. }
  53. if (options.method === "POST") {
  54. headers["Content-Type"] = headers["Content-Type"] ?? "application/json";
  55. }
  56. const request = new UndiciRequest(url, {
  57. method: options.method,
  58. headers,
  59. body: options.method === "POST" ? JSON.stringify(options.body ?? {}) : undefined,
  60. });
  61. const response = options.method === "GET" ? await GET(request) : await POST(request);
  62. const contentType = response.headers.get("content-type") || "";
  63. if (contentType.includes("application/json")) {
  64. return { response, json: await response.json() };
  65. }
  66. return { response, text: await response.text() };
  67. }