test(chatgpt): await current reconnect completion deterministically
This commit is contained in:
@@ -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(); }
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user