Skip to main content
Glama

XC-MCP: XCode CLI wrapper

by conorluddy
simctl-get-app-container.test.ts14.5 kB
import { simctlGetAppContainerTool } from '../../../src/tools/simctl/get-app-container.js'; import { simulatorCache } from '../../../src/state/simulator-cache.js'; import { McpError } from '@modelcontextprotocol/sdk/types.js'; // Mock the simulator cache jest.mock('../../../src/state/simulator-cache.js', () => ({ simulatorCache: { findSimulatorByUdid: jest.fn(), getSimulatorList: jest.fn(), }, })); // Mock command execution jest.mock('../../../src/utils/command.js', () => ({ executeCommand: jest.fn().mockResolvedValue({ code: 0, stdout: '/Users/conor/Library/Developer/CoreSimulator/Devices/device-id/data/Containers/Bundle/Application/app-uuid', stderr: '', }), })); const mockSimulatorCache = simulatorCache as jest.Mocked<typeof simulatorCache>; describe('simctlGetAppContainerTool', () => { const validUDID = 'device-iphone16pro'; const validBundleID = 'com.example.MyApp'; const validContainerPath = '/Users/conor/Library/Developer/CoreSimulator/Devices/device-id/data/Containers/Bundle/Application/app-uuid'; const validSimulator = { name: 'iPhone 16 Pro', udid: validUDID, state: 'Booted', isAvailable: true, }; beforeEach(() => { jest.clearAllMocks(); mockSimulatorCache.findSimulatorByUdid.mockResolvedValue(validSimulator as any); const { executeCommand } = require('../../../src/utils/command.js'); executeCommand.mockResolvedValue({ code: 0, stdout: validContainerPath, stderr: '', }); }); describe('successful container lookup', () => { it('should return app container path', async () => { const result = await simctlGetAppContainerTool({ udid: validUDID, bundleId: validBundleID, }); expect(result.isError).toBe(false); const response = JSON.parse(result.content[0].text); expect(response.success).toBe(true); expect(response.containerPath).toBe(validContainerPath); }); it('should return simulator information', async () => { const result = await simctlGetAppContainerTool({ udid: validUDID, bundleId: validBundleID, }); const response = JSON.parse(result.content[0].text); expect(response.simulatorInfo).toBeDefined(); expect(response.simulatorInfo.name).toBe('iPhone 16 Pro'); expect(response.simulatorInfo.udid).toBe(validUDID); }); it('should include bundle ID in response', async () => { const result = await simctlGetAppContainerTool({ udid: validUDID, bundleId: validBundleID, }); const response = JSON.parse(result.content[0].text); expect(response.bundleId).toBe(validBundleID); }); it('should provide guidance for accessing container', async () => { const result = await simctlGetAppContainerTool({ udid: validUDID, bundleId: validBundleID, }); const response = JSON.parse(result.content[0].text); expect(response.guidance).toBeDefined(); expect(Array.isArray(response.guidance)).toBe(true); expect(response.guidance.length).toBeGreaterThan(0); }); it('should format response correctly', async () => { const result = await simctlGetAppContainerTool({ udid: validUDID, bundleId: validBundleID, }); expect(result.content).toBeDefined(); expect(result.content[0]).toBeDefined(); expect(result.content[0].type).toBe('text'); expect(typeof result.content[0].text).toBe('string'); expect(result.isError).toBe(false); }); }); describe('input validation', () => { it('should reject empty UDID', async () => { await expect( simctlGetAppContainerTool({ udid: '', bundleId: validBundleID, }) ).rejects.toThrow(McpError); }); it('should reject empty bundle ID', async () => { await expect( simctlGetAppContainerTool({ udid: validUDID, bundleId: '', }) ).rejects.toThrow(McpError); }); it('should reject invalid bundle ID format', async () => { await expect( simctlGetAppContainerTool({ udid: validUDID, bundleId: 'invalid bundle id', }) ).rejects.toThrow(McpError); }); it('should reject non-existent simulator', async () => { mockSimulatorCache.findSimulatorByUdid.mockResolvedValueOnce(null); await expect( simctlGetAppContainerTool({ udid: 'invalid-udid', bundleId: validBundleID, }) ).rejects.toThrow(McpError); }); it('should handle whitespace-only inputs', async () => { await expect( simctlGetAppContainerTool({ udid: ' ', bundleId: ' ', }) ).rejects.toThrow(McpError); }); }); describe('container path parsing', () => { it('should handle standard container paths', async () => { const standardPath = '/Users/conor/Library/Developer/CoreSimulator/Devices/UUID/data/Containers/Bundle/Application/app-uuid'; const { executeCommand } = require('../../../src/utils/command.js'); executeCommand.mockResolvedValueOnce({ code: 0, stdout: standardPath, stderr: '', }); const result = await simctlGetAppContainerTool({ udid: validUDID, bundleId: validBundleID, }); const response = JSON.parse(result.content[0].text); expect(response.containerPath).toBe(standardPath); }); it('should extract app name from bundle ID', async () => { const result = await simctlGetAppContainerTool({ udid: validUDID, bundleId: 'com.example.MyApp', }); const response = JSON.parse(result.content[0].text); expect(response.bundleId).toBe('com.example.MyApp'); }); it('should handle paths with spaces', async () => { const pathWithSpaces = '/Users/conor/Library/Developer/CoreSimulator/Devices/device-id/data/Containers/Bundle/Application/app with spaces'; const { executeCommand } = require('../../../src/utils/command.js'); executeCommand.mockResolvedValueOnce({ code: 0, stdout: pathWithSpaces, stderr: '', }); const result = await simctlGetAppContainerTool({ udid: validUDID, bundleId: validBundleID, }); const response = JSON.parse(result.content[0].text); expect(response.containerPath).toBe(pathWithSpaces); }); }); describe('optional container type parameter', () => { it('should accept bundle container type', async () => { const result = await simctlGetAppContainerTool({ udid: validUDID, bundleId: validBundleID, containerType: 'bundle', }); expect(result.isError).toBe(false); }); it('should accept data container type', async () => { const result = await simctlGetAppContainerTool({ udid: validUDID, bundleId: validBundleID, containerType: 'data', }); expect(result.isError).toBe(false); }); it('should accept group container type', async () => { const result = await simctlGetAppContainerTool({ udid: validUDID, bundleId: validBundleID, containerType: 'group', }); expect(result.isError).toBe(false); }); it('should handle undefined container type', async () => { const result = await simctlGetAppContainerTool({ udid: validUDID, bundleId: validBundleID, containerType: undefined, }); expect(result.isError).toBe(false); }); }); describe('simulator state handling', () => { it('should work with booted simulator', async () => { const bootedSimulator = { ...validSimulator, state: 'Booted' }; mockSimulatorCache.findSimulatorByUdid.mockResolvedValueOnce(bootedSimulator as any); const result = await simctlGetAppContainerTool({ udid: validUDID, bundleId: validBundleID, }); expect(result.isError).toBe(false); }); it('should work with shutdown simulator', async () => { const shutdownSimulator = { ...validSimulator, state: 'Shutdown' }; mockSimulatorCache.findSimulatorByUdid.mockResolvedValueOnce(shutdownSimulator as any); const result = await simctlGetAppContainerTool({ udid: validUDID, bundleId: validBundleID, }); expect(result.isError).toBe(false); }); it('should warn about unavailable simulator', async () => { const unavailableSimulator = { ...validSimulator, isAvailable: false }; mockSimulatorCache.findSimulatorByUdid.mockResolvedValueOnce(unavailableSimulator as any); const result = await simctlGetAppContainerTool({ udid: validUDID, bundleId: validBundleID, }); const response = JSON.parse(result.content[0].text); expect(response.guidance.some((g: string) => g.includes('unavailable'))).toBe(true); }); }); describe('error handling', () => { it('should handle app not installed error', async () => { const { executeCommand } = require('../../../src/utils/command.js'); executeCommand.mockResolvedValueOnce({ code: 1, stdout: '', stderr: 'Error: App not found', }); const result = await simctlGetAppContainerTool({ udid: validUDID, bundleId: validBundleID, }); expect(result.isError).toBe(true); const response = JSON.parse(result.content[0].text); expect(response.error).toBeDefined(); }); it('should handle command execution failure', async () => { const { executeCommand } = require('../../../src/utils/command.js'); executeCommand.mockRejectedValueOnce(new Error('Failed to get container')); await expect( simctlGetAppContainerTool({ udid: validUDID, bundleId: validBundleID, }) ).rejects.toThrow(McpError); }); it('should handle simulator cache lookup error', async () => { mockSimulatorCache.findSimulatorByUdid.mockRejectedValueOnce(new Error('Cache error')); await expect( simctlGetAppContainerTool({ udid: validUDID, bundleId: validBundleID, }) ).rejects.toThrow(McpError); }); it('should handle empty response from simctl', async () => { const { executeCommand } = require('../../../src/utils/command.js'); executeCommand.mockResolvedValueOnce({ code: 0, stdout: '', stderr: '', }); const result = await simctlGetAppContainerTool({ udid: validUDID, bundleId: validBundleID, }); const response = JSON.parse(result.content[0].text); expect(response.success).toBe(false); }); }); describe('response format', () => { it('should include all required fields on success', async () => { const result = await simctlGetAppContainerTool({ udid: validUDID, bundleId: validBundleID, }); const response = JSON.parse(result.content[0].text); expect(response).toHaveProperty('success'); expect(response).toHaveProperty('udid'); expect(response).toHaveProperty('bundleId'); expect(response).toHaveProperty('containerPath'); expect(response).toHaveProperty('simulatorInfo'); expect(response).toHaveProperty('guidance'); }); it('should include error details on failure', async () => { mockSimulatorCache.findSimulatorByUdid.mockResolvedValueOnce(null); await expect( simctlGetAppContainerTool({ udid: 'invalid', bundleId: validBundleID, }) ).rejects.toThrow(McpError); }); it('should be valid JSON', async () => { const result = await simctlGetAppContainerTool({ udid: validUDID, bundleId: validBundleID, }); // Should not throw const response = JSON.parse(result.content[0].text); expect(response).toBeDefined(); }); }); describe('practical usage scenarios', () => { it('should help locate app documents', async () => { const result = await simctlGetAppContainerTool({ udid: validUDID, bundleId: 'com.example.DocumentApp', containerType: 'data', }); const response = JSON.parse(result.content[0].text); expect( response.guidance.some((g: string) => g.includes('documents') || g.includes('data')) ).toBe(true); }); it('should provide path for simulator file access', async () => { const result = await simctlGetAppContainerTool({ udid: validUDID, bundleId: validBundleID, }); const response = JSON.parse(result.content[0].text); expect( response.guidance.some( (g: string) => g.toLowerCase().includes('access') || g.toLowerCase().includes('file') ) ).toBe(true); }); it('should help debugging with container path', async () => { const result = await simctlGetAppContainerTool({ udid: validUDID, bundleId: validBundleID, }); const response = JSON.parse(result.content[0].text); expect(response.containerPath).toBeDefined(); }); }); describe('edge cases', () => { it('should handle very long bundle IDs', async () => { const longBundleId = 'com.' + 'a'.repeat(200) + '.app'; const result = await simctlGetAppContainerTool({ udid: validUDID, bundleId: longBundleId, }); const response = JSON.parse(result.content[0].text); expect(response.bundleId).toBe(longBundleId); }); it('should handle concurrent container lookups', async () => { const result1 = await simctlGetAppContainerTool({ udid: validUDID, bundleId: 'com.example.app1', }); const result2 = await simctlGetAppContainerTool({ udid: validUDID, bundleId: 'com.example.app2', }); expect(result1.isError).toBe(false); expect(result2.isError).toBe(false); }); it('should handle very long UDID values', async () => { const longUDID = 'a'.repeat(100); mockSimulatorCache.findSimulatorByUdid.mockResolvedValueOnce({ ...validSimulator, udid: longUDID, } as any); const result = await simctlGetAppContainerTool({ udid: longUDID, bundleId: validBundleID, }); expect(result.isError).toBe(false); }); }); });

Latest Blog Posts

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