Skip to main content
Glama
prefs-reader.integration.test.ts6.46 kB
/** * iOS UserDefaults Reader Integration Tests * Tests against real iOS simulator with SpecterTestSubject app * * Prerequisites: * - iOS simulator running (xcrun simctl list shows Booted device) * - SpecterCounter app installed (com.specter.counter) * - App has been launched at least once (to create UserDefaults) */ import { describe, it, expect, beforeAll } from 'vitest'; import { executeShell } from '../../../src/utils/shell.js'; import { readUserDefaults, readDefaultsDomain, getAppContainerPath, listInstalledApps, isAppInstalled, } from '../../../src/platforms/ios/prefs-reader.js'; const BUNDLE_ID = 'com.specter.counter'; async function isSimulatorRunning(): Promise<boolean> { try { const result = await executeShell('xcrun', ['simctl', 'list', 'devices']); return result.stdout.includes('(Booted)'); } catch { return false; } } async function getBootedDeviceId(): Promise<string | null> { try { const result = await executeShell('xcrun', ['simctl', 'list', 'devices']); const bootedMatch = result.stdout.match(/([A-F0-9-]{36})\) \(Booted\)/); return bootedMatch ? bootedMatch[1] : null; } catch { return null; } } async function isTestAppInstalled(deviceId: string): Promise<boolean> { try { const result = await executeShell('xcrun', ['simctl', 'listapps', deviceId]); return result.stdout.includes(BUNDLE_ID); } catch { return false; } } async function triggerPrefsWrite(deviceId: string): Promise<void> { // Launch app await executeShell('xcrun', ['simctl', 'launch', deviceId, BUNDLE_ID]); await new Promise(resolve => setTimeout(resolve, 2000)); // The app writes to UserDefaults on launch (counter value, etc.) } describe('iOS UserDefaults Reader Integration', () => { let simulatorAvailable = false; let appInstalled = false; let deviceId: string | null = null; beforeAll(async () => { simulatorAvailable = await isSimulatorRunning(); deviceId = await getBootedDeviceId(); if (simulatorAvailable && deviceId) { appInstalled = await isTestAppInstalled(deviceId); if (appInstalled) { await triggerPrefsWrite(deviceId); } } console.log(`Simulator available: ${simulatorAvailable} (${deviceId})`); console.log(`App installed: ${appInstalled}`); }); describe('getAppContainerPath', () => { it('should return container path for installed app', async () => { expect(simulatorAvailable, 'No iOS simulator available').toBe(true); expect(appInstalled, `App ${BUNDLE_ID} not installed`).toBe(true); expect(deviceId, 'No device ID available').toBeTruthy(); const containerPath = await getAppContainerPath(BUNDLE_ID, deviceId); expect(containerPath).not.toBeNull(); expect(containerPath).toContain('Library/Developer/CoreSimulator'); console.log(`Container path: ${containerPath}`); }); it('should return null for non-existent app', async () => { expect(simulatorAvailable, 'No iOS simulator available').toBe(true); expect(deviceId, 'No device ID available').toBeTruthy(); const containerPath = await getAppContainerPath('com.nonexistent.app', deviceId!); expect(containerPath).toBeNull(); }); }); describe('isAppInstalled', () => { it('should detect installed app', async () => { expect(simulatorAvailable, 'No iOS simulator available').toBe(true); expect(deviceId, 'No device ID available').toBeTruthy(); const installed = await isAppInstalled(BUNDLE_ID, deviceId); expect(installed).toBe(appInstalled); }); it('should return false for non-existent app', async () => { expect(simulatorAvailable, 'No iOS simulator available').toBe(true); expect(deviceId, 'No device ID available').toBeTruthy(); const installed = await isAppInstalled('com.nonexistent.app', deviceId!); expect(installed).toBe(false); }); }); describe('listInstalledApps', () => { it('should list apps on simulator', async () => { expect(simulatorAvailable, 'No iOS simulator available').toBe(true); expect(deviceId, 'No device ID available').toBeTruthy(); const apps = await listInstalledApps(deviceId); expect(apps).toBeInstanceOf(Array); console.log(`Found ${apps.length} installed apps`); if (appInstalled) { expect(apps).toContain(BUNDLE_ID); } }); }); describe('readUserDefaults', () => { it('should read UserDefaults from SpecterCounter', async () => { expect(simulatorAvailable, 'No iOS simulator available').toBe(true); expect(appInstalled, `App ${BUNDLE_ID} not installed`).toBe(true); expect(deviceId, 'No device ID available').toBeTruthy(); const prefs = await readUserDefaults(BUNDLE_ID, { deviceId }); console.log(`Found ${prefs.length} preferences files`); for (const pref of prefs) { console.log(` - ${pref.name}: ${pref.entries.length} entries`); for (const entry of pref.entries.slice(0, 5)) { console.log(` ${entry.key} = ${entry.value} (${entry.type})`); } } }); it('should return empty array for non-existent app', async () => { expect(simulatorAvailable, 'No iOS simulator available').toBe(true); expect(deviceId, 'No device ID available').toBeTruthy(); const prefs = await readUserDefaults('com.nonexistent.app', { deviceId }); expect(prefs).toEqual([]); }); }); describe('readDefaultsDomain', () => { it('should read defaults domain for app', async () => { expect(simulatorAvailable, 'No iOS simulator available').toBe(true); expect(appInstalled, `App ${BUNDLE_ID} not installed`).toBe(true); expect(deviceId, 'No device ID available').toBeTruthy(); const domain = await readDefaultsDomain(BUNDLE_ID, deviceId); if (domain) { console.log(`Read ${domain.entries.length} entries from defaults domain`); for (const entry of domain.entries.slice(0, 5)) { console.log(` ${entry.key} = ${entry.value} (${entry.type})`); } } }); it('should return null for non-existent app', async () => { expect(simulatorAvailable, 'No iOS simulator available').toBe(true); expect(deviceId, 'No device ID available').toBeTruthy(); const domain = await readDefaultsDomain('com.nonexistent.app', deviceId!); expect(domain).toBeNull(); }); }); });

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/abd3lraouf/specter-mcp'

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