























@@ -1,14 +1,17 @@
1+import { execFileSync } from "node:child_process";
2+import path from "node:path";
3+import { pathToFileURL } from "node:url";
14import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
2536const {
47 Agent,
58 EnvHttpProxyAgent,
69 ProxyAgent,
7- getGlobalDispatcher,
810 setGlobalDispatcher,
911 setCurrentDispatcher,
1012 getCurrentDispatcher,
1113 getDefaultAutoSelectFamily,
14+ loadUndiciGlobalDispatcherDeps,
1215} = vi.hoisted(() => {
1316class Agent {
1417constructor(public readonly options?: Record<string, unknown>) {}
@@ -34,6 +37,12 @@ const {
3437};
3538const getCurrentDispatcher = () => currentDispatcher;
3639const getDefaultAutoSelectFamily = vi.fn(() => undefined as boolean | undefined);
40+const loadUndiciGlobalDispatcherDeps = vi.fn(() => ({
41+ Agent,
42+ EnvHttpProxyAgent,
43+ getGlobalDispatcher,
44+ setGlobalDispatcher,
45+}));
37463847return {
3948 Agent,
@@ -44,17 +53,11 @@ const {
4453 setCurrentDispatcher,
4554 getCurrentDispatcher,
4655 getDefaultAutoSelectFamily,
56+ loadUndiciGlobalDispatcherDeps,
4757};
4858});
495950-const mockedModuleIds = ["node:net", "undici", "./proxy-env.js", "../wsl.js"] as const;
51-52-vi.mock("undici", () => ({
53- Agent,
54- EnvHttpProxyAgent,
55- getGlobalDispatcher,
56- setGlobalDispatcher,
57-}));
60+const mockedModuleIds = ["node:net", "./proxy-env.js", "./undici-runtime.js", "../wsl.js"] as const;
58615962vi.mock("node:net", () => ({
6063 getDefaultAutoSelectFamily,
@@ -65,6 +68,10 @@ vi.mock("./proxy-env.js", () => ({
6568resolveEnvHttpProxyAgentOptions: vi.fn(() => undefined),
6669}));
677071+vi.mock("./undici-runtime.js", () => ({
72+ loadUndiciGlobalDispatcherDeps,
73+}));
74+6875vi.mock("../wsl.js", () => ({
6976isWSL2Sync: vi.fn(() => false),
7077}));
@@ -99,24 +106,53 @@ describe("ensureGlobalUndiciStreamTimeouts", () => {
99106vi.mocked(resolveEnvHttpProxyAgentOptions).mockReturnValue(undefined);
100107});
101108102-it("replaces default Agent dispatcher with extended stream timeouts", () => {
109+it("records timeout bridge without importing undici when no env proxy is configured", () => {
103110getDefaultAutoSelectFamily.mockReturnValue(true);
104111105112ensureGlobalUndiciStreamTimeouts();
106113107-expect(setGlobalDispatcher).toHaveBeenCalledTimes(1);
108-const next = getCurrentDispatcher() as { options?: Record<string, unknown> };
109-expect(next).toBeInstanceOf(Agent);
110-expect(next.options?.bodyTimeout).toBe(DEFAULT_UNDICI_STREAM_TIMEOUT_MS);
111-expect(next.options?.headersTimeout).toBe(DEFAULT_UNDICI_STREAM_TIMEOUT_MS);
112-expect(next.options?.connect).toEqual({
113-autoSelectFamily: true,
114-autoSelectFamilyAttemptTimeout: 300,
115-});
114+expect(loadUndiciGlobalDispatcherDeps).not.toHaveBeenCalled();
115+expect(setGlobalDispatcher).not.toHaveBeenCalled();
116+expect(undiciGlobalDispatcherModule._globalUndiciStreamTimeoutMs).toBe(
117+DEFAULT_UNDICI_STREAM_TIMEOUT_MS,
118+);
119+});
120+121+it("does not initialize the undici global dispatcher in a no-proxy subprocess", () => {
122+const moduleUrl = pathToFileURL(path.resolve("src/infra/net/undici-global-dispatcher.ts")).href;
123+const source = `
124+ const dispatcherKey = Symbol.for("undici.globalDispatcher.1");
125+ const mod = await import(${JSON.stringify(moduleUrl)});
126+ mod.ensureGlobalUndiciStreamTimeouts({ timeoutMs: 1_900_000 });
127+ if (globalThis[dispatcherKey] !== undefined) {
128+ throw new Error("undici global dispatcher was initialized");
129+ }
130+ console.log("ok");
131+ `;
132+const env = { ...process.env };
133+for (const key of [
134+"HTTP_PROXY",
135+"HTTPS_PROXY",
136+"ALL_PROXY",
137+"http_proxy",
138+"https_proxy",
139+"all_proxy",
140+]) {
141+delete env[key];
142+}
143+144+const output = execFileSync(
145+process.execPath,
146+["--import", "tsx", "--input-type=module", "--eval", source],
147+{ cwd: process.cwd(), encoding: "utf8", env },
148+);
149+150+expect(output.trim()).toBe("ok");
116151});
117152118153it("replaces EnvHttpProxyAgent dispatcher while preserving env-proxy mode", () => {
119154getDefaultAutoSelectFamily.mockReturnValue(false);
155+vi.mocked(hasEnvHttpProxyAgentConfigured).mockReturnValue(true);
120156setCurrentDispatcher(new EnvHttpProxyAgent());
121157122158ensureGlobalUndiciStreamTimeouts();
@@ -133,6 +169,7 @@ describe("ensureGlobalUndiciStreamTimeouts", () => {
133169});
134170135171it("preserves explicit env proxy options when replacing EnvHttpProxyAgent dispatcher", () => {
172+vi.mocked(hasEnvHttpProxyAgentConfigured).mockReturnValue(true);
136173vi.mocked(resolveEnvHttpProxyAgentOptions).mockReturnValue({
137174httpProxy: "socks5://proxy.test:1080",
138175httpsProxy: "socks5://proxy.test:1080",
@@ -165,6 +202,8 @@ describe("ensureGlobalUndiciStreamTimeouts", () => {
165202166203it("is idempotent for unchanged dispatcher kind and network policy", () => {
167204getDefaultAutoSelectFamily.mockReturnValue(true);
205+vi.mocked(hasEnvHttpProxyAgentConfigured).mockReturnValue(true);
206+setCurrentDispatcher(new EnvHttpProxyAgent());
168207169208ensureGlobalUndiciStreamTimeouts();
170209ensureGlobalUndiciStreamTimeouts();
@@ -175,24 +214,26 @@ describe("ensureGlobalUndiciStreamTimeouts", () => {
175214it("does not lower global stream timeouts below the default floor", () => {
176215ensureGlobalUndiciStreamTimeouts({ timeoutMs: 15_000 });
177216178-expect(setGlobalDispatcher).toHaveBeenCalledTimes(1);
179-const next = getCurrentDispatcher() as { options?: Record<string, unknown> };
180-expect(next.options?.bodyTimeout).toBe(DEFAULT_UNDICI_STREAM_TIMEOUT_MS);
181-expect(next.options?.headersTimeout).toBe(DEFAULT_UNDICI_STREAM_TIMEOUT_MS);
217+expect(loadUndiciGlobalDispatcherDeps).not.toHaveBeenCalled();
218+expect(setGlobalDispatcher).not.toHaveBeenCalled();
219+expect(undiciGlobalDispatcherModule._globalUndiciStreamTimeoutMs).toBe(
220+DEFAULT_UNDICI_STREAM_TIMEOUT_MS,
221+);
182222});
183223184224it("honors explicit global stream timeouts above the default floor", () => {
185225const timeoutMs = DEFAULT_UNDICI_STREAM_TIMEOUT_MS + 1_000;
186226187227ensureGlobalUndiciStreamTimeouts({ timeoutMs });
188228189-expect(setGlobalDispatcher).toHaveBeenCalledTimes(1);
190-const next = getCurrentDispatcher() as { options?: Record<string, unknown> };
191-expect(next.options?.bodyTimeout).toBe(timeoutMs);
192-expect(next.options?.headersTimeout).toBe(timeoutMs);
229+expect(loadUndiciGlobalDispatcherDeps).not.toHaveBeenCalled();
230+expect(setGlobalDispatcher).not.toHaveBeenCalled();
231+expect(undiciGlobalDispatcherModule._globalUndiciStreamTimeoutMs).toBe(timeoutMs);
193232});
194233195234it("re-applies when autoSelectFamily decision changes", () => {
235+vi.mocked(hasEnvHttpProxyAgentConfigured).mockReturnValue(true);
236+setCurrentDispatcher(new EnvHttpProxyAgent());
196237getDefaultAutoSelectFamily.mockReturnValue(true);
197238ensureGlobalUndiciStreamTimeouts();
198239@@ -210,12 +251,14 @@ describe("ensureGlobalUndiciStreamTimeouts", () => {
210251it("disables autoSelectFamily on WSL2 to avoid IPv6 connectivity issues", () => {
211252getDefaultAutoSelectFamily.mockReturnValue(true);
212253vi.mocked(isWSL2Sync).mockReturnValue(true);
254+vi.mocked(hasEnvHttpProxyAgentConfigured).mockReturnValue(true);
255+setCurrentDispatcher(new EnvHttpProxyAgent());
213256214257ensureGlobalUndiciStreamTimeouts();
215258216259expect(setGlobalDispatcher).toHaveBeenCalledTimes(1);
217260const next = getCurrentDispatcher() as { options?: Record<string, unknown> };
218-expect(next).toBeInstanceOf(Agent);
261+expect(next).toBeInstanceOf(EnvHttpProxyAgent);
219262expect(next.options?.connect).toEqual({
220263autoSelectFamily: false,
221264autoSelectFamilyAttemptTimeout: 300,
@@ -313,11 +356,26 @@ describe("forceResetGlobalDispatcher", () => {
313356vi.mocked(resolveEnvHttpProxyAgentOptions).mockReturnValue(undefined);
314357});
315358316-it("replaces an EnvHttpProxyAgent with a direct Agent when proxy env is cleared", () => {
359+it("does not import undici when proxy env is cleared", () => {
317360setCurrentDispatcher(new EnvHttpProxyAgent());
318361319362forceResetGlobalDispatcher();
320363364+expect(loadUndiciGlobalDispatcherDeps).not.toHaveBeenCalled();
365+expect(setGlobalDispatcher).not.toHaveBeenCalled();
366+});
367+368+it("restores a direct Agent when clearing a proxy dispatcher installed by OpenClaw", () => {
369+vi.mocked(hasEnvHttpProxyAgentConfigured).mockReturnValue(true);
370+ensureGlobalUndiciEnvProxyDispatcher();
371+expect(getCurrentDispatcher()).toBeInstanceOf(EnvHttpProxyAgent);
372+373+vi.clearAllMocks();
374+vi.mocked(hasEnvHttpProxyAgentConfigured).mockReturnValue(false);
375+376+forceResetGlobalDispatcher();
377+378+expect(loadUndiciGlobalDispatcherDeps).toHaveBeenCalledTimes(1);
321379expect(setGlobalDispatcher).toHaveBeenCalledTimes(1);
322380expect(getCurrentDispatcher()).toBeInstanceOf(Agent);
323381});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。