fix: only let the newest OAuth state read write, and harden its tests
Copilot review round 1. Refreshes are concurrent — an `oauthComplete` can start a second read while the first is still in flight — and nothing makes them settle in order. The new catch made that visible: a slow earlier read could reject *after* a newer one succeeded and clear the fresh result. Guard both handlers with a monotonically increasing sequence so only the newest read writes. The two tests were also too weak to detect their own guards, as the review pointed out. Both started from an undefined panel, so neither could tell a correct handler from one that merely swallowed the error: - The rejection test now seeds a successful read first, then rejects an `oauthComplete` refresh and asserts the already-loaded details clear. - The post-cleanup test now reconnects with a replacement client that populates details, then rejects the stale read, and asserts the current details survive. - A third test covers the ordering guard directly. Each of the three guards has exactly one test that fails without it, verified by mutation: dropping the sequence check, dropping the clear, and dropping the `cancelled` check each break one test and no others. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GBkPzbuxgyqiz39ytmQzMB Signed-off-by: cliffhall <cliff@futurescale.com>
This commit is contained in:
parent
19a39235a2
commit
79751383da
1 changed files with 66 additions and 26 deletions
|
|
@ -405,13 +405,20 @@ export function useOAuthRecovery({
|
|||
}
|
||||
let cancelled = false;
|
||||
|
||||
// Reads are concurrent — an `oauthComplete` can start a second one while
|
||||
// the first is still in flight — and nothing makes them settle in order.
|
||||
// Only the newest read may write, so a slow earlier one cannot overwrite
|
||||
// (or, on rejection, clear) a newer result.
|
||||
let latest = 0;
|
||||
|
||||
const refresh = (): void => {
|
||||
const seq = ++latest;
|
||||
// void: a synchronous useEffect body cannot await. The chain is
|
||||
// terminated below, so the rejection is handled rather than discarded.
|
||||
void inspectorClient
|
||||
.getOAuthState()
|
||||
.then((state) => {
|
||||
if (cancelled) return;
|
||||
if (cancelled || seq !== latest) return;
|
||||
setConnectionInfoOAuthWhenConnected(
|
||||
state ? oauthDetailsFromConnectionState(state) : undefined,
|
||||
);
|
||||
|
|
@ -420,7 +427,7 @@ export function useOAuthRecovery({
|
|||
// The read failed (backend down, 401 on the API token, malformed
|
||||
// stored state). Clear rather than keep the last successful read —
|
||||
// a stale answer is indistinguishable from a fresh one in the panel.
|
||||
if (cancelled) return;
|
||||
if (cancelled || seq !== latest) return;
|
||||
setConnectionInfoOAuthWhenConnected(undefined);
|
||||
});
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in a new issue