test(chatgpt): await current reconnect completion deterministically

This commit is contained in:
amosblomqvist
2026-10-24 20:23:42 +03:00
parent 74b0c65843
commit c549494488
+42 -6
View File
@@ -269,7 +269,7 @@ test("revocation endpoint remains restricted to the exact secure issuer", () =>
assert(!allowedAuthEndpoint("https://auth.openai.com@evil.invalid/revoke")); assert(!allowedAuthEndpoint("https://auth.openai.com@evil.invalid/revoke"));
}); });
test("invalid rotating replacement is durably revocation-only; verified mapping and prior validated ID survive restart", async () => { for (const holdVerification of [false, true]) test(`invalid rotating replacement is durably revocation-only; verified mapping and prior validated ID survive restart${holdVerification ? " (held JWKS regression)" : ""}`, { timeout: 10000 }, async () => {
const f = await loggedIn({ expires: 1, token: async (form, url) => form.get("grant_type") === "refresh_token" const f = await loggedIn({ expires: 1, token: async (form, url) => form.get("grant_type") === "refresh_token"
? Response.json({ access_token: "never-save-this-access", refresh_token: "likely-latest-revoke-handle", id_token: "invalid.signature.jwt", token_type: "Bearer", expires_in: 3600 }) ? Response.json({ access_token: "never-save-this-access", refresh_token: "likely-latest-revoke-handle", id_token: "invalid.signature.jwt", token_type: "Bearer", expires_in: 3600 })
: Response.json({ access_token: "old-verified-access", refresh_token: "old-refresh", id_token: await signed(form.get("client_id")!, url.searchParams.get("nonce")!), token_type: "Bearer", expires_in: 1, scope: SCOPES.join(" ") }) }); : Response.json({ access_token: "old-verified-access", refresh_token: "old-refresh", id_token: await signed(form.get("client_id")!, url.searchParams.get("nonce")!), token_type: "Bearer", expires_in: 1, scope: SCOPES.join(" ") }) });
@@ -281,7 +281,22 @@ test("invalid rotating replacement is durably revocation-only; verified mapping
for (const field of ["id", "issuer", "subject", "email", "clientId"]) assert.equal(restricted[field], before[field]); for (const field of ["id", "issuer", "subject", "email", "clientId"]) assert.equal(restricted[field], before[field]);
assert.deepEqual(restricted.tokens, { ...before.tokens, refreshToken: "likely-latest-revoke-handle" }); assert.deepEqual(restricted.tokens, { ...before.tokens, refreshToken: "likely-latest-revoke-handle" });
assert(!JSON.stringify(f.saved).includes("never-save-this-access")); assert(!JSON.stringify(f.saved).includes("never-save-this-access"));
const restarted = fixture({ state: f.saved }); const reconnectComplete = deferred<any>();
const verificationEntered = deferred(), verificationRelease = deferred();
let reconnectArmed = false, sawPending = false, completed = false;
const restarted = fixture({ state: f.saved,
change: (push) => {
if (!reconnectArmed) return;
if (push.status.pending) sawPending = true;
else if (sawPending) { completed = true; reconnectComplete.resolve(push.status); }
},
provider: async (url) => {
if (holdVerification && reconnectArmed && url.endsWith("jwks.json")) {
verificationEntered.resolve(); await verificationRelease.promise;
}
return undefined;
},
});
try { try {
await restarted.service.status(); await restarted.service.status();
await restarted.service.select(restricted.id); await restarted.service.select(restricted.id);
@@ -293,11 +308,32 @@ test("invalid rotating replacement is durably revocation-only; verified mapping
await restarted.service.logout(restricted.id); await restarted.service.logout(restricted.id);
assert.equal(restarted.requests.find((r) => r.form?.has("token_type_hint"))!.form!.get("token"), "likely-latest-revoke-handle"); assert.equal(restarted.requests.find((r) => r.form?.has("token_type_hint"))!.form!.get("token"), "likely-latest-revoke-handle");
assert.equal(restarted.saved.registrations[0].reconnectRequired, true); assert.equal(restarted.saved.registrations[0].reconnectRequired, true);
await restarted.service.login(restricted.id); await restarted.callback(); await restarted.changed.promise; // Earlier select/consent/logout notifications have already settled changed.
// Wait for callback's asynchronous durable activation, not the prior status push. // Arm before this login and await its pending -> terminal transition instead.
for (let i = 0; i < 30 && restarted.saved.registrations[0].reconnectRequired; i++) await new Promise((resolve) => setImmediate(resolve)); reconnectArmed = true;
await restarted.service.login(restricted.id);
assert.equal(sawPending, true);
await restarted.callback(); // HTTP acknowledgement precedes verification/save.
if (holdVerification) {
await verificationEntered.promise;
assert.equal(completed, false, "the current attempt cannot complete before identity verification");
const held = await restarted.service.status();
assert.equal(held.pending, true); assert.equal(held.error, null);
assert.equal(held.accounts[0].connected, false); assert.equal(held.accounts[0].planEnabled, false);
assert.equal(restarted.saved.registrations[0].reconnectRequired, true);
assert.deepEqual((await restarted.service.models()).models, []);
const expected = { expectedAccountId: restricted.id, expectedAuthorityRevision: restarted.service.stamp().authorityRevision, selectedModelId: "gpt-5" };
await assert.rejects(restarted.rawLease(false, expected), /disconnected/);
await assert.rejects(restarted.rawLease(true, expected), /disconnected/);
assert.equal(completed, false, "status/catalog/lease checks cannot settle the pending attempt");
verificationRelease.resolve();
}
const terminal = await reconnectComplete.promise;
assert.equal(terminal.pending, false); assert.equal(terminal.error, null);
assert.equal(terminal.activeId, restricted.id);
assert.equal(terminal.accounts[0].connected, true);
assert.equal(restarted.saved.registrations[0].reconnectRequired, undefined); assert.equal(restarted.saved.registrations[0].reconnectRequired, undefined);
} finally { restarted.service.close(); } } finally { verificationRelease.resolve(); restarted.service.close(); }
} finally { f.service.close(); } } finally { f.service.close(); }
}); });