Move project and host queries into native MCP with host-owned OAuth/context, while retaining memory, skills, actions and publication plugins. Add nearby-model queries and explicit chat/task/learning model defaults. Adopt Responses HTTP/SSE and native steer/queue inbox delivery, preserving scoped session ownership, pending input edits and investigation communication. Disable local-provider discovery: Server /health allowed native vLLM to probe /v1/models on port 8000. A native before/after regression prevents recurrence. Validation: 170 focused Bun tests; service/plugin typechecks; native discovery, provider-discovery and input-inbox smoke checks; no paid model inference.
77 lines
4.8 KiB
TypeScript
77 lines
4.8 KiB
TypeScript
import { describe, expect, it } from "bun:test";
|
|
import { CredentialRefreshCoordinator, runWithCredentialRefresh, bindSessionCredential, credentialRefreshCoordinator, refreshSessionCredential, releaseSessionCredential, requestWithSessionCredential } from "../../src/product/credential-refresh.js";
|
|
import { removeRuntimeSessionContext, setRuntimeSessionContext } from "../../src/runtime/sessionContext.js";
|
|
|
|
describe("credential renewal", () => {
|
|
it("requires a project before personal chats can call business APIs", async () => {
|
|
const sessionId = "personal-business-access";
|
|
let calls = 0;
|
|
setRuntimeSessionContext({ actorKey: "actor-personal", projectKey: "personal-actor-personal", sessionId,
|
|
clientSessionId: sessionId, traceId: "test" });
|
|
try {
|
|
await expect(requestWithSessionCredential(sessionId, async () => { calls++; return Response.json({}); })).rejects.toMatchObject({ status: 403, code: "PROJECT_CONTEXT_REQUIRED" });
|
|
expect(calls).toBe(0);
|
|
} finally { removeRuntimeSessionContext(sessionId); }
|
|
});
|
|
it("rejects stale replies and stops repeated requests when the renewed credential is still rejected", async () => {
|
|
const session = "rejected-credential-session";
|
|
bindSessionCredential(session, { authorization: "Bearer OLD", projectId: "p" });
|
|
let calls = 0;
|
|
let failures = 0;
|
|
const unsubscribe = credentialRefreshCoordinator.subscribe(session, (event) => {
|
|
if (event.type === "credential_refresh_required") {
|
|
expect(refreshSessionCredential(session, { authorization: "Bearer NEW", projectId: "other" }, event.requestId)).toBeFalse();
|
|
expect(refreshSessionCredential(session, { authorization: "Bearer NEW", projectId: "p" }, "stale")).toBeFalse();
|
|
refreshSessionCredential(session, { authorization: "Bearer NEW", projectId: "p" }, event.requestId);
|
|
} else if (event.type === "credential_refresh_failed") failures += 1;
|
|
});
|
|
try {
|
|
expect((await requestWithSessionCredential(session, async () => { calls += 1; return new Response(null, { status: 401 }); })).status).toBe(401);
|
|
await expect(requestWithSessionCredential(session, async () => { calls += 1; return new Response(null, { status: 200 }); })).rejects.toThrow("renewal failed");
|
|
expect(calls).toBe(2);
|
|
expect(failures).toBe(1);
|
|
} finally { unsubscribe(); releaseSessionCredential(session); }
|
|
});
|
|
it("coalesces concurrent 401 responses, resumes both calls, and retries only once", async () => {
|
|
const coordinator = new CredentialRefreshCoordinator(1000);
|
|
const old = { authorization: "Bearer TEST_OLD", projectId: "p" };
|
|
const renewed = { authorization: "Bearer TEST_NEW", projectId: "p" };
|
|
let requests = 0;
|
|
coordinator.subscribe("s", (event) => {
|
|
if (event.type === "credential_refresh_required") {
|
|
requests += 1;
|
|
queueMicrotask(() => coordinator.resolve("s", event.requestId, renewed));
|
|
}
|
|
});
|
|
const calls: string[] = [];
|
|
const execute = async (credential: typeof old) => {
|
|
calls.push(credential.authorization);
|
|
return { status: credential.authorization === old.authorization ? 401 : 200 };
|
|
};
|
|
expect((await Promise.all([runWithCredentialRefresh(coordinator, "s", old, execute), runWithCredentialRefresh(coordinator, "s", old, execute)])).map((result) => result.status)).toEqual([200, 200]);
|
|
expect(requests).toBe(1);
|
|
expect(calls).toHaveLength(4);
|
|
});
|
|
it("does not refresh 403 and fails when refresh is unavailable or cancelled", async () => {
|
|
const coordinator = new CredentialRefreshCoordinator(1000);
|
|
const credential = { authorization: "Bearer TEST", projectId: "p" };
|
|
expect((await runWithCredentialRefresh(coordinator, "s", credential, async () => ({ status: 403 }))).status).toBe(403);
|
|
await expect(runWithCredentialRefresh(coordinator, "s", credential, async () => ({ status: 401 }))).rejects.toThrow("unavailable");
|
|
coordinator.enableBackground("s");
|
|
const pending = coordinator.request("s", "access_token_expired");
|
|
coordinator.cancelSession("s");
|
|
await expect(pending).rejects.toThrow("cancelled");
|
|
});
|
|
it("renews before near-expiry execution and exposes the same pending request for reconnect", async () => {
|
|
const coordinator = new CredentialRefreshCoordinator(1000);
|
|
const old = { authorization: "Bearer OLD", projectId: "p", expiresAt: new Date(Date.now() + 1000).toISOString() };
|
|
coordinator.subscribe("s", (event) => {
|
|
if (event.type === "credential_refresh_required") {
|
|
expect(coordinator.getPendingEvent("s")?.requestId).toBe(event.requestId);
|
|
coordinator.resolve("s", event.requestId, { authorization: "Bearer NEW", projectId: "p" });
|
|
}
|
|
});
|
|
expect(await runWithCredentialRefresh(coordinator, "s", old, async (credential) => ({ status: 200, authorization: credential.authorization }))).toMatchObject({ authorization: "Bearer NEW" });
|
|
});
|
|
});
|