Skip to main content
Glama

XC-MCP: XCode CLI wrapper

by conorluddy
simctl-stream-logs.test.ts6.58 kB
import { streamLogsTool } from '../../../src/tools/simctl/stream-logs.js'; import { McpError } from '@modelcontextprotocol/sdk/types.js'; // Mock command execution jest.mock('../../../src/utils/command.js', () => ({ executeCommand: jest.fn().mockResolvedValue({ code: 0, stdout: `2025-01-23 12:00:00.123 [MyApp] Application launched 2025-01-23 12:00:01.456 [MyApp] Login screen displayed 2025-01-23 12:00:02.789 [SystemLog] System event occurred`, stderr: '', }), })); describe('streamLogsTool', () => { const validUDID = 'device-iphone16pro'; const validBundleID = 'com.example.MyApp'; beforeEach(() => { jest.clearAllMocks(); }); describe('successful log streaming', () => { it('should stream logs from simulator', async () => { const result = await streamLogsTool({ udid: validUDID, duration: 5, }); expect(result.isError).toBe(false); const response = JSON.parse(result.content[0].text); expect(response.success).toBe(true); expect(response.logs).toBeDefined(); expect(response.logs.count).toBeGreaterThan(0); }); it('should filter logs by bundle ID', async () => { const result = await streamLogsTool({ udid: validUDID, bundleId: validBundleID, duration: 5, }); const response = JSON.parse(result.content[0].text); expect(response.logs.bundleId).toBe(validBundleID); expect(response.logs.predicate).toBe(`process == "${validBundleID}"`); }); it('should use custom predicate when provided', async () => { const customPredicate = 'subsystem == "com.example" AND category == "network"'; const result = await streamLogsTool({ udid: validUDID, predicate: customPredicate, duration: 5, }); const response = JSON.parse(result.content[0].text); expect(response.logs.predicate).toBe(customPredicate); }); it('should return log items with parsed data', async () => { const result = await streamLogsTool({ udid: validUDID, duration: 5, }); const response = JSON.parse(result.content[0].text); expect(response.logs.items).toBeDefined(); expect(Array.isArray(response.logs.items)).toBe(true); expect(response.logs.items.length).toBeGreaterThan(0); const firstLog = response.logs.items[0]; expect(firstLog).toHaveProperty('timestamp'); expect(firstLog).toHaveProperty('process'); expect(firstLog).toHaveProperty('message'); }); it('should limit returned logs to 100 items', async () => { // Mock many log lines const manyLogs = Array.from( { length: 200 }, (_, i) => `2025-01-23 12:00:${String(i).padStart(2, '0')}.000 [MyApp] Log ${i}` ).join('\n'); const { executeCommand } = require('../../../src/utils/command.js'); executeCommand.mockResolvedValueOnce({ code: 0, stdout: manyLogs, stderr: '', }); const result = await streamLogsTool({ udid: validUDID, duration: 5, }); const response = JSON.parse(result.content[0].text); expect(response.logs.count).toBe(200); expect(response.logs.items.length).toBe(100); }); it('should provide guidance for log streaming', async () => { const result = await streamLogsTool({ udid: validUDID, bundleId: validBundleID, duration: 10, }); 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 use default duration of 10 seconds', async () => { const result = await streamLogsTool({ udid: validUDID, }); const response = JSON.parse(result.content[0].text); expect(response.logs.duration).toBe(10); }); it('should use custom duration when provided', async () => { const result = await streamLogsTool({ udid: validUDID, duration: 30, }); const response = JSON.parse(result.content[0].text); expect(response.logs.duration).toBe(30); }); it('should default to "true" predicate when no filter specified', async () => { const result = await streamLogsTool({ udid: validUDID, duration: 5, }); const response = JSON.parse(result.content[0].text); expect(response.logs.predicate).toBe('true'); }); }); describe('input validation', () => { it('should reject missing UDID', async () => { await expect( streamLogsTool({ duration: 5, }) ).rejects.toThrow(McpError); }); it('should reject empty UDID', async () => { await expect( streamLogsTool({ udid: '', duration: 5, }) ).rejects.toThrow(McpError); }); }); describe('error handling', () => { it('should handle command execution errors', async () => { const { executeCommand } = require('../../../src/utils/command.js'); executeCommand.mockRejectedValueOnce(new Error('Command failed')); await expect( streamLogsTool({ udid: validUDID, duration: 5, }) ).rejects.toThrow(McpError); }); it('should handle empty log output', async () => { const { executeCommand } = require('../../../src/utils/command.js'); executeCommand.mockResolvedValueOnce({ code: 0, stdout: '', stderr: '', }); const result = await streamLogsTool({ udid: validUDID, duration: 5, }); const response = JSON.parse(result.content[0].text); expect(response.logs.count).toBe(0); expect(response.logs.items).toEqual([]); }); }); describe('response format', () => { it('should return proper MCP response format', async () => { const result = await streamLogsTool({ udid: validUDID, duration: 5, }); 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); }); it('should return valid JSON in text content', async () => { const result = await streamLogsTool({ udid: validUDID, duration: 5, }); expect(() => JSON.parse(result.content[0].text)).not.toThrow(); }); }); });

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