Skip to content

test: expand adapter-claude and mcp-server coverage, fix transport harness#46

Open
iliassjabali wants to merge 4 commits intomainfrom
test/adapter-claude-mcp-server
Open

test: expand adapter-claude and mcp-server coverage, fix transport harness#46
iliassjabali wants to merge 4 commits intomainfrom
test/adapter-claude-mcp-server

Conversation

@iliassjabali
Copy link
Copy Markdown
Collaborator

@iliassjabali iliassjabali commented Apr 12, 2026

Closes #24.

Summary

  • adapter-claude: 9 new tests covering repairYaml() (zero prior coverage), prompt structure (manifest wrapped in <context_manifest>, resolved $file: refs embedded as <context_file> blocks), streaming-error propagation, non-text content blocks, and $file:-as-directory edge case. 45 → 54 tests.
  • mcp-server: 2 new test files (scan.test.ts, generate.test.ts) for the previously-untested agentspec_scan and agentspec_generate tools. New index.test.ts with an inline snapshot of the full TOOLS registry (names + input schemas) to catch schema drift, plus handleRpc dispatch coverage. Adds sidecar malformed-JSON, hung-connection (timeout), and operator-mode network-error tests. 102 → 123 tests.
  • transport harness fix: 18 transport tests were broken on fresh worktrees because the harness spawned node dist/index.js without a pretest build hook. Switched to tsx src/index.ts, awaited child exit in afterEach, and isolated the exits when stdin closes test from the afterEach SIGTERM race. Fresh-worktree run (rm -rf packages/mcp-server/dist && pnpm --filter @agentspec/mcp test) now passes.
  • entry-point guard: tiny 3-line ESM "if executed as main" guard in mcp-server/src/index.ts so the module can be imported by unit tests without spawning a readline interface.

Acceptance criteria (#24)

  • adapter-claude has ≥5 unit tests covering core functions → 54
  • mcp-server has ≥5 unit tests covering tool registration and responses → 123
  • Both packages pass pnpm test

Test plan

  • pnpm --filter @agentspec/adapter-claude test → 54 passing
  • pnpm --filter @agentspec/mcp test → 123 passing
  • pnpm test (workspace) → sdk 285, sidecar 245, cli 436, adapter-claude 54, mcp-server 123, all green
  • pnpm -r typecheck → green
  • Fresh-worktree simulation: rm -rf packages/mcp-server/dist && pnpm --filter @agentspec/mcp test → 123/123

The 18 failing tests in transport.test.ts all share one root cause: the
harness spawned `node packages/mcp-server/dist/index.js`, but `dist/`
does not exist on a fresh worktree (no pretest build hook). Spawned
processes exited with MODULE_NOT_FOUND, stdio reads hung, and 17 tests
hit the 5s vitest timeout while the "exits when stdin closes" test got
exit code 1 (instead of 0) for the same reason.

Switch BIN to spawn `tsx src/index.ts` directly via the package-local
tsx binary, removing the build dependency entirely. Also:

- Await child exit in afterEach so the next test starts on a clean
  process table and isn't fighting an orphaned readline interface.
- Wait for the "running on stdio" startup banner in the
  "exits when stdin closes" test before closing stdin, so the readline
  close handler is wired up before the close event fires (tsx cold-start
  is ~1-2s slower than node + JS).
- Bump per-test timeout to 15s on every transport test to absorb the
  tsx cold-start cost without affecting other suites.

After: 18/18 transport tests pass, including from a fresh worktree
(`rm -rf packages/mcp-server/dist && pnpm --filter @agentspec/mcp test`).
Adds 9 new tests covering gaps in the existing 45-test suite (issue #24):

- repairYaml() (4 tests): happy path returns Claude's repaired YAML;
  throws when ANTHROPIC_API_KEY missing; throws when response lacks the
  agent.yaml field; system prompt mentions AgentSpec v1 schema rules and
  the user message embeds the broken YAML for context.
- generateWithClaude() prompt structure (2 tests): the user message
  wraps the manifest JSON in <context_manifest> tags AND embeds resolved
  $file: refs as <context_file> blocks with their actual content. These
  are the regression-sensitive tests where silent prompt drift breaks
  generation quality without any failed assertion downstream.
- generateWithClaude() ignores non-text content blocks in the response
  array (e.g. tool_use), parsing only the text block.
- generateWithClaude() streaming path surfaces an error thrown
  mid-stream from the async iterable.
- buildContext() silently skips $file: refs that resolve to a directory
  rather than a file (readFileSync EISDIR fall-through).

All 54 tests pass.
Closes the largest gap from issue #24: two registered MCP tools
(agentspec_scan, agentspec_generate) had zero direct test files. Also
adds a tool-registry snapshot test to catch schema drift, which is the
most common silent break for MCP clients (Claude Desktop, Cursor, etc.)
when a tool's input schema changes without anyone noticing.

New test files:

- scan.test.ts (3 tests): mocks spawnCli, asserts CLI args
  ['scan', '--dir', dir, '--dry-run'], output trimming, error
  propagation.
- generate.test.ts (4 tests): asserts CLI args with and without --out,
  JSON-wrapped success response, error propagation.
- index.test.ts (11 tests):
  - TOOLS registry snapshot guard — locks the names + input schemas of
    all 9 registered tools via toMatchInlineSnapshot. Free-form
    descriptions are stripped from the snapshot so wording tweaks
    don't churn the diff; only the contract (name, properties, required)
    is locked.
  - Per-tool invariants: object schema, properties present, required
    is an array, description non-empty.
  - handleRpc dispatch tests covering initialize, tools/list, ping,
    method-not-found (-32601), invalid-request (-32600), missing tool
    name (-32602), and tool error wrapping as isError content blocks.

To enable importing the entry-point module from a unit test, src/index.ts
now guards the startStdio/startHttp call behind an isMainEntry() check
(standard ESM "if executed as main" pattern). Without this, importing
{ TOOLS, handleRpc } in index.test.ts would spawn a readline interface
and call process.exit(0) when vitest's stdin closed, surfacing as an
unhandled error.

123 mcp-server tests pass.
…operator network errors

Adds the error-path scenarios highlighted as missing in #24:

- health.test.ts: sidecar returns 200 with malformed JSON (a common
  failure mode when a reverse proxy serves an HTML error page —
  res.json() throws SyntaxError). Plus a hung-connection / fetch
  timeout test, since a silently dropped connection is worse than a
  refused one (the client waits forever instead of failing fast).
- gap.test.ts: control-plane (operator-mode) fetch fails with a
  network error like ENOTFOUND — the existing tests covered the
  sidecar-mode equivalent but never exercised fetchFromControlPlane's
  rejection path.

123 mcp-server tests still pass.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add tests for adapter-claude and mcp-server packages

1 participant