Skip to main content
Glama

XC-MCP: XCode CLI wrapper

by conorluddy
idb-target-cache.test.ts7.73 kB
import { jest } from '@jest/globals'; import { IDBTargetCache } from '../../../src/state/idb-target-cache.js'; import * as command from '../../../src/utils/command.js'; // Mock the command module jest.mock('../../../src/utils/command.js'); const mockExecuteCommand = command.executeCommand as jest.MockedFunction< typeof command.executeCommand >; describe('IDBTargetCache', () => { beforeEach(() => { jest.clearAllMocks(); IDBTargetCache.clearCache(); }); describe('Cache TTL Fix', () => { it('should use 5-second TTL by default to prevent stale boot state', () => { const stats = IDBTargetCache.getCacheStats(); expect(stats.ttl).toBe(5000); // 5 seconds, not 60 seconds }); it('should refresh cache after 5 seconds', async () => { // Mock idb list-targets output - first call shows Shutdown mockExecuteCommand.mockResolvedValueOnce({ code: 0, stdout: JSON.stringify({ udid: 'test-udid', name: 'iPhone 16 Pro', type: 'simulator', state: 'Shutdown', os_version: 'iOS 18.5', architecture: 'arm64', screen_dimensions: { width: 390, height: 844 }, }), stderr: '', }); // First call - should fetch from IDB const target1 = await IDBTargetCache.getTarget('test-udid'); expect(target1.state).toBe('Shutdown'); expect(mockExecuteCommand).toHaveBeenCalledTimes(1); // Second call immediately - should use cache const target2 = await IDBTargetCache.getTarget('test-udid'); expect(target2.state).toBe('Shutdown'); expect(mockExecuteCommand).toHaveBeenCalledTimes(1); // Still 1 - cache hit // Mock idb list-targets output - second call shows Booted mockExecuteCommand.mockResolvedValueOnce({ code: 0, stdout: JSON.stringify({ udid: 'test-udid', name: 'iPhone 16 Pro', type: 'simulator', state: 'Booted', os_version: 'iOS 18.5', architecture: 'arm64', screen_dimensions: { width: 390, height: 844 }, }), stderr: '', }); // Wait for cache to expire (5+ seconds) await new Promise(resolve => setTimeout(resolve, 5100)); // Third call - should refresh cache and get new state const target3 = await IDBTargetCache.getTarget('test-udid'); expect(target3.state).toBe('Booted'); expect(mockExecuteCommand).toHaveBeenCalledTimes(2); // Cache refresh }); it('should allow custom TTL configuration', () => { IDBTargetCache.setCacheTTL(10000); // 10 seconds const stats = IDBTargetCache.getCacheStats(); expect(stats.ttl).toBe(10000); // Reset to default IDBTargetCache.setCacheTTL(5000); }); it('should clear cache and force immediate refresh', async () => { // Mock idb list-targets output - first call mockExecuteCommand.mockResolvedValueOnce({ code: 0, stdout: JSON.stringify({ udid: 'test-udid', name: 'iPhone 16 Pro', type: 'simulator', state: 'Shutdown', os_version: 'iOS 18.5', architecture: 'arm64', screen_dimensions: { width: 390, height: 844 }, }), stderr: '', }); // First call - populate cache await IDBTargetCache.getTarget('test-udid'); expect(mockExecuteCommand).toHaveBeenCalledTimes(1); // Clear cache IDBTargetCache.clearCache(); const stats = IDBTargetCache.getCacheStats(); expect(stats.cacheAge).toBeGreaterThan(0); // lastFetched reset to 0 // Mock idb list-targets output - second call mockExecuteCommand.mockResolvedValueOnce({ code: 0, stdout: JSON.stringify({ udid: 'test-udid', name: 'iPhone 16 Pro', type: 'simulator', state: 'Booted', os_version: 'iOS 18.5', architecture: 'arm64', screen_dimensions: { width: 390, height: 844 }, }), stderr: '', }); // Next call should refresh immediately const target = await IDBTargetCache.getTarget('test-udid'); expect(target.state).toBe('Booted'); expect(mockExecuteCommand).toHaveBeenCalledTimes(2); }); }); describe('NDJSON Parsing', () => { it('should correctly parse NDJSON output from idb list-targets', async () => { // Mock NDJSON output (multiple JSON objects, one per line) const ndjsonOutput = [ JSON.stringify({ udid: 'uuid-1', name: 'iPhone 16 Pro', type: 'simulator', state: 'Booted', os_version: 'iOS 18.5', }), JSON.stringify({ udid: 'uuid-2', name: 'iPad Pro', type: 'simulator', state: 'Shutdown', os_version: 'iOS 18.5', }), ].join('\n'); mockExecuteCommand.mockResolvedValueOnce({ code: 0, stdout: ndjsonOutput, stderr: '', }); const targets = await IDBTargetCache.listTargets(); expect(targets).toHaveLength(2); expect(targets[0].udid).toBe('uuid-1'); expect(targets[0].state).toBe('Booted'); expect(targets[1].udid).toBe('uuid-2'); expect(targets[1].state).toBe('Shutdown'); }); it('should handle empty lines in NDJSON output', async () => { const ndjsonOutput = [ JSON.stringify({ udid: 'uuid-1', name: 'iPhone 16 Pro', type: 'simulator', state: 'Booted', os_version: 'iOS 18.5', }), '', // Empty line JSON.stringify({ udid: 'uuid-2', name: 'iPad Pro', type: 'simulator', state: 'Shutdown', os_version: 'iOS 18.5', }), ].join('\n'); mockExecuteCommand.mockResolvedValueOnce({ code: 0, stdout: ndjsonOutput, stderr: '', }); const targets = await IDBTargetCache.listTargets(); expect(targets).toHaveLength(2); // Empty line ignored }); }); describe('State Filtering', () => { beforeEach(async () => { // Mock multiple targets with different states const ndjsonOutput = [ JSON.stringify({ udid: 'booted-1', name: 'iPhone 16 Pro', type: 'simulator', state: 'Booted', os_version: 'iOS 18.5', }), JSON.stringify({ udid: 'shutdown-1', name: 'iPad Pro', type: 'simulator', state: 'Shutdown', os_version: 'iOS 18.5', }), JSON.stringify({ udid: 'booted-2', name: 'iPhone 15', type: 'simulator', state: 'Booted', os_version: 'iOS 17.0', }), ].join('\n'); mockExecuteCommand.mockResolvedValueOnce({ code: 0, stdout: ndjsonOutput, stderr: '', }); }); it('should filter targets by Booted state', async () => { const bootedTargets = await IDBTargetCache.listTargets({ state: 'Booted' }); expect(bootedTargets).toHaveLength(2); expect(bootedTargets.every(t => t.state === 'Booted')).toBe(true); }); it('should filter targets by Shutdown state', async () => { const shutdownTargets = await IDBTargetCache.listTargets({ state: 'Shutdown' }); expect(shutdownTargets).toHaveLength(1); expect(shutdownTargets[0].udid).toBe('shutdown-1'); }); it('should return last used booted target', async () => { const lastUsed = await IDBTargetCache.getLastUsedTarget(); expect(lastUsed).toBeDefined(); expect(lastUsed?.state).toBe('Booted'); }); }); });

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