146 lines · 5.2 KB · TypeScript
RawDownload
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);  });});