Skip to main content
Glama

XC-MCP: XCode CLI wrapper

by conorluddy
simctl-shutdown.test.ts10.2 kB
import { jest } from '@jest/globals'; import { simctlShutdownTool } from '../../../src/tools/simctl/shutdown.js'; import { validateDeviceId } from '../../../src/utils/validation.js'; import { executeCommand, buildSimctlCommand } from '../../../src/utils/command.js'; import { McpError, ErrorCode } from '@modelcontextprotocol/sdk/types.js'; // Mock dependencies jest.mock('../../../src/utils/validation.js', () => ({ validateDeviceId: jest.fn(), })); jest.mock('../../../src/utils/command.js', () => ({ executeCommand: jest.fn(), buildSimctlCommand: jest.fn(), })); const mockValidateDeviceId = validateDeviceId as jest.MockedFunction<typeof validateDeviceId>; const mockExecuteCommand = executeCommand as jest.MockedFunction<typeof executeCommand>; const mockBuildSimctlCommand = buildSimctlCommand as jest.MockedFunction<typeof buildSimctlCommand>; // Mock console.error to suppress output during tests const mockConsoleError = jest.spyOn(console, 'error').mockImplementation(() => {}); describe('simctlShutdownTool', () => { beforeEach(() => { jest.clearAllMocks(); }); afterAll(() => { mockConsoleError.mockRestore(); }); const mockDeviceId = '12345678-1234-1234-1234-123456789012'; it('should shutdown device successfully', async () => { const args = { deviceId: mockDeviceId }; mockValidateDeviceId.mockReturnValue(undefined); mockBuildSimctlCommand.mockReturnValue( 'xcrun simctl shutdown 12345678-1234-1234-1234-123456789012' ); mockExecuteCommand.mockResolvedValue({ code: 0, stdout: 'Device shutdown successfully', stderr: '', }); // Mock Date.now for consistent timing const mockStartTime = 1640995200000; const mockEndTime = mockStartTime + 1500; // 1.5 seconds jest.spyOn(Date, 'now').mockReturnValueOnce(mockStartTime).mockReturnValueOnce(mockEndTime); const result = await simctlShutdownTool(args); expect(mockValidateDeviceId).toHaveBeenCalledWith(mockDeviceId); expect(mockBuildSimctlCommand).toHaveBeenCalledWith('shutdown', { deviceId: mockDeviceId }); expect(mockExecuteCommand).toHaveBeenCalledWith( 'xcrun simctl shutdown 12345678-1234-1234-1234-123456789012', { timeout: 60000 } ); expect(mockConsoleError).toHaveBeenCalledWith( '[simctl-shutdown] Executing: xcrun simctl shutdown 12345678-1234-1234-1234-123456789012' ); expect(result.content).toHaveLength(1); expect(result.content[0].type).toBe('text'); expect(result.isError).toBe(false); const response = JSON.parse(result.content[0].text); expect(response).toEqual({ success: true, command: 'xcrun simctl shutdown 12345678-1234-1234-1234-123456789012', output: 'Device shutdown successfully', error: '', exitCode: 0, duration: 1500, }); }); it('should handle device already shut down', async () => { const args = { deviceId: mockDeviceId }; mockValidateDeviceId.mockReturnValue(undefined); mockBuildSimctlCommand.mockReturnValue( 'xcrun simctl shutdown 12345678-1234-1234-1234-123456789012' ); mockExecuteCommand.mockResolvedValue({ code: 1, stdout: '', stderr: 'Unable to shutdown device in current state: Shutdown', }); jest.spyOn(Date, 'now').mockReturnValueOnce(1640995200000).mockReturnValueOnce(1640995200500); const result = await simctlShutdownTool(args); expect(result.isError).toBe(false); const response = JSON.parse(result.content[0].text); expect(response.success).toBe(true); expect(response.error).toBe('Device was already shut down'); expect(response.exitCode).toBe(1); }); it('should handle invalid device ID from simctl', async () => { const args = { deviceId: 'invalid-device-id' }; mockValidateDeviceId.mockReturnValue(undefined); mockBuildSimctlCommand.mockReturnValue('xcrun simctl shutdown invalid-device-id'); mockExecuteCommand.mockResolvedValue({ code: 1, stdout: '', stderr: 'Invalid device: invalid-device-id', }); await expect(simctlShutdownTool(args)).rejects.toThrow(McpError); await expect(simctlShutdownTool(args)).rejects.toThrow('Invalid device ID: invalid-device-id'); }); it('should handle validation errors', async () => { const args = { deviceId: 'invalid' }; mockValidateDeviceId.mockImplementation(() => { throw new McpError(ErrorCode.InvalidParams, 'Invalid device ID format'); }); await expect(simctlShutdownTool(args)).rejects.toThrow(McpError); await expect(simctlShutdownTool(args)).rejects.toThrow('Invalid device ID format'); expect(mockBuildSimctlCommand).not.toHaveBeenCalled(); expect(mockExecuteCommand).not.toHaveBeenCalled(); }); it('should handle command execution failure', async () => { const args = { deviceId: mockDeviceId }; mockValidateDeviceId.mockReturnValue(undefined); mockBuildSimctlCommand.mockReturnValue( 'xcrun simctl shutdown 12345678-1234-1234-1234-123456789012' ); mockExecuteCommand.mockResolvedValue({ code: 1, stdout: '', stderr: 'Simulator service connection interrupted', }); jest.spyOn(Date, 'now').mockReturnValueOnce(1640995200000).mockReturnValueOnce(1640995201000); const result = await simctlShutdownTool(args); expect(result.isError).toBe(true); const response = JSON.parse(result.content[0].text); expect(response.success).toBe(false); expect(response.error).toBe('Simulator service connection interrupted'); expect(response.exitCode).toBe(1); expect(response.duration).toBe(1000); }); it('should handle executeCommand throwing errors', async () => { const args = { deviceId: mockDeviceId }; mockValidateDeviceId.mockReturnValue(undefined); mockBuildSimctlCommand.mockReturnValue( 'xcrun simctl shutdown 12345678-1234-1234-1234-123456789012' ); mockExecuteCommand.mockRejectedValue(new Error('Command execution failed')); await expect(simctlShutdownTool(args)).rejects.toThrow(McpError); await expect(simctlShutdownTool(args)).rejects.toThrow( 'simctl-shutdown failed: Command execution failed' ); }); it('should handle non-Error exceptions', async () => { const args = { deviceId: mockDeviceId }; mockValidateDeviceId.mockReturnValue(undefined); mockBuildSimctlCommand.mockReturnValue( 'xcrun simctl shutdown 12345678-1234-1234-1234-123456789012' ); mockExecuteCommand.mockRejectedValue('String error'); await expect(simctlShutdownTool(args)).rejects.toThrow(McpError); await expect(simctlShutdownTool(args)).rejects.toThrow('simctl-shutdown failed: String error'); }); it('should preserve McpError instances', async () => { const args = { deviceId: mockDeviceId }; const originalError = new McpError(ErrorCode.InvalidParams, 'Original error'); mockValidateDeviceId.mockImplementation(() => { throw originalError; }); await expect(simctlShutdownTool(args)).rejects.toBe(originalError); }); it('should handle timeout correctly', async () => { const args = { deviceId: mockDeviceId }; mockValidateDeviceId.mockReturnValue(undefined); mockBuildSimctlCommand.mockReturnValue( 'xcrun simctl shutdown 12345678-1234-1234-1234-123456789012' ); mockExecuteCommand.mockResolvedValue({ code: 0, stdout: 'Success', stderr: '', }); jest.spyOn(Date, 'now').mockReturnValueOnce(1640995200000).mockReturnValueOnce(1640995260000); // 60 seconds later await simctlShutdownTool(args); expect(mockExecuteCommand).toHaveBeenCalledWith( 'xcrun simctl shutdown 12345678-1234-1234-1234-123456789012', { timeout: 60000 } // 1 minute timeout ); }); it('should log command execution', async () => { const args = { deviceId: mockDeviceId }; const expectedCommand = 'xcrun simctl shutdown 12345678-1234-1234-1234-123456789012'; mockValidateDeviceId.mockReturnValue(undefined); mockBuildSimctlCommand.mockReturnValue(expectedCommand); mockExecuteCommand.mockResolvedValue({ code: 0, stdout: 'Success', stderr: '', }); jest.spyOn(Date, 'now').mockReturnValueOnce(1640995200000).mockReturnValueOnce(1640995200100); await simctlShutdownTool(args); expect(mockConsoleError).toHaveBeenCalledWith( `[simctl-shutdown] Executing: ${expectedCommand}` ); }); it('should handle various shutdown error messages', async () => { const errorScenarios = [ { stderr: 'Unable to shutdown device in current state: Shutdown', expectedSuccess: true, expectedError: 'Device was already shut down', }, { stderr: 'Invalid device: some-invalid-id', shouldThrow: true, expectedMessage: 'Invalid device ID: 12345678-1234-1234-1234-123456789012', }, { stderr: 'Simulator service connection interrupted', expectedSuccess: false, expectedError: 'Simulator service connection interrupted', }, ]; for (const scenario of errorScenarios) { jest.clearAllMocks(); const args = { deviceId: mockDeviceId }; mockValidateDeviceId.mockReturnValue(undefined); mockBuildSimctlCommand.mockReturnValue( 'xcrun simctl shutdown 12345678-1234-1234-1234-123456789012' ); mockExecuteCommand.mockResolvedValue({ code: 1, stdout: '', stderr: scenario.stderr, }); jest.spyOn(Date, 'now').mockReturnValueOnce(1640995200000).mockReturnValueOnce(1640995200500); if (scenario.shouldThrow) { await expect(simctlShutdownTool(args)).rejects.toThrow(McpError); await expect(simctlShutdownTool(args)).rejects.toThrow(scenario.expectedMessage); } else { const result = await simctlShutdownTool(args); const response = JSON.parse(result.content[0].text); expect(response.success).toBe(scenario.expectedSuccess); expect(response.error).toBe(scenario.expectedError); expect(result.isError).toBe(!scenario.expectedSuccess); } } }); });

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/conorluddy/xc-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server