Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 64 additions & 0 deletions docs/session-aware-migration-todo.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# Session-Aware Migration TODO

_Audit date: October 6, 2025_

Reference: `docs/session_management_plan.md`

## Utilities
- [ ] `src/mcp/tools/utilities/clean.ts` — session defaults: `projectPath`, `workspacePath`, `scheme`, `configuration`.

## Project Discovery
- [ ] `src/mcp/tools/project-discovery/list_schemes.ts` — session defaults: `projectPath`, `workspacePath`.
- [ ] `src/mcp/tools/project-discovery/show_build_settings.ts` — session defaults: `projectPath`, `workspacePath`, `scheme`.

## Device Workflows
- [ ] `src/mcp/tools/device/build_device.ts` — session defaults: `projectPath`, `workspacePath`, `scheme`, `configuration`.
- [ ] `src/mcp/tools/device/test_device.ts` — session defaults: `projectPath`, `workspacePath`, `scheme`, `deviceId`, `configuration`.
- [ ] `src/mcp/tools/device/get_device_app_path.ts` — session defaults: `projectPath`, `workspacePath`, `scheme`, `configuration`.
- [ ] `src/mcp/tools/device/install_app_device.ts` — session defaults: `deviceId`.
- [ ] `src/mcp/tools/device/launch_app_device.ts` — session defaults: `deviceId`.
- [ ] `src/mcp/tools/device/stop_app_device.ts` — session defaults: `deviceId`.

## Device Logging
- [ ] `src/mcp/tools/logging/start_device_log_cap.ts` — session defaults: `deviceId`.

## macOS Workflows
- [ ] `src/mcp/tools/macos/build_macos.ts` — session defaults: `projectPath`, `workspacePath`, `scheme`, `configuration`, `arch`.
- [ ] `src/mcp/tools/macos/build_run_macos.ts` — session defaults: `projectPath`, `workspacePath`, `scheme`, `configuration`, `arch`.
- [ ] `src/mcp/tools/macos/test_macos.ts` — session defaults: `projectPath`, `workspacePath`, `scheme`, `configuration`.
- [ ] `src/mcp/tools/macos/get_mac_app_path.ts` — session defaults: `projectPath`, `workspacePath`, `scheme`, `configuration`, `arch`.

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

## Simulator Runtime Actions
- [ ] `src/mcp/tools/simulator/boot_sim.ts` — session defaults: `simulatorId` (hydrate `simulatorUuid`).
- [ ] `src/mcp/tools/simulator/install_app_sim.ts` — session defaults: `simulatorId` (hydrate `simulatorUuid`).
- [ ] `src/mcp/tools/simulator/launch_app_sim.ts` — session defaults: `simulatorId`, `simulatorName` (hydrate `simulatorUuid`).
- [ ] `src/mcp/tools/simulator/launch_app_logs_sim.ts` — session defaults: `simulatorId` (hydrate `simulatorUuid`).
- [ ] `src/mcp/tools/simulator/stop_app_sim.ts` — session defaults: `simulatorId`, `simulatorName` (hydrate `simulatorUuid`).
- [ ] `src/mcp/tools/simulator/record_sim_video.ts` — session defaults: `simulatorId` (hydrate `simulatorUuid`).

## Simulator Management
- [ ] `src/mcp/tools/simulator-management/erase_sims.ts` — session defaults: `simulatorId` (covers `simulatorUdid`).
- [ ] `src/mcp/tools/simulator-management/set_sim_location.ts` — session defaults: `simulatorId` (hydrate `simulatorUuid`).
- [ ] `src/mcp/tools/simulator-management/reset_sim_location.ts` — session defaults: `simulatorId` (hydrate `simulatorUuid`).
- [ ] `src/mcp/tools/simulator-management/set_sim_appearance.ts` — session defaults: `simulatorId` (hydrate `simulatorUuid`).
- [ ] `src/mcp/tools/simulator-management/sim_statusbar.ts` — session defaults: `simulatorId` (hydrate `simulatorUuid`).

## Simulator Logging
- [ ] `src/mcp/tools/logging/start_sim_log_cap.ts` — session defaults: `simulatorId` (hydrate `simulatorUuid`).

## AXe UI Testing Tools
- [ ] `src/mcp/tools/ui-testing/button.ts` — session defaults: `simulatorId` (hydrate `simulatorUuid`).
- [ ] `src/mcp/tools/ui-testing/describe_ui.ts` — session defaults: `simulatorId` (hydrate `simulatorUuid`).
- [ ] `src/mcp/tools/ui-testing/gesture.ts` — session defaults: `simulatorId` (hydrate `simulatorUuid`).
- [ ] `src/mcp/tools/ui-testing/key_press.ts` — session defaults: `simulatorId` (hydrate `simulatorUuid`).
- [ ] `src/mcp/tools/ui-testing/key_sequence.ts` — session defaults: `simulatorId` (hydrate `simulatorUuid`).
- [ ] `src/mcp/tools/ui-testing/long_press.ts` — session defaults: `simulatorId` (hydrate `simulatorUuid`).
- [ ] `src/mcp/tools/ui-testing/screenshot.ts` — session defaults: `simulatorId` (hydrate `simulatorUuid`).
- [ ] `src/mcp/tools/ui-testing/swipe.ts` — session defaults: `simulatorId` (hydrate `simulatorUuid`).
- [ ] `src/mcp/tools/ui-testing/tap.ts` — session defaults: `simulatorId` (hydrate `simulatorUuid`).
- [ ] `src/mcp/tools/ui-testing/touch.ts` — session defaults: `simulatorId` (hydrate `simulatorUuid`).
- [ ] `src/mcp/tools/ui-testing/type_text.ts` — session defaults: `simulatorId` (hydrate `simulatorUuid`).
195 changes: 195 additions & 0 deletions src/mcp/tools/simulator/__tests__/get_sim_app_path.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
/**
* Tests for get_sim_app_path plugin (session-aware version)
* Mirrors patterns from other simulator session-aware migrations.
*/

import { describe, it, expect, beforeEach } from 'vitest';
import { ChildProcess } from 'child_process';
import { z } from 'zod';
import { createMockExecutor } from '../../../../test-utils/mock-executors.ts';
import { sessionStore } from '../../../../utils/session-store.ts';
import getSimAppPath, { get_sim_app_pathLogic } from '../get_sim_app_path.ts';
import type { CommandExecutor } from '../../../../utils/CommandExecutor.ts';

describe('get_sim_app_path tool', () => {
beforeEach(() => {
sessionStore.clear();
});

describe('Export Field Validation (Literal)', () => {
it('should have correct name', () => {
expect(getSimAppPath.name).toBe('get_sim_app_path');
});

it('should have concise description', () => {
expect(getSimAppPath.description).toBe('Retrieves the built app path for an iOS simulator.');
});

it('should have handler function', () => {
expect(typeof getSimAppPath.handler).toBe('function');
});

it('should expose only platform in public schema', () => {
const schema = z.object(getSimAppPath.schema);

expect(schema.safeParse({ platform: 'iOS Simulator' }).success).toBe(true);
expect(schema.safeParse({}).success).toBe(false);
expect(schema.safeParse({ platform: 'iOS' }).success).toBe(false);

const schemaKeys = Object.keys(getSimAppPath.schema).sort();
expect(schemaKeys).toEqual(['platform']);
});
});

describe('Handler Requirements', () => {
it('should require scheme when not provided', async () => {
const result = await getSimAppPath.handler({
platform: 'iOS Simulator',
});

expect(result.isError).toBe(true);
expect(result.content[0].text).toContain('scheme is required');
});

it('should require project or workspace when scheme default exists', async () => {
sessionStore.setDefaults({ scheme: 'MyScheme' });

const result = await getSimAppPath.handler({
platform: 'iOS Simulator',
});

expect(result.isError).toBe(true);
expect(result.content[0].text).toContain('Provide a project or workspace');
});

it('should require simulator identifier when scheme and project defaults exist', async () => {
sessionStore.setDefaults({
scheme: 'MyScheme',
projectPath: '/path/to/project.xcodeproj',
});

const result = await getSimAppPath.handler({
platform: 'iOS Simulator',
});

expect(result.isError).toBe(true);
expect(result.content[0].text).toContain('Provide simulatorId or simulatorName');
});

it('should error when both projectPath and workspacePath provided explicitly', async () => {
sessionStore.setDefaults({ scheme: 'MyScheme' });

const result = await getSimAppPath.handler({
platform: 'iOS Simulator',
projectPath: '/path/project.xcodeproj',
workspacePath: '/path/workspace.xcworkspace',
});

expect(result.isError).toBe(true);
expect(result.content[0].text).toContain('Mutually exclusive parameters provided');
expect(result.content[0].text).toContain('projectPath');
expect(result.content[0].text).toContain('workspacePath');
});

it('should error when both simulatorId and simulatorName provided explicitly', async () => {
sessionStore.setDefaults({
scheme: 'MyScheme',
workspacePath: '/path/to/workspace.xcworkspace',
});

const result = await getSimAppPath.handler({
platform: 'iOS Simulator',
simulatorId: 'SIM-UUID',
simulatorName: 'iPhone 16',
});

expect(result.isError).toBe(true);
expect(result.content[0].text).toContain('Mutually exclusive parameters provided');
expect(result.content[0].text).toContain('simulatorId');
expect(result.content[0].text).toContain('simulatorName');
});
});

describe('Logic Behavior', () => {
it('should return app path with simulator name destination', async () => {
const callHistory: Array<{
command: string[];
logPrefix?: string;
useShell?: boolean;
opts?: unknown;
}> = [];

const trackingExecutor: CommandExecutor = async (
command,
logPrefix,
useShell,
opts,
): Promise<{
success: boolean;
output: string;
process: ChildProcess;
}> => {
callHistory.push({ command, logPrefix, useShell, opts });
return {
success: true,
output:
' BUILT_PRODUCTS_DIR = /tmp/DerivedData/Build\n FULL_PRODUCT_NAME = MyApp.app\n',
process: { pid: 12345 } as unknown as ChildProcess,
};
};

const result = await get_sim_app_pathLogic(
{
workspacePath: '/path/to/workspace.xcworkspace',
scheme: 'MyScheme',
platform: 'iOS Simulator',
simulatorName: 'iPhone 16',
useLatestOS: true,
},
trackingExecutor,
);

expect(callHistory).toHaveLength(1);
expect(callHistory[0].logPrefix).toBe('Get App Path');
expect(callHistory[0].useShell).toBe(true);
expect(callHistory[0].command).toEqual([
'xcodebuild',
'-showBuildSettings',
'-workspace',
'/path/to/workspace.xcworkspace',
'-scheme',
'MyScheme',
'-configuration',
'Debug',
'-destination',
'platform=iOS Simulator,name=iPhone 16,OS=latest',
]);

expect(result.isError).toBe(false);
expect(result.content[0].text).toContain(
'✅ App path retrieved successfully: /tmp/DerivedData/Build/MyApp.app',
);
});

it('should surface executor failures when build settings cannot be retrieved', async () => {
const mockExecutor = createMockExecutor({
success: false,
error: 'Failed to run xcodebuild',
});

const result = await get_sim_app_pathLogic(
{
projectPath: '/path/to/project.xcodeproj',
scheme: 'MyScheme',
platform: 'iOS Simulator',
simulatorId: 'SIM-UUID',
},
mockExecutor,
);

expect(result.isError).toBe(true);
expect(result.content[0].text).toContain('Failed to get app path');
expect(result.content[0].text).toContain('Failed to run xcodebuild');
});
});
});
100 changes: 100 additions & 0 deletions src/mcp/tools/simulator/__tests__/test_sim.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
/**
* Tests for test_sim plugin (session-aware version)
* Follows CLAUDE.md guidance: dependency injection, no vi-mocks, literal validation.
*/

import { describe, it, expect, beforeEach } from 'vitest';
import { z } from 'zod';
import { sessionStore } from '../../../../utils/session-store.ts';
import testSim from '../test_sim.ts';

describe('test_sim tool', () => {
beforeEach(() => {
sessionStore.clear();
});

describe('Export Field Validation (Literal)', () => {
it('should have correct name', () => {
expect(testSim.name).toBe('test_sim');
});

it('should have concise description', () => {
expect(testSim.description).toBe('Runs tests on an iOS simulator.');
});

it('should have handler function', () => {
expect(typeof testSim.handler).toBe('function');
});

it('should expose only non-session fields in public schema', () => {
const schema = z.object(testSim.schema);

expect(schema.safeParse({}).success).toBe(true);
expect(
schema.safeParse({
derivedDataPath: '/tmp/derived',
extraArgs: ['--quiet'],
preferXcodebuild: true,
testRunnerEnv: { FOO: 'BAR' },
}).success,
).toBe(true);

expect(schema.safeParse({ derivedDataPath: 123 }).success).toBe(false);
expect(schema.safeParse({ extraArgs: ['--ok', 42] }).success).toBe(false);
expect(schema.safeParse({ preferXcodebuild: 'yes' }).success).toBe(false);
expect(schema.safeParse({ testRunnerEnv: { FOO: 123 } }).success).toBe(false);

const schemaKeys = Object.keys(testSim.schema).sort();
expect(schemaKeys).toEqual(
['derivedDataPath', 'extraArgs', 'preferXcodebuild', 'testRunnerEnv'].sort(),
);
});
});

describe('Handler Requirements', () => {
it('should require scheme when not provided', async () => {
const result = await testSim.handler({});

expect(result.isError).toBe(true);
expect(result.content[0].text).toContain('scheme is required');
});

it('should require project or workspace when scheme default exists', async () => {
sessionStore.setDefaults({ scheme: 'MyScheme' });

const result = await testSim.handler({});

expect(result.isError).toBe(true);
expect(result.content[0].text).toContain('Provide a project or workspace');
});

it('should require simulator identifier when scheme and project defaults exist', async () => {
sessionStore.setDefaults({
scheme: 'MyScheme',
projectPath: '/path/to/project.xcodeproj',
});

const result = await testSim.handler({});

expect(result.isError).toBe(true);
expect(result.content[0].text).toContain('Provide simulatorId or simulatorName');
});

it('should error when both simulatorId and simulatorName provided explicitly', async () => {
sessionStore.setDefaults({
scheme: 'MyScheme',
workspacePath: '/path/to/workspace.xcworkspace',
});

const result = await testSim.handler({
simulatorId: 'SIM-UUID',
simulatorName: 'iPhone 16',
});

expect(result.isError).toBe(true);
expect(result.content[0].text).toContain('Mutually exclusive parameters provided');
expect(result.content[0].text).toContain('simulatorId');
expect(result.content[0].text).toContain('simulatorName');
});
});
});
Loading
Loading