diff --git a/apps/mobile-shell/scripts/check-config.mjs b/apps/mobile-shell/scripts/check-config.mjs index 17c913a24..4526e3b18 100644 --- a/apps/mobile-shell/scripts/check-config.mjs +++ b/apps/mobile-shell/scripts/check-config.mjs @@ -2078,6 +2078,9 @@ for (const snippet of [ 'exportAudioFile({', "code: 'unsupported_capability'", "code: 'cancelled'", + 'rejects image import before picker launch when library permission is denied', + "message: 'photo library permission denied'", + 'expect(imageLibraryMock).not.toHaveBeenCalled()', "message: 'camera permission denied'", "mediaTypes: ['images']", "options: { encoding: 'base64' }", diff --git a/apps/mobile-shell/src/host-bridge/files.test.ts b/apps/mobile-shell/src/host-bridge/files.test.ts index dd80582af..136cccaa0 100644 --- a/apps/mobile-shell/src/host-bridge/files.test.ts +++ b/apps/mobile-shell/src/host-bridge/files.test.ts @@ -288,6 +288,21 @@ describe('mobile HostBridge file actions', () => { expect(cameraMock).not.toHaveBeenCalled(); }); + test('rejects image import before picker launch when library permission is denied', async () => { + libraryPermissionMock.mockResolvedValueOnce({ + status: ImagePicker.PermissionStatus.DENIED, + granted: false, + canAskAgain: false, + expires: 'never', + }); + + await expect(importImageFile()).rejects.toMatchObject({ + code: 'host_error', + message: 'photo library permission denied', + }); + expect(imageLibraryMock).not.toHaveBeenCalled(); + }); + test('imports audio and exports binary files through controlled payloads', async () => { fileBase64Data.set('file:///picked/voice.mp3', MP3_BASE64); fileSizes.set('file:///picked/voice.mp3', Buffer.byteLength('ID3\x04\x00\x00\x00\x00\x00\x10'));