@onekeyfe/hd-core 1.2.0-alpha.78 → 1.2.0-alpha.79

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.
Files changed (38) hide show
  1. package/__tests__/check-all-firmware-release-protocol-v2.test.ts +12 -71
  2. package/__tests__/protocol-v2-resources.test.ts +102 -222
  3. package/__tests__/protocol-v2.test.ts +110 -380
  4. package/dist/api/CheckAllFirmwareRelease.d.ts.map +1 -1
  5. package/dist/api/FirmwareUpdateV4.d.ts +0 -6
  6. package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
  7. package/dist/api/UploadPortfolio.d.ts.map +1 -1
  8. package/dist/data-manager/DataManager.d.ts +1 -2
  9. package/dist/data-manager/DataManager.d.ts.map +1 -1
  10. package/dist/index.d.ts +74 -32
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js +255 -499
  13. package/dist/inject.d.ts.map +1 -1
  14. package/dist/protocols/protocol-v2/resources.d.ts +31 -28
  15. package/dist/protocols/protocol-v2/resources.d.ts.map +1 -1
  16. package/dist/types/api/checkAllFirmwareRelease.d.ts +1 -0
  17. package/dist/types/api/checkAllFirmwareRelease.d.ts.map +1 -1
  18. package/dist/types/api/export.d.ts +1 -0
  19. package/dist/types/api/export.d.ts.map +1 -1
  20. package/dist/types/api/index.d.ts +2 -0
  21. package/dist/types/api/index.d.ts.map +1 -1
  22. package/dist/types/api/protocolV2ResourceManifest.d.ts +17 -0
  23. package/dist/types/api/protocolV2ResourceManifest.d.ts.map +1 -0
  24. package/dist/types/settings.d.ts +30 -21
  25. package/dist/types/settings.d.ts.map +1 -1
  26. package/package.json +4 -4
  27. package/src/api/CheckAllFirmwareRelease.ts +5 -34
  28. package/src/api/FirmwareUpdateV4.ts +48 -220
  29. package/src/api/UploadPortfolio.ts +18 -0
  30. package/src/data-manager/DataManager.ts +2 -6
  31. package/src/index.ts +6 -0
  32. package/src/inject.ts +2 -0
  33. package/src/protocols/protocol-v2/resources.ts +193 -324
  34. package/src/types/api/checkAllFirmwareRelease.ts +1 -0
  35. package/src/types/api/export.ts +4 -0
  36. package/src/types/api/index.ts +2 -0
  37. package/src/types/api/protocolV2ResourceManifest.ts +19 -0
  38. package/src/types/settings.ts +30 -36
@@ -5,7 +5,6 @@ import CheckAllFirmwareRelease, {
5
5
  } from '../src/api/CheckAllFirmwareRelease';
6
6
  import { DataManager } from '../src/data-manager';
7
7
  import { createCoreApi } from '../src/inject';
8
- import { PROTOCOL_V2_RESOURCE_DEVICE_PATHS } from '../src/protocols/protocol-v2/resources';
9
8
 
10
9
  import type { CoreApi } from '../src/types/api';
11
10
  import type { DeviceStateVersions, IFirmwareReleaseInfo } from '../src/types';
@@ -73,67 +72,8 @@ const release: IFirmwareReleaseInfo = {
73
72
  ],
74
73
  };
75
74
 
76
- const stableResources = ['images', 'animation', 'wallpaper', 'translations', 'roobert', 'noto'].map(
77
- (type, index) => ({
78
- type,
79
- url: `https://example.com/${type}.okpkg`,
80
- size: 0x52a0 + index + 1,
81
- fileHash: 'a'.repeat(64),
82
- headerHash: index.toString(16).padStart(128, '0'),
83
- })
84
- );
85
-
86
- const createFilesystemTypedCall = (missingAll = false) => {
87
- const resourceByPath = new Map(
88
- stableResources.map(resource => [
89
- PROTOCOL_V2_RESOURCE_DEVICE_PATHS[
90
- resource.type as keyof typeof PROTOCOL_V2_RESOURCE_DEVICE_PATHS
91
- ],
92
- resource,
93
- ])
94
- );
95
- return jest.fn((requestType: string, _responseType: string, payload: Record<string, any>) => {
96
- if (requestType === 'ResourceInventoryGet') {
97
- throw new Error('ResourceInventoryGet is unavailable on released firmware');
98
- }
99
- if (requestType === 'FilesystemPathInfoQuery') {
100
- const resource = resourceByPath.get(payload.path);
101
- return {
102
- message: {
103
- exist: Boolean(resource) && !missingAll,
104
- directory: false,
105
- size: missingAll ? 0 : resource?.size,
106
- },
107
- };
108
- }
109
- if (requestType === 'FilesystemFileRead') {
110
- const resource = resourceByPath.get(payload.file.path);
111
- if (!resource || missingAll) throw new Error('missing resource');
112
- const header = new Uint8Array(0x52a0);
113
- const view = new DataView(header.buffer);
114
- 'OKPP'.split('').forEach((char, index) => {
115
- header[index] = char.charCodeAt(0);
116
- });
117
- 'RESC'.split('').forEach((char, index) => {
118
- header[0x08 + index] = char.charCodeAt(0);
119
- });
120
- view.setUint32(0x0c, header.byteLength, true);
121
- for (let index = 0; index < resource.headerHash.length / 2; index++) {
122
- header[0x240 + index] = Number.parseInt(
123
- resource.headerHash.slice(index * 2, index * 2 + 2),
124
- 16
125
- );
126
- }
127
- const offset = Number(payload.file.offset);
128
- const chunkLength = Number(payload.chunk_len);
129
- return {
130
- message: {
131
- data: header.slice(offset, offset + chunkLength),
132
- },
133
- };
134
- }
135
- throw new Error(`Unexpected request: ${requestType}`);
136
- });
75
+ const resourceSource = {
76
+ manifestUrl: 'https://example.com/pro2-resource/manifest.json',
137
77
  };
138
78
 
139
79
  describe('checkAllFirmwareRelease Protocol V2 support', () => {
@@ -370,7 +310,7 @@ describe('checkAllFirmwareRelease Protocol V2 support', () => {
370
310
  status: { mode: 'normal' },
371
311
  versions: currentVersions,
372
312
  });
373
- const typedCall = createFilesystemTypedCall(true);
313
+ const typedCall = jest.fn();
374
314
  method.device = {
375
315
  isProtocolV2: () => true,
376
316
  features: {
@@ -381,13 +321,14 @@ describe('checkAllFirmwareRelease Protocol V2 support', () => {
381
321
  getCommands: () => ({ typedCall }),
382
322
  } as unknown as CheckAllFirmwareRelease['device'];
383
323
  jest.spyOn(DataManager, 'getFirmwareLatestRelease').mockReturnValue(release);
384
- jest.spyOn(DataManager, 'getProtocolV2Resources').mockReturnValue(stableResources as any);
324
+ jest.spyOn(DataManager, 'getProtocolV2ResourceSource').mockReturnValue(resourceSource);
385
325
 
386
326
  await expect(method.run()).resolves.toMatchObject({
387
327
  protocol: 'V2',
388
328
  deviceType: 'pro2',
389
329
  status: 'required',
390
330
  resourceStatus: 'unknown',
331
+ resourceManifestUrl: resourceSource.manifestUrl,
391
332
  targetsToUpdate: ['boot', 'app_v1'],
392
333
  firmware: {
393
334
  status: 'required',
@@ -437,7 +378,7 @@ describe('checkAllFirmwareRelease Protocol V2 support', () => {
437
378
  getCommands: () => ({ typedCall: jest.fn() }),
438
379
  } as unknown as CheckAllFirmwareRelease['device'];
439
380
  jest.spyOn(DataManager, 'getFirmwareLatestRelease').mockReturnValue(packageSet);
440
- jest.spyOn(DataManager, 'getProtocolV2Resources').mockReturnValue(undefined);
381
+ jest.spyOn(DataManager, 'getProtocolV2ResourceSource').mockReturnValue(undefined);
441
382
 
442
383
  await expect(method.run()).resolves.toMatchObject({
443
384
  status: 'outdated',
@@ -449,7 +390,7 @@ describe('checkAllFirmwareRelease Protocol V2 support', () => {
449
390
  });
450
391
 
451
392
  test.each(['bootloader', 'romloader'] as const)(
452
- 'uses filesystem inventory in %s mode instead of forcing all resources',
393
+ 'does not read vol0 resource inventory in %s mode',
453
394
  async mode => {
454
395
  const method = new CheckAllFirmwareRelease({
455
396
  id: 1,
@@ -459,7 +400,7 @@ describe('checkAllFirmwareRelease Protocol V2 support', () => {
459
400
  },
460
401
  });
461
402
  method.init();
462
- const typedCall = createFilesystemTypedCall();
403
+ const typedCall = jest.fn();
463
404
  method.device = {
464
405
  isProtocolV2: () => true,
465
406
  features: { deviceType: 'pro2', firmwareVersion: '1.0.0' },
@@ -471,15 +412,15 @@ describe('checkAllFirmwareRelease Protocol V2 support', () => {
471
412
  getCommands: () => ({ typedCall }),
472
413
  } as unknown as CheckAllFirmwareRelease['device'];
473
414
  jest.spyOn(DataManager, 'getFirmwareLatestRelease').mockReturnValue(release);
474
- jest.spyOn(DataManager, 'getProtocolV2Resources').mockReturnValue(stableResources as any);
415
+ jest.spyOn(DataManager, 'getProtocolV2ResourceSource').mockReturnValue(resourceSource);
475
416
 
476
417
  await expect(method.run()).resolves.toMatchObject({
477
418
  protocol: 'V2',
478
- resourceStatus: 'valid',
419
+ resourceStatus: 'unknown',
420
+ resourceManifestUrl: resourceSource.manifestUrl,
479
421
  targetsToUpdate: ['boot', 'app_v1'],
480
422
  });
481
- expect(typedCall.mock.calls.some(call => call[0] === 'ResourceInventoryGet')).toBe(false);
482
- expect(typedCall.mock.calls.some(call => call[0] === 'FilesystemFileRead')).toBe(true);
423
+ expect(typedCall).not.toHaveBeenCalled();
483
424
  }
484
425
  );
485
426
 
@@ -4,20 +4,13 @@ import { HardwareErrorCode } from '@onekeyfe/hd-shared';
4
4
 
5
5
  import { DataManager } from '../src/data-manager';
6
6
  import {
7
- PROTOCOL_V2_RESOURCE_DEVICE_PATHS,
8
- PROTOCOL_V2_RESOURCE_TYPES,
9
- buildProtocolV2ResourceUpdatePlan,
10
7
  isProtocolV2ResourceFileValid,
8
+ parseProtocolV2ResourceManifest,
11
9
  parseProtocolV2Resources,
12
- readProtocolV2ResourceInventory,
10
+ prepareProtocolV2ResourceFiles,
13
11
  } from '../src/protocols/protocol-v2/resources';
14
12
 
15
- import type {
16
- ConnectSettings,
17
- IProtocolV2BootResources,
18
- IProtocolV2Resource,
19
- RemoteConfigResponse,
20
- } from '../src/types';
13
+ import type { ConnectSettings, RemoteConfigResponse } from '../src/types';
21
14
 
22
15
  jest.mock('axios');
23
16
  jest.mock('../src/data/config', () => ({
@@ -28,45 +21,69 @@ jest.mock('../src/data/config', () => ({
28
21
  const bytesToHex = (bytes: Uint8Array) =>
29
22
  Array.from(bytes, byte => byte.toString(16).padStart(2, '0')).join('');
30
23
 
31
- const PROTOCOL_V2_OKPP_HEADER_SIZE = 0x52a0;
32
-
33
- const createResourceHeader = (headerHash: string) => {
34
- const header = new Uint8Array(PROTOCOL_V2_OKPP_HEADER_SIZE);
35
- const view = new DataView(header.buffer);
36
- 'OKPP'.split('').forEach((char, index) => {
37
- header[index] = char.charCodeAt(0);
38
- });
39
- 'RESC'.split('').forEach((char, index) => {
40
- header[0x08 + index] = char.charCodeAt(0);
41
- });
42
- view.setUint32(0x0c, header.byteLength, true);
43
- for (let index = 0; index < headerHash.length / 2; index++) {
44
- header[0x240 + index] = Number.parseInt(headerHash.slice(index * 2, index * 2 + 2), 16);
45
- }
46
- return header;
24
+ const resourceSource = {
25
+ manifestUrl: 'https://example.com/resource/manifest.json',
47
26
  };
48
27
 
49
- const resources: IProtocolV2Resource[] = PROTOCOL_V2_RESOURCE_TYPES.map((type, index) => ({
50
- type,
51
- url: `https://example.com/${type}.okpkg`,
52
- size: index + 100,
53
- fileHash: index.toString(16).padStart(64, '0'),
54
- headerHash: index.toString(16).padStart(128, '0'),
55
- }));
28
+ const manifestFiles = [
29
+ ['bundles/firmware_logo-build.okpkg', 'firmware_logo.okpkg', 'vol0:/bundles/firmware_logo.okpkg'],
30
+ ['bundles/font/noto-build.okpkg', 'noto.okpkg', 'vol0:/bundles/font/noto.okpkg'],
31
+ ['bundles/font/roobert-build.okpkg', 'roobert.okpkg', 'vol0:/bundles/font/roobert.okpkg'],
32
+ [
33
+ 'bundles/images/animation-build.okpkg',
34
+ 'animation.okpkg',
35
+ 'vol0:/bundles/images/animation.okpkg',
36
+ ],
37
+ ['bundles/images/images-build.okpkg', 'images.okpkg', 'vol0:/bundles/images/images.okpkg'],
38
+ [
39
+ 'bundles/images/wallpaper-build.okpkg',
40
+ 'wallpaper.okpkg',
41
+ 'vol0:/bundles/images/wallpaper.okpkg',
42
+ ],
43
+ [
44
+ 'bundles/translations/translations-build.okpkg',
45
+ 'translations.okpkg',
46
+ 'vol0:/bundles/translations/translations.okpkg',
47
+ ],
48
+ [
49
+ 'loaders/bootloader/boot_resource-build.okpkg',
50
+ 'boot_resource.okpkg',
51
+ 'vol0:/loaders/bootloader/boot_resource.okpkg',
52
+ ],
53
+ ['loaders/rom/params-build.okpkg', 'params.okpkg', 'vol0:/loaders/rom/params.okpkg'],
54
+ ].map(([archive_path, original_name, device_path], index) => {
55
+ const binary = new Uint8Array([index + 1]).buffer;
56
+ return {
57
+ archive_path,
58
+ original_name,
59
+ device_path,
60
+ size: 1,
61
+ sha256: bytesToHex(sha256(new Uint8Array(binary))),
62
+ signed: true,
63
+ sig_algo: device_path.startsWith('vol0:/bundles/') ? 'ed25519' : 'mldsa65',
64
+ payload_version: '1.0.0',
65
+ binary,
66
+ };
67
+ });
56
68
 
57
- const bootResources: IProtocolV2BootResources = {
58
- required: false,
59
- target: 'RES',
60
- manifestUrl: 'https://example.com/manifest.json',
61
- files: [
62
- {
63
- name: 'bootloader_crest.bin',
64
- url: 'https://example.com/assets/bootloader_crest.bin',
65
- devicePath: 'vol0:/assets/loaders/boot.staging/graphics/bootloader_crest.bin',
66
- size: 1234,
67
- fileHash: 'ab'.repeat(32),
68
- },
69
+ const resourceManifest = {
70
+ schema: 1,
71
+ artifact_name: 'pro2-resource-build',
72
+ release_name: 'resource-build',
73
+ variant: 'resource',
74
+ commit: 'a'.repeat(40),
75
+ short_sha: 'aaaaaaa',
76
+ timestamp_utc: '20260807_091424',
77
+ core_version: '1.0.0',
78
+ key_set: 'dev',
79
+ device_root: 'vol0:',
80
+ restore_mode: 'bootloader_update',
81
+ trees: [
82
+ { path: 'bundles', device: 'vol0:/bundles' },
83
+ { path: 'loaders/bootloader', device: 'vol0:/loaders/bootloader' },
84
+ { path: 'loaders/rom', device: 'vol0:/loaders/rom' },
69
85
  ],
86
+ files: manifestFiles.map(({ binary: _binary, ...file }) => file),
70
87
  };
71
88
 
72
89
  const createSettings = (configFetcher: ConnectSettings['configFetcher']): ConnectSettings =>
@@ -85,7 +102,7 @@ const createRemoteConfig = (): RemoteConfigResponse =>
85
102
  mini: { firmware: [], ble: [] },
86
103
  touch: { firmware: [], ble: [] },
87
104
  pro: { firmware: [], ble: [] },
88
- pro2: { firmware: [], ble: [], resources: { stable: resources, boot: bootResources } },
105
+ pro2: { firmware: [], ble: [], resources: { source: resourceSource } },
89
106
  bridge: {},
90
107
  } as unknown as RemoteConfigResponse);
91
108
 
@@ -95,186 +112,51 @@ describe('Pro2 resource configuration', () => {
95
112
  DataManager.lastCheckTimestamp = 0;
96
113
  });
97
114
 
98
- test('accepts exactly seven resources and normalizes their deterministic order', () => {
99
- const parsed = parseProtocolV2Resources({
100
- stable: [...resources].reverse(),
101
- boot: bootResources,
115
+ test('accepts the manifest source and the CI schema 1 file set', () => {
116
+ expect(parseProtocolV2Resources({ source: resourceSource })).toEqual({
117
+ source: resourceSource,
102
118
  });
103
-
104
- expect(parsed?.stable.map(item => item.type)).toEqual(PROTOCOL_V2_RESOURCE_TYPES);
105
- expect(parsed?.boot).toEqual(bootResources);
106
- expect(PROTOCOL_V2_RESOURCE_DEVICE_PATHS.translations).toBe(
107
- 'vol0:/bundles/translations/translations.okpkg'
108
- );
109
- expect(PROTOCOL_V2_RESOURCE_DEVICE_PATHS.firmware_logo).toBe(
110
- 'vol0:/bundles/firmware_logo.okpkg'
111
- );
119
+ expect(parseProtocolV2ResourceManifest(resourceManifest).files).toHaveLength(9);
112
120
  });
113
121
 
114
- test('rejects incomplete, duplicate, or malformed stable sets', () => {
115
- expect(() => parseProtocolV2Resources({ stable: resources.slice(1) })).toThrow(
116
- '7 unique resource types'
117
- );
122
+ test('rejects missing source and malformed manifest paths', () => {
123
+ expect(() => parseProtocolV2Resources({})).toThrow('source is required');
118
124
  expect(() =>
119
- parseProtocolV2Resources({ stable: [...resources.slice(0, 6), resources[0]] })
120
- ).toThrow('7 unique resource types');
125
+ parseProtocolV2Resources({ source: { manifestUrl: 'http://example.com/manifest.json' } })
126
+ ).toThrow('must use HTTPS');
121
127
  expect(() =>
122
- parseProtocolV2Resources({
123
- stable: resources.map((resource, index) =>
124
- index === 0 ? { ...resource, headerHash: 'bad' } : resource
128
+ parseProtocolV2ResourceManifest({
129
+ ...resourceManifest,
130
+ files: resourceManifest.files.map((file, index) =>
131
+ index === 0 ? { ...file, archive_path: '../outside.okpkg' } : file
125
132
  ),
126
133
  })
127
- ).toThrow('headerHash');
134
+ ).toThrow('archive_path');
128
135
  });
129
136
 
130
- test('requires boot resources to remain optional and use RES file entries', () => {
131
- expect(() =>
132
- parseProtocolV2Resources({
133
- stable: resources,
134
- boot: { ...bootResources, required: true },
135
- })
136
- ).toThrow('required flag');
137
- expect(() =>
138
- parseProtocolV2Resources({
139
- stable: resources,
140
- boot: { ...bootResources, target: 'INVALID' },
141
- })
142
- ).toThrow('expected RES');
137
+ test('verifies selected manifest files and preserves manifest device paths', () => {
138
+ const prepared = prepareProtocolV2ResourceFiles({
139
+ manifest: resourceManifest,
140
+ files: manifestFiles.map(file => ({
141
+ archivePath: file.archive_path,
142
+ binary: file.binary,
143
+ })),
144
+ targetsToUpdate: ['boot_resources'],
145
+ });
146
+ expect(prepared.map(file => file.devicePath)).toEqual([
147
+ 'vol0:/loaders/bootloader/boot_resource.okpkg',
148
+ 'vol0:/loaders/rom/params.okpkg',
149
+ ]);
143
150
  expect(() =>
144
- parseProtocolV2Resources({
145
- stable: resources,
146
- boot: {
147
- ...bootResources,
148
- files: [{ ...bootResources.files[0], devicePath: '../outside.bin' }],
149
- },
151
+ prepareProtocolV2ResourceFiles({
152
+ manifest: resourceManifest,
153
+ files: manifestFiles.map((file, index) => ({
154
+ archivePath: file.archive_path,
155
+ binary: index === 0 ? new Uint8Array([0]).buffer : file.binary,
156
+ })),
157
+ targetsToUpdate: ['resource'],
150
158
  })
151
- ).toThrow('devicePath');
152
-
153
- for (const devicePath of [
154
- 'vol0:/assets/loaders\\bootloader_crest.bin',
155
- 'vol0:/assets/loaders\\\\bootloader_crest.bin',
156
- ]) {
157
- expect(() =>
158
- parseProtocolV2Resources({
159
- stable: resources,
160
- boot: {
161
- ...bootResources,
162
- files: [{ ...bootResources.files[0], devicePath }],
163
- },
164
- })
165
- ).toThrow('devicePath');
166
- }
167
- });
168
-
169
- test('downloads nothing when all resource identities match', () => {
170
- const inventory = resources.map(({ type, size, headerHash }) => ({ type, size, headerHash }));
171
-
172
- expect(
173
- buildProtocolV2ResourceUpdatePlan({ resources, inventory, mode: 'application' })
174
- ).toEqual({ status: 'valid', resources: [] });
175
- });
176
-
177
- test('builds inventory from existing filesystem calls without ResourceInventoryGet', async () => {
178
- const installedResources = resources.map((resource, index) => ({
179
- ...resource,
180
- size: PROTOCOL_V2_OKPP_HEADER_SIZE + index + 1,
181
- }));
182
- const resourceByPath = new Map(
183
- installedResources.map(resource => [
184
- PROTOCOL_V2_RESOURCE_DEVICE_PATHS[resource.type],
185
- resource,
186
- ])
187
- );
188
- const typedCall = jest.fn(
189
- (requestType: string, _responseType: string, payload: Record<string, any>) => {
190
- if (requestType === 'ResourceInventoryGet') {
191
- throw new Error('ResourceInventoryGet is unavailable on released firmware');
192
- }
193
- if (requestType === 'FilesystemPathInfoQuery') {
194
- const resource = resourceByPath.get(payload.path);
195
- return {
196
- message: {
197
- exist: Boolean(resource),
198
- directory: false,
199
- size: resource?.size ?? 0,
200
- },
201
- };
202
- }
203
- if (requestType === 'FilesystemFileRead') {
204
- const resource = resourceByPath.get(payload.file.path);
205
- if (!resource) throw new Error('missing resource');
206
- const header = createResourceHeader(resource.headerHash);
207
- const offset = Number(payload.file.offset);
208
- const chunkLength = Number(payload.chunk_len);
209
- return {
210
- message: {
211
- data: header.slice(offset, offset + chunkLength),
212
- },
213
- };
214
- }
215
- throw new Error(`Unexpected request: ${requestType}`);
216
- }
217
- );
218
-
219
- await expect(
220
- readProtocolV2ResourceInventory({
221
- commands: { typedCall },
222
- resources: installedResources,
223
- chunkSize: 4000,
224
- })
225
- ).resolves.toEqual(
226
- installedResources.map(({ type, size, headerHash }) => ({ type, size, headerHash }))
227
- );
228
- expect(typedCall.mock.calls.some(call => call[0] === 'ResourceInventoryGet')).toBe(false);
229
- expect(typedCall.mock.calls.filter(call => call[0] === 'FilesystemPathInfoQuery')).toHaveLength(
230
- 7
231
- );
232
- expect(typedCall.mock.calls.some(call => call[0] === 'FilesystemFileRead')).toBe(true);
233
- });
234
-
235
- test('selects only the changed or missing resource in application mode', () => {
236
- const inventory = resources
237
- .filter(resource => resource.type !== 'noto')
238
- .map(({ type, size, headerHash }) => ({
239
- type,
240
- size: type === 'images' ? size + 1 : size,
241
- headerHash,
242
- }));
243
-
244
- const result = buildProtocolV2ResourceUpdatePlan({
245
- resources,
246
- inventory,
247
- mode: 'application',
248
- });
249
-
250
- expect(result.status).toBe('outdated');
251
- expect(result.resources.map(resource => resource.type)).toEqual(['images', 'noto']);
252
- });
253
-
254
- test('reports unknown without an application inventory and selects all in recovery mode', () => {
255
- expect(buildProtocolV2ResourceUpdatePlan({ resources, mode: 'application' })).toEqual({
256
- status: 'unknown',
257
- resources: [],
258
- });
259
- expect(
260
- buildProtocolV2ResourceUpdatePlan({ resources, mode: 'bootloader-recovery' }).resources
261
- ).toHaveLength(7);
262
- });
263
-
264
- test('uses a filesystem inventory for incremental recovery mode updates', () => {
265
- const inventory = resources.slice(1).map(({ type, size, headerHash }) => ({
266
- type,
267
- size,
268
- headerHash,
269
- }));
270
-
271
- expect(
272
- buildProtocolV2ResourceUpdatePlan({
273
- resources,
274
- inventory,
275
- mode: 'bootloader-recovery',
276
- }).resources.map(resource => resource.type)
277
- ).toEqual(['images']);
159
+ ).toThrow('verification failed');
278
160
  });
279
161
 
280
162
  test('verifies both full file size and SHA-256 before transfer', () => {
@@ -289,7 +171,7 @@ describe('Pro2 resource configuration', () => {
289
171
  );
290
172
  });
291
173
 
292
- test('applies a validated pre-release config and exposes the stable resource set', async () => {
174
+ test('applies a validated pre-release config and exposes its manifest source', async () => {
293
175
  const configFetcher = jest.fn().mockResolvedValue(createRemoteConfig());
294
176
 
295
177
  await expect(DataManager.load(createSettings(configFetcher))).resolves.toBe(true);
@@ -297,8 +179,7 @@ describe('Pro2 resource configuration', () => {
297
179
  expect(configFetcher).toHaveBeenCalledWith(
298
180
  expect.stringMatching(/^https:\/\/data\.onekey\.so\/pre-config\.json\?noCache=/)
299
181
  );
300
- expect(DataManager.getProtocolV2Resources()).toEqual(resources);
301
- expect(DataManager.getProtocolV2BootResources()).toEqual(bootResources);
182
+ expect(DataManager.getProtocolV2ResourceSource()).toEqual(resourceSource);
302
183
  expect(DataManager.lastCheckTimestamp).toBeGreaterThan(0);
303
184
  });
304
185
 
@@ -314,18 +195,17 @@ describe('Pro2 resource configuration', () => {
314
195
 
315
196
  test('keeps base SDK initialization available when remote Pro2 resources are invalid', async () => {
316
197
  const remoteConfig = createRemoteConfig();
317
- (remoteConfig.pro2 as { resources?: unknown }).resources = { stable: [] };
198
+ (remoteConfig.pro2 as { resources?: unknown }).resources = { source: {} };
318
199
  const configFetcher = jest.fn().mockResolvedValue(remoteConfig);
319
200
 
320
201
  await expect(DataManager.load(createSettings(configFetcher))).resolves.toBe(true);
321
202
 
322
- expect(DataManager.getProtocolV2Resources()).toBeUndefined();
323
- expect(DataManager.getProtocolV2BootResources()).toBeUndefined();
203
+ expect(DataManager.getProtocolV2ResourceSource()).toBeUndefined();
324
204
  });
325
205
 
326
206
  test('only blocks resource mutation when the refreshed Pro2 resources are invalid', async () => {
327
207
  const remoteConfig = createRemoteConfig();
328
- (remoteConfig.pro2 as { resources?: unknown }).resources = { stable: [] };
208
+ (remoteConfig.pro2 as { resources?: unknown }).resources = { source: {} };
329
209
  const settings = createSettings(jest.fn().mockResolvedValue(remoteConfig));
330
210
  DataManager.settings = settings;
331
211