锁定移动壳图片选择失败响应

移动壳图片库启动失败返回稳定错误

移动壳相机启动失败返回稳定错误

移动壳配置检查补充图片选择失败边界反查
This commit is contained in:
2026-06-20 14:05:23 +08:00
parent 933e690824
commit 6bbf510102
3 changed files with 65 additions and 15 deletions
@@ -2116,12 +2116,16 @@ for (const snippet of [
"message: 'file sharing failed'",
'rejects image import before picker launch when library permission is denied',
'rejects image import before picker launch when library permission request fails',
'maps native image library launch failures to stable import errors',
'rejects image capture before camera launch when camera permission request fails',
'maps native camera launch failures to stable capture errors',
"message: 'photo library permission denied'",
"message: 'photo library permission unavailable'",
"message: 'photo library unavailable'",
'expect(imageLibraryMock).not.toHaveBeenCalled()',
"message: 'camera permission denied'",
"message: 'camera permission unavailable'",
"message: 'camera unavailable'",
'expect(cameraMock).not.toHaveBeenCalled()',
"mediaTypes: ['images']",
"options: { encoding: 'base64' }",
@@ -354,6 +354,21 @@ describe('mobile HostBridge file actions', () => {
expect(imageLibraryMock).not.toHaveBeenCalled();
});
test('maps native image library launch failures to stable import errors', async () => {
libraryPermissionMock.mockResolvedValueOnce({
status: ImagePicker.PermissionStatus.GRANTED,
granted: true,
canAskAgain: true,
expires: 'never',
});
imageLibraryMock.mockRejectedValueOnce(new Error('native image picker failed'));
await expect(importImageFile()).rejects.toMatchObject({
code: 'host_error',
message: 'photo library unavailable',
});
});
test('rejects image capture before camera launch when camera permission request fails', async () => {
cameraPermissionMock.mockRejectedValueOnce(new Error('native permission failed'));
@@ -364,6 +379,21 @@ describe('mobile HostBridge file actions', () => {
expect(cameraMock).not.toHaveBeenCalled();
});
test('maps native camera launch failures to stable capture errors', async () => {
cameraPermissionMock.mockResolvedValueOnce({
status: ImagePicker.PermissionStatus.GRANTED,
granted: true,
canAskAgain: true,
expires: 'never',
});
cameraMock.mockRejectedValueOnce(new Error('native camera failed'));
await expect(captureImageFile()).rejects.toMatchObject({
code: 'host_error',
message: 'camera unavailable',
});
});
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'));
+31 -15
View File
@@ -331,14 +331,22 @@ export async function importImageFile(): Promise<FileImportImageResult> {
} satisfies HostBridgeError;
}
const result = await ImagePicker.launchImageLibraryAsync({
allowsEditing: false,
allowsMultipleSelection: false,
base64: true,
exif: false,
mediaTypes: ['images'],
quality: 1,
});
let result: ImagePicker.ImagePickerResult;
try {
result = await ImagePicker.launchImageLibraryAsync({
allowsEditing: false,
allowsMultipleSelection: false,
base64: true,
exif: false,
mediaTypes: ['images'],
quality: 1,
});
} catch {
throw {
code: 'host_error',
message: 'photo library unavailable',
} satisfies HostBridgeError;
}
const payload = imagePickerResultToImportPayload(result, 'selected');
if (payload.bytes > HOST_BRIDGE_IMPORT_IMAGE_MAX_BYTES) {
@@ -370,13 +378,21 @@ export async function captureImageFile(): Promise<FileImportImageResult> {
} satisfies HostBridgeError;
}
const result = await ImagePicker.launchCameraAsync({
allowsEditing: false,
base64: true,
exif: false,
mediaTypes: ['images'],
quality: 1,
});
let result: ImagePicker.ImagePickerResult;
try {
result = await ImagePicker.launchCameraAsync({
allowsEditing: false,
base64: true,
exif: false,
mediaTypes: ['images'],
quality: 1,
});
} catch {
throw {
code: 'host_error',
message: 'camera unavailable',
} satisfies HostBridgeError;
}
const payload = imagePickerResultToImportPayload(result, 'captured');
if (payload.bytes > HOST_BRIDGE_IMPORT_IMAGE_MAX_BYTES) {