@hot-updater/react-native 1.0.0-rc.4 → 1.0.0-rc.41

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 (317) hide show
  1. package/android/build.gradle +13 -2
  2. package/android/proguard-rules.pro +8 -2
  3. package/android/src/bridge/BridgeReactInstance.kt +38 -0
  4. package/android/src/bridgeless/BridgeReactInstance.kt +24 -0
  5. package/android/src/main/java/com/hotupdater/ActivityStartGate.kt +94 -0
  6. package/android/src/main/java/com/hotupdater/BuiltInAssetResolver.kt +286 -0
  7. package/android/src/main/java/com/hotupdater/BundleFileStorageService.kt +1064 -844
  8. package/android/src/main/java/com/hotupdater/BundleMetadata.kt +50 -11
  9. package/android/src/main/java/com/hotupdater/FileManagerService.kt +45 -0
  10. package/android/src/main/java/com/hotupdater/HotUpdaterException.kt +287 -22
  11. package/android/src/main/java/com/hotupdater/HotUpdaterImpl.kt +46 -30
  12. package/android/src/main/java/com/hotupdater/HotUpdaterRecoveryManager.kt +75 -192
  13. package/android/src/main/java/com/hotupdater/HotUpdaterRecoveryState.kt +64 -0
  14. package/android/src/main/java/com/hotupdater/InstallIdentityService.kt +37 -0
  15. package/android/src/main/java/com/hotupdater/KeyValueStorageService.kt +86 -0
  16. package/android/src/main/java/com/hotupdater/NetworkState.kt +31 -0
  17. package/android/src/main/java/com/hotupdater/NoBackupStorage.kt +17 -0
  18. package/android/src/main/java/com/hotupdater/OkHttpDownloadService.kt +127 -50
  19. package/android/src/main/java/com/hotupdater/ReactNativeValueConverters.kt +34 -0
  20. package/android/src/main/java/com/hotupdater/TarArchiveInputStream.kt +214 -375
  21. package/android/src/main/java/com/hotupdater/TarBrArchiveExtractor.kt +119 -0
  22. package/android/src/newarch/HotUpdater.kt +7 -9
  23. package/android/src/newarch/HotUpdaterModule.kt +109 -84
  24. package/android/src/newarch/JavaScriptExceptionHooks.kt +48 -0
  25. package/android/src/newarch/ReactIntegrationManager.kt +6 -32
  26. package/android/src/oldarch/HotUpdater.kt +7 -9
  27. package/android/src/oldarch/HotUpdaterModule.kt +98 -74
  28. package/android/src/oldarch/HotUpdaterSpec.kt +4 -6
  29. package/android/src/oldarch/JavaScriptExceptionHooks.kt +71 -0
  30. package/ios/HotUpdater/Internal/BrotliFileDecompressor.swift +167 -0
  31. package/ios/HotUpdater/Internal/BuiltInAssetResolver.swift +196 -0
  32. package/ios/HotUpdater/Internal/BundleFileStorageService.swift +1032 -1116
  33. package/ios/HotUpdater/Internal/BundleMetadata.swift +61 -54
  34. package/ios/HotUpdater/Internal/FileManagerService.swift +48 -0
  35. package/ios/HotUpdater/Internal/FileUtilities.swift +64 -0
  36. package/ios/HotUpdater/Internal/HotUpdater-Bridging-Header.h +0 -1
  37. package/ios/HotUpdater/Internal/HotUpdater.mm +32 -67
  38. package/ios/HotUpdater/Internal/HotUpdaterImpl.swift +229 -73
  39. package/ios/HotUpdater/Internal/KeyValueStorageService.swift +71 -0
  40. package/ios/HotUpdater/Internal/NotificationExtension.swift +0 -2
  41. package/ios/HotUpdater/Internal/TarArchiveExtractor.swift +182 -311
  42. package/ios/HotUpdater/Internal/URLSessionDownloadService.swift +119 -71
  43. package/ios/HotUpdater/Internal/UpdateFailure.swift +390 -0
  44. package/ios/HotUpdater/Recovery/HotUpdaterRecovery.h +16 -0
  45. package/ios/HotUpdater/Recovery/HotUpdaterRecovery.mm +83 -0
  46. package/ios/HotUpdater/Recovery/HotUpdaterRecoveryHooks.mm +49 -0
  47. package/lib/commonjs/appReady.js +96 -0
  48. package/lib/commonjs/appReady.js.map +1 -0
  49. package/lib/commonjs/checkForUpdate.js +121 -69
  50. package/lib/commonjs/checkForUpdate.js.map +1 -1
  51. package/lib/commonjs/clientPlugin.js +13 -0
  52. package/lib/commonjs/clientPlugin.js.map +1 -0
  53. package/lib/commonjs/error.js +5 -14
  54. package/lib/commonjs/error.js.map +1 -1
  55. package/lib/commonjs/fetchJSON.js +16 -27
  56. package/lib/commonjs/fetchJSON.js.map +1 -1
  57. package/lib/commonjs/fetchUpdateResponse.js +60 -0
  58. package/lib/commonjs/fetchUpdateResponse.js.map +1 -0
  59. package/lib/commonjs/httpClient.js +64 -95
  60. package/lib/commonjs/httpClient.js.map +1 -1
  61. package/lib/commonjs/index.js +355 -324
  62. package/lib/commonjs/index.js.map +1 -1
  63. package/lib/commonjs/init.types.js +6 -0
  64. package/lib/commonjs/init.types.js.map +1 -0
  65. package/lib/commonjs/native.js +77 -63
  66. package/lib/commonjs/native.js.map +1 -1
  67. package/lib/commonjs/pluginHost.js +41 -0
  68. package/lib/commonjs/pluginHost.js.map +1 -0
  69. package/lib/commonjs/releaseCatalogCache.js +36 -87
  70. package/lib/commonjs/releaseCatalogCache.js.map +1 -1
  71. package/lib/commonjs/sdkVersion.js +1 -1
  72. package/lib/commonjs/sdkVersion.js.map +1 -1
  73. package/lib/commonjs/specs/NativeHotUpdater.js.map +1 -1
  74. package/lib/commonjs/store.js +3 -10
  75. package/lib/commonjs/store.js.map +1 -1
  76. package/lib/commonjs/updateError.js +145 -0
  77. package/lib/commonjs/updateError.js.map +1 -0
  78. package/lib/commonjs/wrap.js +46 -38
  79. package/lib/commonjs/wrap.js.map +1 -1
  80. package/lib/module/appReady.js +90 -0
  81. package/lib/module/appReady.js.map +1 -0
  82. package/lib/module/checkForUpdate.js +114 -64
  83. package/lib/module/checkForUpdate.js.map +1 -1
  84. package/lib/module/clientPlugin.js +4 -0
  85. package/lib/module/clientPlugin.js.map +1 -0
  86. package/lib/module/error.js +5 -14
  87. package/lib/module/error.js.map +1 -1
  88. package/lib/module/fetchJSON.js +14 -26
  89. package/lib/module/fetchJSON.js.map +1 -1
  90. package/lib/module/fetchUpdateResponse.js +55 -0
  91. package/lib/module/fetchUpdateResponse.js.map +1 -0
  92. package/lib/module/httpClient.js +63 -94
  93. package/lib/module/httpClient.js.map +1 -1
  94. package/lib/module/index.js +335 -327
  95. package/lib/module/index.js.map +1 -1
  96. package/lib/module/init.types.js +4 -0
  97. package/lib/module/init.types.js.map +1 -0
  98. package/lib/module/native.js +70 -56
  99. package/lib/module/native.js.map +1 -1
  100. package/lib/module/pluginHost.js +36 -0
  101. package/lib/module/pluginHost.js.map +1 -0
  102. package/lib/module/releaseCatalogCache.js +32 -82
  103. package/lib/module/releaseCatalogCache.js.map +1 -1
  104. package/lib/module/sdkVersion.js +1 -1
  105. package/lib/module/sdkVersion.js.map +1 -1
  106. package/lib/module/specs/NativeHotUpdater.js.map +1 -1
  107. package/lib/module/store.js +3 -10
  108. package/lib/module/store.js.map +1 -1
  109. package/lib/module/updateError.js +139 -0
  110. package/lib/module/updateError.js.map +1 -0
  111. package/lib/module/wrap.js +47 -38
  112. package/lib/module/wrap.js.map +1 -1
  113. package/lib/typescript/commonjs/appReady.d.ts +30 -0
  114. package/lib/typescript/commonjs/appReady.d.ts.map +1 -0
  115. package/lib/typescript/commonjs/checkForUpdate.d.ts +20 -6
  116. package/lib/typescript/commonjs/checkForUpdate.d.ts.map +1 -1
  117. package/lib/typescript/commonjs/clientPlugin.d.ts +2 -0
  118. package/lib/typescript/commonjs/clientPlugin.d.ts.map +1 -0
  119. package/lib/typescript/commonjs/error.d.ts +5 -14
  120. package/lib/typescript/commonjs/error.d.ts.map +1 -1
  121. package/lib/typescript/commonjs/fetchJSON.d.ts +5 -5
  122. package/lib/typescript/commonjs/fetchJSON.d.ts.map +1 -1
  123. package/lib/typescript/commonjs/fetchUpdateResponse.d.ts +14 -0
  124. package/lib/typescript/commonjs/fetchUpdateResponse.d.ts.map +1 -0
  125. package/lib/typescript/commonjs/httpClient.d.ts +5 -34
  126. package/lib/typescript/commonjs/httpClient.d.ts.map +1 -1
  127. package/lib/typescript/commonjs/index.d.ts +124 -116
  128. package/lib/typescript/commonjs/index.d.ts.map +1 -1
  129. package/lib/typescript/commonjs/init.types.d.ts +21 -0
  130. package/lib/typescript/commonjs/init.types.d.ts.map +1 -0
  131. package/lib/typescript/commonjs/native.d.ts +32 -25
  132. package/lib/typescript/commonjs/native.d.ts.map +1 -1
  133. package/lib/typescript/commonjs/pluginHost.d.ts +8 -0
  134. package/lib/typescript/commonjs/pluginHost.d.ts.map +1 -0
  135. package/lib/typescript/commonjs/releaseCatalogCache.d.ts +8 -14
  136. package/lib/typescript/commonjs/releaseCatalogCache.d.ts.map +1 -1
  137. package/lib/typescript/commonjs/sdkVersion.d.ts +1 -1
  138. package/lib/typescript/commonjs/sdkVersion.d.ts.map +1 -1
  139. package/lib/typescript/commonjs/specs/NativeHotUpdater.d.ts +35 -47
  140. package/lib/typescript/commonjs/specs/NativeHotUpdater.d.ts.map +1 -1
  141. package/lib/typescript/commonjs/store.d.ts +1 -3
  142. package/lib/typescript/commonjs/store.d.ts.map +1 -1
  143. package/lib/typescript/commonjs/types.d.ts +1 -1
  144. package/lib/typescript/commonjs/types.d.ts.map +1 -1
  145. package/lib/typescript/commonjs/updateError.d.ts +26 -0
  146. package/lib/typescript/commonjs/updateError.d.ts.map +1 -0
  147. package/lib/typescript/commonjs/wrap.d.ts +2 -3
  148. package/lib/typescript/commonjs/wrap.d.ts.map +1 -1
  149. package/lib/typescript/commonjs/wrap.types.d.ts +13 -41
  150. package/lib/typescript/commonjs/wrap.types.d.ts.map +1 -1
  151. package/lib/typescript/module/appReady.d.ts +30 -0
  152. package/lib/typescript/module/appReady.d.ts.map +1 -0
  153. package/lib/typescript/module/checkForUpdate.d.ts +20 -6
  154. package/lib/typescript/module/checkForUpdate.d.ts.map +1 -1
  155. package/lib/typescript/module/clientPlugin.d.ts +2 -0
  156. package/lib/typescript/module/clientPlugin.d.ts.map +1 -0
  157. package/lib/typescript/module/error.d.ts +5 -14
  158. package/lib/typescript/module/error.d.ts.map +1 -1
  159. package/lib/typescript/module/fetchJSON.d.ts +5 -5
  160. package/lib/typescript/module/fetchJSON.d.ts.map +1 -1
  161. package/lib/typescript/module/fetchUpdateResponse.d.ts +14 -0
  162. package/lib/typescript/module/fetchUpdateResponse.d.ts.map +1 -0
  163. package/lib/typescript/module/httpClient.d.ts +5 -34
  164. package/lib/typescript/module/httpClient.d.ts.map +1 -1
  165. package/lib/typescript/module/index.d.ts +124 -116
  166. package/lib/typescript/module/index.d.ts.map +1 -1
  167. package/lib/typescript/module/init.types.d.ts +21 -0
  168. package/lib/typescript/module/init.types.d.ts.map +1 -0
  169. package/lib/typescript/module/native.d.ts +32 -25
  170. package/lib/typescript/module/native.d.ts.map +1 -1
  171. package/lib/typescript/module/pluginHost.d.ts +8 -0
  172. package/lib/typescript/module/pluginHost.d.ts.map +1 -0
  173. package/lib/typescript/module/releaseCatalogCache.d.ts +8 -14
  174. package/lib/typescript/module/releaseCatalogCache.d.ts.map +1 -1
  175. package/lib/typescript/module/sdkVersion.d.ts +1 -1
  176. package/lib/typescript/module/sdkVersion.d.ts.map +1 -1
  177. package/lib/typescript/module/specs/NativeHotUpdater.d.ts +35 -47
  178. package/lib/typescript/module/specs/NativeHotUpdater.d.ts.map +1 -1
  179. package/lib/typescript/module/store.d.ts +1 -3
  180. package/lib/typescript/module/store.d.ts.map +1 -1
  181. package/lib/typescript/module/types.d.ts +1 -1
  182. package/lib/typescript/module/types.d.ts.map +1 -1
  183. package/lib/typescript/module/updateError.d.ts +26 -0
  184. package/lib/typescript/module/updateError.d.ts.map +1 -0
  185. package/lib/typescript/module/wrap.d.ts +2 -3
  186. package/lib/typescript/module/wrap.d.ts.map +1 -1
  187. package/lib/typescript/module/wrap.types.d.ts +13 -41
  188. package/lib/typescript/module/wrap.types.d.ts.map +1 -1
  189. package/package.json +8 -4
  190. package/src/appReady.ts +147 -0
  191. package/src/checkForUpdate.ts +156 -89
  192. package/src/clientPlugin.ts +21 -0
  193. package/src/error.ts +5 -15
  194. package/src/fetchJSON.ts +14 -30
  195. package/src/fetchUpdateResponse.ts +60 -0
  196. package/src/httpClient.ts +89 -160
  197. package/src/index.ts +413 -425
  198. package/src/init.types.ts +24 -0
  199. package/src/native.ts +101 -105
  200. package/src/pluginHost.ts +52 -0
  201. package/src/releaseCatalogCache.ts +43 -172
  202. package/src/sdkVersion.ts +1 -1
  203. package/src/specs/NativeHotUpdater.ts +34 -48
  204. package/src/store.ts +2 -23
  205. package/src/types.ts +1 -1
  206. package/src/updateError.ts +167 -0
  207. package/src/wrap.tsx +60 -43
  208. package/src/wrap.types.ts +18 -46
  209. package/android/src/main/java/com/hotupdater/DecompressService.kt +0 -89
  210. package/android/src/main/java/com/hotupdater/DecompressionStrategy.kt +0 -26
  211. package/android/src/main/java/com/hotupdater/TarBrDecompressionStrategy.kt +0 -117
  212. package/android/src/main/java/com/hotupdater/TarGzDecompressionStrategy.kt +0 -117
  213. package/android/src/main/java/com/hotupdater/ZipDecompressionStrategy.kt +0 -192
  214. package/android/src/test/java/com/hotupdater/BsdiffPatchTest.kt +0 -46
  215. package/android/src/test/java/com/hotupdater/BundleFileStorageServiceTest.kt +0 -799
  216. package/android/src/test/java/com/hotupdater/HotUpdaterImplTest.kt +0 -273
  217. package/android/src/test/java/com/hotupdater/NativeConfigUtilsTest.kt +0 -20
  218. package/android/src/test/java/com/hotupdater/OkHttpDownloadServiceTest.kt +0 -111
  219. package/android/src/test/java/com/hotupdater/RelativePathResolverTest.kt +0 -41
  220. package/android/src/test/java/com/hotupdater/ReleaseCatalogCacheServiceTest.kt +0 -118
  221. package/android/src/test/java/com/hotupdater/TarArchiveInputStreamTest.kt +0 -68
  222. package/ios/HotUpdater/Internal/ArchiveExtractionUtilities.swift +0 -178
  223. package/ios/HotUpdater/Internal/DecompressService.swift +0 -112
  224. package/ios/HotUpdater/Internal/DecompressionStrategy.swift +0 -22
  225. package/ios/HotUpdater/Internal/StreamingTarArchiveExtractor.swift +0 -370
  226. package/ios/HotUpdater/Internal/TarBrDecompressionStrategy.swift +0 -39
  227. package/ios/HotUpdater/Internal/TarGzDecompressionStrategy.swift +0 -59
  228. package/ios/HotUpdater/Internal/ZipArchiveExtractor.swift +0 -482
  229. package/ios/HotUpdater/Internal/ZipDecompressionStrategy.swift +0 -56
  230. package/lib/commonjs/catalogCacheNative.spec.js +0 -31
  231. package/lib/commonjs/catalogCacheNative.spec.js.map +0 -1
  232. package/lib/commonjs/checkForUpdate.releaseCatalog.spec.js +0 -616
  233. package/lib/commonjs/checkForUpdate.releaseCatalog.spec.js.map +0 -1
  234. package/lib/commonjs/httpClient.spec.js +0 -102
  235. package/lib/commonjs/httpClient.spec.js.map +0 -1
  236. package/lib/commonjs/index.spec.js +0 -302
  237. package/lib/commonjs/index.spec.js.map +0 -1
  238. package/lib/commonjs/iosInterop.spec.js +0 -18
  239. package/lib/commonjs/iosInterop.spec.js.map +0 -1
  240. package/lib/commonjs/native.spec.js +0 -883
  241. package/lib/commonjs/native.spec.js.map +0 -1
  242. package/lib/commonjs/notifyAppReadyInsights.boundary.spec.js +0 -218
  243. package/lib/commonjs/notifyAppReadyInsights.boundary.spec.js.map +0 -1
  244. package/lib/commonjs/notifyAppReadyInsights.js +0 -133
  245. package/lib/commonjs/notifyAppReadyInsights.js.map +0 -1
  246. package/lib/commonjs/notifyAppReadyInsights.spec.js +0 -246
  247. package/lib/commonjs/notifyAppReadyInsights.spec.js.map +0 -1
  248. package/lib/commonjs/notifyAppReadyInsights.test-utils.js +0 -24
  249. package/lib/commonjs/notifyAppReadyInsights.test-utils.js.map +0 -1
  250. package/lib/commonjs/notifyAppReadyInsights.transport.spec.js +0 -117
  251. package/lib/commonjs/notifyAppReadyInsights.transport.spec.js.map +0 -1
  252. package/lib/commonjs/releaseCatalogCache.spec.js +0 -195
  253. package/lib/commonjs/releaseCatalogCache.spec.js.map +0 -1
  254. package/lib/commonjs/releaseCatalogCacheNativeContract.spec.js +0 -64
  255. package/lib/commonjs/releaseCatalogCacheNativeContract.spec.js.map +0 -1
  256. package/lib/commonjs/store.notification.spec.js +0 -76
  257. package/lib/commonjs/store.notification.spec.js.map +0 -1
  258. package/lib/commonjs/store.spec.js +0 -316
  259. package/lib/commonjs/store.spec.js.map +0 -1
  260. package/lib/commonjs/wrap.spec.js +0 -329
  261. package/lib/commonjs/wrap.spec.js.map +0 -1
  262. package/lib/module/catalogCacheNative.spec.js +0 -31
  263. package/lib/module/catalogCacheNative.spec.js.map +0 -1
  264. package/lib/module/checkForUpdate.releaseCatalog.spec.js +0 -615
  265. package/lib/module/checkForUpdate.releaseCatalog.spec.js.map +0 -1
  266. package/lib/module/httpClient.spec.js +0 -102
  267. package/lib/module/httpClient.spec.js.map +0 -1
  268. package/lib/module/index.spec.js +0 -301
  269. package/lib/module/index.spec.js.map +0 -1
  270. package/lib/module/iosInterop.spec.js +0 -18
  271. package/lib/module/iosInterop.spec.js.map +0 -1
  272. package/lib/module/native.spec.js +0 -882
  273. package/lib/module/native.spec.js.map +0 -1
  274. package/lib/module/notifyAppReadyInsights.boundary.spec.js +0 -217
  275. package/lib/module/notifyAppReadyInsights.boundary.spec.js.map +0 -1
  276. package/lib/module/notifyAppReadyInsights.js +0 -128
  277. package/lib/module/notifyAppReadyInsights.js.map +0 -1
  278. package/lib/module/notifyAppReadyInsights.spec.js +0 -245
  279. package/lib/module/notifyAppReadyInsights.spec.js.map +0 -1
  280. package/lib/module/notifyAppReadyInsights.test-utils.js +0 -18
  281. package/lib/module/notifyAppReadyInsights.test-utils.js.map +0 -1
  282. package/lib/module/notifyAppReadyInsights.transport.spec.js +0 -116
  283. package/lib/module/notifyAppReadyInsights.transport.spec.js.map +0 -1
  284. package/lib/module/releaseCatalogCache.spec.js +0 -195
  285. package/lib/module/releaseCatalogCache.spec.js.map +0 -1
  286. package/lib/module/releaseCatalogCacheNativeContract.spec.js +0 -64
  287. package/lib/module/releaseCatalogCacheNativeContract.spec.js.map +0 -1
  288. package/lib/module/store.notification.spec.js +0 -77
  289. package/lib/module/store.notification.spec.js.map +0 -1
  290. package/lib/module/store.spec.js +0 -315
  291. package/lib/module/store.spec.js.map +0 -1
  292. package/lib/module/wrap.spec.js +0 -330
  293. package/lib/module/wrap.spec.js.map +0 -1
  294. package/lib/typescript/commonjs/notifyAppReadyInsights.d.ts +0 -13
  295. package/lib/typescript/commonjs/notifyAppReadyInsights.d.ts.map +0 -1
  296. package/lib/typescript/commonjs/notifyAppReadyInsights.test-utils.d.ts +0 -8
  297. package/lib/typescript/commonjs/notifyAppReadyInsights.test-utils.d.ts.map +0 -1
  298. package/lib/typescript/module/notifyAppReadyInsights.d.ts +0 -13
  299. package/lib/typescript/module/notifyAppReadyInsights.d.ts.map +0 -1
  300. package/lib/typescript/module/notifyAppReadyInsights.test-utils.d.ts +0 -8
  301. package/lib/typescript/module/notifyAppReadyInsights.test-utils.d.ts.map +0 -1
  302. package/src/catalogCacheNative.spec.ts +0 -63
  303. package/src/checkForUpdate.releaseCatalog.spec.ts +0 -612
  304. package/src/httpClient.spec.ts +0 -132
  305. package/src/index.spec.ts +0 -337
  306. package/src/iosInterop.spec.ts +0 -30
  307. package/src/native.spec.ts +0 -945
  308. package/src/notifyAppReadyInsights.boundary.spec.ts +0 -234
  309. package/src/notifyAppReadyInsights.spec.ts +0 -243
  310. package/src/notifyAppReadyInsights.test-utils.ts +0 -31
  311. package/src/notifyAppReadyInsights.transport.spec.ts +0 -130
  312. package/src/notifyAppReadyInsights.ts +0 -194
  313. package/src/releaseCatalogCache.spec.ts +0 -282
  314. package/src/releaseCatalogCacheNativeContract.spec.ts +0 -80
  315. package/src/store.notification.spec.ts +0 -92
  316. package/src/store.spec.ts +0 -372
  317. package/src/wrap.spec.ts +0 -406
@@ -1,799 +0,0 @@
1
- package com.hotupdater
2
-
3
- import android.content.ContextWrapper
4
- import kotlinx.coroutines.asCoroutineDispatcher
5
- import kotlinx.coroutines.runBlocking
6
- import org.json.JSONObject
7
- import org.junit.Assert.assertEquals
8
- import org.junit.Assert.assertFalse
9
- import org.junit.Assert.assertNotNull
10
- import org.junit.Assert.assertNull
11
- import org.junit.Assert.assertTrue
12
- import org.junit.Rule
13
- import org.junit.Test
14
- import org.junit.rules.TemporaryFolder
15
- import java.io.File
16
- import java.net.URL
17
- import java.util.concurrent.CopyOnWriteArrayList
18
- import java.util.concurrent.Executors
19
- import java.util.zip.ZipEntry
20
- import java.util.zip.ZipOutputStream
21
-
22
- class BundleFileStorageServiceTest {
23
- @get:Rule
24
- val temporaryFolder = TemporaryFolder()
25
-
26
- @Test
27
- fun `resolveBundleFile uses single manifest bundle at root`() {
28
- val rootDir = temporaryFolder.newFolder("root-manifest-bundle")
29
- val service = createService(rootDir)
30
- val bundleDir = createBundleDir(rootDir, "bundle-root")
31
- val expectedBundleFile = writeFile(bundleDir, "foo.android.bundle")
32
-
33
- writeManifest(bundleDir, listOf("foo.android.bundle"))
34
-
35
- assertResolvedBundlePath(service, bundleDir, expectedBundleFile)
36
- }
37
-
38
- @Test
39
- fun `resolveBundleFile uses single nested manifest bundle`() {
40
- val rootDir = temporaryFolder.newFolder("nested-manifest-bundle")
41
- val service = createService(rootDir)
42
- val bundleDir = createBundleDir(rootDir, "bundle-nested")
43
- val expectedBundleFile = writeFile(bundleDir, "dist/foo.android.bundle")
44
-
45
- writeManifest(bundleDir, listOf("dist/foo.android.bundle"))
46
-
47
- assertResolvedBundlePath(service, bundleDir, expectedBundleFile)
48
- }
49
-
50
- @Test
51
- fun `resolveBundleFile rejects a manifest with missing assets even when root index exists`() {
52
- val rootDir = temporaryFolder.newFolder("no-android-candidate")
53
- val service = createService(rootDir)
54
- val bundleDir = createBundleDir(rootDir, "bundle-no-candidate")
55
- writeFile(bundleDir, "index.android.bundle")
56
-
57
- writeManifest(bundleDir, listOf("index.ios.bundle", "assets/image.png"))
58
-
59
- assertNull(invokeResolveBundleFile(service, bundleDir))
60
- }
61
-
62
- @Test
63
- fun `resolveBundleFile rejects a manifest with multiple android bundle candidates`() {
64
- val rootDir = temporaryFolder.newFolder("multiple-android-candidates")
65
- val service = createService(rootDir)
66
- val bundleDir = createBundleDir(rootDir, "bundle-multiple-candidates")
67
- writeFile(bundleDir, "index.android.bundle")
68
-
69
- writeFile(bundleDir, "foo.android.bundle")
70
- writeFile(bundleDir, "dist/bar.android.bundle")
71
- writeManifest(bundleDir, listOf("foo.android.bundle", "dist/bar.android.bundle"))
72
-
73
- assertNull(invokeResolveBundleFile(service, bundleDir))
74
- }
75
-
76
- @Test
77
- fun `resolveBundleFile returns null when manifest escapes root and no fallback exists`() {
78
- val rootDir = temporaryFolder.newFolder("escaped-manifest-path")
79
- val service = createService(rootDir)
80
- val bundleDir = createBundleDir(rootDir, "bundle-escaped-path")
81
-
82
- writeFile(bundleStoreDir(rootDir), "outside.android.bundle")
83
- writeManifest(bundleDir, listOf("../outside.android.bundle"))
84
-
85
- assertNull(invokeResolveBundleFile(service, bundleDir))
86
- }
87
-
88
- @Test
89
- fun `resolveBundleFile returns null when manifest target is missing and no fallback exists`() {
90
- val rootDir = temporaryFolder.newFolder("missing-manifest-target")
91
- val service = createService(rootDir)
92
- val bundleDir = createBundleDir(rootDir, "bundle-missing-target")
93
-
94
- writeManifest(bundleDir, listOf("dist/missing.android.bundle"))
95
-
96
- assertNull(invokeResolveBundleFile(service, bundleDir))
97
- }
98
-
99
- @Test
100
- fun `resolveBundleFile allows legacy root index without manifest`() {
101
- val rootDir = temporaryFolder.newFolder("legacy-root-index")
102
- val service = createService(rootDir)
103
- val bundleDir = createBundleDir(rootDir, "bundle-legacy")
104
- val fallbackBundleFile = writeFile(bundleDir, "index.android.bundle")
105
-
106
- assertResolvedBundlePath(service, bundleDir, fallbackBundleFile)
107
- }
108
-
109
- @Test
110
- fun `resolveBundleFile returns null when manifest and root index are both missing`() {
111
- val rootDir = temporaryFolder.newFolder("missing-everything")
112
- val service = createService(rootDir)
113
- val bundleDir = createBundleDir(rootDir, "bundle-invalid")
114
-
115
- assertNull(invokeResolveBundleFile(service, bundleDir))
116
- }
117
-
118
- @Test
119
- fun `prepareLaunch rolls back invalid staging and selects stable bundle`() {
120
- val rootDir = temporaryFolder.newFolder("rollback-to-stable")
121
- val preferences = InMemoryPreferencesService()
122
- val service = createService(rootDir, preferences)
123
-
124
- val stagingDir = createBundleDir(rootDir, "staging-bundle")
125
- writeFile(stagingDir, "dist/staging.android.bundle")
126
- writeManifest(
127
- stagingDir,
128
- listOf(
129
- "dist/staging.android.bundle",
130
- "assets/missing.png",
131
- ),
132
- )
133
-
134
- val stableDir = createBundleDir(rootDir, "stable-bundle")
135
- val stableBundleFile = writeFile(stableDir, "dist/stable.android.bundle")
136
- writeManifest(stableDir, listOf("dist/stable.android.bundle"))
137
-
138
- writeMetadata(
139
- rootDir,
140
- BundleMetadata(
141
- isolationKey = TEST_ISOLATION_KEY,
142
- stableBundleId = stableDir.name,
143
- stagingBundleId = stagingDir.name,
144
- verificationPending = true,
145
- ),
146
- )
147
-
148
- val selection = service.prepareLaunch(null)
149
- val report = service.notifyAppReady()
150
-
151
- assertEquals(stableBundleFile.absolutePath, selection.bundleUrl)
152
- assertEquals(stableDir.name, selection.launchedBundleId)
153
- assertFalse(selection.shouldRollbackOnCrash)
154
- assertFalse(stagingDir.exists())
155
- assertEquals("RECOVERED", report["status"])
156
- assertEquals(stagingDir.name, report["fromBundleId"])
157
- assertEquals(stableDir.name, report["toBundleId"])
158
- assertEquals("appVersion", report["updateStrategy"])
159
-
160
- val metadata = loadMetadata(rootDir)
161
- assertNotNull(metadata)
162
- assertEquals(stableDir.name, metadata?.stagingBundleId)
163
- assertNull(metadata?.stableBundleId)
164
- assertFalse(metadata?.verificationPending ?: true)
165
- assertEquals(stableBundleFile.absolutePath, preferences.getItem("HotUpdaterBundleURL"))
166
- }
167
-
168
- @Test
169
- fun `prepareLaunch falls back to built in bundle when staging and stable are both invalid`() {
170
- val rootDir = temporaryFolder.newFolder("fallback-to-built-in")
171
- val service = createService(rootDir)
172
-
173
- val stagingDir = createBundleDir(rootDir, "staging-bundle")
174
- writeManifest(stagingDir, listOf("dist/missing.android.bundle"))
175
-
176
- val stableDir = createBundleDir(rootDir, "stable-bundle")
177
- writeManifest(stableDir, listOf("../outside.android.bundle"))
178
-
179
- writeMetadata(
180
- rootDir,
181
- BundleMetadata(
182
- isolationKey = TEST_ISOLATION_KEY,
183
- stableBundleId = stableDir.name,
184
- stagingBundleId = stagingDir.name,
185
- verificationPending = true,
186
- ),
187
- )
188
-
189
- val selection = service.prepareLaunch(null)
190
- val report = service.notifyAppReady()
191
-
192
- assertEquals("assets://index.android.bundle", selection.bundleUrl)
193
- assertNull(selection.launchedBundleId)
194
- assertFalse(selection.shouldRollbackOnCrash)
195
- assertFalse(stagingDir.exists())
196
- assertEquals("RECOVERED", report["status"])
197
- assertEquals(stagingDir.name, report["fromBundleId"])
198
- assertEquals(HotUpdaterImpl.getMinBundleId(), report["toBundleId"])
199
- assertEquals("appVersion", report["updateStrategy"])
200
- }
201
-
202
- @Test
203
- fun `getBundleId falls back to built in while staging verification is pending`() {
204
- val rootDir = temporaryFolder.newFolder("pending-staging-built-in")
205
- val service = createService(rootDir)
206
-
207
- val stagingDir = createBundleDir(rootDir, "staging-bundle")
208
- writeFile(stagingDir, "index.android.bundle")
209
- writeManifest(stagingDir, listOf("index.android.bundle"))
210
-
211
- writeMetadata(
212
- rootDir,
213
- BundleMetadata(
214
- isolationKey = TEST_ISOLATION_KEY,
215
- stableBundleId = null,
216
- stagingBundleId = stagingDir.name,
217
- verificationPending = true,
218
- ),
219
- )
220
-
221
- assertNull(service.getBundleId())
222
- assertEquals("", service.getBaseURL())
223
- assertTrue(service.getManifest().isEmpty())
224
- }
225
-
226
- @Test
227
- fun `getBundleId returns launched staging bundle while verification is pending`() {
228
- val rootDir = temporaryFolder.newFolder("pending-staging-active")
229
- val preferences = InMemoryPreferencesService()
230
- val service = createService(rootDir, preferences)
231
-
232
- val stagingDir = createBundleDir(rootDir, "staging-bundle")
233
- val stagingBundleFile = writeFile(stagingDir, "index.android.bundle")
234
- writeManifest(stagingDir, listOf("index.android.bundle"))
235
-
236
- writeMetadata(
237
- rootDir,
238
- BundleMetadata(
239
- isolationKey = TEST_ISOLATION_KEY,
240
- stableBundleId = null,
241
- stagingBundleId = stagingDir.name,
242
- verificationPending = true,
243
- ),
244
- )
245
-
246
- preferences.setItem("HotUpdaterBundleURL", stagingBundleFile.absolutePath)
247
-
248
- assertEquals(stagingDir.name, service.getBundleId())
249
- }
250
-
251
- @Test
252
- fun `markLaunchCompleted records update applied transition`() {
253
- val rootDir = temporaryFolder.newFolder("update-applied-transition")
254
- val service = createService(rootDir)
255
-
256
- val stableDir = createBundleDir(rootDir, "stable-bundle")
257
- writeFile(stableDir, "index.android.bundle")
258
- writeManifest(stableDir, listOf("index.android.bundle"))
259
-
260
- val stagingDir = createBundleDir(rootDir, "staging-bundle")
261
- writeFile(stagingDir, "index.android.bundle")
262
- writeManifest(stagingDir, listOf("index.android.bundle"))
263
-
264
- writeMetadata(
265
- rootDir,
266
- BundleMetadata(
267
- isolationKey = TEST_ISOLATION_KEY,
268
- stableBundleId = stableDir.name,
269
- stagingBundleId = stagingDir.name,
270
- verificationPending = true,
271
- ),
272
- )
273
-
274
- assertEquals(mapOf("status" to "PENDING"), service.notifyAppReady())
275
-
276
- service.markLaunchCompleted(stagingDir.name)
277
-
278
- assertEquals(
279
- mapOf(
280
- "status" to "UPDATE_APPLIED",
281
- "fromBundleId" to stableDir.name,
282
- "toBundleId" to stagingDir.name,
283
- "updateStrategy" to "appVersion",
284
- ),
285
- service.notifyAppReady(),
286
- )
287
- }
288
-
289
- @Test
290
- fun `install identity persists across service instances and user clear keeps install id`() {
291
- val rootDir = temporaryFolder.newFolder("install-identity")
292
- val firstService = createService(rootDir)
293
-
294
- val firstInstallId = firstService.getInstallId()
295
- assertTrue(firstInstallId.isNotBlank())
296
-
297
- firstService.setUser("user-123", "alice")
298
- assertEquals(
299
- InstallationIdentity(
300
- installId = firstInstallId,
301
- userId = "user-123",
302
- username = "alice",
303
- ),
304
- loadInstallationIdentity(rootDir),
305
- )
306
-
307
- val restartedService = createService(rootDir)
308
- assertEquals(firstInstallId, restartedService.getInstallId())
309
-
310
- restartedService.setUser(null, null)
311
- assertEquals(
312
- InstallationIdentity(
313
- installId = firstInstallId,
314
- userId = null,
315
- username = null,
316
- ),
317
- loadInstallationIdentity(rootDir),
318
- )
319
- }
320
-
321
- @Test
322
- fun `user update keeps cached install id when persisted identity becomes unreadable`() {
323
- val rootDir = temporaryFolder.newFolder("install-identity-corruption")
324
- val service = createService(rootDir)
325
- val installId = service.getInstallId()
326
- val identityFile = File(bundleStoreDir(rootDir), InstallationIdentity.IDENTITY_FILENAME)
327
- identityFile.writeText("{")
328
-
329
- service.setUser("user-123", "alice")
330
-
331
- assertEquals(installId, service.getInstallId())
332
- assertEquals(
333
- InstallationIdentity(
334
- installId = installId,
335
- userId = "user-123",
336
- username = "alice",
337
- ),
338
- loadInstallationIdentity(rootDir),
339
- )
340
- }
341
-
342
- @Test
343
- fun `manifest driven install is disabled before first OTA`() {
344
- val rootDir = temporaryFolder.newFolder("first-ota-manifest-disabled")
345
- val service = createService(rootDir)
346
-
347
- assertFalse(invokeCanUseManifestDrivenInstall(service))
348
- }
349
-
350
- @Test
351
- fun `manifest driven install is enabled for active OTA bundle with manifest`() {
352
- val rootDir = temporaryFolder.newFolder("active-ota-manifest-enabled")
353
- val preferences = InMemoryPreferencesService()
354
- val service = createService(rootDir, preferences)
355
- val activeDir = createBundleDir(rootDir, "active-bundle")
356
- val activeBundleFile = writeFile(activeDir, "index.android.bundle")
357
- writeManifest(activeDir, listOf("index.android.bundle"))
358
-
359
- preferences.setItem("HotUpdaterBundleURL", activeBundleFile.absolutePath)
360
-
361
- assertTrue(invokeCanUseManifestDrivenInstall(service))
362
- }
363
-
364
- @Test
365
- fun `manifest driven install rejects unsafe asset paths`() {
366
- val rootDir = temporaryFolder.newFolder("active-ota-unsafe-manifest")
367
- val preferences = InMemoryPreferencesService()
368
- val service = createService(rootDir, preferences)
369
- val activeDir = createBundleDir(rootDir, "active-bundle")
370
- val activeBundleFile = writeFile(activeDir, "index.android.bundle")
371
- writeManifest(activeDir, listOf("../active-bundle_evil/index.android.bundle"))
372
-
373
- preferences.setItem("HotUpdaterBundleURL", activeBundleFile.absolutePath)
374
-
375
- assertFalse(invokeCanUseManifestDrivenInstall(service))
376
- }
377
-
378
- @Test
379
- fun `catalog high water rejects replay and survives channel reset`() =
380
- runBlocking {
381
- val rootDir = temporaryFolder.newFolder("release-high-water")
382
- val service = createService(rootDir)
383
-
384
- assertTrue(
385
- service.acceptReleaseCatalog(
386
- catalogId = "project-a",
387
- scopeKey = "scope-production",
388
- generation = 2,
389
- catalogHash = "hash-2",
390
- channel = "production",
391
- selectionContextHash = "context-2",
392
- ),
393
- )
394
- assertFalse(
395
- service.acceptReleaseCatalog(
396
- catalogId = "project-a",
397
- scopeKey = "scope-production",
398
- generation = 1,
399
- catalogHash = "hash-1",
400
- channel = "production",
401
- selectionContextHash = "context-1",
402
- ),
403
- )
404
- assertFalse(
405
- service.acceptReleaseCatalog(
406
- catalogId = "project-a",
407
- scopeKey = "scope-production",
408
- generation = 2,
409
- catalogHash = "different-hash",
410
- channel = "production",
411
- selectionContextHash = "context-2",
412
- ),
413
- )
414
-
415
- assertTrue(service.resetChannel())
416
- val metadata = loadMetadata(rootDir)
417
- assertEquals(
418
- CatalogHighWater(generation = 2, catalogHash = "hash-2"),
419
- metadata?.highestSeenCatalogs?.get("project-a|scope-production"),
420
- )
421
- }
422
-
423
- @Test
424
- fun `same bundle adoption refreshes its receipt without changing bytes`() =
425
- runBlocking {
426
- val rootDir = temporaryFolder.newFolder("same-bundle-adoption")
427
- val service = createService(rootDir)
428
- val bundleDir = createBundleDir(rootDir, "bundle-one")
429
- writeFile(bundleDir, "index.android.bundle")
430
- writeManifest(bundleDir, listOf("index.android.bundle"))
431
- val oldSelection =
432
- releaseSelection(
433
- releaseId = "release-one",
434
- bundleId = bundleDir.name,
435
- generation = 1,
436
- catalogHash = "hash-1",
437
- selectionContextHash = "context-1",
438
- )
439
- writeMetadata(
440
- rootDir,
441
- BundleMetadata(
442
- isolationKey = TEST_ISOLATION_KEY,
443
- stagingBundleId = bundleDir.name,
444
- stagingSelection = oldSelection,
445
- ),
446
- )
447
- assertTrue(
448
- service.acceptReleaseCatalog(
449
- catalogId = "project-a",
450
- scopeKey = "scope-production",
451
- generation = 2,
452
- catalogHash = "hash-2",
453
- channel = "production",
454
- selectionContextHash = "context-2",
455
- ),
456
- )
457
- val newSelection =
458
- releaseSelection(
459
- releaseId = "release-two",
460
- bundleId = bundleDir.name,
461
- generation = 2,
462
- catalogHash = "hash-2",
463
- selectionContextHash = "context-2",
464
- )
465
-
466
- assertTrue(service.commitReleaseSelection(newSelection))
467
-
468
- assertTrue(bundleDir.isDirectory)
469
- assertEquals(newSelection, loadMetadata(rootDir)?.stagingSelection)
470
- assertFalse(loadMetadata(rootDir)?.verificationPending ?: true)
471
- }
472
-
473
- @Test
474
- fun `crash restores the complete stable receipt but retains newer high water`() {
475
- val rootDir = temporaryFolder.newFolder("release-crash-ledger")
476
- val service = createService(rootDir)
477
- val stableDir = createBundleDir(rootDir, "bundle-one")
478
- writeFile(stableDir, "index.android.bundle")
479
- writeManifest(stableDir, listOf("index.android.bundle"))
480
- val stagingDir = createBundleDir(rootDir, "bundle-two")
481
- writeFile(stagingDir, "index.android.bundle")
482
- writeManifest(stagingDir, listOf("index.android.bundle"))
483
- val stableSelection =
484
- releaseSelection("release-one", stableDir.name, 1, "hash-1", "context-1")
485
- val stagingSelection =
486
- releaseSelection("release-two", stagingDir.name, 2, "hash-2", "context-2")
487
- writeMetadata(
488
- rootDir,
489
- BundleMetadata(
490
- isolationKey = TEST_ISOLATION_KEY,
491
- stableBundleId = stableDir.name,
492
- stagingBundleId = stagingDir.name,
493
- stableSelection = stableSelection,
494
- stagingSelection = stagingSelection,
495
- pendingUpdateStrategy = "appVersion",
496
- pendingTransition =
497
- PendingSelectionTransition(
498
- fromReleaseId = stableSelection.releaseId,
499
- fromBundleId = stableSelection.bundleId,
500
- toReleaseId = stagingSelection.releaseId,
501
- toBundleId = stagingSelection.bundleId,
502
- ),
503
- verificationPending = true,
504
- highestSeenCatalogs =
505
- mapOf(
506
- "project-a|scope-production" to
507
- CatalogHighWater(generation = 2, catalogHash = "hash-2"),
508
- ),
509
- currentSelectionContexts =
510
- mapOf("project-a|scope-production" to "production\ncontext-2"),
511
- ),
512
- )
513
-
514
- val launch =
515
- service.prepareLaunch(
516
- PendingCrashRecovery(
517
- launchedBundleId = stagingDir.name,
518
- shouldRollback = true,
519
- ),
520
- )
521
- val report = service.notifyAppReady()
522
- val metadata = loadMetadata(rootDir)
523
-
524
- assertEquals(stableDir.name, launch.launchedBundleId)
525
- assertEquals("RECOVERED", report["status"])
526
- assertEquals("release-two", report["fromReleaseId"])
527
- assertEquals("release-one", report["toReleaseId"])
528
- assertEquals(stableSelection, metadata?.stagingSelection)
529
- assertEquals(
530
- CatalogHighWater(generation = 2, catalogHash = "hash-2"),
531
- metadata?.highestSeenCatalogs?.get("project-a|scope-production"),
532
- )
533
- assertTrue(service.getCrashHistory().contains(stagingDir.name))
534
- }
535
-
536
- @Test
537
- fun `manifest driven install moves blocking work off caller dispatcher`() {
538
- val rootDir = temporaryFolder.newFolder("manifest-install-dispatcher")
539
- val preferences = InMemoryPreferencesService()
540
- val downloadService = RecordingFailedDownloadService()
541
- val service = createService(rootDir, preferences, downloadService)
542
- val activeDir = createBundleDir(rootDir, "active-bundle")
543
- val activeBundleFile = writeFile(activeDir, "index.android.bundle")
544
- writeManifest(activeDir, listOf("index.android.bundle"))
545
-
546
- preferences.setItem("HotUpdaterBundleURL", activeBundleFile.absolutePath)
547
-
548
- Executors
549
- .newSingleThreadExecutor { runnable -> Thread(runnable, "manifest-caller") }
550
- .asCoroutineDispatcher()
551
- .use { callerDispatcher ->
552
- runBlocking(callerDispatcher) {
553
- val result =
554
- runCatching {
555
- service.updateBundle(
556
- bundleId = "target-bundle",
557
- fileUrl = "https://example.com/bundle.zip",
558
- fileHash = null,
559
- manifestUrl = "https://example.com/manifest.json",
560
- manifestFileHash = "manifest-hash",
561
- changedAssets = emptyMap(),
562
- progressCallback = {},
563
- )
564
- }
565
- assertTrue(result.exceptionOrNull() is HotUpdaterException)
566
- }
567
- }
568
-
569
- val manifestCall = downloadService.calls.first()
570
- assertEquals("https://example.com/manifest.json", manifestCall.first)
571
- assertFalse(
572
- "Manifest install ran on the caller dispatcher: ${manifestCall.second}",
573
- manifestCall.second.contains("manifest-caller"),
574
- )
575
- }
576
-
577
- @Test
578
- fun `zip decompression does not write sibling prefix traversal entries`() {
579
- val rootDir = temporaryFolder.newFolder("zip-sibling-prefix")
580
- val zipFile = File(rootDir, "bundle.zip")
581
- ZipOutputStream(zipFile.outputStream()).use { zip ->
582
- writeZipEntry(zip, "../bundle-temp_evil/escape.txt", "blocked")
583
- writeZipEntry(zip, "safe/kept.txt", "kept")
584
- }
585
-
586
- val destinationDir = File(rootDir, "bundle-temp")
587
- val extracted =
588
- ZipDecompressionStrategy().decompress(
589
- zipFile.absolutePath,
590
- destinationDir.absolutePath,
591
- ) {}
592
-
593
- assertTrue(extracted)
594
- assertTrue(File(destinationDir, "safe/kept.txt").isFile)
595
- assertFalse(File(rootDir, "bundle-temp_evil/escape.txt").exists())
596
- }
597
-
598
- private fun createService(
599
- rootDir: File,
600
- preferences: InMemoryPreferencesService = InMemoryPreferencesService(),
601
- downloadService: DownloadService = UnusedDownloadService,
602
- ): BundleFileStorageService =
603
- BundleFileStorageService(
604
- ContextWrapper(null),
605
- TestFileSystemService(rootDir),
606
- downloadService,
607
- DecompressService(),
608
- preferences,
609
- TEST_ISOLATION_KEY,
610
- { "production" },
611
- )
612
-
613
- private fun releaseSelection(
614
- releaseId: String,
615
- bundleId: String,
616
- generation: Long,
617
- catalogHash: String,
618
- selectionContextHash: String,
619
- ): PersistedSelection =
620
- PersistedSelection(
621
- kind = "BUNDLE",
622
- releaseId = releaseId,
623
- bundleId = bundleId,
624
- catalogId = "project-a",
625
- scopeKey = "scope-production",
626
- generation = generation,
627
- catalogHash = catalogHash,
628
- channel = "production",
629
- selectionContextHash = selectionContextHash,
630
- )
631
-
632
- private fun createBundleDir(
633
- rootDir: File,
634
- bundleId: String,
635
- ): File = File(bundleStoreDir(rootDir), bundleId).apply { mkdirs() }
636
-
637
- private fun writeManifest(
638
- bundleDir: File,
639
- assetPaths: List<String>,
640
- ) {
641
- val assets =
642
- JSONObject().apply {
643
- assetPaths.forEach { assetPath ->
644
- put(assetPath, JSONObject().put("fileHash", "$assetPath-hash"))
645
- }
646
- }
647
-
648
- File(bundleDir, "manifest.json").writeText(
649
- JSONObject()
650
- .put("bundleId", bundleDir.name)
651
- .put("assets", assets)
652
- .toString(),
653
- )
654
- }
655
-
656
- private fun writeMetadata(
657
- rootDir: File,
658
- metadata: BundleMetadata,
659
- ) {
660
- assertTrue(metadata.saveToFile(File(bundleStoreDir(rootDir), BundleMetadata.METADATA_FILENAME)))
661
- }
662
-
663
- private fun loadMetadata(rootDir: File): BundleMetadata? =
664
- BundleMetadata.loadFromFile(
665
- File(bundleStoreDir(rootDir), BundleMetadata.METADATA_FILENAME),
666
- TEST_ISOLATION_KEY,
667
- )
668
-
669
- private fun loadInstallationIdentity(rootDir: File): InstallationIdentity? =
670
- InstallationIdentity.loadFromFile(
671
- File(bundleStoreDir(rootDir), InstallationIdentity.IDENTITY_FILENAME),
672
- )
673
-
674
- private fun writeFile(
675
- rootDir: File,
676
- relativePath: String,
677
- content: String = "bundle-content",
678
- ): File =
679
- File(rootDir, relativePath).apply {
680
- parentFile?.mkdirs()
681
- writeText(content)
682
- }
683
-
684
- private fun writeZipEntry(
685
- zip: ZipOutputStream,
686
- path: String,
687
- content: String,
688
- ) {
689
- zip.putNextEntry(ZipEntry(path))
690
- zip.write(content.toByteArray())
691
- zip.closeEntry()
692
- }
693
-
694
- private fun bundleStoreDir(rootDir: File): File = File(rootDir, "bundle-store").apply { mkdirs() }
695
-
696
- private fun invokeResolveBundleFile(
697
- service: BundleFileStorageService,
698
- bundleDir: File,
699
- ): File? {
700
- val method =
701
- BundleFileStorageService::class.java.getDeclaredMethod(
702
- "resolveBundleFile",
703
- File::class.java,
704
- String::class.java,
705
- )
706
- method.isAccessible = true
707
- return method.invoke(service, bundleDir, bundleDir.name) as File?
708
- }
709
-
710
- private fun invokeCanUseManifestDrivenInstall(service: BundleFileStorageService): Boolean {
711
- val method = BundleFileStorageService::class.java.getDeclaredMethod("canUseManifestDrivenInstall")
712
- method.isAccessible = true
713
- return method.invoke(service) as Boolean
714
- }
715
-
716
- private fun assertResolvedBundlePath(
717
- service: BundleFileStorageService,
718
- bundleDir: File,
719
- expected: File,
720
- ) {
721
- val resolved = invokeResolveBundleFile(service, bundleDir)
722
-
723
- assertNotNull(resolved)
724
- assertEquals(expected.canonicalFile.absolutePath, resolved?.canonicalFile?.absolutePath)
725
- }
726
-
727
- private class TestFileSystemService(
728
- private val internalFilesDir: File,
729
- ) : FileSystemService {
730
- override fun fileExists(path: String): Boolean = File(path).exists()
731
-
732
- override fun createDirectory(path: String): Boolean = File(path).mkdirs()
733
-
734
- override fun removeItem(path: String): Boolean = File(path).deleteRecursively()
735
-
736
- override fun moveItem(
737
- sourcePath: String,
738
- destinationPath: String,
739
- ): Boolean = File(sourcePath).renameTo(File(destinationPath))
740
-
741
- override fun copyItem(
742
- sourcePath: String,
743
- destinationPath: String,
744
- ): Boolean =
745
- try {
746
- File(sourcePath).copyRecursively(File(destinationPath), overwrite = true)
747
- } catch (_: Exception) {
748
- false
749
- }
750
-
751
- override fun contentsOfDirectory(path: String): List<String> = File(path).list()?.toList() ?: emptyList()
752
-
753
- override fun getInternalFilesDir(): File = internalFilesDir
754
- }
755
-
756
- private class InMemoryPreferencesService : PreferencesService {
757
- private val values = mutableMapOf<String, String?>()
758
-
759
- override fun getItem(key: String): String? = values[key]
760
-
761
- override fun setItem(
762
- key: String,
763
- value: String?,
764
- ) {
765
- if (value == null) {
766
- values.remove(key)
767
- } else {
768
- values[key] = value
769
- }
770
- }
771
- }
772
-
773
- private object UnusedDownloadService : DownloadService {
774
- override suspend fun downloadFile(
775
- fileUrl: URL,
776
- destination: File,
777
- fileSizeCallback: ((Long) -> Unit)?,
778
- progressCallback: (DownloadProgress) -> Unit,
779
- ): DownloadResult = error("downloadFile should not be called in these tests")
780
- }
781
-
782
- private class RecordingFailedDownloadService : DownloadService {
783
- val calls = CopyOnWriteArrayList<Pair<String, String>>()
784
-
785
- override suspend fun downloadFile(
786
- fileUrl: URL,
787
- destination: File,
788
- fileSizeCallback: ((Long) -> Unit)?,
789
- progressCallback: (DownloadProgress) -> Unit,
790
- ): DownloadResult {
791
- calls += fileUrl.toString() to Thread.currentThread().name
792
- return DownloadResult.Error(IllegalStateException("expected download failure"))
793
- }
794
- }
795
-
796
- companion object {
797
- private const val TEST_ISOLATION_KEY = "test-isolation-key"
798
- }
799
- }