@onekeyfe/hd-core 1.2.2-alpha.100 → 1.2.2-alpha.102
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 +102 -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 +937 -74
- package/__tests__/protocolV2FileWrite.test.ts +40 -0
- package/__tests__/sol-sign-offchain-message.test.ts +64 -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/UploadPortfolio.d.ts +1 -0
- package/dist/api/UploadPortfolio.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/api/solana/SolSignOffchainMessage.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 +4 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/uiPromiseRegistry.d.ts +1 -1
- package/dist/device/Device.d.ts +1 -1
- package/dist/device/Device.d.ts.map +1 -1
- package/dist/device/DeviceCommands.d.ts +4 -4
- 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 +38 -16
- package/dist/index.js +2569 -1555
- 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/api/protocolV2.d.ts +3 -4
- package/dist/types/api/protocolV2.d.ts.map +1 -1
- package/dist/types/api/solSignOffchainMessage.d.ts +1 -0
- package/dist/types/api/solSignOffchainMessage.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/UploadPortfolio.ts +5 -4
- 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/api/solana/SolSignOffchainMessage.ts +44 -4
- package/src/core/RequestQueue.ts +16 -1
- package/src/core/index.ts +179 -27
- package/src/data/messages/messages-protocol-v2.json +1591 -1382
- package/src/data/messages/messages.json +8 -5
- 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/api/protocolV2.ts +3 -4
- package/src/types/api/solSignOffchainMessage.ts +2 -0
- 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: {} };
|
|
@@ -444,6 +536,41 @@ describe('UploadPortfolio', () => {
|
|
|
444
536
|
});
|
|
445
537
|
});
|
|
446
538
|
|
|
539
|
+
test('emits transfer progress when progress UI is requested', async () => {
|
|
540
|
+
const typedCall = jest
|
|
541
|
+
.fn()
|
|
542
|
+
.mockResolvedValueOnce({ message: { processed_byte: 3 } })
|
|
543
|
+
.mockResolvedValueOnce({ message: { message: 'Portfolio updated' } });
|
|
544
|
+
const method = new UploadPortfolio({
|
|
545
|
+
id: 1,
|
|
546
|
+
payload: {
|
|
547
|
+
method: 'uploadPortfolio',
|
|
548
|
+
packageBase64: 'AQID',
|
|
549
|
+
uiMode: 'progress',
|
|
550
|
+
},
|
|
551
|
+
});
|
|
552
|
+
(method as any).device = stubPortfolioDevice({ commands: { typedCall } });
|
|
553
|
+
method.postMessage = jest.fn();
|
|
554
|
+
|
|
555
|
+
method.init();
|
|
556
|
+
await method.run();
|
|
557
|
+
|
|
558
|
+
expect(method.unlockPolicy).toBe('none');
|
|
559
|
+
expect(method.protocolV2UiMode).toBe('auto');
|
|
560
|
+
expect(method.protocolV2UiInteraction).toBeUndefined();
|
|
561
|
+
expect(method.payload.emitProgress).toBe(true);
|
|
562
|
+
expect(method.postMessage).toHaveBeenCalledWith({
|
|
563
|
+
event: 'UI_EVENT',
|
|
564
|
+
type: UI_REQUEST.DEVICE_PROGRESS,
|
|
565
|
+
payload: expect.objectContaining({
|
|
566
|
+
progress: 100,
|
|
567
|
+
transferredBytes: 3,
|
|
568
|
+
totalBytes: 3,
|
|
569
|
+
elapsedMs: expect.any(Number),
|
|
570
|
+
}),
|
|
571
|
+
});
|
|
572
|
+
});
|
|
573
|
+
|
|
447
574
|
test('does not apply PortfolioUpdate when staging fails', async () => {
|
|
448
575
|
const typedCall = jest.fn().mockRejectedValue(new Error('write failed'));
|
|
449
576
|
const method = new UploadPortfolio({
|
|
@@ -1022,6 +1149,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1022
1149
|
newSession: 'session-1',
|
|
1023
1150
|
unlockedAttachPin: false,
|
|
1024
1151
|
resumed: false,
|
|
1152
|
+
walletStatusRefreshed: true,
|
|
1025
1153
|
});
|
|
1026
1154
|
expect(typedCall).toHaveBeenCalledWith('ProtocolInfoRequest', 'ProtocolInfo', {
|
|
1027
1155
|
eventless_wallet_session: true,
|
|
@@ -1032,10 +1160,9 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1032
1160
|
expect(typedCall).toHaveBeenCalledWith('DeviceSessionAskPassphrase', 'Success', {
|
|
1033
1161
|
passphrase: '',
|
|
1034
1162
|
on_device: false,
|
|
1163
|
+
seed_domains: [DeviceSessionSeedDomain.SeedDomain_Standard],
|
|
1035
1164
|
});
|
|
1036
|
-
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
1037
|
-
seed_domains: [],
|
|
1038
|
-
});
|
|
1165
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {});
|
|
1039
1166
|
device.passphraseState = 'state-1';
|
|
1040
1167
|
expect(device.getInternalState()).toBe('session-1');
|
|
1041
1168
|
});
|
|
@@ -1088,13 +1215,10 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1088
1215
|
expect(
|
|
1089
1216
|
typedCall.mock.calls.filter(call => call[0] === 'DeviceSessionAskPassphrase')
|
|
1090
1217
|
).toHaveLength(1);
|
|
1091
|
-
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
1092
|
-
seed_domains: [],
|
|
1093
|
-
});
|
|
1218
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {});
|
|
1094
1219
|
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
1095
1220
|
session_id: 'standard-session',
|
|
1096
1221
|
btc_test_address: 'standard-state',
|
|
1097
|
-
seed_domains: [],
|
|
1098
1222
|
});
|
|
1099
1223
|
expect(deviceWalletSessionStore.getStandard(deviceId)).toEqual({
|
|
1100
1224
|
passphraseState: 'standard-state',
|
|
@@ -1285,6 +1409,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1285
1409
|
newSession: 'main-wallet-session',
|
|
1286
1410
|
unlockedAttachPin: undefined,
|
|
1287
1411
|
resumed: false,
|
|
1412
|
+
walletStatusRefreshed: true,
|
|
1288
1413
|
});
|
|
1289
1414
|
|
|
1290
1415
|
expect(promptPassphrase).toHaveBeenCalled();
|
|
@@ -1371,6 +1496,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1371
1496
|
newSession: 'hidden-after-unlock-session',
|
|
1372
1497
|
unlockedAttachPin: undefined,
|
|
1373
1498
|
resumed: false,
|
|
1499
|
+
walletStatusRefreshed: true,
|
|
1374
1500
|
});
|
|
1375
1501
|
|
|
1376
1502
|
expect(unlockDevice).not.toHaveBeenCalled();
|
|
@@ -1457,10 +1583,9 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1457
1583
|
expect(typedCall).toHaveBeenCalledWith('DeviceSessionAskPassphrase', 'Success', {
|
|
1458
1584
|
passphrase: 'host hidden wallet',
|
|
1459
1585
|
on_device: false,
|
|
1586
|
+
seed_domains: [DeviceSessionSeedDomain.SeedDomain_Standard],
|
|
1460
1587
|
});
|
|
1461
|
-
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
1462
|
-
seed_domains: [],
|
|
1463
|
-
});
|
|
1588
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {});
|
|
1464
1589
|
});
|
|
1465
1590
|
|
|
1466
1591
|
test('deviceStatusGet returns raw DeviceStatus and updates dynamic features', async () => {
|
|
@@ -1569,7 +1694,6 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1569
1694
|
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
1570
1695
|
session_id: 'session-a',
|
|
1571
1696
|
btc_test_address: 'state-a',
|
|
1572
|
-
seed_domains: [],
|
|
1573
1697
|
});
|
|
1574
1698
|
expect(device.getInternalState()).toBe('session-b');
|
|
1575
1699
|
});
|
|
@@ -1643,7 +1767,6 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1643
1767
|
{
|
|
1644
1768
|
session_id: 'session-a',
|
|
1645
1769
|
btc_test_address: 'state-a',
|
|
1646
|
-
seed_domains: [],
|
|
1647
1770
|
},
|
|
1648
1771
|
],
|
|
1649
1772
|
]);
|
|
@@ -1690,10 +1813,14 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1690
1813
|
[
|
|
1691
1814
|
'DeviceSessionAskPassphrase',
|
|
1692
1815
|
'Success',
|
|
1693
|
-
{
|
|
1816
|
+
{
|
|
1817
|
+
passphrase: 'host hidden wallet',
|
|
1818
|
+
on_device: false,
|
|
1819
|
+
seed_domains: [DeviceSessionSeedDomain.SeedDomain_Standard],
|
|
1820
|
+
},
|
|
1694
1821
|
],
|
|
1695
1822
|
['DeviceStatusGet', 'DeviceStatus', {}],
|
|
1696
|
-
['DeviceSessionGet', 'DeviceSession', {
|
|
1823
|
+
['DeviceSessionGet', 'DeviceSession', {}],
|
|
1697
1824
|
]);
|
|
1698
1825
|
});
|
|
1699
1826
|
|
|
@@ -1855,7 +1982,6 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1855
1982
|
expect(promptPassphrase).not.toHaveBeenCalled();
|
|
1856
1983
|
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
1857
1984
|
btc_test_address: 'expected-state',
|
|
1858
|
-
seed_domains: [],
|
|
1859
1985
|
});
|
|
1860
1986
|
expect(typedCall).toHaveBeenCalledWith('LockDevice', 'Success', {});
|
|
1861
1987
|
expect(typedCall.mock.calls.filter(call => call[0] === 'DeviceSessionGet')).toHaveLength(1);
|
|
@@ -2153,7 +2279,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2153
2279
|
firmwareVersion: '4.15.0',
|
|
2154
2280
|
passphraseProtection: true,
|
|
2155
2281
|
sessionId: 'feature-session',
|
|
2156
|
-
unlockedAttachPin:
|
|
2282
|
+
unlockedAttachPin: false,
|
|
2157
2283
|
};
|
|
2158
2284
|
const typedCall = jest
|
|
2159
2285
|
.fn()
|
|
@@ -2246,7 +2372,6 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2246
2372
|
).resolves.toMatchObject({ passphraseState: 'expected-state' });
|
|
2247
2373
|
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
2248
2374
|
btc_test_address: 'expected-state',
|
|
2249
|
-
seed_domains: [],
|
|
2250
2375
|
});
|
|
2251
2376
|
expect(promptPassphrase).not.toHaveBeenCalled();
|
|
2252
2377
|
});
|
|
@@ -2320,7 +2445,6 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2320
2445
|
{
|
|
2321
2446
|
session_id: 'session-pro2-app',
|
|
2322
2447
|
btc_test_address: 'state-pro2-app',
|
|
2323
|
-
seed_domains: [],
|
|
2324
2448
|
},
|
|
2325
2449
|
],
|
|
2326
2450
|
]);
|
|
@@ -2510,10 +2634,9 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2510
2634
|
expect(typedCall).toHaveBeenNthCalledWith(2, 'DeviceSessionAskPassphrase', 'Success', {
|
|
2511
2635
|
passphrase: 'host hidden wallet',
|
|
2512
2636
|
on_device: false,
|
|
2637
|
+
seed_domains: [DeviceSessionSeedDomain.SeedDomain_Standard],
|
|
2513
2638
|
});
|
|
2514
|
-
expect(typedCall).toHaveBeenLastCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
2515
|
-
seed_domains: [],
|
|
2516
|
-
});
|
|
2639
|
+
expect(typedCall).toHaveBeenLastCalledWith('DeviceSessionGet', 'DeviceSession', {});
|
|
2517
2640
|
});
|
|
2518
2641
|
|
|
2519
2642
|
test('does not mark Pro2 passphrase enabled from a main PIN session alone', async () => {
|
|
@@ -2593,16 +2716,14 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2593
2716
|
expect(device.getInternalState()).toBeUndefined();
|
|
2594
2717
|
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
2595
2718
|
btc_test_address: 'expected-state',
|
|
2596
|
-
seed_domains: [],
|
|
2597
2719
|
});
|
|
2598
2720
|
expect(typedCall).toHaveBeenCalledWith('DeviceSessionAskPassphrase', 'Success', {
|
|
2599
2721
|
passphrase: 'host hidden wallet',
|
|
2600
2722
|
on_device: false,
|
|
2723
|
+
seed_domains: [DeviceSessionSeedDomain.SeedDomain_Standard],
|
|
2601
2724
|
});
|
|
2602
2725
|
expect(typedCall).toHaveBeenCalledWith('DeviceStatusGet', 'DeviceStatus', {});
|
|
2603
|
-
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
2604
|
-
seed_domains: [],
|
|
2605
|
-
});
|
|
2726
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {});
|
|
2606
2727
|
});
|
|
2607
2728
|
|
|
2608
2729
|
test('fails closed instead of switching to Main PIN during a standard-wallet safety check', async () => {
|
|
@@ -2700,20 +2821,19 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2700
2821
|
await expect(
|
|
2701
2822
|
device.checkPassphraseStateSafety('stale-hidden-state', true, false)
|
|
2702
2823
|
).resolves.toBe(true);
|
|
2703
|
-
expect(typedCall).toHaveBeenCalledTimes(
|
|
2824
|
+
expect(typedCall).toHaveBeenCalledTimes(6);
|
|
2704
2825
|
expect(typedCall).toHaveBeenCalledWith('ProtocolInfoRequest', 'ProtocolInfo', {
|
|
2705
2826
|
eventless_wallet_session: true,
|
|
2706
2827
|
});
|
|
2707
|
-
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
2708
|
-
seed_domains: [],
|
|
2709
|
-
});
|
|
2828
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {});
|
|
2710
2829
|
expect(typedCall).toHaveBeenCalledWith('DeviceSessionAskPassphrase', 'Success', {
|
|
2711
2830
|
passphrase: '',
|
|
2712
2831
|
on_device: false,
|
|
2832
|
+
seed_domains: [DeviceSessionSeedDomain.SeedDomain_Standard],
|
|
2713
2833
|
});
|
|
2714
2834
|
expect(typedCall).toHaveBeenCalledWith('DeviceStatusGet', 'DeviceStatus', {});
|
|
2715
2835
|
expect(typedCall.mock.calls.filter(([request]) => request === 'DeviceStatusGet')).toHaveLength(
|
|
2716
|
-
|
|
2836
|
+
3
|
|
2717
2837
|
);
|
|
2718
2838
|
expect(typedCall).not.toHaveBeenCalledWith('DeviceSessionAskPin', 'Success', expect.anything());
|
|
2719
2839
|
});
|
|
@@ -4820,6 +4940,23 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4820
4940
|
expect(typedCall.mock.calls.map(call => call[0])).toEqual(['DeviceInfoGet']);
|
|
4821
4941
|
});
|
|
4822
4942
|
|
|
4943
|
+
test('stops V4 bootloader reconnect polling on a stale BLE bond', async () => {
|
|
4944
|
+
const method = new FirmwareUpdateV4({
|
|
4945
|
+
id: 1,
|
|
4946
|
+
payload: {
|
|
4947
|
+
method: 'firmwareUpdateV4',
|
|
4948
|
+
},
|
|
4949
|
+
});
|
|
4950
|
+
const staleBondError = ERRORS.TypedError(HardwareErrorCode.BlePeerRemovedPairingInformation);
|
|
4951
|
+
const reconnectProtocolV2Device = jest.fn().mockRejectedValue(staleBondError);
|
|
4952
|
+
(method as any).reconnectProtocolV2Device = reconnectProtocolV2Device;
|
|
4953
|
+
|
|
4954
|
+
await expect((method as any).waitForProtocolV2BootloaderMode(60_000, 0)).rejects.toBe(
|
|
4955
|
+
staleBondError
|
|
4956
|
+
);
|
|
4957
|
+
expect(reconnectProtocolV2Device).toHaveBeenCalledTimes(1);
|
|
4958
|
+
});
|
|
4959
|
+
|
|
4823
4960
|
test('polls again when Protocol V2 bootloader serial is temporarily unavailable', async () => {
|
|
4824
4961
|
const method = new FirmwareUpdateV4({
|
|
4825
4962
|
id: 1,
|
|
@@ -4901,6 +5038,87 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4901
5038
|
expect(initialize).not.toHaveBeenCalled();
|
|
4902
5039
|
});
|
|
4903
5040
|
|
|
5041
|
+
test('reacquires the same WebUSB device without a protocol probe during install recovery', async () => {
|
|
5042
|
+
const method = new FirmwareUpdateV4({
|
|
5043
|
+
id: 1,
|
|
5044
|
+
payload: {
|
|
5045
|
+
method: 'firmwareUpdateV4',
|
|
5046
|
+
},
|
|
5047
|
+
});
|
|
5048
|
+
const acquire = jest.fn().mockResolvedValue('usb-path');
|
|
5049
|
+
const enumerate = jest.fn().mockResolvedValue({
|
|
5050
|
+
descriptors: [
|
|
5051
|
+
{ id: 'other-usb', path: 'other-usb', protocolType: 'V2' },
|
|
5052
|
+
{ id: 'usb-id', path: 'usb-path', protocolType: 'V2' },
|
|
5053
|
+
],
|
|
5054
|
+
});
|
|
5055
|
+
const commands = { disposed: true, mainId: '' };
|
|
5056
|
+
const device = stubDevice({
|
|
5057
|
+
originalDescriptor: { id: 'usb-id', path: 'usb-path', protocolType: 'V2' },
|
|
5058
|
+
deviceConnector: { acquire, enumerate },
|
|
5059
|
+
updateDescriptor: jest.fn(),
|
|
5060
|
+
acquire: jest.fn(),
|
|
5061
|
+
commands,
|
|
5062
|
+
getCommands: () => commands,
|
|
5063
|
+
mainId: 'old-usb-path',
|
|
5064
|
+
});
|
|
5065
|
+
const getDevices = jest.spyOn(DevicePool, 'getDevices');
|
|
5066
|
+
const isBrowserWebUsb = jest.spyOn(DataManager, 'isBrowserWebUsb').mockReturnValue(false);
|
|
5067
|
+
const isDesktopWebUsb = jest.spyOn(DataManager, 'isDesktopWebUsb').mockReturnValue(true);
|
|
5068
|
+
(method as any).device = device;
|
|
5069
|
+
(method as any).protocolV2ExpectedPath = 'usb-path';
|
|
5070
|
+
|
|
5071
|
+
try {
|
|
5072
|
+
await (method as any).reconnectProtocolV2Device({ skipProtocolProbe: true });
|
|
5073
|
+
} finally {
|
|
5074
|
+
getDevices.mockRestore();
|
|
5075
|
+
isBrowserWebUsb.mockRestore();
|
|
5076
|
+
isDesktopWebUsb.mockRestore();
|
|
5077
|
+
}
|
|
5078
|
+
|
|
5079
|
+
expect(getDevices).not.toHaveBeenCalled();
|
|
5080
|
+
expect(acquire).toHaveBeenCalledWith(
|
|
5081
|
+
'usb-path',
|
|
5082
|
+
null,
|
|
5083
|
+
undefined,
|
|
5084
|
+
'V2',
|
|
5085
|
+
undefined,
|
|
5086
|
+
undefined,
|
|
5087
|
+
true
|
|
5088
|
+
);
|
|
5089
|
+
expect(device.acquire).not.toHaveBeenCalled();
|
|
5090
|
+
expect(commands.disposed).toBe(false);
|
|
5091
|
+
expect(commands.mainId).toBe('usb-path');
|
|
5092
|
+
});
|
|
5093
|
+
|
|
5094
|
+
test('reacquires the same BLE peripheral by id without scanning during install recovery', async () => {
|
|
5095
|
+
const method = new FirmwareUpdateV4({
|
|
5096
|
+
id: 1,
|
|
5097
|
+
payload: {
|
|
5098
|
+
method: 'firmwareUpdateV4',
|
|
5099
|
+
},
|
|
5100
|
+
});
|
|
5101
|
+
const acquire = jest.fn().mockResolvedValue({ uuid: 'ble-id', protocolType: 'V2' });
|
|
5102
|
+
const enumerate = jest.fn().mockResolvedValue({
|
|
5103
|
+
descriptors: [{ id: 'ble-id', name: 'OneKey Pro 2', protocolType: 'V2' }],
|
|
5104
|
+
});
|
|
5105
|
+
const commands = { disposed: true, mainId: '' };
|
|
5106
|
+
(method as any).isBleReconnect = jest.fn(() => true);
|
|
5107
|
+
(method as any).device = stubDevice({
|
|
5108
|
+
originalDescriptor: { id: 'ble-id', path: 'ble-path', protocolType: 'V2' },
|
|
5109
|
+
deviceConnector: { acquire, enumerate },
|
|
5110
|
+
commands,
|
|
5111
|
+
getCommands: () => commands,
|
|
5112
|
+
});
|
|
5113
|
+
|
|
5114
|
+
await (method as any).reconnectProtocolV2Device({ skipProtocolProbe: true });
|
|
5115
|
+
|
|
5116
|
+
expect(enumerate).not.toHaveBeenCalled();
|
|
5117
|
+
expect(acquire).toHaveBeenCalledWith('ble-id', null, true, 'V2', undefined, undefined, true);
|
|
5118
|
+
expect(commands.disposed).toBe(false);
|
|
5119
|
+
expect(commands.mainId).toBe('ble-id');
|
|
5120
|
+
});
|
|
5121
|
+
|
|
4904
5122
|
test('selects the matching physical device from multiple Protocol V2 USB reconnect candidates', async () => {
|
|
4905
5123
|
const method = new FirmwareUpdateV4({
|
|
4906
5124
|
id: 1,
|
|
@@ -5290,6 +5508,108 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5290
5508
|
});
|
|
5291
5509
|
});
|
|
5292
5510
|
|
|
5511
|
+
test('treats a typed BLE disconnect during Protocol V2 normal reboot as expected', async () => {
|
|
5512
|
+
const method = new FirmwareUpdateV4({
|
|
5513
|
+
id: 1,
|
|
5514
|
+
payload: {
|
|
5515
|
+
method: 'firmwareUpdateV4',
|
|
5516
|
+
},
|
|
5517
|
+
});
|
|
5518
|
+
const typedCall = jest
|
|
5519
|
+
.fn()
|
|
5520
|
+
.mockRejectedValue(ERRORS.TypedError(HardwareErrorCode.BleDeviceDisconnected));
|
|
5521
|
+
|
|
5522
|
+
(method as any).device = stubDevice({
|
|
5523
|
+
getCommands: () => ({ typedCall }),
|
|
5524
|
+
});
|
|
5525
|
+
|
|
5526
|
+
await expect((method as any).protocolV2Reboot(DeviceRebootType.Normal)).resolves.toEqual({
|
|
5527
|
+
message: 'Device rebooted successfully',
|
|
5528
|
+
});
|
|
5529
|
+
expect((method as any).device.markProtocolV2Reboot).toHaveBeenCalledWith(
|
|
5530
|
+
DeviceRebootType.Normal
|
|
5531
|
+
);
|
|
5532
|
+
});
|
|
5533
|
+
|
|
5534
|
+
test('uses a short timeout and continues after a BLE reboot response timeout', async () => {
|
|
5535
|
+
const method = new FirmwareUpdateV4({
|
|
5536
|
+
id: 1,
|
|
5537
|
+
payload: {
|
|
5538
|
+
method: 'firmwareUpdateV4',
|
|
5539
|
+
},
|
|
5540
|
+
});
|
|
5541
|
+
const typedCall = jest
|
|
5542
|
+
.fn()
|
|
5543
|
+
.mockRejectedValue(
|
|
5544
|
+
ERRORS.TypedError(
|
|
5545
|
+
HardwareErrorCode.BleTimeoutError,
|
|
5546
|
+
'Lowlevel response timeout after 5000ms for DeviceReboot'
|
|
5547
|
+
)
|
|
5548
|
+
);
|
|
5549
|
+
|
|
5550
|
+
(method as any).device = stubDevice({
|
|
5551
|
+
getCommands: () => ({ typedCall }),
|
|
5552
|
+
});
|
|
5553
|
+
|
|
5554
|
+
await expect((method as any).protocolV2Reboot(DeviceRebootType.Normal)).resolves.toEqual({
|
|
5555
|
+
message: 'Device rebooted successfully',
|
|
5556
|
+
});
|
|
5557
|
+
expect(typedCall).toHaveBeenCalledWith(
|
|
5558
|
+
'DeviceReboot',
|
|
5559
|
+
'Success',
|
|
5560
|
+
{ reboot_type: DeviceRebootType.Normal },
|
|
5561
|
+
{ timeoutMs: 5000 }
|
|
5562
|
+
);
|
|
5563
|
+
expect((method as any).device.markProtocolV2Reboot).toHaveBeenCalledWith(
|
|
5564
|
+
DeviceRebootType.Normal
|
|
5565
|
+
);
|
|
5566
|
+
});
|
|
5567
|
+
|
|
5568
|
+
test('continues after a USB Protocol V2 reboot response timeout', async () => {
|
|
5569
|
+
const method = new FirmwareUpdateV4({
|
|
5570
|
+
id: 1,
|
|
5571
|
+
payload: {
|
|
5572
|
+
method: 'firmwareUpdateV4',
|
|
5573
|
+
},
|
|
5574
|
+
});
|
|
5575
|
+
const timeoutError = Object.assign(
|
|
5576
|
+
new Error('Protocol V2 response timeout after 5000ms for DeviceReboot'),
|
|
5577
|
+
{ code: 'response-timeout' }
|
|
5578
|
+
);
|
|
5579
|
+
const typedCall = jest.fn().mockRejectedValue(timeoutError);
|
|
5580
|
+
|
|
5581
|
+
(method as any).device = stubDevice({
|
|
5582
|
+
getCommands: () => ({ typedCall }),
|
|
5583
|
+
});
|
|
5584
|
+
|
|
5585
|
+
await expect((method as any).protocolV2Reboot(DeviceRebootType.Normal)).resolves.toEqual({
|
|
5586
|
+
message: 'Device rebooted successfully',
|
|
5587
|
+
});
|
|
5588
|
+
expect((method as any).device.markProtocolV2Reboot).toHaveBeenCalledWith(
|
|
5589
|
+
DeviceRebootType.Normal
|
|
5590
|
+
);
|
|
5591
|
+
});
|
|
5592
|
+
|
|
5593
|
+
test('propagates non-transport errors from a Protocol V2 reboot', async () => {
|
|
5594
|
+
const method = new FirmwareUpdateV4({
|
|
5595
|
+
id: 1,
|
|
5596
|
+
payload: {
|
|
5597
|
+
method: 'firmwareUpdateV4',
|
|
5598
|
+
},
|
|
5599
|
+
});
|
|
5600
|
+
const rebootError = new Error('Device rejected reboot');
|
|
5601
|
+
const typedCall = jest.fn().mockRejectedValue(rebootError);
|
|
5602
|
+
|
|
5603
|
+
(method as any).device = stubDevice({
|
|
5604
|
+
getCommands: () => ({ typedCall }),
|
|
5605
|
+
});
|
|
5606
|
+
|
|
5607
|
+
await expect((method as any).protocolV2Reboot(DeviceRebootType.Normal)).rejects.toBe(
|
|
5608
|
+
rebootError
|
|
5609
|
+
);
|
|
5610
|
+
expect((method as any).device.markProtocolV2Reboot).not.toHaveBeenCalled();
|
|
5611
|
+
});
|
|
5612
|
+
|
|
5293
5613
|
test('treats Android BLE transport release during Protocol V2 reboot as expected', async () => {
|
|
5294
5614
|
const method = new FirmwareUpdateV4({
|
|
5295
5615
|
id: 1,
|
|
@@ -5365,7 +5685,11 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5365
5685
|
expect(call).toHaveBeenCalledWith(
|
|
5366
5686
|
'DeviceFirmwareUpdateRequest',
|
|
5367
5687
|
{},
|
|
5368
|
-
{
|
|
5688
|
+
expect.objectContaining({
|
|
5689
|
+
returnAfterWrite: true,
|
|
5690
|
+
expectedTypes: ['Success'],
|
|
5691
|
+
onResponseAfterWrite: expect.any(Function),
|
|
5692
|
+
})
|
|
5369
5693
|
);
|
|
5370
5694
|
expect(typedCall.mock.invocationCallOrder[0]).toBeLessThan(call.mock.invocationCallOrder[0]);
|
|
5371
5695
|
expect((method.postMessage as jest.Mock).mock.invocationCallOrder[0]).toBeLessThan(
|
|
@@ -5374,7 +5698,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5374
5698
|
expect(method.postTipMessage).not.toHaveBeenCalled();
|
|
5375
5699
|
expect(method.postProgressMessage).not.toHaveBeenCalled();
|
|
5376
5700
|
await cancelableAction?.();
|
|
5377
|
-
expect(cancelDevice).
|
|
5701
|
+
expect(cancelDevice).not.toHaveBeenCalled();
|
|
5378
5702
|
});
|
|
5379
5703
|
|
|
5380
5704
|
test('does not send the install request when Protocol V2 staging fails', async () => {
|
|
@@ -5406,7 +5730,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5406
5730
|
expect(method.postProgressMessage).not.toHaveBeenCalled();
|
|
5407
5731
|
});
|
|
5408
5732
|
|
|
5409
|
-
test('does not enter install state when
|
|
5733
|
+
test('does not enter install state when the empty request fails', async () => {
|
|
5410
5734
|
const method = new FirmwareUpdateV4({
|
|
5411
5735
|
id: 1,
|
|
5412
5736
|
payload: {
|
|
@@ -5414,7 +5738,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5414
5738
|
},
|
|
5415
5739
|
});
|
|
5416
5740
|
const typedCall = jest.fn().mockResolvedValue({ type: 'Success', message: {} });
|
|
5417
|
-
const call = jest.fn().mockRejectedValue(new Error('
|
|
5741
|
+
const call = jest.fn().mockRejectedValue(new Error('request failed'));
|
|
5418
5742
|
|
|
5419
5743
|
(method as any).device = stubDevice({
|
|
5420
5744
|
getCommands: () => ({ typedCall, call }),
|
|
@@ -5429,13 +5753,118 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5429
5753
|
(method as any).protocolV2StartFirmwareUpdate({
|
|
5430
5754
|
targets: [{ target_id: 4, path: 'vol0:/application_p1.bin' }],
|
|
5431
5755
|
})
|
|
5432
|
-
).rejects.toThrow('
|
|
5756
|
+
).rejects.toThrow('request failed');
|
|
5433
5757
|
|
|
5434
|
-
expect(call).
|
|
5758
|
+
expect(call).toHaveBeenCalledWith(
|
|
5759
|
+
'DeviceFirmwareUpdateRequest',
|
|
5760
|
+
{},
|
|
5761
|
+
expect.objectContaining({
|
|
5762
|
+
returnAfterWrite: true,
|
|
5763
|
+
expectedTypes: ['Success'],
|
|
5764
|
+
onResponseAfterWrite: expect.any(Function),
|
|
5765
|
+
})
|
|
5766
|
+
);
|
|
5435
5767
|
expect(method.postTipMessage).not.toHaveBeenCalled();
|
|
5436
5768
|
expect(method.postProgressMessage).not.toHaveBeenCalled();
|
|
5437
5769
|
});
|
|
5438
5770
|
|
|
5771
|
+
test('continues to install polling when the empty request releases BLE transport', async () => {
|
|
5772
|
+
const method = new FirmwareUpdateV4({
|
|
5773
|
+
id: 1,
|
|
5774
|
+
payload: {
|
|
5775
|
+
method: 'firmwareUpdateV4',
|
|
5776
|
+
},
|
|
5777
|
+
});
|
|
5778
|
+
const typedCall = jest.fn().mockResolvedValue({ type: 'Success', message: {} });
|
|
5779
|
+
const call = jest.fn().mockRejectedValue(new Error('React Native BLE transport released'));
|
|
5780
|
+
|
|
5781
|
+
(method as any).device = stubDevice({
|
|
5782
|
+
getCommands: () => ({ typedCall, call }),
|
|
5783
|
+
createProtocolV2UiPhaseMetadata: jest.fn().mockReturnValue(undefined),
|
|
5784
|
+
toMessageObject: jest.fn().mockReturnValue({ connectId: 'pro2' }),
|
|
5785
|
+
});
|
|
5786
|
+
(method as any).isBleReconnect = jest.fn(() => true);
|
|
5787
|
+
method.postMessage = jest.fn();
|
|
5788
|
+
|
|
5789
|
+
await expect(
|
|
5790
|
+
(method as any).protocolV2StartFirmwareUpdate({
|
|
5791
|
+
targets: [{ target_id: 4, path: 'vol0:/application_p1.bin' }],
|
|
5792
|
+
})
|
|
5793
|
+
).resolves.toBeUndefined();
|
|
5794
|
+
|
|
5795
|
+
expect(call).toHaveBeenCalledWith(
|
|
5796
|
+
'DeviceFirmwareUpdateRequest',
|
|
5797
|
+
{},
|
|
5798
|
+
expect.objectContaining({
|
|
5799
|
+
returnAfterWrite: true,
|
|
5800
|
+
expectedTypes: ['Success'],
|
|
5801
|
+
onResponseAfterWrite: expect.any(Function),
|
|
5802
|
+
})
|
|
5803
|
+
);
|
|
5804
|
+
expect(typedCall).not.toHaveBeenCalledWith('DeviceFirmwareUpdateRequest', 'Success', {});
|
|
5805
|
+
expect((method as any).protocolV2InstallNeedsReconnect).toBe(true);
|
|
5806
|
+
expect((method as any).protocolV2InstallDisconnectObserved).toBe(true);
|
|
5807
|
+
});
|
|
5808
|
+
|
|
5809
|
+
test.each([
|
|
5810
|
+
{ name: 'bootloader', targetId: 3, path: 'vol0:/bootloader.bin' },
|
|
5811
|
+
{ name: 'application', targetId: 4, path: 'vol0:/application_p1.bin' },
|
|
5812
|
+
{ name: 'secure element', targetId: 7, path: 'vol0:/se01.bin' },
|
|
5813
|
+
])(
|
|
5814
|
+
'reconnects a released BLE install link before polling $name status without DeviceInfo',
|
|
5815
|
+
async ({ targetId, path }) => {
|
|
5816
|
+
const method = new FirmwareUpdateV4({
|
|
5817
|
+
id: 1,
|
|
5818
|
+
payload: {
|
|
5819
|
+
method: 'firmwareUpdateV4',
|
|
5820
|
+
},
|
|
5821
|
+
});
|
|
5822
|
+
const targets = [{ target_id: targetId, path }];
|
|
5823
|
+
const typedCall = jest
|
|
5824
|
+
.fn()
|
|
5825
|
+
.mockResolvedValueOnce({
|
|
5826
|
+
type: 'DeviceFirmwareUpdateStatus',
|
|
5827
|
+
message: { records: [{ ...targets[0], status: 1, payload_version: 0 }] },
|
|
5828
|
+
})
|
|
5829
|
+
.mockResolvedValueOnce({
|
|
5830
|
+
type: 'DeviceFirmwareUpdateStatus',
|
|
5831
|
+
message: { records: [{ ...targets[0], status: 2, payload_version: 0x010000 }] },
|
|
5832
|
+
});
|
|
5833
|
+
const reconnectProtocolV2Device = jest.fn().mockResolvedValue(undefined);
|
|
5834
|
+
const verifyProtocolV2ReconnectIdentity = jest.fn();
|
|
5835
|
+
const setTimeoutSpy = jest.spyOn(global, 'setTimeout').mockImplementation(((
|
|
5836
|
+
callback: () => void
|
|
5837
|
+
) => {
|
|
5838
|
+
callback();
|
|
5839
|
+
return 0 as any;
|
|
5840
|
+
}) as typeof setTimeout);
|
|
5841
|
+
|
|
5842
|
+
(method as any).device = stubDevice({
|
|
5843
|
+
getCommands: () => ({ typedCall }),
|
|
5844
|
+
});
|
|
5845
|
+
(method as any).isBleReconnect = jest.fn(() => true);
|
|
5846
|
+
(method as any).protocolV2InstallNeedsReconnect = true;
|
|
5847
|
+
(method as any).reconnectProtocolV2Device = reconnectProtocolV2Device;
|
|
5848
|
+
(method as any).verifyProtocolV2ReconnectIdentity = verifyProtocolV2ReconnectIdentity;
|
|
5849
|
+
method.postProgressMessage = jest.fn();
|
|
5850
|
+
|
|
5851
|
+
try {
|
|
5852
|
+
await expect(
|
|
5853
|
+
(method as any).waitForProtocolV2FirmwareUpdateComplete(targets, true)
|
|
5854
|
+
).resolves.toBeUndefined();
|
|
5855
|
+
} finally {
|
|
5856
|
+
setTimeoutSpy.mockRestore();
|
|
5857
|
+
}
|
|
5858
|
+
|
|
5859
|
+
expect(reconnectProtocolV2Device).toHaveBeenCalledWith({ skipProtocolProbe: true });
|
|
5860
|
+
expect(verifyProtocolV2ReconnectIdentity).not.toHaveBeenCalled();
|
|
5861
|
+
expect(typedCall.mock.calls.map(callArgs => callArgs[0])).toEqual([
|
|
5862
|
+
'DeviceFirmwareUpdateStatusGet',
|
|
5863
|
+
'DeviceFirmwareUpdateStatusGet',
|
|
5864
|
+
]);
|
|
5865
|
+
}
|
|
5866
|
+
);
|
|
5867
|
+
|
|
5439
5868
|
test('polls only firmware status while Protocol V2 bootloader is installing', async () => {
|
|
5440
5869
|
const method = new FirmwareUpdateV4({
|
|
5441
5870
|
id: 1,
|
|
@@ -5479,7 +5908,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5479
5908
|
expect(method.postProgressMessage).toHaveBeenCalledWith(100, 'installingFirmware');
|
|
5480
5909
|
});
|
|
5481
5910
|
|
|
5482
|
-
test('keeps polling after
|
|
5911
|
+
test('keeps polling after an empty status response until the target finishes', async () => {
|
|
5483
5912
|
const method = new FirmwareUpdateV4({
|
|
5484
5913
|
id: 1,
|
|
5485
5914
|
payload: {
|
|
@@ -5488,7 +5917,10 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5488
5917
|
});
|
|
5489
5918
|
const typedCall = jest
|
|
5490
5919
|
.fn()
|
|
5491
|
-
.mockResolvedValueOnce({
|
|
5920
|
+
.mockResolvedValueOnce({
|
|
5921
|
+
type: 'DeviceFirmwareUpdateStatus',
|
|
5922
|
+
message: { records: [] },
|
|
5923
|
+
})
|
|
5492
5924
|
.mockResolvedValueOnce({
|
|
5493
5925
|
type: 'DeviceFirmwareUpdateStatus',
|
|
5494
5926
|
message: { records: [] },
|
|
@@ -5630,6 +6062,38 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5630
6062
|
expect(method.postProgressMessage).toHaveBeenCalledWith(100, 'installingFirmware');
|
|
5631
6063
|
});
|
|
5632
6064
|
|
|
6065
|
+
test('does not count a stale finished target when only another target was live', () => {
|
|
6066
|
+
const method = new FirmwareUpdateV4({
|
|
6067
|
+
id: 1,
|
|
6068
|
+
payload: {
|
|
6069
|
+
method: 'firmwareUpdateV4',
|
|
6070
|
+
},
|
|
6071
|
+
});
|
|
6072
|
+
const assertProtocolV2TargetStatus = (method as any).assertProtocolV2TargetStatus.bind(method);
|
|
6073
|
+
const expectedTargetIds = new Set([3, 4]);
|
|
6074
|
+
const expectedPaths = new Map([
|
|
6075
|
+
[3, 'vol0:/bootloader.bin'],
|
|
6076
|
+
[4, 'vol0:/application_p1.bin'],
|
|
6077
|
+
]);
|
|
6078
|
+
const liveTargetIds = new Set([4]);
|
|
6079
|
+
method.postProgressMessage = jest.fn();
|
|
6080
|
+
|
|
6081
|
+
const completed = assertProtocolV2TargetStatus(
|
|
6082
|
+
[
|
|
6083
|
+
{ target_id: 3, status: 2, path: 'vol0:/bootloader.bin' },
|
|
6084
|
+
{ target_id: 4, status: 2, path: 'vol0:/application_p1.bin' },
|
|
6085
|
+
],
|
|
6086
|
+
expectedTargetIds,
|
|
6087
|
+
expectedPaths,
|
|
6088
|
+
liveTargetIds
|
|
6089
|
+
);
|
|
6090
|
+
|
|
6091
|
+
expect(completed).toBe(false);
|
|
6092
|
+
expect((method as any).protocolV2CompletedTargetIds).toEqual(new Set([4]));
|
|
6093
|
+
expect(method.postProgressMessage).toHaveBeenCalledWith(50, 'installingFirmware');
|
|
6094
|
+
expect(method.postProgressMessage).not.toHaveBeenCalledWith(100, 'installingFirmware');
|
|
6095
|
+
});
|
|
6096
|
+
|
|
5633
6097
|
test('ignores stale failed records until the current install starts', async () => {
|
|
5634
6098
|
const method = new FirmwareUpdateV4({
|
|
5635
6099
|
id: 1,
|
|
@@ -5678,6 +6142,141 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5678
6142
|
expect(typedCall).toHaveBeenCalledTimes(3);
|
|
5679
6143
|
});
|
|
5680
6144
|
|
|
6145
|
+
test('ignores leftover failed records while another leftover target is still pending', async () => {
|
|
6146
|
+
const method = new FirmwareUpdateV4({
|
|
6147
|
+
id: 1,
|
|
6148
|
+
payload: {
|
|
6149
|
+
method: 'firmwareUpdateV4',
|
|
6150
|
+
},
|
|
6151
|
+
});
|
|
6152
|
+
const typedCall = jest
|
|
6153
|
+
.fn()
|
|
6154
|
+
.mockResolvedValueOnce({
|
|
6155
|
+
type: 'DeviceFirmwareUpdateStatus',
|
|
6156
|
+
message: {
|
|
6157
|
+
records: [
|
|
6158
|
+
{ target_id: 3, status: 0, path: 'vol0:/bootloader.bin' },
|
|
6159
|
+
{ target_id: 4, status: 3, path: 'vol0:/application_p1.bin' },
|
|
6160
|
+
],
|
|
6161
|
+
},
|
|
6162
|
+
})
|
|
6163
|
+
.mockResolvedValueOnce({
|
|
6164
|
+
type: 'DeviceFirmwareUpdateStatus',
|
|
6165
|
+
message: {
|
|
6166
|
+
records: [
|
|
6167
|
+
{ target_id: 3, status: 0, path: 'vol0:/bootloader.bin' },
|
|
6168
|
+
{ target_id: 4, status: 0, path: 'vol0:/application_p1.bin' },
|
|
6169
|
+
],
|
|
6170
|
+
},
|
|
6171
|
+
})
|
|
6172
|
+
.mockResolvedValueOnce({
|
|
6173
|
+
type: 'DeviceFirmwareUpdateStatus',
|
|
6174
|
+
message: {
|
|
6175
|
+
records: [
|
|
6176
|
+
{ target_id: 3, status: 2, path: 'vol0:/bootloader.bin' },
|
|
6177
|
+
{ target_id: 4, status: 2, path: 'vol0:/application_p1.bin' },
|
|
6178
|
+
],
|
|
6179
|
+
},
|
|
6180
|
+
});
|
|
6181
|
+
const setTimeoutSpy = jest.spyOn(global, 'setTimeout').mockImplementation(((
|
|
6182
|
+
callback: () => void
|
|
6183
|
+
) => {
|
|
6184
|
+
callback();
|
|
6185
|
+
return 0 as any;
|
|
6186
|
+
}) as typeof setTimeout);
|
|
6187
|
+
|
|
6188
|
+
(method as any).device = stubDevice({
|
|
6189
|
+
getCommands: () => ({ typedCall }),
|
|
6190
|
+
});
|
|
6191
|
+
(method as any).reconnectProtocolV2Device = jest.fn().mockResolvedValue(undefined);
|
|
6192
|
+
(method as any).verifyProtocolV2ReconnectIdentity = jest.fn().mockResolvedValue(undefined);
|
|
6193
|
+
method.postProgressMessage = jest.fn();
|
|
6194
|
+
|
|
6195
|
+
try {
|
|
6196
|
+
await (method as any).waitForProtocolV2FirmwareUpdateComplete(
|
|
6197
|
+
[
|
|
6198
|
+
{ target_id: 3, path: 'vol0:/bootloader.bin' },
|
|
6199
|
+
{ target_id: 4, path: 'vol0:/application_p1.bin' },
|
|
6200
|
+
],
|
|
6201
|
+
true
|
|
6202
|
+
);
|
|
6203
|
+
} finally {
|
|
6204
|
+
setTimeoutSpy.mockRestore();
|
|
6205
|
+
}
|
|
6206
|
+
|
|
6207
|
+
expect(typedCall).toHaveBeenCalledTimes(3);
|
|
6208
|
+
expect(method.postProgressMessage).toHaveBeenCalledWith(100, 'installingFirmware');
|
|
6209
|
+
});
|
|
6210
|
+
|
|
6211
|
+
test('reports failed only after that target was live in the current install', async () => {
|
|
6212
|
+
const method = new FirmwareUpdateV4({
|
|
6213
|
+
id: 1,
|
|
6214
|
+
payload: {
|
|
6215
|
+
method: 'firmwareUpdateV4',
|
|
6216
|
+
},
|
|
6217
|
+
});
|
|
6218
|
+
const typedCall = jest
|
|
6219
|
+
.fn()
|
|
6220
|
+
.mockResolvedValueOnce({
|
|
6221
|
+
type: 'DeviceFirmwareUpdateStatus',
|
|
6222
|
+
message: {
|
|
6223
|
+
records: [
|
|
6224
|
+
{ target_id: 3, status: 0, path: 'vol0:/bootloader.bin' },
|
|
6225
|
+
{ target_id: 4, status: 3, path: 'vol0:/application_p1.bin' },
|
|
6226
|
+
],
|
|
6227
|
+
},
|
|
6228
|
+
})
|
|
6229
|
+
.mockResolvedValueOnce({
|
|
6230
|
+
type: 'DeviceFirmwareUpdateStatus',
|
|
6231
|
+
message: {
|
|
6232
|
+
records: [
|
|
6233
|
+
{ target_id: 3, status: 0, path: 'vol0:/bootloader.bin' },
|
|
6234
|
+
{ target_id: 4, status: 0, path: 'vol0:/application_p1.bin' },
|
|
6235
|
+
],
|
|
6236
|
+
},
|
|
6237
|
+
})
|
|
6238
|
+
.mockResolvedValueOnce({
|
|
6239
|
+
type: 'DeviceFirmwareUpdateStatus',
|
|
6240
|
+
message: {
|
|
6241
|
+
records: [
|
|
6242
|
+
{ target_id: 3, status: 0, path: 'vol0:/bootloader.bin' },
|
|
6243
|
+
{ target_id: 4, status: 3, path: 'vol0:/application_p1.bin' },
|
|
6244
|
+
],
|
|
6245
|
+
},
|
|
6246
|
+
});
|
|
6247
|
+
const setTimeoutSpy = jest.spyOn(global, 'setTimeout').mockImplementation(((
|
|
6248
|
+
callback: () => void
|
|
6249
|
+
) => {
|
|
6250
|
+
callback();
|
|
6251
|
+
return 0 as any;
|
|
6252
|
+
}) as typeof setTimeout);
|
|
6253
|
+
|
|
6254
|
+
(method as any).device = stubDevice({
|
|
6255
|
+
getCommands: () => ({ typedCall }),
|
|
6256
|
+
});
|
|
6257
|
+
(method as any).reconnectProtocolV2Device = jest.fn().mockResolvedValue(undefined);
|
|
6258
|
+
(method as any).verifyProtocolV2ReconnectIdentity = jest.fn().mockResolvedValue(undefined);
|
|
6259
|
+
|
|
6260
|
+
try {
|
|
6261
|
+
await expect(
|
|
6262
|
+
(method as any).waitForProtocolV2FirmwareUpdateComplete(
|
|
6263
|
+
[
|
|
6264
|
+
{ target_id: 3, path: 'vol0:/bootloader.bin' },
|
|
6265
|
+
{ target_id: 4, path: 'vol0:/application_p1.bin' },
|
|
6266
|
+
],
|
|
6267
|
+
true
|
|
6268
|
+
)
|
|
6269
|
+
).rejects.toMatchObject({
|
|
6270
|
+
errorCode: HardwareErrorCode.FirmwareError,
|
|
6271
|
+
message: 'Protocol V2 firmware install failed',
|
|
6272
|
+
});
|
|
6273
|
+
} finally {
|
|
6274
|
+
setTimeoutSpy.mockRestore();
|
|
6275
|
+
}
|
|
6276
|
+
|
|
6277
|
+
expect(typedCall).toHaveBeenCalledTimes(3);
|
|
6278
|
+
});
|
|
6279
|
+
|
|
5681
6280
|
test('waits five minutes before rejecting normal mode without install evidence', async () => {
|
|
5682
6281
|
const method = new FirmwareUpdateV4({
|
|
5683
6282
|
id: 1,
|
|
@@ -5784,7 +6383,6 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5784
6383
|
});
|
|
5785
6384
|
const typedCall = jest
|
|
5786
6385
|
.fn()
|
|
5787
|
-
.mockResolvedValueOnce({ type: 'Success', message: {} })
|
|
5788
6386
|
.mockRejectedValueOnce(new Error('Pro2 is rebooting'))
|
|
5789
6387
|
.mockResolvedValueOnce({
|
|
5790
6388
|
type: 'DeviceFirmwareUpdateStatus',
|
|
@@ -5801,6 +6399,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5801
6399
|
(method as any).device = stubDevice({
|
|
5802
6400
|
getCommands: () => ({ typedCall }),
|
|
5803
6401
|
});
|
|
6402
|
+
(method as any).protocolV2InstallTerminalSuccessObserved = true;
|
|
5804
6403
|
(method as any).reconnectProtocolV2Device = jest.fn().mockResolvedValue(undefined);
|
|
5805
6404
|
(method as any).verifyProtocolV2ReconnectIdentity = jest.fn().mockResolvedValue(deviceInfo);
|
|
5806
6405
|
(method as any).probeProtocolV2NormalMode = jest.fn().mockResolvedValue(true);
|
|
@@ -5821,6 +6420,54 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5821
6420
|
expect(method.postProgressMessage).toHaveBeenCalledWith(100, 'installingFirmware');
|
|
5822
6421
|
});
|
|
5823
6422
|
|
|
6423
|
+
test('uses the confirmed install request as evidence after the same device returns', async () => {
|
|
6424
|
+
const method = new FirmwareUpdateV4({
|
|
6425
|
+
id: 1,
|
|
6426
|
+
payload: {
|
|
6427
|
+
method: 'firmwareUpdateV4',
|
|
6428
|
+
},
|
|
6429
|
+
});
|
|
6430
|
+
const typedCall = jest
|
|
6431
|
+
.fn()
|
|
6432
|
+
.mockRejectedValueOnce(new Error('The USB device was disconnected'))
|
|
6433
|
+
.mockResolvedValueOnce({
|
|
6434
|
+
type: 'DeviceFirmwareUpdateStatus',
|
|
6435
|
+
message: { records: [] },
|
|
6436
|
+
});
|
|
6437
|
+
const deviceInfo = { hw: { serial_no: 'PRO2-PHYSICAL-1' } };
|
|
6438
|
+
const setTimeoutSpy = jest.spyOn(global, 'setTimeout').mockImplementation(((
|
|
6439
|
+
callback: () => void
|
|
6440
|
+
) => {
|
|
6441
|
+
callback();
|
|
6442
|
+
return 0 as any;
|
|
6443
|
+
}) as typeof setTimeout);
|
|
6444
|
+
|
|
6445
|
+
(method as any).device = stubDevice({
|
|
6446
|
+
getCommands: () => ({ typedCall }),
|
|
6447
|
+
});
|
|
6448
|
+
(method as any).protocolV2InstallTerminalSuccessObserved = true;
|
|
6449
|
+
(method as any).reconnectProtocolV2Device = jest.fn().mockResolvedValue(undefined);
|
|
6450
|
+
(method as any).verifyProtocolV2ReconnectIdentity = jest.fn().mockResolvedValue(deviceInfo);
|
|
6451
|
+
(method as any).probeProtocolV2NormalMode = jest.fn().mockResolvedValue(true);
|
|
6452
|
+
method.postProgressMessage = jest.fn();
|
|
6453
|
+
|
|
6454
|
+
try {
|
|
6455
|
+
await expect(
|
|
6456
|
+
(method as any).waitForProtocolV2FirmwareUpdateComplete(
|
|
6457
|
+
[{ target_id: 4, path: 'vol0:/application_p1.bin' }],
|
|
6458
|
+
true
|
|
6459
|
+
)
|
|
6460
|
+
).resolves.toBeUndefined();
|
|
6461
|
+
} finally {
|
|
6462
|
+
setTimeoutSpy.mockRestore();
|
|
6463
|
+
}
|
|
6464
|
+
|
|
6465
|
+
expect((method as any).reconnectProtocolV2Device).toHaveBeenCalledWith({
|
|
6466
|
+
skipProtocolProbe: true,
|
|
6467
|
+
});
|
|
6468
|
+
expect(method.postProgressMessage).toHaveBeenLastCalledWith(100, 'installingFirmware');
|
|
6469
|
+
});
|
|
6470
|
+
|
|
5824
6471
|
test.each([
|
|
5825
6472
|
[{ mode: 'normal', bootloaderMode: false }, true],
|
|
5826
6473
|
[{ mode: 'notInitialized', bootloaderMode: false }, true],
|
|
@@ -6183,7 +6830,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
6183
6830
|
expect(reconnectProtocolV2Device).not.toHaveBeenCalled();
|
|
6184
6831
|
expect(typedCall).toHaveBeenCalledWith(
|
|
6185
6832
|
'DeviceFirmwareUpdateStatusGet',
|
|
6186
|
-
|
|
6833
|
+
'DeviceFirmwareUpdateStatus',
|
|
6187
6834
|
expect.anything(),
|
|
6188
6835
|
expect.anything()
|
|
6189
6836
|
);
|
|
@@ -6465,10 +7112,12 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
6465
7112
|
});
|
|
6466
7113
|
expect(typedCall).toHaveBeenCalledWith(
|
|
6467
7114
|
'DeviceFirmwareUpdateStatusGet',
|
|
6468
|
-
|
|
7115
|
+
'DeviceFirmwareUpdateStatus',
|
|
6469
7116
|
{
|
|
6470
7117
|
fields: {
|
|
6471
7118
|
status: true,
|
|
7119
|
+
progress_percent: true,
|
|
7120
|
+
phase_info: true,
|
|
6472
7121
|
payload_version: true,
|
|
6473
7122
|
path: true,
|
|
6474
7123
|
},
|
|
@@ -6570,7 +7219,14 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
6570
7219
|
true
|
|
6571
7220
|
);
|
|
6572
7221
|
expect((method as any).exitProtocolV2BootloaderToNormal).not.toHaveBeenCalled();
|
|
6573
|
-
expect(method.postProgressMessage).toHaveBeenCalledWith(
|
|
7222
|
+
expect(method.postProgressMessage).toHaveBeenCalledWith(
|
|
7223
|
+
100,
|
|
7224
|
+
'transferData',
|
|
7225
|
+
expect.objectContaining({
|
|
7226
|
+
transferredBytes: 5,
|
|
7227
|
+
totalBytes: 5,
|
|
7228
|
+
})
|
|
7229
|
+
);
|
|
6574
7230
|
expect((method as any).completeProtocolV2FinalVerification).toHaveBeenCalledTimes(1);
|
|
6575
7231
|
});
|
|
6576
7232
|
|
|
@@ -6644,7 +7300,14 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
6644
7300
|
expect.objectContaining({ processedSize: 2, totalSize: 3 })
|
|
6645
7301
|
);
|
|
6646
7302
|
expect(method.postProgressMessage).toHaveBeenCalledTimes(1);
|
|
6647
|
-
expect(method.postProgressMessage).toHaveBeenCalledWith(
|
|
7303
|
+
expect(method.postProgressMessage).toHaveBeenCalledWith(
|
|
7304
|
+
100,
|
|
7305
|
+
'transferData',
|
|
7306
|
+
expect.objectContaining({
|
|
7307
|
+
transferredBytes: 3,
|
|
7308
|
+
totalBytes: 3,
|
|
7309
|
+
})
|
|
7310
|
+
);
|
|
6648
7311
|
expect((method as any).protocolV2StartFirmwareUpdate).toHaveBeenCalledTimes(1);
|
|
6649
7312
|
expect((method as any).protocolV2StartFirmwareUpdate).toHaveBeenCalledWith({
|
|
6650
7313
|
targets: [{ target_id: 4, path: 'vol0:/application_p1.bin' }],
|
|
@@ -7964,26 +8627,43 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
7964
8627
|
(method as any).verifyProtocolV2StagedFile = jest.fn().mockResolvedValue(undefined);
|
|
7965
8628
|
(method as any).protocolV2StartFirmwareUpdate = jest.fn();
|
|
7966
8629
|
(method as any).waitForProtocolV2FirmwareUpdateComplete = jest.fn();
|
|
8630
|
+
const dateNowSpy = jest
|
|
8631
|
+
.spyOn(Date, 'now')
|
|
8632
|
+
.mockReturnValueOnce(1_000)
|
|
8633
|
+
.mockReturnValueOnce(5_000);
|
|
7967
8634
|
|
|
7968
|
-
|
|
7969
|
-
|
|
7970
|
-
|
|
7971
|
-
|
|
7972
|
-
|
|
7973
|
-
|
|
7974
|
-
|
|
7975
|
-
|
|
7976
|
-
|
|
8635
|
+
try {
|
|
8636
|
+
await (method as any).executeProtocolV2SourceUpdate({
|
|
8637
|
+
installSources: [],
|
|
8638
|
+
resourceSources: [
|
|
8639
|
+
{
|
|
8640
|
+
name: 'images.okpkg',
|
|
8641
|
+
source: {
|
|
8642
|
+
size: 3,
|
|
8643
|
+
readAt: jest.fn(),
|
|
8644
|
+
close: jest.fn(),
|
|
8645
|
+
},
|
|
8646
|
+
devicePath: 'vol0:/bundles/images/images.okpkg',
|
|
7977
8647
|
},
|
|
7978
|
-
|
|
7979
|
-
|
|
7980
|
-
|
|
7981
|
-
|
|
8648
|
+
],
|
|
8649
|
+
});
|
|
8650
|
+
} finally {
|
|
8651
|
+
dateNowSpy.mockRestore();
|
|
8652
|
+
}
|
|
7982
8653
|
|
|
7983
8654
|
expect((method as any).protocolV2SourceUpdateProcess).toHaveBeenCalledTimes(1);
|
|
7984
8655
|
expect((method as any).protocolV2SourceUpdateProcess).toHaveBeenCalledWith(
|
|
7985
|
-
expect.objectContaining({
|
|
8656
|
+
expect.objectContaining({
|
|
8657
|
+
filePath: 'vol0:/bundles/images/images.okpkg',
|
|
8658
|
+
transferStartedAt: 1_000,
|
|
8659
|
+
})
|
|
7986
8660
|
);
|
|
8661
|
+
expect(method.postProgressMessage).toHaveBeenLastCalledWith(100, 'transferData', {
|
|
8662
|
+
transferredBytes: 3,
|
|
8663
|
+
totalBytes: 3,
|
|
8664
|
+
rateBytesPerSecond: 1,
|
|
8665
|
+
elapsedMs: 4_000,
|
|
8666
|
+
});
|
|
7987
8667
|
expect((method as any).verifyProtocolV2StagedFile).toHaveBeenCalledWith(
|
|
7988
8668
|
'vol0:/bundles/images/images.okpkg',
|
|
7989
8669
|
3
|
|
@@ -8183,6 +8863,114 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
8183
8863
|
});
|
|
8184
8864
|
});
|
|
8185
8865
|
|
|
8866
|
+
test('does not retry or wrap a stale BLE bond during a V4 file transfer', async () => {
|
|
8867
|
+
const method = new FirmwareUpdateV4({
|
|
8868
|
+
id: 1,
|
|
8869
|
+
payload: {
|
|
8870
|
+
method: 'firmwareUpdateV4',
|
|
8871
|
+
},
|
|
8872
|
+
});
|
|
8873
|
+
const staleBondError = ERRORS.TypedError(HardwareErrorCode.BlePeerRemovedPairingInformation);
|
|
8874
|
+
(method as any).fileWriteChunk = jest.fn().mockRejectedValue(staleBondError);
|
|
8875
|
+
const recoverProtocolV2FileTransfer = jest.fn();
|
|
8876
|
+
(method as any).recoverProtocolV2FileTransfer = recoverProtocolV2FileTransfer;
|
|
8877
|
+
const source = await openFirmwareByteSource({
|
|
8878
|
+
binary: new Uint8Array([1]).buffer,
|
|
8879
|
+
});
|
|
8880
|
+
|
|
8881
|
+
try {
|
|
8882
|
+
await expect(
|
|
8883
|
+
(method as any).protocolV2SourceUpdateProcess({
|
|
8884
|
+
source,
|
|
8885
|
+
filePath: 'vol1:firmware.bin',
|
|
8886
|
+
processedSize: 0,
|
|
8887
|
+
totalSize: 1,
|
|
8888
|
+
})
|
|
8889
|
+
).rejects.toBe(staleBondError);
|
|
8890
|
+
} finally {
|
|
8891
|
+
await source?.close();
|
|
8892
|
+
}
|
|
8893
|
+
expect(recoverProtocolV2FileTransfer).not.toHaveBeenCalled();
|
|
8894
|
+
});
|
|
8895
|
+
|
|
8896
|
+
test('does not recover or wrap a cancelled V4 file transfer', async () => {
|
|
8897
|
+
const method = new FirmwareUpdateV4({
|
|
8898
|
+
id: 1,
|
|
8899
|
+
payload: {
|
|
8900
|
+
method: 'firmwareUpdateV4',
|
|
8901
|
+
},
|
|
8902
|
+
});
|
|
8903
|
+
const abortController = new AbortController();
|
|
8904
|
+
method.abortSignal = abortController.signal;
|
|
8905
|
+
(method as any).fileWriteChunk = jest.fn().mockImplementation(() => {
|
|
8906
|
+
abortController.abort();
|
|
8907
|
+
return Promise.reject(new Error('transport disposed'));
|
|
8908
|
+
});
|
|
8909
|
+
const recoverProtocolV2FileTransfer = jest.fn();
|
|
8910
|
+
(method as any).recoverProtocolV2FileTransfer = recoverProtocolV2FileTransfer;
|
|
8911
|
+
const source = await openFirmwareByteSource({
|
|
8912
|
+
binary: new Uint8Array([1]).buffer,
|
|
8913
|
+
});
|
|
8914
|
+
|
|
8915
|
+
try {
|
|
8916
|
+
await expect(
|
|
8917
|
+
(method as any).protocolV2SourceUpdateProcess({
|
|
8918
|
+
source,
|
|
8919
|
+
filePath: 'vol1:firmware.bin',
|
|
8920
|
+
processedSize: 0,
|
|
8921
|
+
totalSize: 1,
|
|
8922
|
+
})
|
|
8923
|
+
).rejects.toMatchObject({ errorCode: HardwareErrorCode.CallQueueActionCancelled });
|
|
8924
|
+
} finally {
|
|
8925
|
+
await source?.close();
|
|
8926
|
+
}
|
|
8927
|
+
expect(recoverProtocolV2FileTransfer).not.toHaveBeenCalled();
|
|
8928
|
+
});
|
|
8929
|
+
|
|
8930
|
+
test('keeps public transfer bytes monotonic when a V4 file retry restarts at zero', async () => {
|
|
8931
|
+
const method = new FirmwareUpdateV4({
|
|
8932
|
+
id: 1,
|
|
8933
|
+
payload: {
|
|
8934
|
+
method: 'firmwareUpdateV4',
|
|
8935
|
+
},
|
|
8936
|
+
});
|
|
8937
|
+
let writeCount = 0;
|
|
8938
|
+
(method as any).fileWriteChunk = jest.fn(
|
|
8939
|
+
(_path: string, _size: number, offset: number, data: Uint8Array) => {
|
|
8940
|
+
writeCount += 1;
|
|
8941
|
+
if (writeCount === 3) {
|
|
8942
|
+
return Promise.reject(new Error('transport timeout'));
|
|
8943
|
+
}
|
|
8944
|
+
return Promise.resolve({
|
|
8945
|
+
message: { processed_byte: offset + data.byteLength },
|
|
8946
|
+
});
|
|
8947
|
+
}
|
|
8948
|
+
);
|
|
8949
|
+
(method as any).getProtocolV2FirmwareChunkSize = jest.fn().mockReturnValue(1000);
|
|
8950
|
+
(method as any).recoverProtocolV2FileTransfer = jest.fn().mockResolvedValue(undefined);
|
|
8951
|
+
method.postProgressMessage = jest.fn();
|
|
8952
|
+
const source = await openFirmwareByteSource({
|
|
8953
|
+
binary: new Uint8Array(3000).buffer,
|
|
8954
|
+
});
|
|
8955
|
+
|
|
8956
|
+
try {
|
|
8957
|
+
await (method as any).protocolV2SourceUpdateProcess({
|
|
8958
|
+
source,
|
|
8959
|
+
filePath: 'vol1:firmware.bin',
|
|
8960
|
+
processedSize: 0,
|
|
8961
|
+
totalSize: 3000,
|
|
8962
|
+
});
|
|
8963
|
+
} finally {
|
|
8964
|
+
await source?.close();
|
|
8965
|
+
}
|
|
8966
|
+
|
|
8967
|
+
const transferredBytes = (method.postProgressMessage as jest.Mock).mock.calls.map(
|
|
8968
|
+
([, , metrics]) => metrics.transferredBytes
|
|
8969
|
+
);
|
|
8970
|
+
expect(transferredBytes).toEqual([1000, 2000, 3000]);
|
|
8971
|
+
expect((method as any).recoverProtocolV2FileTransfer).toHaveBeenCalledTimes(1);
|
|
8972
|
+
});
|
|
8973
|
+
|
|
8186
8974
|
test('throttles repeated transfer progress while preserving file completion', async () => {
|
|
8187
8975
|
const method = new FirmwareUpdateV4({
|
|
8188
8976
|
id: 1,
|
|
@@ -8541,6 +9329,42 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
8541
9329
|
expect(typedCall).toHaveBeenCalledTimes(6);
|
|
8542
9330
|
});
|
|
8543
9331
|
|
|
9332
|
+
test('preserves the underlying transport error code after firmware transfer retries', async () => {
|
|
9333
|
+
const method = new FirmwareUpdateV4({
|
|
9334
|
+
id: 1,
|
|
9335
|
+
payload: {
|
|
9336
|
+
method: 'firmwareUpdateV4',
|
|
9337
|
+
},
|
|
9338
|
+
});
|
|
9339
|
+
const typedCall = jest
|
|
9340
|
+
.fn()
|
|
9341
|
+
.mockRejectedValue(ERRORS.TypedError(HardwareErrorCode.BleTimeoutError, 'response timeout'));
|
|
9342
|
+
|
|
9343
|
+
(method as any).device = stubDevice({
|
|
9344
|
+
getCommands: () => ({ typedCall }),
|
|
9345
|
+
});
|
|
9346
|
+
method.postProgressMessage = jest.fn();
|
|
9347
|
+
method.postTipMessage = jest.fn();
|
|
9348
|
+
(method as any).recoverProtocolV2FileTransfer = jest.fn().mockResolvedValue(undefined);
|
|
9349
|
+
|
|
9350
|
+
const source = await openFirmwareByteSource({
|
|
9351
|
+
binary: new Uint8Array([1, 2, 3]).buffer,
|
|
9352
|
+
});
|
|
9353
|
+
await expect(
|
|
9354
|
+
(method as any).protocolV2SourceUpdateProcess({
|
|
9355
|
+
source,
|
|
9356
|
+
filePath: 'vol0:/firmware.bin',
|
|
9357
|
+
processedSize: 0,
|
|
9358
|
+
totalSize: 3,
|
|
9359
|
+
})
|
|
9360
|
+
).rejects.toMatchObject({
|
|
9361
|
+
errorCode: HardwareErrorCode.EmmcFileWriteFirmwareError,
|
|
9362
|
+
params: { causeCode: HardwareErrorCode.BleTimeoutError },
|
|
9363
|
+
});
|
|
9364
|
+
await source?.close();
|
|
9365
|
+
expect(typedCall).toHaveBeenCalledTimes(3);
|
|
9366
|
+
});
|
|
9367
|
+
|
|
8544
9368
|
// TODO(#850/#855): PR #855 added resume-on-retry and per-chunk retry on the
|
|
8545
9369
|
// writeProtocolV2File path. PR #850 replaced that path with FirmwareByteSource
|
|
8546
9370
|
// streaming (protocolV2SourceUpdateProcess), which restarts a failed transfer from
|
|
@@ -8689,26 +9513,28 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
8689
9513
|
expect(writePayloads.map(payload => payload.file.data.byteLength)).toEqual([1960, 1]);
|
|
8690
9514
|
});
|
|
8691
9515
|
|
|
8692
|
-
test('keeps
|
|
9516
|
+
test('keeps confirmation active after the Request write until terminal Success arrives', async () => {
|
|
8693
9517
|
const method = new FirmwareUpdateV4({
|
|
8694
9518
|
id: 1,
|
|
8695
9519
|
payload: {
|
|
8696
9520
|
method: 'firmwareUpdateV4',
|
|
8697
9521
|
},
|
|
8698
9522
|
});
|
|
8699
|
-
let
|
|
9523
|
+
let onResponseAfterWrite:
|
|
9524
|
+
| ((response: { type: string; message: Record<string, never> }) => void)
|
|
9525
|
+
| undefined;
|
|
8700
9526
|
const typedCall = jest.fn().mockResolvedValue({ type: 'Success', message: {} });
|
|
8701
|
-
const call = jest.fn().mockImplementation(
|
|
8702
|
-
|
|
8703
|
-
|
|
8704
|
-
|
|
8705
|
-
|
|
8706
|
-
);
|
|
9527
|
+
const call = jest.fn().mockImplementation((_type, _message, options) => {
|
|
9528
|
+
onResponseAfterWrite = options.onResponseAfterWrite;
|
|
9529
|
+
return Promise.resolve({ type: 'WriteCompleted', message: {} });
|
|
9530
|
+
});
|
|
9531
|
+
const clearCancelableAction = jest.fn();
|
|
8707
9532
|
|
|
8708
9533
|
(method as any).device = stubDevice({
|
|
8709
|
-
getCommands: () => ({ typedCall, call }),
|
|
9534
|
+
getCommands: () => ({ typedCall, call, cancelDevice: jest.fn() }),
|
|
8710
9535
|
createProtocolV2UiPhaseMetadata: jest.fn().mockReturnValue(undefined),
|
|
8711
9536
|
toMessageObject: jest.fn().mockReturnValue({ connectId: 'pro2' }),
|
|
9537
|
+
clearCancelableAction,
|
|
8712
9538
|
});
|
|
8713
9539
|
method.postMessage = jest.fn();
|
|
8714
9540
|
method.postProgressMessage = jest.fn();
|
|
@@ -8729,18 +9555,25 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
8729
9555
|
expect(call).toHaveBeenCalledWith(
|
|
8730
9556
|
'DeviceFirmwareUpdateRequest',
|
|
8731
9557
|
{},
|
|
8732
|
-
{
|
|
9558
|
+
expect.objectContaining({
|
|
9559
|
+
returnAfterWrite: true,
|
|
9560
|
+
expectedTypes: ['Success'],
|
|
9561
|
+
onResponseAfterWrite: expect.any(Function),
|
|
9562
|
+
})
|
|
8733
9563
|
);
|
|
8734
9564
|
expect(method.postTipMessage).not.toHaveBeenCalled();
|
|
8735
9565
|
expect(method.postProgressMessage).not.toHaveBeenCalled();
|
|
9566
|
+
expect(clearCancelableAction).not.toHaveBeenCalled();
|
|
8736
9567
|
|
|
8737
|
-
resolveWrite?.({ type: 'WriteCompleted', message: {} });
|
|
8738
9568
|
await startPromise;
|
|
9569
|
+
onResponseAfterWrite?.({ type: 'Success', message: {} });
|
|
9570
|
+
expect(clearCancelableAction).toHaveBeenCalledTimes(1);
|
|
9571
|
+
expect((method as any).protocolV2InstallTerminalSuccessObserved).toBe(true);
|
|
8739
9572
|
expect(method.postTipMessage).not.toHaveBeenCalled();
|
|
8740
9573
|
expect(method.postProgressMessage).not.toHaveBeenCalled();
|
|
8741
9574
|
});
|
|
8742
9575
|
|
|
8743
|
-
test('
|
|
9576
|
+
test('returns after the empty Protocol V2 firmware install request is written', async () => {
|
|
8744
9577
|
const method = new FirmwareUpdateV4({
|
|
8745
9578
|
id: 1,
|
|
8746
9579
|
payload: {
|
|
@@ -8751,10 +9584,13 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
8751
9584
|
type: 'Success',
|
|
8752
9585
|
message: { message: 'accepted' },
|
|
8753
9586
|
});
|
|
8754
|
-
const call = jest.fn().mockResolvedValue({
|
|
9587
|
+
const call = jest.fn().mockResolvedValue({
|
|
9588
|
+
type: 'WriteCompleted',
|
|
9589
|
+
message: {},
|
|
9590
|
+
});
|
|
8755
9591
|
|
|
8756
9592
|
(method as any).device = stubDevice({
|
|
8757
|
-
getCommands: () => ({ typedCall, call }),
|
|
9593
|
+
getCommands: () => ({ typedCall, call, cancelDevice: jest.fn() }),
|
|
8758
9594
|
createProtocolV2UiPhaseMetadata: jest.fn().mockReturnValue(undefined),
|
|
8759
9595
|
toMessageObject: jest.fn().mockReturnValue({ connectId: 'pro2' }),
|
|
8760
9596
|
});
|
|
@@ -8770,9 +9606,14 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
8770
9606
|
expect(call).toHaveBeenCalledWith(
|
|
8771
9607
|
'DeviceFirmwareUpdateRequest',
|
|
8772
9608
|
{},
|
|
8773
|
-
{
|
|
9609
|
+
expect.objectContaining({
|
|
9610
|
+
returnAfterWrite: true,
|
|
9611
|
+
expectedTypes: ['Success'],
|
|
9612
|
+
onResponseAfterWrite: expect.any(Function),
|
|
9613
|
+
})
|
|
8774
9614
|
);
|
|
8775
9615
|
expect(method.postTipMessage).not.toHaveBeenCalled();
|
|
9616
|
+
expect(method.postProgressMessage).not.toHaveBeenCalled();
|
|
8776
9617
|
});
|
|
8777
9618
|
});
|
|
8778
9619
|
|
|
@@ -8955,7 +9796,16 @@ describe('Protocol V2 firmware update method', () => {
|
|
|
8955
9796
|
test('passes firmware update status fields through to Protocol V2', async () => {
|
|
8956
9797
|
const typedCall = jest.fn().mockResolvedValue({
|
|
8957
9798
|
message: {
|
|
8958
|
-
records: [
|
|
9799
|
+
records: [
|
|
9800
|
+
{
|
|
9801
|
+
target_id: 4,
|
|
9802
|
+
status: 1,
|
|
9803
|
+
progress_percent: 42,
|
|
9804
|
+
phase_info: { phase: 1, progress_percent: 60 },
|
|
9805
|
+
payload_version: 1,
|
|
9806
|
+
path: 'vol1:application_p1.bin',
|
|
9807
|
+
},
|
|
9808
|
+
],
|
|
8959
9809
|
},
|
|
8960
9810
|
});
|
|
8961
9811
|
const method = new DeviceGetFirmwareUpdateStatus({
|
|
@@ -8964,6 +9814,8 @@ describe('Protocol V2 firmware update method', () => {
|
|
|
8964
9814
|
method: 'deviceGetFirmwareUpdateStatus',
|
|
8965
9815
|
fields: {
|
|
8966
9816
|
status: true,
|
|
9817
|
+
progress_percent: true,
|
|
9818
|
+
phase_info: true,
|
|
8967
9819
|
payload_version: true,
|
|
8968
9820
|
path: true,
|
|
8969
9821
|
},
|
|
@@ -8975,7 +9827,16 @@ describe('Protocol V2 firmware update method', () => {
|
|
|
8975
9827
|
});
|
|
8976
9828
|
|
|
8977
9829
|
await expect(method.run()).resolves.toEqual({
|
|
8978
|
-
records: [
|
|
9830
|
+
records: [
|
|
9831
|
+
{
|
|
9832
|
+
target_id: 4,
|
|
9833
|
+
status: 1,
|
|
9834
|
+
progress_percent: 42,
|
|
9835
|
+
phase_info: { phase: 1, progress_percent: 60 },
|
|
9836
|
+
payload_version: 1,
|
|
9837
|
+
path: 'vol1:application_p1.bin',
|
|
9838
|
+
},
|
|
9839
|
+
],
|
|
8979
9840
|
});
|
|
8980
9841
|
expect(typedCall).toHaveBeenCalledWith(
|
|
8981
9842
|
'DeviceFirmwareUpdateStatusGet',
|
|
@@ -8983,6 +9844,8 @@ describe('Protocol V2 firmware update method', () => {
|
|
|
8983
9844
|
{
|
|
8984
9845
|
fields: {
|
|
8985
9846
|
status: true,
|
|
9847
|
+
progress_percent: true,
|
|
9848
|
+
phase_info: true,
|
|
8986
9849
|
payload_version: true,
|
|
8987
9850
|
path: true,
|
|
8988
9851
|
},
|