


























1+// Google tests cover oauth.http proxy-mode selection for the Gemini CLI OAuth
2+// token-exchange/identity calls (issue openclaw#46184).
3+import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
4+import { TOKEN_URL } from "./oauth.shared.js";
5+6+const fetchWithSsrFGuardMock = vi.fn();
7+8+vi.mock("openclaw/plugin-sdk/ssrf-runtime", async () => {
9+const actual = await vi.importActual<typeof import("openclaw/plugin-sdk/ssrf-runtime")>(
10+"openclaw/plugin-sdk/ssrf-runtime",
11+);
12+return {
13+ ...actual,
14+fetchWithSsrFGuard: (params: unknown) => fetchWithSsrFGuardMock(params),
15+};
16+});
17+18+const { fetchWithTimeout } = await import("./oauth.http.js");
19+20+const PROXY_ENV_KEYS = [
21+"HTTP_PROXY",
22+"HTTPS_PROXY",
23+"ALL_PROXY",
24+"NO_PROXY",
25+"http_proxy",
26+"https_proxy",
27+"all_proxy",
28+"no_proxy",
29+] as const;
30+31+const savedEnv = new Map<string, string | undefined>();
32+33+type ProxyEnvOverrides = {
34+HTTP_PROXY?: string;
35+HTTPS_PROXY?: string;
36+ALL_PROXY?: string;
37+NO_PROXY?: string;
38+};
39+40+function setProxyEnv(values: ProxyEnvOverrides): void {
41+for (const key of PROXY_ENV_KEYS) {
42+delete process.env[key];
43+}
44+if (values.HTTP_PROXY !== undefined) {
45+process.env.HTTP_PROXY = values.HTTP_PROXY;
46+}
47+if (values.HTTPS_PROXY !== undefined) {
48+process.env.HTTPS_PROXY = values.HTTPS_PROXY;
49+}
50+if (values.ALL_PROXY !== undefined) {
51+process.env.ALL_PROXY = values.ALL_PROXY;
52+}
53+if (values.NO_PROXY !== undefined) {
54+process.env.NO_PROXY = values.NO_PROXY;
55+}
56+}
57+58+function lastGuardedOptions(): Record<string, unknown> {
59+const call = fetchWithSsrFGuardMock.mock.calls.at(-1)?.[0];
60+if (!call || typeof call !== "object") {
61+throw new Error("Expected fetchWithSsrFGuard to be called");
62+}
63+return call as Record<string, unknown>;
64+}
65+66+describe("oauth.http fetchWithTimeout proxy selection", () => {
67+beforeEach(() => {
68+for (const key of PROXY_ENV_KEYS) {
69+savedEnv.set(key, process.env[key]);
70+}
71+fetchWithSsrFGuardMock.mockReset();
72+fetchWithSsrFGuardMock.mockResolvedValue({
73+response: new Response("{}", { status: 200 }),
74+finalUrl: TOKEN_URL,
75+release: async () => {},
76+});
77+});
78+79+afterEach(() => {
80+for (const [key, value] of savedEnv) {
81+if (value === undefined) {
82+delete process.env[key];
83+} else {
84+process.env[key] = value;
85+}
86+}
87+savedEnv.clear();
88+});
89+90+it("routes the Google token exchange through the env proxy when configured", async () => {
91+setProxyEnv({ HTTPS_PROXY: "http://127.0.0.1:7897", HTTP_PROXY: "http://127.0.0.1:7897" });
92+93+await fetchWithTimeout(TOKEN_URL, { method: "POST", body: "grant_type=refresh_token" });
94+95+expect(lastGuardedOptions().mode).toBe("trusted_env_proxy");
96+});
97+98+it("keeps the strict default when no proxy is configured", async () => {
99+setProxyEnv({});
100+101+await fetchWithTimeout(TOKEN_URL, { method: "POST" });
102+103+expect(lastGuardedOptions().mode).toBeUndefined();
104+});
105+106+it("keeps the strict default when NO_PROXY bypasses the target host", async () => {
107+setProxyEnv({ HTTPS_PROXY: "http://127.0.0.1:7897", NO_PROXY: "googleapis.com" });
108+109+await fetchWithTimeout(TOKEN_URL, { method: "POST" });
110+111+expect(lastGuardedOptions().mode).toBeUndefined();
112+});
113+114+it("keeps the strict default for ALL_PROXY-only environments", async () => {
115+setProxyEnv({ ALL_PROXY: "http://127.0.0.1:7897" });
116+117+await fetchWithTimeout(TOKEN_URL, { method: "POST" });
118+119+expect(lastGuardedOptions().mode).toBeUndefined();
120+});
121+});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。