From 11d38cbc000a1dc537f94ca74e59366d423ea431 Mon Sep 17 00:00:00 2001 From: morluto <76467478+morluto@users.noreply.github.com> Date: Wed, 14 Oct 2026 15:20:09 +0800 Subject: [PATCH] test(mcp): verify retained Interface Builder limits Verify retained hierarchy limits and summary identity through the production artifact-only composition and real MCP SDK, alongside compiled CLI controls. Deep-provider selection is a separate lane. Co-authored-by: Aliciawque <166712943+Aliciawque@users.noreply.github.com> --- .../interfaceBuilderHierarchyLimits.test.ts | 40 ++++++++++++++++++- .../mcp/interfaceBuilderSummaryMcp.test.ts | 4 +- tests/support/mcp/artifactSession.ts | 25 ++++++++++++ 3 files changed, 66 insertions(+), 3 deletions(-) create mode 100644 tests/support/mcp/artifactSession.ts diff --git a/tests/acceptance/applications/interfaceBuilderHierarchyLimits.test.ts b/tests/acceptance/applications/interfaceBuilderHierarchyLimits.test.ts index 30fb20b60b..af508592cf 100644 --- a/tests/acceptance/applications/interfaceBuilderHierarchyLimits.test.ts +++ b/tests/acceptance/applications/interfaceBuilderHierarchyLimits.test.ts @@ -2,13 +2,50 @@ import { mkdir, readFile, writeFile } from "node:fs/promises"; import { join } from "node:path"; import { build } from "plist"; -import { expect } from "vitest"; +import { expect, onTestFinished } from "vitest"; import { interfaceBuilderAnalysisSchema } from "../../../src/domain/apple/interfaceBuilderGraph.js"; import { thinMach } from "../../../src/domain/binaryTarget.fixture.js"; import { parseEvidence } from "../../../src/domain/evidence.js"; import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js"; import { cliTest } from "../../support/cli/cliFixture.js"; +import { createTestMcp } from "../../support/mcp/mcpFixture.js"; +import { openArtifactTarget } from "../../support/mcp/artifactSession.js"; + +const verifyMcpParity = async ( + app: string, + expected: ReturnType, +): Promise => { + const mcp = createTestMcp(); + onTestFinished(async () => mcp.close()); + const client = await openArtifactTarget(mcp, onTestFinished, app); + const complete = await client.callTool({ + name: "decode_interface_builder", + arguments: { max_objects: 2 }, + }); + expect(complete.isError, JSON.stringify(complete)).not.toBe(true); + const evidence = parseEvidence(complete.structuredContent); + expect( + interfaceBuilderAnalysisSchema.parse(evidence.normalized_result), + ).toEqual(expected); + + const summarized = await client.callTool({ + name: "decode_interface_builder", + arguments: { max_objects: 2, detail: "summary" }, + }); + expect(summarized.isError, JSON.stringify(summarized)).not.toBe(true); + expect( + parseEvidence(summarized.structuredContent).normalized_result, + ).toMatchObject({ + kind: "summary", + parent_evidence_id: evidence.evidence_id, + summary: { + documents: expected.documents, + truncated: true, + coverage_statuses: { partial: expect.any(Number) }, + }, + }); +}; const archive = (objects: readonly string[], hierarchy: readonly string[]) => build({ @@ -82,6 +119,7 @@ cliTest.for(cases)( const report = interfaceBuilderAnalysisSchema.parse( evidence.normalized_result, ); + await verifyMcpParity(app, report); expect( report.documents.map(({ hierarchy_complete }) => hierarchy_complete), ).toEqual(hierarchyComplete); diff --git a/tests/boundary/mcp/interfaceBuilderSummaryMcp.test.ts b/tests/boundary/mcp/interfaceBuilderSummaryMcp.test.ts index b62fcdf53b..4df8d2d553 100644 --- a/tests/boundary/mcp/interfaceBuilderSummaryMcp.test.ts +++ b/tests/boundary/mcp/interfaceBuilderSummaryMcp.test.ts @@ -9,7 +9,7 @@ import { thinMach } from "../../../src/domain/binaryTarget.fixture.js"; import { parseMcpToolError } from "../../fixtures/mcpToolError.js"; import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js"; import { mcpTest } from "../../support/mcp/mcpFixture.js"; -import { openProductionTarget } from "../../support/mcp/productionSession.js"; +import { openArtifactTarget } from "../../support/mcp/artifactSession.js"; const evidenceOf = (schema: Schema) => z.object({ @@ -65,7 +65,7 @@ mcpTest( "retains complete Interface Builder Evidence and pages one document's rows", async ({ mcp, onTestFinished }) => { const app = await fixtureApp(); - const client = await openProductionTarget(mcp, onTestFinished, app); + const client = await openArtifactTarget(mcp, onTestFinished, app); // Summary first: its own retained parent must serve every later page. const summarized = await client.callTool({ diff --git a/tests/support/mcp/artifactSession.ts b/tests/support/mcp/artifactSession.ts new file mode 100644 index 0000000000..569724b9b6 --- /dev/null +++ b/tests/support/mcp/artifactSession.ts @@ -0,0 +1,25 @@ +import type { Client } from "@modelcontextprotocol/client"; +import { expect, type OnTestFinishedHandler } from "vitest"; + +import { createArtifactBinarySession } from "../../../src/composition/binary.js"; +import { createServer } from "../../../src/server/createServer.js"; +import type { TestMcp } from "./mcpFixture.js"; + +/** Open a static artifact target in the production artifact-only MCP composition. */ +export const openArtifactTarget = async ( + mcp: TestMcp, + onTestFinished: (handler: OnTestFinishedHandler) => void, + path: string, +): Promise => { + const session = await createArtifactBinarySession(process.env); + onTestFinished(async () => { + await session.close(); + }); + const client = await mcp.connect(createServer({ kind: "session", session })); + const opened = await client.callTool({ + name: "open_binary", + arguments: { path }, + }); + expect(opened.isError, JSON.stringify(opened.content)).not.toBe(true); + return client; +};