fix: address Copilot review round 2 on #2319

Both findings are on the caller-signal handling added in round 1:

- `"signal" in init` treated a member present as `undefined` as an
  explicit override. `RequestInit` is a WebIDL dictionary, where such a
  member converts as absent — so `{ ...base, signal: undefined }`, which
  is what a spread over an options object with no signal produces, was
  clearing a Request's embedded signal instead of inheriting it. Only a
  non-`undefined` value overrides now; an explicit `null` is still the
  genuine no-signal case.

- An already-aborted caller signal rejected the race, but
  `Promise.race` still evaluated `fetchFn(...)`, so on the
  signal-dropping proxy path the OAuth request went out anyway. It now
  short-circuits before anything is constructed or sent, and the
  now-unreachable aborted branch inside the race is gone.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YFnM6RqB4nXqNysgUYmjmq
Signed-off-by: cliffhall <cliff@futurescale.com>
This commit is contained in:
cliffhall 2026-09-11 02:44:43 -04:00
commit 2710c98681

View file

@ -139,13 +139,34 @@ describe("withOAuthRequestTimeout", () => {
await expect(pending).rejects.toBe(reason);
});
it("rejects immediately when the caller's signal is already aborted", async () => {
const wrapped = withOAuthRequestTimeout(neverSettles, 60_000);
it("rejects an already-aborted signal without sending the request", async () => {
const inner = vi.fn<typeof fetch>(neverSettles);
const wrapped = withOAuthRequestTimeout(inner, 60_000);
const reason = new Error("already gone");
await expect(
wrapped(URL_UNDER_TEST, { signal: AbortSignal.abort(reason) }),
).rejects.toBe(reason);
// Racing it would still have evaluated the inner fetch, which on the
// signal-dropping proxy path means the request actually goes out.
expect(inner).not.toHaveBeenCalled();
});
it("treats an undefined init.signal as absent, keeping a Request's own", async () => {
// `RequestInit` is a WebIDL dictionary: a member present as `undefined` is
// converted as absent. `{ ...base, signal: undefined }` is what a spread
// over an options object with no signal produces.
const wrapped = withOAuthRequestTimeout(neverSettles, 60_000);
const caller = new AbortController();
const pending = wrapped(
new Request(URL_UNDER_TEST, { signal: caller.signal }),
{ signal: undefined },
);
const reason = new Error("request cancelled");
caller.abort(reason);
await expect(pending).rejects.toBe(reason);
});
it("honours the signal embedded in a Request input", async () => {