@onekeyfe/hd-core 1.2.0-alpha.7 → 1.2.0-alpha.9
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__/device-wallet-session-store.test.ts +131 -0
- package/__tests__/protocol-v2-firmware-targets.test.ts +19 -0
- package/__tests__/protocol-v2.test.ts +742 -536
- package/dist/api/ClearSessionCache.d.ts +9 -0
- package/dist/api/ClearSessionCache.d.ts.map +1 -0
- package/dist/api/FirmwareUpdateV4.d.ts +7 -6
- package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
- package/dist/api/firmware/getBinary.d.ts +1 -1
- package/dist/api/index.d.ts +3 -0
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/protocol-v2/DeviceSessionGet.d.ts +9 -0
- package/dist/api/protocol-v2/DeviceSessionGet.d.ts.map +1 -0
- package/dist/api/protocol-v2/DeviceStatusGet.d.ts +6 -0
- package/dist/api/protocol-v2/DeviceStatusGet.d.ts.map +1 -0
- package/dist/api/protocol-v2/FilesystemFormat.d.ts.map +1 -1
- package/dist/api/protocol-v2/helpers.d.ts +1 -1
- package/dist/api/protocol-v2/helpers.d.ts.map +1 -1
- package/dist/device/Device.d.ts +2 -2
- package/dist/device/Device.d.ts.map +1 -1
- package/dist/device/DeviceWalletSessionStore.d.ts +15 -0
- package/dist/device/DeviceWalletSessionStore.d.ts.map +1 -0
- package/dist/index.d.ts +32 -19
- package/dist/index.js +651 -288
- package/dist/inject.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/index.d.ts +1 -0
- package/dist/protocols/protocol-v2/index.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/walletSession.d.ts +13 -0
- package/dist/protocols/protocol-v2/walletSession.d.ts.map +1 -0
- package/dist/types/api/firmwareUpdate.d.ts +4 -1
- package/dist/types/api/firmwareUpdate.d.ts.map +1 -1
- package/dist/types/api/index.d.ts +6 -1
- package/dist/types/api/index.d.ts.map +1 -1
- package/dist/types/api/protocolV2.d.ts +6 -2
- package/dist/types/api/protocolV2.d.ts.map +1 -1
- package/dist/types/api/sessionCache.d.ts +10 -0
- package/dist/types/api/sessionCache.d.ts.map +1 -0
- package/dist/types/settings.d.ts +6 -14
- package/dist/types/settings.d.ts.map +1 -1
- 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/package.json +4 -4
- package/src/api/ClearSessionCache.ts +28 -0
- package/src/api/FileRead.ts +2 -2
- package/src/api/FirmwareUpdateV4.ts +276 -239
- package/src/api/index.ts +3 -0
- package/src/api/protocol-v2/DeviceSessionGet.ts +26 -0
- package/src/api/protocol-v2/DeviceStatusGet.ts +14 -0
- package/src/api/protocol-v2/FilesystemFormat.ts +4 -1
- package/src/api/protocol-v2/helpers.ts +17 -14
- package/src/data/messages/messages-protocol-v2.json +134 -3
- package/src/data/messages/messages.json +8 -0
- package/src/device/Device.ts +64 -56
- package/src/device/DeviceWalletSessionStore.ts +78 -0
- package/src/inject.ts +5 -2
- package/src/protocols/protocol-v2/index.ts +1 -0
- package/src/protocols/protocol-v2/walletSession.ts +83 -0
- package/src/types/api/firmwareUpdate.ts +8 -2
- package/src/types/api/index.ts +10 -3
- package/src/types/api/protocolV2.ts +16 -2
- package/src/types/api/sessionCache.ts +14 -0
- package/src/types/settings.ts +14 -16
- package/src/utils/deviceFeaturesUtils.ts +22 -32
|
@@ -13,6 +13,9 @@ import DeviceFirmwareUpdate from '../src/api/protocol-v2/DeviceFirmwareUpdate';
|
|
|
13
13
|
import DeviceGetFirmwareUpdateStatus from '../src/api/protocol-v2/DeviceGetFirmwareUpdateStatus';
|
|
14
14
|
import DeviceInfoGet from '../src/api/protocol-v2/DeviceInfoGet';
|
|
15
15
|
import DeviceReboot from '../src/api/protocol-v2/DeviceReboot';
|
|
16
|
+
import DeviceSessionGet from '../src/api/protocol-v2/DeviceSessionGet';
|
|
17
|
+
import DeviceStatusGet from '../src/api/protocol-v2/DeviceStatusGet';
|
|
18
|
+
import FilesystemFormat from '../src/api/protocol-v2/FilesystemFormat';
|
|
16
19
|
import FilesystemPermissionFix from '../src/api/protocol-v2/FilesystemPermissionFix';
|
|
17
20
|
import ProtocolInfoRequest from '../src/api/protocol-v2/ProtocolInfoRequest';
|
|
18
21
|
import EVMSignTypedData from '../src/api/evm/EVMSignTypedData';
|
|
@@ -42,12 +45,17 @@ import StellarGetAddress from '../src/api/stellar/StellarGetAddress';
|
|
|
42
45
|
import BenfenSignMessage from '../src/api/benfen/BenfenSignMessage';
|
|
43
46
|
import { getBitcoinForkVersionRange } from '../src/api/btc/helpers/versionLimit';
|
|
44
47
|
import { DataManager } from '../src/data-manager';
|
|
45
|
-
import {
|
|
48
|
+
import { createCoreApi } from '../src/inject';
|
|
49
|
+
import { Device, preloadSessionCache } from '../src/device/Device';
|
|
46
50
|
import { UI_REQUEST } from '../src/events/ui-request';
|
|
47
51
|
import {
|
|
48
52
|
PROTOCOL_V2_DEVICE_INFO_TIMEOUT_MS,
|
|
49
53
|
requestProtocolV2DeviceInfo,
|
|
50
54
|
} from '../src/protocols/protocol-v2/features';
|
|
55
|
+
import {
|
|
56
|
+
getProtocolV2WalletSession,
|
|
57
|
+
refreshProtocolV2DeviceStatus,
|
|
58
|
+
} from '../src/protocols/protocol-v2/walletSession';
|
|
51
59
|
import { buildProfileFromProtocolV2, buildProtocolV2FeaturesPayload } from '../src/deviceProfile';
|
|
52
60
|
import {
|
|
53
61
|
getDeviceType,
|
|
@@ -243,6 +251,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
243
251
|
test('uses DeviceSessionGet for Protocol V2 passphrase sessions', async () => {
|
|
244
252
|
const features = normalizeProtocolV2Features(descriptor as any);
|
|
245
253
|
features.firmwareVersion = '1.2.3';
|
|
254
|
+
features.passphraseProtection = true;
|
|
246
255
|
const typedCall = jest.fn().mockResolvedValue({
|
|
247
256
|
type: 'DeviceSession',
|
|
248
257
|
message: {
|
|
@@ -260,10 +269,16 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
260
269
|
});
|
|
261
270
|
expect(typedCall).toHaveBeenLastCalledWith('DeviceSessionGet', 'DeviceSession', {});
|
|
262
271
|
|
|
263
|
-
await
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
272
|
+
await expect(
|
|
273
|
+
getPassphraseState(device, {
|
|
274
|
+
expectPassphraseState: 'state-2',
|
|
275
|
+
allowCreateAttachPin: true,
|
|
276
|
+
})
|
|
277
|
+
).rejects.toEqual(
|
|
278
|
+
expect.objectContaining({
|
|
279
|
+
errorCode: HardwareErrorCode.DeviceCheckPassphraseStateError,
|
|
280
|
+
})
|
|
281
|
+
);
|
|
267
282
|
expect(typedCall).toHaveBeenLastCalledWith('DeviceSessionGet', 'DeviceSession', {});
|
|
268
283
|
|
|
269
284
|
await getPassphraseState(device, {
|
|
@@ -272,6 +287,266 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
272
287
|
expect(typedCall).toHaveBeenLastCalledWith('DeviceSessionGet', 'DeviceSession', {});
|
|
273
288
|
});
|
|
274
289
|
|
|
290
|
+
test('deviceStatusGet returns raw DeviceStatus without updating features', async () => {
|
|
291
|
+
const typedCall = jest.fn().mockResolvedValue({
|
|
292
|
+
type: 'DeviceStatus',
|
|
293
|
+
message: { device_id: 'device-1', unlocked: true },
|
|
294
|
+
});
|
|
295
|
+
const updateProtocolV2Features = jest.fn();
|
|
296
|
+
const method = new DeviceStatusGet({
|
|
297
|
+
payload: { method: 'deviceStatusGet', connectId: 'connect-id' },
|
|
298
|
+
});
|
|
299
|
+
method.init();
|
|
300
|
+
method.device = stubDevice({
|
|
301
|
+
originalDescriptor: { ...descriptor, protocolType: 'V2' },
|
|
302
|
+
commands: { typedCall },
|
|
303
|
+
updateProtocolV2Features,
|
|
304
|
+
}) as any;
|
|
305
|
+
|
|
306
|
+
await expect(method.run()).resolves.toEqual({ device_id: 'device-1', unlocked: true });
|
|
307
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceStatusGet', 'DeviceStatus', {});
|
|
308
|
+
expect(updateProtocolV2Features).not.toHaveBeenCalled();
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
test('deviceSessionGet maps sessionId and does not mutate wallet cache', async () => {
|
|
312
|
+
const typedCall = jest.fn().mockResolvedValue({
|
|
313
|
+
type: 'DeviceSession',
|
|
314
|
+
message: { session_id: 'new-session', btc_test_address: 'state-a' },
|
|
315
|
+
});
|
|
316
|
+
const updateInternalState = jest.fn();
|
|
317
|
+
const method = new DeviceSessionGet({
|
|
318
|
+
payload: {
|
|
319
|
+
method: 'deviceSessionGet',
|
|
320
|
+
connectId: 'connect-id',
|
|
321
|
+
sessionId: 'cached-session',
|
|
322
|
+
},
|
|
323
|
+
});
|
|
324
|
+
method.init();
|
|
325
|
+
method.device = stubDevice({
|
|
326
|
+
originalDescriptor: { ...descriptor, protocolType: 'V2' },
|
|
327
|
+
commands: { typedCall },
|
|
328
|
+
updateInternalState,
|
|
329
|
+
}) as any;
|
|
330
|
+
|
|
331
|
+
await expect(method.run()).resolves.toEqual({
|
|
332
|
+
session_id: 'new-session',
|
|
333
|
+
btc_test_address: 'state-a',
|
|
334
|
+
});
|
|
335
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
336
|
+
session_id: 'cached-session',
|
|
337
|
+
});
|
|
338
|
+
expect(updateInternalState).not.toHaveBeenCalled();
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
test('routes raw status and session methods through CoreApi', async () => {
|
|
342
|
+
const call = jest.fn().mockResolvedValue({ success: true, payload: {} });
|
|
343
|
+
const api = createCoreApi(call as any);
|
|
344
|
+
|
|
345
|
+
await api.deviceStatusGet('connect-id', { retryCount: 1 });
|
|
346
|
+
await api.deviceSessionGet('connect-id', { sessionId: 'cached-session' });
|
|
347
|
+
|
|
348
|
+
expect(call).toHaveBeenNthCalledWith(1, {
|
|
349
|
+
method: 'deviceStatusGet',
|
|
350
|
+
connectId: 'connect-id',
|
|
351
|
+
retryCount: 1,
|
|
352
|
+
});
|
|
353
|
+
expect(call).toHaveBeenNthCalledWith(2, {
|
|
354
|
+
method: 'deviceSessionGet',
|
|
355
|
+
connectId: 'connect-id',
|
|
356
|
+
sessionId: 'cached-session',
|
|
357
|
+
});
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
test('reuses the cached session id for the selected Pro2 wallet', async () => {
|
|
361
|
+
const device = Device.fromDescriptor({
|
|
362
|
+
id: 'cache-device-1',
|
|
363
|
+
path: 'cache-path-1',
|
|
364
|
+
protocolType: 'V2',
|
|
365
|
+
} as any);
|
|
366
|
+
(device as any).features = normalizeProtocolV2Features(
|
|
367
|
+
{ ...descriptor, protocolType: 'V2' } as any,
|
|
368
|
+
{
|
|
369
|
+
status: {
|
|
370
|
+
device_id: 'stable-device-1',
|
|
371
|
+
unlocked: true,
|
|
372
|
+
passphrase_enabled: true,
|
|
373
|
+
},
|
|
374
|
+
}
|
|
375
|
+
);
|
|
376
|
+
device.passphraseState = 'state-a';
|
|
377
|
+
preloadSessionCache('stable-device-1', 'state-a', 'session-a');
|
|
378
|
+
const typedCall = jest.fn().mockResolvedValue({
|
|
379
|
+
type: 'DeviceSession',
|
|
380
|
+
message: { session_id: 'session-b', btc_test_address: 'state-a' },
|
|
381
|
+
});
|
|
382
|
+
(device as any).commands = { typedCall };
|
|
383
|
+
|
|
384
|
+
await expect(getProtocolV2WalletSession(device)).resolves.toMatchObject({
|
|
385
|
+
passphraseState: 'state-a',
|
|
386
|
+
newSession: 'session-b',
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
390
|
+
session_id: 'session-a',
|
|
391
|
+
});
|
|
392
|
+
expect(device.getInternalState()).toBe('session-b');
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
test('does not reuse another Pro2 wallet session without passphraseState', async () => {
|
|
396
|
+
const device = Device.fromDescriptor({
|
|
397
|
+
id: 'cache-device-2',
|
|
398
|
+
path: 'cache-path-2',
|
|
399
|
+
protocolType: 'V2',
|
|
400
|
+
} as any);
|
|
401
|
+
(device as any).features = normalizeProtocolV2Features(
|
|
402
|
+
{ ...descriptor, protocolType: 'V2' } as any,
|
|
403
|
+
{ status: { device_id: 'stable-device-2', unlocked: true } }
|
|
404
|
+
);
|
|
405
|
+
preloadSessionCache('stable-device-2', 'state-a', 'session-a');
|
|
406
|
+
const typedCall = jest.fn().mockResolvedValue({
|
|
407
|
+
type: 'DeviceSession',
|
|
408
|
+
message: { session_id: 'main-session' },
|
|
409
|
+
});
|
|
410
|
+
(device as any).commands = { typedCall };
|
|
411
|
+
|
|
412
|
+
await getProtocolV2WalletSession(device);
|
|
413
|
+
|
|
414
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {});
|
|
415
|
+
expect(device.getInternalState()).toBeUndefined();
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
test('clears the selected Pro2 cache entry after invalid session rejection', async () => {
|
|
419
|
+
const device = Device.fromDescriptor({
|
|
420
|
+
id: 'cache-device-3',
|
|
421
|
+
path: 'cache-path-3',
|
|
422
|
+
protocolType: 'V2',
|
|
423
|
+
} as any);
|
|
424
|
+
(device as any).features = normalizeProtocolV2Features(
|
|
425
|
+
{ ...descriptor, protocolType: 'V2' } as any,
|
|
426
|
+
{
|
|
427
|
+
status: {
|
|
428
|
+
device_id: 'stable-device-3',
|
|
429
|
+
unlocked: true,
|
|
430
|
+
passphrase_enabled: true,
|
|
431
|
+
},
|
|
432
|
+
}
|
|
433
|
+
);
|
|
434
|
+
device.passphraseState = 'state-a';
|
|
435
|
+
preloadSessionCache('stable-device-3', 'state-a', 'session-a');
|
|
436
|
+
(device as any).commands = {
|
|
437
|
+
typedCall: jest.fn().mockRejectedValue(new Error('Failure_InvalidSession,no error message')),
|
|
438
|
+
};
|
|
439
|
+
|
|
440
|
+
await expect(getProtocolV2WalletSession(device)).rejects.toThrow('Failure_InvalidSession');
|
|
441
|
+
expect(device.getInternalState()).toBeUndefined();
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
test('rejects a mismatched Pro2 wallet state and clears the selected cache entry', async () => {
|
|
445
|
+
const device = Device.fromDescriptor({
|
|
446
|
+
id: 'cache-device-mismatch',
|
|
447
|
+
path: 'cache-path-mismatch',
|
|
448
|
+
protocolType: 'V2',
|
|
449
|
+
} as any);
|
|
450
|
+
(device as any).features = normalizeProtocolV2Features(
|
|
451
|
+
{ ...descriptor, protocolType: 'V2' } as any,
|
|
452
|
+
{
|
|
453
|
+
status: {
|
|
454
|
+
device_id: 'stable-device-mismatch',
|
|
455
|
+
unlocked: true,
|
|
456
|
+
passphrase_enabled: true,
|
|
457
|
+
},
|
|
458
|
+
}
|
|
459
|
+
);
|
|
460
|
+
device.passphraseState = 'state-a';
|
|
461
|
+
preloadSessionCache('stable-device-mismatch', 'state-a', 'session-a');
|
|
462
|
+
(device as any).commands = {
|
|
463
|
+
typedCall: jest.fn().mockResolvedValue({
|
|
464
|
+
type: 'DeviceSession',
|
|
465
|
+
message: { session_id: 'session-b', btc_test_address: 'state-b' },
|
|
466
|
+
}),
|
|
467
|
+
};
|
|
468
|
+
|
|
469
|
+
await expect(
|
|
470
|
+
getPassphraseStateWithRefreshDeviceInfo(device, { expectPassphraseState: 'state-a' })
|
|
471
|
+
).rejects.toEqual(
|
|
472
|
+
expect.objectContaining({
|
|
473
|
+
errorCode: HardwareErrorCode.DeviceCheckPassphraseStateError,
|
|
474
|
+
})
|
|
475
|
+
);
|
|
476
|
+
expect(device.getInternalState()).toBeUndefined();
|
|
477
|
+
});
|
|
478
|
+
|
|
479
|
+
test('rejects DeviceSessionGet while cached Pro2 status is locked', async () => {
|
|
480
|
+
const device = Device.fromDescriptor({
|
|
481
|
+
id: 'cache-device-4',
|
|
482
|
+
path: 'cache-path-4',
|
|
483
|
+
protocolType: 'V2',
|
|
484
|
+
} as any);
|
|
485
|
+
(device as any).features = normalizeProtocolV2Features(
|
|
486
|
+
{ ...descriptor, protocolType: 'V2' } as any,
|
|
487
|
+
{ status: { device_id: 'stable-device-4', unlocked: false } }
|
|
488
|
+
);
|
|
489
|
+
const typedCall = jest.fn();
|
|
490
|
+
(device as any).commands = { typedCall };
|
|
491
|
+
|
|
492
|
+
await expect(getProtocolV2WalletSession(device)).rejects.toThrow('Device is locked');
|
|
493
|
+
expect(typedCall).not.toHaveBeenCalled();
|
|
494
|
+
});
|
|
495
|
+
|
|
496
|
+
test('does not request a Pro2 wallet session before features are initialized', async () => {
|
|
497
|
+
const device = Device.fromDescriptor({
|
|
498
|
+
id: 'cache-device-5',
|
|
499
|
+
path: 'cache-path-5',
|
|
500
|
+
protocolType: 'V2',
|
|
501
|
+
} as any);
|
|
502
|
+
const typedCall = jest.fn();
|
|
503
|
+
(device as any).commands = { typedCall };
|
|
504
|
+
|
|
505
|
+
await expect(getPassphraseStateWithRefreshDeviceInfo(device)).resolves.toEqual({
|
|
506
|
+
passphraseState: undefined,
|
|
507
|
+
newSession: undefined,
|
|
508
|
+
unlockedAttachPin: undefined,
|
|
509
|
+
});
|
|
510
|
+
expect(typedCall).not.toHaveBeenCalled();
|
|
511
|
+
});
|
|
512
|
+
|
|
513
|
+
test('merges DeviceStatus into existing Protocol V2 features', async () => {
|
|
514
|
+
const device = Device.fromDescriptor({
|
|
515
|
+
id: 'status-device',
|
|
516
|
+
path: 'status-path',
|
|
517
|
+
protocolType: 'V2',
|
|
518
|
+
} as any);
|
|
519
|
+
(device as any).features = normalizeProtocolV2Features(
|
|
520
|
+
{ ...descriptor, protocolType: 'V2' } as any,
|
|
521
|
+
{
|
|
522
|
+
protocol_version: 2,
|
|
523
|
+
fw: { application: { version: '1.2.3' } },
|
|
524
|
+
se1: { application: { version: '4.5.6' } },
|
|
525
|
+
status: { unlocked: false, passphrase_enabled: false },
|
|
526
|
+
}
|
|
527
|
+
);
|
|
528
|
+
const typedCall = jest.fn().mockResolvedValue({
|
|
529
|
+
type: 'DeviceStatus',
|
|
530
|
+
message: {
|
|
531
|
+
device_id: 'stable-status-device',
|
|
532
|
+
unlocked: true,
|
|
533
|
+
passphrase_enabled: true,
|
|
534
|
+
},
|
|
535
|
+
});
|
|
536
|
+
(device as any).commands = { typedCall };
|
|
537
|
+
|
|
538
|
+
const features = await refreshProtocolV2DeviceStatus(device);
|
|
539
|
+
|
|
540
|
+
expect(features).toMatchObject({
|
|
541
|
+
deviceId: 'stable-status-device',
|
|
542
|
+
unlocked: true,
|
|
543
|
+
passphraseProtection: true,
|
|
544
|
+
firmwareVersion: '1.2.3',
|
|
545
|
+
se01Version: '4.5.6',
|
|
546
|
+
});
|
|
547
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceStatusGet', 'DeviceStatus', {});
|
|
548
|
+
});
|
|
549
|
+
|
|
275
550
|
test('returns unified GetPassphraseState object payload for existing Pro devices', async () => {
|
|
276
551
|
const features = {
|
|
277
552
|
deviceId: 'pro-device-id',
|
|
@@ -534,8 +809,10 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
534
809
|
(device as any).features.passphraseProtection = true;
|
|
535
810
|
(device as any).commands = { typedCall };
|
|
536
811
|
|
|
537
|
-
await expect(device.checkPassphraseStateSafety('expected-state', false, true)).
|
|
538
|
-
|
|
812
|
+
await expect(device.checkPassphraseStateSafety('expected-state', false, true)).rejects.toEqual(
|
|
813
|
+
expect.objectContaining({
|
|
814
|
+
errorCode: HardwareErrorCode.DeviceCheckPassphraseStateError,
|
|
815
|
+
})
|
|
539
816
|
);
|
|
540
817
|
|
|
541
818
|
expect(device.getInternalState()).toBeUndefined();
|
|
@@ -1297,9 +1574,24 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1297
1574
|
path: 'usb-path',
|
|
1298
1575
|
protocolType: 'V2',
|
|
1299
1576
|
} as any);
|
|
1300
|
-
const typedCall = jest.fn().
|
|
1301
|
-
|
|
1302
|
-
|
|
1577
|
+
const typedCall = jest.fn().mockImplementation(requestType => {
|
|
1578
|
+
if (requestType === 'UnLockDevice') {
|
|
1579
|
+
return {
|
|
1580
|
+
type: 'UnLockDeviceResponse',
|
|
1581
|
+
message: { unlocked: false, passphrase_protection: false },
|
|
1582
|
+
};
|
|
1583
|
+
}
|
|
1584
|
+
if (requestType === 'DeviceStatusGet') {
|
|
1585
|
+
return {
|
|
1586
|
+
type: 'DeviceStatus',
|
|
1587
|
+
message: {
|
|
1588
|
+
device_id: 'PRO2-DEVICE-ID',
|
|
1589
|
+
unlocked: true,
|
|
1590
|
+
passphrase_enabled: true,
|
|
1591
|
+
},
|
|
1592
|
+
};
|
|
1593
|
+
}
|
|
1594
|
+
throw new Error(`Unexpected request: ${requestType}`);
|
|
1303
1595
|
});
|
|
1304
1596
|
|
|
1305
1597
|
(device as any).commands = { typedCall };
|
|
@@ -1316,18 +1608,22 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1316
1608
|
|
|
1317
1609
|
const features = await device.unlockDevice();
|
|
1318
1610
|
|
|
1319
|
-
expect(typedCall).
|
|
1611
|
+
expect(typedCall.mock.calls).toEqual([
|
|
1612
|
+
['UnLockDevice', 'UnLockDeviceResponse'],
|
|
1613
|
+
['DeviceStatusGet', 'DeviceStatus', {}],
|
|
1614
|
+
]);
|
|
1320
1615
|
expect(typedCall).not.toHaveBeenCalledWith('GetAddress', 'Address', expect.anything());
|
|
1321
1616
|
expect(typedCall).not.toHaveBeenCalledWith('GetFeatures', 'Features', {});
|
|
1322
1617
|
expect(features).toMatchObject({
|
|
1323
1618
|
deviceType: 'pro2',
|
|
1324
|
-
deviceId:
|
|
1619
|
+
deviceId: 'PRO2-DEVICE-ID',
|
|
1325
1620
|
firmwareVersion: '1.2.3',
|
|
1326
1621
|
unlocked: true,
|
|
1622
|
+
passphraseProtection: true,
|
|
1327
1623
|
});
|
|
1328
1624
|
});
|
|
1329
1625
|
|
|
1330
|
-
test('syncs Protocol V2 features passphrase state after unlock
|
|
1626
|
+
test('syncs Protocol V2 features passphrase state from DeviceStatusGet after unlock', async () => {
|
|
1331
1627
|
const device = Device.fromDescriptor({ ...descriptor, protocolType: 'V2' } as any);
|
|
1332
1628
|
(device as any).features = normalizeProtocolV2Features(
|
|
1333
1629
|
{ ...descriptor, protocolType: 'V2' } as any,
|
|
@@ -1337,6 +1633,74 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1337
1633
|
status: { passphrase_enabled: false },
|
|
1338
1634
|
}
|
|
1339
1635
|
);
|
|
1636
|
+
const typedCall = jest.fn().mockImplementation(requestType => {
|
|
1637
|
+
if (requestType === 'UnLockDevice') {
|
|
1638
|
+
return {
|
|
1639
|
+
type: 'UnLockDeviceResponse',
|
|
1640
|
+
message: {
|
|
1641
|
+
unlocked: false,
|
|
1642
|
+
unlocked_attach_pin: false,
|
|
1643
|
+
passphrase_protection: false,
|
|
1644
|
+
},
|
|
1645
|
+
};
|
|
1646
|
+
}
|
|
1647
|
+
if (requestType === 'DeviceStatusGet') {
|
|
1648
|
+
return {
|
|
1649
|
+
type: 'DeviceStatus',
|
|
1650
|
+
message: {
|
|
1651
|
+
unlocked: true,
|
|
1652
|
+
unlocked_by_attach_to_pin: true,
|
|
1653
|
+
passphrase_enabled: true,
|
|
1654
|
+
},
|
|
1655
|
+
};
|
|
1656
|
+
}
|
|
1657
|
+
throw new Error(`Unexpected request: ${requestType}`);
|
|
1658
|
+
});
|
|
1659
|
+
(device as any).commands = { typedCall };
|
|
1660
|
+
|
|
1661
|
+
await device.unlockDevice();
|
|
1662
|
+
|
|
1663
|
+
expect((device as any).profile).toBeUndefined();
|
|
1664
|
+
expect(device.features?.passphraseProtection).toBe(true);
|
|
1665
|
+
expect(device.features?.unlockedAttachPin).toBe(true);
|
|
1666
|
+
});
|
|
1667
|
+
|
|
1668
|
+
test('maps unsupported Protocol V2 UnLockDevice to DeviceNotSupportMethod', async () => {
|
|
1669
|
+
const device = Device.fromDescriptor({ ...descriptor, protocolType: 'V2' } as any);
|
|
1670
|
+
(device as any).features = normalizeProtocolV2Features(
|
|
1671
|
+
{ ...descriptor, protocolType: 'V2' } as any,
|
|
1672
|
+
{
|
|
1673
|
+
fw: { application: { version: '1.2.3' } },
|
|
1674
|
+
status: { unlocked: false },
|
|
1675
|
+
}
|
|
1676
|
+
);
|
|
1677
|
+
const typedCall = jest
|
|
1678
|
+
.fn()
|
|
1679
|
+
.mockRejectedValue(new Error('Failure_UnexpectedMessage,Unknown message'));
|
|
1680
|
+
(device as any).commands = { typedCall };
|
|
1681
|
+
|
|
1682
|
+
await expect(device.unlockDevice()).rejects.toEqual(
|
|
1683
|
+
expect.objectContaining({
|
|
1684
|
+
errorCode: HardwareErrorCode.DeviceNotSupportMethod,
|
|
1685
|
+
})
|
|
1686
|
+
);
|
|
1687
|
+
expect(typedCall).toHaveBeenCalledTimes(1);
|
|
1688
|
+
expect(typedCall).not.toHaveBeenCalledWith(
|
|
1689
|
+
'DeviceSessionGet',
|
|
1690
|
+
'DeviceSession',
|
|
1691
|
+
expect.anything()
|
|
1692
|
+
);
|
|
1693
|
+
});
|
|
1694
|
+
|
|
1695
|
+
test('keeps Protocol V1 unlock response handling unchanged', async () => {
|
|
1696
|
+
const device = Device.fromDescriptor({ path: 'v1-path', protocolType: 'V1' } as any);
|
|
1697
|
+
(device as any).features = {
|
|
1698
|
+
deviceType: 'pro',
|
|
1699
|
+
firmwareVersion: '4.15.0',
|
|
1700
|
+
capabilities: [],
|
|
1701
|
+
unlocked: false,
|
|
1702
|
+
passphraseProtection: false,
|
|
1703
|
+
} as Features;
|
|
1340
1704
|
const typedCall = jest.fn().mockResolvedValue({
|
|
1341
1705
|
type: 'UnLockDeviceResponse',
|
|
1342
1706
|
message: {
|
|
@@ -1347,11 +1711,12 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1347
1711
|
});
|
|
1348
1712
|
(device as any).commands = { typedCall };
|
|
1349
1713
|
|
|
1350
|
-
await device.unlockDevice()
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1714
|
+
await expect(device.unlockDevice()).resolves.toMatchObject({
|
|
1715
|
+
unlocked: true,
|
|
1716
|
+
unlockedAttachPin: true,
|
|
1717
|
+
passphraseProtection: true,
|
|
1718
|
+
});
|
|
1719
|
+
expect(typedCall.mock.calls).toEqual([['UnLockDevice', 'UnLockDeviceResponse']]);
|
|
1355
1720
|
});
|
|
1356
1721
|
});
|
|
1357
1722
|
|
|
@@ -1840,13 +2205,12 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
1840
2205
|
originalDescriptor: { id: 'ble-id', path: 'ble-path', protocolType: 'V2' },
|
|
1841
2206
|
features: { capabilities: [] },
|
|
1842
2207
|
});
|
|
1843
|
-
(method as any).prepareResourceBinaries = jest.fn().mockResolvedValue([]);
|
|
1844
2208
|
(method as any).prepareBootloaderBinary = jest.fn().mockReturnValue(null);
|
|
1845
2209
|
(method as any).collectExplicitTargetBinaries = jest.fn().mockReturnValue([
|
|
1846
2210
|
{
|
|
1847
2211
|
fileName: 'coprocessor.bin',
|
|
1848
2212
|
binary: new Uint8Array([1, 2, 3]).buffer,
|
|
1849
|
-
targetId:
|
|
2213
|
+
targetId: 5,
|
|
1850
2214
|
},
|
|
1851
2215
|
]);
|
|
1852
2216
|
(method as any).executeProtocolV2Update = jest.fn().mockResolvedValue(undefined);
|
|
@@ -1864,12 +2228,11 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
1864
2228
|
|
|
1865
2229
|
expect((method as any).enterProtocolV2BootloaderMode).toHaveBeenCalledTimes(1);
|
|
1866
2230
|
expect((method as any).executeProtocolV2Update).toHaveBeenCalledWith({
|
|
1867
|
-
resourceBinaryMap: [],
|
|
1868
2231
|
fwBinaryMap: [
|
|
1869
2232
|
{
|
|
1870
2233
|
fileName: 'coprocessor.bin',
|
|
1871
2234
|
binary: expect.any(ArrayBuffer),
|
|
1872
|
-
targetId:
|
|
2235
|
+
targetId: 5,
|
|
1873
2236
|
},
|
|
1874
2237
|
],
|
|
1875
2238
|
bootloaderBinary: null,
|
|
@@ -2052,7 +2415,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
2052
2415
|
|
|
2053
2416
|
await expect(
|
|
2054
2417
|
(method as any).protocolV2StartFirmwareUpdate({
|
|
2055
|
-
targets: [{ target_id:
|
|
2418
|
+
targets: [{ target_id: 4, path: 'vol1:application_p1.bin' }],
|
|
2056
2419
|
})
|
|
2057
2420
|
).resolves.toBeUndefined();
|
|
2058
2421
|
|
|
@@ -2192,12 +2555,12 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
2192
2555
|
(method as any).assertProtocolV2TargetStatus(
|
|
2193
2556
|
[
|
|
2194
2557
|
{
|
|
2195
|
-
target_id: '
|
|
2558
|
+
target_id: 'FW_MGMT_TARGET_APPLICATION_P1',
|
|
2196
2559
|
status: 'FW_MGMT_UPDATER_TASK_STATUS_FINISHED',
|
|
2197
2560
|
},
|
|
2198
2561
|
{ target_id: 'FW_MGMT_TARGET_SE04', status: 'FW_MGMT_UPDATER_TASK_STATUS_FINISHED' },
|
|
2199
2562
|
],
|
|
2200
|
-
new Set([
|
|
2563
|
+
new Set([4, 10])
|
|
2201
2564
|
)
|
|
2202
2565
|
).toBe(true);
|
|
2203
2566
|
|
|
@@ -2233,7 +2596,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
2233
2596
|
}
|
|
2234
2597
|
});
|
|
2235
2598
|
|
|
2236
|
-
test('passes
|
|
2599
|
+
test('passes bootloader, coprocessor, SE and app files to DeviceFirmwareUpdate targets', async () => {
|
|
2237
2600
|
const method = new FirmwareUpdateV4({
|
|
2238
2601
|
id: 1,
|
|
2239
2602
|
payload: {
|
|
@@ -2248,19 +2611,13 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
2248
2611
|
writtenPaths.push(params.filePath);
|
|
2249
2612
|
return Number(params.processedSize ?? 0) + Number(params.payload.byteLength);
|
|
2250
2613
|
});
|
|
2614
|
+
(method as any).verifyProtocolV2StagedFile = jest.fn().mockResolvedValue(undefined);
|
|
2251
2615
|
(method as any).protocolV2StartFirmwareUpdate = jest.fn().mockResolvedValue(undefined);
|
|
2252
2616
|
(method as any).waitForProtocolV2FirmwareUpdateComplete = jest
|
|
2253
2617
|
.fn()
|
|
2254
2618
|
.mockResolvedValue(undefined);
|
|
2255
2619
|
|
|
2256
2620
|
await (method as any).executeProtocolV2Update({
|
|
2257
|
-
resourceBinaryMap: [
|
|
2258
|
-
{
|
|
2259
|
-
fileName: 'resource.bin',
|
|
2260
|
-
binary: new Uint8Array([1, 2, 3]).buffer,
|
|
2261
|
-
targetId: 1,
|
|
2262
|
-
},
|
|
2263
|
-
],
|
|
2264
2621
|
bootloaderBinary: new Uint8Array([4, 5]).buffer,
|
|
2265
2622
|
fwBinaryMap: [
|
|
2266
2623
|
{
|
|
@@ -2282,44 +2639,37 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
2282
2639
|
});
|
|
2283
2640
|
|
|
2284
2641
|
expect(writtenPaths).toEqual([
|
|
2285
|
-
'
|
|
2286
|
-
'
|
|
2287
|
-
'
|
|
2288
|
-
'
|
|
2289
|
-
'vol1:application_p1.bin',
|
|
2642
|
+
'vol0:/bootloader.bin',
|
|
2643
|
+
'vol0:/coprocessor.bin',
|
|
2644
|
+
'vol0:/se01.bin',
|
|
2645
|
+
'vol0:/application_p1.bin',
|
|
2290
2646
|
]);
|
|
2291
|
-
expect((method as any).
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2647
|
+
expect((method as any).verifyProtocolV2StagedFile).toHaveBeenCalledTimes(4);
|
|
2648
|
+
expect((method as any).verifyProtocolV2StagedFile).toHaveBeenNthCalledWith(
|
|
2649
|
+
2,
|
|
2650
|
+
'vol0:/coprocessor.bin',
|
|
2651
|
+
1
|
|
2652
|
+
);
|
|
2653
|
+
expect((method as any).protocolV2StartFirmwareUpdate).toHaveBeenCalledTimes(1);
|
|
2654
|
+
expect((method as any).protocolV2StartFirmwareUpdate).toHaveBeenCalledWith({
|
|
2295
2655
|
targets: [
|
|
2296
|
-
{ target_id: 3, path: '
|
|
2297
|
-
{ target_id: 6, path: '
|
|
2298
|
-
{ target_id: 7, path: '
|
|
2299
|
-
{ target_id: 4, path: '
|
|
2656
|
+
{ target_id: 3, path: 'vol0:/bootloader.bin' },
|
|
2657
|
+
{ target_id: 6, path: 'vol0:/coprocessor.bin' },
|
|
2658
|
+
{ target_id: 7, path: 'vol0:/se01.bin' },
|
|
2659
|
+
{ target_id: 4, path: 'vol0:/application_p1.bin' },
|
|
2300
2660
|
],
|
|
2301
2661
|
});
|
|
2302
2662
|
expect(method.postProgressMessage).toHaveBeenCalledWith(100, 'transferData');
|
|
2303
2663
|
expect((method as any).waitForProtocolV2FirmwareUpdateComplete).toHaveBeenCalled();
|
|
2304
2664
|
});
|
|
2305
2665
|
|
|
2306
|
-
test('
|
|
2666
|
+
test('announces one transfer when syncing resource bundles and staging firmware', async () => {
|
|
2307
2667
|
const method = new FirmwareUpdateV4({
|
|
2308
2668
|
id: 1,
|
|
2309
2669
|
payload: {
|
|
2310
2670
|
method: 'firmwareUpdateV4',
|
|
2311
|
-
platform: 'web',
|
|
2312
|
-
applicationP2Binary: new Uint8Array([2]).buffer,
|
|
2313
|
-
se04Binary: new Uint8Array([3]).buffer,
|
|
2314
2671
|
},
|
|
2315
2672
|
});
|
|
2316
|
-
method.init();
|
|
2317
|
-
|
|
2318
|
-
const explicit = (method as any).collectExplicitTargetBinaries();
|
|
2319
|
-
expect(explicit).toEqual([
|
|
2320
|
-
{ fileName: 'application_p2.bin', binary: expect.anything(), targetId: 5 },
|
|
2321
|
-
{ fileName: 'se04.bin', binary: expect.anything(), targetId: 10 },
|
|
2322
|
-
]);
|
|
2323
2673
|
|
|
2324
2674
|
method.postTipMessage = jest.fn();
|
|
2325
2675
|
method.postProgressMessage = jest.fn();
|
|
@@ -2328,57 +2678,121 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
2328
2678
|
.mockImplementation(params =>
|
|
2329
2679
|
Promise.resolve(Number(params.processedSize ?? 0) + Number(params.payload.byteLength))
|
|
2330
2680
|
);
|
|
2681
|
+
(method as any).verifyProtocolV2StagedFile = jest.fn().mockResolvedValue(undefined);
|
|
2331
2682
|
(method as any).protocolV2StartFirmwareUpdate = jest.fn().mockResolvedValue(undefined);
|
|
2332
2683
|
(method as any).waitForProtocolV2FirmwareUpdateComplete = jest
|
|
2333
2684
|
.fn()
|
|
2334
2685
|
.mockResolvedValue(undefined);
|
|
2335
2686
|
|
|
2336
2687
|
await (method as any).executeProtocolV2Update({
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2688
|
+
resourceBundles: [
|
|
2689
|
+
{
|
|
2690
|
+
name: 'images.okpkg',
|
|
2691
|
+
binary: new Uint8Array([1, 2]).buffer,
|
|
2692
|
+
devicePath: 'vol0:/bundles/images/images.okpkg',
|
|
2693
|
+
},
|
|
2694
|
+
],
|
|
2695
|
+
bootloaderBinary: null,
|
|
2696
|
+
fwBinaryMap: [
|
|
2697
|
+
{
|
|
2698
|
+
fileName: 'application_p1.bin',
|
|
2699
|
+
binary: new Uint8Array([3]).buffer,
|
|
2700
|
+
targetId: 3,
|
|
2701
|
+
},
|
|
2346
2702
|
],
|
|
2347
2703
|
});
|
|
2704
|
+
|
|
2705
|
+
expect(method.postTipMessage).toHaveBeenCalledTimes(2);
|
|
2706
|
+
expect(method.postTipMessage).toHaveBeenNthCalledWith(1, 'StartTransferData');
|
|
2707
|
+
expect(method.postTipMessage).toHaveBeenNthCalledWith(2, 'ConfirmOnDevice');
|
|
2708
|
+
expect((method as any).protocolV2CommonUpdateProcess).toHaveBeenNthCalledWith(
|
|
2709
|
+
1,
|
|
2710
|
+
expect.objectContaining({ processedSize: 0, totalSize: 3 })
|
|
2711
|
+
);
|
|
2712
|
+
expect((method as any).protocolV2CommonUpdateProcess).toHaveBeenNthCalledWith(
|
|
2713
|
+
2,
|
|
2714
|
+
expect.objectContaining({ processedSize: 2, totalSize: 3 })
|
|
2715
|
+
);
|
|
2348
2716
|
});
|
|
2349
2717
|
|
|
2350
|
-
test('
|
|
2351
|
-
const firstResource = new Uint8Array([1]).buffer;
|
|
2352
|
-
const secondResource = new Uint8Array([2]).buffer;
|
|
2718
|
+
test('does not request installation when the staged file size does not match', async () => {
|
|
2353
2719
|
const method = new FirmwareUpdateV4({
|
|
2354
2720
|
id: 1,
|
|
2355
2721
|
payload: {
|
|
2356
2722
|
method: 'firmwareUpdateV4',
|
|
2357
|
-
platform: 'web',
|
|
2358
|
-
resourceBinaries: [firstResource, secondResource],
|
|
2359
2723
|
},
|
|
2360
2724
|
});
|
|
2361
|
-
method.init();
|
|
2362
2725
|
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2726
|
+
method.postTipMessage = jest.fn();
|
|
2727
|
+
method.postProgressMessage = jest.fn();
|
|
2728
|
+
(method as any).protocolV2CommonUpdateProcess = jest.fn().mockResolvedValue(3);
|
|
2729
|
+
(method as any).device = stubDevice({
|
|
2730
|
+
getCommands: () => ({
|
|
2731
|
+
typedCall: jest.fn().mockResolvedValue({
|
|
2732
|
+
type: 'FilesystemPathInfo',
|
|
2733
|
+
message: { exist: true, directory: false, size: 2 },
|
|
2734
|
+
}),
|
|
2735
|
+
}),
|
|
2367
2736
|
});
|
|
2737
|
+
(method as any).protocolV2StartFirmwareUpdate = jest.fn();
|
|
2368
2738
|
|
|
2369
|
-
expect(
|
|
2370
|
-
{
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2739
|
+
await expect(
|
|
2740
|
+
(method as any).executeProtocolV2Update({
|
|
2741
|
+
bootloaderBinary: null,
|
|
2742
|
+
fwBinaryMap: [
|
|
2743
|
+
{
|
|
2744
|
+
fileName: 'coprocessor.bin',
|
|
2745
|
+
binary: new Uint8Array([1, 2, 3]).buffer,
|
|
2746
|
+
targetId: 5,
|
|
2747
|
+
},
|
|
2748
|
+
],
|
|
2749
|
+
})
|
|
2750
|
+
).rejects.toThrow(/staged file verification failed/);
|
|
2751
|
+
expect((method as any).protocolV2StartFirmwareUpdate).not.toHaveBeenCalled();
|
|
2752
|
+
});
|
|
2753
|
+
|
|
2754
|
+
test('passes explicit per-target binaries through without file name heuristics', async () => {
|
|
2755
|
+
const method = new FirmwareUpdateV4({
|
|
2756
|
+
id: 1,
|
|
2757
|
+
payload: {
|
|
2758
|
+
method: 'firmwareUpdateV4',
|
|
2759
|
+
platform: 'web',
|
|
2760
|
+
applicationP2Binary: new Uint8Array([2]).buffer,
|
|
2761
|
+
se04Binary: new Uint8Array([3]).buffer,
|
|
2379
2762
|
},
|
|
2763
|
+
});
|
|
2764
|
+
method.init();
|
|
2765
|
+
|
|
2766
|
+
const explicit = (method as any).collectExplicitTargetBinaries();
|
|
2767
|
+
expect(explicit).toEqual([
|
|
2768
|
+
{ fileName: 'application_p2.bin', binary: expect.anything(), targetId: 5 },
|
|
2769
|
+
{ fileName: 'se04.bin', binary: expect.anything(), targetId: 10 },
|
|
2380
2770
|
]);
|
|
2381
|
-
|
|
2771
|
+
|
|
2772
|
+
method.postTipMessage = jest.fn();
|
|
2773
|
+
method.postProgressMessage = jest.fn();
|
|
2774
|
+
(method as any).protocolV2CommonUpdateProcess = jest
|
|
2775
|
+
.fn()
|
|
2776
|
+
.mockImplementation(params =>
|
|
2777
|
+
Promise.resolve(Number(params.processedSize ?? 0) + Number(params.payload.byteLength))
|
|
2778
|
+
);
|
|
2779
|
+
(method as any).verifyProtocolV2StagedFile = jest.fn().mockResolvedValue(undefined);
|
|
2780
|
+
(method as any).protocolV2StartFirmwareUpdate = jest.fn().mockResolvedValue(undefined);
|
|
2781
|
+
(method as any).waitForProtocolV2FirmwareUpdateComplete = jest
|
|
2782
|
+
.fn()
|
|
2783
|
+
.mockResolvedValue(undefined);
|
|
2784
|
+
|
|
2785
|
+
await (method as any).executeProtocolV2Update({
|
|
2786
|
+
bootloaderBinary: null,
|
|
2787
|
+
fwBinaryMap: explicit,
|
|
2788
|
+
});
|
|
2789
|
+
|
|
2790
|
+
expect((method as any).protocolV2StartFirmwareUpdate).toHaveBeenCalledWith({
|
|
2791
|
+
targets: [
|
|
2792
|
+
{ target_id: 5, path: 'vol0:/application_p2.bin' },
|
|
2793
|
+
{ target_id: 10, path: 'vol0:/se04.bin' },
|
|
2794
|
+
],
|
|
2795
|
+
});
|
|
2382
2796
|
});
|
|
2383
2797
|
|
|
2384
2798
|
test('rejects romloaderBinary before sending a Protocol V2 update request', () => {
|
|
@@ -2510,7 +2924,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
2510
2924
|
getFirmwareLatestReleaseSpy.mockRestore();
|
|
2511
2925
|
});
|
|
2512
2926
|
|
|
2513
|
-
test('
|
|
2927
|
+
test('maps remote Pro2 resource bundles to direct-write descriptors', () => {
|
|
2514
2928
|
const method = new FirmwareUpdateV4({
|
|
2515
2929
|
id: 1,
|
|
2516
2930
|
payload: {
|
|
@@ -2520,303 +2934,22 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
2520
2934
|
});
|
|
2521
2935
|
method.init();
|
|
2522
2936
|
|
|
2523
|
-
const payloadHash = 'ab'.repeat(64);
|
|
2524
|
-
const header = buildOkppHeader({ payloadHash });
|
|
2525
|
-
const typedCall = jest.fn(
|
|
2526
|
-
(name: string, _resType: string, params: { file: { offset: number }; chunk_len: number }) => {
|
|
2527
|
-
if (name === 'FilesystemPathInfoQuery') {
|
|
2528
|
-
return Promise.resolve({
|
|
2529
|
-
type: 'FilesystemPathInfo',
|
|
2530
|
-
message: { exist: true, directory: false, size: header.byteLength },
|
|
2531
|
-
});
|
|
2532
|
-
}
|
|
2533
|
-
if (name === 'FilesystemFileRead') {
|
|
2534
|
-
const { offset } = params.file;
|
|
2535
|
-
const readLen = params.chunk_len;
|
|
2536
|
-
return Promise.resolve({
|
|
2537
|
-
type: 'FilesystemFile',
|
|
2538
|
-
message: { data: header.slice(offset, offset + readLen) },
|
|
2539
|
-
});
|
|
2540
|
-
}
|
|
2541
|
-
return Promise.reject(new Error(`unexpected call ${name}`));
|
|
2542
|
-
}
|
|
2543
|
-
);
|
|
2544
|
-
(method as any).device = stubDevice({
|
|
2545
|
-
getCommands: () => ({ typedCall }),
|
|
2546
|
-
});
|
|
2547
|
-
|
|
2548
|
-
const getSysResourceBinarySpy = jest
|
|
2549
|
-
.spyOn(firmwareBinaryApi, 'getSysResourceBinary')
|
|
2550
|
-
.mockResolvedValue({ binary: new Uint8Array([9]).buffer });
|
|
2551
2937
|
const getFirmwareLatestReleaseSpy = jest
|
|
2552
2938
|
.spyOn(DataManager, 'getFirmwareLatestRelease')
|
|
2553
2939
|
.mockReturnValue({
|
|
2554
2940
|
required: false,
|
|
2555
2941
|
version: [1, 0, 0],
|
|
2556
2942
|
url: '',
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
resourceManifest: {
|
|
2566
|
-
format: 'okpkg-crate',
|
|
2567
|
-
target: 'CRATE',
|
|
2568
|
-
version: [1, 0, 0],
|
|
2569
|
-
packages: [
|
|
2570
|
-
{
|
|
2571
|
-
path: 'resource/images/images.okpkg',
|
|
2572
|
-
type: 'RESC',
|
|
2573
|
-
payloadHash,
|
|
2574
|
-
},
|
|
2575
|
-
],
|
|
2576
|
-
},
|
|
2577
|
-
fingerprint: '',
|
|
2578
|
-
changelog: {
|
|
2579
|
-
'zh-CN': '',
|
|
2580
|
-
'en-US': '',
|
|
2581
|
-
},
|
|
2582
|
-
});
|
|
2583
|
-
|
|
2584
|
-
const remoteBinaries = await (method as any).prepareRemoteProtocolV2Binaries('universal', {
|
|
2585
|
-
deviceType: 'pro2',
|
|
2586
|
-
firmwareVersion: '1.0.0',
|
|
2587
|
-
capabilities: [],
|
|
2588
|
-
});
|
|
2589
|
-
|
|
2590
|
-
expect(remoteBinaries).toEqual({
|
|
2591
|
-
resourceBinaryMap: [],
|
|
2592
|
-
bootloaderBinary: null,
|
|
2593
|
-
fwBinaryMap: [],
|
|
2594
|
-
installItems: [],
|
|
2595
|
-
});
|
|
2596
|
-
expect(getSysResourceBinarySpy).not.toHaveBeenCalled();
|
|
2597
|
-
expect(typedCall.mock.calls[0][2]).toEqual({
|
|
2598
|
-
path: 'vol0:/resource/images/images.okpkg',
|
|
2599
|
-
});
|
|
2600
|
-
|
|
2601
|
-
getSysResourceBinarySpy.mockRestore();
|
|
2602
|
-
getFirmwareLatestReleaseSpy.mockRestore();
|
|
2603
|
-
});
|
|
2604
|
-
|
|
2605
|
-
test('downloads Pro2 resource crate when resourceManifest package is missing', async () => {
|
|
2606
|
-
const method = new FirmwareUpdateV4({
|
|
2607
|
-
id: 1,
|
|
2608
|
-
payload: {
|
|
2609
|
-
method: 'firmwareUpdateV4',
|
|
2610
|
-
platform: 'web',
|
|
2611
|
-
},
|
|
2612
|
-
});
|
|
2613
|
-
method.init();
|
|
2614
|
-
|
|
2615
|
-
const resourceBinary = new Uint8Array([9]).buffer;
|
|
2616
|
-
const typedCall = jest.fn().mockResolvedValue({
|
|
2617
|
-
type: 'FilesystemPathInfo',
|
|
2618
|
-
message: { exist: false, directory: false, size: 0 },
|
|
2619
|
-
});
|
|
2620
|
-
(method as any).device = stubDevice({
|
|
2621
|
-
getCommands: () => ({ typedCall }),
|
|
2622
|
-
});
|
|
2623
|
-
|
|
2624
|
-
const getSysResourceBinarySpy = jest
|
|
2625
|
-
.spyOn(firmwareBinaryApi, 'getSysResourceBinary')
|
|
2626
|
-
.mockResolvedValue({ binary: resourceBinary });
|
|
2627
|
-
const getFirmwareLatestReleaseSpy = jest
|
|
2628
|
-
.spyOn(DataManager, 'getFirmwareLatestRelease')
|
|
2629
|
-
.mockReturnValue({
|
|
2630
|
-
required: false,
|
|
2631
|
-
version: [1, 0, 0],
|
|
2632
|
-
url: '',
|
|
2633
|
-
upgradeType: 'payload-package-set',
|
|
2634
|
-
installOrder: ['resource'],
|
|
2635
|
-
components: {
|
|
2636
|
-
resource: {
|
|
2637
|
-
target: 'CRATE',
|
|
2638
|
-
url: 'https://example.com/pro2-resource.crate.okpkg',
|
|
2639
|
-
resourceManifest: {
|
|
2640
|
-
format: 'okpkg-crate',
|
|
2641
|
-
target: 'CRATE',
|
|
2642
|
-
version: [1, 0, 0],
|
|
2643
|
-
packages: [{ path: 'resource/images/images.okpkg', type: 'RESC' }],
|
|
2644
|
-
},
|
|
2645
|
-
},
|
|
2646
|
-
},
|
|
2647
|
-
fingerprint: '',
|
|
2648
|
-
changelog: {
|
|
2649
|
-
'zh-CN': '',
|
|
2650
|
-
'en-US': '',
|
|
2651
|
-
},
|
|
2652
|
-
});
|
|
2653
|
-
|
|
2654
|
-
const remoteBinaries = await (method as any).prepareRemoteProtocolV2Binaries('universal', {
|
|
2655
|
-
deviceType: 'pro2',
|
|
2656
|
-
firmwareVersion: '1.0.0',
|
|
2657
|
-
capabilities: [],
|
|
2658
|
-
});
|
|
2659
|
-
|
|
2660
|
-
expect(remoteBinaries.resourceBinaryMap).toEqual([
|
|
2661
|
-
{
|
|
2662
|
-
fileName: 'resource-resource.bin',
|
|
2663
|
-
binary: resourceBinary,
|
|
2664
|
-
targetId: 1,
|
|
2665
|
-
},
|
|
2666
|
-
]);
|
|
2667
|
-
expect(getSysResourceBinarySpy).toHaveBeenCalledWith(
|
|
2668
|
-
'https://example.com/pro2-resource.crate.okpkg'
|
|
2669
|
-
);
|
|
2670
|
-
|
|
2671
|
-
getSysResourceBinarySpy.mockRestore();
|
|
2672
|
-
getFirmwareLatestReleaseSpy.mockRestore();
|
|
2673
|
-
});
|
|
2674
|
-
|
|
2675
|
-
test('maps Pro2 CRATE remote component to resource binary target', async () => {
|
|
2676
|
-
const method = new FirmwareUpdateV4({
|
|
2677
|
-
id: 1,
|
|
2678
|
-
payload: {
|
|
2679
|
-
method: 'firmwareUpdateV4',
|
|
2680
|
-
platform: 'web',
|
|
2681
|
-
},
|
|
2682
|
-
});
|
|
2683
|
-
method.init();
|
|
2684
|
-
|
|
2685
|
-
const resourceBinary = new Uint8Array([1, 2, 3]).buffer;
|
|
2686
|
-
const getSysResourceBinarySpy = jest
|
|
2687
|
-
.spyOn(firmwareBinaryApi, 'getSysResourceBinary')
|
|
2688
|
-
.mockResolvedValue({ binary: resourceBinary });
|
|
2689
|
-
const getFirmwareLatestReleaseSpy = jest
|
|
2690
|
-
.spyOn(DataManager, 'getFirmwareLatestRelease')
|
|
2691
|
-
.mockReturnValue({
|
|
2692
|
-
required: false,
|
|
2693
|
-
version: [1, 0, 0],
|
|
2694
|
-
url: '',
|
|
2695
|
-
upgradeType: 'payload-package-set',
|
|
2696
|
-
installOrder: ['resource'],
|
|
2697
|
-
components: {
|
|
2698
|
-
resource: {
|
|
2699
|
-
target: 'CRATE',
|
|
2700
|
-
url: 'https://example.com/pro2-resource.crate.okpkg',
|
|
2701
|
-
},
|
|
2702
|
-
},
|
|
2703
|
-
fingerprint: '',
|
|
2704
|
-
changelog: {
|
|
2705
|
-
'zh-CN': '',
|
|
2706
|
-
'en-US': '',
|
|
2707
|
-
},
|
|
2708
|
-
});
|
|
2709
|
-
|
|
2710
|
-
const remoteBinaries = await (method as any).prepareRemoteProtocolV2Binaries('universal', {
|
|
2711
|
-
deviceType: 'pro2',
|
|
2712
|
-
firmwareVersion: '0.0.0',
|
|
2713
|
-
capabilities: [],
|
|
2714
|
-
});
|
|
2715
|
-
|
|
2716
|
-
expect(remoteBinaries).toEqual({
|
|
2717
|
-
resourceBinaryMap: [
|
|
2718
|
-
{
|
|
2719
|
-
fileName: 'resource-resource.bin',
|
|
2720
|
-
binary: resourceBinary,
|
|
2721
|
-
targetId: 1,
|
|
2722
|
-
},
|
|
2723
|
-
],
|
|
2724
|
-
bootloaderBinary: null,
|
|
2725
|
-
fwBinaryMap: [],
|
|
2726
|
-
installItems: [
|
|
2727
|
-
{
|
|
2728
|
-
fileName: 'resource-resource.bin',
|
|
2729
|
-
binary: resourceBinary,
|
|
2730
|
-
targetId: 1,
|
|
2731
|
-
kind: 'resource',
|
|
2732
|
-
},
|
|
2733
|
-
],
|
|
2734
|
-
});
|
|
2735
|
-
|
|
2736
|
-
getSysResourceBinarySpy.mockRestore();
|
|
2737
|
-
getFirmwareLatestReleaseSpy.mockRestore();
|
|
2738
|
-
});
|
|
2739
|
-
|
|
2740
|
-
test('downloads only unmatched Pro2 resource crate components by component manifests', async () => {
|
|
2741
|
-
const method = new FirmwareUpdateV4({
|
|
2742
|
-
id: 1,
|
|
2743
|
-
payload: {
|
|
2744
|
-
method: 'firmwareUpdateV4',
|
|
2745
|
-
platform: 'web',
|
|
2746
|
-
},
|
|
2747
|
-
});
|
|
2748
|
-
method.init();
|
|
2749
|
-
|
|
2750
|
-
const imagesPayloadHash = 'ab'.repeat(64);
|
|
2751
|
-
const imagesHeader = buildOkppHeader({ payloadHash: imagesPayloadHash });
|
|
2752
|
-
const fontsBinary = new Uint8Array([7, 8, 9]).buffer;
|
|
2753
|
-
const typedCall = jest.fn(
|
|
2754
|
-
(
|
|
2755
|
-
name: string,
|
|
2756
|
-
_resType: string,
|
|
2757
|
-
params: { path?: string; file?: { offset: number }; chunk_len?: number }
|
|
2758
|
-
) => {
|
|
2759
|
-
if (name === 'FilesystemPathInfoQuery') {
|
|
2760
|
-
const exist = params.path === 'vol0:/resource/images/images.okpkg';
|
|
2761
|
-
return Promise.resolve({
|
|
2762
|
-
type: 'FilesystemPathInfo',
|
|
2763
|
-
message: { exist, directory: false, size: exist ? imagesHeader.byteLength : 0 },
|
|
2764
|
-
});
|
|
2765
|
-
}
|
|
2766
|
-
if (name === 'FilesystemFileRead') {
|
|
2767
|
-
const offset = params.file?.offset ?? 0;
|
|
2768
|
-
const readLen = params.chunk_len ?? 0;
|
|
2769
|
-
return Promise.resolve({
|
|
2770
|
-
type: 'FilesystemFile',
|
|
2771
|
-
message: { data: imagesHeader.slice(offset, offset + readLen) },
|
|
2772
|
-
});
|
|
2773
|
-
}
|
|
2774
|
-
return Promise.reject(new Error(`unexpected call ${name}`));
|
|
2775
|
-
}
|
|
2776
|
-
);
|
|
2777
|
-
(method as any).device = stubDevice({
|
|
2778
|
-
getCommands: () => ({ typedCall }),
|
|
2779
|
-
});
|
|
2780
|
-
|
|
2781
|
-
const getSysResourceBinarySpy = jest
|
|
2782
|
-
.spyOn(firmwareBinaryApi, 'getSysResourceBinary')
|
|
2783
|
-
.mockResolvedValue({ binary: fontsBinary });
|
|
2784
|
-
const getFirmwareLatestReleaseSpy = jest
|
|
2785
|
-
.spyOn(DataManager, 'getFirmwareLatestRelease')
|
|
2786
|
-
.mockReturnValue({
|
|
2787
|
-
required: false,
|
|
2788
|
-
version: [1, 0, 0],
|
|
2789
|
-
url: '',
|
|
2790
|
-
upgradeType: 'payload-package-set',
|
|
2791
|
-
installOrder: ['resourceImages', 'resourceFonts'],
|
|
2792
|
-
components: {
|
|
2793
|
-
resourceImages: {
|
|
2794
|
-
target: 'CRATE',
|
|
2795
|
-
url: 'https://example.com/pro2-resource-images.crate.okpkg',
|
|
2796
|
-
resourceManifest: {
|
|
2797
|
-
format: 'okpkg-crate',
|
|
2798
|
-
target: 'CRATE',
|
|
2799
|
-
version: [1, 0, 0],
|
|
2800
|
-
packages: [
|
|
2801
|
-
{
|
|
2802
|
-
path: 'resource/images/images.okpkg',
|
|
2803
|
-
type: 'RESC',
|
|
2804
|
-
payloadHash: imagesPayloadHash,
|
|
2805
|
-
},
|
|
2806
|
-
],
|
|
2807
|
-
},
|
|
2808
|
-
},
|
|
2809
|
-
resourceFonts: {
|
|
2810
|
-
target: 'CRATE',
|
|
2811
|
-
url: 'https://example.com/pro2-resource-fonts.crate.okpkg',
|
|
2812
|
-
resourceManifest: {
|
|
2813
|
-
format: 'okpkg-crate',
|
|
2814
|
-
target: 'CRATE',
|
|
2815
|
-
version: [1, 0, 0],
|
|
2816
|
-
packages: [{ path: 'resource/font/noto.okpkg', type: 'RESC' }],
|
|
2817
|
-
},
|
|
2943
|
+
resourceBundles: [
|
|
2944
|
+
{
|
|
2945
|
+
name: 'images',
|
|
2946
|
+
url: 'https://example.com/images.okpkg',
|
|
2947
|
+
devicePath: 'vol0:/resource/images/images.okpkg',
|
|
2948
|
+
version: [2, 0, 0],
|
|
2949
|
+
payloadHash: 'ab'.repeat(64),
|
|
2950
|
+
headerHash: 'cd'.repeat(64),
|
|
2818
2951
|
},
|
|
2819
|
-
|
|
2952
|
+
],
|
|
2820
2953
|
fingerprint: '',
|
|
2821
2954
|
changelog: {
|
|
2822
2955
|
'zh-CN': '',
|
|
@@ -2824,160 +2957,28 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
2824
2957
|
},
|
|
2825
2958
|
});
|
|
2826
2959
|
|
|
2827
|
-
const
|
|
2960
|
+
const bundles = (method as any).prepareProtocolV2ResourceBundles('universal', {
|
|
2828
2961
|
deviceType: 'pro2',
|
|
2829
2962
|
firmwareVersion: '1.0.0',
|
|
2830
2963
|
capabilities: [],
|
|
2831
2964
|
});
|
|
2832
2965
|
|
|
2833
|
-
expect(
|
|
2966
|
+
expect(bundles).toEqual([
|
|
2834
2967
|
{
|
|
2835
|
-
|
|
2836
|
-
binary:
|
|
2837
|
-
|
|
2968
|
+
name: 'images',
|
|
2969
|
+
binary: expect.any(ArrayBuffer),
|
|
2970
|
+
devicePath: 'vol0:/resource/images/images.okpkg',
|
|
2971
|
+
url: 'https://example.com/images.okpkg',
|
|
2972
|
+
version: [2, 0, 0],
|
|
2973
|
+
payloadHash: 'ab'.repeat(64),
|
|
2974
|
+
headerHash: 'cd'.repeat(64),
|
|
2838
2975
|
},
|
|
2839
2976
|
]);
|
|
2840
|
-
expect(
|
|
2841
|
-
expect(getSysResourceBinarySpy).toHaveBeenCalledWith(
|
|
2842
|
-
'https://example.com/pro2-resource-fonts.crate.okpkg'
|
|
2843
|
-
);
|
|
2977
|
+
expect(bundles[0].binary.byteLength).toBe(0);
|
|
2844
2978
|
|
|
2845
|
-
getSysResourceBinarySpy.mockRestore();
|
|
2846
2979
|
getFirmwareLatestReleaseSpy.mockRestore();
|
|
2847
2980
|
});
|
|
2848
2981
|
|
|
2849
|
-
test('installs multiple Pro2 resource crates as separate update requests', async () => {
|
|
2850
|
-
const method = new FirmwareUpdateV4({
|
|
2851
|
-
id: 1,
|
|
2852
|
-
payload: {
|
|
2853
|
-
method: 'firmwareUpdateV4',
|
|
2854
|
-
},
|
|
2855
|
-
});
|
|
2856
|
-
|
|
2857
|
-
const operations: string[] = [];
|
|
2858
|
-
method.postTipMessage = jest.fn();
|
|
2859
|
-
method.postProgressMessage = jest.fn();
|
|
2860
|
-
(method as any).protocolV2CommonUpdateProcess = jest
|
|
2861
|
-
.fn()
|
|
2862
|
-
.mockImplementation(params => {
|
|
2863
|
-
operations.push(`write:${params.filePath}`);
|
|
2864
|
-
return Promise.resolve(
|
|
2865
|
-
Number(params.processedSize ?? 0) + Number(params.payload.byteLength)
|
|
2866
|
-
);
|
|
2867
|
-
});
|
|
2868
|
-
(method as any).protocolV2StartFirmwareUpdate = jest.fn().mockImplementation(({ targets }) => {
|
|
2869
|
-
operations.push(
|
|
2870
|
-
`install:${targets.map((target: { path: string }) => target.path).join(',')}`
|
|
2871
|
-
);
|
|
2872
|
-
return Promise.resolve(undefined);
|
|
2873
|
-
});
|
|
2874
|
-
(method as any).waitForProtocolV2FirmwareUpdateComplete = jest
|
|
2875
|
-
.fn()
|
|
2876
|
-
.mockResolvedValue(undefined);
|
|
2877
|
-
|
|
2878
|
-
await (method as any).executeProtocolV2Update({
|
|
2879
|
-
resourceBinaryMap: [
|
|
2880
|
-
{
|
|
2881
|
-
fileName: 'resource-images.bin',
|
|
2882
|
-
binary: new Uint8Array([1]).buffer,
|
|
2883
|
-
targetId: 1,
|
|
2884
|
-
},
|
|
2885
|
-
{
|
|
2886
|
-
fileName: 'resource-fonts.bin',
|
|
2887
|
-
binary: new Uint8Array([2]).buffer,
|
|
2888
|
-
targetId: 1,
|
|
2889
|
-
},
|
|
2890
|
-
],
|
|
2891
|
-
bootloaderBinary: null,
|
|
2892
|
-
fwBinaryMap: [
|
|
2893
|
-
{
|
|
2894
|
-
fileName: 'application_p1.bin',
|
|
2895
|
-
binary: new Uint8Array([3]).buffer,
|
|
2896
|
-
targetId: 4,
|
|
2897
|
-
},
|
|
2898
|
-
],
|
|
2899
|
-
});
|
|
2900
|
-
|
|
2901
|
-
expect((method as any).protocolV2StartFirmwareUpdate).toHaveBeenNthCalledWith(1, {
|
|
2902
|
-
targets: [{ target_id: 1, path: 'vol1:resource-images.bin' }],
|
|
2903
|
-
});
|
|
2904
|
-
expect((method as any).protocolV2StartFirmwareUpdate).toHaveBeenNthCalledWith(2, {
|
|
2905
|
-
targets: [{ target_id: 1, path: 'vol1:resource-fonts.bin' }],
|
|
2906
|
-
});
|
|
2907
|
-
expect((method as any).protocolV2StartFirmwareUpdate).toHaveBeenNthCalledWith(3, {
|
|
2908
|
-
targets: [{ target_id: 4, path: 'vol1:application_p1.bin' }],
|
|
2909
|
-
});
|
|
2910
|
-
expect(operations).toEqual([
|
|
2911
|
-
'write:vol1:resource-images.bin',
|
|
2912
|
-
'write:vol1:resource-fonts.bin',
|
|
2913
|
-
'write:vol1:application_p1.bin',
|
|
2914
|
-
'install:vol1:resource-images.bin',
|
|
2915
|
-
'install:vol1:resource-fonts.bin',
|
|
2916
|
-
'install:vol1:application_p1.bin',
|
|
2917
|
-
]);
|
|
2918
|
-
});
|
|
2919
|
-
|
|
2920
|
-
test('installs merged Pro2 resource crate as one CRATE request before firmware targets', async () => {
|
|
2921
|
-
const method = new FirmwareUpdateV4({
|
|
2922
|
-
id: 1,
|
|
2923
|
-
payload: {
|
|
2924
|
-
method: 'firmwareUpdateV4',
|
|
2925
|
-
},
|
|
2926
|
-
});
|
|
2927
|
-
|
|
2928
|
-
const operations: string[] = [];
|
|
2929
|
-
method.postTipMessage = jest.fn();
|
|
2930
|
-
method.postProgressMessage = jest.fn();
|
|
2931
|
-
(method as any).protocolV2CommonUpdateProcess = jest
|
|
2932
|
-
.fn()
|
|
2933
|
-
.mockImplementation(params => {
|
|
2934
|
-
operations.push(`write:${params.filePath}`);
|
|
2935
|
-
return Promise.resolve(
|
|
2936
|
-
Number(params.processedSize ?? 0) + Number(params.payload.byteLength)
|
|
2937
|
-
);
|
|
2938
|
-
});
|
|
2939
|
-
(method as any).protocolV2StartFirmwareUpdate = jest.fn().mockImplementation(({ targets }) => {
|
|
2940
|
-
operations.push(
|
|
2941
|
-
`install:${targets.map((target: { path: string }) => target.path).join(',')}`
|
|
2942
|
-
);
|
|
2943
|
-
return Promise.resolve(undefined);
|
|
2944
|
-
});
|
|
2945
|
-
(method as any).waitForProtocolV2FirmwareUpdateComplete = jest
|
|
2946
|
-
.fn()
|
|
2947
|
-
.mockResolvedValue(undefined);
|
|
2948
|
-
|
|
2949
|
-
await (method as any).executeProtocolV2Update({
|
|
2950
|
-
resourceBinaryMap: [
|
|
2951
|
-
{
|
|
2952
|
-
fileName: 'resource-resource.bin',
|
|
2953
|
-
binary: new Uint8Array([1, 2]).buffer,
|
|
2954
|
-
targetId: 1,
|
|
2955
|
-
},
|
|
2956
|
-
],
|
|
2957
|
-
bootloaderBinary: null,
|
|
2958
|
-
fwBinaryMap: [
|
|
2959
|
-
{
|
|
2960
|
-
fileName: 'application_p1.bin',
|
|
2961
|
-
binary: new Uint8Array([3]).buffer,
|
|
2962
|
-
targetId: 4,
|
|
2963
|
-
},
|
|
2964
|
-
],
|
|
2965
|
-
});
|
|
2966
|
-
|
|
2967
|
-
expect((method as any).protocolV2StartFirmwareUpdate).toHaveBeenNthCalledWith(1, {
|
|
2968
|
-
targets: [{ target_id: 1, path: 'vol1:resource-resource.bin' }],
|
|
2969
|
-
});
|
|
2970
|
-
expect((method as any).protocolV2StartFirmwareUpdate).toHaveBeenNthCalledWith(2, {
|
|
2971
|
-
targets: [{ target_id: 4, path: 'vol1:application_p1.bin' }],
|
|
2972
|
-
});
|
|
2973
|
-
expect(operations).toEqual([
|
|
2974
|
-
'write:vol1:resource-resource.bin',
|
|
2975
|
-
'write:vol1:application_p1.bin',
|
|
2976
|
-
'install:vol1:resource-resource.bin',
|
|
2977
|
-
'install:vol1:application_p1.bin',
|
|
2978
|
-
]);
|
|
2979
|
-
});
|
|
2980
|
-
|
|
2981
2982
|
test('skips Pro2 firmware components when configured versions are already installed', async () => {
|
|
2982
2983
|
const method = new FirmwareUpdateV4({
|
|
2983
2984
|
id: 1,
|
|
@@ -3029,7 +3030,6 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3029
3030
|
});
|
|
3030
3031
|
|
|
3031
3032
|
expect(remoteBinaries).toEqual({
|
|
3032
|
-
resourceBinaryMap: [],
|
|
3033
3033
|
bootloaderBinary: null,
|
|
3034
3034
|
fwBinaryMap: [],
|
|
3035
3035
|
installItems: [],
|
|
@@ -3051,13 +3051,12 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3051
3051
|
method.init();
|
|
3052
3052
|
|
|
3053
3053
|
const remoteBinaries = {
|
|
3054
|
-
resourceBinaryMap: [],
|
|
3055
3054
|
bootloaderBinary: new Uint8Array([1]).buffer,
|
|
3056
3055
|
fwBinaryMap: [
|
|
3057
3056
|
{
|
|
3058
3057
|
fileName: 'application_p1.bin',
|
|
3059
3058
|
binary: new Uint8Array([2]).buffer,
|
|
3060
|
-
targetId:
|
|
3059
|
+
targetId: 3,
|
|
3061
3060
|
},
|
|
3062
3061
|
],
|
|
3063
3062
|
};
|
|
@@ -3066,7 +3065,6 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3066
3065
|
originalDescriptor: { protocolType: 'V2' },
|
|
3067
3066
|
features: { deviceType: 'pro2', firmwareVersion: '0.0.0', capabilities: [] },
|
|
3068
3067
|
});
|
|
3069
|
-
(method as any).prepareResourceBinaries = jest.fn().mockResolvedValue([]);
|
|
3070
3068
|
(method as any).prepareRemoteProtocolV2Binaries = jest.fn().mockResolvedValue(remoteBinaries);
|
|
3071
3069
|
(method as any).enterProtocolV2BootloaderMode = jest.fn().mockResolvedValue(true);
|
|
3072
3070
|
(method as any).executeProtocolV2Update = jest.fn().mockResolvedValue(undefined);
|
|
@@ -3082,7 +3080,6 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3082
3080
|
|
|
3083
3081
|
expect((method as any).prepareRemoteProtocolV2Binaries).toHaveBeenCalledTimes(1);
|
|
3084
3082
|
expect((method as any).executeProtocolV2Update).toHaveBeenCalledWith({
|
|
3085
|
-
resourceBinaryMap: remoteBinaries.resourceBinaryMap,
|
|
3086
3083
|
bootloaderBinary: remoteBinaries.bootloaderBinary,
|
|
3087
3084
|
fwBinaryMap: remoteBinaries.fwBinaryMap,
|
|
3088
3085
|
});
|
|
@@ -3103,7 +3100,6 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3103
3100
|
originalDescriptor: { protocolType: 'V2' },
|
|
3104
3101
|
features: { deviceType: 'pro2', firmwareVersion: '0.0.0', capabilities: [] },
|
|
3105
3102
|
});
|
|
3106
|
-
(method as any).prepareResourceBinaries = jest.fn().mockResolvedValue([]);
|
|
3107
3103
|
(method as any).prepareRemoteProtocolV2Binaries = jest.fn();
|
|
3108
3104
|
(method as any).enterProtocolV2BootloaderMode = jest.fn().mockResolvedValue(true);
|
|
3109
3105
|
(method as any).executeProtocolV2Update = jest.fn().mockResolvedValue(undefined);
|
|
@@ -3119,12 +3115,90 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3119
3115
|
|
|
3120
3116
|
expect((method as any).prepareRemoteProtocolV2Binaries).not.toHaveBeenCalled();
|
|
3121
3117
|
expect((method as any).executeProtocolV2Update).toHaveBeenCalledWith({
|
|
3122
|
-
resourceBinaryMap: [],
|
|
3123
3118
|
bootloaderBinary: null,
|
|
3124
3119
|
fwBinaryMap: [{ fileName: 'coprocessor.bin', binary: expect.anything(), targetId: 6 }],
|
|
3125
3120
|
});
|
|
3126
3121
|
});
|
|
3127
3122
|
|
|
3123
|
+
test('treats manual resource bundles as explicit payload without remote firmware auto-fill', async () => {
|
|
3124
|
+
const resourceBundle = new Uint8Array([1, 2, 3]).buffer;
|
|
3125
|
+
const method = new FirmwareUpdateV4({
|
|
3126
|
+
id: 1,
|
|
3127
|
+
payload: {
|
|
3128
|
+
method: 'firmwareUpdateV4',
|
|
3129
|
+
platform: 'web',
|
|
3130
|
+
resourceBundleFiles: [
|
|
3131
|
+
{
|
|
3132
|
+
binary: resourceBundle,
|
|
3133
|
+
devicePath: 'vol0:/resource/images/images.okpkg',
|
|
3134
|
+
},
|
|
3135
|
+
],
|
|
3136
|
+
},
|
|
3137
|
+
});
|
|
3138
|
+
method.init();
|
|
3139
|
+
|
|
3140
|
+
(method as any).device = stubDevice({
|
|
3141
|
+
originalDescriptor: { protocolType: 'V2' },
|
|
3142
|
+
features: { deviceType: 'pro2', firmwareVersion: '0.0.0', capabilities: [] },
|
|
3143
|
+
});
|
|
3144
|
+
(method as any).prepareRemoteProtocolV2Binaries = jest.fn();
|
|
3145
|
+
(method as any).enterProtocolV2BootloaderMode = jest.fn().mockResolvedValue(true);
|
|
3146
|
+
(method as any).executeProtocolV2Update = jest.fn().mockResolvedValue(undefined);
|
|
3147
|
+
(method as any).exitProtocolV2BootloaderToNormal = jest.fn().mockResolvedValue(undefined);
|
|
3148
|
+
(method as any).waitForProtocolV2FinalFeatures = jest.fn().mockResolvedValue({
|
|
3149
|
+
bootloaderVersion: '1.0.0',
|
|
3150
|
+
bleVersion: '0.0.0',
|
|
3151
|
+
firmwareVersion: '1.0.0',
|
|
3152
|
+
});
|
|
3153
|
+
method.postTipMessage = jest.fn();
|
|
3154
|
+
|
|
3155
|
+
await method.run();
|
|
3156
|
+
|
|
3157
|
+
expect((method as any).prepareRemoteProtocolV2Binaries).not.toHaveBeenCalled();
|
|
3158
|
+
expect((method as any).executeProtocolV2Update).toHaveBeenCalledWith(
|
|
3159
|
+
expect.objectContaining({
|
|
3160
|
+
resourceBundles: [
|
|
3161
|
+
{
|
|
3162
|
+
name: 'images.okpkg',
|
|
3163
|
+
binary: resourceBundle,
|
|
3164
|
+
devicePath: 'vol0:/resource/images/images.okpkg',
|
|
3165
|
+
},
|
|
3166
|
+
],
|
|
3167
|
+
})
|
|
3168
|
+
);
|
|
3169
|
+
});
|
|
3170
|
+
|
|
3171
|
+
test('syncs resource bundles without sending a firmware install request', async () => {
|
|
3172
|
+
const method = new FirmwareUpdateV4({
|
|
3173
|
+
id: 1,
|
|
3174
|
+
payload: {
|
|
3175
|
+
method: 'firmwareUpdateV4',
|
|
3176
|
+
},
|
|
3177
|
+
});
|
|
3178
|
+
|
|
3179
|
+
method.postTipMessage = jest.fn();
|
|
3180
|
+
method.postProgressMessage = jest.fn();
|
|
3181
|
+
(method as any).protocolV2CommonUpdateProcess = jest.fn().mockResolvedValue(3);
|
|
3182
|
+
(method as any).protocolV2StartFirmwareUpdate = jest.fn();
|
|
3183
|
+
(method as any).waitForProtocolV2FirmwareUpdateComplete = jest.fn();
|
|
3184
|
+
|
|
3185
|
+
await (method as any).executeProtocolV2Update({
|
|
3186
|
+
resourceBundles: [
|
|
3187
|
+
{
|
|
3188
|
+
name: 'images.okpkg',
|
|
3189
|
+
binary: new Uint8Array([1, 2, 3]).buffer,
|
|
3190
|
+
devicePath: 'vol0:/resource/images/images.okpkg',
|
|
3191
|
+
},
|
|
3192
|
+
],
|
|
3193
|
+
bootloaderBinary: null,
|
|
3194
|
+
fwBinaryMap: [],
|
|
3195
|
+
});
|
|
3196
|
+
|
|
3197
|
+
expect((method as any).protocolV2CommonUpdateProcess).toHaveBeenCalledTimes(1);
|
|
3198
|
+
expect((method as any).protocolV2StartFirmwareUpdate).not.toHaveBeenCalled();
|
|
3199
|
+
expect((method as any).waitForProtocolV2FirmwareUpdateComplete).not.toHaveBeenCalled();
|
|
3200
|
+
});
|
|
3201
|
+
|
|
3128
3202
|
test('uses absolute processed_byte offsets and disables append for firmware file writes', async () => {
|
|
3129
3203
|
const method = new FirmwareUpdateV4({
|
|
3130
3204
|
id: 1,
|
|
@@ -3166,6 +3240,60 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3166
3240
|
expect(writePayloads.map(payload => payload.ui_percentage)).toEqual([0, 100]);
|
|
3167
3241
|
});
|
|
3168
3242
|
|
|
3243
|
+
test('restarts the whole file from offset zero after an ambiguous chunk write failure', async () => {
|
|
3244
|
+
const method = new FirmwareUpdateV4({
|
|
3245
|
+
id: 1,
|
|
3246
|
+
payload: {
|
|
3247
|
+
method: 'firmwareUpdateV4',
|
|
3248
|
+
},
|
|
3249
|
+
});
|
|
3250
|
+
const writeOffsets: number[] = [];
|
|
3251
|
+
let failedSecondChunk = false;
|
|
3252
|
+
const typedCall = jest.fn(
|
|
3253
|
+
(
|
|
3254
|
+
_name: string,
|
|
3255
|
+
_resType: string,
|
|
3256
|
+
params: { file: { offset: number; data: { byteLength: number } } }
|
|
3257
|
+
) => {
|
|
3258
|
+
writeOffsets.push(params.file.offset);
|
|
3259
|
+
if (params.file.offset === 4000 && !failedSecondChunk) {
|
|
3260
|
+
failedSecondChunk = true;
|
|
3261
|
+
return Promise.reject(new Error('response lost after device write'));
|
|
3262
|
+
}
|
|
3263
|
+
return Promise.resolve({
|
|
3264
|
+
type: 'FilesystemFile',
|
|
3265
|
+
message: {
|
|
3266
|
+
processed_byte: params.file.offset + params.file.data.byteLength,
|
|
3267
|
+
},
|
|
3268
|
+
});
|
|
3269
|
+
}
|
|
3270
|
+
);
|
|
3271
|
+
const setTimeoutSpy = jest.spyOn(global, 'setTimeout').mockImplementation(((
|
|
3272
|
+
callback: (...args: any[]) => void
|
|
3273
|
+
) => {
|
|
3274
|
+
callback();
|
|
3275
|
+
return 0 as any;
|
|
3276
|
+
}) as typeof setTimeout);
|
|
3277
|
+
|
|
3278
|
+
(method as any).device = stubDevice({
|
|
3279
|
+
getCommands: () => ({ typedCall }),
|
|
3280
|
+
});
|
|
3281
|
+
method.postProgressMessage = jest.fn();
|
|
3282
|
+
|
|
3283
|
+
try {
|
|
3284
|
+
await (method as any).protocolV2CommonUpdateProcess({
|
|
3285
|
+
payload: new Uint8Array(8001).buffer,
|
|
3286
|
+
filePath: 'vol0:/firmware.bin',
|
|
3287
|
+
processedSize: 0,
|
|
3288
|
+
totalSize: 8001,
|
|
3289
|
+
});
|
|
3290
|
+
} finally {
|
|
3291
|
+
setTimeoutSpy.mockRestore();
|
|
3292
|
+
}
|
|
3293
|
+
|
|
3294
|
+
expect(writeOffsets).toEqual([0, 4000, 0, 4000, 8000]);
|
|
3295
|
+
});
|
|
3296
|
+
|
|
3169
3297
|
test('continues to DeviceFirmwareUpdate when FilesystemFileWrite returns processed chunk length', async () => {
|
|
3170
3298
|
const method = new FirmwareUpdateV4({
|
|
3171
3299
|
id: 1,
|
|
@@ -3182,6 +3310,12 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3182
3310
|
},
|
|
3183
3311
|
});
|
|
3184
3312
|
}
|
|
3313
|
+
if (name === 'FilesystemPathInfoQuery') {
|
|
3314
|
+
return Promise.resolve({
|
|
3315
|
+
type: 'FilesystemPathInfo',
|
|
3316
|
+
message: { exist: true, directory: false, size: 4097 },
|
|
3317
|
+
});
|
|
3318
|
+
}
|
|
3185
3319
|
if (name === 'DeviceFirmwareUpdateRequest') {
|
|
3186
3320
|
return Promise.resolve({ type: 'Success', message: { message: 'ok' } });
|
|
3187
3321
|
}
|
|
@@ -3203,7 +3337,6 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3203
3337
|
method.postTipMessage = jest.fn();
|
|
3204
3338
|
|
|
3205
3339
|
await (method as any).executeProtocolV2Update({
|
|
3206
|
-
resourceBinaryMap: [],
|
|
3207
3340
|
bootloaderBinary: null,
|
|
3208
3341
|
fwBinaryMap: [
|
|
3209
3342
|
{
|
|
@@ -3218,9 +3351,11 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3218
3351
|
'DeviceFirmwareUpdateRequest',
|
|
3219
3352
|
['Success', 'DeviceFirmwareUpdateStatus'],
|
|
3220
3353
|
{
|
|
3221
|
-
targets: [{ target_id: 4, path: '
|
|
3354
|
+
targets: [{ target_id: 4, path: 'vol0:/firmware.bin' }],
|
|
3222
3355
|
},
|
|
3223
|
-
expect.
|
|
3356
|
+
expect.objectContaining({
|
|
3357
|
+
timeoutMs: 3 * 60 * 1000,
|
|
3358
|
+
})
|
|
3224
3359
|
);
|
|
3225
3360
|
});
|
|
3226
3361
|
|
|
@@ -3404,6 +3539,30 @@ describe('Protocol V2 firmware update method', () => {
|
|
|
3404
3539
|
expect(typedCall).not.toHaveBeenCalled();
|
|
3405
3540
|
});
|
|
3406
3541
|
|
|
3542
|
+
test.each([0, 2])(
|
|
3543
|
+
'rejects firmware target %s because the Pro2 bootloader cannot install it',
|
|
3544
|
+
async targetId => {
|
|
3545
|
+
const typedCall = jest.fn();
|
|
3546
|
+
const method = new DeviceFirmwareUpdate({
|
|
3547
|
+
id: 1,
|
|
3548
|
+
payload: {
|
|
3549
|
+
method: 'deviceFirmwareUpdate',
|
|
3550
|
+
targetId,
|
|
3551
|
+
path: 'vol0:firmware.bin',
|
|
3552
|
+
},
|
|
3553
|
+
});
|
|
3554
|
+
method.init();
|
|
3555
|
+
(method as any).device = stubDevice({
|
|
3556
|
+
commands: { typedCall },
|
|
3557
|
+
});
|
|
3558
|
+
|
|
3559
|
+
await expect(method.run()).rejects.toMatchObject({
|
|
3560
|
+
errorCode: HardwareErrorCode.CallMethodInvalidParameter,
|
|
3561
|
+
});
|
|
3562
|
+
expect(typedCall).not.toHaveBeenCalled();
|
|
3563
|
+
}
|
|
3564
|
+
);
|
|
3565
|
+
|
|
3407
3566
|
test('accepts targetId alias inside firmware targets', async () => {
|
|
3408
3567
|
const typedCall = jest.fn().mockResolvedValue({ message: {} });
|
|
3409
3568
|
const method = new DeviceFirmwareUpdate({
|
|
@@ -3413,8 +3572,8 @@ describe('Protocol V2 firmware update method', () => {
|
|
|
3413
3572
|
targets: [
|
|
3414
3573
|
{
|
|
3415
3574
|
target_id: undefined,
|
|
3416
|
-
targetId:
|
|
3417
|
-
path: 'vol0:
|
|
3575
|
+
targetId: 1,
|
|
3576
|
+
path: 'vol0:resource.crate.okpkg',
|
|
3418
3577
|
},
|
|
3419
3578
|
],
|
|
3420
3579
|
} as any,
|
|
@@ -3426,7 +3585,7 @@ describe('Protocol V2 firmware update method', () => {
|
|
|
3426
3585
|
|
|
3427
3586
|
await method.run();
|
|
3428
3587
|
expect(typedCall.mock.calls[0][2]).toEqual({
|
|
3429
|
-
targets: [{ target_id:
|
|
3588
|
+
targets: [{ target_id: 1, path: 'vol0:resource.crate.okpkg' }],
|
|
3430
3589
|
});
|
|
3431
3590
|
});
|
|
3432
3591
|
|
|
@@ -3436,8 +3595,8 @@ describe('Protocol V2 firmware update method', () => {
|
|
|
3436
3595
|
id: 1,
|
|
3437
3596
|
payload: {
|
|
3438
3597
|
method: 'deviceFirmwareUpdate',
|
|
3439
|
-
targetId: '
|
|
3440
|
-
path: 'vol0:
|
|
3598
|
+
targetId: 'FW_MGMT_TARGET_APPLICATION_P1',
|
|
3599
|
+
path: 'vol0:application_p1.bin',
|
|
3441
3600
|
},
|
|
3442
3601
|
});
|
|
3443
3602
|
method.init();
|
|
@@ -3447,7 +3606,7 @@ describe('Protocol V2 firmware update method', () => {
|
|
|
3447
3606
|
|
|
3448
3607
|
await method.run();
|
|
3449
3608
|
expect(typedCall.mock.calls[0][2]).toEqual({
|
|
3450
|
-
targets: [{ target_id:
|
|
3609
|
+
targets: [{ target_id: 4, path: 'vol0:application_p1.bin' }],
|
|
3451
3610
|
});
|
|
3452
3611
|
});
|
|
3453
3612
|
|
|
@@ -3577,6 +3736,23 @@ describe('Protocol V2 current low-level methods', () => {
|
|
|
3577
3736
|
|
|
3578
3737
|
expect(typedCall).toHaveBeenCalledWith('FilesystemPermissionFix', 'Success', {});
|
|
3579
3738
|
});
|
|
3739
|
+
|
|
3740
|
+
test('sends required FilesystemFormat partition flags', async () => {
|
|
3741
|
+
const typedCall = jest.fn().mockResolvedValue({ message: {} });
|
|
3742
|
+
const method = new FilesystemFormat({
|
|
3743
|
+
id: 1,
|
|
3744
|
+
payload: { method: 'filesystemFormat' },
|
|
3745
|
+
});
|
|
3746
|
+
method.init();
|
|
3747
|
+
(method as any).device = stubDevice({ commands: { typedCall } });
|
|
3748
|
+
|
|
3749
|
+
await method.run();
|
|
3750
|
+
|
|
3751
|
+
expect(typedCall).toHaveBeenCalledWith('FilesystemFormat', 'Success', {
|
|
3752
|
+
data: true,
|
|
3753
|
+
user: true,
|
|
3754
|
+
});
|
|
3755
|
+
});
|
|
3580
3756
|
});
|
|
3581
3757
|
|
|
3582
3758
|
describe('Protocol V2 raw device info method', () => {
|
|
@@ -3859,6 +4035,36 @@ describe('Protocol V2 file write method', () => {
|
|
|
3859
4035
|
});
|
|
3860
4036
|
|
|
3861
4037
|
describe('Protocol V2 file read method', () => {
|
|
4038
|
+
test('uses a 900-byte chunk limit by default in BLE environments', async () => {
|
|
4039
|
+
const getSettingsSpy = jest.spyOn(DataManager, 'getSettings').mockReturnValue('react-native');
|
|
4040
|
+
const typedCall = jest
|
|
4041
|
+
.fn()
|
|
4042
|
+
.mockResolvedValueOnce({ message: { data: new Uint8Array(900) } })
|
|
4043
|
+
.mockResolvedValueOnce({ message: { data: new Uint8Array(1) } });
|
|
4044
|
+
const method = new FileRead({
|
|
4045
|
+
id: 1,
|
|
4046
|
+
payload: {
|
|
4047
|
+
method: 'fileRead',
|
|
4048
|
+
path: 'vol1:test.bin',
|
|
4049
|
+
offset: 0,
|
|
4050
|
+
totalSize: 901,
|
|
4051
|
+
},
|
|
4052
|
+
});
|
|
4053
|
+
(method as any).device = stubDevice({ commands: { typedCall } });
|
|
4054
|
+
|
|
4055
|
+
try {
|
|
4056
|
+
method.init();
|
|
4057
|
+
await method.run();
|
|
4058
|
+
} finally {
|
|
4059
|
+
getSettingsSpy.mockRestore();
|
|
4060
|
+
}
|
|
4061
|
+
|
|
4062
|
+
expect(typedCall).toHaveBeenCalledTimes(2);
|
|
4063
|
+
expect(typedCall.mock.calls[0][2].chunk_len).toBe(900);
|
|
4064
|
+
expect(typedCall.mock.calls[1][2].file.offset).toBe(900);
|
|
4065
|
+
expect(typedCall.mock.calls[1][2].chunk_len).toBe(1);
|
|
4066
|
+
});
|
|
4067
|
+
|
|
3862
4068
|
test('rejects invalid read and directory parameters before transport call', () => {
|
|
3863
4069
|
expect(() => {
|
|
3864
4070
|
const method = new FileRead({
|