Skip to content

Commit 75d408f

Browse files
committed
feat: add MCP integration snapshot test harness
Add mcp-harness.ts that combines the MCP test harness (real server with in-memory transport and mocked executors) with the snapshot normalization and fixture comparison pipeline. Initial test scenarios cover session management (show/set defaults) and error paths (missing required params). The harness can be extended with additional tool scenarios.
1 parent babe27b commit 75d408f

File tree

5 files changed

+180
-0
lines changed

5 files changed

+180
-0
lines changed
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
2+
❌ Missing required session defaults: Provide a project or workspace
3+
Set with: session-set-defaults { "projectPath": "..." } OR session-set-defaults { "workspacePath": "..." }
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
2+
⚙️ Set Defaults
3+
4+
Scheme: CalculatorApp
5+
Profile: (default)
6+
7+
✅ Session defaults updated (default profile)
8+
├ projectPath: (not set)
9+
├ workspacePath: (not set)
10+
├ scheme: CalculatorApp
11+
├ configuration: (not set)
12+
├ simulatorName: (not set)
13+
├ simulatorId: (not set)
14+
├ simulatorPlatform: (not set)
15+
├ deviceId: (not set)
16+
├ useLatestOS: (not set)
17+
├ arch: (not set)
18+
├ suppressWarnings: (not set)
19+
├ derivedDataPath: (not set)
20+
├ preferXcodebuild: (not set)
21+
├ platform: (not set)
22+
├ bundleId: (not set)
23+
└ env: (not set)
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
2+
⚙️ Show Defaults
3+
4+
📁 (default)
5+
├ projectPath: (not set)
6+
├ workspacePath: (not set)
7+
├ scheme: (not set)
8+
├ configuration: (not set)
9+
├ simulatorName: (not set)
10+
├ simulatorId: (not set)
11+
├ simulatorPlatform: (not set)
12+
├ deviceId: (not set)
13+
├ useLatestOS: (not set)
14+
├ arch: (not set)
15+
├ suppressWarnings: (not set)
16+
├ derivedDataPath: (not set)
17+
├ preferXcodebuild: (not set)
18+
├ platform: (not set)
19+
├ bundleId: (not set)
20+
└ env: (not set)
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import { describe, it, expect, beforeAll, afterAll, beforeEach } from 'vitest';
2+
import { createMcpSnapshotHarness, type McpSnapshotHarness } from '../mcp-harness.ts';
3+
import { expectMatchesFixture } from '../fixture-io.ts';
4+
5+
let harness: McpSnapshotHarness;
6+
7+
beforeAll(async () => {
8+
harness = await createMcpSnapshotHarness({
9+
commandResponses: {
10+
'xcodebuild -version': {
11+
success: true,
12+
output: 'Xcode 16.0\nBuild version 16A242d',
13+
},
14+
'xcodebuild -showBuildSettings': {
15+
success: true,
16+
output:
17+
'Build settings for action build and target "CalculatorApp":\n' +
18+
' PRODUCT_BUNDLE_IDENTIFIER = io.sentry.calculatorapp\n' +
19+
' PRODUCT_NAME = CalculatorApp\n',
20+
},
21+
'simctl list devices': {
22+
success: true,
23+
output: JSON.stringify({
24+
devices: {
25+
'com.apple.CoreSimulator.SimRuntime.iOS-18-0': [
26+
{
27+
name: 'iPhone 16 Pro',
28+
udid: 'AAAAAAAA-1111-2222-3333-444444444444',
29+
state: 'Booted',
30+
isAvailable: true,
31+
},
32+
{
33+
name: 'iPhone 16',
34+
udid: 'BBBBBBBB-1111-2222-3333-444444444444',
35+
state: 'Shutdown',
36+
isAvailable: true,
37+
},
38+
],
39+
},
40+
}),
41+
},
42+
xcodebuild: { success: true, output: '** BUILD SUCCEEDED **' },
43+
},
44+
});
45+
}, 30_000);
46+
47+
afterAll(async () => {
48+
await harness.cleanup();
49+
});
50+
51+
beforeEach(() => {
52+
harness.resetCapturedCommands();
53+
});
54+
55+
describe('MCP Integration Snapshots', () => {
56+
describe('session-management', () => {
57+
it('session_show_defaults -- empty', async () => {
58+
await harness.client.callTool({
59+
name: 'session_clear_defaults',
60+
arguments: { all: true },
61+
});
62+
const { text, isError } = await harness.callTool('session_show_defaults', {});
63+
expect(isError).toBe(false);
64+
expectMatchesFixture(text, __filename, 'session-show-defaults--empty');
65+
});
66+
67+
it('session_set_defaults -- set scheme', async () => {
68+
await harness.client.callTool({
69+
name: 'session_clear_defaults',
70+
arguments: { all: true },
71+
});
72+
const { text, isError } = await harness.callTool('session_set_defaults', {
73+
scheme: 'CalculatorApp',
74+
});
75+
expect(isError).toBe(false);
76+
expectMatchesFixture(text, __filename, 'session-set-defaults--scheme');
77+
});
78+
});
79+
80+
describe('error-paths', () => {
81+
it('build_sim -- missing required params', async () => {
82+
const { text, isError } = await harness.callTool('build_sim', {});
83+
expect(isError).toBe(true);
84+
expectMatchesFixture(text, __filename, 'build-sim--missing-params');
85+
});
86+
});
87+
});

src/snapshot-tests/mcp-harness.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import {
2+
createMcpTestHarness,
3+
type McpTestHarness,
4+
type McpTestHarnessOptions,
5+
} from '../smoke-tests/mcp-test-harness.ts';
6+
import { extractText } from '../smoke-tests/test-helpers.ts';
7+
import { normalizeSnapshotOutput } from './normalize.ts';
8+
9+
export interface McpSnapshotHarness {
10+
callTool(name: string, args: Record<string, unknown>): Promise<McpSnapshotResult>;
11+
client: McpTestHarness['client'];
12+
capturedCommands: McpTestHarness['capturedCommands'];
13+
resetCapturedCommands(): void;
14+
cleanup(): Promise<void>;
15+
}
16+
17+
export interface McpSnapshotResult {
18+
text: string;
19+
rawText: string;
20+
isError: boolean;
21+
}
22+
23+
export async function createMcpSnapshotHarness(
24+
opts?: McpTestHarnessOptions,
25+
): Promise<McpSnapshotHarness> {
26+
const harness = await createMcpTestHarness(opts);
27+
28+
async function callTool(
29+
name: string,
30+
args: Record<string, unknown>,
31+
): Promise<McpSnapshotResult> {
32+
const result = await harness.client.callTool({ name, arguments: args });
33+
const rawText = extractText(result) + '\n';
34+
const text = normalizeSnapshotOutput(rawText);
35+
const isError = (result as { isError?: boolean }).isError ?? false;
36+
37+
return { text, rawText, isError };
38+
}
39+
40+
return {
41+
callTool,
42+
client: harness.client,
43+
capturedCommands: harness.capturedCommands,
44+
resetCapturedCommands: harness.resetCapturedCommands,
45+
cleanup: harness.cleanup,
46+
};
47+
}

0 commit comments

Comments
 (0)