Files
OmniRoute/tests/unit/socks-connect-timeout-e2e.test.ts
Diego Rodrigues de Sa e Souza 3b752f9d4c chore(quality): type the 55 no-explicit-any sites frozen under #11924 (#11975)
Production (open-sse/utils/socksConnectorWithFamily.ts, 4 sites): every cast was
redundant — undici's buildConnector.BuildOptions already has `timeout?: number | null`,
socks' SocksClientOptions has `timeout?: number`, and Agent.Options' `connect` /
`connectTimeout` narrow to the connector's parameter types on their own. Behaviour
unchanged; check:open-sse-typecheck stays at the frozen 5.

Tests (51 sites): the socks-timeout mocks now carry the real types — the patched
SocksClient.createConnection is typed as the static it replaces, the fake
buildConnector returns buildConnector.connector, the proxy is a SocksProxy, the
dynamic import is typed as the module it loads; the e2e suite passes a SocksProxy and
Agent.Options and no longer casts undici's fetch init (its RequestInit already has
`dispatcher`); the isFree suites narrow getCustomModels()' JSON to a declared row
shape, feed deliberately-wrong values through `unknown`, and stop casting for
zod's safeParse, which takes unknown.

The six files' suppression entries are removed: 1238 → 1232 files, 5487 → 5432
suppressed. ESLint without the suppressions file reports 0 problems on all six;
with it, no stale entry is left. The five suites pass (4, 2, 5, 4, 4).
2026-08-29 03:06:50 -03:00

90 lines
3.1 KiB
TypeScript

import { describe, it, afterEach } from "node:test";
import assert from "node:assert/strict";
import net from "node:net";
import { fetch, type Agent } from "undici";
import type { SocksProxy } from "socks";
import { createSocksDispatcherWithFamily } from "../../open-sse/utils/socksConnectorWithFamily.ts";
import { clearDispatcherCache } from "../../open-sse/utils/proxyDispatcher.ts";
async function startFakeSocks(opts: {
stallAfterGrant: boolean;
}): Promise<{ port: number; close: () => Promise<void> }> {
return new Promise((resolve) => {
const serverSockets = new Set<net.Socket>();
const server = net.createServer((socket) => {
let step = 0;
serverSockets.add(socket);
socket.on("close", () => serverSockets.delete(socket));
socket.on("data", (_data: Buffer) => {
if (step === 0) {
socket.write(Buffer.from([0x05, 0x00]));
step = 1;
return;
}
if (step === 1) {
if (!opts.stallAfterGrant) return;
socket.write(Buffer.from([0x05, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]));
step = 2;
}
});
});
server.listen(0, "127.0.0.1", () => {
const addr = server.address() as net.AddressInfo;
resolve({
port: addr.port,
close: () =>
new Promise<void>((r) => {
for (const s of serverSockets) s.destroy();
server.close(() => r());
}),
});
});
});
}
describe("stub SOCKS e2e", () => {
afterEach(() => clearDispatcherCache());
it("pre-grant stall (SOCKS timer) \u2192 error < 1000ms", async () => {
const { port, close } = await startFakeSocks({ stallAfterGrant: false });
const proxy: SocksProxy = { host: "127.0.0.1", port, type: 5 };
const dispatcher = createSocksDispatcherWithFamily(proxy, 4, {
connectTimeout: 300,
connect: {},
} as Agent.Options);
const t0 = Date.now();
await assert.rejects(() => fetch("https://example.invalid/", { dispatcher }));
assert.ok(
Date.now() - t0 < 1000,
`pre-grant stall must error < 1000ms, took ${Date.now() - t0}ms`
);
await close();
});
it("post-grant stall (TLS timer, https:// only) \u2192 error < 1500ms", async () => {
const { port, close } = await startFakeSocks({ stallAfterGrant: true });
const proxy: SocksProxy = { host: "127.0.0.1", port, type: 5 };
const dispatcher = createSocksDispatcherWithFamily(proxy, 4, {
connectTimeout: 300,
connect: {},
} as Agent.Options);
const t0 = Date.now();
let caught: unknown = null;
await assert.rejects(async () => {
try {
await fetch("https://example.invalid/", { dispatcher });
} catch (e) {
caught = e;
throw e;
}
});
const err = caught as { message?: string } | null;
// The ~1000ms wall time is connectTimeout 300ms + undici immediate/queue overhead, not the 10000ms default.
assert.ok(
Date.now() - t0 < 1500,
`post-grant stall must error < 1500ms, took ${Date.now() - t0}ms (err: ${String(err?.message ?? err).slice(0, 120)})`
);
await close();
});
});