Skip to content

Commit dfad2c9

Browse files
committed
♻️ refactor(smoke-tests): deduplicate helpers, harden harness, improve types
- Extract shared extractText, isErrorResponse, getContent into test-helpers.ts - Add resetCapturedCommands() to McpTestHarness interface replacing direct array mutation - Add build-directory guard with actionable error message in createMcpTestHarness - Document dynamic import necessity for built module patching - Replace inline error-checking blocks in device-macos tests with shared isErrorResponse - Narrow cli-surface.test.ts catch type to NodeJS.ErrnoException
1 parent 8655eaa commit dfad2c9

13 files changed

Lines changed: 127 additions & 169 deletions

src/smoke-tests/__tests__/cli-surface.test.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,11 @@ const runMayFail = (args: string): { stdout: string; status: number } => {
2323
const stdout = run(args);
2424
return { stdout, status: 0 };
2525
} catch (err: unknown) {
26-
const error = err as { stdout?: string; stderr?: string; status?: number };
26+
const error = err as NodeJS.ErrnoException & {
27+
stdout?: string;
28+
stderr?: string;
29+
status?: number;
30+
};
2731
return {
2832
stdout: (error.stdout ?? '') + (error.stderr ?? ''),
2933
status: error.status ?? 1,

src/smoke-tests/__tests__/e2e-mcp-device-macos.test.ts

Lines changed: 21 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
22
import { createMcpTestHarness, type McpTestHarness } from '../mcp-test-harness.ts';
3+
import { isErrorResponse } from '../test-helpers.ts';
34

45
let harness: McpTestHarness;
56

@@ -34,7 +35,7 @@ describe('MCP Device and macOS Tool Invocation (e2e)', () => {
3435
},
3536
});
3637

37-
harness.capturedCommands.length = 0;
38+
harness.resetCapturedCommands();
3839
const result = await harness.client.callTool({
3940
name: 'build_device',
4041
arguments: {},
@@ -59,7 +60,7 @@ describe('MCP Device and macOS Tool Invocation (e2e)', () => {
5960
},
6061
});
6162

62-
harness.capturedCommands.length = 0;
63+
harness.resetCapturedCommands();
6364
const result = await harness.client.callTool({
6465
name: 'test_device',
6566
arguments: {},
@@ -83,7 +84,7 @@ describe('MCP Device and macOS Tool Invocation (e2e)', () => {
8384
},
8485
});
8586

86-
harness.capturedCommands.length = 0;
87+
harness.resetCapturedCommands();
8788
const result = await harness.client.callTool({
8889
name: 'launch_app_device',
8990
arguments: {},
@@ -106,7 +107,7 @@ describe('MCP Device and macOS Tool Invocation (e2e)', () => {
106107
},
107108
});
108109

109-
harness.capturedCommands.length = 0;
110+
harness.resetCapturedCommands();
110111
const result = await harness.client.callTool({
111112
name: 'stop_app_device',
112113
arguments: { processId: 12345 },
@@ -131,7 +132,7 @@ describe('MCP Device and macOS Tool Invocation (e2e)', () => {
131132
},
132133
});
133134

134-
harness.capturedCommands.length = 0;
135+
harness.resetCapturedCommands();
135136
const result = await harness.client.callTool({
136137
name: 'install_app_device',
137138
arguments: { appPath: '/path/to/MyApp.app' },
@@ -155,7 +156,7 @@ describe('MCP Device and macOS Tool Invocation (e2e)', () => {
155156
},
156157
});
157158

158-
harness.capturedCommands.length = 0;
159+
harness.resetCapturedCommands();
159160
const result = await harness.client.callTool({
160161
name: 'get_device_app_path',
161162
arguments: {},
@@ -173,7 +174,7 @@ describe('MCP Device and macOS Tool Invocation (e2e)', () => {
173174
});
174175

175176
it('list_devices captures devicectl or xctrace command', async () => {
176-
harness.capturedCommands.length = 0;
177+
harness.resetCapturedCommands();
177178
const result = await harness.client.callTool({
178179
name: 'list_devices',
179180
arguments: {},
@@ -190,7 +191,7 @@ describe('MCP Device and macOS Tool Invocation (e2e)', () => {
190191

191192
describe('project discovery tools', () => {
192193
it('discover_projs responds with content', async () => {
193-
harness.capturedCommands.length = 0;
194+
harness.resetCapturedCommands();
194195
const result = await harness.client.callTool({
195196
name: 'discover_projs',
196197
arguments: { workspaceRoot: '/path/to/workspace' },
@@ -203,7 +204,7 @@ describe('MCP Device and macOS Tool Invocation (e2e)', () => {
203204
});
204205

205206
it('get_app_bundle_id responds with content', async () => {
206-
harness.capturedCommands.length = 0;
207+
harness.resetCapturedCommands();
207208
const result = await harness.client.callTool({
208209
name: 'get_app_bundle_id',
209210
arguments: { appPath: '/path/to/MyApp.app' },
@@ -216,7 +217,7 @@ describe('MCP Device and macOS Tool Invocation (e2e)', () => {
216217
});
217218

218219
it('get_mac_bundle_id responds with content', async () => {
219-
harness.capturedCommands.length = 0;
220+
harness.resetCapturedCommands();
220221
const result = await harness.client.callTool({
221222
name: 'get_mac_bundle_id',
222223
arguments: { appPath: '/path/to/MyApp.app' },
@@ -239,7 +240,7 @@ describe('MCP Device and macOS Tool Invocation (e2e)', () => {
239240
},
240241
});
241242

242-
harness.capturedCommands.length = 0;
243+
harness.resetCapturedCommands();
243244
const result = await harness.client.callTool({
244245
name: 'build_macos',
245246
arguments: {},
@@ -265,7 +266,7 @@ describe('MCP Device and macOS Tool Invocation (e2e)', () => {
265266
},
266267
});
267268

268-
harness.capturedCommands.length = 0;
269+
harness.resetCapturedCommands();
269270
const result = await harness.client.callTool({
270271
name: 'build_run_macos',
271272
arguments: {},
@@ -289,7 +290,7 @@ describe('MCP Device and macOS Tool Invocation (e2e)', () => {
289290
},
290291
});
291292

292-
harness.capturedCommands.length = 0;
293+
harness.resetCapturedCommands();
293294
const result = await harness.client.callTool({
294295
name: 'test_macos',
295296
arguments: {},
@@ -305,7 +306,7 @@ describe('MCP Device and macOS Tool Invocation (e2e)', () => {
305306
});
306307

307308
it('launch_mac_app responds with content', async () => {
308-
harness.capturedCommands.length = 0;
309+
harness.resetCapturedCommands();
309310
const result = await harness.client.callTool({
310311
name: 'launch_mac_app',
311312
arguments: { appPath: '/path/to/MyMacApp.app' },
@@ -318,7 +319,7 @@ describe('MCP Device and macOS Tool Invocation (e2e)', () => {
318319
});
319320

320321
it('stop_mac_app captures kill command with processId', async () => {
321-
harness.capturedCommands.length = 0;
322+
harness.resetCapturedCommands();
322323
const result = await harness.client.callTool({
323324
name: 'stop_mac_app',
324325
arguments: { processId: 54321 },
@@ -334,7 +335,7 @@ describe('MCP Device and macOS Tool Invocation (e2e)', () => {
334335
});
335336

336337
it('stop_mac_app captures pkill command with appName', async () => {
337-
harness.capturedCommands.length = 0;
338+
harness.resetCapturedCommands();
338339
const result = await harness.client.callTool({
339340
name: 'stop_mac_app',
340341
arguments: { appName: 'MyMacApp' },
@@ -358,7 +359,7 @@ describe('MCP Device and macOS Tool Invocation (e2e)', () => {
358359
},
359360
});
360361

361-
harness.capturedCommands.length = 0;
362+
harness.resetCapturedCommands();
362363
const result = await harness.client.callTool({
363364
name: 'get_mac_app_path',
364365
arguments: {},
@@ -388,20 +389,7 @@ describe('MCP Device and macOS Tool Invocation (e2e)', () => {
388389
arguments: {},
389390
});
390391

391-
const content = 'content' in result ? result.content : [];
392-
const isError = 'isError' in result ? result.isError : false;
393-
const hasErrorText =
394-
Array.isArray(content) &&
395-
content.some(
396-
(c) =>
397-
'text' in c &&
398-
typeof c.text === 'string' &&
399-
(c.text.toLowerCase().includes('error') ||
400-
c.text.toLowerCase().includes('required') ||
401-
c.text.toLowerCase().includes('must provide') ||
402-
c.text.toLowerCase().includes('provide')),
403-
);
404-
expect(isError || hasErrorText).toBe(true);
392+
expect(isErrorResponse(result)).toBe(true);
405393
});
406394

407395
it('build_macos returns error when session defaults missing', async () => {
@@ -415,20 +403,7 @@ describe('MCP Device and macOS Tool Invocation (e2e)', () => {
415403
arguments: {},
416404
});
417405

418-
const content = 'content' in result ? result.content : [];
419-
const isError = 'isError' in result ? result.isError : false;
420-
const hasErrorText =
421-
Array.isArray(content) &&
422-
content.some(
423-
(c) =>
424-
'text' in c &&
425-
typeof c.text === 'string' &&
426-
(c.text.toLowerCase().includes('error') ||
427-
c.text.toLowerCase().includes('required') ||
428-
c.text.toLowerCase().includes('must provide') ||
429-
c.text.toLowerCase().includes('provide')),
430-
);
431-
expect(isError || hasErrorText).toBe(true);
406+
expect(isErrorResponse(result)).toBe(true);
432407
});
433408

434409
it('stop_mac_app returns error when no appName or processId provided', async () => {
@@ -437,19 +412,7 @@ describe('MCP Device and macOS Tool Invocation (e2e)', () => {
437412
arguments: {},
438413
});
439414

440-
const content = 'content' in result ? result.content : [];
441-
const isError = 'isError' in result ? result.isError : false;
442-
const hasErrorText =
443-
Array.isArray(content) &&
444-
content.some(
445-
(c) =>
446-
'text' in c &&
447-
typeof c.text === 'string' &&
448-
(c.text.toLowerCase().includes('either') ||
449-
c.text.toLowerCase().includes('error') ||
450-
c.text.toLowerCase().includes('must be provided')),
451-
);
452-
expect(isError || hasErrorText).toBe(true);
415+
expect(isErrorResponse(result)).toBe(true);
453416
});
454417
});
455418
});

src/smoke-tests/__tests__/e2e-mcp-doctor.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ afterAll(async () => {
1717

1818
describe('MCP Doctor Tool (e2e)', () => {
1919
it('doctor returns diagnostic content', async () => {
20-
harness.capturedCommands.length = 0;
20+
harness.resetCapturedCommands();
2121
const result = await harness.client.callTool({
2222
name: 'doctor',
2323
arguments: {},

src/smoke-tests/__tests__/e2e-mcp-error-paths.test.ts

Lines changed: 1 addition & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { describe, it, expect, beforeAll, afterAll, beforeEach } from 'vitest';
22
import { createMcpTestHarness, type McpTestHarness } from '../mcp-test-harness.ts';
3+
import { extractText, isErrorResponse } from '../test-helpers.ts';
34

45
let harness: McpTestHarness;
56

@@ -33,24 +34,6 @@ beforeEach(async () => {
3334
});
3435
});
3536

36-
function extractText(result: unknown): string {
37-
const r = result as { content?: Array<{ text?: string }> };
38-
if (!r.content || !Array.isArray(r.content)) return '';
39-
return r.content.map((c) => c.text ?? '').join('\n');
40-
}
41-
42-
function isErrorResponse(result: unknown): boolean {
43-
const r = result as { isError?: boolean; content?: Array<{ text?: string }> };
44-
if (r.isError) return true;
45-
const text = extractText(result).toLowerCase();
46-
return (
47-
text.includes('error') ||
48-
text.includes('fail') ||
49-
text.includes('missing') ||
50-
text.includes('required')
51-
);
52-
}
53-
5437
describe('MCP Error Paths (e2e)', () => {
5538
describe('missing session defaults', () => {
5639
it('build_sim errors without session defaults', async () => {

src/smoke-tests/__tests__/e2e-mcp-invocation.test.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ describe('MCP Tool Invocation (e2e)', () => {
7070

7171
describe('representative tools with valid args', () => {
7272
it('list_sims captures simctl command', async () => {
73-
harness.capturedCommands.length = 0;
73+
harness.resetCapturedCommands();
7474
const result = await harness.client.callTool({
7575
name: 'list_sims',
7676
arguments: {},
@@ -96,7 +96,7 @@ describe('MCP Tool Invocation (e2e)', () => {
9696
},
9797
});
9898

99-
harness.capturedCommands.length = 0;
99+
harness.resetCapturedCommands();
100100
const result = await harness.client.callTool({
101101
name: 'build_sim',
102102
arguments: {},
@@ -120,7 +120,7 @@ describe('MCP Tool Invocation (e2e)', () => {
120120
},
121121
});
122122

123-
harness.capturedCommands.length = 0;
123+
harness.resetCapturedCommands();
124124
const result = await harness.client.callTool({
125125
name: 'clean',
126126
arguments: {},
@@ -136,7 +136,7 @@ describe('MCP Tool Invocation (e2e)', () => {
136136
});
137137

138138
it('swift_package_build captures swift build command', async () => {
139-
harness.capturedCommands.length = 0;
139+
harness.resetCapturedCommands();
140140
const result = await harness.client.callTool({
141141
name: 'swift_package_build',
142142
arguments: {
@@ -161,7 +161,7 @@ describe('MCP Tool Invocation (e2e)', () => {
161161
},
162162
});
163163

164-
harness.capturedCommands.length = 0;
164+
harness.resetCapturedCommands();
165165
const result = await harness.client.callTool({
166166
name: 'boot_sim',
167167
arguments: {},
@@ -184,7 +184,7 @@ describe('MCP Tool Invocation (e2e)', () => {
184184
},
185185
});
186186

187-
harness.capturedCommands.length = 0;
187+
harness.resetCapturedCommands();
188188
const result = await harness.client.callTool({
189189
name: 'list_schemes',
190190
arguments: {},
@@ -199,7 +199,7 @@ describe('MCP Tool Invocation (e2e)', () => {
199199
});
200200

201201
it('list_devices responds with content', async () => {
202-
harness.capturedCommands.length = 0;
202+
harness.resetCapturedCommands();
203203
const result = await harness.client.callTool({
204204
name: 'list_devices',
205205
arguments: {},
@@ -214,7 +214,7 @@ describe('MCP Tool Invocation (e2e)', () => {
214214
});
215215

216216
it('session_set_defaults works without external commands', async () => {
217-
harness.capturedCommands.length = 0;
217+
harness.resetCapturedCommands();
218218
const result = await harness.client.callTool({
219219
name: 'session_set_defaults',
220220
arguments: {
@@ -249,7 +249,7 @@ describe('MCP Tool Invocation (e2e)', () => {
249249
},
250250
});
251251

252-
harness.capturedCommands.length = 0;
252+
harness.resetCapturedCommands();
253253
const result = await harness.client.callTool({
254254
name: 'show_build_settings',
255255
arguments: {},

src/smoke-tests/__tests__/e2e-mcp-logging.test.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ describe('MCP Logging Tools (e2e)', () => {
2828
},
2929
});
3030

31-
harness.capturedCommands.length = 0;
31+
harness.resetCapturedCommands();
3232
const result = await harness.client.callTool({
3333
name: 'start_sim_log_cap',
3434
arguments: {},
@@ -41,7 +41,7 @@ describe('MCP Logging Tools (e2e)', () => {
4141
});
4242

4343
it('stop_sim_log_cap returns error for unknown session', async () => {
44-
harness.capturedCommands.length = 0;
44+
harness.resetCapturedCommands();
4545
const result = await harness.client.callTool({
4646
name: 'stop_sim_log_cap',
4747
arguments: {
@@ -77,7 +77,7 @@ describe('MCP Logging Tools (e2e)', () => {
7777
},
7878
});
7979

80-
harness.capturedCommands.length = 0;
80+
harness.resetCapturedCommands();
8181
const result = await harness.client.callTool({
8282
name: 'start_device_log_cap',
8383
arguments: {},
@@ -90,7 +90,7 @@ describe('MCP Logging Tools (e2e)', () => {
9090
});
9191

9292
it('stop_device_log_cap returns error for unknown session', async () => {
93-
harness.capturedCommands.length = 0;
93+
harness.resetCapturedCommands();
9494
const result = await harness.client.callTool({
9595
name: 'stop_device_log_cap',
9696
arguments: {

0 commit comments

Comments
 (0)