Coordinate a2amcp credential refresh
This commit is contained in:
@@ -93,3 +93,89 @@ test("refreshCredentialsIfNeeded refreshes expired Keycloak access token", async
|
||||
assert.equal((await loadCredentials())!.token, "fresh-token");
|
||||
assert.ok(calls.some((call) => call.body?.includes("grant_type=refresh_token")));
|
||||
});
|
||||
|
||||
test("refreshCredentialsIfNeeded serializes concurrent refreshes", async () => {
|
||||
let tokenCalls = 0;
|
||||
globalThis.fetch = (async (input: RequestInfo | URL) => {
|
||||
const url = String(input);
|
||||
if (url.endsWith("/.well-known/openid-configuration")) {
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
authorization_endpoint: "https://auth.example.test/auth",
|
||||
token_endpoint: "https://auth.example.test/token",
|
||||
}),
|
||||
{ status: 200, headers: { "content-type": "application/json" } },
|
||||
);
|
||||
}
|
||||
tokenCalls += 1;
|
||||
await new Promise((resolve) => setTimeout(resolve, 25));
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
access_token: `fresh-token-${tokenCalls}`,
|
||||
refresh_token: `fresh-refresh-${tokenCalls}`,
|
||||
expires_in: 3600,
|
||||
scope: "openid email mcp:invoke",
|
||||
}),
|
||||
{ status: 200, headers: { "content-type": "application/json" } },
|
||||
);
|
||||
}) as typeof fetch;
|
||||
|
||||
await saveCredentials({
|
||||
apiUrl: "https://api.example.test",
|
||||
token: "old-token",
|
||||
refreshToken: "refresh-token",
|
||||
expiresAt: 1,
|
||||
email: "dev@example.test",
|
||||
issuer: "https://auth.example.test",
|
||||
clientId: "a2acloud-cli",
|
||||
});
|
||||
|
||||
const stale = (await loadCredentials())!;
|
||||
const [first, second] = await Promise.all([
|
||||
refreshCredentialsIfNeeded(stale),
|
||||
refreshCredentialsIfNeeded({ ...stale }),
|
||||
]);
|
||||
|
||||
assert.equal(tokenCalls, 1);
|
||||
assert.equal(first.token, "fresh-token-1");
|
||||
assert.equal(second.token, "fresh-token-1");
|
||||
assert.equal((await loadCredentials())!.refreshToken, "fresh-refresh-1");
|
||||
});
|
||||
|
||||
test("refreshCredentialsIfNeeded reports stale Keycloak sessions clearly", async () => {
|
||||
globalThis.fetch = (async (input: RequestInfo | URL) => {
|
||||
const url = String(input);
|
||||
if (url.endsWith("/.well-known/openid-configuration")) {
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
authorization_endpoint: "https://auth.example.test/auth",
|
||||
token_endpoint: "https://auth.example.test/token",
|
||||
}),
|
||||
{ status: 200, headers: { "content-type": "application/json" } },
|
||||
);
|
||||
}
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
error: "invalid_grant",
|
||||
error_description: "Session doesn't have required client",
|
||||
}),
|
||||
{ status: 400, headers: { "content-type": "application/json" } },
|
||||
);
|
||||
}) as typeof fetch;
|
||||
|
||||
await saveCredentials({
|
||||
apiUrl: "https://api.example.test",
|
||||
token: "old-token",
|
||||
refreshToken: "refresh-token",
|
||||
expiresAt: 1,
|
||||
email: "dev@example.test",
|
||||
issuer: "https://auth.example.test",
|
||||
clientId: "a2acloud-cli",
|
||||
});
|
||||
|
||||
await assert.rejects(
|
||||
refreshCredentialsIfNeeded((await loadCredentials())!),
|
||||
/Login expired or revoked\. Run `a2amcp login` and retry\. .*Session doesn't have required client/,
|
||||
);
|
||||
assert.equal((await loadCredentials())!.token, "old-token");
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user