@onekeyfe/hd-core 1.2.0-alpha.95 → 1.2.0-alpha.97

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 (89) hide show
  1. package/__tests__/check-all-firmware-release-protocol-v2.test.ts +84 -404
  2. package/__tests__/firmware-update/device-update-bootloader.test.ts +2 -6
  3. package/__tests__/firmware-update/firmware-host-binding.test.ts +0 -9
  4. package/__tests__/firmware-update/firmware-update-bootloader-poll.test.ts +112 -0
  5. package/__tests__/firmware-update/firmware-update-plan.test.ts +70 -131
  6. package/__tests__/firmware-update/firmware-update-prepared-plan.test.ts +1 -53
  7. package/__tests__/protocol-v2-resources.test.ts +232 -148
  8. package/__tests__/protocol-v2.test.ts +517 -1106
  9. package/dist/api/CheckAllFirmwareRelease.d.ts +3 -2
  10. package/dist/api/CheckAllFirmwareRelease.d.ts.map +1 -1
  11. package/dist/api/CheckBLEFirmwareRelease.d.ts +2 -3
  12. package/dist/api/CheckBLEFirmwareRelease.d.ts.map +1 -1
  13. package/dist/api/CheckBootloaderRelease.d.ts +1 -2
  14. package/dist/api/CheckBootloaderRelease.d.ts.map +1 -1
  15. package/dist/api/CheckFirmwareRelease.d.ts +2 -3
  16. package/dist/api/CheckFirmwareRelease.d.ts.map +1 -1
  17. package/dist/api/FirmwareUpdateV2.d.ts +0 -1
  18. package/dist/api/FirmwareUpdateV2.d.ts.map +1 -1
  19. package/dist/api/FirmwareUpdateV3.d.ts.map +1 -1
  20. package/dist/api/FirmwareUpdateV4.d.ts +8 -6
  21. package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
  22. package/dist/api/UploadPortfolio.d.ts.map +1 -1
  23. package/dist/api/device/DeviceUpdateBootloader.d.ts.map +1 -1
  24. package/dist/api/firmware/FirmwareHostBinding.d.ts +1 -1
  25. package/dist/api/firmware/FirmwareHostBinding.d.ts.map +1 -1
  26. package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts.map +1 -1
  27. package/dist/api/firmware/FirmwareUpdatePlan.d.ts +1 -48
  28. package/dist/api/firmware/FirmwareUpdatePlan.d.ts.map +1 -1
  29. package/dist/api/firmware/FirmwareUpdatePreparedPlan.d.ts.map +1 -1
  30. package/dist/api/firmware/getBinary.d.ts +1 -0
  31. package/dist/api/firmware/getBinary.d.ts.map +1 -1
  32. package/dist/data-manager/DataManager.d.ts +3 -4
  33. package/dist/data-manager/DataManager.d.ts.map +1 -1
  34. package/dist/index.d.ts +111 -103
  35. package/dist/index.d.ts.map +1 -1
  36. package/dist/index.js +849 -1301
  37. package/dist/protocols/protocol-v2/resources.d.ts +28 -10
  38. package/dist/protocols/protocol-v2/resources.d.ts.map +1 -1
  39. package/dist/types/api/checkAllFirmwareRelease.d.ts +8 -20
  40. package/dist/types/api/checkAllFirmwareRelease.d.ts.map +1 -1
  41. package/dist/types/api/checkBLEFirmwareRelease.d.ts +13 -2
  42. package/dist/types/api/checkBLEFirmwareRelease.d.ts.map +1 -1
  43. package/dist/types/api/checkBootloaderRelease.d.ts +6 -2
  44. package/dist/types/api/checkBootloaderRelease.d.ts.map +1 -1
  45. package/dist/types/api/checkFirmwareRelease.d.ts +13 -2
  46. package/dist/types/api/checkFirmwareRelease.d.ts.map +1 -1
  47. package/dist/types/api/checkFirmwareTypeAvailable.d.ts +2 -2
  48. package/dist/types/api/checkFirmwareTypeAvailable.d.ts.map +1 -1
  49. package/dist/types/api/export.d.ts +1 -1
  50. package/dist/types/api/export.d.ts.map +1 -1
  51. package/dist/types/api/firmwareUpdate.d.ts +11 -3
  52. package/dist/types/api/firmwareUpdate.d.ts.map +1 -1
  53. package/dist/types/settings.d.ts +36 -35
  54. package/dist/types/settings.d.ts.map +1 -1
  55. package/package.json +4 -4
  56. package/src/api/CheckAllFirmwareRelease.ts +80 -108
  57. package/src/api/CheckBLEFirmwareRelease.ts +5 -37
  58. package/src/api/CheckBootloaderRelease.ts +3 -35
  59. package/src/api/CheckFirmwareRelease.ts +7 -35
  60. package/src/api/FirmwareUpdateV2.ts +3 -15
  61. package/src/api/FirmwareUpdateV3.ts +6 -13
  62. package/src/api/FirmwareUpdateV4.ts +407 -824
  63. package/src/api/UploadPortfolio.ts +0 -18
  64. package/src/api/device/DeviceUpdateBootloader.ts +5 -26
  65. package/src/api/firmware/FirmwareHostBinding.ts +3 -16
  66. package/src/api/firmware/FirmwareUpdateBaseMethod.ts +2 -0
  67. package/src/api/firmware/FirmwareUpdatePlan.ts +88 -226
  68. package/src/api/firmware/FirmwareUpdatePreparedPlan.ts +3 -10
  69. package/src/data-manager/DataManager.ts +19 -35
  70. package/src/index.ts +0 -10
  71. package/src/protocols/protocol-v2/resources.ts +341 -157
  72. package/src/types/api/checkAllFirmwareRelease.ts +12 -27
  73. package/src/types/api/checkBLEFirmwareRelease.ts +13 -4
  74. package/src/types/api/checkBootloaderRelease.ts +6 -2
  75. package/src/types/api/checkFirmwareRelease.ts +13 -2
  76. package/src/types/api/checkFirmwareTypeAvailable.ts +2 -2
  77. package/src/types/api/export.ts +1 -6
  78. package/src/types/api/firmwareUpdate.ts +16 -10
  79. package/src/types/settings.ts +61 -35
  80. package/src/utils/deviceFeaturesUtils.ts +2 -2
  81. package/__tests__/check-firmware-release-protocol-v2.test.ts +0 -199
  82. package/__tests__/firmware-memory-host.test.ts +0 -112
  83. package/__tests__/firmware-update/firmware-prepared-host-digest.test.ts +0 -448
  84. package/dist/api/firmware/FirmwareMemoryHost.d.ts +0 -22
  85. package/dist/api/firmware/FirmwareMemoryHost.d.ts.map +0 -1
  86. package/dist/api/firmware/protocolV2Release.d.ts +0 -33
  87. package/dist/api/firmware/protocolV2Release.d.ts.map +0 -1
  88. package/src/api/firmware/FirmwareMemoryHost.ts +0 -142
  89. package/src/api/firmware/protocolV2Release.ts +0 -168
@@ -1,14 +1,23 @@
1
1
  import axios from 'axios';
2
2
  import { sha256 } from '@noble/hashes/sha256';
3
- import { EDeviceType, HardwareErrorCode } from '@onekeyfe/hd-shared';
3
+ import { HardwareErrorCode } from '@onekeyfe/hd-shared';
4
4
 
5
5
  import { DataManager } from '../src/data-manager';
6
6
  import {
7
- parseProtocolV2ResourceManifest,
7
+ PROTOCOL_V2_RESOURCE_DEVICE_PATHS,
8
+ PROTOCOL_V2_RESOURCE_TYPES,
9
+ buildProtocolV2ResourceUpdatePlan,
10
+ isProtocolV2ResourceFileValid,
8
11
  parseProtocolV2Resources,
12
+ readProtocolV2ResourceInventory,
9
13
  } from '../src/protocols/protocol-v2/resources';
10
14
 
11
- import type { ConnectSettings, RemoteConfigResponse } from '../src/types';
15
+ import type {
16
+ ConnectSettings,
17
+ IProtocolV2BootResources,
18
+ IProtocolV2Resource,
19
+ RemoteConfigResponse,
20
+ } from '../src/types';
12
21
 
13
22
  jest.mock('axios');
14
23
  jest.mock('../src/data/config', () => ({
@@ -19,77 +28,45 @@ jest.mock('../src/data/config', () => ({
19
28
  const bytesToHex = (bytes: Uint8Array) =>
20
29
  Array.from(bytes, byte => byte.toString(16).padStart(2, '0')).join('');
21
30
 
22
- const resourceSource = {
23
- archiveUrl: 'https://example.com/resource/pro2-resource.zip',
24
- archiveSha256: 'a'.repeat(64),
25
- archiveSize: 16_815_479,
26
- };
31
+ const PROTOCOL_V2_OKPP_HEADER_SIZE = 0x52a0;
27
32
 
28
- const neoResourceSource = {
29
- archiveUrl: 'https://example.com/resource/neo-resource.zip',
30
- archiveSha256: 'b'.repeat(64),
31
- archiveSize: 12_345_678,
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;
32
47
  };
33
48
 
34
- const manifestFiles = [
35
- ['bundles/firmware_logo-build.okpkg', 'firmware_logo.okpkg', 'vol0:/bundles/firmware_logo.okpkg'],
36
- ['bundles/font/noto-build.okpkg', 'noto.okpkg', 'vol0:/bundles/font/noto.okpkg'],
37
- ['bundles/font/roobert-build.okpkg', 'roobert.okpkg', 'vol0:/bundles/font/roobert.okpkg'],
38
- [
39
- 'bundles/images/animation-build.okpkg',
40
- 'animation.okpkg',
41
- 'vol0:/bundles/images/animation.okpkg',
42
- ],
43
- ['bundles/images/images-build.okpkg', 'images.okpkg', 'vol0:/bundles/images/images.okpkg'],
44
- [
45
- 'bundles/images/wallpaper-build.okpkg',
46
- 'wallpaper.okpkg',
47
- 'vol0:/bundles/images/wallpaper.okpkg',
48
- ],
49
- [
50
- 'bundles/translations/translations-build.okpkg',
51
- 'translations.okpkg',
52
- 'vol0:/bundles/translations/translations.okpkg',
53
- ],
54
- [
55
- 'loaders/bootloader/boot_resource-build.okpkg',
56
- 'boot_resource.okpkg',
57
- 'vol0:/loaders/bootloader/boot_resource.okpkg',
58
- ],
59
- ['loaders/rom/params-build.okpkg', 'params.okpkg', 'vol0:/loaders/rom/params.okpkg'],
60
- ].map(([archive_path, original_name, device_path], index) => {
61
- const binary = new Uint8Array([index + 1]).buffer;
62
- return {
63
- archive_path,
64
- original_name,
65
- device_path,
66
- size: 1,
67
- sha256: bytesToHex(sha256(new Uint8Array(binary))),
68
- signed: true,
69
- sig_algo: device_path.startsWith('vol0:/bundles/') ? 'ed25519' : 'mldsa65',
70
- payload_version: '1.0.0',
71
- binary,
72
- };
73
- });
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
+ }));
74
56
 
75
- const resourceManifest = {
76
- schema: 1,
77
- artifact_name: 'pro2-resource-build',
78
- release_name: 'resource-build',
79
- variant: 'resource',
80
- commit: 'a'.repeat(40),
81
- short_sha: 'aaaaaaa',
82
- timestamp_utc: '20260807_091424',
83
- core_version: '1.0.0',
84
- key_set: 'dev',
85
- device_root: 'vol0:',
86
- restore_mode: 'bootloader_update',
87
- trees: [
88
- { path: 'bundles', device: 'vol0:/bundles' },
89
- { path: 'loaders/bootloader', device: 'vol0:/loaders/bootloader' },
90
- { path: 'loaders/rom', device: 'vol0:/loaders/rom' },
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
+ },
91
69
  ],
92
- files: manifestFiles.map(({ binary: _binary, ...file }) => file),
93
70
  };
94
71
 
95
72
  const createSettings = (configFetcher: ConnectSettings['configFetcher']): ConnectSettings =>
@@ -108,8 +85,7 @@ const createRemoteConfig = (): RemoteConfigResponse =>
108
85
  mini: { firmware: [], ble: [] },
109
86
  touch: { firmware: [], ble: [] },
110
87
  pro: { firmware: [], ble: [] },
111
- pro2: { firmware: [], ble: [], resources: { source: resourceSource } },
112
- neo: { firmware: [], ble: [], resources: { source: neoResourceSource } },
88
+ pro2: { firmware: [], ble: [], resources: { stable: resources, boot: bootResources } },
113
89
  bridge: {},
114
90
  } as unknown as RemoteConfigResponse);
115
91
 
@@ -119,44 +95,201 @@ describe('Pro2 resource configuration', () => {
119
95
  DataManager.lastCheckTimestamp = 0;
120
96
  });
121
97
 
122
- test('accepts the manifest source and the CI schema 1 file set', () => {
123
- expect(parseProtocolV2Resources({ source: resourceSource })).toEqual({
124
- source: resourceSource,
98
+ test('accepts exactly seven resources and normalizes their deterministic order', () => {
99
+ const parsed = parseProtocolV2Resources({
100
+ stable: [...resources].reverse(),
101
+ boot: bootResources,
125
102
  });
126
- expect(parseProtocolV2ResourceManifest(resourceManifest).files).toHaveLength(9);
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
+ );
127
112
  });
128
113
 
129
- test('rejects missing source and malformed manifest paths', () => {
130
- expect(() => parseProtocolV2Resources({})).toThrow('source is required');
114
+ test('rejects incomplete, duplicate, or malformed stable sets', () => {
115
+ expect(() => parseProtocolV2Resources({ stable: resources.slice(1) })).toThrow(
116
+ '7 unique resource types'
117
+ );
118
+ expect(() =>
119
+ parseProtocolV2Resources({ stable: [...resources.slice(0, 6), resources[0]] })
120
+ ).toThrow('7 unique resource types');
131
121
  expect(() =>
132
122
  parseProtocolV2Resources({
133
- source: {
134
- ...resourceSource,
135
- archiveUrl: 'http://example.com/pro2-resource.zip',
136
- },
123
+ stable: resources.map((resource, index) =>
124
+ index === 0 ? { ...resource, headerHash: 'bad' } : resource
125
+ ),
137
126
  })
138
- ).toThrow('must use HTTPS');
127
+ ).toThrow('headerHash');
128
+ });
129
+
130
+ test('requires boot resources to remain optional and use RES file entries', () => {
139
131
  expect(() =>
140
132
  parseProtocolV2Resources({
141
- source: { ...resourceSource, archiveSha256: 'invalid' },
133
+ stable: resources,
134
+ boot: { ...bootResources, required: true },
142
135
  })
143
- ).toThrow('SHA-256');
136
+ ).toThrow('required flag');
144
137
  expect(() =>
145
138
  parseProtocolV2Resources({
146
- source: { ...resourceSource, archiveSize: 0 },
139
+ stable: resources,
140
+ boot: { ...bootResources, target: 'INVALID' },
147
141
  })
148
- ).toThrow('positive integer');
142
+ ).toThrow('expected RES');
149
143
  expect(() =>
150
- parseProtocolV2ResourceManifest({
151
- ...resourceManifest,
152
- files: resourceManifest.files.map((file, index) =>
153
- index === 0 ? { ...file, archive_path: '../outside.okpkg' } : file
154
- ),
144
+ parseProtocolV2Resources({
145
+ stable: resources,
146
+ boot: {
147
+ ...bootResources,
148
+ files: [{ ...bootResources.files[0], devicePath: '../outside.bin' }],
149
+ },
150
+ })
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,
155
224
  })
156
- ).toThrow('archive_path');
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']);
278
+ });
279
+
280
+ test('verifies both full file size and SHA-256 before transfer', () => {
281
+ const bytes = new Uint8Array([1, 2, 3]);
282
+ const binary = bytes.buffer;
283
+ const identity = { size: bytes.byteLength, fileHash: bytesToHex(sha256(bytes)) };
284
+
285
+ expect(isProtocolV2ResourceFileValid(binary, identity)).toBe(true);
286
+ expect(isProtocolV2ResourceFileValid(binary, { ...identity, size: 4 })).toBe(false);
287
+ expect(isProtocolV2ResourceFileValid(binary, { ...identity, fileHash: '0'.repeat(64) })).toBe(
288
+ false
289
+ );
157
290
  });
158
291
 
159
- test('applies a validated pre-release config and exposes its archive source', async () => {
292
+ test('applies a validated pre-release config and exposes the stable resource set', async () => {
160
293
  const configFetcher = jest.fn().mockResolvedValue(createRemoteConfig());
161
294
 
162
295
  await expect(DataManager.load(createSettings(configFetcher))).resolves.toBe(true);
@@ -164,7 +297,8 @@ describe('Pro2 resource configuration', () => {
164
297
  expect(configFetcher).toHaveBeenCalledWith(
165
298
  expect.stringMatching(/^https:\/\/data\.onekey\.so\/pre-config\.json\?noCache=/)
166
299
  );
167
- expect(DataManager.getProtocolV2ResourceSource()).toEqual(resourceSource);
300
+ expect(DataManager.getProtocolV2Resources()).toEqual(resources);
301
+ expect(DataManager.getProtocolV2BootResources()).toEqual(bootResources);
168
302
  expect(DataManager.lastCheckTimestamp).toBeGreaterThan(0);
169
303
  });
170
304
 
@@ -180,45 +314,18 @@ describe('Pro2 resource configuration', () => {
180
314
 
181
315
  test('keeps base SDK initialization available when remote Pro2 resources are invalid', async () => {
182
316
  const remoteConfig = createRemoteConfig();
183
- (remoteConfig.pro2 as { resources?: unknown }).resources = { source: {} };
317
+ (remoteConfig.pro2 as { resources?: unknown }).resources = { stable: [] };
184
318
  const configFetcher = jest.fn().mockResolvedValue(remoteConfig);
185
319
 
186
320
  await expect(DataManager.load(createSettings(configFetcher))).resolves.toBe(true);
187
321
 
188
- expect(DataManager.getProtocolV2ResourceSource()).toBeUndefined();
189
- expect(DataManager.protocolV2ResourcesConfigError).toBeInstanceOf(Error);
190
- expect(DataManager.getProtocolV2ResourceSource(EDeviceType.Neo)).toEqual(neoResourceSource);
191
- });
192
-
193
- test('keeps Pro2 resources available when remote Neo resources are invalid', async () => {
194
- const remoteConfig = createRemoteConfig();
195
- (remoteConfig.neo as { resources?: unknown }).resources = { source: {} };
196
- const configFetcher = jest.fn().mockResolvedValue(remoteConfig);
197
-
198
- await expect(DataManager.load(createSettings(configFetcher))).resolves.toBe(true);
199
-
200
- expect(DataManager.getProtocolV2ResourceSource(EDeviceType.Pro2)).toEqual(resourceSource);
201
- expect(DataManager.getProtocolV2ResourceSource(EDeviceType.Neo)).toBeUndefined();
202
- await expect(
203
- DataManager.forceReloadData({
204
- requireResources: true,
205
- resourceDeviceType: EDeviceType.Pro2,
206
- })
207
- ).resolves.toBeUndefined();
208
- await expect(
209
- DataManager.forceReloadData({
210
- requireResources: true,
211
- resourceDeviceType: EDeviceType.Neo,
212
- })
213
- ).rejects.toMatchObject({
214
- errorCode: HardwareErrorCode.FirmwareUpdateDownloadFailed,
215
- message: expect.stringContaining('Invalid Neo resources config'),
216
- });
322
+ expect(DataManager.getProtocolV2Resources()).toBeUndefined();
323
+ expect(DataManager.getProtocolV2BootResources()).toBeUndefined();
217
324
  });
218
325
 
219
326
  test('only blocks resource mutation when the refreshed Pro2 resources are invalid', async () => {
220
327
  const remoteConfig = createRemoteConfig();
221
- (remoteConfig.pro2 as { resources?: unknown }).resources = { source: {} };
328
+ (remoteConfig.pro2 as { resources?: unknown }).resources = { stable: [] };
222
329
  const settings = createSettings(jest.fn().mockResolvedValue(remoteConfig));
223
330
  DataManager.settings = settings;
224
331
 
@@ -230,29 +337,6 @@ describe('Pro2 resource configuration', () => {
230
337
  expect(DataManager.lastCheckTimestamp).toBeGreaterThan(0);
231
338
  });
232
339
 
233
- test('checks resource configuration errors for the requested device only', async () => {
234
- const remoteConfig = createRemoteConfig();
235
- (remoteConfig.pro2 as { resources?: unknown }).resources = { source: {} };
236
- const settings = createSettings(jest.fn().mockResolvedValue(remoteConfig));
237
- DataManager.settings = settings;
238
-
239
- await expect(
240
- DataManager.forceReloadData({
241
- requireResources: true,
242
- resourceDeviceType: EDeviceType.Neo,
243
- })
244
- ).resolves.toBeUndefined();
245
- await expect(
246
- DataManager.forceReloadData({
247
- requireResources: true,
248
- resourceDeviceType: EDeviceType.Pro2,
249
- })
250
- ).rejects.toMatchObject({
251
- errorCode: HardwareErrorCode.FirmwareUpdateDownloadFailed,
252
- message: expect.stringContaining('Invalid Pro2 resources config'),
253
- });
254
- });
255
-
256
340
  test('does not advance the cache timestamp when refresh fails', async () => {
257
341
  jest.spyOn(axios, 'get').mockRejectedValue(new Error('offline'));
258
342
  const settings = createSettings(jest.fn().mockResolvedValue(null));