diff --git a/tests/fixtures/provider-outbound-e2e.ts b/tests/fixtures/provider-outbound-e2e.ts index fdad3b78383..03f7a473a5d 100644 --- a/tests/fixtures/provider-outbound-e2e.ts +++ b/tests/fixtures/provider-outbound-e2e.ts @@ -1,12 +1,38 @@ +import { mock } from "bun:test"; +import * as dns from "node:dns/promises"; import { createServer } from "node:http"; import type { AddressInfo } from "node:net"; -import { saveConfig } from "../../src/config"; -import { fetchProviderModels } from "../../src/codex/catalog/provider-fetch"; -import { providerOutboundGet } from "../../src/lib/provider-outbound"; import { PROXY_ENV_KEYS } from "../../src/lib/proxy-env"; -import { handleManagementAPI } from "../../src/server/management-api"; import type { OcxConfig } from "../../src/types"; -import { ManagementRequest as Request } from "../helpers/management-auth"; + +// Exercise real loopback HTTP transport without depending on the host resolver's +// retry policy for intentionally unresolvable names. Keep destination-policy +// real so it still classifies this child-local DNS failure itself. +const proxyHostnames = new Set([ + "proxy-only.invalid", + "connection-proxy.invalid", + "proxy-models.invalid", + "all-proxy-only.invalid", +]); +const dnsLookups: string[] = []; +const unexpectedDnsLookups: string[] = []; +mock.module("node:dns/promises", () => ({ + ...dns, + lookup: async (hostname: string) => { + dnsLookups.push(hostname); + if (!proxyHostnames.has(hostname)) unexpectedDnsLookups.push(hostname); + throw Object.assign(new Error(`getaddrinfo ENOTFOUND ${hostname}`), { + code: "ENOTFOUND", syscall: "getaddrinfo", hostname, + }); + }, +})); + +// Install the seam before loading production modules that capture lookup. +const { saveConfig } = await import("../../src/config"); +const { fetchProviderModels } = await import("../../src/codex/catalog/provider-fetch"); +const { providerOutboundGet } = await import("../../src/lib/provider-outbound"); +const { handleManagementAPI } = await import("../../src/server/management-api"); +const { ManagementRequest: Request } = await import("../helpers/management-auth"); const proxyKeys = PROXY_ENV_KEYS.flatMap(key => [key, key.toLowerCase()]); @@ -135,7 +161,12 @@ try { models: [], }, 0); + if (unexpectedDnsLookups.length > 0) { + throw new Error(`Unexpected fixture DNS lookups: ${unexpectedDnsLookups.join(", ")}`); + } + console.log(JSON.stringify({ + dnsLookups, outbound, allProxy, managementProxy, diff --git a/tests/providers/provider-outbound.test.ts b/tests/providers/provider-outbound.test.ts index df1e54f3113..f0e0ff4c91c 100644 --- a/tests/providers/provider-outbound.test.ts +++ b/tests/providers/provider-outbound.test.ts @@ -506,6 +506,9 @@ describe("provider outbound GET transport", () => { }, stdout: "pipe", stderr: "pipe", + // Terminate and reap the fixture before the existing 15s test deadline. + timeout: 12_000, + killSignal: "SIGKILL", }); try { @@ -518,6 +521,7 @@ describe("provider outbound GET transport", () => { throw new Error(`provider outbound fixture exited ${exitCode}: ${stderr.trim()}`); } const result = JSON.parse(stdout.trim()) as { + dnsLookups: string[]; outbound: { status: number; body: string }; allProxy: { status: number; body: string }; managementProxy: Record; @@ -529,6 +533,12 @@ describe("provider outbound GET transport", () => { providerRequests: string[]; }; + expect(result.dnsLookups).toEqual([ + "proxy-only.invalid", + "connection-proxy.invalid", + "proxy-models.invalid", + "all-proxy-only.invalid", + ]); expect(result.outbound).toEqual({ status: 200, body: '{"data":[{"id":"proxied-model"}]}', @@ -555,7 +565,7 @@ describe("provider outbound GET transport", () => { expect(result.providerRequests).toEqual(["/v1/models", "/v1/models", "/v1/models"]); expect(stderr).toContain("cannot be pinned locally"); } finally { - if (child.exitCode === null) child.kill(); + if (child.exitCode === null) child.kill("SIGKILL"); await child.exited; removeTreeWithRetry(childHome); }