Skip to content

Commit 503c8d5

Browse files
committed
test(oauth): drive the denial case through the flow's own callback server
The probe server raced the flow for the callback port: close() returns before the socket is released, so the flow could fall back to the no-loopback path and skip the assertion silently. Retry delivery to a deadline instead, and say so when an environment truly has no loopback listener.
1 parent 4d6defc commit 503c8d5

1 file changed

Lines changed: 25 additions & 6 deletions

File tree

packages/oauth/test/openai-codex-oauth.test.ts

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -330,14 +330,14 @@ describe('startOpenAICodexCallbackServer', () => {
330330
});
331331

332332
it('fails the whole flow with OAuthAccessDeniedError when the user denies consent', async () => {
333-
const probe = await startOpenAICodexCallbackServer('probe');
334-
const loopback = probe.loopback;
335-
probe.close();
336-
if (!loopback) return;
337-
333+
// The flow owns its callback server, so drive that one rather than probing
334+
// with a second server first: `close()` returns before the socket is
335+
// actually released, and the probe would race the flow for the port.
336+
const abort = new AbortController();
338337
let authorizeUrl: string | undefined;
339338
const pending = runOpenAICodexOAuthFlow({
340339
timeoutMs: 10_000,
340+
signal: abort.signal,
341341
openBrowser: (url) => {
342342
authorizeUrl = url;
343343
},
@@ -351,7 +351,26 @@ describe('startOpenAICodexCallbackServer', () => {
351351
const url = new URL(OPENAI_CODEX_REDIRECT_URI);
352352
url.searchParams.set('error', 'access_denied');
353353
url.searchParams.set('state', state!);
354-
await fetch(url);
354+
355+
// Reaching the callback is the whole point of this case, so retry rather
356+
// than skip on the first refusal; only an environment that never accepts a
357+
// loopback listener gets a pass, and it says so.
358+
const deadline = Date.now() + 5_000;
359+
let delivered = false;
360+
while (!delivered && Date.now() < deadline) {
361+
try {
362+
await fetch(url);
363+
delivered = true;
364+
} catch {
365+
await new Promise((resolve) => setTimeout(resolve, 50));
366+
}
367+
}
368+
if (!delivered) {
369+
abort.abort();
370+
await pending.catch(() => undefined);
371+
console.warn('skipped: no loopback listener on the OAuth callback port');
372+
return;
373+
}
355374

356375
await settled;
357376
});

0 commit comments

Comments
 (0)