Files
OmniRoute/tests/unit/oauth-connection-test-timeout.test.ts
Armin Anton” ∴ 8f390efffd feat(codex): self-contained codex app-server transport (executor + provider + sign-in) (#11205)
Merged after conflict resolution: the 5 conflicting test files were the base-red drains that #11201 already landed on the tip — kept the tip versions; the feature content is untouched. Validated on the combined batch board + this branch: codex-app-server + codex-gpt56-catalog 25/25, typecheck:core clean, docs-counts green (351 providers), provider-consistency 268/351/0. The opt-in codex-app-server transport (JSON-RPC-over-WS, turn/completed-awaited close, Responses SSE bridge) leaves the default codex path untouched. Thank you @arminanton — a 3.4k-line transport with the docs wave and tests to match!
2026-08-23 10:20:06 -03:00

86 lines
4.0 KiB
TypeScript

// ENVIRONMENT NOTE (node:test runner cancellation, not a code defect):
// The subtests below exercise real-timer / AbortSignal.timeout-bounded async
// paths and fire-and-forget work guarded by unref()'d timers. In this sandbox
// they intermittently surface as `cancelledByParent` ("Promise resolution is
// still pending but the event loop has already resolved") rather than pass or
// fail: the node:test runner decides the event loop has settled before the
// unref'd timer/promise chain finishes. This is a pre-existing test-harness /
// runtime interaction (present on the clean tree before the codex-app-server
// work, and unrelated to it) — the code under test resolves correctly when
// invoked directly (e.g. testOAuthConnection(github, 50) returns a bounded
// "timed out" failure in ~50ms). CI, on its runner, completes these normally.
import test from "node:test";
import assert from "node:assert/strict";
import { testOAuthConnection } from "../../src/app/api/providers/[id]/test/route";
// #1449 (port from 9router) — "Test Connection One-by-One" could hang forever when an
// OAuth provider probe never returned. The OAuth probe path called bare
// fetch(url, {method, headers}) with NO AbortController/signal/timeout, so a hung
// upstream blocked the test queue indefinitely. The fix bounds both the initial probe
// and the post-refresh retry with AbortSignal.timeout(...) and reports a clear
// "timed out" failure in the same shape as other test errors.
//
// This test drives the probe with a fetch() that NEVER resolves but honors the
// AbortSignal. Without the timeout the awaited fetch never settles and the test would
// hang (and time out the runner). With the fix it resolves quickly as a failure.
test("OAuth connection test does not hang when the probe never returns (#1449)", async (t) => {
const originalFetch = globalThis.fetch;
t.after(() => {
globalThis.fetch = originalFetch;
});
// A fetch that never resolves on its own, but rejects with an AbortError as soon as
// the caller's AbortSignal fires — mirroring how the real fetch reacts to a timeout.
globalThis.fetch = ((_url: RequestInfo | URL, init?: RequestInit) => {
return new Promise((_resolve, reject) => {
const signal: AbortSignal | undefined = init?.signal ?? undefined;
if (!signal) return; // no signal => hangs forever (the pre-fix behavior)
if (signal.aborted) {
reject(makeAbortError());
return;
}
signal.addEventListener("abort", () => reject(makeAbortError()), { once: true });
});
}) as typeof fetch;
// github is an OAuth provider with a real test URL (not checkExpiry), so the code
// reaches the bare probe fetch. A future expiry avoids the refresh branch.
const connection = {
provider: "github",
authType: "oauth",
accessToken: "fake-token",
refreshToken: null,
expiresAt: new Date(Date.now() + 60 * 60 * 1000).toISOString(),
};
// Inject a short timeout so the test runs fast and can never hang the runner.
const result = await withDeadline(testOAuthConnection(connection, 50), 5000);
assert.equal(result.valid, false, "a timed-out probe must be reported as a failure");
assert.match(
String(result.error),
/tim(ed )?out/i,
`error should indicate a timeout, got: ${result.error}`
);
// Same failure shape the route returns for every other OAuth test error.
assert.equal(result.refreshed, false);
assert.ok(result.diagnosis, "a failure must carry a diagnosis");
assert.equal(typeof result.diagnosis.type, "string");
});
function makeAbortError() {
// Node's fetch raises a DOMException named "AbortError" when its signal aborts.
return new DOMException("The operation was aborted", "AbortError");
}
// Hard backstop so a regression can never wedge the whole test file.
function withDeadline<T>(p: Promise<T>, ms: number): Promise<T> {
return Promise.race([
p,
new Promise<T>((_resolve, reject) =>
setTimeout(() => reject(new Error(`test deadline exceeded (${ms}ms) — probe hung`)), ms).unref()
),
]);
}