Skip to content
Open
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
356 changes: 143 additions & 213 deletions src/mcp/tools/device/__tests__/build_device.test.ts

Large diffs are not rendered by default.

537 changes: 316 additions & 221 deletions src/mcp/tools/device/__tests__/build_run_device.test.ts

Large diffs are not rendered by default.

207 changes: 88 additions & 119 deletions src/mcp/tools/device/__tests__/get_device_app_path.test.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,14 @@
/**
* Tests for get_device_app_path plugin (unified)
* Following CLAUDE.md testing standards with literal validation
* Using dependency injection for deterministic testing
*/

import { describe, it, expect, beforeEach } from 'vitest';
import { DERIVED_DATA_DIR } from '../../../../utils/log-paths.ts';
import * as z from 'zod';
import {
createMockCommandResponse,
createMockExecutor,
} from '../../../../test-utils/mock-executors.ts';
import { schema, handler, get_device_app_pathLogic } from '../get_device_app_path.ts';
import { sessionStore } from '../../../../utils/session-store.ts';
import { runLogic } from '../../../../test-utils/test-helpers.ts';


describe('get_device_app_path plugin', () => {
beforeEach(() => {
Expand Down Expand Up @@ -107,12 +104,14 @@ describe('get_device_app_path plugin', () => {
);
};

await get_device_app_pathLogic(
{
projectPath: '/path/to/project.xcodeproj',
scheme: 'MyScheme',
},
mockExecutor,
await runLogic(() =>
get_device_app_pathLogic(
{
projectPath: '/path/to/project.xcodeproj',
scheme: 'MyScheme',
},
mockExecutor,
),
);

expect(calls).toHaveLength(1);
Expand All @@ -128,6 +127,8 @@ describe('get_device_app_path plugin', () => {
'Debug',
'-destination',
'generic/platform=iOS',
'-derivedDataPath',
DERIVED_DATA_DIR,
],
logPrefix: 'Get App Path',
useShell: false,
Expand Down Expand Up @@ -161,13 +162,15 @@ describe('get_device_app_path plugin', () => {
);
};

await get_device_app_pathLogic(
{
projectPath: '/path/to/project.xcodeproj',
scheme: 'MyScheme',
platform: 'watchOS',
},
mockExecutor,
await runLogic(() =>
get_device_app_pathLogic(
{
projectPath: '/path/to/project.xcodeproj',
scheme: 'MyScheme',
platform: 'watchOS',
},
mockExecutor,
),
);

expect(calls).toHaveLength(1);
Expand All @@ -183,6 +186,8 @@ describe('get_device_app_path plugin', () => {
'Debug',
'-destination',
'generic/platform=watchOS',
'-derivedDataPath',
DERIVED_DATA_DIR,
],
logPrefix: 'Get App Path',
useShell: false,
Expand Down Expand Up @@ -216,12 +221,14 @@ describe('get_device_app_path plugin', () => {
);
};

await get_device_app_pathLogic(
{
workspacePath: '/path/to/workspace.xcworkspace',
scheme: 'MyScheme',
},
mockExecutor,
await runLogic(() =>
get_device_app_pathLogic(
{
workspacePath: '/path/to/workspace.xcworkspace',
scheme: 'MyScheme',
},
mockExecutor,
),
);

expect(calls).toHaveLength(1);
Expand All @@ -237,6 +244,8 @@ describe('get_device_app_path plugin', () => {
'Debug',
'-destination',
'generic/platform=iOS',
'-derivedDataPath',
DERIVED_DATA_DIR,
],
logPrefix: 'Get App Path',
useShell: false,
Expand All @@ -251,29 +260,24 @@ describe('get_device_app_path plugin', () => {
'Build settings for scheme "MyScheme"\n\nBUILT_PRODUCTS_DIR = /path/to/build/Debug-iphoneos\nFULL_PRODUCT_NAME = MyApp.app\n',
});

const result = await get_device_app_pathLogic(
{
projectPath: '/path/to/project.xcodeproj',
scheme: 'MyScheme',
},
mockExecutor,
);

expect(result).toEqual({
content: [
const result = await runLogic(() =>
get_device_app_pathLogic(
{
type: 'text',
text: '✅ App path retrieved successfully: /path/to/build/Debug-iphoneos/MyApp.app',
},
],
nextStepParams: {
get_app_bundle_id: { appPath: '/path/to/build/Debug-iphoneos/MyApp.app' },
install_app_device: {
deviceId: 'DEVICE_UDID',
appPath: '/path/to/build/Debug-iphoneos/MyApp.app',
projectPath: '/path/to/project.xcodeproj',
scheme: 'MyScheme',
},
launch_app_device: { deviceId: 'DEVICE_UDID', bundleId: 'BUNDLE_ID' },
mockExecutor,
),
);

expect(result.isError).toBeFalsy();
expect(result.nextStepParams).toEqual({
get_app_bundle_id: { appPath: '/path/to/build/Debug-iphoneos/MyApp.app' },
install_app_device: {
deviceId: 'DEVICE_UDID',
appPath: '/path/to/build/Debug-iphoneos/MyApp.app',
},
launch_app_device: { deviceId: 'DEVICE_UDID', bundleId: 'BUNDLE_ID' },
});
});

Expand All @@ -283,23 +287,18 @@ describe('get_device_app_path plugin', () => {
error: 'xcodebuild: error: The project does not exist.',
});

const result = await get_device_app_pathLogic(
{
projectPath: '/path/to/nonexistent.xcodeproj',
scheme: 'MyScheme',
},
mockExecutor,
);

expect(result).toEqual({
content: [
const result = await runLogic(() =>
get_device_app_pathLogic(
{
type: 'text',
text: 'Failed to get app path: xcodebuild: error: The project does not exist.',
projectPath: '/path/to/nonexistent.xcodeproj',
scheme: 'MyScheme',
},
],
isError: true,
});
mockExecutor,
),
);

expect(result.isError).toBe(true);
expect(result.nextStepParams).toBeUndefined();
});

it('should return exact parse failure response', async () => {
Expand All @@ -308,23 +307,18 @@ describe('get_device_app_path plugin', () => {
output: 'Build settings without required fields',
});

const result = await get_device_app_pathLogic(
{
projectPath: '/path/to/project.xcodeproj',
scheme: 'MyScheme',
},
mockExecutor,
);

expect(result).toEqual({
content: [
const result = await runLogic(() =>
get_device_app_pathLogic(
{
type: 'text',
text: 'Failed to extract app path from build settings. Make sure the app has been built first.',
projectPath: '/path/to/project.xcodeproj',
scheme: 'MyScheme',
},
],
isError: true,
});
mockExecutor,
),
);

expect(result.isError).toBe(true);
expect(result.nextStepParams).toBeUndefined();
});

it('should include optional configuration parameter in command', async () => {
Expand Down Expand Up @@ -353,13 +347,15 @@ describe('get_device_app_path plugin', () => {
);
};

await get_device_app_pathLogic(
{
projectPath: '/path/to/project.xcodeproj',
scheme: 'MyScheme',
configuration: 'Release',
},
mockExecutor,
await runLogic(() =>
get_device_app_pathLogic(
{
projectPath: '/path/to/project.xcodeproj',
scheme: 'MyScheme',
configuration: 'Release',
},
mockExecutor,
),
);

expect(calls).toHaveLength(1);
Expand All @@ -375,6 +371,8 @@ describe('get_device_app_path plugin', () => {
'Release',
'-destination',
'generic/platform=iOS',
'-derivedDataPath',
DERIVED_DATA_DIR,
],
logPrefix: 'Get App Path',
useShell: false,
Expand All @@ -393,47 +391,18 @@ describe('get_device_app_path plugin', () => {
return Promise.reject(new Error('Network error'));
};

const result = await get_device_app_pathLogic(
{
projectPath: '/path/to/project.xcodeproj',
scheme: 'MyScheme',
},
mockExecutor,
);

expect(result).toEqual({
content: [
const result = await runLogic(() =>
get_device_app_pathLogic(
{
type: 'text',
text: 'Error retrieving app path: Network error',
projectPath: '/path/to/project.xcodeproj',
scheme: 'MyScheme',
},
],
isError: true,
});
});

it('should return exact string error handling response', async () => {
const mockExecutor = () => {
return Promise.reject('String error');
};

const result = await get_device_app_pathLogic(
{
projectPath: '/path/to/project.xcodeproj',
scheme: 'MyScheme',
},
mockExecutor,
mockExecutor,
),
);

expect(result).toEqual({
content: [
{
type: 'text',
text: 'Error retrieving app path: String error',
},
],
isError: true,
});
expect(result.isError).toBe(true);
expect(result.nextStepParams).toBeUndefined();
});
});
});
Loading