Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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 src/mcp/tools/simulator-management/__tests__/erase_sims.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { describe, it, expect } from 'vitest';
import { z } from 'zod';
import eraseSims, { erase_simsLogic } from '../erase_sims.ts';
import { createMockExecutor } from '../../../../test-utils/mock-executors.ts';

describe('erase_sims tool (UDID or ALL only)', () => {
describe('Export Field Validation (Literal)', () => {
it('should have correct name', () => {
expect(eraseSims.name).toBe('erase_sims');
});

it('should have correct description', () => {
expect(eraseSims.description).toContain('Provide exactly one of: simulatorUuid or all=true');
});

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

it('should validate schema fields (shape only)', () => {
const schema = z.object(eraseSims.schema);
// Valid
expect(schema.safeParse({ simulatorUuid: 'UDID-1' }).success).toBe(true);
expect(schema.safeParse({ all: true }).success).toBe(true);
// Shape-level schema does not enforce selection rules; handler validation covers that.
});
});

describe('Single mode', () => {
it('erases a simulator successfully', async () => {
const mock = createMockExecutor({ success: true, output: 'OK' });
const res = await erase_simsLogic({ simulatorUuid: 'UD1' }, mock);
expect(res).toEqual({
content: [{ type: 'text', text: 'Successfully erased simulator UD1' }],
});
});

it('returns failure when erase fails', async () => {
const mock = createMockExecutor({ success: false, error: 'Booted device' });
const res = await erase_simsLogic({ simulatorUuid: 'UD1' }, mock);
expect(res).toEqual({
content: [{ type: 'text', text: 'Failed to erase simulator: Booted device' }],
});
});
});

describe('All mode', () => {
it('erases all simulators successfully', async () => {
const exec = createMockExecutor({ success: true, output: 'OK' });
const res = await erase_simsLogic({ all: true }, exec);
expect(res).toEqual({
content: [{ type: 'text', text: 'Successfully erased all simulators' }],
});
});

it('returns failure when erase all fails', async () => {
const exec = createMockExecutor({ success: false, error: 'Denied' });
const res = await erase_simsLogic({ all: true }, exec);
expect(res).toEqual({
content: [{ type: 'text', text: 'Failed to erase all simulators: Denied' }],
});
});
});
});
3 changes: 2 additions & 1 deletion src/mcp/tools/simulator-management/__tests__/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ describe('simulator-management workflow metadata', () => {

it('should have correct description', () => {
expect(workflow.description).toBe(
'Tools for managing simulators from booting, opening simulators, listing simulators, stopping simulators and setting simulator environment options like location, network, statusbar and appearance.',
'Tools for managing simulators from booting, opening simulators, listing simulators, stopping simulators, erasing simulator content and settings, and setting simulator environment options like location, network, statusbar and appearance.',
);
});

Expand All @@ -46,6 +46,7 @@ describe('simulator-management workflow metadata', () => {
'location',
'network',
'statusbar',
'erase',
]);
});
});
Expand Down
86 changes: 86 additions & 0 deletions src/mcp/tools/simulator-management/erase_sims.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import { z } from 'zod';
import { ToolResponse } from '../../../types/common.ts';
import { log } from '../../../utils/logging/index.ts';
import { CommandExecutor, getDefaultCommandExecutor } from '../../../utils/execution/index.ts';
import { createTypedTool } from '../../../utils/typed-tool-factory.ts';

const eraseSimsBaseSchema = z.object({
simulatorUuid: z.string().optional().describe('UUID of the simulator to erase.'),
all: z.boolean().optional().describe('When true, erases all simulators.'),
});

Comment thread
coderabbitai[bot] marked this conversation as resolved.
const eraseSimsSchema = eraseSimsBaseSchema.refine(
(v) => {
const selectors = (v.simulatorUuid ? 1 : 0) + (v.all === true ? 1 : 0);
return selectors === 1;
},
{ message: 'Provide exactly one of: simulatorUuid OR all=true.' },
);

type EraseSimsParams = z.infer<typeof eraseSimsSchema>;

async function eraseSingle(udid: string, executor: CommandExecutor): Promise<ToolResponse> {
const result = await executor(
['xcrun', 'simctl', 'erase', udid],
'Erase Simulator',
true,
undefined,
);
if (result.success) {
return { content: [{ type: 'text', text: `Successfully erased simulator ${udid}` }] };
}
return {
content: [
{ type: 'text', text: `Failed to erase simulator: ${result.error ?? 'Unknown error'}` },
],
};
}

export async function erase_simsLogic(
params: EraseSimsParams,
executor: CommandExecutor,
): Promise<ToolResponse> {
try {
if (params.simulatorUuid) {
log('info', `Erasing simulator ${params.simulatorUuid}`);
return await eraseSingle(params.simulatorUuid, executor);
}

Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
if (params.all === true) {
log('info', 'Erasing ALL simulators');
const result = await executor(
['xcrun', 'simctl', 'erase', 'all'],
'Erase All Simulators',
true,
undefined,
);
if (!result.success) {
return {
content: [
{
type: 'text',
text: `Failed to erase all simulators: ${result.error ?? 'Unknown error'}`,
},
],
};
}
return { content: [{ type: 'text', text: 'Successfully erased all simulators' }] };
}

return {
content: [{ type: 'text', text: 'Invalid parameters: provide simulatorUuid or all=true.' }],
};
Comment thread
coderabbitai[bot] marked this conversation as resolved.
} catch (error: unknown) {
const message = error instanceof Error ? error.message : String(error);
log('error', `Error erasing simulators: ${message}`);
return { content: [{ type: 'text', text: `Failed to erase simulators: ${message}` }] };
}
}

export default {
name: 'erase_sims',
description:
'Erases simulator content and settings. Provide exactly one of: simulatorUuid or all=true.',
schema: eraseSimsBaseSchema.shape,
handler: createTypedTool(eraseSimsSchema, erase_simsLogic, getDefaultCommandExecutor),
};
7 changes: 4 additions & 3 deletions src/mcp/tools/simulator-management/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,16 @@
* Simulator Management workflow
*
* Provides tools for working with simulators like booting and opening simulators, launching apps,
* listing sims, stopping apps and setting sim environment options like location, network, statusbar and appearance.
* listing sims, stopping apps, erasing simulator content and settings, and setting sim environment
* options like location, network, statusbar and appearance.
*/

export const workflow = {
name: 'Simulator Management',
description:
'Tools for managing simulators from booting, opening simulators, listing simulators, stopping simulators and setting simulator environment options like location, network, statusbar and appearance.',
'Tools for managing simulators from booting, opening simulators, listing simulators, stopping simulators, erasing simulator content and settings, and setting simulator environment options like location, network, statusbar and appearance.',
platforms: ['iOS'],
targets: ['simulator'],
projectTypes: ['project', 'workspace'],
capabilities: ['boot', 'open', 'list', 'appearance', 'location', 'network', 'statusbar'],
capabilities: ['boot', 'open', 'list', 'appearance', 'location', 'network', 'statusbar', 'erase'],
};
Loading