Files
openclaw/src/infra/net/proxy-fetch.test.ts
Tak Hoffman 87876a3e36 Fix env proxy bootstrap for model traffic (#43248)
* Fix env proxy bootstrap for model traffic

* Address proxy dispatcher review followups

* Fix proxy env precedence for empty lowercase vars
2026-03-11 10:21:35 -05:00

135 lines
4.0 KiB
TypeScript

import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
const { ProxyAgent, EnvHttpProxyAgent, undiciFetch, proxyAgentSpy, envAgentSpy, getLastAgent } =
vi.hoisted(() => {
const undiciFetch = vi.fn();
const proxyAgentSpy = vi.fn();
const envAgentSpy = vi.fn();
class ProxyAgent {
static lastCreated: ProxyAgent | undefined;
proxyUrl: string;
constructor(proxyUrl: string) {
this.proxyUrl = proxyUrl;
ProxyAgent.lastCreated = this;
proxyAgentSpy(proxyUrl);
}
}
class EnvHttpProxyAgent {
static lastCreated: EnvHttpProxyAgent | undefined;
constructor() {
EnvHttpProxyAgent.lastCreated = this;
envAgentSpy();
}
}
return {
ProxyAgent,
EnvHttpProxyAgent,
undiciFetch,
proxyAgentSpy,
envAgentSpy,
getLastAgent: () => ProxyAgent.lastCreated,
};
});
vi.mock("undici", () => ({
ProxyAgent,
EnvHttpProxyAgent,
fetch: undiciFetch,
}));
import { makeProxyFetch, resolveProxyFetchFromEnv } from "./proxy-fetch.js";
describe("makeProxyFetch", () => {
beforeEach(() => vi.clearAllMocks());
it("uses undici fetch with ProxyAgent dispatcher", async () => {
const proxyUrl = "http://proxy.test:8080";
undiciFetch.mockResolvedValue({ ok: true });
const proxyFetch = makeProxyFetch(proxyUrl);
expect(proxyAgentSpy).not.toHaveBeenCalled();
await proxyFetch("https://api.example.com/v1/audio");
expect(proxyAgentSpy).toHaveBeenCalledWith(proxyUrl);
expect(undiciFetch).toHaveBeenCalledWith(
"https://api.example.com/v1/audio",
expect.objectContaining({ dispatcher: getLastAgent() }),
);
});
});
describe("resolveProxyFetchFromEnv", () => {
beforeEach(() => vi.clearAllMocks());
afterEach(() => vi.unstubAllEnvs());
it("returns undefined when no proxy env vars are set", () => {
vi.stubEnv("HTTPS_PROXY", "");
vi.stubEnv("HTTP_PROXY", "");
vi.stubEnv("https_proxy", "");
vi.stubEnv("http_proxy", "");
expect(resolveProxyFetchFromEnv()).toBeUndefined();
});
it("returns proxy fetch using EnvHttpProxyAgent when HTTPS_PROXY is set", async () => {
vi.stubEnv("HTTP_PROXY", "");
vi.stubEnv("HTTPS_PROXY", "http://proxy.test:8080");
undiciFetch.mockResolvedValue({ ok: true });
const fetchFn = resolveProxyFetchFromEnv();
expect(fetchFn).toBeDefined();
expect(envAgentSpy).toHaveBeenCalled();
await fetchFn!("https://api.example.com");
expect(undiciFetch).toHaveBeenCalledWith(
"https://api.example.com",
expect.objectContaining({ dispatcher: EnvHttpProxyAgent.lastCreated }),
);
});
it("returns proxy fetch when HTTP_PROXY is set", () => {
vi.stubEnv("HTTPS_PROXY", "");
vi.stubEnv("HTTP_PROXY", "http://fallback.test:3128");
const fetchFn = resolveProxyFetchFromEnv();
expect(fetchFn).toBeDefined();
expect(envAgentSpy).toHaveBeenCalled();
});
it("returns proxy fetch when lowercase https_proxy is set", () => {
vi.stubEnv("HTTPS_PROXY", "");
vi.stubEnv("HTTP_PROXY", "");
vi.stubEnv("http_proxy", "");
vi.stubEnv("https_proxy", "http://lower.test:1080");
const fetchFn = resolveProxyFetchFromEnv();
expect(fetchFn).toBeDefined();
expect(envAgentSpy).toHaveBeenCalled();
});
it("returns proxy fetch when lowercase http_proxy is set", () => {
vi.stubEnv("HTTPS_PROXY", "");
vi.stubEnv("HTTP_PROXY", "");
vi.stubEnv("https_proxy", "");
vi.stubEnv("http_proxy", "http://lower-http.test:1080");
const fetchFn = resolveProxyFetchFromEnv();
expect(fetchFn).toBeDefined();
expect(envAgentSpy).toHaveBeenCalled();
});
it("returns undefined when EnvHttpProxyAgent constructor throws", () => {
vi.stubEnv("HTTP_PROXY", "");
vi.stubEnv("https_proxy", "");
vi.stubEnv("http_proxy", "");
vi.stubEnv("HTTPS_PROXY", "not-a-valid-url");
envAgentSpy.mockImplementationOnce(() => {
throw new Error("Invalid URL");
});
const fetchFn = resolveProxyFetchFromEnv();
expect(fetchFn).toBeUndefined();
});
});