Skip to main content
Glama
integration.test.js11.9 kB
import { jest } from '@jest/globals'; import { EventEmitter } from 'events'; // Create the mock spawn function before importing anything const mockSpawn = jest.fn(); // Mock child_process before any imports jest.unstable_mockModule('child_process', () => ({ spawn: mockSpawn })); // Mock fs/promises jest.unstable_mockModule('fs/promises', () => ({ readdir: jest.fn(), stat: jest.fn() })); // Mock MCP SDK jest.mock('@modelcontextprotocol/sdk/server/index.js', () => ({ Server: jest.fn().mockImplementation(() => ({ setRequestHandler: jest.fn(), connect: jest.fn() })) })); jest.mock('@modelcontextprotocol/sdk/server/stdio.js', () => ({ StdioServerTransport: jest.fn() })); jest.mock('@modelcontextprotocol/sdk/types.js', () => ({ CallToolRequestSchema: 'CallToolRequestSchema', ErrorCode: { MethodNotFound: 'MethodNotFound', InternalError: 'InternalError' }, ListToolsRequestSchema: 'ListToolsRequestSchema', McpError: class McpError extends Error { constructor(code, message) { super(message); this.code = code; } } })); // Suppress console.error during tests const originalConsoleError = console.error; beforeAll(() => { console.error = jest.fn(); }); afterAll(() => { console.error = originalConsoleError; }); // Skip integration tests when Xcode is not available const describeIfXcode = process.env.SKIP_XCODE_TESTS ? describe.skip : describe; describeIfXcode('Integration Tests', () => { let XcodeMCPServer; let mockProcess; beforeAll(async () => { const module = await import('../dist/index.js'); XcodeMCPServer = module.default || module.XcodeMCPServer; }); beforeEach(() => { mockProcess = new EventEmitter(); mockProcess.stdout = new EventEmitter(); mockProcess.stderr = new EventEmitter(); mockProcess.kill = jest.fn(); jest.clearAllMocks(); mockSpawn.mockClear(); mockSpawn.mockReturnValue(mockProcess); }); afterEach(() => { jest.clearAllMocks(); }); describe('Complete Workflow Tests', () => { test('should handle complete build workflow', async () => { const server = new XcodeMCPServer(); // Step 1: Open project const openPromise = server.openProject('/Users/test/TestApp.xcodeproj'); setTimeout(() => { mockProcess.stdout.emit('data', 'Project opened successfully\n'); mockProcess.emit('close', 0); }, 10); const openResult = await openPromise; expect(openResult.content[0].text).toBe('Project opened successfully'); // Reset mock for next call mockProcess = new EventEmitter(); mockProcess.stdout = new EventEmitter(); mockProcess.stderr = new EventEmitter(); mockSpawn.mockReturnValue(mockProcess); // Step 2: Get workspace info const infoPromise = server.getWorkspaceInfo(); setTimeout(() => { const workspaceInfo = JSON.stringify({ name: 'TestApp.xcodeproj', path: '/Users/test/TestApp.xcodeproj', loaded: true, activeScheme: 'TestApp', activeRunDestination: 'iPhone 15 Simulator' }, null, 2); mockProcess.stdout.emit('data', workspaceInfo); mockProcess.emit('close', 0); }, 10); const infoResult = await infoPromise; const workspaceData = JSON.parse(infoResult.content[0].text); expect(workspaceData.loaded).toBe(true); expect(workspaceData.activeScheme).toBe('TestApp'); // Reset mock for next call mockProcess = new EventEmitter(); mockProcess.stdout = new EventEmitter(); mockProcess.stderr = new EventEmitter(); mockSpawn.mockReturnValue(mockProcess); // Step 3: Build project const buildPromise = server.build('/Users/test/TestApp.xcodeproj'); setTimeout(() => { mockProcess.stdout.emit('data', '/Users/test/TestApp.xcodeproj\n'); mockProcess.emit('close', 0); }, 10); const buildResult = await buildPromise; expect(buildResult.content[0].text).toContain('BUILD SUCCESSFUL'); }); test('should handle test workflow with custom arguments', async () => { const server = new XcodeMCPServer(); // Step 1: Get available schemes const schemesPromise = server.getSchemes(); setTimeout(() => { const schemes = JSON.stringify([ { name: 'TestApp', id: 'scheme-1', isActive: true }, { name: 'TestApp-Tests', id: 'scheme-2', isActive: false } ], null, 2); mockProcess.stdout.emit('data', schemes); mockProcess.emit('close', 0); }, 10); const schemesResult = await schemesPromise; const schemes = JSON.parse(schemesResult.content[0].text); expect(schemes).toHaveLength(2); // Reset mock for next call mockProcess = new EventEmitter(); mockProcess.stdout = new EventEmitter(); mockProcess.stderr = new EventEmitter(); mockSpawn.mockReturnValue(mockProcess); // Step 2: Set active scheme to test scheme const setSchemePromise = server.setActiveScheme('TestApp-Tests'); setTimeout(() => { mockProcess.stdout.emit('data', 'Active scheme set to: TestApp-Tests\n'); mockProcess.emit('close', 0); }, 10); const setSchemeResult = await setSchemePromise; expect(setSchemeResult.content[0].text).toContain('TestApp-Tests'); // Reset mock for next call mockProcess = new EventEmitter(); mockProcess.stdout = new EventEmitter(); mockProcess.stderr = new EventEmitter(); mockSpawn.mockReturnValue(mockProcess); // Step 3: Run tests with arguments const testArgs = ['--verbose', '--parallel-testing-enabled', 'YES']; const testPromise = server.test(testArgs); setTimeout(() => { mockProcess.stdout.emit('data', 'Test started. Result ID: test-456\n'); mockProcess.emit('close', 0); }, 10); const testResult = await testPromise; expect(testResult.content[0].text).toContain('Test started'); }); }); describe('Error Recovery Tests', () => { test('should handle Xcode not running error', async () => { const server = new XcodeMCPServer(); const buildPromise = server.build('/Users/test/TestApp.xcodeproj'); setTimeout(() => { mockProcess.stderr.emit('data', 'Error: Application "Xcode" is not running\n'); mockProcess.emit('close', 1); }, 10); await expect(buildPromise).rejects.toThrow('JXA execution failed'); }); test('should handle invalid scheme name', async () => { const server = new XcodeMCPServer(); const setSchemePromise = server.setActiveScheme('NonExistentScheme'); setTimeout(() => { mockProcess.stderr.emit('data', 'Error: Scheme "NonExistentScheme" not found\n'); mockProcess.emit('close', 1); }, 10); await expect(setSchemePromise).rejects.toThrow('JXA execution failed'); }); test('should handle invalid project path', async () => { const server = new XcodeMCPServer(); const openPromise = server.openProject('/invalid/path/Project.xcodeproj'); setTimeout(() => { mockProcess.stderr.emit('data', 'Error: File not found\n'); mockProcess.emit('close', 1); }, 10); await expect(openPromise).rejects.toThrow('JXA execution failed'); }); }); describe('Performance and Timeout Tests', () => { test('should handle slow JXA execution', async () => { const server = new XcodeMCPServer(); const buildPromise = server.build('/Users/test/TestApp.xcodeproj'); // Simulate slow response setTimeout(() => { mockProcess.stdout.emit('data', '/Users/test/TestApp.xcodeproj\n'); mockProcess.emit('close', 0); }, 100); // 100ms delay const result = await buildPromise; expect(result.content[0].text).toContain('BUILD SUCCESSFUL'); }); test('should handle multiple concurrent operations', async () => { const server = new XcodeMCPServer(); // Create multiple mock processes for concurrent calls const processes = []; mockSpawn.mockImplementation(() => { const proc = new EventEmitter(); proc.stdout = new EventEmitter(); proc.stderr = new EventEmitter(); processes.push(proc); return proc; }); // Start multiple operations concurrently const promises = [ server.getSchemes(), server.getRunDestinations(), server.getWorkspaceInfo() ]; // Simulate responses for all processes setTimeout(() => { processes.forEach((proc, index) => { const responses = [ JSON.stringify([{ name: 'Scheme1', id: '1', isActive: true }]), JSON.stringify([{ name: 'iPhone 15', platform: 'iOS', architecture: 'arm64', isActive: true }]), JSON.stringify({ name: 'Test', loaded: true }) ]; proc.stdout.emit('data', responses[index]); proc.emit('close', 0); }); }, 10); const results = await Promise.all(promises); expect(results).toHaveLength(3); results.forEach(result => { expect(result).toHaveProperty('content'); expect(result.content[0]).toHaveProperty('text'); }); }); }); describe('Real-world Scenario Tests', () => { test('should handle debugging workflow', async () => { const server = new XcodeMCPServer(); // Step 1: Get run destinations const destPromise = server.getRunDestinations(); setTimeout(() => { const destinations = JSON.stringify([ { name: 'iPhone 15 Pro', platform: 'iOS', architecture: 'arm64', isActive: false }, { name: 'iPhone 15 Simulator', platform: 'iOS Simulator', architecture: 'x86_64', isActive: true } ], null, 2); mockProcess.stdout.emit('data', destinations); mockProcess.emit('close', 0); }, 10); const destResult = await destPromise; const destinations = JSON.parse(destResult.content[0].text); const activeDestination = destinations.find(d => d.isActive); expect(activeDestination.name).toBe('iPhone 15 Simulator'); // Reset mock mockProcess = new EventEmitter(); mockProcess.stdout = new EventEmitter(); mockProcess.stderr = new EventEmitter(); mockSpawn.mockReturnValue(mockProcess); // Step 2: Start debugging const debugPromise = server.debug('TestApp', false); setTimeout(() => { mockProcess.stdout.emit('data', 'Debug started. Result ID: debug-789\n'); mockProcess.emit('close', 0); }, 10); const debugResult = await debugPromise; expect(debugResult.content[0].text).toContain('Debug started'); }); test('should handle file operations workflow', async () => { const server = new XcodeMCPServer(); // Open a source file at specific line const filePath = '/Users/test/TestApp/ViewController.swift'; const lineNumber = 25; const openFilePromise = server.openFile(filePath, lineNumber); setTimeout(() => { mockProcess.stdout.emit('data', 'File opened successfully\n'); mockProcess.emit('close', 0); }, 10); const result = await openFilePromise; expect(result.content[0].text).toBe('File opened successfully'); // Verify the correct osascript call was made expect(mockSpawn).toHaveBeenCalledWith('osascript', ['-l', 'JavaScript', '-e', expect.any(String)]); const script = mockSpawn.mock.calls[0][2][2]; expect(script).toContain(filePath); expect(script).toContain(lineNumber.toString()); }); }); });

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/lapfelix/XcodeMCP'

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