@onekeyfe/hd-core 1.2.2-alpha.100 → 1.2.2-alpha.11
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 +20 -4
- package/__tests__/DeviceCommands.test.ts +13 -0
- package/__tests__/check-all-firmware-release-protocol-v2.test.ts +0 -2
- package/__tests__/check-firmware-release-protocol-v2.test.ts +0 -2
- package/__tests__/device-lifecycle-events.test.ts +86 -0
- package/__tests__/deviceUploadNft.test.ts +68 -0
- package/__tests__/evmSignTypedData.test.ts +42 -0
- package/__tests__/firmware-update/firmware-update-v4-install-poll.test.ts +825 -12
- package/__tests__/logBlockEvent.test.ts +45 -122
- package/__tests__/open-wallet-session.test.ts +781 -69
- package/__tests__/pro2HostAssetPackage.test.ts +165 -0
- package/__tests__/protocol-v2-resources.test.ts +7 -4
- package/__tests__/protocol-v2-unlock-policy.test.ts +35 -1
- package/__tests__/protocol-v2.test.ts +925 -97
- package/__tests__/protocolV2FileWrite.test.ts +40 -0
- package/dist/api/FirmwareUpdateV4.d.ts +5 -0
- package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
- package/dist/api/OpenWalletSession.d.ts.map +1 -1
- package/dist/api/allnetwork/AllNetworkGetAddressBase.d.ts.map +1 -1
- package/dist/api/evm/EVMSignTypedData.d.ts.map +1 -1
- package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts +2 -2
- package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts.map +1 -1
- package/dist/api/firmware/getBinary.d.ts +2 -2
- package/dist/api/helpers/protocolV2FileWrite.d.ts +1 -0
- package/dist/api/helpers/protocolV2FileWrite.d.ts.map +1 -1
- package/dist/api/protocol-v2/DeviceGetFirmwareUpdateStatus.d.ts.map +1 -1
- package/dist/api/protocol-v2/DeviceUploadNft.d.ts.map +1 -1
- package/dist/api/protocol-v2/DeviceUploadWallpaper.d.ts.map +1 -1
- package/dist/api/protocol-v2/helpers.d.ts.map +1 -1
- package/dist/core/RequestQueue.d.ts +2 -0
- 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 +1 -1
- package/dist/device/Device.d.ts.map +1 -1
- package/dist/device/DeviceConnector.d.ts +1 -1
- package/dist/device/DeviceConnector.d.ts.map +1 -1
- package/dist/events/logBlockEvent.d.ts.map +1 -1
- package/dist/events/ui-request.d.ts +3 -0
- package/dist/events/ui-request.d.ts.map +1 -1
- package/dist/index.d.ts +21 -5
- package/dist/index.js +2511 -1538
- package/dist/protocols/protocol-v2/resources.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/unlockPolicyRunner.d.ts +2 -0
- package/dist/protocols/protocol-v2/unlockPolicyRunner.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/walletSession.d.ts +3 -0
- package/dist/protocols/protocol-v2/walletSession.d.ts.map +1 -1
- package/dist/types/settings.d.ts +2 -2
- package/dist/types/settings.d.ts.map +1 -1
- package/dist/utils/deviceFeaturesUtils.d.ts +3 -0
- package/dist/utils/deviceFeaturesUtils.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/pro2HostAssetPackage.d.ts +8 -0
- package/dist/utils/pro2HostAssetPackage.d.ts.map +1 -0
- package/package.json +4 -4
- package/src/api/FirmwareUpdateV4.ts +474 -83
- package/src/api/OpenWalletSession.ts +39 -12
- package/src/api/allnetwork/AllNetworkGetAddressBase.ts +5 -2
- package/src/api/evm/EVMSignTypedData.ts +1 -0
- package/src/api/firmware/FirmwareUpdateBaseMethod.ts +9 -3
- package/src/api/helpers/protocolV2FileWrite.ts +11 -5
- package/src/api/protocol-v2/DeviceGetFirmwareUpdateStatus.ts +7 -1
- package/src/api/protocol-v2/DeviceUploadNft.ts +31 -8
- package/src/api/protocol-v2/DeviceUploadWallpaper.ts +33 -8
- package/src/api/protocol-v2/helpers.ts +8 -0
- package/src/core/RequestQueue.ts +16 -1
- package/src/core/index.ts +164 -26
- package/src/data/messages/messages-protocol-v2.json +1589 -1375
- package/src/device/Device.ts +3 -1
- package/src/device/DeviceConnector.ts +7 -3
- package/src/events/logBlockEvent.ts +6 -75
- package/src/events/ui-request.ts +3 -0
- package/src/protocols/protocol-v2/resources.ts +9 -1
- package/src/protocols/protocol-v2/unlockPolicyRunner.ts +8 -1
- package/src/protocols/protocol-v2/walletSession.ts +223 -56
- package/src/types/settings.ts +4 -2
- package/src/utils/deviceFeaturesUtils.ts +4 -0
- package/src/utils/pro2HostAssetPackage.ts +400 -0
|
@@ -7,6 +7,7 @@ import { encode as encodeJpeg } from 'jpeg-js';
|
|
|
7
7
|
import {
|
|
8
8
|
DeviceRebootType,
|
|
9
9
|
DeviceSessionPinType,
|
|
10
|
+
DeviceSessionSeedDomain,
|
|
10
11
|
DeviceSettingsPage,
|
|
11
12
|
DeviceType,
|
|
12
13
|
} from '@onekeyfe/hd-transport';
|
|
@@ -254,6 +255,97 @@ describe('DeviceUploadWallpaper', () => {
|
|
|
254
255
|
});
|
|
255
256
|
});
|
|
256
257
|
|
|
258
|
+
test('uploads and applies the fixed wallpaper package on firmware 1.0.1', async () => {
|
|
259
|
+
const getSettingsSpy = jest
|
|
260
|
+
.spyOn(DataManager, 'getSettings')
|
|
261
|
+
.mockReturnValue('react-native' as any);
|
|
262
|
+
const typedCall = jest.fn().mockImplementation((request, _response, params) => {
|
|
263
|
+
if (request === 'FilesystemDirMake') return { message: {} };
|
|
264
|
+
if (request === 'FilesystemFileWrite') {
|
|
265
|
+
const file = params.file as { data: Uint8Array; offset: number };
|
|
266
|
+
return { message: { processed_byte: file.offset + file.data.byteLength } };
|
|
267
|
+
}
|
|
268
|
+
if (request === 'DeviceSettingsSet') {
|
|
269
|
+
return { message: { message: 'wallpaper applied' } };
|
|
270
|
+
}
|
|
271
|
+
throw new Error(`Unexpected request: ${request}`);
|
|
272
|
+
});
|
|
273
|
+
const method = new DeviceUploadWallpaper({
|
|
274
|
+
id: 1,
|
|
275
|
+
payload: {
|
|
276
|
+
method: 'deviceUploadWallpaper',
|
|
277
|
+
jpegBase64: createJpegBase64(604, 1024),
|
|
278
|
+
fileName: 'ignored-on-package-firmware.bin',
|
|
279
|
+
},
|
|
280
|
+
});
|
|
281
|
+
const device = stubWallpaperDevice({
|
|
282
|
+
commands: { typedCall },
|
|
283
|
+
state: { versions: { firmware: '1.0.1' } },
|
|
284
|
+
getCurrentFirmwareVersionString: jest.fn(() => '1.0.1'),
|
|
285
|
+
});
|
|
286
|
+
(method as any).device = device;
|
|
287
|
+
method.postMessage = jest.fn();
|
|
288
|
+
|
|
289
|
+
let result;
|
|
290
|
+
try {
|
|
291
|
+
method.init();
|
|
292
|
+
result = await method.run();
|
|
293
|
+
} finally {
|
|
294
|
+
getSettingsSpy.mockRestore();
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
const fileWrites = typedCall.mock.calls.filter(call => call[0] === 'FilesystemFileWrite');
|
|
298
|
+
expect(new Set(fileWrites.map(call => call[2].file.path))).toEqual(
|
|
299
|
+
new Set(['vol1:/wallpapers/wallpaper.okpkg'])
|
|
300
|
+
);
|
|
301
|
+
expect(fileWrites[0][2].file.data.subarray(0, 4)).toEqual(
|
|
302
|
+
new Uint8Array([0x4f, 0x4b, 0x50, 0x50])
|
|
303
|
+
);
|
|
304
|
+
expect(fileWrites[0][2].file.data).toHaveLength(1960);
|
|
305
|
+
expect(typedCall).toHaveBeenLastCalledWith('DeviceSettingsSet', 'Success', {
|
|
306
|
+
settings: { wallpaper_path: 'vol1:/wallpapers/wallpaper.okpkg' },
|
|
307
|
+
});
|
|
308
|
+
expect(result).toMatchObject({
|
|
309
|
+
path: 'vol1:/wallpapers/wallpaper.okpkg',
|
|
310
|
+
colorFormat: 'RGB565',
|
|
311
|
+
message: 'wallpaper applied',
|
|
312
|
+
});
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
test('keeps the legacy wallpaper path on a 1.0.1 prerelease', async () => {
|
|
316
|
+
const typedCall = jest.fn().mockImplementation((request, _response, params) => {
|
|
317
|
+
if (request === 'FilesystemDirMake') return { message: {} };
|
|
318
|
+
if (request === 'FilesystemFileWrite') {
|
|
319
|
+
const file = params.file as { data: Uint8Array; offset: number };
|
|
320
|
+
return { message: { processed_byte: file.offset + file.data.byteLength } };
|
|
321
|
+
}
|
|
322
|
+
if (request === 'DeviceSettingsSet') return { message: {} };
|
|
323
|
+
throw new Error(`Unexpected request: ${request}`);
|
|
324
|
+
});
|
|
325
|
+
const method = new DeviceUploadWallpaper({
|
|
326
|
+
id: 1,
|
|
327
|
+
payload: {
|
|
328
|
+
method: 'deviceUploadWallpaper',
|
|
329
|
+
jpegBase64: createJpegBase64(604, 1024),
|
|
330
|
+
fileName: 'prerelease-wallpaper.bin',
|
|
331
|
+
},
|
|
332
|
+
});
|
|
333
|
+
const device = stubWallpaperDevice({
|
|
334
|
+
commands: { typedCall },
|
|
335
|
+
state: { versions: { firmware: '1.0.1-beta.1' } },
|
|
336
|
+
});
|
|
337
|
+
(method as any).device = device;
|
|
338
|
+
|
|
339
|
+
method.init();
|
|
340
|
+
const result = await method.run();
|
|
341
|
+
|
|
342
|
+
expect(result.path).toBe('vol1:/wallpapers/prerelease-wallpaper.bin');
|
|
343
|
+
const fileWrites = typedCall.mock.calls.filter(call => call[0] === 'FilesystemFileWrite');
|
|
344
|
+
expect(new Set(fileWrites.map(call => call[2].file.path))).toEqual(
|
|
345
|
+
new Set(['vol1:/wallpapers/prerelease-wallpaper.bin'])
|
|
346
|
+
);
|
|
347
|
+
});
|
|
348
|
+
|
|
257
349
|
test('文件上传失败时不修改 wallpaper_path', async () => {
|
|
258
350
|
const typedCall = jest.fn().mockImplementation(request => {
|
|
259
351
|
if (request === 'FilesystemDirMake') return { message: {} };
|
|
@@ -1022,6 +1114,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1022
1114
|
newSession: 'session-1',
|
|
1023
1115
|
unlockedAttachPin: false,
|
|
1024
1116
|
resumed: false,
|
|
1117
|
+
walletStatusRefreshed: true,
|
|
1025
1118
|
});
|
|
1026
1119
|
expect(typedCall).toHaveBeenCalledWith('ProtocolInfoRequest', 'ProtocolInfo', {
|
|
1027
1120
|
eventless_wallet_session: true,
|
|
@@ -1032,10 +1125,9 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1032
1125
|
expect(typedCall).toHaveBeenCalledWith('DeviceSessionAskPassphrase', 'Success', {
|
|
1033
1126
|
passphrase: '',
|
|
1034
1127
|
on_device: false,
|
|
1128
|
+
seed_domains: [DeviceSessionSeedDomain.SeedDomain_Standard],
|
|
1035
1129
|
});
|
|
1036
|
-
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
1037
|
-
seed_domains: [],
|
|
1038
|
-
});
|
|
1130
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {});
|
|
1039
1131
|
device.passphraseState = 'state-1';
|
|
1040
1132
|
expect(device.getInternalState()).toBe('session-1');
|
|
1041
1133
|
});
|
|
@@ -1088,13 +1180,10 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1088
1180
|
expect(
|
|
1089
1181
|
typedCall.mock.calls.filter(call => call[0] === 'DeviceSessionAskPassphrase')
|
|
1090
1182
|
).toHaveLength(1);
|
|
1091
|
-
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
1092
|
-
seed_domains: [],
|
|
1093
|
-
});
|
|
1183
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {});
|
|
1094
1184
|
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
1095
1185
|
session_id: 'standard-session',
|
|
1096
1186
|
btc_test_address: 'standard-state',
|
|
1097
|
-
seed_domains: [],
|
|
1098
1187
|
});
|
|
1099
1188
|
expect(deviceWalletSessionStore.getStandard(deviceId)).toEqual({
|
|
1100
1189
|
passphraseState: 'standard-state',
|
|
@@ -1285,6 +1374,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1285
1374
|
newSession: 'main-wallet-session',
|
|
1286
1375
|
unlockedAttachPin: undefined,
|
|
1287
1376
|
resumed: false,
|
|
1377
|
+
walletStatusRefreshed: true,
|
|
1288
1378
|
});
|
|
1289
1379
|
|
|
1290
1380
|
expect(promptPassphrase).toHaveBeenCalled();
|
|
@@ -1371,6 +1461,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1371
1461
|
newSession: 'hidden-after-unlock-session',
|
|
1372
1462
|
unlockedAttachPin: undefined,
|
|
1373
1463
|
resumed: false,
|
|
1464
|
+
walletStatusRefreshed: true,
|
|
1374
1465
|
});
|
|
1375
1466
|
|
|
1376
1467
|
expect(unlockDevice).not.toHaveBeenCalled();
|
|
@@ -1457,10 +1548,9 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1457
1548
|
expect(typedCall).toHaveBeenCalledWith('DeviceSessionAskPassphrase', 'Success', {
|
|
1458
1549
|
passphrase: 'host hidden wallet',
|
|
1459
1550
|
on_device: false,
|
|
1551
|
+
seed_domains: [DeviceSessionSeedDomain.SeedDomain_Standard],
|
|
1460
1552
|
});
|
|
1461
|
-
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
1462
|
-
seed_domains: [],
|
|
1463
|
-
});
|
|
1553
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {});
|
|
1464
1554
|
});
|
|
1465
1555
|
|
|
1466
1556
|
test('deviceStatusGet returns raw DeviceStatus and updates dynamic features', async () => {
|
|
@@ -1569,7 +1659,6 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1569
1659
|
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
1570
1660
|
session_id: 'session-a',
|
|
1571
1661
|
btc_test_address: 'state-a',
|
|
1572
|
-
seed_domains: [],
|
|
1573
1662
|
});
|
|
1574
1663
|
expect(device.getInternalState()).toBe('session-b');
|
|
1575
1664
|
});
|
|
@@ -1643,7 +1732,6 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1643
1732
|
{
|
|
1644
1733
|
session_id: 'session-a',
|
|
1645
1734
|
btc_test_address: 'state-a',
|
|
1646
|
-
seed_domains: [],
|
|
1647
1735
|
},
|
|
1648
1736
|
],
|
|
1649
1737
|
]);
|
|
@@ -1690,10 +1778,14 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1690
1778
|
[
|
|
1691
1779
|
'DeviceSessionAskPassphrase',
|
|
1692
1780
|
'Success',
|
|
1693
|
-
{
|
|
1781
|
+
{
|
|
1782
|
+
passphrase: 'host hidden wallet',
|
|
1783
|
+
on_device: false,
|
|
1784
|
+
seed_domains: [DeviceSessionSeedDomain.SeedDomain_Standard],
|
|
1785
|
+
},
|
|
1694
1786
|
],
|
|
1695
1787
|
['DeviceStatusGet', 'DeviceStatus', {}],
|
|
1696
|
-
['DeviceSessionGet', 'DeviceSession', {
|
|
1788
|
+
['DeviceSessionGet', 'DeviceSession', {}],
|
|
1697
1789
|
]);
|
|
1698
1790
|
});
|
|
1699
1791
|
|
|
@@ -1855,7 +1947,6 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1855
1947
|
expect(promptPassphrase).not.toHaveBeenCalled();
|
|
1856
1948
|
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
1857
1949
|
btc_test_address: 'expected-state',
|
|
1858
|
-
seed_domains: [],
|
|
1859
1950
|
});
|
|
1860
1951
|
expect(typedCall).toHaveBeenCalledWith('LockDevice', 'Success', {});
|
|
1861
1952
|
expect(typedCall.mock.calls.filter(call => call[0] === 'DeviceSessionGet')).toHaveLength(1);
|
|
@@ -2153,7 +2244,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2153
2244
|
firmwareVersion: '4.15.0',
|
|
2154
2245
|
passphraseProtection: true,
|
|
2155
2246
|
sessionId: 'feature-session',
|
|
2156
|
-
unlockedAttachPin:
|
|
2247
|
+
unlockedAttachPin: false,
|
|
2157
2248
|
};
|
|
2158
2249
|
const typedCall = jest
|
|
2159
2250
|
.fn()
|
|
@@ -2246,7 +2337,6 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2246
2337
|
).resolves.toMatchObject({ passphraseState: 'expected-state' });
|
|
2247
2338
|
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
2248
2339
|
btc_test_address: 'expected-state',
|
|
2249
|
-
seed_domains: [],
|
|
2250
2340
|
});
|
|
2251
2341
|
expect(promptPassphrase).not.toHaveBeenCalled();
|
|
2252
2342
|
});
|
|
@@ -2320,7 +2410,6 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2320
2410
|
{
|
|
2321
2411
|
session_id: 'session-pro2-app',
|
|
2322
2412
|
btc_test_address: 'state-pro2-app',
|
|
2323
|
-
seed_domains: [],
|
|
2324
2413
|
},
|
|
2325
2414
|
],
|
|
2326
2415
|
]);
|
|
@@ -2510,10 +2599,9 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2510
2599
|
expect(typedCall).toHaveBeenNthCalledWith(2, 'DeviceSessionAskPassphrase', 'Success', {
|
|
2511
2600
|
passphrase: 'host hidden wallet',
|
|
2512
2601
|
on_device: false,
|
|
2602
|
+
seed_domains: [DeviceSessionSeedDomain.SeedDomain_Standard],
|
|
2513
2603
|
});
|
|
2514
|
-
expect(typedCall).toHaveBeenLastCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
2515
|
-
seed_domains: [],
|
|
2516
|
-
});
|
|
2604
|
+
expect(typedCall).toHaveBeenLastCalledWith('DeviceSessionGet', 'DeviceSession', {});
|
|
2517
2605
|
});
|
|
2518
2606
|
|
|
2519
2607
|
test('does not mark Pro2 passphrase enabled from a main PIN session alone', async () => {
|
|
@@ -2593,16 +2681,14 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2593
2681
|
expect(device.getInternalState()).toBeUndefined();
|
|
2594
2682
|
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
2595
2683
|
btc_test_address: 'expected-state',
|
|
2596
|
-
seed_domains: [],
|
|
2597
2684
|
});
|
|
2598
2685
|
expect(typedCall).toHaveBeenCalledWith('DeviceSessionAskPassphrase', 'Success', {
|
|
2599
2686
|
passphrase: 'host hidden wallet',
|
|
2600
2687
|
on_device: false,
|
|
2688
|
+
seed_domains: [DeviceSessionSeedDomain.SeedDomain_Standard],
|
|
2601
2689
|
});
|
|
2602
2690
|
expect(typedCall).toHaveBeenCalledWith('DeviceStatusGet', 'DeviceStatus', {});
|
|
2603
|
-
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
2604
|
-
seed_domains: [],
|
|
2605
|
-
});
|
|
2691
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {});
|
|
2606
2692
|
});
|
|
2607
2693
|
|
|
2608
2694
|
test('fails closed instead of switching to Main PIN during a standard-wallet safety check', async () => {
|
|
@@ -2700,20 +2786,19 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2700
2786
|
await expect(
|
|
2701
2787
|
device.checkPassphraseStateSafety('stale-hidden-state', true, false)
|
|
2702
2788
|
).resolves.toBe(true);
|
|
2703
|
-
expect(typedCall).toHaveBeenCalledTimes(
|
|
2789
|
+
expect(typedCall).toHaveBeenCalledTimes(6);
|
|
2704
2790
|
expect(typedCall).toHaveBeenCalledWith('ProtocolInfoRequest', 'ProtocolInfo', {
|
|
2705
2791
|
eventless_wallet_session: true,
|
|
2706
2792
|
});
|
|
2707
|
-
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
2708
|
-
seed_domains: [],
|
|
2709
|
-
});
|
|
2793
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {});
|
|
2710
2794
|
expect(typedCall).toHaveBeenCalledWith('DeviceSessionAskPassphrase', 'Success', {
|
|
2711
2795
|
passphrase: '',
|
|
2712
2796
|
on_device: false,
|
|
2797
|
+
seed_domains: [DeviceSessionSeedDomain.SeedDomain_Standard],
|
|
2713
2798
|
});
|
|
2714
2799
|
expect(typedCall).toHaveBeenCalledWith('DeviceStatusGet', 'DeviceStatus', {});
|
|
2715
2800
|
expect(typedCall.mock.calls.filter(([request]) => request === 'DeviceStatusGet')).toHaveLength(
|
|
2716
|
-
|
|
2801
|
+
3
|
|
2717
2802
|
);
|
|
2718
2803
|
expect(typedCall).not.toHaveBeenCalledWith('DeviceSessionAskPin', 'Success', expect.anything());
|
|
2719
2804
|
});
|
|
@@ -4820,6 +4905,23 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4820
4905
|
expect(typedCall.mock.calls.map(call => call[0])).toEqual(['DeviceInfoGet']);
|
|
4821
4906
|
});
|
|
4822
4907
|
|
|
4908
|
+
test('stops V4 bootloader reconnect polling on a stale BLE bond', async () => {
|
|
4909
|
+
const method = new FirmwareUpdateV4({
|
|
4910
|
+
id: 1,
|
|
4911
|
+
payload: {
|
|
4912
|
+
method: 'firmwareUpdateV4',
|
|
4913
|
+
},
|
|
4914
|
+
});
|
|
4915
|
+
const staleBondError = ERRORS.TypedError(HardwareErrorCode.BlePeerRemovedPairingInformation);
|
|
4916
|
+
const reconnectProtocolV2Device = jest.fn().mockRejectedValue(staleBondError);
|
|
4917
|
+
(method as any).reconnectProtocolV2Device = reconnectProtocolV2Device;
|
|
4918
|
+
|
|
4919
|
+
await expect((method as any).waitForProtocolV2BootloaderMode(60_000, 0)).rejects.toBe(
|
|
4920
|
+
staleBondError
|
|
4921
|
+
);
|
|
4922
|
+
expect(reconnectProtocolV2Device).toHaveBeenCalledTimes(1);
|
|
4923
|
+
});
|
|
4924
|
+
|
|
4823
4925
|
test('polls again when Protocol V2 bootloader serial is temporarily unavailable', async () => {
|
|
4824
4926
|
const method = new FirmwareUpdateV4({
|
|
4825
4927
|
id: 1,
|
|
@@ -4901,6 +5003,87 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4901
5003
|
expect(initialize).not.toHaveBeenCalled();
|
|
4902
5004
|
});
|
|
4903
5005
|
|
|
5006
|
+
test('reacquires the same WebUSB device without a protocol probe during install recovery', async () => {
|
|
5007
|
+
const method = new FirmwareUpdateV4({
|
|
5008
|
+
id: 1,
|
|
5009
|
+
payload: {
|
|
5010
|
+
method: 'firmwareUpdateV4',
|
|
5011
|
+
},
|
|
5012
|
+
});
|
|
5013
|
+
const acquire = jest.fn().mockResolvedValue('usb-path');
|
|
5014
|
+
const enumerate = jest.fn().mockResolvedValue({
|
|
5015
|
+
descriptors: [
|
|
5016
|
+
{ id: 'other-usb', path: 'other-usb', protocolType: 'V2' },
|
|
5017
|
+
{ id: 'usb-id', path: 'usb-path', protocolType: 'V2' },
|
|
5018
|
+
],
|
|
5019
|
+
});
|
|
5020
|
+
const commands = { disposed: true, mainId: '' };
|
|
5021
|
+
const device = stubDevice({
|
|
5022
|
+
originalDescriptor: { id: 'usb-id', path: 'usb-path', protocolType: 'V2' },
|
|
5023
|
+
deviceConnector: { acquire, enumerate },
|
|
5024
|
+
updateDescriptor: jest.fn(),
|
|
5025
|
+
acquire: jest.fn(),
|
|
5026
|
+
commands,
|
|
5027
|
+
getCommands: () => commands,
|
|
5028
|
+
mainId: 'old-usb-path',
|
|
5029
|
+
});
|
|
5030
|
+
const getDevices = jest.spyOn(DevicePool, 'getDevices');
|
|
5031
|
+
const isBrowserWebUsb = jest.spyOn(DataManager, 'isBrowserWebUsb').mockReturnValue(false);
|
|
5032
|
+
const isDesktopWebUsb = jest.spyOn(DataManager, 'isDesktopWebUsb').mockReturnValue(true);
|
|
5033
|
+
(method as any).device = device;
|
|
5034
|
+
(method as any).protocolV2ExpectedPath = 'usb-path';
|
|
5035
|
+
|
|
5036
|
+
try {
|
|
5037
|
+
await (method as any).reconnectProtocolV2Device({ skipProtocolProbe: true });
|
|
5038
|
+
} finally {
|
|
5039
|
+
getDevices.mockRestore();
|
|
5040
|
+
isBrowserWebUsb.mockRestore();
|
|
5041
|
+
isDesktopWebUsb.mockRestore();
|
|
5042
|
+
}
|
|
5043
|
+
|
|
5044
|
+
expect(getDevices).not.toHaveBeenCalled();
|
|
5045
|
+
expect(acquire).toHaveBeenCalledWith(
|
|
5046
|
+
'usb-path',
|
|
5047
|
+
null,
|
|
5048
|
+
undefined,
|
|
5049
|
+
'V2',
|
|
5050
|
+
undefined,
|
|
5051
|
+
undefined,
|
|
5052
|
+
true
|
|
5053
|
+
);
|
|
5054
|
+
expect(device.acquire).not.toHaveBeenCalled();
|
|
5055
|
+
expect(commands.disposed).toBe(false);
|
|
5056
|
+
expect(commands.mainId).toBe('usb-path');
|
|
5057
|
+
});
|
|
5058
|
+
|
|
5059
|
+
test('reacquires the same BLE peripheral by id without scanning during install recovery', async () => {
|
|
5060
|
+
const method = new FirmwareUpdateV4({
|
|
5061
|
+
id: 1,
|
|
5062
|
+
payload: {
|
|
5063
|
+
method: 'firmwareUpdateV4',
|
|
5064
|
+
},
|
|
5065
|
+
});
|
|
5066
|
+
const acquire = jest.fn().mockResolvedValue({ uuid: 'ble-id', protocolType: 'V2' });
|
|
5067
|
+
const enumerate = jest.fn().mockResolvedValue({
|
|
5068
|
+
descriptors: [{ id: 'ble-id', name: 'OneKey Pro 2', protocolType: 'V2' }],
|
|
5069
|
+
});
|
|
5070
|
+
const commands = { disposed: true, mainId: '' };
|
|
5071
|
+
(method as any).isBleReconnect = jest.fn(() => true);
|
|
5072
|
+
(method as any).device = stubDevice({
|
|
5073
|
+
originalDescriptor: { id: 'ble-id', path: 'ble-path', protocolType: 'V2' },
|
|
5074
|
+
deviceConnector: { acquire, enumerate },
|
|
5075
|
+
commands,
|
|
5076
|
+
getCommands: () => commands,
|
|
5077
|
+
});
|
|
5078
|
+
|
|
5079
|
+
await (method as any).reconnectProtocolV2Device({ skipProtocolProbe: true });
|
|
5080
|
+
|
|
5081
|
+
expect(enumerate).not.toHaveBeenCalled();
|
|
5082
|
+
expect(acquire).toHaveBeenCalledWith('ble-id', null, true, 'V2', undefined, undefined, true);
|
|
5083
|
+
expect(commands.disposed).toBe(false);
|
|
5084
|
+
expect(commands.mainId).toBe('ble-id');
|
|
5085
|
+
});
|
|
5086
|
+
|
|
4904
5087
|
test('selects the matching physical device from multiple Protocol V2 USB reconnect candidates', async () => {
|
|
4905
5088
|
const method = new FirmwareUpdateV4({
|
|
4906
5089
|
id: 1,
|
|
@@ -5290,6 +5473,108 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5290
5473
|
});
|
|
5291
5474
|
});
|
|
5292
5475
|
|
|
5476
|
+
test('treats a typed BLE disconnect during Protocol V2 normal reboot as expected', async () => {
|
|
5477
|
+
const method = new FirmwareUpdateV4({
|
|
5478
|
+
id: 1,
|
|
5479
|
+
payload: {
|
|
5480
|
+
method: 'firmwareUpdateV4',
|
|
5481
|
+
},
|
|
5482
|
+
});
|
|
5483
|
+
const typedCall = jest
|
|
5484
|
+
.fn()
|
|
5485
|
+
.mockRejectedValue(ERRORS.TypedError(HardwareErrorCode.BleDeviceDisconnected));
|
|
5486
|
+
|
|
5487
|
+
(method as any).device = stubDevice({
|
|
5488
|
+
getCommands: () => ({ typedCall }),
|
|
5489
|
+
});
|
|
5490
|
+
|
|
5491
|
+
await expect((method as any).protocolV2Reboot(DeviceRebootType.Normal)).resolves.toEqual({
|
|
5492
|
+
message: 'Device rebooted successfully',
|
|
5493
|
+
});
|
|
5494
|
+
expect((method as any).device.markProtocolV2Reboot).toHaveBeenCalledWith(
|
|
5495
|
+
DeviceRebootType.Normal
|
|
5496
|
+
);
|
|
5497
|
+
});
|
|
5498
|
+
|
|
5499
|
+
test('uses a short timeout and continues after a BLE reboot response timeout', async () => {
|
|
5500
|
+
const method = new FirmwareUpdateV4({
|
|
5501
|
+
id: 1,
|
|
5502
|
+
payload: {
|
|
5503
|
+
method: 'firmwareUpdateV4',
|
|
5504
|
+
},
|
|
5505
|
+
});
|
|
5506
|
+
const typedCall = jest
|
|
5507
|
+
.fn()
|
|
5508
|
+
.mockRejectedValue(
|
|
5509
|
+
ERRORS.TypedError(
|
|
5510
|
+
HardwareErrorCode.BleTimeoutError,
|
|
5511
|
+
'Lowlevel response timeout after 5000ms for DeviceReboot'
|
|
5512
|
+
)
|
|
5513
|
+
);
|
|
5514
|
+
|
|
5515
|
+
(method as any).device = stubDevice({
|
|
5516
|
+
getCommands: () => ({ typedCall }),
|
|
5517
|
+
});
|
|
5518
|
+
|
|
5519
|
+
await expect((method as any).protocolV2Reboot(DeviceRebootType.Normal)).resolves.toEqual({
|
|
5520
|
+
message: 'Device rebooted successfully',
|
|
5521
|
+
});
|
|
5522
|
+
expect(typedCall).toHaveBeenCalledWith(
|
|
5523
|
+
'DeviceReboot',
|
|
5524
|
+
'Success',
|
|
5525
|
+
{ reboot_type: DeviceRebootType.Normal },
|
|
5526
|
+
{ timeoutMs: 5000 }
|
|
5527
|
+
);
|
|
5528
|
+
expect((method as any).device.markProtocolV2Reboot).toHaveBeenCalledWith(
|
|
5529
|
+
DeviceRebootType.Normal
|
|
5530
|
+
);
|
|
5531
|
+
});
|
|
5532
|
+
|
|
5533
|
+
test('continues after a USB Protocol V2 reboot response timeout', async () => {
|
|
5534
|
+
const method = new FirmwareUpdateV4({
|
|
5535
|
+
id: 1,
|
|
5536
|
+
payload: {
|
|
5537
|
+
method: 'firmwareUpdateV4',
|
|
5538
|
+
},
|
|
5539
|
+
});
|
|
5540
|
+
const timeoutError = Object.assign(
|
|
5541
|
+
new Error('Protocol V2 response timeout after 5000ms for DeviceReboot'),
|
|
5542
|
+
{ code: 'response-timeout' }
|
|
5543
|
+
);
|
|
5544
|
+
const typedCall = jest.fn().mockRejectedValue(timeoutError);
|
|
5545
|
+
|
|
5546
|
+
(method as any).device = stubDevice({
|
|
5547
|
+
getCommands: () => ({ typedCall }),
|
|
5548
|
+
});
|
|
5549
|
+
|
|
5550
|
+
await expect((method as any).protocolV2Reboot(DeviceRebootType.Normal)).resolves.toEqual({
|
|
5551
|
+
message: 'Device rebooted successfully',
|
|
5552
|
+
});
|
|
5553
|
+
expect((method as any).device.markProtocolV2Reboot).toHaveBeenCalledWith(
|
|
5554
|
+
DeviceRebootType.Normal
|
|
5555
|
+
);
|
|
5556
|
+
});
|
|
5557
|
+
|
|
5558
|
+
test('propagates non-transport errors from a Protocol V2 reboot', async () => {
|
|
5559
|
+
const method = new FirmwareUpdateV4({
|
|
5560
|
+
id: 1,
|
|
5561
|
+
payload: {
|
|
5562
|
+
method: 'firmwareUpdateV4',
|
|
5563
|
+
},
|
|
5564
|
+
});
|
|
5565
|
+
const rebootError = new Error('Device rejected reboot');
|
|
5566
|
+
const typedCall = jest.fn().mockRejectedValue(rebootError);
|
|
5567
|
+
|
|
5568
|
+
(method as any).device = stubDevice({
|
|
5569
|
+
getCommands: () => ({ typedCall }),
|
|
5570
|
+
});
|
|
5571
|
+
|
|
5572
|
+
await expect((method as any).protocolV2Reboot(DeviceRebootType.Normal)).rejects.toBe(
|
|
5573
|
+
rebootError
|
|
5574
|
+
);
|
|
5575
|
+
expect((method as any).device.markProtocolV2Reboot).not.toHaveBeenCalled();
|
|
5576
|
+
});
|
|
5577
|
+
|
|
5293
5578
|
test('treats Android BLE transport release during Protocol V2 reboot as expected', async () => {
|
|
5294
5579
|
const method = new FirmwareUpdateV4({
|
|
5295
5580
|
id: 1,
|
|
@@ -5365,7 +5650,11 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5365
5650
|
expect(call).toHaveBeenCalledWith(
|
|
5366
5651
|
'DeviceFirmwareUpdateRequest',
|
|
5367
5652
|
{},
|
|
5368
|
-
{
|
|
5653
|
+
expect.objectContaining({
|
|
5654
|
+
returnAfterWrite: true,
|
|
5655
|
+
expectedTypes: ['Success'],
|
|
5656
|
+
onResponseAfterWrite: expect.any(Function),
|
|
5657
|
+
})
|
|
5369
5658
|
);
|
|
5370
5659
|
expect(typedCall.mock.invocationCallOrder[0]).toBeLessThan(call.mock.invocationCallOrder[0]);
|
|
5371
5660
|
expect((method.postMessage as jest.Mock).mock.invocationCallOrder[0]).toBeLessThan(
|
|
@@ -5374,7 +5663,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5374
5663
|
expect(method.postTipMessage).not.toHaveBeenCalled();
|
|
5375
5664
|
expect(method.postProgressMessage).not.toHaveBeenCalled();
|
|
5376
5665
|
await cancelableAction?.();
|
|
5377
|
-
expect(cancelDevice).
|
|
5666
|
+
expect(cancelDevice).not.toHaveBeenCalled();
|
|
5378
5667
|
});
|
|
5379
5668
|
|
|
5380
5669
|
test('does not send the install request when Protocol V2 staging fails', async () => {
|
|
@@ -5406,7 +5695,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5406
5695
|
expect(method.postProgressMessage).not.toHaveBeenCalled();
|
|
5407
5696
|
});
|
|
5408
5697
|
|
|
5409
|
-
test('does not enter install state when
|
|
5698
|
+
test('does not enter install state when the empty request fails', async () => {
|
|
5410
5699
|
const method = new FirmwareUpdateV4({
|
|
5411
5700
|
id: 1,
|
|
5412
5701
|
payload: {
|
|
@@ -5414,7 +5703,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5414
5703
|
},
|
|
5415
5704
|
});
|
|
5416
5705
|
const typedCall = jest.fn().mockResolvedValue({ type: 'Success', message: {} });
|
|
5417
|
-
const call = jest.fn().mockRejectedValue(new Error('
|
|
5706
|
+
const call = jest.fn().mockRejectedValue(new Error('request failed'));
|
|
5418
5707
|
|
|
5419
5708
|
(method as any).device = stubDevice({
|
|
5420
5709
|
getCommands: () => ({ typedCall, call }),
|
|
@@ -5429,46 +5718,151 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5429
5718
|
(method as any).protocolV2StartFirmwareUpdate({
|
|
5430
5719
|
targets: [{ target_id: 4, path: 'vol0:/application_p1.bin' }],
|
|
5431
5720
|
})
|
|
5432
|
-
).rejects.toThrow('
|
|
5721
|
+
).rejects.toThrow('request failed');
|
|
5433
5722
|
|
|
5434
|
-
expect(call).
|
|
5723
|
+
expect(call).toHaveBeenCalledWith(
|
|
5724
|
+
'DeviceFirmwareUpdateRequest',
|
|
5725
|
+
{},
|
|
5726
|
+
expect.objectContaining({
|
|
5727
|
+
returnAfterWrite: true,
|
|
5728
|
+
expectedTypes: ['Success'],
|
|
5729
|
+
onResponseAfterWrite: expect.any(Function),
|
|
5730
|
+
})
|
|
5731
|
+
);
|
|
5435
5732
|
expect(method.postTipMessage).not.toHaveBeenCalled();
|
|
5436
5733
|
expect(method.postProgressMessage).not.toHaveBeenCalled();
|
|
5437
5734
|
});
|
|
5438
5735
|
|
|
5439
|
-
test('
|
|
5736
|
+
test('continues to install polling when the empty request releases BLE transport', async () => {
|
|
5440
5737
|
const method = new FirmwareUpdateV4({
|
|
5441
5738
|
id: 1,
|
|
5442
5739
|
payload: {
|
|
5443
5740
|
method: 'firmwareUpdateV4',
|
|
5444
5741
|
},
|
|
5445
5742
|
});
|
|
5446
|
-
const typedCall = jest
|
|
5447
|
-
|
|
5448
|
-
.mockResolvedValueOnce({
|
|
5449
|
-
type: 'DeviceFirmwareUpdateStatus',
|
|
5450
|
-
message: { records: [{ target_id: 4, status: 1 }] },
|
|
5451
|
-
})
|
|
5452
|
-
.mockResolvedValueOnce({
|
|
5453
|
-
type: 'DeviceFirmwareUpdateStatus',
|
|
5454
|
-
message: { records: [{ target_id: 4, status: 2 }] },
|
|
5455
|
-
});
|
|
5456
|
-
const reconnectProtocolV2Device = jest.fn();
|
|
5457
|
-
const verifyProtocolV2ReconnectIdentity = jest.fn();
|
|
5743
|
+
const typedCall = jest.fn().mockResolvedValue({ type: 'Success', message: {} });
|
|
5744
|
+
const call = jest.fn().mockRejectedValue(new Error('React Native BLE transport released'));
|
|
5458
5745
|
|
|
5459
5746
|
(method as any).device = stubDevice({
|
|
5460
|
-
getCommands: () => ({ typedCall }),
|
|
5747
|
+
getCommands: () => ({ typedCall, call }),
|
|
5748
|
+
createProtocolV2UiPhaseMetadata: jest.fn().mockReturnValue(undefined),
|
|
5749
|
+
toMessageObject: jest.fn().mockReturnValue({ connectId: 'pro2' }),
|
|
5461
5750
|
});
|
|
5462
|
-
(method as any).
|
|
5463
|
-
|
|
5464
|
-
method.postProgressMessage = jest.fn();
|
|
5465
|
-
method.postTipMessage = jest.fn();
|
|
5466
|
-
|
|
5467
|
-
await (method as any).waitForProtocolV2FirmwareUpdateComplete([
|
|
5468
|
-
{ target_id: 4, path: 'vol0:/application_p1.bin' },
|
|
5469
|
-
]);
|
|
5751
|
+
(method as any).isBleReconnect = jest.fn(() => true);
|
|
5752
|
+
method.postMessage = jest.fn();
|
|
5470
5753
|
|
|
5471
|
-
expect(
|
|
5754
|
+
await expect(
|
|
5755
|
+
(method as any).protocolV2StartFirmwareUpdate({
|
|
5756
|
+
targets: [{ target_id: 4, path: 'vol0:/application_p1.bin' }],
|
|
5757
|
+
})
|
|
5758
|
+
).resolves.toBeUndefined();
|
|
5759
|
+
|
|
5760
|
+
expect(call).toHaveBeenCalledWith(
|
|
5761
|
+
'DeviceFirmwareUpdateRequest',
|
|
5762
|
+
{},
|
|
5763
|
+
expect.objectContaining({
|
|
5764
|
+
returnAfterWrite: true,
|
|
5765
|
+
expectedTypes: ['Success'],
|
|
5766
|
+
onResponseAfterWrite: expect.any(Function),
|
|
5767
|
+
})
|
|
5768
|
+
);
|
|
5769
|
+
expect(typedCall).not.toHaveBeenCalledWith('DeviceFirmwareUpdateRequest', 'Success', {});
|
|
5770
|
+
expect((method as any).protocolV2InstallNeedsReconnect).toBe(true);
|
|
5771
|
+
expect((method as any).protocolV2InstallDisconnectObserved).toBe(true);
|
|
5772
|
+
});
|
|
5773
|
+
|
|
5774
|
+
test.each([
|
|
5775
|
+
{ name: 'bootloader', targetId: 3, path: 'vol0:/bootloader.bin' },
|
|
5776
|
+
{ name: 'application', targetId: 4, path: 'vol0:/application_p1.bin' },
|
|
5777
|
+
{ name: 'secure element', targetId: 7, path: 'vol0:/se01.bin' },
|
|
5778
|
+
])(
|
|
5779
|
+
'reconnects a released BLE install link before polling $name status without DeviceInfo',
|
|
5780
|
+
async ({ targetId, path }) => {
|
|
5781
|
+
const method = new FirmwareUpdateV4({
|
|
5782
|
+
id: 1,
|
|
5783
|
+
payload: {
|
|
5784
|
+
method: 'firmwareUpdateV4',
|
|
5785
|
+
},
|
|
5786
|
+
});
|
|
5787
|
+
const targets = [{ target_id: targetId, path }];
|
|
5788
|
+
const typedCall = jest
|
|
5789
|
+
.fn()
|
|
5790
|
+
.mockResolvedValueOnce({
|
|
5791
|
+
type: 'DeviceFirmwareUpdateStatus',
|
|
5792
|
+
message: { records: [{ ...targets[0], status: 1, payload_version: 0 }] },
|
|
5793
|
+
})
|
|
5794
|
+
.mockResolvedValueOnce({
|
|
5795
|
+
type: 'DeviceFirmwareUpdateStatus',
|
|
5796
|
+
message: { records: [{ ...targets[0], status: 2, payload_version: 0x010000 }] },
|
|
5797
|
+
});
|
|
5798
|
+
const reconnectProtocolV2Device = jest.fn().mockResolvedValue(undefined);
|
|
5799
|
+
const verifyProtocolV2ReconnectIdentity = jest.fn();
|
|
5800
|
+
const setTimeoutSpy = jest.spyOn(global, 'setTimeout').mockImplementation(((
|
|
5801
|
+
callback: () => void
|
|
5802
|
+
) => {
|
|
5803
|
+
callback();
|
|
5804
|
+
return 0 as any;
|
|
5805
|
+
}) as typeof setTimeout);
|
|
5806
|
+
|
|
5807
|
+
(method as any).device = stubDevice({
|
|
5808
|
+
getCommands: () => ({ typedCall }),
|
|
5809
|
+
});
|
|
5810
|
+
(method as any).isBleReconnect = jest.fn(() => true);
|
|
5811
|
+
(method as any).protocolV2InstallNeedsReconnect = true;
|
|
5812
|
+
(method as any).reconnectProtocolV2Device = reconnectProtocolV2Device;
|
|
5813
|
+
(method as any).verifyProtocolV2ReconnectIdentity = verifyProtocolV2ReconnectIdentity;
|
|
5814
|
+
method.postProgressMessage = jest.fn();
|
|
5815
|
+
|
|
5816
|
+
try {
|
|
5817
|
+
await expect(
|
|
5818
|
+
(method as any).waitForProtocolV2FirmwareUpdateComplete(targets, true)
|
|
5819
|
+
).resolves.toBeUndefined();
|
|
5820
|
+
} finally {
|
|
5821
|
+
setTimeoutSpy.mockRestore();
|
|
5822
|
+
}
|
|
5823
|
+
|
|
5824
|
+
expect(reconnectProtocolV2Device).toHaveBeenCalledWith({ skipProtocolProbe: true });
|
|
5825
|
+
expect(verifyProtocolV2ReconnectIdentity).not.toHaveBeenCalled();
|
|
5826
|
+
expect(typedCall.mock.calls.map(callArgs => callArgs[0])).toEqual([
|
|
5827
|
+
'DeviceFirmwareUpdateStatusGet',
|
|
5828
|
+
'DeviceFirmwareUpdateStatusGet',
|
|
5829
|
+
]);
|
|
5830
|
+
}
|
|
5831
|
+
);
|
|
5832
|
+
|
|
5833
|
+
test('polls only firmware status while Protocol V2 bootloader is installing', async () => {
|
|
5834
|
+
const method = new FirmwareUpdateV4({
|
|
5835
|
+
id: 1,
|
|
5836
|
+
payload: {
|
|
5837
|
+
method: 'firmwareUpdateV4',
|
|
5838
|
+
},
|
|
5839
|
+
});
|
|
5840
|
+
const typedCall = jest
|
|
5841
|
+
.fn()
|
|
5842
|
+
.mockResolvedValueOnce({
|
|
5843
|
+
type: 'DeviceFirmwareUpdateStatus',
|
|
5844
|
+
message: { records: [{ target_id: 4, status: 1 }] },
|
|
5845
|
+
})
|
|
5846
|
+
.mockResolvedValueOnce({
|
|
5847
|
+
type: 'DeviceFirmwareUpdateStatus',
|
|
5848
|
+
message: { records: [{ target_id: 4, status: 2 }] },
|
|
5849
|
+
});
|
|
5850
|
+
const reconnectProtocolV2Device = jest.fn();
|
|
5851
|
+
const verifyProtocolV2ReconnectIdentity = jest.fn();
|
|
5852
|
+
|
|
5853
|
+
(method as any).device = stubDevice({
|
|
5854
|
+
getCommands: () => ({ typedCall }),
|
|
5855
|
+
});
|
|
5856
|
+
(method as any).reconnectProtocolV2Device = reconnectProtocolV2Device;
|
|
5857
|
+
(method as any).verifyProtocolV2ReconnectIdentity = verifyProtocolV2ReconnectIdentity;
|
|
5858
|
+
method.postProgressMessage = jest.fn();
|
|
5859
|
+
method.postTipMessage = jest.fn();
|
|
5860
|
+
|
|
5861
|
+
await (method as any).waitForProtocolV2FirmwareUpdateComplete([
|
|
5862
|
+
{ target_id: 4, path: 'vol0:/application_p1.bin' },
|
|
5863
|
+
]);
|
|
5864
|
+
|
|
5865
|
+
expect(reconnectProtocolV2Device).not.toHaveBeenCalled();
|
|
5472
5866
|
expect(verifyProtocolV2ReconnectIdentity).not.toHaveBeenCalled();
|
|
5473
5867
|
expect(typedCall.mock.calls.map(call => call[0])).toEqual([
|
|
5474
5868
|
'DeviceFirmwareUpdateStatusGet',
|
|
@@ -5479,7 +5873,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5479
5873
|
expect(method.postProgressMessage).toHaveBeenCalledWith(100, 'installingFirmware');
|
|
5480
5874
|
});
|
|
5481
5875
|
|
|
5482
|
-
test('keeps polling after
|
|
5876
|
+
test('keeps polling after an empty status response until the target finishes', async () => {
|
|
5483
5877
|
const method = new FirmwareUpdateV4({
|
|
5484
5878
|
id: 1,
|
|
5485
5879
|
payload: {
|
|
@@ -5488,7 +5882,10 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5488
5882
|
});
|
|
5489
5883
|
const typedCall = jest
|
|
5490
5884
|
.fn()
|
|
5491
|
-
.mockResolvedValueOnce({
|
|
5885
|
+
.mockResolvedValueOnce({
|
|
5886
|
+
type: 'DeviceFirmwareUpdateStatus',
|
|
5887
|
+
message: { records: [] },
|
|
5888
|
+
})
|
|
5492
5889
|
.mockResolvedValueOnce({
|
|
5493
5890
|
type: 'DeviceFirmwareUpdateStatus',
|
|
5494
5891
|
message: { records: [] },
|
|
@@ -5630,6 +6027,38 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5630
6027
|
expect(method.postProgressMessage).toHaveBeenCalledWith(100, 'installingFirmware');
|
|
5631
6028
|
});
|
|
5632
6029
|
|
|
6030
|
+
test('does not count a stale finished target when only another target was live', () => {
|
|
6031
|
+
const method = new FirmwareUpdateV4({
|
|
6032
|
+
id: 1,
|
|
6033
|
+
payload: {
|
|
6034
|
+
method: 'firmwareUpdateV4',
|
|
6035
|
+
},
|
|
6036
|
+
});
|
|
6037
|
+
const assertProtocolV2TargetStatus = (method as any).assertProtocolV2TargetStatus.bind(method);
|
|
6038
|
+
const expectedTargetIds = new Set([3, 4]);
|
|
6039
|
+
const expectedPaths = new Map([
|
|
6040
|
+
[3, 'vol0:/bootloader.bin'],
|
|
6041
|
+
[4, 'vol0:/application_p1.bin'],
|
|
6042
|
+
]);
|
|
6043
|
+
const liveTargetIds = new Set([4]);
|
|
6044
|
+
method.postProgressMessage = jest.fn();
|
|
6045
|
+
|
|
6046
|
+
const completed = assertProtocolV2TargetStatus(
|
|
6047
|
+
[
|
|
6048
|
+
{ target_id: 3, status: 2, path: 'vol0:/bootloader.bin' },
|
|
6049
|
+
{ target_id: 4, status: 2, path: 'vol0:/application_p1.bin' },
|
|
6050
|
+
],
|
|
6051
|
+
expectedTargetIds,
|
|
6052
|
+
expectedPaths,
|
|
6053
|
+
liveTargetIds
|
|
6054
|
+
);
|
|
6055
|
+
|
|
6056
|
+
expect(completed).toBe(false);
|
|
6057
|
+
expect((method as any).protocolV2CompletedTargetIds).toEqual(new Set([4]));
|
|
6058
|
+
expect(method.postProgressMessage).toHaveBeenCalledWith(50, 'installingFirmware');
|
|
6059
|
+
expect(method.postProgressMessage).not.toHaveBeenCalledWith(100, 'installingFirmware');
|
|
6060
|
+
});
|
|
6061
|
+
|
|
5633
6062
|
test('ignores stale failed records until the current install starts', async () => {
|
|
5634
6063
|
const method = new FirmwareUpdateV4({
|
|
5635
6064
|
id: 1,
|
|
@@ -5678,6 +6107,141 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5678
6107
|
expect(typedCall).toHaveBeenCalledTimes(3);
|
|
5679
6108
|
});
|
|
5680
6109
|
|
|
6110
|
+
test('ignores leftover failed records while another leftover target is still pending', async () => {
|
|
6111
|
+
const method = new FirmwareUpdateV4({
|
|
6112
|
+
id: 1,
|
|
6113
|
+
payload: {
|
|
6114
|
+
method: 'firmwareUpdateV4',
|
|
6115
|
+
},
|
|
6116
|
+
});
|
|
6117
|
+
const typedCall = jest
|
|
6118
|
+
.fn()
|
|
6119
|
+
.mockResolvedValueOnce({
|
|
6120
|
+
type: 'DeviceFirmwareUpdateStatus',
|
|
6121
|
+
message: {
|
|
6122
|
+
records: [
|
|
6123
|
+
{ target_id: 3, status: 0, path: 'vol0:/bootloader.bin' },
|
|
6124
|
+
{ target_id: 4, status: 3, path: 'vol0:/application_p1.bin' },
|
|
6125
|
+
],
|
|
6126
|
+
},
|
|
6127
|
+
})
|
|
6128
|
+
.mockResolvedValueOnce({
|
|
6129
|
+
type: 'DeviceFirmwareUpdateStatus',
|
|
6130
|
+
message: {
|
|
6131
|
+
records: [
|
|
6132
|
+
{ target_id: 3, status: 0, path: 'vol0:/bootloader.bin' },
|
|
6133
|
+
{ target_id: 4, status: 0, path: 'vol0:/application_p1.bin' },
|
|
6134
|
+
],
|
|
6135
|
+
},
|
|
6136
|
+
})
|
|
6137
|
+
.mockResolvedValueOnce({
|
|
6138
|
+
type: 'DeviceFirmwareUpdateStatus',
|
|
6139
|
+
message: {
|
|
6140
|
+
records: [
|
|
6141
|
+
{ target_id: 3, status: 2, path: 'vol0:/bootloader.bin' },
|
|
6142
|
+
{ target_id: 4, status: 2, path: 'vol0:/application_p1.bin' },
|
|
6143
|
+
],
|
|
6144
|
+
},
|
|
6145
|
+
});
|
|
6146
|
+
const setTimeoutSpy = jest.spyOn(global, 'setTimeout').mockImplementation(((
|
|
6147
|
+
callback: () => void
|
|
6148
|
+
) => {
|
|
6149
|
+
callback();
|
|
6150
|
+
return 0 as any;
|
|
6151
|
+
}) as typeof setTimeout);
|
|
6152
|
+
|
|
6153
|
+
(method as any).device = stubDevice({
|
|
6154
|
+
getCommands: () => ({ typedCall }),
|
|
6155
|
+
});
|
|
6156
|
+
(method as any).reconnectProtocolV2Device = jest.fn().mockResolvedValue(undefined);
|
|
6157
|
+
(method as any).verifyProtocolV2ReconnectIdentity = jest.fn().mockResolvedValue(undefined);
|
|
6158
|
+
method.postProgressMessage = jest.fn();
|
|
6159
|
+
|
|
6160
|
+
try {
|
|
6161
|
+
await (method as any).waitForProtocolV2FirmwareUpdateComplete(
|
|
6162
|
+
[
|
|
6163
|
+
{ target_id: 3, path: 'vol0:/bootloader.bin' },
|
|
6164
|
+
{ target_id: 4, path: 'vol0:/application_p1.bin' },
|
|
6165
|
+
],
|
|
6166
|
+
true
|
|
6167
|
+
);
|
|
6168
|
+
} finally {
|
|
6169
|
+
setTimeoutSpy.mockRestore();
|
|
6170
|
+
}
|
|
6171
|
+
|
|
6172
|
+
expect(typedCall).toHaveBeenCalledTimes(3);
|
|
6173
|
+
expect(method.postProgressMessage).toHaveBeenCalledWith(100, 'installingFirmware');
|
|
6174
|
+
});
|
|
6175
|
+
|
|
6176
|
+
test('reports failed only after that target was live in the current install', async () => {
|
|
6177
|
+
const method = new FirmwareUpdateV4({
|
|
6178
|
+
id: 1,
|
|
6179
|
+
payload: {
|
|
6180
|
+
method: 'firmwareUpdateV4',
|
|
6181
|
+
},
|
|
6182
|
+
});
|
|
6183
|
+
const typedCall = jest
|
|
6184
|
+
.fn()
|
|
6185
|
+
.mockResolvedValueOnce({
|
|
6186
|
+
type: 'DeviceFirmwareUpdateStatus',
|
|
6187
|
+
message: {
|
|
6188
|
+
records: [
|
|
6189
|
+
{ target_id: 3, status: 0, path: 'vol0:/bootloader.bin' },
|
|
6190
|
+
{ target_id: 4, status: 3, path: 'vol0:/application_p1.bin' },
|
|
6191
|
+
],
|
|
6192
|
+
},
|
|
6193
|
+
})
|
|
6194
|
+
.mockResolvedValueOnce({
|
|
6195
|
+
type: 'DeviceFirmwareUpdateStatus',
|
|
6196
|
+
message: {
|
|
6197
|
+
records: [
|
|
6198
|
+
{ target_id: 3, status: 0, path: 'vol0:/bootloader.bin' },
|
|
6199
|
+
{ target_id: 4, status: 0, path: 'vol0:/application_p1.bin' },
|
|
6200
|
+
],
|
|
6201
|
+
},
|
|
6202
|
+
})
|
|
6203
|
+
.mockResolvedValueOnce({
|
|
6204
|
+
type: 'DeviceFirmwareUpdateStatus',
|
|
6205
|
+
message: {
|
|
6206
|
+
records: [
|
|
6207
|
+
{ target_id: 3, status: 0, path: 'vol0:/bootloader.bin' },
|
|
6208
|
+
{ target_id: 4, status: 3, path: 'vol0:/application_p1.bin' },
|
|
6209
|
+
],
|
|
6210
|
+
},
|
|
6211
|
+
});
|
|
6212
|
+
const setTimeoutSpy = jest.spyOn(global, 'setTimeout').mockImplementation(((
|
|
6213
|
+
callback: () => void
|
|
6214
|
+
) => {
|
|
6215
|
+
callback();
|
|
6216
|
+
return 0 as any;
|
|
6217
|
+
}) as typeof setTimeout);
|
|
6218
|
+
|
|
6219
|
+
(method as any).device = stubDevice({
|
|
6220
|
+
getCommands: () => ({ typedCall }),
|
|
6221
|
+
});
|
|
6222
|
+
(method as any).reconnectProtocolV2Device = jest.fn().mockResolvedValue(undefined);
|
|
6223
|
+
(method as any).verifyProtocolV2ReconnectIdentity = jest.fn().mockResolvedValue(undefined);
|
|
6224
|
+
|
|
6225
|
+
try {
|
|
6226
|
+
await expect(
|
|
6227
|
+
(method as any).waitForProtocolV2FirmwareUpdateComplete(
|
|
6228
|
+
[
|
|
6229
|
+
{ target_id: 3, path: 'vol0:/bootloader.bin' },
|
|
6230
|
+
{ target_id: 4, path: 'vol0:/application_p1.bin' },
|
|
6231
|
+
],
|
|
6232
|
+
true
|
|
6233
|
+
)
|
|
6234
|
+
).rejects.toMatchObject({
|
|
6235
|
+
errorCode: HardwareErrorCode.FirmwareError,
|
|
6236
|
+
message: 'Protocol V2 firmware install failed',
|
|
6237
|
+
});
|
|
6238
|
+
} finally {
|
|
6239
|
+
setTimeoutSpy.mockRestore();
|
|
6240
|
+
}
|
|
6241
|
+
|
|
6242
|
+
expect(typedCall).toHaveBeenCalledTimes(3);
|
|
6243
|
+
});
|
|
6244
|
+
|
|
5681
6245
|
test('waits five minutes before rejecting normal mode without install evidence', async () => {
|
|
5682
6246
|
const method = new FirmwareUpdateV4({
|
|
5683
6247
|
id: 1,
|
|
@@ -5784,7 +6348,6 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5784
6348
|
});
|
|
5785
6349
|
const typedCall = jest
|
|
5786
6350
|
.fn()
|
|
5787
|
-
.mockResolvedValueOnce({ type: 'Success', message: {} })
|
|
5788
6351
|
.mockRejectedValueOnce(new Error('Pro2 is rebooting'))
|
|
5789
6352
|
.mockResolvedValueOnce({
|
|
5790
6353
|
type: 'DeviceFirmwareUpdateStatus',
|
|
@@ -5801,6 +6364,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5801
6364
|
(method as any).device = stubDevice({
|
|
5802
6365
|
getCommands: () => ({ typedCall }),
|
|
5803
6366
|
});
|
|
6367
|
+
(method as any).protocolV2InstallTerminalSuccessObserved = true;
|
|
5804
6368
|
(method as any).reconnectProtocolV2Device = jest.fn().mockResolvedValue(undefined);
|
|
5805
6369
|
(method as any).verifyProtocolV2ReconnectIdentity = jest.fn().mockResolvedValue(deviceInfo);
|
|
5806
6370
|
(method as any).probeProtocolV2NormalMode = jest.fn().mockResolvedValue(true);
|
|
@@ -5821,6 +6385,54 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5821
6385
|
expect(method.postProgressMessage).toHaveBeenCalledWith(100, 'installingFirmware');
|
|
5822
6386
|
});
|
|
5823
6387
|
|
|
6388
|
+
test('uses the confirmed install request as evidence after the same device returns', async () => {
|
|
6389
|
+
const method = new FirmwareUpdateV4({
|
|
6390
|
+
id: 1,
|
|
6391
|
+
payload: {
|
|
6392
|
+
method: 'firmwareUpdateV4',
|
|
6393
|
+
},
|
|
6394
|
+
});
|
|
6395
|
+
const typedCall = jest
|
|
6396
|
+
.fn()
|
|
6397
|
+
.mockRejectedValueOnce(new Error('The USB device was disconnected'))
|
|
6398
|
+
.mockResolvedValueOnce({
|
|
6399
|
+
type: 'DeviceFirmwareUpdateStatus',
|
|
6400
|
+
message: { records: [] },
|
|
6401
|
+
});
|
|
6402
|
+
const deviceInfo = { hw: { serial_no: 'PRO2-PHYSICAL-1' } };
|
|
6403
|
+
const setTimeoutSpy = jest.spyOn(global, 'setTimeout').mockImplementation(((
|
|
6404
|
+
callback: () => void
|
|
6405
|
+
) => {
|
|
6406
|
+
callback();
|
|
6407
|
+
return 0 as any;
|
|
6408
|
+
}) as typeof setTimeout);
|
|
6409
|
+
|
|
6410
|
+
(method as any).device = stubDevice({
|
|
6411
|
+
getCommands: () => ({ typedCall }),
|
|
6412
|
+
});
|
|
6413
|
+
(method as any).protocolV2InstallTerminalSuccessObserved = true;
|
|
6414
|
+
(method as any).reconnectProtocolV2Device = jest.fn().mockResolvedValue(undefined);
|
|
6415
|
+
(method as any).verifyProtocolV2ReconnectIdentity = jest.fn().mockResolvedValue(deviceInfo);
|
|
6416
|
+
(method as any).probeProtocolV2NormalMode = jest.fn().mockResolvedValue(true);
|
|
6417
|
+
method.postProgressMessage = jest.fn();
|
|
6418
|
+
|
|
6419
|
+
try {
|
|
6420
|
+
await expect(
|
|
6421
|
+
(method as any).waitForProtocolV2FirmwareUpdateComplete(
|
|
6422
|
+
[{ target_id: 4, path: 'vol0:/application_p1.bin' }],
|
|
6423
|
+
true
|
|
6424
|
+
)
|
|
6425
|
+
).resolves.toBeUndefined();
|
|
6426
|
+
} finally {
|
|
6427
|
+
setTimeoutSpy.mockRestore();
|
|
6428
|
+
}
|
|
6429
|
+
|
|
6430
|
+
expect((method as any).reconnectProtocolV2Device).toHaveBeenCalledWith({
|
|
6431
|
+
skipProtocolProbe: true,
|
|
6432
|
+
});
|
|
6433
|
+
expect(method.postProgressMessage).toHaveBeenLastCalledWith(100, 'installingFirmware');
|
|
6434
|
+
});
|
|
6435
|
+
|
|
5824
6436
|
test.each([
|
|
5825
6437
|
[{ mode: 'normal', bootloaderMode: false }, true],
|
|
5826
6438
|
[{ mode: 'notInitialized', bootloaderMode: false }, true],
|
|
@@ -6183,7 +6795,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
6183
6795
|
expect(reconnectProtocolV2Device).not.toHaveBeenCalled();
|
|
6184
6796
|
expect(typedCall).toHaveBeenCalledWith(
|
|
6185
6797
|
'DeviceFirmwareUpdateStatusGet',
|
|
6186
|
-
|
|
6798
|
+
'DeviceFirmwareUpdateStatus',
|
|
6187
6799
|
expect.anything(),
|
|
6188
6800
|
expect.anything()
|
|
6189
6801
|
);
|
|
@@ -6465,10 +7077,12 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
6465
7077
|
});
|
|
6466
7078
|
expect(typedCall).toHaveBeenCalledWith(
|
|
6467
7079
|
'DeviceFirmwareUpdateStatusGet',
|
|
6468
|
-
|
|
7080
|
+
'DeviceFirmwareUpdateStatus',
|
|
6469
7081
|
{
|
|
6470
7082
|
fields: {
|
|
6471
7083
|
status: true,
|
|
7084
|
+
progress_percent: true,
|
|
7085
|
+
phase_info: true,
|
|
6472
7086
|
payload_version: true,
|
|
6473
7087
|
path: true,
|
|
6474
7088
|
},
|
|
@@ -6570,7 +7184,14 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
6570
7184
|
true
|
|
6571
7185
|
);
|
|
6572
7186
|
expect((method as any).exitProtocolV2BootloaderToNormal).not.toHaveBeenCalled();
|
|
6573
|
-
expect(method.postProgressMessage).toHaveBeenCalledWith(
|
|
7187
|
+
expect(method.postProgressMessage).toHaveBeenCalledWith(
|
|
7188
|
+
100,
|
|
7189
|
+
'transferData',
|
|
7190
|
+
expect.objectContaining({
|
|
7191
|
+
transferredBytes: 5,
|
|
7192
|
+
totalBytes: 5,
|
|
7193
|
+
})
|
|
7194
|
+
);
|
|
6574
7195
|
expect((method as any).completeProtocolV2FinalVerification).toHaveBeenCalledTimes(1);
|
|
6575
7196
|
});
|
|
6576
7197
|
|
|
@@ -6644,7 +7265,14 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
6644
7265
|
expect.objectContaining({ processedSize: 2, totalSize: 3 })
|
|
6645
7266
|
);
|
|
6646
7267
|
expect(method.postProgressMessage).toHaveBeenCalledTimes(1);
|
|
6647
|
-
expect(method.postProgressMessage).toHaveBeenCalledWith(
|
|
7268
|
+
expect(method.postProgressMessage).toHaveBeenCalledWith(
|
|
7269
|
+
100,
|
|
7270
|
+
'transferData',
|
|
7271
|
+
expect.objectContaining({
|
|
7272
|
+
transferredBytes: 3,
|
|
7273
|
+
totalBytes: 3,
|
|
7274
|
+
})
|
|
7275
|
+
);
|
|
6648
7276
|
expect((method as any).protocolV2StartFirmwareUpdate).toHaveBeenCalledTimes(1);
|
|
6649
7277
|
expect((method as any).protocolV2StartFirmwareUpdate).toHaveBeenCalledWith({
|
|
6650
7278
|
targets: [{ target_id: 4, path: 'vol0:/application_p1.bin' }],
|
|
@@ -7964,26 +8592,43 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
7964
8592
|
(method as any).verifyProtocolV2StagedFile = jest.fn().mockResolvedValue(undefined);
|
|
7965
8593
|
(method as any).protocolV2StartFirmwareUpdate = jest.fn();
|
|
7966
8594
|
(method as any).waitForProtocolV2FirmwareUpdateComplete = jest.fn();
|
|
8595
|
+
const dateNowSpy = jest
|
|
8596
|
+
.spyOn(Date, 'now')
|
|
8597
|
+
.mockReturnValueOnce(1_000)
|
|
8598
|
+
.mockReturnValueOnce(5_000);
|
|
7967
8599
|
|
|
7968
|
-
|
|
7969
|
-
|
|
7970
|
-
|
|
7971
|
-
|
|
7972
|
-
|
|
7973
|
-
|
|
7974
|
-
|
|
7975
|
-
|
|
7976
|
-
|
|
8600
|
+
try {
|
|
8601
|
+
await (method as any).executeProtocolV2SourceUpdate({
|
|
8602
|
+
installSources: [],
|
|
8603
|
+
resourceSources: [
|
|
8604
|
+
{
|
|
8605
|
+
name: 'images.okpkg',
|
|
8606
|
+
source: {
|
|
8607
|
+
size: 3,
|
|
8608
|
+
readAt: jest.fn(),
|
|
8609
|
+
close: jest.fn(),
|
|
8610
|
+
},
|
|
8611
|
+
devicePath: 'vol0:/bundles/images/images.okpkg',
|
|
7977
8612
|
},
|
|
7978
|
-
|
|
7979
|
-
|
|
7980
|
-
|
|
7981
|
-
|
|
8613
|
+
],
|
|
8614
|
+
});
|
|
8615
|
+
} finally {
|
|
8616
|
+
dateNowSpy.mockRestore();
|
|
8617
|
+
}
|
|
7982
8618
|
|
|
7983
8619
|
expect((method as any).protocolV2SourceUpdateProcess).toHaveBeenCalledTimes(1);
|
|
7984
8620
|
expect((method as any).protocolV2SourceUpdateProcess).toHaveBeenCalledWith(
|
|
7985
|
-
expect.objectContaining({
|
|
8621
|
+
expect.objectContaining({
|
|
8622
|
+
filePath: 'vol0:/bundles/images/images.okpkg',
|
|
8623
|
+
transferStartedAt: 1_000,
|
|
8624
|
+
})
|
|
7986
8625
|
);
|
|
8626
|
+
expect(method.postProgressMessage).toHaveBeenLastCalledWith(100, 'transferData', {
|
|
8627
|
+
transferredBytes: 3,
|
|
8628
|
+
totalBytes: 3,
|
|
8629
|
+
rateBytesPerSecond: 1,
|
|
8630
|
+
elapsedMs: 4_000,
|
|
8631
|
+
});
|
|
7987
8632
|
expect((method as any).verifyProtocolV2StagedFile).toHaveBeenCalledWith(
|
|
7988
8633
|
'vol0:/bundles/images/images.okpkg',
|
|
7989
8634
|
3
|
|
@@ -8183,6 +8828,114 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
8183
8828
|
});
|
|
8184
8829
|
});
|
|
8185
8830
|
|
|
8831
|
+
test('does not retry or wrap a stale BLE bond during a V4 file transfer', async () => {
|
|
8832
|
+
const method = new FirmwareUpdateV4({
|
|
8833
|
+
id: 1,
|
|
8834
|
+
payload: {
|
|
8835
|
+
method: 'firmwareUpdateV4',
|
|
8836
|
+
},
|
|
8837
|
+
});
|
|
8838
|
+
const staleBondError = ERRORS.TypedError(HardwareErrorCode.BlePeerRemovedPairingInformation);
|
|
8839
|
+
(method as any).fileWriteChunk = jest.fn().mockRejectedValue(staleBondError);
|
|
8840
|
+
const recoverProtocolV2FileTransfer = jest.fn();
|
|
8841
|
+
(method as any).recoverProtocolV2FileTransfer = recoverProtocolV2FileTransfer;
|
|
8842
|
+
const source = await openFirmwareByteSource({
|
|
8843
|
+
binary: new Uint8Array([1]).buffer,
|
|
8844
|
+
});
|
|
8845
|
+
|
|
8846
|
+
try {
|
|
8847
|
+
await expect(
|
|
8848
|
+
(method as any).protocolV2SourceUpdateProcess({
|
|
8849
|
+
source,
|
|
8850
|
+
filePath: 'vol1:firmware.bin',
|
|
8851
|
+
processedSize: 0,
|
|
8852
|
+
totalSize: 1,
|
|
8853
|
+
})
|
|
8854
|
+
).rejects.toBe(staleBondError);
|
|
8855
|
+
} finally {
|
|
8856
|
+
await source?.close();
|
|
8857
|
+
}
|
|
8858
|
+
expect(recoverProtocolV2FileTransfer).not.toHaveBeenCalled();
|
|
8859
|
+
});
|
|
8860
|
+
|
|
8861
|
+
test('does not recover or wrap a cancelled V4 file transfer', async () => {
|
|
8862
|
+
const method = new FirmwareUpdateV4({
|
|
8863
|
+
id: 1,
|
|
8864
|
+
payload: {
|
|
8865
|
+
method: 'firmwareUpdateV4',
|
|
8866
|
+
},
|
|
8867
|
+
});
|
|
8868
|
+
const abortController = new AbortController();
|
|
8869
|
+
method.abortSignal = abortController.signal;
|
|
8870
|
+
(method as any).fileWriteChunk = jest.fn().mockImplementation(() => {
|
|
8871
|
+
abortController.abort();
|
|
8872
|
+
return Promise.reject(new Error('transport disposed'));
|
|
8873
|
+
});
|
|
8874
|
+
const recoverProtocolV2FileTransfer = jest.fn();
|
|
8875
|
+
(method as any).recoverProtocolV2FileTransfer = recoverProtocolV2FileTransfer;
|
|
8876
|
+
const source = await openFirmwareByteSource({
|
|
8877
|
+
binary: new Uint8Array([1]).buffer,
|
|
8878
|
+
});
|
|
8879
|
+
|
|
8880
|
+
try {
|
|
8881
|
+
await expect(
|
|
8882
|
+
(method as any).protocolV2SourceUpdateProcess({
|
|
8883
|
+
source,
|
|
8884
|
+
filePath: 'vol1:firmware.bin',
|
|
8885
|
+
processedSize: 0,
|
|
8886
|
+
totalSize: 1,
|
|
8887
|
+
})
|
|
8888
|
+
).rejects.toMatchObject({ errorCode: HardwareErrorCode.CallQueueActionCancelled });
|
|
8889
|
+
} finally {
|
|
8890
|
+
await source?.close();
|
|
8891
|
+
}
|
|
8892
|
+
expect(recoverProtocolV2FileTransfer).not.toHaveBeenCalled();
|
|
8893
|
+
});
|
|
8894
|
+
|
|
8895
|
+
test('keeps public transfer bytes monotonic when a V4 file retry restarts at zero', async () => {
|
|
8896
|
+
const method = new FirmwareUpdateV4({
|
|
8897
|
+
id: 1,
|
|
8898
|
+
payload: {
|
|
8899
|
+
method: 'firmwareUpdateV4',
|
|
8900
|
+
},
|
|
8901
|
+
});
|
|
8902
|
+
let writeCount = 0;
|
|
8903
|
+
(method as any).fileWriteChunk = jest.fn(
|
|
8904
|
+
(_path: string, _size: number, offset: number, data: Uint8Array) => {
|
|
8905
|
+
writeCount += 1;
|
|
8906
|
+
if (writeCount === 3) {
|
|
8907
|
+
return Promise.reject(new Error('transport timeout'));
|
|
8908
|
+
}
|
|
8909
|
+
return Promise.resolve({
|
|
8910
|
+
message: { processed_byte: offset + data.byteLength },
|
|
8911
|
+
});
|
|
8912
|
+
}
|
|
8913
|
+
);
|
|
8914
|
+
(method as any).getProtocolV2FirmwareChunkSize = jest.fn().mockReturnValue(1000);
|
|
8915
|
+
(method as any).recoverProtocolV2FileTransfer = jest.fn().mockResolvedValue(undefined);
|
|
8916
|
+
method.postProgressMessage = jest.fn();
|
|
8917
|
+
const source = await openFirmwareByteSource({
|
|
8918
|
+
binary: new Uint8Array(3000).buffer,
|
|
8919
|
+
});
|
|
8920
|
+
|
|
8921
|
+
try {
|
|
8922
|
+
await (method as any).protocolV2SourceUpdateProcess({
|
|
8923
|
+
source,
|
|
8924
|
+
filePath: 'vol1:firmware.bin',
|
|
8925
|
+
processedSize: 0,
|
|
8926
|
+
totalSize: 3000,
|
|
8927
|
+
});
|
|
8928
|
+
} finally {
|
|
8929
|
+
await source?.close();
|
|
8930
|
+
}
|
|
8931
|
+
|
|
8932
|
+
const transferredBytes = (method.postProgressMessage as jest.Mock).mock.calls.map(
|
|
8933
|
+
([, , metrics]) => metrics.transferredBytes
|
|
8934
|
+
);
|
|
8935
|
+
expect(transferredBytes).toEqual([1000, 2000, 3000]);
|
|
8936
|
+
expect((method as any).recoverProtocolV2FileTransfer).toHaveBeenCalledTimes(1);
|
|
8937
|
+
});
|
|
8938
|
+
|
|
8186
8939
|
test('throttles repeated transfer progress while preserving file completion', async () => {
|
|
8187
8940
|
const method = new FirmwareUpdateV4({
|
|
8188
8941
|
id: 1,
|
|
@@ -8541,6 +9294,42 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
8541
9294
|
expect(typedCall).toHaveBeenCalledTimes(6);
|
|
8542
9295
|
});
|
|
8543
9296
|
|
|
9297
|
+
test('preserves the underlying transport error code after firmware transfer retries', async () => {
|
|
9298
|
+
const method = new FirmwareUpdateV4({
|
|
9299
|
+
id: 1,
|
|
9300
|
+
payload: {
|
|
9301
|
+
method: 'firmwareUpdateV4',
|
|
9302
|
+
},
|
|
9303
|
+
});
|
|
9304
|
+
const typedCall = jest
|
|
9305
|
+
.fn()
|
|
9306
|
+
.mockRejectedValue(ERRORS.TypedError(HardwareErrorCode.BleTimeoutError, 'response timeout'));
|
|
9307
|
+
|
|
9308
|
+
(method as any).device = stubDevice({
|
|
9309
|
+
getCommands: () => ({ typedCall }),
|
|
9310
|
+
});
|
|
9311
|
+
method.postProgressMessage = jest.fn();
|
|
9312
|
+
method.postTipMessage = jest.fn();
|
|
9313
|
+
(method as any).recoverProtocolV2FileTransfer = jest.fn().mockResolvedValue(undefined);
|
|
9314
|
+
|
|
9315
|
+
const source = await openFirmwareByteSource({
|
|
9316
|
+
binary: new Uint8Array([1, 2, 3]).buffer,
|
|
9317
|
+
});
|
|
9318
|
+
await expect(
|
|
9319
|
+
(method as any).protocolV2SourceUpdateProcess({
|
|
9320
|
+
source,
|
|
9321
|
+
filePath: 'vol0:/firmware.bin',
|
|
9322
|
+
processedSize: 0,
|
|
9323
|
+
totalSize: 3,
|
|
9324
|
+
})
|
|
9325
|
+
).rejects.toMatchObject({
|
|
9326
|
+
errorCode: HardwareErrorCode.EmmcFileWriteFirmwareError,
|
|
9327
|
+
params: { causeCode: HardwareErrorCode.BleTimeoutError },
|
|
9328
|
+
});
|
|
9329
|
+
await source?.close();
|
|
9330
|
+
expect(typedCall).toHaveBeenCalledTimes(3);
|
|
9331
|
+
});
|
|
9332
|
+
|
|
8544
9333
|
// TODO(#850/#855): PR #855 added resume-on-retry and per-chunk retry on the
|
|
8545
9334
|
// writeProtocolV2File path. PR #850 replaced that path with FirmwareByteSource
|
|
8546
9335
|
// streaming (protocolV2SourceUpdateProcess), which restarts a failed transfer from
|
|
@@ -8689,26 +9478,28 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
8689
9478
|
expect(writePayloads.map(payload => payload.file.data.byteLength)).toEqual([1960, 1]);
|
|
8690
9479
|
});
|
|
8691
9480
|
|
|
8692
|
-
test('keeps
|
|
9481
|
+
test('keeps confirmation active after the Request write until terminal Success arrives', async () => {
|
|
8693
9482
|
const method = new FirmwareUpdateV4({
|
|
8694
9483
|
id: 1,
|
|
8695
9484
|
payload: {
|
|
8696
9485
|
method: 'firmwareUpdateV4',
|
|
8697
9486
|
},
|
|
8698
9487
|
});
|
|
8699
|
-
let
|
|
9488
|
+
let onResponseAfterWrite:
|
|
9489
|
+
| ((response: { type: string; message: Record<string, never> }) => void)
|
|
9490
|
+
| undefined;
|
|
8700
9491
|
const typedCall = jest.fn().mockResolvedValue({ type: 'Success', message: {} });
|
|
8701
|
-
const call = jest.fn().mockImplementation(
|
|
8702
|
-
|
|
8703
|
-
|
|
8704
|
-
|
|
8705
|
-
|
|
8706
|
-
);
|
|
9492
|
+
const call = jest.fn().mockImplementation((_type, _message, options) => {
|
|
9493
|
+
onResponseAfterWrite = options.onResponseAfterWrite;
|
|
9494
|
+
return Promise.resolve({ type: 'WriteCompleted', message: {} });
|
|
9495
|
+
});
|
|
9496
|
+
const clearCancelableAction = jest.fn();
|
|
8707
9497
|
|
|
8708
9498
|
(method as any).device = stubDevice({
|
|
8709
|
-
getCommands: () => ({ typedCall, call }),
|
|
9499
|
+
getCommands: () => ({ typedCall, call, cancelDevice: jest.fn() }),
|
|
8710
9500
|
createProtocolV2UiPhaseMetadata: jest.fn().mockReturnValue(undefined),
|
|
8711
9501
|
toMessageObject: jest.fn().mockReturnValue({ connectId: 'pro2' }),
|
|
9502
|
+
clearCancelableAction,
|
|
8712
9503
|
});
|
|
8713
9504
|
method.postMessage = jest.fn();
|
|
8714
9505
|
method.postProgressMessage = jest.fn();
|
|
@@ -8729,18 +9520,25 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
8729
9520
|
expect(call).toHaveBeenCalledWith(
|
|
8730
9521
|
'DeviceFirmwareUpdateRequest',
|
|
8731
9522
|
{},
|
|
8732
|
-
{
|
|
9523
|
+
expect.objectContaining({
|
|
9524
|
+
returnAfterWrite: true,
|
|
9525
|
+
expectedTypes: ['Success'],
|
|
9526
|
+
onResponseAfterWrite: expect.any(Function),
|
|
9527
|
+
})
|
|
8733
9528
|
);
|
|
8734
9529
|
expect(method.postTipMessage).not.toHaveBeenCalled();
|
|
8735
9530
|
expect(method.postProgressMessage).not.toHaveBeenCalled();
|
|
9531
|
+
expect(clearCancelableAction).not.toHaveBeenCalled();
|
|
8736
9532
|
|
|
8737
|
-
resolveWrite?.({ type: 'WriteCompleted', message: {} });
|
|
8738
9533
|
await startPromise;
|
|
9534
|
+
onResponseAfterWrite?.({ type: 'Success', message: {} });
|
|
9535
|
+
expect(clearCancelableAction).toHaveBeenCalledTimes(1);
|
|
9536
|
+
expect((method as any).protocolV2InstallTerminalSuccessObserved).toBe(true);
|
|
8739
9537
|
expect(method.postTipMessage).not.toHaveBeenCalled();
|
|
8740
9538
|
expect(method.postProgressMessage).not.toHaveBeenCalled();
|
|
8741
9539
|
});
|
|
8742
9540
|
|
|
8743
|
-
test('
|
|
9541
|
+
test('returns after the empty Protocol V2 firmware install request is written', async () => {
|
|
8744
9542
|
const method = new FirmwareUpdateV4({
|
|
8745
9543
|
id: 1,
|
|
8746
9544
|
payload: {
|
|
@@ -8751,10 +9549,13 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
8751
9549
|
type: 'Success',
|
|
8752
9550
|
message: { message: 'accepted' },
|
|
8753
9551
|
});
|
|
8754
|
-
const call = jest.fn().mockResolvedValue({
|
|
9552
|
+
const call = jest.fn().mockResolvedValue({
|
|
9553
|
+
type: 'WriteCompleted',
|
|
9554
|
+
message: {},
|
|
9555
|
+
});
|
|
8755
9556
|
|
|
8756
9557
|
(method as any).device = stubDevice({
|
|
8757
|
-
getCommands: () => ({ typedCall, call }),
|
|
9558
|
+
getCommands: () => ({ typedCall, call, cancelDevice: jest.fn() }),
|
|
8758
9559
|
createProtocolV2UiPhaseMetadata: jest.fn().mockReturnValue(undefined),
|
|
8759
9560
|
toMessageObject: jest.fn().mockReturnValue({ connectId: 'pro2' }),
|
|
8760
9561
|
});
|
|
@@ -8770,9 +9571,14 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
8770
9571
|
expect(call).toHaveBeenCalledWith(
|
|
8771
9572
|
'DeviceFirmwareUpdateRequest',
|
|
8772
9573
|
{},
|
|
8773
|
-
{
|
|
9574
|
+
expect.objectContaining({
|
|
9575
|
+
returnAfterWrite: true,
|
|
9576
|
+
expectedTypes: ['Success'],
|
|
9577
|
+
onResponseAfterWrite: expect.any(Function),
|
|
9578
|
+
})
|
|
8774
9579
|
);
|
|
8775
9580
|
expect(method.postTipMessage).not.toHaveBeenCalled();
|
|
9581
|
+
expect(method.postProgressMessage).not.toHaveBeenCalled();
|
|
8776
9582
|
});
|
|
8777
9583
|
});
|
|
8778
9584
|
|
|
@@ -8955,7 +9761,16 @@ describe('Protocol V2 firmware update method', () => {
|
|
|
8955
9761
|
test('passes firmware update status fields through to Protocol V2', async () => {
|
|
8956
9762
|
const typedCall = jest.fn().mockResolvedValue({
|
|
8957
9763
|
message: {
|
|
8958
|
-
records: [
|
|
9764
|
+
records: [
|
|
9765
|
+
{
|
|
9766
|
+
target_id: 4,
|
|
9767
|
+
status: 1,
|
|
9768
|
+
progress_percent: 42,
|
|
9769
|
+
phase_info: { phase: 1, progress_percent: 60 },
|
|
9770
|
+
payload_version: 1,
|
|
9771
|
+
path: 'vol1:application_p1.bin',
|
|
9772
|
+
},
|
|
9773
|
+
],
|
|
8959
9774
|
},
|
|
8960
9775
|
});
|
|
8961
9776
|
const method = new DeviceGetFirmwareUpdateStatus({
|
|
@@ -8964,6 +9779,8 @@ describe('Protocol V2 firmware update method', () => {
|
|
|
8964
9779
|
method: 'deviceGetFirmwareUpdateStatus',
|
|
8965
9780
|
fields: {
|
|
8966
9781
|
status: true,
|
|
9782
|
+
progress_percent: true,
|
|
9783
|
+
phase_info: true,
|
|
8967
9784
|
payload_version: true,
|
|
8968
9785
|
path: true,
|
|
8969
9786
|
},
|
|
@@ -8975,7 +9792,16 @@ describe('Protocol V2 firmware update method', () => {
|
|
|
8975
9792
|
});
|
|
8976
9793
|
|
|
8977
9794
|
await expect(method.run()).resolves.toEqual({
|
|
8978
|
-
records: [
|
|
9795
|
+
records: [
|
|
9796
|
+
{
|
|
9797
|
+
target_id: 4,
|
|
9798
|
+
status: 1,
|
|
9799
|
+
progress_percent: 42,
|
|
9800
|
+
phase_info: { phase: 1, progress_percent: 60 },
|
|
9801
|
+
payload_version: 1,
|
|
9802
|
+
path: 'vol1:application_p1.bin',
|
|
9803
|
+
},
|
|
9804
|
+
],
|
|
8979
9805
|
});
|
|
8980
9806
|
expect(typedCall).toHaveBeenCalledWith(
|
|
8981
9807
|
'DeviceFirmwareUpdateStatusGet',
|
|
@@ -8983,6 +9809,8 @@ describe('Protocol V2 firmware update method', () => {
|
|
|
8983
9809
|
{
|
|
8984
9810
|
fields: {
|
|
8985
9811
|
status: true,
|
|
9812
|
+
progress_percent: true,
|
|
9813
|
+
phase_info: true,
|
|
8986
9814
|
payload_version: true,
|
|
8987
9815
|
path: true,
|
|
8988
9816
|
},
|