History

Merge remote-tracking branch 'origin/codex/batch21-cli-host-test-boundaries' into codex/effect-lint-integration

Rhys Sullivan committed 6 months ago

Commit bd712692 parents ad94c1d df6f3743 files · +31 −24Browse files

Unified · Split

3 files
  1. packages/hosts/mcp/src/stdio-integration.test.ts +27 −22
  2. tests/presets-reachable.test.ts +3 −2
  3. tests/tools-cli.test.ts +1 −0
packages/hosts/mcp/src/stdio-integration.test.ts+27−22
@@ -1,6 +1,7 @@
1import { describe, expect, it } from "@effect/vitest";1import { describe, expect, it } from "@effect/vitest";
2import { Client } from "@modelcontextprotocol/sdk/client/index.js";2import { Client } from "@modelcontextprotocol/sdk/client/index.js";
3import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";3import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
4import { Effect } from "effect";
4import { mkdtempSync } from "node:fs";5import { mkdtempSync } from "node:fs";
5import { tmpdir } from "node:os";6import { tmpdir } from "node:os";
6import { join, resolve } from "node:path";7import { join, resolve } from "node:path";
@@ -10,35 +11,39 @@ const cliEntry = resolve(repoRoot, "apps/cli/src/main.ts"); · 3 unmodified lines
10const testScope = resolve(repoRoot, "apps/local");11const testScope = resolve(repoRoot, "apps/local");
11 12
12describe("MCP stdio integration", () => {13describe("MCP stdio integration", () => {
13 it("execute tool returns result over stdio transport", async () => {14 it.effect("execute tool returns result over stdio transport", () =>
14 // Fresh temp dir so the test doesn't migrate against the developer's15 Effect.gen(function* () {
15 // real ~/.executor/data.db.16 // Fresh temp dir so the test doesn't migrate against the developer's
16 const dataDir = mkdtempSync(join(tmpdir(), "executor-mcp-test-"));17 // real ~/.executor/data.db.
17 18 const dataDir = mkdtempSync(join(tmpdir(), "executor-mcp-test-"));
18 const transport = new StdioClientTransport({19
19 command: "bun",20 const transport = new StdioClientTransport({
20 args: ["run", cliEntry, "mcp", "--scope", testScope],21 command: "bun",
21 env: { ...process.env, EXECUTOR_DATA_DIR: dataDir },22 args: ["run", cliEntry, "mcp", "--scope", testScope],
22 });23 env: { ...process.env, EXECUTOR_DATA_DIR: dataDir },
24 });
23 25
24 const client = new Client({ name: "test-client", version: "1.0.0" }, { capabilities: {} });26 const client = new Client({ name: "test-client", version: "1.0.0" }, { capabilities: {} });
25 27
26 await client.connect(transport);28 yield* Effect.acquireRelease(
29 Effect.promise(() => client.connect(transport)),
30 () => Effect.promise(() => transport.close()),
31 );
27 32
28 try {33 const { tools } = yield* Effect.promise(() => client.listTools());
29 const { tools } = await client.listTools();
30 expect(tools.map((t) => t.name)).toContain("execute");34 expect(tools.map((t) => t.name)).toContain("execute");
31 35
32 const result = await client.callTool({36 const result = yield* Effect.promise(() =>
33 name: "execute",37 client.callTool({
34 arguments: { code: "return 2+2" },38 name: "execute",
35 });39 arguments: { code: "return 2+2" },
40 }),
41 );
36 42
37 const text = (result.content as Array<{ type: string; text: string }>)[0]?.text;43 const text = (result.content as Array<{ type: string; text: string }>)[0]?.text;
38 expect(text).toContain("4");44 expect(text).toContain("4");
39 expect(result.isError).toBeFalsy();45 expect(result.isError).toBeFalsy();
40 } finally {46 }).pipe(Effect.scoped),
41 await transport.close();47 { timeout: 30_000 },
42 }48 );
43 }, 30_000);
44});49});
tests/presets-reachable.test.ts+3−2
@@ -62,10 +62,11 @@ describe("graphql presets are reachable endpoints", () => { · 61 unmodified lines
62 const result = yield* introspect(preset.url).pipe(62 const result = yield* introspect(preset.url).pipe(
63 Effect.provide(FetchHttpClient.layer),63 Effect.provide(FetchHttpClient.layer),
64 Effect.map((r) => ({ ok: true as const, schema: r })),64 Effect.map((r) => ({ ok: true as const, schema: r })),
65 Effect.catch((err) =>65 Effect.catchTag("GraphqlIntrospectionError", (err) =>
66 Effect.succeed({66 Effect.succeed({
67 ok: false as const,67 ok: false as const,
68 message: String(err),68 // oxlint-disable-next-line executor/no-unknown-error-message -- boundary: catchTag narrows to GraphqlIntrospectionError whose public contract includes message
69 message: err.message,
69 }),70 }),
70 ),71 ),
71 );72 );
tests/tools-cli.test.ts+1−0
@@ -34,6 +34,7 @@ describe("CLI tooling helpers", () => { · 33 unmodified lines
34 it.effect("rejects non-object JSON input", () =>34 it.effect("rejects non-object JSON input", () =>
35 Effect.gen(function* () {35 Effect.gen(function* () {
36 const error = yield* parseJsonObjectInput('[1,2,3]').pipe(Effect.flip);36 const error = yield* parseJsonObjectInput('[1,2,3]').pipe(Effect.flip);
37 // oxlint-disable-next-line executor/no-unknown-error-message -- boundary: helper contract returns a native Error for CLI input parsing
37 expect(error.message).toContain("must decode to a JSON object");38 expect(error.message).toContain("must decode to a JSON object");
38 }),39 }),
39 );40 );