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:
parent
e89ea4cf19
commit
2710c98681
1 changed files with 40 additions and 12 deletions
|
|
@ -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 () => {
|
||||
|
|
|
|||
Loading…
Reference in a new issue