diff --git a/src/tests/tools/app-management/resolve-app-id.test.ts b/src/tests/tools/app-management/resolve-app-id.test.ts new file mode 100644 index 00000000..619b1867 --- /dev/null +++ b/src/tests/tools/app-management/resolve-app-id.test.ts @@ -0,0 +1,50 @@ +import {beforeEach, describe, expect, jest, test} from '@jest/globals'; + +const rehydratedDriver = {rehydrated: true}; + +const mockResolveDriver = jest.fn<(sessionId?: string) => Promise>(); +const mockListAppsFromDevice = jest.fn<(...args: any[]) => Promise<{packageName: string; appName: string}[]>>(); + +jest.unstable_mockModule('../../../session-store', () => ({ + // The in-memory cache is empty after an MCP process recycle. + getDriver: jest.fn(() => null), + getSessionId: jest.fn(() => undefined), + getPlatformName: jest.fn(() => 'Android'), + isXCUITestDriverSession: jest.fn(() => false), + PLATFORM: {ios: 'iOS', android: 'Android'}, +})); + +jest.unstable_mockModule('../../../tools/tool-response', () => ({ + resolveDriver: mockResolveDriver, + noActiveDriverSessionMessage: (sessionId?: string) => + `No active driver session${sessionId ? ` for session '${sessionId}'` : ''}.`, +})); + +jest.unstable_mockModule('../../../tools/app-management/list-apps.js', () => ({ + listAppsFromDevice: mockListAppsFromDevice, +})); + +const {resolveAppId} = await import('../../../tools/app-management/resolve-app-id.js'); + +describe('resolveAppId on a persisted session', () => { + beforeEach(() => { + jest.clearAllMocks(); + mockListAppsFromDevice.mockResolvedValue([{packageName: 'com.example.calc', appName: 'Calculator'}]); + }); + + test('rehydrates the session instead of failing on an empty driver cache', async () => { + mockResolveDriver.mockResolvedValue({ok: true, driver: rehydratedDriver}); + + await expect(resolveAppId('Calculator', 'persisted-1')).resolves.toBe('com.example.calc'); + + expect(mockResolveDriver).toHaveBeenCalledWith('persisted-1'); + expect(mockListAppsFromDevice).toHaveBeenCalledWith(rehydratedDriver, 'User'); + }); + + test('reports no active driver session when the session cannot be resolved', async () => { + mockResolveDriver.mockResolvedValue({ok: false, result: {content: [], isError: true}}); + + await expect(resolveAppId('Calculator', 'missing')).rejects.toThrow(/No active driver session/); + expect(mockListAppsFromDevice).not.toHaveBeenCalled(); + }); +}); diff --git a/src/tools/app-management/resolve-app-id.ts b/src/tools/app-management/resolve-app-id.ts index 726c696b..a6b556e0 100644 --- a/src/tools/app-management/resolve-app-id.ts +++ b/src/tools/app-management/resolve-app-id.ts @@ -1,7 +1,7 @@ import type {XCUITestDriver} from 'appium-xcuitest-driver'; -import {getSessionId, getDriver, getPlatformName, PLATFORM, isXCUITestDriverSession} from '../../session-store.js'; -import {noActiveDriverSessionMessage} from '../tool-response.js'; +import {getSessionId, getPlatformName, PLATFORM, isXCUITestDriverSession} from '../../session-store.js'; +import {noActiveDriverSessionMessage, resolveDriver} from '../tool-response.js'; import {listAppsFromDevice} from './list-apps.js'; interface CacheEntry { @@ -89,10 +89,11 @@ async function getInstalledApps(sessionId?: string): Promise<{packageName: strin return cached.apps; } - const driver = getDriver(sessionId); - if (!driver) { + const resolved = await resolveDriver(sessionId); + if (!resolved.ok) { throw new Error(noActiveDriverSessionMessage(sessionId)); } + const {driver} = resolved; const platform = getPlatformName(driver);