import { beforeEach, describe, expect, it, vi } from "@effect/vitest";import { Effect, Layer } from "effect"; import { authenticated, McpAuthProvider, unauthorized, type Principal,} from "@executor-js/host-mcp"; import { cloudDeadSessionCacheForTest, makeCloudMcpAgentHandler } from "./agent-handler"; const principal: Principal = { accountId: "acct_test", organizationId: "org_test", organizationName: "Test Org", email: "test@example.com", name: "Test", avatarUrl: null, roles: ["member"],}; const AuthProviderLive = Layer.succeed(McpAuthProvider)({ discoveryRoutes: [], resourceMetadataUrl: (request) => new URL("/.well-known/mcp", request.url).toString(), authenticate: (request) => Effect.succeed( request.headers.has("authorization") ? authenticated(principal) : unauthorized(), ),}); const requestFor = (method: "GET" | "POST", sessionId: string, authenticated = true): Request => new Request("https://executor.test/mcp", { method, headers: { ...(authenticated ? { authorization: "Bearer test" } : {}), "mcp-session-id": sessionId, ...(method === "POST" ? { "content-type": "application/json" } : {}), }, ...(method === "POST" ? { body: JSON.stringify({ jsonrpc: "2.0", id: 1, method: "tools/list", params: {} }), } : {}), }); const makeHarness = (owner: "not_found" | "terminated" = "not_found") => { const ownerChecks = { count: 0 }; const traces = { count: 0 }; const stub = { validateMcpSessionOwner: async () => { ownerChecks.count += 1; return owner; }, }; const namespace = { idFromString: (sessionId: string) => sessionId, get: () => stub, }; // oxlint-disable-next-line executor/no-double-cast -- test boundary: the handler only reads the MCP_SESSION namespace in these legacy dead-session cases const env = { MCP_SESSION: namespace } as unknown as Env; // oxlint-disable-next-line executor/no-double-cast -- test boundary: no ExecutionContext capability is used before the dead-session response const ctx = {} as unknown as ExecutionContext; const handler = makeCloudMcpAgentHandler({ authProvider: AuthProviderLive, makeModernServerBuilder: () => ({ build: () => Effect.die("unused modern builder") }), traceRequest: async (request, _env, _ctx, handle) => { traces.count += 1; return handle(request); }, }); return { ctx, env, handler, ownerChecks, traces };}; describe("cloud MCP dead-session negative cache", () => { beforeEach(() => { vi.useRealTimers(); cloudDeadSessionCacheForTest.clear(); }); it.each([ { method: "GET" as const, status: 405 }, { method: "POST" as const, status: 404 }, ])("serves a repeated $method without another DO lookup", async ({ method, status }) => { const { ctx, env, handler, ownerChecks, traces } = makeHarness(); const sessionId = `dead-${method.toLowerCase()}`; const first = await handler(requestFor(method, sessionId), env, ctx); const second = await handler(requestFor(method, sessionId), env, ctx); expect(first.status).toBe(status); expect(second.status).toBe(status); expect(ownerChecks.count).toBe(1); expect(traces.count).toBe(1); }); it("keeps authentication ahead of cached session existence", async () => { const { ctx, env, handler, ownerChecks, traces } = makeHarness(); const sessionId = "dead-auth"; expect((await handler(requestFor("GET", sessionId), env, ctx)).status).toBe(405); const unauthenticated = await handler(requestFor("GET", sessionId, false), env, ctx); expect(unauthenticated.status).toBe(401); expect(ownerChecks.count).toBe(1); expect(traces.count).toBe(1); }); it("preserves the terminated-session response on a cached hit", async () => { const { ctx, env, handler, ownerChecks, traces } = makeHarness("terminated"); const sessionId = "dead-terminated"; const first = await handler(requestFor("POST", sessionId), env, ctx); const second = await handler(requestFor("POST", sessionId), env, ctx); expect(first.status).toBe(404); expect(second.status).toBe(404); expect(await second.text()).toBe(await first.text()); expect(ownerChecks.count).toBe(1); expect(traces.count).toBe(1); }); it("consults the DO again after five minutes", async () => { vi.useFakeTimers(); vi.setSystemTime(0); const { ctx, env, handler, ownerChecks } = makeHarness(); const sessionId = "dead-expiry"; expect((await handler(requestFor("GET", sessionId), env, ctx)).status).toBe(405); vi.advanceTimersByTime(5 * 60 * 1_000 + 1); expect((await handler(requestFor("GET", sessionId), env, ctx)).status).toBe(405); expect(ownerChecks.count).toBe(2); }); it("evicts the oldest entry without exceeding 4,096 sessions", () => { for (let index = 0; index <= 4_096; index += 1) { cloudDeadSessionCacheForTest.remember(`dead-${index}`, 0); } expect(cloudDeadSessionCacheForTest.size()).toBe(4_096); expect(cloudDeadSessionCacheForTest.has("dead-0", 1)).toBe(false); expect(cloudDeadSessionCacheForTest.has("dead-1", 1)).toBe(true); expect(cloudDeadSessionCacheForTest.has("dead-4096", 1)).toBe(true); });});
import { beforeEach, describe, expect, it, vi } from "@effect/vitest";
import { Effect, Layer } from "effect";
import {
authenticated,
McpAuthProvider,
unauthorized,
type Principal,
} from "@executor-js/host-mcp";
import { cloudDeadSessionCacheForTest, makeCloudMcpAgentHandler } from "./agent-handler";
const principal: Principal = {
accountId: "acct_test",
organizationId: "org_test",
organizationName: "Test Org",
email: "test@example.com",
name: "Test",
avatarUrl: null,
roles: ["member"],
};
const AuthProviderLive = Layer.succeed(McpAuthProvider)({
discoveryRoutes: [],
resourceMetadataUrl: (request) => new URL("/.well-known/mcp", request.url).toString(),
authenticate: (request) =>
Effect.succeed(
request.headers.has("authorization") ? authenticated(principal) : unauthorized(),
),
});
const requestFor = (method: "GET" | "POST", sessionId: string, authenticated = true): Request =>
new Request("https://executor.test/mcp", {
method,
headers: {
...(authenticated ? { authorization: "Bearer test" } : {}),
"mcp-session-id": sessionId,
...(method === "POST" ? { "content-type": "application/json" } : {}),
},
...(method === "POST"
? {
body: JSON.stringify({ jsonrpc: "2.0", id: 1, method: "tools/list", params: {} }),
}
: {}),
});
const makeHarness = (owner: "not_found" | "terminated" = "not_found") => {
const ownerChecks = { count: 0 };
const traces = { count: 0 };
const stub = {
validateMcpSessionOwner: async () => {
ownerChecks.count += 1;
return owner;
},
};
const namespace = {
idFromString: (sessionId: string) => sessionId,
get: () => stub,
};
// oxlint-disable-next-line executor/no-double-cast -- test boundary: the handler only reads the MCP_SESSION namespace in these legacy dead-session cases
const env = { MCP_SESSION: namespace } as unknown as Env;
// oxlint-disable-next-line executor/no-double-cast -- test boundary: no ExecutionContext capability is used before the dead-session response
const ctx = {} as unknown as ExecutionContext;
const handler = makeCloudMcpAgentHandler({
authProvider: AuthProviderLive,
makeModernServerBuilder: () => ({ build: () => Effect.die("unused modern builder") }),
traceRequest: async (request, _env, _ctx, handle) => {
traces.count += 1;
return handle(request);
},
});
return { ctx, env, handler, ownerChecks, traces };
};
describe("cloud MCP dead-session negative cache", () => {
beforeEach(() => {
vi.useRealTimers();
cloudDeadSessionCacheForTest.clear();
});
it.each([
{ method: "GET" as const, status: 405 },
{ method: "POST" as const, status: 404 },
])("serves a repeated $method without another DO lookup", async ({ method, status }) => {
const { ctx, env, handler, ownerChecks, traces } = makeHarness();
const sessionId = `dead-${method.toLowerCase()}`;
const first = await handler(requestFor(method, sessionId), env, ctx);
const second = await handler(requestFor(method, sessionId), env, ctx);
expect(first.status).toBe(status);
expect(second.status).toBe(status);
expect(ownerChecks.count).toBe(1);
expect(traces.count).toBe(1);
});
it("keeps authentication ahead of cached session existence", async () => {
const { ctx, env, handler, ownerChecks, traces } = makeHarness();
const sessionId = "dead-auth";
expect((await handler(requestFor("GET", sessionId), env, ctx)).status).toBe(405);
const unauthenticated = await handler(requestFor("GET", sessionId, false), env, ctx);
expect(unauthenticated.status).toBe(401);
expect(ownerChecks.count).toBe(1);
expect(traces.count).toBe(1);
});
it("preserves the terminated-session response on a cached hit", async () => {
const { ctx, env, handler, ownerChecks, traces } = makeHarness("terminated");
const sessionId = "dead-terminated";
const first = await handler(requestFor("POST", sessionId), env, ctx);
const second = await handler(requestFor("POST", sessionId), env, ctx);
expect(first.status).toBe(404);
expect(second.status).toBe(404);
expect(await second.text()).toBe(await first.text());
expect(ownerChecks.count).toBe(1);
expect(traces.count).toBe(1);
});
it("consults the DO again after five minutes", async () => {
vi.useFakeTimers();
vi.setSystemTime(0);
const { ctx, env, handler, ownerChecks } = makeHarness();
const sessionId = "dead-expiry";
expect((await handler(requestFor("GET", sessionId), env, ctx)).status).toBe(405);
vi.advanceTimersByTime(5 * 60 * 1_000 + 1);
expect((await handler(requestFor("GET", sessionId), env, ctx)).status).toBe(405);
expect(ownerChecks.count).toBe(2);
});
it("evicts the oldest entry without exceeding 4,096 sessions", () => {
for (let index = 0; index <= 4_096; index += 1) {
cloudDeadSessionCacheForTest.remember(`dead-${index}`, 0);
}
expect(cloudDeadSessionCacheForTest.size()).toBe(4_096);
expect(cloudDeadSessionCacheForTest.has("dead-0", 1)).toBe(false);
expect(cloudDeadSessionCacheForTest.has("dead-1", 1)).toBe(true);
expect(cloudDeadSessionCacheForTest.has("dead-4096", 1)).toBe(true);
});
});