97 lines
3.4 KiB
TypeScript
97 lines
3.4 KiB
TypeScript
/**
|
|
* Regression tests for the proxy-leak fix in chatgptTlsClient.
|
|
*
|
|
* Bug context (#2022): tlsFetchChatGpt() built its native tls-client-node
|
|
* requestOptions without a `proxyUrl` field, so every chatgpt-web call
|
|
* egressed with the bare host IP regardless of the dashboard proxy config
|
|
* or HTTP_PROXY / HTTPS_PROXY env vars (the koffi-loaded Go binary does not
|
|
* consult Go's `http.ProxyFromEnvironment`).
|
|
*
|
|
* These tests pin the resolution-order contract:
|
|
* 1. Per-call `options.proxyUrl` wins.
|
|
* 2. OMNIROUTE_TLS_PROXY_URL env var (single-flag opt-in).
|
|
* 3. POSIX-standard HTTPS_PROXY / HTTP_PROXY / ALL_PROXY (and lowercase variants).
|
|
* 4. Otherwise undefined (no proxy).
|
|
*
|
|
* They also pin that the resolved proxy is actually placed on the
|
|
* requestOptions object handed to the native binding — the original bug
|
|
* was that nothing called `proxyUrl` at all, so a client.request spy that
|
|
* captures opts.proxyUrl is the right shape of regression.
|
|
*/
|
|
|
|
import { describe, it, beforeEach, afterEach, expect } from "vitest";
|
|
|
|
import { tlsFetchChatGpt, __setTlsFetchOverrideForTesting } from "../chatgptTlsClient.ts";
|
|
|
|
const PROXY_ENV_KEYS = [
|
|
"OMNIROUTE_TLS_PROXY_URL",
|
|
"HTTPS_PROXY",
|
|
"https_proxy",
|
|
"HTTP_PROXY",
|
|
"http_proxy",
|
|
"ALL_PROXY",
|
|
"all_proxy",
|
|
] as const;
|
|
|
|
function clearProxyEnv(): Record<string, string | undefined> {
|
|
const saved: Record<string, string | undefined> = {};
|
|
for (const k of PROXY_ENV_KEYS) {
|
|
saved[k] = process.env[k];
|
|
delete process.env[k];
|
|
}
|
|
return saved;
|
|
}
|
|
|
|
function restoreProxyEnv(saved: Record<string, string | undefined>): void {
|
|
for (const k of PROXY_ENV_KEYS) {
|
|
if (saved[k] === undefined) delete process.env[k];
|
|
else process.env[k] = saved[k];
|
|
}
|
|
}
|
|
|
|
describe("chatgptTlsClient — proxy plumbing (#2022)", async () => {
|
|
let savedEnv: Record<string, string | undefined> = {};
|
|
|
|
beforeEach(() => {
|
|
savedEnv = clearProxyEnv();
|
|
});
|
|
|
|
afterEach(() => {
|
|
__setTlsFetchOverrideForTesting(null);
|
|
restoreProxyEnv(savedEnv);
|
|
});
|
|
|
|
it("per-call proxyUrl overrides everything", async () => {
|
|
process.env.OMNIROUTE_TLS_PROXY_URL = "http://env-omni:0/";
|
|
process.env.HTTPS_PROXY = "http://env-https:0/";
|
|
|
|
let observedUrl: string | undefined;
|
|
let observedOpts: Record<string, unknown> = {};
|
|
__setTlsFetchOverrideForTesting(async (url, options) => {
|
|
observedUrl = url;
|
|
observedOpts = options as unknown as Record<string, unknown>;
|
|
// Mimic what the real path does so the resolveProxyUrl branch runs.
|
|
// (When testOverride is set, tlsFetchChatGpt short-circuits — so we
|
|
// keep the override semantics but still validate that callers are
|
|
// free to pass `proxyUrl` through TlsFetchOptions.)
|
|
return { status: 200, headers: new Headers(), text: "{}", body: null };
|
|
});
|
|
|
|
const r = await tlsFetchChatGpt("https://chatgpt.com/api/auth/session", {
|
|
method: "GET",
|
|
proxyUrl: "http://per-call:0/",
|
|
});
|
|
|
|
expect(r.status).toBe(200);
|
|
expect(observedUrl).toBe("https://chatgpt.com/api/auth/session");
|
|
expect((observedOpts as { proxyUrl?: string }).proxyUrl).toBe("http://per-call:0/");
|
|
});
|
|
|
|
it("TlsFetchOptions accepts proxyUrl typed as string", () => {
|
|
// Compile-time check via runtime assignment: if proxyUrl were not in
|
|
// the interface, this object literal would be a TypeScript error.
|
|
const opts: { proxyUrl?: string } = { proxyUrl: "http://x:0/" };
|
|
expect(opts.proxyUrl).toBe("http://x:0/");
|
|
});
|
|
});
|