Files
openwork/apps/app/tests/mcp-silent-reauth.test.ts

174 lines
5.8 KiB
TypeScript

import { describe, expect, test } from "bun:test";
import {
attemptSilentMcpReauth,
isSilentReauthCandidate,
selectSilentReauthCandidates,
SILENT_REAUTH_COOLDOWN_MS,
silentReauthAttemptKey,
} from "../src/react-app/domains/connections/mcp-silent-reauth";
import type { McpServerEntry, McpStatusMap } from "../src/app/types";
const DIR = "/tmp/workspace";
function remoteOauthEntry(name: string, config: Partial<McpServerEntry["config"]> = {}): McpServerEntry {
return {
name,
config: { type: "remote", url: "https://example.com/mcp", ...config },
};
}
describe("isSilentReauthCandidate", () => {
test("accepts unhealthy remote oauth entries", () => {
const entry = remoteOauthEntry("crm");
expect(isSilentReauthCandidate(entry, { crm: { status: "needs_auth" } })).toBe(true);
expect(isSilentReauthCandidate(entry, { crm: { status: "failed", error: "Connection closed" } })).toBe(true);
});
test("rejects healthy, interactive-only, and non-oauth entries", () => {
const entry = remoteOauthEntry("crm");
expect(isSilentReauthCandidate(entry, { crm: { status: "connected" } })).toBe(false);
expect(isSilentReauthCandidate(entry, { crm: { status: "disabled" } })).toBe(false);
expect(isSilentReauthCandidate(entry, { crm: { status: "needs_client_registration", error: "register" } })).toBe(false);
expect(isSilentReauthCandidate(entry, {})).toBe(false);
const statuses: McpStatusMap = { crm: { status: "needs_auth" } };
expect(isSilentReauthCandidate(remoteOauthEntry("crm", { oauth: false }), statuses)).toBe(false);
expect(
isSilentReauthCandidate(remoteOauthEntry("crm", { headers: { Authorization: "Bearer x" } }), statuses),
).toBe(false);
expect(isSilentReauthCandidate(remoteOauthEntry("crm", { enabled: false }), statuses)).toBe(false);
expect(
isSilentReauthCandidate({ name: "crm", config: { type: "local", command: ["mcp"] } }, statuses),
).toBe(false);
});
});
describe("selectSilentReauthCandidates", () => {
test("applies the cooldown per entry and resets it on recovery", () => {
const attempts = new Map<string, number>();
const servers = [remoteOauthEntry("crm")];
const unhealthy: McpStatusMap = { crm: { status: "needs_auth" } };
expect(
selectSilentReauthCandidates({ directory: DIR, servers, statuses: unhealthy, now: 1_000, attempts }),
).toEqual(["crm"]);
attempts.set(silentReauthAttemptKey(DIR, "crm"), 1_000);
expect(
selectSilentReauthCandidates({ directory: DIR, servers, statuses: unhealthy, now: 2_000, attempts }),
).toEqual([]);
expect(
selectSilentReauthCandidates({
directory: DIR,
servers,
statuses: unhealthy,
now: 1_000 + SILENT_REAUTH_COOLDOWN_MS,
attempts,
}),
).toEqual(["crm"]);
// A connected pass clears the cooldown so the next episode retries
// immediately.
selectSilentReauthCandidates({
directory: DIR,
servers,
statuses: { crm: { status: "connected" } },
now: 2_000,
attempts,
});
expect(attempts.has(silentReauthAttemptKey(DIR, "crm"))).toBe(false);
expect(
selectSilentReauthCandidates({ directory: DIR, servers, statuses: unhealthy, now: 3_000, attempts }),
).toEqual(["crm"]);
});
test("scopes cooldowns by directory", () => {
const attempts = new Map<string, number>([[silentReauthAttemptKey("/other", "crm"), 1_000]]);
expect(
selectSilentReauthCandidates({
directory: DIR,
servers: [remoteOauthEntry("crm")],
statuses: { crm: { status: "needs_auth" } },
now: 1_001,
attempts,
}),
).toEqual(["crm"]);
});
});
describe("attemptSilentMcpReauth", () => {
test("connects each eligible entry once and reports the attempt", async () => {
const connected: string[] = [];
const client = {
mcp: {
connect: async ({ name }: { name: string; directory?: string }) => {
connected.push(name);
return true;
},
},
};
const attempts = new Map<string, number>();
const servers = [
remoteOauthEntry("crm"),
remoteOauthEntry("header-authed", { headers: { Authorization: "Bearer x" }, oauth: false }),
remoteOauthEntry("healthy"),
];
const statuses: McpStatusMap = {
crm: { status: "needs_auth" },
"header-authed": { status: "needs_auth" },
healthy: { status: "connected" },
};
await expect(
attemptSilentMcpReauth({ client, directory: DIR, servers, statuses, now: 1_000, attempts }),
).resolves.toBe(true);
expect(connected).toEqual(["crm"]);
// Second pass inside the cooldown window is a no-op.
await expect(
attemptSilentMcpReauth({ client, directory: DIR, servers, statuses, now: 2_000, attempts }),
).resolves.toBe(false);
expect(connected).toEqual(["crm"]);
});
test("swallows connect failures and still records the attempt", async () => {
const client = {
mcp: {
connect: async () => {
throw new Error("engine unavailable");
},
},
};
const attempts = new Map<string, number>();
await expect(
attemptSilentMcpReauth({
client,
directory: DIR,
servers: [remoteOauthEntry("crm")],
statuses: { crm: { status: "failed", error: "Connection closed" } },
now: 1_000,
attempts,
}),
).resolves.toBe(true);
expect(attempts.get(silentReauthAttemptKey(DIR, "crm"))).toBe(1_000);
});
test("returns false for a blank directory", async () => {
const client = {
mcp: {
connect: async () => true,
},
};
await expect(
attemptSilentMcpReauth({
client,
directory: " ",
servers: [remoteOauthEntry("crm")],
statuses: { crm: { status: "needs_auth" } },
attempts: new Map(),
}),
).resolves.toBe(false);
});
});