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
24 changes: 12 additions & 12 deletions src/mcp/tools/device/__tests__/build_device.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,16 +132,16 @@ describe('build_device plugin', () => {
args: string[];
logPrefix: string;
silent: boolean;
timeout: number | undefined;
opts: { cwd?: string } | undefined;
}> = [];

const stubExecutor = async (
args: string[],
logPrefix: string,
silent: boolean,
timeout?: number,
opts?: { cwd?: string },
) => {
commandCalls.push({ args, logPrefix, silent, timeout });
commandCalls.push({ args, logPrefix, silent, opts });
return {
success: true,
output: 'Build succeeded',
Expand Down Expand Up @@ -175,7 +175,7 @@ describe('build_device plugin', () => {
],
logPrefix: 'iOS Device Build',
silent: true,
timeout: undefined,
opts: { cwd: '/path/to' },
});
});

Expand All @@ -184,16 +184,16 @@ describe('build_device plugin', () => {
args: string[];
logPrefix: string;
silent: boolean;
timeout: number | undefined;
opts: { cwd?: string } | undefined;
}> = [];

const stubExecutor = async (
args: string[],
logPrefix: string,
silent: boolean,
timeout?: number,
opts?: { cwd?: string },
) => {
commandCalls.push({ args, logPrefix, silent, timeout });
commandCalls.push({ args, logPrefix, silent, opts });
return {
success: true,
output: 'Build succeeded',
Expand Down Expand Up @@ -227,7 +227,7 @@ describe('build_device plugin', () => {
],
logPrefix: 'iOS Device Build',
silent: true,
timeout: undefined,
opts: { cwd: '/path/to' },
});
});

Expand Down Expand Up @@ -293,16 +293,16 @@ describe('build_device plugin', () => {
args: string[];
logPrefix: string;
silent: boolean;
timeout: number | undefined;
opts: { cwd?: string } | undefined;
}> = [];

const stubExecutor = async (
args: string[],
logPrefix: string,
silent: boolean,
timeout?: number,
opts?: { cwd?: string },
) => {
commandCalls.push({ args, logPrefix, silent, timeout });
commandCalls.push({ args, logPrefix, silent, opts });
return {
success: true,
output: 'Build succeeded',
Expand Down Expand Up @@ -342,7 +342,7 @@ describe('build_device plugin', () => {
],
logPrefix: 'iOS Device Build',
silent: true,
timeout: undefined,
opts: { cwd: '/path/to' },
});
});
});
Expand Down
18 changes: 9 additions & 9 deletions src/mcp/tools/macos/__tests__/build_run_macos.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,10 +82,10 @@ describe('build_run_macos', () => {
command: string[],
description: string,
logOutput: boolean,
timeout?: number,
opts?: { cwd?: string },
) => {
callCount++;
executorCalls.push({ command, description, logOutput, timeout });
executorCalls.push({ command, description, logOutput, opts });

if (callCount === 1) {
// First call for build
Expand Down Expand Up @@ -131,7 +131,7 @@ describe('build_run_macos', () => {
],
description: 'macOS Build',
logOutput: true,
timeout: undefined,
opts: { cwd: '/path/to' },
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test expectations use timeout but stub tracks opts

Medium Severity

The mock executor stub was refactored to track opts instead of timeout (line 88 pushes { command, description, logOutput, opts }), but the test expectations for executorCalls[1] at lines 151 and 247 still assert timeout: undefined. This mismatch causes the toEqual assertion to fail because the actual object has an opts property while the expected object has a timeout property. These should be changed to opts: undefined to match the stub's tracking structure.

Additional Locations (1)

Fix in Cursor Fix in Web

});

// Verify build settings command was called
Expand Down Expand Up @@ -178,10 +178,10 @@ describe('build_run_macos', () => {
command: string[],
description: string,
logOutput: boolean,
timeout?: number,
opts?: { cwd?: string },
) => {
callCount++;
executorCalls.push({ command, description, logOutput, timeout });
executorCalls.push({ command, description, logOutput, opts });

if (callCount === 1) {
// First call for build
Expand Down Expand Up @@ -227,7 +227,7 @@ describe('build_run_macos', () => {
],
description: 'macOS Build',
logOutput: true,
timeout: undefined,
opts: { cwd: '/path/to' },
});

// Verify build settings command was called
Expand Down Expand Up @@ -445,10 +445,10 @@ describe('build_run_macos', () => {
command: string[],
description: string,
logOutput: boolean,
timeout?: number,
opts?: { cwd?: string },
) => {
callCount++;
executorCalls.push({ command, description, logOutput, timeout });
executorCalls.push({ command, description, logOutput, opts });

if (callCount === 1) {
// First call for build
Expand Down Expand Up @@ -493,7 +493,7 @@ describe('build_run_macos', () => {
],
description: 'macOS Build',
logOutput: true,
timeout: undefined,
opts: { cwd: '/path/to' },
});
});
});
Expand Down
28 changes: 21 additions & 7 deletions src/test-utils/mock-executors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,13 @@ export function createMockExecutor(
process?: unknown;
exitCode?: number;
shouldThrow?: Error;
onExecute?: (
command: string[],
logPrefix?: string,
useShell?: boolean,
opts?: { env?: Record<string, string>; cwd?: string },
detached?: boolean,
) => void;
}
| Error
| string,
Expand Down Expand Up @@ -65,13 +72,20 @@ export function createMockExecutor(
spawnfile: 'sh',
} as unknown as ChildProcess;

return async () => ({
success: result.success ?? true,
output: result.output ?? '',
error: result.error,
process: (result.process ?? mockProcess) as ChildProcess,
exitCode: result.exitCode ?? (result.success === false ? 1 : 0),
});
return async (command, logPrefix, useShell, opts, detached) => {
// Call onExecute callback if provided
if (result.onExecute) {
result.onExecute(command, logPrefix, useShell, opts, detached);
}

return {
success: result.success ?? true,
output: result.output ?? '',
error: result.error,
process: (result.process ?? mockProcess) as ChildProcess,
exitCode: result.exitCode ?? (result.success === false ? 1 : 0),
};
};
}

/**
Expand Down
85 changes: 85 additions & 0 deletions src/utils/__tests__/build-utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -260,4 +260,89 @@ describe('build-utils Sentry Classification', () => {
expect(result.content[0].text).toContain('❌ [stderr] Some error without exit code');
});
});

describe('Working Directory (cwd) Handling', () => {
it('should pass project directory as cwd for workspace builds', async () => {
let capturedOptions: any;
const mockExecutor = createMockExecutor({
success: true,
output: 'BUILD SUCCEEDED',
exitCode: 0,
onExecute: (_command, _logPrefix, _useShell, opts) => {
capturedOptions = opts;
},
});

await executeXcodeBuildCommand(
{
scheme: 'TestScheme',
configuration: 'Debug',
workspacePath: '/path/to/project/MyProject.xcworkspace',
},
mockPlatformOptions,
false,
'build',
mockExecutor,
);

expect(capturedOptions).toBeDefined();
expect(capturedOptions.cwd).toBe('/path/to/project');
});

it('should pass project directory as cwd for project builds', async () => {
let capturedOptions: any;
const mockExecutor = createMockExecutor({
success: true,
output: 'BUILD SUCCEEDED',
exitCode: 0,
onExecute: (_command, _logPrefix, _useShell, opts) => {
capturedOptions = opts;
},
});

await executeXcodeBuildCommand(
{
scheme: 'TestScheme',
configuration: 'Debug',
projectPath: '/path/to/project/MyProject.xcodeproj',
},
mockPlatformOptions,
false,
'build',
mockExecutor,
);

expect(capturedOptions).toBeDefined();
expect(capturedOptions.cwd).toBe('/path/to/project');
});

it('should merge cwd with existing execOpts', async () => {
let capturedOptions: any;
const mockExecutor = createMockExecutor({
success: true,
output: 'BUILD SUCCEEDED',
exitCode: 0,
onExecute: (_command, _logPrefix, _useShell, opts) => {
capturedOptions = opts;
},
});

await executeXcodeBuildCommand(
{
scheme: 'TestScheme',
configuration: 'Debug',
workspacePath: '/path/to/project/MyProject.xcworkspace',
},
mockPlatformOptions,
false,
'build',
mockExecutor,
{ env: { CUSTOM_VAR: 'value' } },
);

expect(capturedOptions).toBeDefined();
expect(capturedOptions.cwd).toBe('/path/to/project');
expect(capturedOptions.env).toEqual({ CUSTOM_VAR: 'value' });
});
});
});
6 changes: 5 additions & 1 deletion src/utils/build-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,11 @@ export async function executeXcodeBuildCommand(
}
} else {
// Use standard xcodebuild
result = await executor(command, platformOptions.logPrefix, true, execOpts);
// Pass projectDir as cwd to ensure CocoaPods relative paths resolve correctly
result = await executor(command, platformOptions.logPrefix, true, {
...execOpts,
cwd: projectDir,
});
}

// Grep warnings and errors from stdout (build output)
Expand Down
Loading