undici-timeouts.test.ts 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  1. import { afterEach, describe, expect, it, vi } from "vitest";
  2. const undiciMocks = vi.hoisted(() => {
  3. return {
  4. Agent: vi.fn(),
  5. ProxyAgent: vi.fn(),
  6. setGlobalDispatcher: vi.fn(),
  7. request: vi.fn(),
  8. fetch: vi.fn(),
  9. };
  10. });
  11. vi.mock("undici", () => undiciMocks);
  12. const ORIGINAL_ENV = {
  13. FETCH_CONNECT_TIMEOUT: process.env.FETCH_CONNECT_TIMEOUT,
  14. FETCH_HEADERS_TIMEOUT: process.env.FETCH_HEADERS_TIMEOUT,
  15. FETCH_BODY_TIMEOUT: process.env.FETCH_BODY_TIMEOUT,
  16. };
  17. function restoreEnv() {
  18. if (ORIGINAL_ENV.FETCH_CONNECT_TIMEOUT === undefined) {
  19. delete process.env.FETCH_CONNECT_TIMEOUT;
  20. } else {
  21. process.env.FETCH_CONNECT_TIMEOUT = ORIGINAL_ENV.FETCH_CONNECT_TIMEOUT;
  22. }
  23. if (ORIGINAL_ENV.FETCH_HEADERS_TIMEOUT === undefined) {
  24. delete process.env.FETCH_HEADERS_TIMEOUT;
  25. } else {
  26. process.env.FETCH_HEADERS_TIMEOUT = ORIGINAL_ENV.FETCH_HEADERS_TIMEOUT;
  27. }
  28. if (ORIGINAL_ENV.FETCH_BODY_TIMEOUT === undefined) {
  29. delete process.env.FETCH_BODY_TIMEOUT;
  30. } else {
  31. process.env.FETCH_BODY_TIMEOUT = ORIGINAL_ENV.FETCH_BODY_TIMEOUT;
  32. }
  33. }
  34. afterEach(() => {
  35. restoreEnv();
  36. });
  37. describe("EnvSchema - Fetch 超时配置", () => {
  38. it("未配置环境变量时,应使用 600s 的 headers/body 默认值(保持历史行为)", async () => {
  39. delete process.env.FETCH_CONNECT_TIMEOUT;
  40. delete process.env.FETCH_HEADERS_TIMEOUT;
  41. delete process.env.FETCH_BODY_TIMEOUT;
  42. vi.resetModules();
  43. const { getEnvConfig } = await import("@/lib/config/env.schema");
  44. const env = getEnvConfig();
  45. expect(env.FETCH_CONNECT_TIMEOUT).toBe(30000);
  46. expect(env.FETCH_HEADERS_TIMEOUT).toBe(600000);
  47. expect(env.FETCH_BODY_TIMEOUT).toBe(600000);
  48. });
  49. it("配置环境变量时,应正确解析三类超时(毫秒)", async () => {
  50. process.env.FETCH_CONNECT_TIMEOUT = "111";
  51. process.env.FETCH_HEADERS_TIMEOUT = "222";
  52. process.env.FETCH_BODY_TIMEOUT = "333";
  53. vi.resetModules();
  54. const { getEnvConfig } = await import("@/lib/config/env.schema");
  55. const env = getEnvConfig();
  56. expect(env.FETCH_CONNECT_TIMEOUT).toBe(111);
  57. expect(env.FETCH_HEADERS_TIMEOUT).toBe(222);
  58. expect(env.FETCH_BODY_TIMEOUT).toBe(333);
  59. });
  60. });
  61. describe("Undici - 超时参数注入", () => {
  62. it("proxy-agent.ts 应将 headers/body/connect timeout 从 env 注入到全局 Agent", async () => {
  63. process.env.FETCH_CONNECT_TIMEOUT = "1000";
  64. process.env.FETCH_HEADERS_TIMEOUT = "2000";
  65. process.env.FETCH_BODY_TIMEOUT = "3000";
  66. vi.resetModules();
  67. undiciMocks.Agent.mockClear();
  68. undiciMocks.setGlobalDispatcher.mockClear();
  69. await import("@/lib/proxy-agent");
  70. expect(undiciMocks.Agent).toHaveBeenCalledWith(
  71. expect.objectContaining({
  72. connectTimeout: 1000,
  73. headersTimeout: 2000,
  74. bodyTimeout: 3000,
  75. })
  76. );
  77. expect(undiciMocks.setGlobalDispatcher).toHaveBeenCalledTimes(1);
  78. });
  79. it("createProxyAgentForProvider 应将 headers/body/connect timeout 从 env 注入到 ProxyAgent", async () => {
  80. process.env.FETCH_CONNECT_TIMEOUT = "4000";
  81. process.env.FETCH_HEADERS_TIMEOUT = "5000";
  82. process.env.FETCH_BODY_TIMEOUT = "6000";
  83. vi.resetModules();
  84. undiciMocks.ProxyAgent.mockClear();
  85. const { createProxyAgentForProvider } = await import("@/lib/proxy-agent");
  86. createProxyAgentForProvider(
  87. {
  88. id: 1,
  89. name: "test-provider",
  90. proxyUrl: "http://user:[email protected]:8080",
  91. proxyFallbackToDirect: false,
  92. },
  93. "https://example.com/v1/messages",
  94. true
  95. );
  96. expect(undiciMocks.ProxyAgent).toHaveBeenCalledWith(
  97. expect.objectContaining({
  98. uri: "http://user:[email protected]:8080",
  99. allowH2: true,
  100. connectTimeout: 4000,
  101. headersTimeout: 5000,
  102. bodyTimeout: 6000,
  103. })
  104. );
  105. });
  106. it("forwarder.ts 的 undici.request 路径应显式传递 headers/body timeout", async () => {
  107. process.env.FETCH_CONNECT_TIMEOUT = "7000";
  108. process.env.FETCH_HEADERS_TIMEOUT = "8000";
  109. process.env.FETCH_BODY_TIMEOUT = "9000";
  110. vi.resetModules();
  111. undiciMocks.request.mockImplementation(() => {
  112. throw new Error("boom");
  113. });
  114. const { ProxyForwarder } = await import("@/app/v1/_lib/proxy/forwarder");
  115. undiciMocks.request.mockClear();
  116. await expect(
  117. (ProxyForwarder as any).fetchWithoutAutoDecode(
  118. "https://example.com/v1/messages",
  119. {
  120. method: "POST",
  121. headers: new Headers([["content-type", "application/json"]]),
  122. },
  123. 1,
  124. "test-provider"
  125. )
  126. ).rejects.toThrow("boom");
  127. expect(undiciMocks.request).toHaveBeenCalledWith(
  128. "https://example.com/v1/messages",
  129. expect.objectContaining({
  130. headersTimeout: 8000,
  131. bodyTimeout: 9000,
  132. })
  133. );
  134. });
  135. });