@onekeyfe/hd-core 1.2.0-alpha.19 → 1.2.0-alpha.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/__tests__/AllNetworkGetAddressBase.tracing.test.ts +75 -0
- package/__tests__/DeviceCommands.test.ts +143 -7
- package/__tests__/RequestQueue.test.ts +106 -0
- package/__tests__/UploadPortfolio.test.ts +46 -0
- package/__tests__/core-dispose.test.ts +34 -0
- package/__tests__/core-error-output.test.ts +39 -0
- package/__tests__/device-lifecycle-events.test.ts +117 -0
- package/__tests__/device-pool-state.test.ts +20 -1
- package/__tests__/device-settings.test.ts +184 -6
- package/__tests__/device-state-events.test.ts +1 -2
- package/__tests__/device-state-mapper.test.ts +33 -26
- package/__tests__/device-state-store.test.ts +1 -34
- package/__tests__/device-utils.test.ts +106 -0
- package/__tests__/device-wallet-session-store.test.ts +65 -29
- package/__tests__/deviceSettings.test.ts +21 -12
- package/__tests__/evmLedgerLegacySafety.test.ts +2 -2
- package/__tests__/evmSignTypedData.test.ts +80 -0
- package/__tests__/filesystemValidation.test.ts +53 -0
- package/__tests__/firmware-update/firmware-update-v2-download-before-boot.test.ts +402 -0
- package/__tests__/get-device-state.test.ts +77 -41
- package/__tests__/logBlockEvent.test.ts +24 -11
- package/__tests__/networkUtils.test.ts +386 -0
- package/__tests__/open-wallet-session-error-response.test.ts +43 -0
- package/__tests__/open-wallet-session.test.ts +559 -0
- package/__tests__/protocol-v2.test.ts +756 -452
- package/__tests__/public-device-state-api.test.ts +113 -13
- package/__tests__/public-pro2-api-boundary.test.ts +91 -0
- package/__tests__/search-devices.test.ts +43 -0
- package/dist/api/BaseMethod.d.ts +3 -0
- package/dist/api/BaseMethod.d.ts.map +1 -1
- package/dist/api/CheckBootloaderRelease.d.ts.map +1 -1
- package/dist/api/CheckFirmwareRelease.d.ts.map +1 -1
- package/dist/api/ClearSessionCache.d.ts.map +1 -1
- package/dist/api/DirList.d.ts.map +1 -1
- package/dist/api/FileRead.d.ts.map +1 -1
- package/dist/api/FirmwareUpdateV2.d.ts +2 -2
- package/dist/api/FirmwareUpdateV2.d.ts.map +1 -1
- package/dist/api/FirmwareUpdateV3.d.ts.map +1 -1
- package/dist/api/FirmwareUpdateV4.d.ts +3 -2
- package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
- package/dist/api/GetPassphraseState.d.ts.map +1 -1
- package/dist/api/OpenWalletSession.d.ts +14 -0
- package/dist/api/OpenWalletSession.d.ts.map +1 -0
- package/dist/api/PathInfo.d.ts.map +1 -1
- package/dist/api/PromptWebDeviceAccess.d.ts.map +1 -1
- package/dist/api/SearchDevices.d.ts +3 -0
- package/dist/api/SearchDevices.d.ts.map +1 -1
- package/dist/api/UploadPortfolio.d.ts.map +1 -1
- package/dist/api/allnetwork/AllNetworkGetAddressBase.d.ts.map +1 -1
- package/dist/api/device/DeviceChangePin.d.ts.map +1 -1
- package/dist/api/device/DeviceFullyUploadResource.d.ts +1 -2
- package/dist/api/device/DeviceFullyUploadResource.d.ts.map +1 -1
- package/dist/api/device/DeviceSettings.d.ts.map +1 -1
- package/dist/api/device/DeviceUpdateBootloader.d.ts.map +1 -1
- package/dist/api/device/DeviceWipe.d.ts.map +1 -1
- package/dist/api/evm/EVMSignTypedData.d.ts.map +1 -1
- package/dist/api/firmware/getBinary.d.ts +7 -3
- package/dist/api/firmware/getBinary.d.ts.map +1 -1
- package/dist/api/helpers/filesystemValidation.d.ts +3 -0
- package/dist/api/helpers/filesystemValidation.d.ts.map +1 -1
- package/dist/api/helpers/paramsValidator.d.ts +1 -0
- package/dist/api/helpers/paramsValidator.d.ts.map +1 -1
- package/dist/api/helpers/protocolV2FileWrite.d.ts.map +1 -1
- package/dist/api/index.d.ts +7 -11
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/protocol-v2/DeviceGetFirmwareUpdateStatus.d.ts.map +1 -1
- package/dist/api/protocol-v2/DeviceSettingsPageShow.d.ts.map +1 -1
- package/dist/api/protocol-v2/DeviceSettingsSet.d.ts.map +1 -1
- package/dist/api/utils.d.ts.map +1 -1
- package/dist/core/RequestQueue.d.ts +17 -1
- package/dist/core/RequestQueue.d.ts.map +1 -1
- package/dist/core/index.d.ts +3 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/device/Device.d.ts +6 -5
- package/dist/device/Device.d.ts.map +1 -1
- package/dist/device/DeviceCommands.d.ts +5 -5
- package/dist/device/DeviceCommands.d.ts.map +1 -1
- package/dist/device/DevicePool.d.ts +1 -0
- package/dist/device/DevicePool.d.ts.map +1 -1
- package/dist/device/DeviceStateMapper.d.ts +2 -2
- package/dist/device/DeviceStateMapper.d.ts.map +1 -1
- package/dist/device/DeviceStateProjector.d.ts +1 -2
- package/dist/device/DeviceStateProjector.d.ts.map +1 -1
- package/dist/device/DeviceStateStore.d.ts +1 -3
- package/dist/device/DeviceStateStore.d.ts.map +1 -1
- package/dist/device/DeviceWalletSessionStore.d.ts +6 -1
- package/dist/device/DeviceWalletSessionStore.d.ts.map +1 -1
- package/dist/device/cloneDeviceState.d.ts +2 -0
- package/dist/device/cloneDeviceState.d.ts.map +1 -0
- package/dist/deviceProfile/buildDeviceFeatures.d.ts +3 -1
- package/dist/deviceProfile/buildDeviceFeatures.d.ts.map +1 -1
- package/dist/events/logBlockEvent.d.ts.map +1 -1
- package/dist/index.d.ts +608 -75
- package/dist/index.js +2287 -1706
- package/dist/inject.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/deviceSession.d.ts +7 -0
- package/dist/protocols/protocol-v2/deviceSession.d.ts.map +1 -0
- package/dist/protocols/protocol-v2/features.d.ts +11 -4
- package/dist/protocols/protocol-v2/features.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/index.d.ts +2 -2
- package/dist/protocols/protocol-v2/index.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/settingsUnlockPolicy.d.ts +16 -0
- package/dist/protocols/protocol-v2/settingsUnlockPolicy.d.ts.map +1 -0
- package/dist/protocols/protocol-v2/uiInteraction.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/walletSession.d.ts +8 -3
- package/dist/protocols/protocol-v2/walletSession.d.ts.map +1 -1
- package/dist/types/api/checkAllFirmwareRelease.d.ts +1 -2
- package/dist/types/api/checkAllFirmwareRelease.d.ts.map +1 -1
- package/dist/types/api/deviceSettings.d.ts +1 -0
- package/dist/types/api/deviceSettings.d.ts.map +1 -1
- package/dist/types/api/deviceUnlock.d.ts +1 -1
- package/dist/types/api/deviceUnlock.d.ts.map +1 -1
- package/dist/types/api/getFeatures.d.ts.map +1 -1
- package/dist/types/api/getOnekeyFeatures.d.ts.map +1 -1
- package/dist/types/api/getPassphraseState.d.ts.map +1 -1
- package/dist/types/api/index.d.ts +13 -15
- package/dist/types/api/index.d.ts.map +1 -1
- package/dist/types/api/openWalletSession.d.ts +41 -0
- package/dist/types/api/openWalletSession.d.ts.map +1 -0
- package/dist/types/api/protocolV2.d.ts +6 -11
- package/dist/types/api/protocolV2.d.ts.map +1 -1
- package/dist/types/device.d.ts +2 -9
- package/dist/types/device.d.ts.map +1 -1
- package/dist/utils/assets.d.ts +2 -1
- package/dist/utils/assets.d.ts.map +1 -1
- package/dist/utils/deviceFeaturesCompat.d.ts +13 -0
- package/dist/utils/deviceFeaturesCompat.d.ts.map +1 -0
- package/dist/utils/deviceFeaturesUtils.d.ts +11 -1
- package/dist/utils/deviceFeaturesUtils.d.ts.map +1 -1
- package/dist/utils/deviceInfoUtils.d.ts +9 -8
- package/dist/utils/deviceInfoUtils.d.ts.map +1 -1
- package/dist/utils/deviceSettings.d.ts +2 -0
- package/dist/utils/deviceSettings.d.ts.map +1 -1
- package/dist/utils/deviceVersionUtils.d.ts +6 -5
- package/dist/utils/deviceVersionUtils.d.ts.map +1 -1
- package/dist/utils/index.d.ts +2 -2
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/networkUtils.d.ts +9 -1
- package/dist/utils/networkUtils.d.ts.map +1 -1
- package/dist/utils/patch.d.ts +1 -1
- package/dist/utils/patch.d.ts.map +1 -1
- package/dist/utils/pro2Wallpaper.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/api/BaseMethod.ts +17 -10
- package/src/api/CheckBootloaderRelease.ts +1 -2
- package/src/api/CheckFirmwareRelease.ts +1 -2
- package/src/api/ClearSessionCache.ts +0 -4
- package/src/api/DirList.ts +5 -3
- package/src/api/DirMake.ts +3 -3
- package/src/api/DirRemove.ts +3 -3
- package/src/api/FileDelete.ts +3 -3
- package/src/api/FileRead.ts +9 -6
- package/src/api/FileWrite.ts +3 -3
- package/src/api/FirmwareUpdate.ts +4 -4
- package/src/api/FirmwareUpdateV2.ts +133 -45
- package/src/api/FirmwareUpdateV3.ts +3 -5
- package/src/api/FirmwareUpdateV4.ts +192 -154
- package/src/api/GetPassphraseState.ts +10 -16
- package/src/api/OpenWalletSession.ts +221 -0
- package/src/api/PathInfo.ts +5 -3
- package/src/api/PromptWebDeviceAccess.ts +2 -3
- package/src/api/SearchDevices.ts +6 -2
- package/src/api/UploadPortfolio.ts +12 -3
- package/src/api/alephium/AlephiumSignTransaction.ts +2 -2
- package/src/api/allnetwork/AllNetworkGetAddressBase.ts +4 -8
- package/src/api/benfen/BenfenGetAddress.ts +1 -1
- package/src/api/btc/BTCGetPublicKey.ts +1 -1
- package/src/api/device/DeviceChangePin.ts +1 -0
- package/src/api/device/DeviceFullyUploadResource.ts +4 -5
- package/src/api/device/DeviceSettings.ts +93 -0
- package/src/api/device/DeviceUpdateBootloader.ts +1 -2
- package/src/api/device/DeviceWipe.ts +1 -0
- package/src/api/evm/EVMSignTypedData.ts +3 -5
- package/src/api/evm/legacyV1/getAddress.ts +2 -2
- package/src/api/evm/legacyV1/getPublicKey.ts +2 -2
- package/src/api/evm/legacyV1/signMessage.ts +2 -2
- package/src/api/evm/legacyV1/signTypedData.ts +2 -2
- package/src/api/evm/legacyV1/signTypedHash.ts +2 -2
- package/src/api/evm/legacyV1/verifyMessage.ts +2 -2
- package/src/api/firmware/FirmwareUpdateBaseMethod.ts +3 -3
- package/src/api/firmware/getBinary.ts +8 -3
- package/src/api/helpers/batchGetPublickeys.ts +2 -2
- package/src/api/helpers/filesystemValidation.ts +61 -0
- package/src/api/helpers/paramsValidator.ts +1 -1
- package/src/api/helpers/protocolV2FileWrite.ts +3 -2
- package/src/api/index.ts +9 -12
- package/src/api/nervos/NervosSignTransaction.ts +2 -2
- package/src/api/nexa/NexaSignTransaction.ts +1 -1
- package/src/api/protocol-v2/DeviceFactoryInfoGet.ts +1 -1
- package/src/api/protocol-v2/DeviceFactoryInfoSet.ts +1 -1
- package/src/api/protocol-v2/DeviceFirmwareUpdate.ts +4 -4
- package/src/api/protocol-v2/DeviceGetFirmwareUpdateStatus.ts +6 -5
- package/src/api/protocol-v2/DeviceInfoGet.ts +4 -4
- package/src/api/protocol-v2/DeviceReboot.ts +1 -1
- package/src/api/protocol-v2/DeviceSettingsPageShow.ts +11 -8
- package/src/api/protocol-v2/DeviceSettingsSet.ts +4 -11
- package/src/api/protocol-v2/FilesystemFormat.ts +1 -1
- package/src/api/protocol-v2/FilesystemPermissionFix.ts +1 -1
- package/src/api/protocol-v2/Ping.ts +1 -1
- package/src/api/protocol-v2/ProtocolInfoRequest.ts +1 -1
- package/src/api/utils.ts +1 -3
- package/src/core/RequestQueue.ts +107 -6
- package/src/core/index.ts +91 -46
- package/src/data/messages/messages-protocol-v2.json +25 -79
- package/src/data-manager/DataManager.ts +6 -6
- package/src/device/Device.ts +186 -102
- package/src/device/DeviceCommands.ts +51 -21
- package/src/device/DeviceFeaturesState.ts +4 -4
- package/src/device/DevicePool.ts +17 -3
- package/src/device/DeviceStateMapper.ts +13 -29
- package/src/device/DeviceStateProjector.ts +11 -8
- package/src/device/DeviceStateStore.ts +11 -52
- package/src/device/DeviceWalletSessionStore.ts +31 -4
- package/src/device/cloneDeviceState.ts +39 -0
- package/src/device/deviceIdentity.ts +2 -2
- package/src/deviceProfile/buildDeviceFeatures.ts +29 -88
- package/src/events/logBlockEvent.ts +8 -1
- package/src/inject.ts +14 -24
- package/src/protocols/protocol-v2/deviceSession.ts +24 -0
- package/src/protocols/protocol-v2/features.ts +37 -49
- package/src/protocols/protocol-v2/firmware.ts +1 -1
- package/src/protocols/protocol-v2/index.ts +2 -3
- package/src/protocols/protocol-v2/settingsUnlockPolicy.ts +61 -0
- package/src/protocols/protocol-v2/uiInteraction.ts +4 -3
- package/src/protocols/protocol-v2/walletSession.ts +57 -146
- package/src/types/api/checkAllFirmwareRelease.ts +1 -2
- package/src/types/api/deviceSettings.ts +1 -0
- package/src/types/api/deviceUnlock.ts +1 -1
- package/src/types/api/firmwareUpdate.ts +5 -5
- package/src/types/api/getDeviceState.ts +1 -1
- package/src/types/api/getFeatures.ts +3 -0
- package/src/types/api/getOnekeyFeatures.ts +3 -0
- package/src/types/api/getPassphraseState.ts +6 -0
- package/src/types/api/index.ts +16 -23
- package/src/types/api/openWalletSession.ts +64 -0
- package/src/types/api/protocolV2.ts +36 -18
- package/src/types/device.ts +20 -14
- package/src/types/params.ts +1 -1
- package/src/types/settings.ts +8 -8
- package/src/utils/assets.ts +4 -1
- package/src/utils/deviceFeaturesCompat.ts +173 -0
- package/src/utils/deviceFeaturesUtils.ts +9 -8
- package/src/utils/deviceInfoUtils.ts +26 -64
- package/src/utils/deviceSettings.ts +18 -11
- package/src/utils/deviceVersionUtils.ts +25 -44
- package/src/utils/findDefectiveBatchDevice.ts +3 -3
- package/src/utils/index.ts +2 -5
- package/src/utils/networkUtils.ts +270 -14
- package/src/utils/pro2Wallpaper.ts +11 -13
- package/src/utils/tracing.ts +2 -2
- package/dist/api/internal.d.ts +0 -4
- package/dist/api/internal.d.ts.map +0 -1
- package/dist/api/protocol-v2/DeviceSessionOpen.d.ts +0 -28
- package/dist/api/protocol-v2/DeviceSessionOpen.d.ts.map +0 -1
- package/src/api/internal.ts +0 -9
- package/src/api/protocol-v2/DeviceSessionOpen.ts +0 -100
|
@@ -18,13 +18,13 @@ import DeviceReboot from '../src/api/protocol-v2/DeviceReboot';
|
|
|
18
18
|
import DeviceCancel from '../src/api/device/DeviceCancel';
|
|
19
19
|
import DeviceChangePin from '../src/api/device/DeviceChangePin';
|
|
20
20
|
import DeviceLock from '../src/api/device/DeviceLock';
|
|
21
|
+
import DeviceSettings from '../src/api/device/DeviceSettings';
|
|
21
22
|
import DeviceUnlock from '../src/api/device/DeviceUnlock';
|
|
22
23
|
import DeviceWipe from '../src/api/device/DeviceWipe';
|
|
23
24
|
import DeviceSettingsGet from '../src/api/protocol-v2/DeviceSettingsGet';
|
|
24
25
|
import DeviceSettingsPageShow from '../src/api/protocol-v2/DeviceSettingsPageShow';
|
|
25
26
|
import DeviceSettingsSet from '../src/api/protocol-v2/DeviceSettingsSet';
|
|
26
27
|
import DeviceUploadWallpaper from '../src/api/protocol-v2/DeviceUploadWallpaper';
|
|
27
|
-
import DeviceSessionOpen from '../src/api/protocol-v2/DeviceSessionOpen';
|
|
28
28
|
import DeviceStatusGet from '../src/api/protocol-v2/DeviceStatusGet';
|
|
29
29
|
import FilesystemFormat from '../src/api/protocol-v2/FilesystemFormat';
|
|
30
30
|
import FilesystemPermissionFix from '../src/api/protocol-v2/FilesystemPermissionFix';
|
|
@@ -58,10 +58,12 @@ import { DataManager } from '../src/data-manager';
|
|
|
58
58
|
import { createCoreApi } from '../src/inject';
|
|
59
59
|
import { Device, preloadSessionCache } from '../src/device/Device';
|
|
60
60
|
import { DevicePool } from '../src/device/DevicePool';
|
|
61
|
-
import { DEVICE } from '../src/events/device';
|
|
62
61
|
import { UI_REQUEST } from '../src/events/ui-request';
|
|
63
62
|
import {
|
|
64
63
|
PROTOCOL_V2_DEVICE_INFO_TIMEOUT_MS,
|
|
64
|
+
type ProtocolV2DeviceInfo,
|
|
65
|
+
type ProtocolV2RuntimeMode,
|
|
66
|
+
getProtocolV2RuntimeMode,
|
|
65
67
|
requestProtocolV2DeviceInfo,
|
|
66
68
|
requestProtocolV2DeviceStatus,
|
|
67
69
|
} from '../src/protocols/protocol-v2/features';
|
|
@@ -92,7 +94,6 @@ import {
|
|
|
92
94
|
import type { DeviceCommands } from '../src/device/DeviceCommands';
|
|
93
95
|
import type { Features } from '../src/types';
|
|
94
96
|
import type { DeviceStatus } from '@onekeyfe/hd-transport';
|
|
95
|
-
import type { ProtocolV2DeviceInfo } from '../src/protocols/protocol-v2/features';
|
|
96
97
|
|
|
97
98
|
jest.mock('../src/data/config', () => ({
|
|
98
99
|
getSDKVersion: jest.fn(() => '1.0.0'),
|
|
@@ -215,6 +216,7 @@ describe('UploadPortfolio', () => {
|
|
|
215
216
|
|
|
216
217
|
expect(method.unlockPolicy).toBe('retry-on-locked');
|
|
217
218
|
expect(method.protocolV2UiMode).toBe('none');
|
|
219
|
+
expect(method.executionPriority).toBe('background');
|
|
218
220
|
expect(method.protocolV2UiInteraction).toBeUndefined();
|
|
219
221
|
expect(method.payload.emitProgress).toBe(false);
|
|
220
222
|
expect(typedCall).toHaveBeenNthCalledWith(
|
|
@@ -232,9 +234,15 @@ describe('UploadPortfolio', () => {
|
|
|
232
234
|
append: false,
|
|
233
235
|
ui_percentage: 100,
|
|
234
236
|
},
|
|
235
|
-
{ timeoutMs:
|
|
237
|
+
{ timeoutMs: 5_000 }
|
|
238
|
+
);
|
|
239
|
+
expect(typedCall).toHaveBeenNthCalledWith(
|
|
240
|
+
2,
|
|
241
|
+
'PortfolioUpdate',
|
|
242
|
+
'Success',
|
|
243
|
+
{},
|
|
244
|
+
{ timeoutMs: 5_000 }
|
|
236
245
|
);
|
|
237
|
-
expect(typedCall).toHaveBeenNthCalledWith(2, 'PortfolioUpdate', 'Success', {});
|
|
238
246
|
expect(method.postMessage).not.toHaveBeenCalled();
|
|
239
247
|
expect(result).toMatchObject({
|
|
240
248
|
path: 'vol1:/portfolio/portfolio.okpkg.pending',
|
|
@@ -294,8 +302,8 @@ const descriptor = {
|
|
|
294
302
|
};
|
|
295
303
|
|
|
296
304
|
/**
|
|
297
|
-
*
|
|
298
|
-
*
|
|
305
|
+
* Add Device protocol and getCurrent* accessors to plain-object stubs.
|
|
306
|
+
* Defaults match Device.ts and never replace an existing member such as jest.fn().
|
|
299
307
|
*/
|
|
300
308
|
function stubDevice<T extends Record<string, any>>(device: T): T {
|
|
301
309
|
const d = device as any;
|
|
@@ -320,15 +328,54 @@ function stubDevice<T extends Record<string, any>>(device: T): T {
|
|
|
320
328
|
};
|
|
321
329
|
d.updateProtocolV2Features ??= (
|
|
322
330
|
deviceInfo?: ProtocolV2DeviceInfo,
|
|
323
|
-
deviceStatus?: DeviceStatus
|
|
331
|
+
deviceStatus?: DeviceStatus,
|
|
332
|
+
runtimeMode?: ProtocolV2RuntimeMode
|
|
324
333
|
) => {
|
|
325
334
|
d.features = buildProtocolV2FeaturesPayload({
|
|
326
335
|
deviceInfo,
|
|
327
336
|
deviceStatus: deviceStatus ?? deviceInfo?.status,
|
|
328
337
|
previous: d.features,
|
|
338
|
+
runtimeMode: runtimeMode ?? (deviceStatus ? 'normal' : undefined),
|
|
329
339
|
});
|
|
330
340
|
return d.features;
|
|
331
341
|
};
|
|
342
|
+
d.probeProtocolV2RuntimeState ??= async (
|
|
343
|
+
deviceInfo: ProtocolV2DeviceInfo,
|
|
344
|
+
timeoutMs?: number
|
|
345
|
+
) => {
|
|
346
|
+
try {
|
|
347
|
+
const response = await d
|
|
348
|
+
.getCommands()
|
|
349
|
+
.typedCall(
|
|
350
|
+
'DeviceStatusGet',
|
|
351
|
+
'DeviceStatus',
|
|
352
|
+
{},
|
|
353
|
+
timeoutMs === undefined ? undefined : { timeoutMs }
|
|
354
|
+
);
|
|
355
|
+
const features = d.updateProtocolV2Features(deviceInfo, response.message, 'normal');
|
|
356
|
+
return (
|
|
357
|
+
features ??
|
|
358
|
+
buildProtocolV2FeaturesPayload({
|
|
359
|
+
deviceInfo,
|
|
360
|
+
deviceStatus: response.message,
|
|
361
|
+
previous: d.features,
|
|
362
|
+
runtimeMode: 'normal',
|
|
363
|
+
})
|
|
364
|
+
);
|
|
365
|
+
} catch {
|
|
366
|
+
const runtimeMode = getProtocolV2RuntimeMode({
|
|
367
|
+
deviceInfo,
|
|
368
|
+
deviceStatusAvailable: false,
|
|
369
|
+
});
|
|
370
|
+
const features = d.updateProtocolV2Features(deviceInfo, null, runtimeMode);
|
|
371
|
+
if (features?.mode === runtimeMode) return features;
|
|
372
|
+
return buildProtocolV2FeaturesPayload({
|
|
373
|
+
deviceInfo,
|
|
374
|
+
previous: d.features,
|
|
375
|
+
runtimeMode,
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
};
|
|
332
379
|
d.updateProtocolV2Status ??= (deviceStatus: DeviceStatus) => {
|
|
333
380
|
d.features = buildProtocolV2FeaturesPayload({
|
|
334
381
|
deviceInfo: d.features?.raw?.protocolV2DeviceInfo,
|
|
@@ -348,10 +395,15 @@ function stubDevice<T extends Record<string, any>>(device: T): T {
|
|
|
348
395
|
}
|
|
349
396
|
|
|
350
397
|
// 直接复用生产映射函数,避免测试内副本与实现漂移(之前的手抄副本已缺失 se boot 字段)
|
|
351
|
-
function normalizeProtocolV2Features(
|
|
398
|
+
function normalizeProtocolV2Features(
|
|
399
|
+
_descriptor: unknown,
|
|
400
|
+
deviceInfo?: ProtocolV2DeviceInfo,
|
|
401
|
+
runtimeMode: ProtocolV2RuntimeMode = 'normal'
|
|
402
|
+
) {
|
|
352
403
|
return buildProtocolV2FeaturesPayload({
|
|
353
404
|
deviceInfo,
|
|
354
405
|
deviceStatus: deviceInfo?.status,
|
|
406
|
+
runtimeMode,
|
|
355
407
|
});
|
|
356
408
|
}
|
|
357
409
|
|
|
@@ -365,6 +417,7 @@ const protocolV2BootloaderDeviceInfo: ProtocolV2DeviceInfo = {
|
|
|
365
417
|
version: '0.0.1',
|
|
366
418
|
},
|
|
367
419
|
},
|
|
420
|
+
se1: {},
|
|
368
421
|
};
|
|
369
422
|
|
|
370
423
|
async function requestProtocolV2Features({
|
|
@@ -576,7 +629,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
576
629
|
expect(features.bleEnabled).toBeNull();
|
|
577
630
|
});
|
|
578
631
|
|
|
579
|
-
test('marks Protocol V2 DeviceInfo
|
|
632
|
+
test('marks Protocol V2 DeviceInfo as bootloader when DeviceStatusGet fails', () => {
|
|
580
633
|
const deviceInfo = {
|
|
581
634
|
protocol_version: 1,
|
|
582
635
|
hw: {
|
|
@@ -587,14 +640,34 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
587
640
|
version: '0.2.0',
|
|
588
641
|
},
|
|
589
642
|
},
|
|
643
|
+
se1: {},
|
|
590
644
|
};
|
|
591
|
-
const
|
|
645
|
+
const runtimeMode = getProtocolV2RuntimeMode({
|
|
646
|
+
deviceInfo: deviceInfo as ProtocolV2DeviceInfo,
|
|
647
|
+
deviceStatusAvailable: false,
|
|
648
|
+
});
|
|
649
|
+
const features = normalizeProtocolV2Features(
|
|
650
|
+
descriptor as any,
|
|
651
|
+
deviceInfo as ProtocolV2DeviceInfo,
|
|
652
|
+
runtimeMode
|
|
653
|
+
);
|
|
592
654
|
expect(features.mode).toBe('bootloader');
|
|
593
655
|
expect(features.bootloaderMode).toBe(true);
|
|
594
656
|
expect(features.initialized).toBeNull();
|
|
595
657
|
});
|
|
596
658
|
|
|
597
|
-
test('
|
|
659
|
+
test('keeps normal mode when DeviceStatusGet succeeds even if application metadata is absent', () => {
|
|
660
|
+
const features = normalizeProtocolV2Features(descriptor as any, {
|
|
661
|
+
protocol_version: 1,
|
|
662
|
+
hw: { serial_no: 'PR2NORMAL' },
|
|
663
|
+
fw: { bootloader: { version: '0.2.0' } },
|
|
664
|
+
});
|
|
665
|
+
|
|
666
|
+
expect(features.mode).toBe('normal');
|
|
667
|
+
expect(features.bootloaderMode).toBe(false);
|
|
668
|
+
});
|
|
669
|
+
|
|
670
|
+
test('temporarily treats romloader metadata as bootloader when DeviceStatusGet fails', () => {
|
|
598
671
|
const deviceInfo = {
|
|
599
672
|
protocol_version: 1,
|
|
600
673
|
hw: {
|
|
@@ -609,9 +682,17 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
609
682
|
},
|
|
610
683
|
},
|
|
611
684
|
};
|
|
612
|
-
const
|
|
613
|
-
|
|
614
|
-
|
|
685
|
+
const runtimeMode = getProtocolV2RuntimeMode({
|
|
686
|
+
deviceInfo: deviceInfo as ProtocolV2DeviceInfo,
|
|
687
|
+
deviceStatusAvailable: false,
|
|
688
|
+
});
|
|
689
|
+
const features = normalizeProtocolV2Features(
|
|
690
|
+
descriptor as any,
|
|
691
|
+
deviceInfo as ProtocolV2DeviceInfo,
|
|
692
|
+
runtimeMode
|
|
693
|
+
);
|
|
694
|
+
expect(features.mode).toBe('bootloader');
|
|
695
|
+
expect(features.bootloaderMode).toBe(true);
|
|
615
696
|
expect(features.boardVersion).toBe('1.0.0');
|
|
616
697
|
});
|
|
617
698
|
|
|
@@ -639,6 +720,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
639
720
|
passphraseState: undefined,
|
|
640
721
|
newSession: undefined,
|
|
641
722
|
unlockedAttachPin: false,
|
|
723
|
+
resumed: false,
|
|
642
724
|
});
|
|
643
725
|
expect(typedCall).not.toHaveBeenCalled();
|
|
644
726
|
expect(device.getInternalState()).toBeUndefined();
|
|
@@ -660,6 +742,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
660
742
|
passphraseState: undefined,
|
|
661
743
|
newSession: undefined,
|
|
662
744
|
unlockedAttachPin: false,
|
|
745
|
+
resumed: false,
|
|
663
746
|
});
|
|
664
747
|
|
|
665
748
|
expect(promptPassphrase).not.toHaveBeenCalled();
|
|
@@ -718,7 +801,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
718
801
|
}
|
|
719
802
|
);
|
|
720
803
|
const typedCall = jest.fn().mockImplementation((request: string) => {
|
|
721
|
-
if (request === '
|
|
804
|
+
if (request === 'DeviceSessionGet') {
|
|
722
805
|
return Promise.resolve({
|
|
723
806
|
message: {
|
|
724
807
|
btc_test_address: 'state-after-unlock',
|
|
@@ -748,6 +831,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
748
831
|
passphraseState: undefined,
|
|
749
832
|
newSession: undefined,
|
|
750
833
|
unlockedAttachPin: false,
|
|
834
|
+
resumed: false,
|
|
751
835
|
});
|
|
752
836
|
|
|
753
837
|
expect(unlockDevice).toHaveBeenCalledTimes(1);
|
|
@@ -773,26 +857,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
773
857
|
expect(device.getInternalState()).toBe('hidden-session');
|
|
774
858
|
});
|
|
775
859
|
|
|
776
|
-
test
|
|
777
|
-
{
|
|
778
|
-
name: 'Host Passphrase',
|
|
779
|
-
response: { passphrase: 'hidden\u00e9 secret' },
|
|
780
|
-
select: { host_passphrase: { passphrase: 'hidden\u0065\u0301 secret' } },
|
|
781
|
-
event: undefined,
|
|
782
|
-
},
|
|
783
|
-
{
|
|
784
|
-
name: '设备 Passphrase',
|
|
785
|
-
response: { passphraseOnDevice: true },
|
|
786
|
-
select: { passphrase_on_device: {} },
|
|
787
|
-
event: DEVICE.PASSPHRASE_ON_DEVICE,
|
|
788
|
-
},
|
|
789
|
-
{
|
|
790
|
-
name: 'Attach PIN',
|
|
791
|
-
response: { attachPinOnDevice: true },
|
|
792
|
-
select: { attach_pin_on_device: {} },
|
|
793
|
-
event: DEVICE.ATTACH_PIN_ON_DEVICE,
|
|
794
|
-
},
|
|
795
|
-
])('maps $name selection to DeviceSessionOpen', async ({ response, select, event }) => {
|
|
860
|
+
test('gets the wallet session selected by the Protocol V2 device', async () => {
|
|
796
861
|
const device = Device.fromDescriptor({
|
|
797
862
|
id: 'select-device',
|
|
798
863
|
path: 'select-path',
|
|
@@ -813,33 +878,16 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
813
878
|
type: 'DeviceSession',
|
|
814
879
|
message: { session_id: 'hidden-session', btc_test_address: 'hidden-state' },
|
|
815
880
|
});
|
|
816
|
-
const promptPassphrase = jest.fn()
|
|
881
|
+
const promptPassphrase = jest.fn();
|
|
817
882
|
(device as any).commands = { typedCall, promptPassphrase };
|
|
818
|
-
const emit = jest.spyOn(device, 'emit');
|
|
819
883
|
|
|
820
884
|
await expect(getProtocolV2WalletSession(device)).resolves.toMatchObject({
|
|
821
885
|
newSession: 'hidden-session',
|
|
822
886
|
passphraseState: 'hidden-state',
|
|
823
887
|
});
|
|
824
888
|
|
|
825
|
-
expect(promptPassphrase).
|
|
826
|
-
|
|
827
|
-
source: 'wallet-session-coordinator',
|
|
828
|
-
reason: 'open-wallet',
|
|
829
|
-
existsAttachPinUser: true,
|
|
830
|
-
}),
|
|
831
|
-
{ cancelDeviceOnReject: false }
|
|
832
|
-
);
|
|
833
|
-
expect(typedCall).toHaveBeenCalledWith('DeviceSessionOpen', 'DeviceSession', {
|
|
834
|
-
select,
|
|
835
|
-
});
|
|
836
|
-
if (event) {
|
|
837
|
-
expect(emit).toHaveBeenCalledWith(
|
|
838
|
-
event,
|
|
839
|
-
device,
|
|
840
|
-
expect.objectContaining({ source: 'wallet-session-coordinator' })
|
|
841
|
-
);
|
|
842
|
-
}
|
|
889
|
+
expect(promptPassphrase).not.toHaveBeenCalled();
|
|
890
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {});
|
|
843
891
|
});
|
|
844
892
|
|
|
845
893
|
test('deviceStatusGet returns raw DeviceStatus and updates dynamic features', async () => {
|
|
@@ -900,77 +948,18 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
900
948
|
expect(method.requireProtocolV2).toBe(true);
|
|
901
949
|
});
|
|
902
950
|
|
|
903
|
-
test('
|
|
904
|
-
const typedCall = jest.fn().mockResolvedValue({
|
|
905
|
-
type: 'DeviceSession',
|
|
906
|
-
message: { session_id: 'new-session', btc_test_address: 'state-a' },
|
|
907
|
-
});
|
|
908
|
-
const updateInternalState = jest.fn();
|
|
909
|
-
const method = new DeviceSessionOpen({
|
|
910
|
-
payload: {
|
|
911
|
-
method: 'deviceSessionOpen',
|
|
912
|
-
connectId: 'connect-id',
|
|
913
|
-
select: { host_passphrase: { passphrase: 'hidden secret' } },
|
|
914
|
-
},
|
|
915
|
-
});
|
|
916
|
-
method.init();
|
|
917
|
-
method.device = stubDevice({
|
|
918
|
-
originalDescriptor: { ...descriptor, protocolType: 'V2' },
|
|
919
|
-
commands: { typedCall },
|
|
920
|
-
updateInternalState,
|
|
921
|
-
}) as any;
|
|
922
|
-
|
|
923
|
-
await expect(method.run()).resolves.toEqual({
|
|
924
|
-
session_id: 'new-session',
|
|
925
|
-
btc_test_address: 'state-a',
|
|
926
|
-
});
|
|
927
|
-
expect(typedCall).toHaveBeenCalledWith('DeviceSessionOpen', 'DeviceSession', {
|
|
928
|
-
select: { host_passphrase: { passphrase: 'hidden secret' } },
|
|
929
|
-
});
|
|
930
|
-
expect(updateInternalState).not.toHaveBeenCalled();
|
|
931
|
-
});
|
|
932
|
-
|
|
933
|
-
test.each([
|
|
934
|
-
{ payload: {}, reason: 'missing mode' },
|
|
935
|
-
{ payload: { resume: { session_id: '' } }, reason: 'empty resume session' },
|
|
936
|
-
{ payload: { select: { wallet_type: 0 } }, reason: 'removed standard wallet selection' },
|
|
937
|
-
{
|
|
938
|
-
payload: { select: { host_passphrase: { passphrase: 'secret' }, passphrase_on_device: {} } },
|
|
939
|
-
reason: 'multiple hidden wallet access modes',
|
|
940
|
-
},
|
|
941
|
-
])('rejects invalid deviceSessionOpen payload: $reason', ({ payload }) => {
|
|
942
|
-
const method = new DeviceSessionOpen({
|
|
943
|
-
payload: {
|
|
944
|
-
method: 'deviceSessionOpen',
|
|
945
|
-
connectId: 'connect-id',
|
|
946
|
-
...payload,
|
|
947
|
-
} as any,
|
|
948
|
-
});
|
|
949
|
-
|
|
950
|
-
expect(() => method.init()).toThrow();
|
|
951
|
-
});
|
|
952
|
-
|
|
953
|
-
test('routes public onboarding and session methods through CoreApi', async () => {
|
|
951
|
+
test('routes onboarding without exposing the raw session command through CoreApi', async () => {
|
|
954
952
|
const call = jest.fn().mockResolvedValue({ success: true, payload: {} });
|
|
955
953
|
const api = createCoreApi(call as any);
|
|
956
954
|
|
|
957
955
|
await api.deviceGetOnboardingStatus('connect-id', { retryCount: 1 });
|
|
958
|
-
await api.deviceSessionOpen('connect-id', {
|
|
959
|
-
retryCount: 1,
|
|
960
|
-
select: { passphrase_on_device: {} },
|
|
961
|
-
});
|
|
962
956
|
|
|
963
|
-
expect(call).
|
|
957
|
+
expect(call).toHaveBeenCalledWith({
|
|
964
958
|
method: 'deviceGetOnboardingStatus',
|
|
965
959
|
connectId: 'connect-id',
|
|
966
960
|
retryCount: 1,
|
|
967
961
|
});
|
|
968
|
-
expect(
|
|
969
|
-
method: 'deviceSessionOpen',
|
|
970
|
-
connectId: 'connect-id',
|
|
971
|
-
retryCount: 1,
|
|
972
|
-
select: { passphrase_on_device: {} },
|
|
973
|
-
});
|
|
962
|
+
expect(api).not.toHaveProperty('deviceSessionOpen');
|
|
974
963
|
});
|
|
975
964
|
|
|
976
965
|
test('reuses the cached session id for the selected Pro2 wallet', async () => {
|
|
@@ -1002,8 +991,8 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1002
991
|
newSession: 'session-b',
|
|
1003
992
|
});
|
|
1004
993
|
|
|
1005
|
-
expect(typedCall).toHaveBeenCalledWith('
|
|
1006
|
-
|
|
994
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
995
|
+
session_id: 'session-a',
|
|
1007
996
|
});
|
|
1008
997
|
expect(device.getInternalState()).toBe('session-b');
|
|
1009
998
|
});
|
|
@@ -1031,7 +1020,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1031
1020
|
expect(device.getInternalState()).toBeUndefined();
|
|
1032
1021
|
});
|
|
1033
1022
|
|
|
1034
|
-
test('
|
|
1023
|
+
test('clears the selected Pro2 cache entry after session rejection', async () => {
|
|
1035
1024
|
const device = Device.fromDescriptor({
|
|
1036
1025
|
id: 'cache-device-3',
|
|
1037
1026
|
path: 'cache-path-3',
|
|
@@ -1049,33 +1038,22 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1049
1038
|
);
|
|
1050
1039
|
device.passphraseState = 'state-a';
|
|
1051
1040
|
preloadSessionCache('stable-device-3', 'state-a', 'session-a');
|
|
1052
|
-
const typedCall = jest
|
|
1053
|
-
.
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
});
|
|
1059
|
-
const promptPassphrase = jest.fn().mockResolvedValue({ passphrase: 'state-a-secret' });
|
|
1041
|
+
const typedCall = jest.fn().mockRejectedValue(
|
|
1042
|
+
Object.assign(new Error('SE request failed'), {
|
|
1043
|
+
errorCode: HardwareErrorCode.RuntimeError,
|
|
1044
|
+
})
|
|
1045
|
+
);
|
|
1046
|
+
const promptPassphrase = jest.fn();
|
|
1060
1047
|
(device as any).commands = { typedCall, promptPassphrase };
|
|
1061
1048
|
|
|
1062
|
-
await expect(getProtocolV2WalletSession(device)).
|
|
1063
|
-
|
|
1064
|
-
newSession: 'session-b',
|
|
1049
|
+
await expect(getProtocolV2WalletSession(device)).rejects.toMatchObject({
|
|
1050
|
+
errorCode: HardwareErrorCode.RuntimeError,
|
|
1065
1051
|
});
|
|
1066
1052
|
expect(typedCall.mock.calls).toEqual([
|
|
1067
|
-
['
|
|
1068
|
-
[
|
|
1069
|
-
'DeviceSessionOpen',
|
|
1070
|
-
'DeviceSession',
|
|
1071
|
-
{
|
|
1072
|
-
select: {
|
|
1073
|
-
host_passphrase: { passphrase: 'state-a-secret' },
|
|
1074
|
-
},
|
|
1075
|
-
},
|
|
1076
|
-
],
|
|
1053
|
+
['DeviceSessionGet', 'DeviceSession', { session_id: 'session-a' }],
|
|
1077
1054
|
]);
|
|
1078
|
-
expect(
|
|
1055
|
+
expect(promptPassphrase).not.toHaveBeenCalled();
|
|
1056
|
+
expect(device.getInternalState()).toBeUndefined();
|
|
1079
1057
|
});
|
|
1080
1058
|
|
|
1081
1059
|
test('does not retry an invalid Pro2 session when no cached session was sent', async () => {
|
|
@@ -1094,26 +1072,20 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1094
1072
|
},
|
|
1095
1073
|
}
|
|
1096
1074
|
);
|
|
1097
|
-
const typedCall = jest
|
|
1098
|
-
.
|
|
1099
|
-
|
|
1075
|
+
const typedCall = jest.fn().mockRejectedValue(
|
|
1076
|
+
Object.assign(new Error('Invalid session'), {
|
|
1077
|
+
errorCode: HardwareErrorCode.WalletSessionInvalid,
|
|
1078
|
+
})
|
|
1079
|
+
);
|
|
1100
1080
|
(device as any).commands = {
|
|
1101
1081
|
typedCall,
|
|
1102
1082
|
promptPassphrase: jest.fn().mockResolvedValue({ passphrase: 'new-secret' }),
|
|
1103
1083
|
};
|
|
1104
1084
|
|
|
1105
|
-
await expect(getProtocolV2WalletSession(device)).rejects.
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
'DeviceSession',
|
|
1110
|
-
{
|
|
1111
|
-
select: {
|
|
1112
|
-
host_passphrase: { passphrase: 'new-secret' },
|
|
1113
|
-
},
|
|
1114
|
-
},
|
|
1115
|
-
],
|
|
1116
|
-
]);
|
|
1085
|
+
await expect(getProtocolV2WalletSession(device)).rejects.toMatchObject({
|
|
1086
|
+
errorCode: HardwareErrorCode.WalletSessionInvalid,
|
|
1087
|
+
});
|
|
1088
|
+
expect(typedCall.mock.calls).toEqual([['DeviceSessionGet', 'DeviceSession', {}]]);
|
|
1117
1089
|
});
|
|
1118
1090
|
|
|
1119
1091
|
test('rejects a mismatched Pro2 wallet state and clears the selected cache entry', async () => {
|
|
@@ -1151,7 +1123,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1151
1123
|
expect(device.getInternalState()).toBeUndefined();
|
|
1152
1124
|
});
|
|
1153
1125
|
|
|
1154
|
-
test('unlocks and retries
|
|
1126
|
+
test('unlocks and retries DeviceSessionGet when wallet selection is locked', async () => {
|
|
1155
1127
|
const device = Device.fromDescriptor({
|
|
1156
1128
|
id: 'cache-device-4',
|
|
1157
1129
|
path: 'cache-path-4',
|
|
@@ -1168,7 +1140,10 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1168
1140
|
.fn()
|
|
1169
1141
|
.mockRejectedValueOnce(lockedError)
|
|
1170
1142
|
.mockResolvedValueOnce({
|
|
1171
|
-
message: {
|
|
1143
|
+
message: {
|
|
1144
|
+
session_id: 'session-after-unlock',
|
|
1145
|
+
btc_test_address: 'state-after-unlock',
|
|
1146
|
+
},
|
|
1172
1147
|
});
|
|
1173
1148
|
(device as any).commands = {
|
|
1174
1149
|
typedCall,
|
|
@@ -1178,6 +1153,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1178
1153
|
|
|
1179
1154
|
await expect(getProtocolV2WalletSession(device)).resolves.toMatchObject({
|
|
1180
1155
|
newSession: 'session-after-unlock',
|
|
1156
|
+
passphraseState: 'state-after-unlock',
|
|
1181
1157
|
});
|
|
1182
1158
|
expect(unlockDevice).toHaveBeenCalledTimes(1);
|
|
1183
1159
|
expect(typedCall).toHaveBeenCalledTimes(2);
|
|
@@ -1256,7 +1232,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1256
1232
|
} as any,
|
|
1257
1233
|
});
|
|
1258
1234
|
|
|
1259
|
-
(device as any).updateProtocolV2Features(protocolV2BootloaderDeviceInfo, null);
|
|
1235
|
+
(device as any).updateProtocolV2Features(protocolV2BootloaderDeviceInfo, null, 'bootloader');
|
|
1260
1236
|
|
|
1261
1237
|
expect(device.features?.mode).toBe('bootloader');
|
|
1262
1238
|
expect(device.features?.bootloaderMode).toBe(true);
|
|
@@ -1311,7 +1287,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1311
1287
|
);
|
|
1312
1288
|
});
|
|
1313
1289
|
|
|
1314
|
-
test('
|
|
1290
|
+
test('does not expose getPassphraseState on Pro2 devices', async () => {
|
|
1315
1291
|
const features = {
|
|
1316
1292
|
deviceId: null,
|
|
1317
1293
|
deviceType: 'pro2',
|
|
@@ -1349,7 +1325,9 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1349
1325
|
getCurrentPassphraseProtection: () => true,
|
|
1350
1326
|
}) as any;
|
|
1351
1327
|
|
|
1352
|
-
await expect(method.run()).
|
|
1328
|
+
await expect(method.run()).rejects.toMatchObject({
|
|
1329
|
+
errorCode: HardwareErrorCode.DeviceNotSupportMethod,
|
|
1330
|
+
});
|
|
1353
1331
|
expect(getFeatures).not.toHaveBeenCalled();
|
|
1354
1332
|
});
|
|
1355
1333
|
|
|
@@ -1382,7 +1360,9 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1382
1360
|
getCurrentPassphraseProtection: () => false,
|
|
1383
1361
|
}) as any;
|
|
1384
1362
|
|
|
1385
|
-
await expect(method.run()).
|
|
1363
|
+
await expect(method.run()).rejects.toMatchObject({
|
|
1364
|
+
errorCode: HardwareErrorCode.DeviceNotSupportMethod,
|
|
1365
|
+
});
|
|
1386
1366
|
expect(typedCall).not.toHaveBeenCalled();
|
|
1387
1367
|
expect(updateInternalState).not.toHaveBeenCalled();
|
|
1388
1368
|
});
|
|
@@ -1424,15 +1404,11 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1424
1404
|
getCurrentPassphraseProtection: () => true,
|
|
1425
1405
|
}) as any;
|
|
1426
1406
|
|
|
1427
|
-
await expect(method.run()).
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
'pro2-device-id',
|
|
1433
|
-
undefined,
|
|
1434
|
-
null
|
|
1435
|
-
);
|
|
1407
|
+
await expect(method.run()).rejects.toMatchObject({
|
|
1408
|
+
errorCode: HardwareErrorCode.DeviceNotSupportMethod,
|
|
1409
|
+
});
|
|
1410
|
+
expect(clearInternalState).not.toHaveBeenCalled();
|
|
1411
|
+
expect(updateInternalState).not.toHaveBeenCalled();
|
|
1436
1412
|
});
|
|
1437
1413
|
|
|
1438
1414
|
test('stores Pro2 passphrase session cache without synthetic device id', async () => {
|
|
@@ -1507,11 +1483,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1507
1483
|
expect(device.getInternalState()).toBeUndefined();
|
|
1508
1484
|
device.passphraseState = 'state-auto';
|
|
1509
1485
|
expect(device.getInternalState()).toBe('session-auto');
|
|
1510
|
-
expect(typedCall).toHaveBeenLastCalledWith('
|
|
1511
|
-
select: {
|
|
1512
|
-
host_passphrase: { passphrase: 'state-auto-secret' },
|
|
1513
|
-
},
|
|
1514
|
-
});
|
|
1486
|
+
expect(typedCall).toHaveBeenLastCalledWith('DeviceSessionGet', 'DeviceSession', {});
|
|
1515
1487
|
});
|
|
1516
1488
|
|
|
1517
1489
|
test('does not mark Pro2 passphrase enabled from a main PIN session alone', async () => {
|
|
@@ -1581,11 +1553,32 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1581
1553
|
);
|
|
1582
1554
|
|
|
1583
1555
|
expect(device.getInternalState()).toBeUndefined();
|
|
1584
|
-
expect(typedCall).toHaveBeenNthCalledWith(1, '
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
});
|
|
1556
|
+
expect(typedCall).toHaveBeenNthCalledWith(1, 'DeviceSessionGet', 'DeviceSession', {});
|
|
1557
|
+
});
|
|
1558
|
+
|
|
1559
|
+
test('useEmptyPassphrase ignores a stale hidden-wallet state during Protocol V2 safety check', async () => {
|
|
1560
|
+
const device = Device.fromDescriptor({ ...descriptor, protocolType: 'V2' } as any);
|
|
1561
|
+
const typedCall = jest.fn();
|
|
1562
|
+
|
|
1563
|
+
(device as any).features = normalizeProtocolV2Features(
|
|
1564
|
+
{ ...descriptor, protocolType: 'V2' } as any,
|
|
1565
|
+
{
|
|
1566
|
+
status: {
|
|
1567
|
+
device_id: 'main-wallet-device',
|
|
1568
|
+
unlocked: true,
|
|
1569
|
+
passphrase_enabled: true,
|
|
1570
|
+
unlocked_by_attach_to_pin: false,
|
|
1571
|
+
},
|
|
1572
|
+
}
|
|
1573
|
+
);
|
|
1574
|
+
(device as any).features.firmwareVersion = '4.15.0';
|
|
1575
|
+
(device as any).commands = { typedCall };
|
|
1576
|
+
device.passphraseState = 'stale-hidden-state';
|
|
1577
|
+
|
|
1578
|
+
await expect(
|
|
1579
|
+
device.checkPassphraseStateSafety('stale-hidden-state', true, false)
|
|
1580
|
+
).resolves.toBe(true);
|
|
1581
|
+
expect(typedCall).not.toHaveBeenCalled();
|
|
1589
1582
|
});
|
|
1590
1583
|
|
|
1591
1584
|
test('marks fallback features as unavailable when DeviceInfo is missing', () => {
|
|
@@ -1647,15 +1640,17 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1647
1640
|
passphraseProtection: true,
|
|
1648
1641
|
};
|
|
1649
1642
|
|
|
1650
|
-
|
|
1643
|
+
const message = device.toMessageObject();
|
|
1644
|
+
expect(message).toMatchObject({
|
|
1645
|
+
serialNo: 'CACHED-SERIAL',
|
|
1651
1646
|
uuid: 'CACHED-SERIAL',
|
|
1652
1647
|
deviceId: null,
|
|
1653
1648
|
bleName: 'Cached BLE',
|
|
1654
1649
|
name: 'Cached BLE',
|
|
1655
|
-
displayName: 'Cached Label',
|
|
1656
1650
|
label: 'Cached Label',
|
|
1657
1651
|
deviceType: 'pro2',
|
|
1658
1652
|
});
|
|
1653
|
+
expect(message).not.toHaveProperty('sessionId');
|
|
1659
1654
|
expect(device.getCurrentPassphraseProtection()).toBe(true);
|
|
1660
1655
|
expect(device.hasUsePassphrase()).toBe(true);
|
|
1661
1656
|
});
|
|
@@ -1674,6 +1669,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1674
1669
|
expect(current.getCurrentDeviceId()).toBeUndefined();
|
|
1675
1670
|
expect(current.getCurrentSerialNo()).toBe('CACHED-SERIAL');
|
|
1676
1671
|
expect(current.toMessageObject()).toMatchObject({
|
|
1672
|
+
serialNo: 'CACHED-SERIAL',
|
|
1677
1673
|
uuid: 'CACHED-SERIAL',
|
|
1678
1674
|
deviceId: null,
|
|
1679
1675
|
});
|
|
@@ -1708,6 +1704,10 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1708
1704
|
hw: true,
|
|
1709
1705
|
fw: true,
|
|
1710
1706
|
coprocessor: true,
|
|
1707
|
+
se1: true,
|
|
1708
|
+
se2: true,
|
|
1709
|
+
se3: true,
|
|
1710
|
+
se4: true,
|
|
1711
1711
|
},
|
|
1712
1712
|
types: {
|
|
1713
1713
|
version: true,
|
|
@@ -1869,18 +1869,24 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1869
1869
|
expect(typedCall).not.toHaveBeenCalled();
|
|
1870
1870
|
});
|
|
1871
1871
|
|
|
1872
|
-
test('initializes and reuses Protocol V2 features
|
|
1872
|
+
test('initializes and reuses Protocol V2 features after DeviceStatusGet succeeds', async () => {
|
|
1873
1873
|
const device = Device.fromDescriptor({
|
|
1874
1874
|
path: 'usb-path',
|
|
1875
1875
|
protocolType: 'V2',
|
|
1876
1876
|
} as any);
|
|
1877
|
-
const typedCall = jest
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1877
|
+
const typedCall = jest
|
|
1878
|
+
.fn()
|
|
1879
|
+
.mockResolvedValueOnce({
|
|
1880
|
+
type: 'DeviceInfo',
|
|
1881
|
+
message: {
|
|
1882
|
+
hw: { serial_no: 'PR2SERIAL' },
|
|
1883
|
+
fw: { application: { version: '1.2.3' } },
|
|
1884
|
+
},
|
|
1885
|
+
})
|
|
1886
|
+
.mockResolvedValueOnce({
|
|
1887
|
+
type: 'DeviceStatus',
|
|
1888
|
+
message: { init_states: true, unlocked: true },
|
|
1889
|
+
});
|
|
1884
1890
|
|
|
1885
1891
|
(device as any).commands = { typedCall };
|
|
1886
1892
|
|
|
@@ -1897,8 +1903,9 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1897
1903
|
expect(device.features?.passphraseProtection).toBeNull();
|
|
1898
1904
|
expect(device.features?.label).toBeNull();
|
|
1899
1905
|
expect(device.features?.firmwareVersion).toBe('1.2.3');
|
|
1900
|
-
expect(typedCall).toHaveBeenCalledTimes(
|
|
1901
|
-
expect(typedCall).
|
|
1906
|
+
expect(typedCall).toHaveBeenCalledTimes(2);
|
|
1907
|
+
expect(typedCall).toHaveBeenNthCalledWith(
|
|
1908
|
+
1,
|
|
1902
1909
|
'DeviceInfoGet',
|
|
1903
1910
|
'DeviceInfo',
|
|
1904
1911
|
{
|
|
@@ -1906,6 +1913,10 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1906
1913
|
hw: true,
|
|
1907
1914
|
fw: true,
|
|
1908
1915
|
coprocessor: true,
|
|
1916
|
+
se1: true,
|
|
1917
|
+
se2: true,
|
|
1918
|
+
se3: true,
|
|
1919
|
+
se4: true,
|
|
1909
1920
|
},
|
|
1910
1921
|
types: {
|
|
1911
1922
|
version: true,
|
|
@@ -1916,9 +1927,10 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1916
1927
|
timeoutMs: PROTOCOL_V2_DEVICE_INFO_TIMEOUT_MS,
|
|
1917
1928
|
}
|
|
1918
1929
|
);
|
|
1930
|
+
expect(typedCall).toHaveBeenNthCalledWith(2, 'DeviceStatusGet', 'DeviceStatus', {});
|
|
1919
1931
|
});
|
|
1920
1932
|
|
|
1921
|
-
test('recognizes bootloader
|
|
1933
|
+
test('recognizes bootloader when DeviceStatusGet fails', async () => {
|
|
1922
1934
|
const device = Device.fromDescriptor({
|
|
1923
1935
|
path: 'usb-path',
|
|
1924
1936
|
protocolType: 'V2',
|
|
@@ -1930,21 +1942,18 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1930
1942
|
message: {
|
|
1931
1943
|
hw: { serial_no: 'PR2SERIAL' },
|
|
1932
1944
|
fw: { bootloader: { version: '0.2.0' } },
|
|
1945
|
+
se1: {},
|
|
1933
1946
|
},
|
|
1934
1947
|
})
|
|
1935
|
-
.
|
|
1936
|
-
type: 'DeviceStatus',
|
|
1937
|
-
message: {
|
|
1938
|
-
device_id: 'UNEXPECTED-SEPARATE-STATUS',
|
|
1939
|
-
},
|
|
1940
|
-
});
|
|
1948
|
+
.mockRejectedValueOnce(new Error('DeviceStatusGet unsupported in bootloader'));
|
|
1941
1949
|
|
|
1942
1950
|
(device as any).commands = { typedCall };
|
|
1943
1951
|
|
|
1944
1952
|
await device.initialize();
|
|
1945
1953
|
|
|
1946
|
-
expect(typedCall).toHaveBeenCalledTimes(
|
|
1947
|
-
expect(typedCall).
|
|
1954
|
+
expect(typedCall).toHaveBeenCalledTimes(2);
|
|
1955
|
+
expect(typedCall).toHaveBeenNthCalledWith(
|
|
1956
|
+
1,
|
|
1948
1957
|
'DeviceInfoGet',
|
|
1949
1958
|
'DeviceInfo',
|
|
1950
1959
|
expect.not.objectContaining({
|
|
@@ -1952,6 +1961,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1952
1961
|
}),
|
|
1953
1962
|
expect.anything()
|
|
1954
1963
|
);
|
|
1964
|
+
expect(typedCall).toHaveBeenNthCalledWith(2, 'DeviceStatusGet', 'DeviceStatus', {});
|
|
1955
1965
|
expect(device.features).toMatchObject({
|
|
1956
1966
|
deviceId: null,
|
|
1957
1967
|
bootloaderMode: true,
|
|
@@ -1959,72 +1969,36 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1959
1969
|
});
|
|
1960
1970
|
});
|
|
1961
1971
|
|
|
1962
|
-
test('does not
|
|
1963
|
-
const
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1972
|
+
test('does not reinterpret a state update error as DeviceStatusGet failure', async () => {
|
|
1973
|
+
const device = Device.fromDescriptor({
|
|
1974
|
+
path: 'usb-path',
|
|
1975
|
+
protocolType: 'V2',
|
|
1976
|
+
} as any);
|
|
1977
|
+
const typedCall = jest.fn().mockResolvedValue({
|
|
1978
|
+
type: 'DeviceStatus',
|
|
1979
|
+
message: { init_states: true, unlocked: true },
|
|
1968
1980
|
});
|
|
1969
|
-
const
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
return Promise.resolve({
|
|
1974
|
-
type: 'DeviceInfo',
|
|
1975
|
-
message: {
|
|
1976
|
-
hw: { serial_no: 'PR2BOOT' },
|
|
1977
|
-
fw: { bootloader: { version: '0.2.0' } },
|
|
1978
|
-
se1: {},
|
|
1979
|
-
},
|
|
1981
|
+
const updateProtocolV2Features = jest
|
|
1982
|
+
.spyOn(device, 'updateProtocolV2Features')
|
|
1983
|
+
.mockImplementation(() => {
|
|
1984
|
+
throw new Error('state update failed');
|
|
1980
1985
|
});
|
|
1981
|
-
}
|
|
1982
|
-
(method as any).device = stubDevice({
|
|
1983
|
-
getCommands: () => ({ typedCall }),
|
|
1984
|
-
updateProtocolV2Features: jest.fn(),
|
|
1985
|
-
});
|
|
1986
|
-
|
|
1987
|
-
await expect((method as any).probeProtocolV2NormalMode()).resolves.toBe(false);
|
|
1988
|
-
expect(typedCall).toHaveBeenCalledTimes(1);
|
|
1989
|
-
expect(typedCall).toHaveBeenCalledWith(
|
|
1990
|
-
'DeviceInfoGet',
|
|
1991
|
-
'DeviceInfo',
|
|
1992
|
-
expect.not.objectContaining({
|
|
1993
|
-
targets: expect.objectContaining({ status: true }),
|
|
1994
|
-
}),
|
|
1995
|
-
expect.anything()
|
|
1996
|
-
);
|
|
1997
|
-
});
|
|
1986
|
+
(device as any).commands = { typedCall };
|
|
1998
1987
|
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
});
|
|
2006
|
-
const typedCall = jest.fn().mockImplementation((request: string) => {
|
|
2007
|
-
if (request === 'DeviceStatusGet') {
|
|
2008
|
-
throw new Error('romloader must not receive DeviceStatusGet');
|
|
2009
|
-
}
|
|
2010
|
-
return Promise.resolve({
|
|
2011
|
-
type: 'DeviceInfo',
|
|
2012
|
-
message: {
|
|
2013
|
-
hw: { serial_no: 'PR2ROM' },
|
|
2014
|
-
fw: {
|
|
2015
|
-
romloader: { version: '0.1.0' },
|
|
2016
|
-
bootloader: { version: '0.2.0' },
|
|
2017
|
-
},
|
|
2018
|
-
},
|
|
2019
|
-
});
|
|
2020
|
-
});
|
|
2021
|
-
(method as any).device = stubDevice({
|
|
2022
|
-
getCommands: () => ({ typedCall }),
|
|
2023
|
-
updateProtocolV2Features: jest.fn(),
|
|
2024
|
-
});
|
|
1988
|
+
await expect(
|
|
1989
|
+
device.probeProtocolV2RuntimeState({
|
|
1990
|
+
hw: { serial_no: 'PR2SERIAL' },
|
|
1991
|
+
fw: { application: { version: '1.0.0' } },
|
|
1992
|
+
})
|
|
1993
|
+
).rejects.toThrow('state update failed');
|
|
2025
1994
|
|
|
2026
|
-
await expect((method as any).probeProtocolV2NormalMode()).resolves.toBe(false);
|
|
2027
1995
|
expect(typedCall).toHaveBeenCalledTimes(1);
|
|
1996
|
+
expect(updateProtocolV2Features).toHaveBeenCalledTimes(1);
|
|
1997
|
+
expect(updateProtocolV2Features).toHaveBeenCalledWith(
|
|
1998
|
+
expect.anything(),
|
|
1999
|
+
expect.objectContaining({ init_states: true }),
|
|
2000
|
+
'normal'
|
|
2001
|
+
);
|
|
2028
2002
|
});
|
|
2029
2003
|
|
|
2030
2004
|
test('refreshes Protocol V2 features without falling back to V1 GetFeatures', async () => {
|
|
@@ -2041,12 +2015,20 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2041
2015
|
fw: { application: { version: '1.2.3' } },
|
|
2042
2016
|
},
|
|
2043
2017
|
})
|
|
2018
|
+
.mockResolvedValueOnce({
|
|
2019
|
+
type: 'DeviceStatus',
|
|
2020
|
+
message: { init_states: true, unlocked: true },
|
|
2021
|
+
})
|
|
2044
2022
|
.mockResolvedValueOnce({
|
|
2045
2023
|
type: 'DeviceInfo',
|
|
2046
2024
|
message: {
|
|
2047
2025
|
hw: { serial_no: 'PR2SERIAL' },
|
|
2048
2026
|
fw: { application: { version: '1.2.4' } },
|
|
2049
2027
|
},
|
|
2028
|
+
})
|
|
2029
|
+
.mockResolvedValueOnce({
|
|
2030
|
+
type: 'DeviceStatus',
|
|
2031
|
+
message: { init_states: true, unlocked: true },
|
|
2050
2032
|
});
|
|
2051
2033
|
|
|
2052
2034
|
(device as any).commands = { typedCall };
|
|
@@ -2065,9 +2047,9 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2065
2047
|
firmwareVersion: '1.2.4',
|
|
2066
2048
|
passphraseProtection: null,
|
|
2067
2049
|
});
|
|
2068
|
-
expect(typedCall).toHaveBeenCalledTimes(
|
|
2050
|
+
expect(typedCall).toHaveBeenCalledTimes(4);
|
|
2069
2051
|
expect(typedCall).toHaveBeenNthCalledWith(
|
|
2070
|
-
|
|
2052
|
+
3,
|
|
2071
2053
|
'DeviceInfoGet',
|
|
2072
2054
|
'DeviceInfo',
|
|
2073
2055
|
{
|
|
@@ -2075,6 +2057,10 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2075
2057
|
hw: true,
|
|
2076
2058
|
fw: true,
|
|
2077
2059
|
coprocessor: true,
|
|
2060
|
+
se1: true,
|
|
2061
|
+
se2: true,
|
|
2062
|
+
se3: true,
|
|
2063
|
+
se4: true,
|
|
2078
2064
|
},
|
|
2079
2065
|
types: {
|
|
2080
2066
|
version: true,
|
|
@@ -2142,12 +2128,22 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2142
2128
|
message: { message: 'ok' },
|
|
2143
2129
|
};
|
|
2144
2130
|
}
|
|
2131
|
+
if (requestType === 'DeviceStatusGet') {
|
|
2132
|
+
return {
|
|
2133
|
+
type: 'DeviceStatus',
|
|
2134
|
+
message: {
|
|
2135
|
+
init_states: true,
|
|
2136
|
+
unlocked: true,
|
|
2137
|
+
passphrase_enabled: false,
|
|
2138
|
+
},
|
|
2139
|
+
};
|
|
2140
|
+
}
|
|
2145
2141
|
throw new Error(`Unexpected request: ${requestType}`);
|
|
2146
2142
|
});
|
|
2147
2143
|
|
|
2148
2144
|
(device as any).commands = { typedCall };
|
|
2149
|
-
// Pro2
|
|
2150
|
-
//
|
|
2145
|
+
// Pro2 uses an independent version line and dedicated device-side PIN flow,
|
|
2146
|
+
// without the Pro version threshold or GetAddress probe fallback.
|
|
2151
2147
|
(device as any).features = normalizeProtocolV2Features(
|
|
2152
2148
|
{ ...descriptor, protocolType: 'V2' } as any,
|
|
2153
2149
|
{
|
|
@@ -2161,6 +2157,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2161
2157
|
|
|
2162
2158
|
expect(typedCall.mock.calls).toEqual([
|
|
2163
2159
|
['DeviceSessionAskPin', 'Success', undefined, { timeoutMs: 120_000 }],
|
|
2160
|
+
['DeviceStatusGet', 'DeviceStatus', {}],
|
|
2164
2161
|
]);
|
|
2165
2162
|
expect(typedCall).not.toHaveBeenCalledWith('GetAddress', 'Address', expect.anything());
|
|
2166
2163
|
expect(typedCall).not.toHaveBeenCalledWith('GetFeatures', 'Features', {});
|
|
@@ -2171,7 +2168,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2171
2168
|
});
|
|
2172
2169
|
});
|
|
2173
2170
|
|
|
2174
|
-
test('
|
|
2171
|
+
test('refreshes Protocol V2 passphrase and attach-PIN status after unlock', async () => {
|
|
2175
2172
|
const device = Device.fromDescriptor({ ...descriptor, protocolType: 'V2' } as any);
|
|
2176
2173
|
(device as any).features = normalizeProtocolV2Features(
|
|
2177
2174
|
{ ...descriptor, protocolType: 'V2' } as any,
|
|
@@ -2188,6 +2185,18 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2188
2185
|
message: { message: 'ok' },
|
|
2189
2186
|
};
|
|
2190
2187
|
}
|
|
2188
|
+
if (requestType === 'DeviceStatusGet') {
|
|
2189
|
+
return {
|
|
2190
|
+
type: 'DeviceStatus',
|
|
2191
|
+
message: {
|
|
2192
|
+
init_states: true,
|
|
2193
|
+
unlocked: true,
|
|
2194
|
+
passphrase_enabled: true,
|
|
2195
|
+
attach_to_pin_enabled: true,
|
|
2196
|
+
unlocked_by_attach_to_pin: true,
|
|
2197
|
+
},
|
|
2198
|
+
};
|
|
2199
|
+
}
|
|
2191
2200
|
throw new Error(`Unexpected request: ${requestType}`);
|
|
2192
2201
|
});
|
|
2193
2202
|
(device as any).commands = { typedCall };
|
|
@@ -2196,11 +2205,12 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2196
2205
|
|
|
2197
2206
|
expect(typedCall.mock.calls).toEqual([
|
|
2198
2207
|
['DeviceSessionAskPin', 'Success', undefined, { timeoutMs: 120_000 }],
|
|
2208
|
+
['DeviceStatusGet', 'DeviceStatus', {}],
|
|
2199
2209
|
]);
|
|
2200
2210
|
expect((device as any).profile).toBeUndefined();
|
|
2201
2211
|
expect(device.features?.unlocked).toBe(true);
|
|
2202
|
-
expect(device.features?.passphraseProtection).
|
|
2203
|
-
expect(device.features?.unlockedAttachPin).
|
|
2212
|
+
expect(device.features?.passphraseProtection).toBe(true);
|
|
2213
|
+
expect(device.features?.unlockedAttachPin).toBe(true);
|
|
2204
2214
|
});
|
|
2205
2215
|
|
|
2206
2216
|
test('maps unsupported Protocol V2 DeviceSessionAskPin to DeviceNotSupportMethod', async () => {
|
|
@@ -2227,7 +2237,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2227
2237
|
['DeviceSessionAskPin', 'Success', undefined, { timeoutMs: 120_000 }],
|
|
2228
2238
|
]);
|
|
2229
2239
|
expect(typedCall).not.toHaveBeenCalledWith(
|
|
2230
|
-
'
|
|
2240
|
+
'DeviceSessionGet',
|
|
2231
2241
|
'DeviceSession',
|
|
2232
2242
|
expect.anything()
|
|
2233
2243
|
);
|
|
@@ -2680,6 +2690,12 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
2680
2690
|
},
|
|
2681
2691
|
});
|
|
2682
2692
|
}
|
|
2693
|
+
if (name === 'DeviceStatusGet') {
|
|
2694
|
+
return Promise.resolve({
|
|
2695
|
+
type: 'DeviceStatus',
|
|
2696
|
+
message: { init_states: true, unlocked: true },
|
|
2697
|
+
});
|
|
2698
|
+
}
|
|
2683
2699
|
return Promise.reject(new Error(`unexpected call ${name}`));
|
|
2684
2700
|
});
|
|
2685
2701
|
const commands = { typedCall };
|
|
@@ -2701,7 +2717,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
2701
2717
|
const versions = await (method as any).waitForProtocolV2FinalFeatures();
|
|
2702
2718
|
|
|
2703
2719
|
expect(acquire).toHaveBeenCalledWith('ble-id', null, true, 'V2');
|
|
2704
|
-
//
|
|
2720
|
+
// Completion uses the VERSIONS request, including SE targets, with matching scope.
|
|
2705
2721
|
expect(typedCall).toHaveBeenNthCalledWith(
|
|
2706
2722
|
1,
|
|
2707
2723
|
'DeviceInfoGet',
|
|
@@ -2723,7 +2739,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
2723
2739
|
},
|
|
2724
2740
|
{ timeoutMs: 5000 }
|
|
2725
2741
|
);
|
|
2726
|
-
expect(typedCall).toHaveBeenCalledTimes(
|
|
2742
|
+
expect(typedCall).toHaveBeenCalledTimes(2);
|
|
2727
2743
|
expect(typedCall).not.toHaveBeenCalledWith('Initialize', 'Features', {});
|
|
2728
2744
|
expect(versions).toEqual({
|
|
2729
2745
|
bootloaderVersion: '0.0.0',
|
|
@@ -2795,9 +2811,14 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
2795
2811
|
message: 'Device rebooted successfully',
|
|
2796
2812
|
});
|
|
2797
2813
|
(method as any).checkDeviceToBootloader = jest.fn();
|
|
2798
|
-
const typedCall = jest.fn().
|
|
2799
|
-
|
|
2800
|
-
|
|
2814
|
+
const typedCall = jest.fn().mockImplementation((requestType: string) => {
|
|
2815
|
+
if (requestType === 'DeviceInfoGet') {
|
|
2816
|
+
return Promise.resolve({
|
|
2817
|
+
type: 'DeviceInfo',
|
|
2818
|
+
message: protocolV2BootloaderDeviceInfo,
|
|
2819
|
+
});
|
|
2820
|
+
}
|
|
2821
|
+
return Promise.reject(new Error('DeviceStatusGet unavailable in bootloader'));
|
|
2801
2822
|
});
|
|
2802
2823
|
const reconnectProtocolV2Device = jest.fn().mockImplementation(() => {
|
|
2803
2824
|
(method as any).device.isBootloader = () => true;
|
|
@@ -2851,9 +2872,14 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
2851
2872
|
features: { bootloader_mode: false, capabilities: [] },
|
|
2852
2873
|
isBootloader: () => false,
|
|
2853
2874
|
});
|
|
2854
|
-
const typedCall = jest.fn().
|
|
2855
|
-
|
|
2856
|
-
|
|
2875
|
+
const typedCall = jest.fn().mockImplementation((requestType: string) => {
|
|
2876
|
+
if (requestType === 'DeviceInfoGet') {
|
|
2877
|
+
return Promise.resolve({
|
|
2878
|
+
type: 'DeviceInfo',
|
|
2879
|
+
message: protocolV2BootloaderDeviceInfo,
|
|
2880
|
+
});
|
|
2881
|
+
}
|
|
2882
|
+
return Promise.reject(new Error('DeviceStatusGet unavailable in bootloader'));
|
|
2857
2883
|
});
|
|
2858
2884
|
const reconnectProtocolV2Device = jest
|
|
2859
2885
|
.fn()
|
|
@@ -2871,6 +2897,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
2871
2897
|
|
|
2872
2898
|
expect(reconnectProtocolV2Device).toHaveBeenCalledTimes(3);
|
|
2873
2899
|
expect(typedCall).toHaveBeenCalledTimes(1);
|
|
2900
|
+
expect(typedCall.mock.calls.map(call => call[0])).toEqual(['DeviceInfoGet']);
|
|
2874
2901
|
});
|
|
2875
2902
|
|
|
2876
2903
|
test('does not run generic initialize during Protocol V2 USB firmware reconnect', async () => {
|
|
@@ -2891,6 +2918,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
2891
2918
|
}),
|
|
2892
2919
|
},
|
|
2893
2920
|
updateFromCache: jest.fn(),
|
|
2921
|
+
hasDeviceAcquire: jest.fn(() => false),
|
|
2894
2922
|
acquire: jest.fn().mockResolvedValue(undefined),
|
|
2895
2923
|
initialize,
|
|
2896
2924
|
commands,
|
|
@@ -2914,7 +2942,92 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
2914
2942
|
expect(initialize).not.toHaveBeenCalled();
|
|
2915
2943
|
});
|
|
2916
2944
|
|
|
2917
|
-
test('
|
|
2945
|
+
test('reuses an acquired Protocol V2 USB session while polling reconnect readiness', async () => {
|
|
2946
|
+
const method = new FirmwareUpdateV4({
|
|
2947
|
+
id: 1,
|
|
2948
|
+
payload: {
|
|
2949
|
+
method: 'firmwareUpdateV4',
|
|
2950
|
+
},
|
|
2951
|
+
});
|
|
2952
|
+
const commands = { disposed: false, mainId: 'usb-session' };
|
|
2953
|
+
const acquire = jest.fn().mockResolvedValue(undefined);
|
|
2954
|
+
const device = stubDevice({
|
|
2955
|
+
originalDescriptor: {
|
|
2956
|
+
id: 'usb-id',
|
|
2957
|
+
path: 'usb-path',
|
|
2958
|
+
session: 'usb-session',
|
|
2959
|
+
protocolType: 'V2',
|
|
2960
|
+
},
|
|
2961
|
+
deviceConnector: {
|
|
2962
|
+
enumerate: jest.fn().mockResolvedValue({
|
|
2963
|
+
descriptors: [
|
|
2964
|
+
{
|
|
2965
|
+
id: 'usb-id',
|
|
2966
|
+
path: 'usb-path',
|
|
2967
|
+
session: 'usb-session',
|
|
2968
|
+
protocolType: 'V2',
|
|
2969
|
+
},
|
|
2970
|
+
],
|
|
2971
|
+
}),
|
|
2972
|
+
},
|
|
2973
|
+
updateDescriptor: jest.fn(),
|
|
2974
|
+
updateFromCache: jest.fn(),
|
|
2975
|
+
hasDeviceAcquire: jest.fn(() => true),
|
|
2976
|
+
acquire,
|
|
2977
|
+
commands,
|
|
2978
|
+
getCommands: () => commands,
|
|
2979
|
+
mainId: 'usb-session',
|
|
2980
|
+
});
|
|
2981
|
+
const getDevices = jest.spyOn(DevicePool, 'getDevices').mockResolvedValue({
|
|
2982
|
+
devices: {},
|
|
2983
|
+
deviceList: [{ getConnectId: () => 'usb-path' }],
|
|
2984
|
+
} as any);
|
|
2985
|
+
(method as any).device = device;
|
|
2986
|
+
|
|
2987
|
+
try {
|
|
2988
|
+
await (method as any).reconnectProtocolV2Device();
|
|
2989
|
+
await (method as any).reconnectProtocolV2Device();
|
|
2990
|
+
} finally {
|
|
2991
|
+
getDevices.mockRestore();
|
|
2992
|
+
}
|
|
2993
|
+
|
|
2994
|
+
expect(acquire).not.toHaveBeenCalled();
|
|
2995
|
+
expect(device.hasDeviceAcquire).toHaveBeenCalledTimes(2);
|
|
2996
|
+
});
|
|
2997
|
+
|
|
2998
|
+
test('stops final reconnect polling immediately when firmware changes device identity', async () => {
|
|
2999
|
+
const method = new FirmwareUpdateV4({
|
|
3000
|
+
id: 1,
|
|
3001
|
+
payload: {
|
|
3002
|
+
method: 'firmwareUpdateV4',
|
|
3003
|
+
},
|
|
3004
|
+
});
|
|
3005
|
+
const reconnectProtocolV2Device = jest.fn().mockResolvedValue(undefined);
|
|
3006
|
+
const typedCall = jest.fn().mockResolvedValue({
|
|
3007
|
+
type: 'DeviceInfo',
|
|
3008
|
+
message: {
|
|
3009
|
+
hw: { serial_no: 'PR2SERIAL' },
|
|
3010
|
+
fw: { application: { version: '1.0.0' } },
|
|
3011
|
+
},
|
|
3012
|
+
});
|
|
3013
|
+
(method as any).protocolV2ExpectedDeviceId = 'expected-device';
|
|
3014
|
+
(method as any).reconnectProtocolV2Device = reconnectProtocolV2Device;
|
|
3015
|
+
(method as any).device = stubDevice({
|
|
3016
|
+
getCommands: () => ({ typedCall }),
|
|
3017
|
+
probeProtocolV2RuntimeState: jest.fn().mockResolvedValue({
|
|
3018
|
+
deviceId: 'changed-device',
|
|
3019
|
+
mode: 'normal',
|
|
3020
|
+
bootloaderMode: false,
|
|
3021
|
+
}),
|
|
3022
|
+
});
|
|
3023
|
+
|
|
3024
|
+
await expect((method as any).waitForProtocolV2ReconnectAndFeatures(60_000)).rejects.toThrow(
|
|
3025
|
+
'identity mismatch'
|
|
3026
|
+
);
|
|
3027
|
+
expect(reconnectProtocolV2Device).toHaveBeenCalledTimes(1);
|
|
3028
|
+
});
|
|
3029
|
+
|
|
3030
|
+
test('reboots Protocol V2 firmware flow back to normal without legacy switch-firmware prompt', async () => {
|
|
2918
3031
|
const method = new FirmwareUpdateV4({
|
|
2919
3032
|
id: 1,
|
|
2920
3033
|
payload: {
|
|
@@ -2922,13 +3035,15 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
2922
3035
|
},
|
|
2923
3036
|
});
|
|
2924
3037
|
method.postTipMessage = jest.fn();
|
|
3038
|
+
(method as any).reconnectProtocolV2Device = jest.fn().mockResolvedValue(undefined);
|
|
2925
3039
|
(method as any).protocolV2Reboot = jest.fn().mockResolvedValue({
|
|
2926
3040
|
message: 'Device rebooted successfully',
|
|
2927
3041
|
});
|
|
2928
3042
|
|
|
2929
3043
|
await (method as any).exitProtocolV2BootloaderToNormal();
|
|
2930
3044
|
|
|
2931
|
-
expect(method.postTipMessage).toHaveBeenCalledWith('SwitchFirmwareReconnectDevice');
|
|
3045
|
+
expect(method.postTipMessage).not.toHaveBeenCalledWith('SwitchFirmwareReconnectDevice');
|
|
3046
|
+
expect((method as any).reconnectProtocolV2Device).toHaveBeenCalledTimes(1);
|
|
2932
3047
|
expect((method as any).protocolV2Reboot).toHaveBeenCalledWith(DeviceRebootType.Normal);
|
|
2933
3048
|
});
|
|
2934
3049
|
|
|
@@ -2974,7 +3089,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
2974
3089
|
});
|
|
2975
3090
|
});
|
|
2976
3091
|
|
|
2977
|
-
test('
|
|
3092
|
+
test('requires an explicit Protocol V2 update ACK before entering install state', async () => {
|
|
2978
3093
|
const method = new FirmwareUpdateV4({
|
|
2979
3094
|
id: 1,
|
|
2980
3095
|
payload: {
|
|
@@ -2994,17 +3109,19 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
2994
3109
|
getCommands: () => ({ typedCall }),
|
|
2995
3110
|
});
|
|
2996
3111
|
method.postTipMessage = jest.fn();
|
|
3112
|
+
method.postProgressMessage = jest.fn();
|
|
2997
3113
|
|
|
2998
3114
|
await expect(
|
|
2999
3115
|
(method as any).protocolV2StartFirmwareUpdate({
|
|
3000
3116
|
targets: [{ target_id: 4, path: 'vol1:firmware.bin' }],
|
|
3001
3117
|
})
|
|
3002
|
-
).
|
|
3118
|
+
).rejects.toThrow();
|
|
3003
3119
|
|
|
3004
|
-
expect(method.postTipMessage).
|
|
3120
|
+
expect(method.postTipMessage).not.toHaveBeenCalled();
|
|
3121
|
+
expect(method.postProgressMessage).not.toHaveBeenCalled();
|
|
3005
3122
|
});
|
|
3006
3123
|
|
|
3007
|
-
test('
|
|
3124
|
+
test('does not poll Protocol V2 install status when the update ACK times out', async () => {
|
|
3008
3125
|
const method = new FirmwareUpdateV4({
|
|
3009
3126
|
id: 1,
|
|
3010
3127
|
payload: {
|
|
@@ -3017,207 +3134,125 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3017
3134
|
getCommands: () => ({ typedCall }),
|
|
3018
3135
|
});
|
|
3019
3136
|
method.postTipMessage = jest.fn();
|
|
3137
|
+
method.postProgressMessage = jest.fn();
|
|
3020
3138
|
|
|
3021
3139
|
await expect(
|
|
3022
3140
|
(method as any).protocolV2StartFirmwareUpdate({
|
|
3023
3141
|
targets: [{ target_id: 4, path: 'vol1:application_p1.bin' }],
|
|
3024
3142
|
})
|
|
3025
|
-
).
|
|
3143
|
+
).rejects.toThrow('LIBUSB_TRANSFER_TIMED_OUT');
|
|
3026
3144
|
|
|
3027
|
-
expect(method.postTipMessage).
|
|
3145
|
+
expect(method.postTipMessage).not.toHaveBeenCalled();
|
|
3146
|
+
expect(method.postProgressMessage).not.toHaveBeenCalled();
|
|
3028
3147
|
});
|
|
3029
3148
|
|
|
3030
|
-
test('
|
|
3149
|
+
test('polls only firmware status while Protocol V2 bootloader is installing', async () => {
|
|
3031
3150
|
const method = new FirmwareUpdateV4({
|
|
3032
3151
|
id: 1,
|
|
3033
3152
|
payload: {
|
|
3034
3153
|
method: 'firmwareUpdateV4',
|
|
3035
3154
|
},
|
|
3036
3155
|
});
|
|
3037
|
-
const typedCall = jest
|
|
3038
|
-
|
|
3039
|
-
|
|
3040
|
-
|
|
3041
|
-
|
|
3042
|
-
|
|
3043
|
-
|
|
3044
|
-
|
|
3045
|
-
|
|
3046
|
-
|
|
3047
|
-
|
|
3048
|
-
|
|
3049
|
-
|
|
3050
|
-
|
|
3051
|
-
}
|
|
3052
|
-
if (name === 'DeviceInfoGet') {
|
|
3053
|
-
return Promise.resolve({
|
|
3054
|
-
type: 'DeviceInfo',
|
|
3055
|
-
message: {
|
|
3056
|
-
hw: { serial_no: 'PR9999999999' },
|
|
3057
|
-
fw: { application: { version: '9.9.9' } },
|
|
3058
|
-
},
|
|
3059
|
-
});
|
|
3060
|
-
}
|
|
3061
|
-
return Promise.reject(new Error(`unexpected call ${name}`));
|
|
3062
|
-
});
|
|
3063
|
-
const reconnectProtocolV2Device = jest.fn().mockResolvedValue(undefined);
|
|
3064
|
-
const updateProtocolV2Features = jest.fn(() => ({
|
|
3065
|
-
deviceId: 'PRO2-DEVICE-ID',
|
|
3066
|
-
bootloaderMode: false,
|
|
3067
|
-
mode: 'normal',
|
|
3068
|
-
}));
|
|
3156
|
+
const typedCall = jest
|
|
3157
|
+
.fn()
|
|
3158
|
+
.mockResolvedValueOnce({
|
|
3159
|
+
type: 'DeviceFirmwareUpdateStatus',
|
|
3160
|
+
message: { records: [{ target_id: 4, status: 1 }] },
|
|
3161
|
+
})
|
|
3162
|
+
.mockResolvedValueOnce({
|
|
3163
|
+
type: 'DeviceFirmwareUpdateStatus',
|
|
3164
|
+
message: { records: [{ target_id: 4, status: 2 }] },
|
|
3165
|
+
});
|
|
3166
|
+
const reconnectProtocolV2Device = jest
|
|
3167
|
+
.fn()
|
|
3168
|
+
.mockRejectedValueOnce(new Error('Device rebooting'))
|
|
3169
|
+
.mockResolvedValue(undefined);
|
|
3069
3170
|
|
|
3070
3171
|
(method as any).device = stubDevice({
|
|
3071
3172
|
getCommands: () => ({ typedCall }),
|
|
3072
|
-
updateProtocolV2Features,
|
|
3073
3173
|
});
|
|
3074
3174
|
(method as any).reconnectProtocolV2Device = reconnectProtocolV2Device;
|
|
3075
3175
|
method.postProgressMessage = jest.fn();
|
|
3076
3176
|
|
|
3077
3177
|
await (method as any).waitForProtocolV2FirmwareUpdateComplete([
|
|
3078
|
-
{ target_id:
|
|
3178
|
+
{ target_id: 4, path: 'vol0:/application_p1.bin' },
|
|
3079
3179
|
]);
|
|
3080
3180
|
|
|
3081
|
-
expect(reconnectProtocolV2Device).toHaveBeenCalledTimes(
|
|
3181
|
+
expect(reconnectProtocolV2Device).toHaveBeenCalledTimes(3);
|
|
3082
3182
|
expect(typedCall.mock.calls.map(call => call[0])).toEqual([
|
|
3083
|
-
'
|
|
3084
|
-
'
|
|
3085
|
-
'DeviceInfoGet',
|
|
3086
|
-
'DeviceStatusGet',
|
|
3183
|
+
'DeviceFirmwareUpdateStatusGet',
|
|
3184
|
+
'DeviceFirmwareUpdateStatusGet',
|
|
3087
3185
|
]);
|
|
3186
|
+
expect(typedCall.mock.calls.map(call => call[0])).not.toContain('DeviceStatusGet');
|
|
3187
|
+
expect(method.postProgressMessage).toHaveBeenCalledWith(1, 'installingFirmware');
|
|
3188
|
+
expect(method.postProgressMessage).toHaveBeenCalledWith(100, 'installingFirmware');
|
|
3088
3189
|
});
|
|
3089
3190
|
|
|
3090
|
-
test('
|
|
3191
|
+
test('accepts a missing firmware status handler as an app reboot signal', async () => {
|
|
3091
3192
|
const method = new FirmwareUpdateV4({
|
|
3092
3193
|
id: 1,
|
|
3093
3194
|
payload: {
|
|
3094
3195
|
method: 'firmwareUpdateV4',
|
|
3095
3196
|
},
|
|
3096
3197
|
});
|
|
3097
|
-
const typedCall = jest
|
|
3098
|
-
|
|
3099
|
-
|
|
3100
|
-
if (callCount === 1) {
|
|
3101
|
-
return Promise.reject(new Error('Device not acquired: PR9999999999'));
|
|
3102
|
-
}
|
|
3103
|
-
return Promise.resolve({
|
|
3104
|
-
type: 'DeviceStatus',
|
|
3105
|
-
message: { device_id: 'PRO2-DEVICE-ID', init_states: true },
|
|
3106
|
-
});
|
|
3107
|
-
}
|
|
3108
|
-
if (name === 'DeviceInfoGet') {
|
|
3109
|
-
return Promise.resolve({
|
|
3110
|
-
type: 'DeviceInfo',
|
|
3111
|
-
message: {
|
|
3112
|
-
hw: { serial_no: 'PR9999999999' },
|
|
3113
|
-
fw: { application: { version: '9.9.9' } },
|
|
3114
|
-
},
|
|
3115
|
-
});
|
|
3116
|
-
}
|
|
3117
|
-
return Promise.reject(new Error(`unexpected call ${name}`));
|
|
3118
|
-
});
|
|
3198
|
+
const typedCall = jest
|
|
3199
|
+
.fn()
|
|
3200
|
+
.mockRejectedValue(new Error('Failure: Handler not registered for this message'));
|
|
3119
3201
|
const reconnectProtocolV2Device = jest.fn().mockResolvedValue(undefined);
|
|
3120
|
-
const updateProtocolV2Features = jest.fn(() => ({
|
|
3121
|
-
deviceId: 'PRO2-DEVICE-ID',
|
|
3122
|
-
bootloaderMode: false,
|
|
3123
|
-
mode: 'normal',
|
|
3124
|
-
}));
|
|
3125
3202
|
|
|
3126
3203
|
(method as any).device = stubDevice({
|
|
3127
3204
|
getCommands: () => ({ typedCall }),
|
|
3128
|
-
updateProtocolV2Features,
|
|
3129
3205
|
});
|
|
3130
3206
|
(method as any).reconnectProtocolV2Device = reconnectProtocolV2Device;
|
|
3131
3207
|
method.postProgressMessage = jest.fn();
|
|
3132
3208
|
|
|
3133
3209
|
await (method as any).waitForProtocolV2FirmwareUpdateComplete([
|
|
3134
|
-
{ target_id:
|
|
3210
|
+
{ target_id: 4, path: 'vol0:/application_p1.bin' },
|
|
3135
3211
|
]);
|
|
3136
3212
|
|
|
3137
|
-
expect(reconnectProtocolV2Device).toHaveBeenCalledTimes(
|
|
3138
|
-
expect(typedCall.mock.calls.map(call => call[0])).toEqual([
|
|
3139
|
-
'DeviceInfoGet',
|
|
3140
|
-
'DeviceStatusGet',
|
|
3141
|
-
'DeviceInfoGet',
|
|
3142
|
-
'DeviceStatusGet',
|
|
3143
|
-
]);
|
|
3213
|
+
expect(reconnectProtocolV2Device).toHaveBeenCalledTimes(1);
|
|
3214
|
+
expect(typedCall.mock.calls.map(call => call[0])).toEqual(['DeviceFirmwareUpdateStatusGet']);
|
|
3144
3215
|
});
|
|
3145
3216
|
|
|
3146
|
-
test('
|
|
3217
|
+
test('polls target status after update ACK and finishes when all targets complete', async () => {
|
|
3147
3218
|
const method = new FirmwareUpdateV4({
|
|
3148
3219
|
id: 1,
|
|
3149
3220
|
payload: {
|
|
3150
3221
|
method: 'firmwareUpdateV4',
|
|
3151
3222
|
},
|
|
3152
3223
|
});
|
|
3153
|
-
const typedCall = jest.fn().
|
|
3154
|
-
|
|
3155
|
-
|
|
3156
|
-
|
|
3157
|
-
|
|
3158
|
-
|
|
3159
|
-
|
|
3160
|
-
|
|
3161
|
-
|
|
3162
|
-
}
|
|
3163
|
-
if (name === 'DeviceInfoGet') {
|
|
3164
|
-
return Promise.resolve({
|
|
3165
|
-
type: 'DeviceInfo',
|
|
3166
|
-
message: {
|
|
3167
|
-
hw: { serial_no: 'PR9999999999' },
|
|
3168
|
-
fw: {
|
|
3169
|
-
bootloader: { version: '9.9.9' },
|
|
3170
|
-
application: { version: '9.9.9' },
|
|
3171
|
-
},
|
|
3172
|
-
},
|
|
3173
|
-
});
|
|
3174
|
-
}
|
|
3175
|
-
return Promise.reject(new Error(`unexpected call ${name}`));
|
|
3176
|
-
});
|
|
3224
|
+
const typedCall = jest.fn().mockResolvedValue({
|
|
3225
|
+
type: 'DeviceFirmwareUpdateStatus',
|
|
3226
|
+
message: {
|
|
3227
|
+
records: [
|
|
3228
|
+
{ target_id: 3, status: 2 },
|
|
3229
|
+
{ target_id: 4, status: 2 },
|
|
3230
|
+
],
|
|
3231
|
+
},
|
|
3232
|
+
});
|
|
3177
3233
|
const reconnectProtocolV2Device = jest.fn().mockResolvedValue(undefined);
|
|
3178
|
-
const updateProtocolV2Features = jest.fn(() => ({
|
|
3179
|
-
deviceId: 'PRO2-DEVICE-ID',
|
|
3180
|
-
bootloaderMode: false,
|
|
3181
|
-
mode: 'normal',
|
|
3182
|
-
}));
|
|
3183
3234
|
|
|
3184
3235
|
(method as any).device = stubDevice({
|
|
3185
3236
|
getCommands: () => ({ typedCall }),
|
|
3186
|
-
updateProtocolV2Features,
|
|
3187
3237
|
});
|
|
3188
3238
|
(method as any).reconnectProtocolV2Device = reconnectProtocolV2Device;
|
|
3189
3239
|
method.postProgressMessage = jest.fn();
|
|
3190
3240
|
|
|
3191
3241
|
await expect(
|
|
3192
|
-
(method as any).waitForProtocolV2FirmwareUpdateComplete(
|
|
3193
|
-
|
|
3194
|
-
|
|
3195
|
-
|
|
3196
|
-
],
|
|
3197
|
-
{
|
|
3198
|
-
type: 'DeviceFirmwareUpdateStatus',
|
|
3199
|
-
message: {
|
|
3200
|
-
records: [
|
|
3201
|
-
{ target_id: 3, status: 2 },
|
|
3202
|
-
{ target_id: 4, status: 2 },
|
|
3203
|
-
],
|
|
3204
|
-
},
|
|
3205
|
-
}
|
|
3206
|
-
)
|
|
3242
|
+
(method as any).waitForProtocolV2FirmwareUpdateComplete([
|
|
3243
|
+
{ target_id: 3, path: 'vol1:bootloader.bin' },
|
|
3244
|
+
{ target_id: 4, path: 'vol1:application_p1.bin' },
|
|
3245
|
+
])
|
|
3207
3246
|
).resolves.toBeUndefined();
|
|
3208
3247
|
|
|
3209
3248
|
expect(reconnectProtocolV2Device).toHaveBeenCalledTimes(1);
|
|
3210
|
-
expect(
|
|
3211
|
-
expect(updateProtocolV2Features).toHaveBeenCalledWith(
|
|
3212
|
-
expect.objectContaining({ hw: { serial_no: 'PR9999999999' } }),
|
|
3213
|
-
expect.objectContaining({ device_id: 'PRO2-DEVICE-ID' })
|
|
3214
|
-
);
|
|
3215
|
-
expect(typedCall.mock.calls.map(call => call[0])).toEqual(['DeviceInfoGet', 'DeviceStatusGet']);
|
|
3216
|
-
expect(typedCall).not.toHaveBeenCalledWith(
|
|
3249
|
+
expect(typedCall).toHaveBeenCalledWith(
|
|
3217
3250
|
'DeviceFirmwareUpdateStatusGet',
|
|
3251
|
+
'DeviceFirmwareUpdateStatus',
|
|
3218
3252
|
expect.anything(),
|
|
3219
3253
|
expect.anything()
|
|
3220
3254
|
);
|
|
3255
|
+
expect(method.postProgressMessage).toHaveBeenCalledWith(100, 'installingFirmware');
|
|
3221
3256
|
});
|
|
3222
3257
|
|
|
3223
3258
|
test('uses SDK decoded enum names for Protocol V2 install polling', () => {
|
|
@@ -3262,7 +3297,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3262
3297
|
expectedTargetIds
|
|
3263
3298
|
)
|
|
3264
3299
|
).toBe(false);
|
|
3265
|
-
expect(method.postProgressMessage).toHaveBeenCalledWith(
|
|
3300
|
+
expect(method.postProgressMessage).toHaveBeenCalledWith(1, 'installingFirmware');
|
|
3266
3301
|
|
|
3267
3302
|
try {
|
|
3268
3303
|
(method as any).assertProtocolV2TargetStatus(
|
|
@@ -3276,15 +3311,72 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3276
3311
|
|
|
3277
3312
|
try {
|
|
3278
3313
|
(method as any).assertProtocolV2TargetStatus(
|
|
3279
|
-
[
|
|
3314
|
+
[
|
|
3315
|
+
{
|
|
3316
|
+
target_id: 4,
|
|
3317
|
+
status: 'FW_MGMT_UPDATER_TASK_STATUS_FAILED_VERIFY',
|
|
3318
|
+
payload_version: 0x010000,
|
|
3319
|
+
path: 'vol0:/application_p1.bin',
|
|
3320
|
+
},
|
|
3321
|
+
],
|
|
3280
3322
|
expectedTargetIds
|
|
3281
3323
|
);
|
|
3282
3324
|
throw new Error('Expected Protocol V2 failed firmware status to throw');
|
|
3283
3325
|
} catch (error: any) {
|
|
3284
3326
|
expect(error.errorCode).toBe(HardwareErrorCode.FirmwareError);
|
|
3327
|
+
expect(error.message).toContain('FW_MGMT_UPDATER_TASK_STATUS_FAILED_VERIFY');
|
|
3328
|
+
expect(error.message).toContain('vol0:/application_p1.bin');
|
|
3285
3329
|
}
|
|
3286
3330
|
});
|
|
3287
3331
|
|
|
3332
|
+
test('reports failed Protocol V2 install status before the normal-mode timeout', async () => {
|
|
3333
|
+
const method = new FirmwareUpdateV4({
|
|
3334
|
+
id: 1,
|
|
3335
|
+
payload: {
|
|
3336
|
+
method: 'firmwareUpdateV4',
|
|
3337
|
+
},
|
|
3338
|
+
});
|
|
3339
|
+
const typedCall = jest.fn().mockResolvedValue({
|
|
3340
|
+
type: 'DeviceFirmwareUpdateStatus',
|
|
3341
|
+
message: {
|
|
3342
|
+
records: [
|
|
3343
|
+
{
|
|
3344
|
+
target_id: 4,
|
|
3345
|
+
status: 'FW_MGMT_UPDATER_TASK_STATUS_FAILED_VERIFY',
|
|
3346
|
+
payload_version: 0x010000,
|
|
3347
|
+
path: 'vol0:/application_p1.bin',
|
|
3348
|
+
},
|
|
3349
|
+
],
|
|
3350
|
+
},
|
|
3351
|
+
});
|
|
3352
|
+
(method as any).device = stubDevice({
|
|
3353
|
+
getCommands: () => ({ typedCall }),
|
|
3354
|
+
});
|
|
3355
|
+
(method as any).reconnectProtocolV2Device = jest.fn().mockResolvedValue(undefined);
|
|
3356
|
+
(method as any).probeProtocolV2NormalMode = jest.fn().mockResolvedValue(false);
|
|
3357
|
+
|
|
3358
|
+
await expect(
|
|
3359
|
+
(method as any).waitForProtocolV2FirmwareUpdateComplete([
|
|
3360
|
+
{ target_id: 4, path: 'vol0:/application_p1.bin' },
|
|
3361
|
+
])
|
|
3362
|
+
).rejects.toMatchObject({
|
|
3363
|
+
errorCode: HardwareErrorCode.FirmwareError,
|
|
3364
|
+
message: expect.stringContaining('vol0:/application_p1.bin'),
|
|
3365
|
+
});
|
|
3366
|
+
expect(typedCall).toHaveBeenCalledWith(
|
|
3367
|
+
'DeviceFirmwareUpdateStatusGet',
|
|
3368
|
+
'DeviceFirmwareUpdateStatus',
|
|
3369
|
+
{
|
|
3370
|
+
fields: {
|
|
3371
|
+
status: true,
|
|
3372
|
+
payload_version: true,
|
|
3373
|
+
path: true,
|
|
3374
|
+
},
|
|
3375
|
+
},
|
|
3376
|
+
{ timeoutMs: 5000 }
|
|
3377
|
+
);
|
|
3378
|
+
});
|
|
3379
|
+
|
|
3288
3380
|
test('passes bootloader, coprocessor, SE and app files to DeviceFirmwareUpdate targets', async () => {
|
|
3289
3381
|
const method = new FirmwareUpdateV4({
|
|
3290
3382
|
id: 1,
|
|
@@ -3679,6 +3771,137 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3679
3771
|
getFirmwareLatestReleaseSpy.mockRestore();
|
|
3680
3772
|
});
|
|
3681
3773
|
|
|
3774
|
+
test('downloads and validates remote RESC bundles before entering bootloader', async () => {
|
|
3775
|
+
const method = new FirmwareUpdateV4({
|
|
3776
|
+
id: 1,
|
|
3777
|
+
payload: {
|
|
3778
|
+
method: 'firmwareUpdateV4',
|
|
3779
|
+
platform: 'web',
|
|
3780
|
+
targetsToUpdate: ['resource'],
|
|
3781
|
+
},
|
|
3782
|
+
});
|
|
3783
|
+
method.init();
|
|
3784
|
+
|
|
3785
|
+
const order: string[] = [];
|
|
3786
|
+
const resourceBinary = new Uint8Array([1, 2, 3]).buffer;
|
|
3787
|
+
const getSysResourceBinarySpy = jest
|
|
3788
|
+
.spyOn(firmwareBinaryApi, 'getSysResourceBinary')
|
|
3789
|
+
.mockImplementation(() => {
|
|
3790
|
+
order.push('download');
|
|
3791
|
+
return Promise.resolve({ binary: resourceBinary });
|
|
3792
|
+
});
|
|
3793
|
+
const getFirmwareLatestReleaseSpy = jest
|
|
3794
|
+
.spyOn(DataManager, 'getFirmwareLatestRelease')
|
|
3795
|
+
.mockReturnValue({
|
|
3796
|
+
required: false,
|
|
3797
|
+
version: [1, 0, 0],
|
|
3798
|
+
url: '',
|
|
3799
|
+
resourceBundles: [
|
|
3800
|
+
{
|
|
3801
|
+
name: 'images',
|
|
3802
|
+
url: 'https://example.com/images.okpkg',
|
|
3803
|
+
devicePath: 'vol0:/resource/images/images.okpkg',
|
|
3804
|
+
},
|
|
3805
|
+
],
|
|
3806
|
+
fingerprint: '',
|
|
3807
|
+
changelog: {
|
|
3808
|
+
'zh-CN': '',
|
|
3809
|
+
'en-US': '',
|
|
3810
|
+
},
|
|
3811
|
+
});
|
|
3812
|
+
|
|
3813
|
+
(method as any).device = stubDevice({
|
|
3814
|
+
originalDescriptor: { protocolType: 'V2' },
|
|
3815
|
+
features: { deviceType: 'pro2', firmwareVersion: '1.0.0', capabilities: [] },
|
|
3816
|
+
});
|
|
3817
|
+
(method as any).prepareRemoteProtocolV2Binaries = jest.fn().mockResolvedValue({
|
|
3818
|
+
bootloaderBinary: null,
|
|
3819
|
+
fwBinaryMap: [],
|
|
3820
|
+
});
|
|
3821
|
+
(method as any).enterProtocolV2BootloaderMode = jest.fn().mockImplementation(() => {
|
|
3822
|
+
order.push('bootloader');
|
|
3823
|
+
return Promise.resolve(true);
|
|
3824
|
+
});
|
|
3825
|
+
(method as any).executeProtocolV2Update = jest.fn().mockResolvedValue(undefined);
|
|
3826
|
+
(method as any).exitProtocolV2BootloaderToNormal = jest.fn().mockResolvedValue(undefined);
|
|
3827
|
+
(method as any).waitForProtocolV2FinalFeatures = jest.fn().mockResolvedValue({
|
|
3828
|
+
bootloaderVersion: '1.0.0',
|
|
3829
|
+
bleVersion: '0.0.0',
|
|
3830
|
+
firmwareVersion: '1.0.0',
|
|
3831
|
+
});
|
|
3832
|
+
method.postTipMessage = jest.fn();
|
|
3833
|
+
|
|
3834
|
+
await method.run();
|
|
3835
|
+
|
|
3836
|
+
expect(order).toEqual(['download', 'bootloader']);
|
|
3837
|
+
expect((method as any).executeProtocolV2Update).toHaveBeenCalledWith({
|
|
3838
|
+
bootloaderBinary: null,
|
|
3839
|
+
fwBinaryMap: [],
|
|
3840
|
+
resourceBundles: [
|
|
3841
|
+
{
|
|
3842
|
+
name: 'images',
|
|
3843
|
+
binary: resourceBinary,
|
|
3844
|
+
devicePath: 'vol0:/resource/images/images.okpkg',
|
|
3845
|
+
url: 'https://example.com/images.okpkg',
|
|
3846
|
+
},
|
|
3847
|
+
],
|
|
3848
|
+
});
|
|
3849
|
+
|
|
3850
|
+
getSysResourceBinarySpy.mockRestore();
|
|
3851
|
+
getFirmwareLatestReleaseSpy.mockRestore();
|
|
3852
|
+
});
|
|
3853
|
+
|
|
3854
|
+
test('rejects an empty remote RESC bundle before entering bootloader', async () => {
|
|
3855
|
+
const method = new FirmwareUpdateV4({
|
|
3856
|
+
id: 1,
|
|
3857
|
+
payload: {
|
|
3858
|
+
method: 'firmwareUpdateV4',
|
|
3859
|
+
platform: 'web',
|
|
3860
|
+
targetsToUpdate: ['resource'],
|
|
3861
|
+
},
|
|
3862
|
+
});
|
|
3863
|
+
method.init();
|
|
3864
|
+
|
|
3865
|
+
const getSysResourceBinarySpy = jest
|
|
3866
|
+
.spyOn(firmwareBinaryApi, 'getSysResourceBinary')
|
|
3867
|
+
.mockResolvedValue({ binary: new ArrayBuffer(0) });
|
|
3868
|
+
const getFirmwareLatestReleaseSpy = jest
|
|
3869
|
+
.spyOn(DataManager, 'getFirmwareLatestRelease')
|
|
3870
|
+
.mockReturnValue({
|
|
3871
|
+
required: false,
|
|
3872
|
+
version: [1, 0, 0],
|
|
3873
|
+
url: '',
|
|
3874
|
+
resourceBundles: [
|
|
3875
|
+
{
|
|
3876
|
+
name: 'images',
|
|
3877
|
+
url: 'https://example.com/images.okpkg',
|
|
3878
|
+
devicePath: 'vol0:/resource/images/images.okpkg',
|
|
3879
|
+
},
|
|
3880
|
+
],
|
|
3881
|
+
fingerprint: '',
|
|
3882
|
+
changelog: {
|
|
3883
|
+
'zh-CN': '',
|
|
3884
|
+
'en-US': '',
|
|
3885
|
+
},
|
|
3886
|
+
});
|
|
3887
|
+
(method as any).device = stubDevice({
|
|
3888
|
+
originalDescriptor: { protocolType: 'V2' },
|
|
3889
|
+
features: { deviceType: 'pro2', firmwareVersion: '1.0.0', capabilities: [] },
|
|
3890
|
+
});
|
|
3891
|
+
(method as any).prepareRemoteProtocolV2Binaries = jest.fn().mockResolvedValue({
|
|
3892
|
+
bootloaderBinary: null,
|
|
3893
|
+
fwBinaryMap: [],
|
|
3894
|
+
});
|
|
3895
|
+
(method as any).enterProtocolV2BootloaderMode = jest.fn();
|
|
3896
|
+
method.postTipMessage = jest.fn();
|
|
3897
|
+
|
|
3898
|
+
await expect(method.run()).rejects.toThrow('Protocol V2 RESC bundle is empty: images');
|
|
3899
|
+
expect((method as any).enterProtocolV2BootloaderMode).not.toHaveBeenCalled();
|
|
3900
|
+
|
|
3901
|
+
getSysResourceBinarySpy.mockRestore();
|
|
3902
|
+
getFirmwareLatestReleaseSpy.mockRestore();
|
|
3903
|
+
});
|
|
3904
|
+
|
|
3682
3905
|
test('does not download Pro2 firmware components that App did not select', async () => {
|
|
3683
3906
|
const method = new FirmwareUpdateV4({
|
|
3684
3907
|
id: 1,
|
|
@@ -4073,14 +4296,20 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4073
4296
|
expect(writePayloads.map(payload => payload.ui_percentage)).toEqual([0, 100]);
|
|
4074
4297
|
});
|
|
4075
4298
|
|
|
4076
|
-
test('
|
|
4299
|
+
test('ends device confirmation and starts install progress only after Protocol V2 ACK', async () => {
|
|
4077
4300
|
const method = new FirmwareUpdateV4({
|
|
4078
4301
|
id: 1,
|
|
4079
4302
|
payload: {
|
|
4080
4303
|
method: 'firmwareUpdateV4',
|
|
4081
4304
|
},
|
|
4082
4305
|
});
|
|
4083
|
-
|
|
4306
|
+
let resolveRequest: ((value: unknown) => void) | undefined;
|
|
4307
|
+
const typedCall = jest.fn().mockImplementation(
|
|
4308
|
+
() =>
|
|
4309
|
+
new Promise(resolve => {
|
|
4310
|
+
resolveRequest = resolve;
|
|
4311
|
+
})
|
|
4312
|
+
);
|
|
4084
4313
|
|
|
4085
4314
|
(method as any).device = stubDevice({
|
|
4086
4315
|
getCommands: () => ({ typedCall }),
|
|
@@ -4088,22 +4317,24 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4088
4317
|
method.postProgressMessage = jest.fn();
|
|
4089
4318
|
method.postTipMessage = jest.fn();
|
|
4090
4319
|
|
|
4091
|
-
|
|
4320
|
+
const startPromise = (method as any).protocolV2StartFirmwareUpdate({
|
|
4092
4321
|
targets: [{ target_id: 4, path: 'vol1:firmware.bin' }],
|
|
4093
4322
|
});
|
|
4323
|
+
await Promise.resolve();
|
|
4094
4324
|
|
|
4095
|
-
|
|
4096
|
-
expect(typedCall.mock.calls[0][
|
|
4097
|
-
expect(
|
|
4098
|
-
|
|
4099
|
-
type: 'DeviceFirmwareUpdateStatus',
|
|
4100
|
-
message: { records: [{ target_id: 4, status: 1 }] },
|
|
4101
|
-
});
|
|
4325
|
+
expect(typedCall.mock.calls[0][1]).toBe('Success');
|
|
4326
|
+
expect(typedCall.mock.calls[0][3]).toEqual(expect.objectContaining({ timeoutMs: 180_000 }));
|
|
4327
|
+
expect(method.postTipMessage).not.toHaveBeenCalled();
|
|
4328
|
+
expect(method.postProgressMessage).not.toHaveBeenCalled();
|
|
4102
4329
|
|
|
4103
|
-
|
|
4330
|
+
resolveRequest?.({ type: 'Success', message: { message: 'ok' } });
|
|
4331
|
+
await startPromise;
|
|
4332
|
+
expect(method.postTipMessage).toHaveBeenCalledWith('FirmwareUpdating');
|
|
4333
|
+
expect(method.postProgressMessage).toHaveBeenCalledWith(0, 'installingFirmware');
|
|
4334
|
+
expect(method.postTipMessage).toHaveBeenCalledTimes(1);
|
|
4104
4335
|
});
|
|
4105
4336
|
|
|
4106
|
-
test('
|
|
4337
|
+
test('requests only a Success ACK for Protocol V2 firmware install', async () => {
|
|
4107
4338
|
const method = new FirmwareUpdateV4({
|
|
4108
4339
|
id: 1,
|
|
4109
4340
|
payload: {
|
|
@@ -4111,8 +4342,8 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4111
4342
|
},
|
|
4112
4343
|
});
|
|
4113
4344
|
const typedCall = jest.fn().mockResolvedValue({
|
|
4114
|
-
type: '
|
|
4115
|
-
message: {
|
|
4345
|
+
type: 'Success',
|
|
4346
|
+
message: { message: 'accepted' },
|
|
4116
4347
|
});
|
|
4117
4348
|
|
|
4118
4349
|
(method as any).device = stubDevice({
|
|
@@ -4125,7 +4356,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4125
4356
|
targets: [{ target_id: 4, path: 'vol1:firmware.bin' }],
|
|
4126
4357
|
});
|
|
4127
4358
|
|
|
4128
|
-
expect(typedCall.mock.calls[0][1]).
|
|
4359
|
+
expect(typedCall.mock.calls[0][1]).toBe('Success');
|
|
4129
4360
|
expect(method.postTipMessage).toHaveBeenCalledWith('FirmwareUpdating');
|
|
4130
4361
|
});
|
|
4131
4362
|
});
|
|
@@ -4472,6 +4703,24 @@ describe('Protocol V2 protected method execution', () => {
|
|
|
4472
4703
|
expect(method.unlockPolicy).toBe('none');
|
|
4473
4704
|
});
|
|
4474
4705
|
|
|
4706
|
+
test('uses the same label interaction for the compatible deviceSettings entry point', () => {
|
|
4707
|
+
const method = new DeviceSettings({
|
|
4708
|
+
id: 2,
|
|
4709
|
+
payload: { method: 'deviceSettings', label: 'My Pro 2' },
|
|
4710
|
+
});
|
|
4711
|
+
|
|
4712
|
+
method.init();
|
|
4713
|
+
|
|
4714
|
+
expect(method.protocolV2UiInteraction).toEqual({
|
|
4715
|
+
request: 'button',
|
|
4716
|
+
source: 'method-lifecycle',
|
|
4717
|
+
reason: 'device-management',
|
|
4718
|
+
completion: 'operation-completed',
|
|
4719
|
+
deviceOnly: true,
|
|
4720
|
+
operation: 'change-label',
|
|
4721
|
+
});
|
|
4722
|
+
});
|
|
4723
|
+
|
|
4475
4724
|
test.each([
|
|
4476
4725
|
['deviceInfoGet', DeviceInfoGet],
|
|
4477
4726
|
['deviceStatusGet', DeviceStatusGet],
|
|
@@ -4722,6 +4971,7 @@ describe('Protocol V2 current low-level methods', () => {
|
|
|
4722
4971
|
});
|
|
4723
4972
|
|
|
4724
4973
|
await expect(v2Method.run()).resolves.toEqual({ message: 'accepted' });
|
|
4974
|
+
expect(v2Method.unlockPolicy).toBe('retry-on-locked');
|
|
4725
4975
|
expect(v2TypedCall).toHaveBeenCalledWith('DeviceSettingsPageShow', 'Success', {
|
|
4726
4976
|
page: DeviceSettingsPage.DeviceReset,
|
|
4727
4977
|
});
|
|
@@ -4764,6 +5014,7 @@ describe('Protocol V2 current low-level methods', () => {
|
|
|
4764
5014
|
});
|
|
4765
5015
|
|
|
4766
5016
|
await expect(v2Method.run()).resolves.toEqual({ message: 'accepted' });
|
|
5017
|
+
expect(v2Method.unlockPolicy).toBe('retry-on-locked');
|
|
4767
5018
|
expect(v2TypedCall).toHaveBeenCalledWith('DeviceSettingsPageShow', 'Success', {
|
|
4768
5019
|
page: DeviceSettingsPage.DevicePinChange,
|
|
4769
5020
|
});
|
|
@@ -4927,6 +5178,9 @@ describe('Protocol V2 current low-level methods', () => {
|
|
|
4927
5178
|
|
|
4928
5179
|
test('opens non-passphrase Protocol V2 settings pages', async () => {
|
|
4929
5180
|
const typedCall = jest.fn().mockResolvedValue({ message: { message: 'ok' } });
|
|
5181
|
+
const getDeviceState = jest.fn().mockResolvedValue({
|
|
5182
|
+
settings: { airgapMode: true },
|
|
5183
|
+
});
|
|
4930
5184
|
const method = new DeviceSettingsPageShow({
|
|
4931
5185
|
id: 1,
|
|
4932
5186
|
payload: {
|
|
@@ -4936,7 +5190,7 @@ describe('Protocol V2 current low-level methods', () => {
|
|
|
4936
5190
|
},
|
|
4937
5191
|
});
|
|
4938
5192
|
method.init();
|
|
4939
|
-
(method as any).device = stubDevice({ commands: { typedCall } });
|
|
5193
|
+
(method as any).device = stubDevice({ commands: { typedCall }, getDeviceState });
|
|
4940
5194
|
|
|
4941
5195
|
await method.run();
|
|
4942
5196
|
|
|
@@ -4952,10 +5206,14 @@ describe('Protocol V2 current low-level methods', () => {
|
|
|
4952
5206
|
deviceOnly: true,
|
|
4953
5207
|
page: DeviceSettingsPage.DeviceAirgap,
|
|
4954
5208
|
});
|
|
5209
|
+
expect(getDeviceState).toHaveBeenCalledWith({ refreshSections: ['settings'] });
|
|
4955
5210
|
});
|
|
4956
5211
|
|
|
4957
5212
|
test('opens the Protocol V2 passphrase settings page', async () => {
|
|
4958
5213
|
const typedCall = jest.fn().mockResolvedValue({ message: { message: 'ok' } });
|
|
5214
|
+
const getDeviceState = jest.fn().mockResolvedValue({
|
|
5215
|
+
status: { passphraseProtection: true },
|
|
5216
|
+
});
|
|
4959
5217
|
const method = new DeviceSettingsPageShow({
|
|
4960
5218
|
id: 1,
|
|
4961
5219
|
payload: {
|
|
@@ -4964,7 +5222,7 @@ describe('Protocol V2 current low-level methods', () => {
|
|
|
4964
5222
|
},
|
|
4965
5223
|
});
|
|
4966
5224
|
method.init();
|
|
4967
|
-
(method as any).device = stubDevice({ commands: { typedCall } });
|
|
5225
|
+
(method as any).device = stubDevice({ commands: { typedCall }, getDeviceState });
|
|
4968
5226
|
|
|
4969
5227
|
await method.run();
|
|
4970
5228
|
|
|
@@ -4972,6 +5230,7 @@ describe('Protocol V2 current low-level methods', () => {
|
|
|
4972
5230
|
page: 2,
|
|
4973
5231
|
field_name: undefined,
|
|
4974
5232
|
});
|
|
5233
|
+
expect(getDeviceState).toHaveBeenCalledWith({ refreshSections: ['status'] });
|
|
4975
5234
|
});
|
|
4976
5235
|
|
|
4977
5236
|
test('does not unlock before reading Protocol V2 device settings', () => {
|
|
@@ -4985,22 +5244,67 @@ describe('Protocol V2 current low-level methods', () => {
|
|
|
4985
5244
|
expect(method.unlockPolicy).toBe('none');
|
|
4986
5245
|
});
|
|
4987
5246
|
|
|
4988
|
-
test(
|
|
4989
|
-
|
|
4990
|
-
|
|
4991
|
-
|
|
4992
|
-
|
|
4993
|
-
|
|
4994
|
-
|
|
4995
|
-
|
|
4996
|
-
|
|
4997
|
-
|
|
4998
|
-
|
|
5247
|
+
test.each([
|
|
5248
|
+
['label', { label: 'My Pro 2' }],
|
|
5249
|
+
['language', { language: 'ja-JP' }],
|
|
5250
|
+
['brightness', { brightness: 80 }],
|
|
5251
|
+
['haptic feedback', { haptic_feedback: true }],
|
|
5252
|
+
[
|
|
5253
|
+
'combined lock-free settings',
|
|
5254
|
+
{
|
|
5255
|
+
label: 'My Pro 2',
|
|
5256
|
+
language: 'ja-JP',
|
|
5257
|
+
brightness: 80,
|
|
5258
|
+
haptic_feedback: true,
|
|
5259
|
+
},
|
|
5260
|
+
],
|
|
5261
|
+
])('does not unlock before changing Protocol V2 %s', (_name, settings) => {
|
|
5262
|
+
const method = new DeviceSettingsSet({
|
|
5263
|
+
id: 2,
|
|
5264
|
+
payload: { method: 'deviceSettingsSet', settings },
|
|
5265
|
+
});
|
|
4999
5266
|
|
|
5000
|
-
|
|
5001
|
-
|
|
5002
|
-
|
|
5267
|
+
method.init();
|
|
5268
|
+
|
|
5269
|
+
expect(method.unlockPolicy).toBe('none');
|
|
5270
|
+
});
|
|
5271
|
+
|
|
5272
|
+
test.each([
|
|
5273
|
+
['auto lock', { autolock_delay_ms: 60_000 }],
|
|
5274
|
+
['auto shutdown', { autoshutdown_delay_ms: 120_000 }],
|
|
5275
|
+
['mixed protected settings', { brightness: 80, autolock_delay_ms: 60_000 }],
|
|
5276
|
+
['other protected settings', { usb_lock_enable: true }],
|
|
5277
|
+
])('unlocks before changing Protocol V2 %s', (_name, settings) => {
|
|
5278
|
+
const method = new DeviceSettingsSet({
|
|
5279
|
+
id: 2,
|
|
5280
|
+
payload: { method: 'deviceSettingsSet', settings },
|
|
5281
|
+
});
|
|
5282
|
+
|
|
5283
|
+
method.init();
|
|
5284
|
+
|
|
5285
|
+
expect(method.unlockPolicy).toBe('retry-on-locked');
|
|
5286
|
+
});
|
|
5287
|
+
|
|
5288
|
+
test('unlocks before opening Protocol V2 settings pages', () => {
|
|
5289
|
+
const method = new DeviceSettingsPageShow({
|
|
5290
|
+
id: 3,
|
|
5291
|
+
payload: { method: 'deviceSettingsPageShow', page: 'DevicePassphrase' },
|
|
5292
|
+
});
|
|
5293
|
+
|
|
5294
|
+
method.init();
|
|
5295
|
+
|
|
5296
|
+
expect(method.unlockPolicy).toBe('retry-on-locked');
|
|
5297
|
+
});
|
|
5298
|
+
|
|
5299
|
+
test('keeps firmwareUpdateV4 unlock-before-reboot behavior explicit', () => {
|
|
5300
|
+
const method = new FirmwareUpdateV4({
|
|
5301
|
+
id: 4,
|
|
5302
|
+
payload: { method: 'firmwareUpdateV4' },
|
|
5003
5303
|
});
|
|
5304
|
+
|
|
5305
|
+
method.init();
|
|
5306
|
+
|
|
5307
|
+
expect(method.unlockPolicy).toBe('retry-on-locked');
|
|
5004
5308
|
});
|
|
5005
5309
|
|
|
5006
5310
|
test('sends FilesystemPermissionFix from filesystemPermissionFix', async () => {
|