diff --git a/release/public-source.json b/release/public-source.json index 6b6dc3b..9ba9d7b 100644 --- a/release/public-source.json +++ b/release/public-source.json @@ -3286,6 +3286,7 @@ "test/byok.test.mjs", "test/executable-resolution.test.ts", "test/network-deny.mjs", + "test/offline-environment.mjs", "test/public-artifact.test.mjs", "test/smoke.test.mjs", "test/source-sync.test.mjs", diff --git a/test/byok.test.mjs b/test/byok.test.mjs index cb93bf2..28943f2 100644 --- a/test/byok.test.mjs +++ b/test/byok.test.mjs @@ -15,6 +15,7 @@ import path from "node:path"; import { fileURLToPath } from "node:url"; import Database from "better-sqlite3"; import { parse as parseYaml } from "yaml"; +import { withoutProxyEnvironment } from "./offline-environment.mjs"; const cli = fileURLToPath(new URL("../dist/cli.js", import.meta.url)); // This fixture validates BYOK transport and real Runtime persistence, not model quality. @@ -152,7 +153,6 @@ test( }); const baseUrl = `http://127.0.0.1:${server.address().port}/v1`; const env = { - ...process.env, MINIMAX_DATA_DIR: dataDir, MAVIS_DATA_DIR: dataDir, MCODE_PROVIDER_API_KEY: "fixture-only-key", @@ -161,12 +161,9 @@ test( MCODE_TEST_MANAGED_OFFLINE: "1", MCODE_TEST_PROCESS_PROBE: "1", NODE_OPTIONS: `--import=${new URL("./network-deny.mjs", import.meta.url).href}`, - HTTP_PROXY: "", - HTTPS_PROXY: "", - ALL_PROXY: "", }; let commandSequence = 0; - async function run(args) { + async function run(args, environment = process.env) { const startedAt = Date.now(); const label = args.slice(0, 2).join(" "); const requestCountAtStart = requests.length; @@ -177,7 +174,7 @@ test( return new Promise((resolve, reject) => { const child = spawn(process.execPath, [cli, ...commandArgs], { cwd: dataDir, - env, + env: { ...withoutProxyEnvironment(environment), ...env }, stdio: ["ignore", "pipe", "pipe"], }); let stdout = "", @@ -247,13 +244,45 @@ test( (p) => p.kind === "custom" && p.name === "Fixture", ); assert.ok(selected?.hasApiKey); - await run([ - "provider", - "test", - selected.providerId, - "--model", - "fixture-model", - ]); + const proxyNames = [ + "HTTP_PROXY", "HTTPS_PROXY", "ALL_PROXY", "NO_PROXY", + "http_proxy", "https_proxy", "all_proxy", "no_proxy", + ]; + const cleanEnvironment = Object.fromEntries( + Object.entries(process.env).filter(([name]) => !proxyNames.includes(name)), + ); + const proxyValue = (name) => name.toLowerCase() === "no_proxy" + ? "*" + : "http://127.0.0.1:9"; + for (const [label, proxies] of [ + ["no proxy", {}], + ...proxyNames.map((name) => [name, { [name]: proxyValue(name) }]), + ["uppercase and lowercase together", Object.fromEntries( + proxyNames.map((name) => [name, proxyValue(name)]), + )], + ]) { + await t.test(`offline BYOK ignores ambient proxies: ${label}`, async () => { + const environment = Object.freeze({ ...cleanEnvironment, ...proxies }); + // NO_PROXY alone does not enable proxy mode, so check its isolation explicitly. + const isolated = withoutProxyEnvironment(environment); + for (const name of proxyNames) assert.equal(isolated[name], ""); + assert.equal(isolated.PATH, environment.PATH); + const beforeRequests = requests.length; + const managedAudit = `${networkAudit}.managed`; + const beforeManaged = readFileSync(managedAudit, "utf8").length; + await run([ + "provider", "test", selected.providerId, "--model", "fixture-model", + ], environment); + assert.ok(requests.length > beforeRequests, "The local provider must receive the request"); + assert.equal(requests[beforeRequests].body.model, "fixture-model"); + assert.match( + readFileSync(managedAudit, "utf8").slice(beforeManaged), + /https:\/\/models\.dev\/api\.json|\/mavis\/api\/v1\/models-dev\/catalog/, + ); + assert.equal(existsSync(networkAudit), false, "No outbound network attempt is allowed"); + for (const [name, value] of Object.entries(proxies)) assert.equal(environment[name], value); + }); + } const configPath = path.join(dataDir, "config.yaml"); const savedConfig = () => parseYaml(readFileSync(configPath, "utf8")); assert.equal(savedConfig().defaultModel, "minimax/MiniMax-M3"); diff --git a/test/offline-environment.mjs b/test/offline-environment.mjs new file mode 100644 index 0000000..69acacf --- /dev/null +++ b/test/offline-environment.mjs @@ -0,0 +1,15 @@ +// Proxy setup installs undici.fetch over the preloaded offline mock. +// Keep host proxy settings out of isolated CLI children without changing the parent. +export function withoutProxyEnvironment(environment = process.env) { + return { + ...environment, + HTTP_PROXY: "", + HTTPS_PROXY: "", + ALL_PROXY: "", + NO_PROXY: "", + http_proxy: "", + https_proxy: "", + all_proxy: "", + no_proxy: "", + }; +} diff --git a/test/smoke.test.mjs b/test/smoke.test.mjs index 81ae2d1..c9e641d 100644 --- a/test/smoke.test.mjs +++ b/test/smoke.test.mjs @@ -6,6 +6,7 @@ import { tmpdir } from "node:os"; import path from "node:path"; import { fileURLToPath } from "node:url"; import Database from "better-sqlite3"; +import { withoutProxyEnvironment } from "./offline-environment.mjs"; const root = fileURLToPath(new URL("../", import.meta.url)); const cli = path.join(root, "dist/cli.js"); @@ -31,17 +32,7 @@ function fixture(t, environment = process.env) { return { cwd: dataDir, env: { - ...environment, - // Proxy setup installs undici.fetch over the preloaded offline mock. - // Keep host proxy settings out of these isolated test children. - HTTP_PROXY: "", - HTTPS_PROXY: "", - ALL_PROXY: "", - NO_PROXY: "", - http_proxy: "", - https_proxy: "", - all_proxy: "", - no_proxy: "", + ...withoutProxyEnvironment(environment), MINIMAX_DATA_DIR: dataDir, MAVIS_DATA_DIR: dataDir, MCODE_TEST_NETWORK_AUDIT: audit,