Skip to content

Commit 9b506d1

Browse files
committed
feat(macos): migrate macOS tools to session-aware defaults
1 parent 8f03a6c commit 9b506d1

9 files changed

Lines changed: 297 additions & 250 deletions

docs/session-aware-migration-todo.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,10 @@ Reference: `docs/session_management_plan.md`
2323
- [x] `src/mcp/tools/logging/start_device_log_cap.ts` — session defaults: `deviceId`.
2424

2525
## macOS Workflows
26-
- [ ] `src/mcp/tools/macos/build_macos.ts` — session defaults: `projectPath`, `workspacePath`, `scheme`, `configuration`, `arch`.
27-
- [ ] `src/mcp/tools/macos/build_run_macos.ts` — session defaults: `projectPath`, `workspacePath`, `scheme`, `configuration`, `arch`.
28-
- [ ] `src/mcp/tools/macos/test_macos.ts` — session defaults: `projectPath`, `workspacePath`, `scheme`, `configuration`.
29-
- [ ] `src/mcp/tools/macos/get_mac_app_path.ts` — session defaults: `projectPath`, `workspacePath`, `scheme`, `configuration`, `arch`.
26+
- [x] `src/mcp/tools/macos/build_macos.ts` — session defaults: `projectPath`, `workspacePath`, `scheme`, `configuration`, `arch`.
27+
- [x] `src/mcp/tools/macos/build_run_macos.ts` — session defaults: `projectPath`, `workspacePath`, `scheme`, `configuration`, `arch`.
28+
- [x] `src/mcp/tools/macos/test_macos.ts` — session defaults: `projectPath`, `workspacePath`, `scheme`, `configuration`.
29+
- [x] `src/mcp/tools/macos/get_mac_app_path.ts` — session defaults: `projectPath`, `workspacePath`, `scheme`, `configuration`, `arch`.
3030

3131
## Simulator Build/Test/Path
3232
- [x] `src/mcp/tools/simulator/test_sim.ts` — session defaults: `projectPath`, `workspacePath`, `scheme`, `simulatorId`, `simulatorName`, `configuration`, `useLatestOS`.

src/mcp/tools/macos/__tests__/build_macos.test.ts

Lines changed: 56 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -5,54 +5,81 @@
55
* NO VITEST MOCKING ALLOWED - Only createMockExecutor and createMockFileSystemExecutor
66
*/
77

8-
import { describe, it, expect } from 'vitest';
8+
import { describe, it, expect, beforeEach } from 'vitest';
99
import { z } from 'zod';
1010
import { createMockExecutor } from '../../../../test-utils/mock-executors.ts';
11+
import { sessionStore } from '../../../../utils/session-store.ts';
1112
import buildMacOS, { buildMacOSLogic } from '../build_macos.ts';
1213

1314
describe('build_macos plugin', () => {
15+
beforeEach(() => {
16+
sessionStore.clear();
17+
});
18+
1419
describe('Export Field Validation (Literal)', () => {
1520
it('should have correct name', () => {
1621
expect(buildMacOS.name).toBe('build_macos');
1722
});
1823

1924
it('should have correct description', () => {
20-
expect(buildMacOS.description).toBe(
21-
"Builds a macOS app using xcodebuild from a project or workspace. Provide exactly one of projectPath or workspacePath. Example: build_macos({ projectPath: '/path/to/MyProject.xcodeproj', scheme: 'MyScheme' })",
22-
);
25+
expect(buildMacOS.description).toBe('Builds a macOS app.');
2326
});
2427

2528
it('should have handler function', () => {
2629
expect(typeof buildMacOS.handler).toBe('function');
2730
});
2831

2932
it('should validate schema correctly', () => {
30-
// Test required fields
31-
expect(buildMacOS.schema.projectPath.safeParse('/path/to/MyProject.xcodeproj').success).toBe(
32-
true,
33-
);
33+
const schema = z.object(buildMacOS.schema);
34+
35+
expect(schema.safeParse({}).success).toBe(true);
3436
expect(
35-
buildMacOS.schema.workspacePath.safeParse('/path/to/MyProject.xcworkspace').success,
37+
schema.safeParse({
38+
derivedDataPath: '/path/to/derived-data',
39+
extraArgs: ['--arg1', '--arg2'],
40+
preferXcodebuild: true,
41+
}).success,
3642
).toBe(true);
37-
expect(buildMacOS.schema.scheme.safeParse('MyScheme').success).toBe(true);
3843

39-
// Test optional fields
40-
expect(buildMacOS.schema.configuration.safeParse('Debug').success).toBe(true);
41-
expect(buildMacOS.schema.derivedDataPath.safeParse('/path/to/derived-data').success).toBe(
42-
true,
43-
);
44-
expect(buildMacOS.schema.arch.safeParse('arm64').success).toBe(true);
45-
expect(buildMacOS.schema.arch.safeParse('x86_64').success).toBe(true);
46-
expect(buildMacOS.schema.extraArgs.safeParse(['--arg1', '--arg2']).success).toBe(true);
47-
expect(buildMacOS.schema.preferXcodebuild.safeParse(true).success).toBe(true);
48-
49-
// Test invalid inputs
50-
expect(buildMacOS.schema.projectPath.safeParse(null).success).toBe(false);
51-
expect(buildMacOS.schema.workspacePath.safeParse(null).success).toBe(false);
52-
expect(buildMacOS.schema.scheme.safeParse(null).success).toBe(false);
53-
expect(buildMacOS.schema.arch.safeParse('invalidArch').success).toBe(false);
54-
expect(buildMacOS.schema.extraArgs.safeParse('not-array').success).toBe(false);
55-
expect(buildMacOS.schema.preferXcodebuild.safeParse('not-boolean').success).toBe(false);
44+
expect(schema.safeParse({ derivedDataPath: 42 }).success).toBe(false);
45+
expect(schema.safeParse({ extraArgs: ['--ok', 1] }).success).toBe(false);
46+
expect(schema.safeParse({ preferXcodebuild: 'yes' }).success).toBe(false);
47+
48+
const schemaKeys = Object.keys(buildMacOS.schema).sort();
49+
expect(schemaKeys).toEqual(['derivedDataPath', 'extraArgs', 'preferXcodebuild'].sort());
50+
});
51+
});
52+
53+
describe('Handler Requirements', () => {
54+
it('should require scheme when no defaults provided', async () => {
55+
const result = await buildMacOS.handler({});
56+
57+
expect(result.isError).toBe(true);
58+
expect(result.content[0].text).toContain('scheme is required');
59+
expect(result.content[0].text).toContain('session-set-defaults');
60+
});
61+
62+
it('should require project or workspace once scheme default exists', async () => {
63+
sessionStore.setDefaults({ scheme: 'MyScheme' });
64+
65+
const result = await buildMacOS.handler({});
66+
67+
expect(result.isError).toBe(true);
68+
expect(result.content[0].text).toContain('Provide a project or workspace');
69+
});
70+
71+
it('should reject when both projectPath and workspacePath provided explicitly', async () => {
72+
sessionStore.setDefaults({ scheme: 'MyScheme' });
73+
74+
const result = await buildMacOS.handler({
75+
projectPath: '/path/to/project.xcodeproj',
76+
workspacePath: '/path/to/workspace.xcworkspace',
77+
});
78+
79+
expect(result.isError).toBe(true);
80+
expect(result.content[0].text).toContain('Mutually exclusive parameters provided');
81+
expect(result.content[0].text).toContain('projectPath');
82+
expect(result.content[0].text).toContain('workspacePath');
5683
});
5784
});
5885

@@ -416,7 +443,7 @@ describe('build_macos plugin', () => {
416443
it('should error when neither projectPath nor workspacePath provided', async () => {
417444
const result = await buildMacOS.handler({ scheme: 'MyScheme' });
418445
expect(result.isError).toBe(true);
419-
expect(result.content[0].text).toContain('Either projectPath or workspacePath is required');
446+
expect(result.content[0].text).toContain('Provide a project or workspace');
420447
});
421448

422449
it('should error when both projectPath and workspacePath provided', async () => {
@@ -426,7 +453,7 @@ describe('build_macos plugin', () => {
426453
scheme: 'MyScheme',
427454
});
428455
expect(result.isError).toBe(true);
429-
expect(result.content[0].text).toContain('mutually exclusive');
456+
expect(result.content[0].text).toContain('Mutually exclusive parameters provided');
430457
});
431458

432459
it('should succeed with valid projectPath', async () => {

src/mcp/tools/macos/__tests__/build_run_macos.test.ts

Lines changed: 42 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -1,114 +1,75 @@
1-
import { describe, it, expect } from 'vitest';
1+
import { describe, it, expect, beforeEach } from 'vitest';
22
import { z } from 'zod';
33
import { createMockExecutor } from '../../../../test-utils/mock-executors.ts';
4+
import { sessionStore } from '../../../../utils/session-store.ts';
45
import tool, { buildRunMacOSLogic } from '../build_run_macos.ts';
56

67
describe('build_run_macos', () => {
8+
beforeEach(() => {
9+
sessionStore.clear();
10+
});
11+
712
describe('Export Field Validation (Literal)', () => {
813
it('should export the correct name', () => {
914
expect(tool.name).toBe('build_run_macos');
1015
});
1116

1217
it('should export the correct description', () => {
13-
expect(tool.description).toBe(
14-
"Builds and runs a macOS app from a project or workspace in one step. Provide exactly one of projectPath or workspacePath. Example: build_run_macos({ projectPath: '/path/to/MyProject.xcodeproj', scheme: 'MyScheme' })",
15-
);
18+
expect(tool.description).toBe('Builds and runs a macOS app.');
1619
});
1720

1821
it('should export a handler function', () => {
1922
expect(typeof tool.handler).toBe('function');
2023
});
2124

22-
it('should validate schema with valid project inputs', () => {
23-
const validInput = {
24-
projectPath: '/path/to/project.xcodeproj',
25-
scheme: 'MyApp',
26-
configuration: 'Debug',
27-
derivedDataPath: '/path/to/derived',
28-
arch: 'arm64',
29-
extraArgs: ['--verbose'],
30-
preferXcodebuild: true,
31-
};
25+
it('should expose only non-session fields in schema', () => {
3226
const schema = z.object(tool.schema);
33-
expect(schema.safeParse(validInput).success).toBe(true);
34-
});
3527

36-
it('should validate schema with valid workspace inputs', () => {
37-
const validInput = {
38-
workspacePath: '/path/to/workspace.xcworkspace',
39-
scheme: 'MyApp',
40-
configuration: 'Debug',
41-
derivedDataPath: '/path/to/derived',
42-
arch: 'arm64',
43-
extraArgs: ['--verbose'],
44-
preferXcodebuild: true,
45-
};
46-
const schema = z.object(tool.schema);
47-
expect(schema.safeParse(validInput).success).toBe(true);
28+
expect(schema.safeParse({}).success).toBe(true);
29+
expect(
30+
schema.safeParse({
31+
derivedDataPath: '/tmp/derived',
32+
extraArgs: ['--verbose'],
33+
preferXcodebuild: true,
34+
}).success,
35+
).toBe(true);
36+
37+
expect(schema.safeParse({ derivedDataPath: 1 }).success).toBe(false);
38+
expect(schema.safeParse({ extraArgs: ['--ok', 2] }).success).toBe(false);
39+
expect(schema.safeParse({ preferXcodebuild: 'yes' }).success).toBe(false);
40+
41+
const schemaKeys = Object.keys(tool.schema).sort();
42+
expect(schemaKeys).toEqual(['derivedDataPath', 'extraArgs', 'preferXcodebuild'].sort());
4843
});
44+
});
4945

50-
it('should validate schema with minimal valid project inputs', () => {
51-
const validInput = {
52-
projectPath: '/path/to/project.xcodeproj',
53-
scheme: 'MyApp',
54-
};
55-
const schema = z.object(tool.schema);
56-
expect(schema.safeParse(validInput).success).toBe(true);
57-
});
46+
describe('Handler Requirements', () => {
47+
it('should require scheme before executing', async () => {
48+
const result = await tool.handler({});
5849

59-
it('should validate schema with minimal valid workspace inputs', () => {
60-
const validInput = {
61-
workspacePath: '/path/to/workspace.xcworkspace',
62-
scheme: 'MyApp',
63-
};
64-
const schema = z.object(tool.schema);
65-
expect(schema.safeParse(validInput).success).toBe(true);
50+
expect(result.isError).toBe(true);
51+
expect(result.content[0].text).toContain('scheme is required');
6652
});
6753

68-
it('should reject inputs with both projectPath and workspacePath', () => {
69-
const invalidInput = {
70-
projectPath: '/path/to/project.xcodeproj',
71-
workspacePath: '/path/to/workspace.xcworkspace',
72-
scheme: 'MyApp',
73-
};
74-
const schema = z.object(tool.schema);
75-
expect(schema.safeParse(invalidInput).success).toBe(true); // Base schema passes, but runtime validation should fail
76-
});
54+
it('should require project or workspace once scheme is set', async () => {
55+
sessionStore.setDefaults({ scheme: 'MyApp' });
7756

78-
it('should reject inputs with neither projectPath nor workspacePath', () => {
79-
const invalidInput = {
80-
scheme: 'MyApp',
81-
};
82-
const schema = z.object(tool.schema);
83-
expect(schema.safeParse(invalidInput).success).toBe(true); // Base schema passes, but runtime validation should fail
84-
});
57+
const result = await tool.handler({});
8558

86-
it('should reject invalid projectPath', () => {
87-
const invalidInput = {
88-
projectPath: 123,
89-
scheme: 'MyApp',
90-
};
91-
const schema = z.object(tool.schema);
92-
expect(schema.safeParse(invalidInput).success).toBe(false);
59+
expect(result.isError).toBe(true);
60+
expect(result.content[0].text).toContain('Provide a project or workspace');
9361
});
9462

95-
it('should reject invalid scheme', () => {
96-
const invalidInput = {
97-
projectPath: '/path/to/project.xcodeproj',
98-
scheme: 123,
99-
};
100-
const schema = z.object(tool.schema);
101-
expect(schema.safeParse(invalidInput).success).toBe(false);
102-
});
63+
it('should fail when both project and workspace provided explicitly', async () => {
64+
sessionStore.setDefaults({ scheme: 'MyApp' });
10365

104-
it('should reject invalid arch', () => {
105-
const invalidInput = {
66+
const result = await tool.handler({
10667
projectPath: '/path/to/project.xcodeproj',
107-
scheme: 'MyApp',
108-
arch: 'invalid',
109-
};
110-
const schema = z.object(tool.schema);
111-
expect(schema.safeParse(invalidInput).success).toBe(false);
68+
workspacePath: '/path/to/workspace.xcworkspace',
69+
});
70+
71+
expect(result.isError).toBe(true);
72+
expect(result.content[0].text).toContain('Mutually exclusive parameters provided');
11273
});
11374
});
11475

0 commit comments

Comments
 (0)