@hot-updater/react-native 1.0.0-rc.15 → 1.0.0-rc.17

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 (302) hide show
  1. package/android/src/main/java/com/hotupdater/BundleFileStorageService.kt +491 -388
  2. package/android/src/main/java/com/hotupdater/BundleMetadata.kt +5 -11
  3. package/android/src/main/java/com/hotupdater/FileManagerService.kt +45 -0
  4. package/android/src/main/java/com/hotupdater/HotUpdaterException.kt +290 -12
  5. package/android/src/main/java/com/hotupdater/HotUpdaterImpl.kt +35 -19
  6. package/android/src/main/java/com/hotupdater/InstallIdentityService.kt +37 -0
  7. package/android/src/main/java/com/hotupdater/KeyValueStorageService.kt +86 -0
  8. package/android/src/main/java/com/hotupdater/NetworkState.kt +31 -0
  9. package/android/src/main/java/com/hotupdater/NoBackupStorage.kt +17 -0
  10. package/android/src/main/java/com/hotupdater/OkHttpDownloadService.kt +110 -52
  11. package/android/src/main/java/com/hotupdater/PreviousProcessExit.kt +89 -0
  12. package/android/src/main/java/com/hotupdater/ReactNativeValueConverters.kt +34 -0
  13. package/android/src/newarch/HotUpdaterModule.kt +74 -69
  14. package/android/src/oldarch/HotUpdaterModule.kt +63 -59
  15. package/android/src/oldarch/HotUpdaterSpec.kt +4 -6
  16. package/android/src/test/java/com/hotupdater/BsdiffPatchTest.kt +1 -1
  17. package/android/src/test/java/com/hotupdater/BundleFileStorageServiceTest.kt +446 -61
  18. package/android/src/test/java/com/hotupdater/HotUpdaterExceptionTest.kt +220 -0
  19. package/android/src/test/java/com/hotupdater/HotUpdaterImplTest.kt +19 -27
  20. package/android/src/test/java/com/hotupdater/InstallIdentityServiceTest.kt +71 -0
  21. package/android/src/test/java/com/hotupdater/KeyValueStorageServiceTest.kt +106 -0
  22. package/android/src/test/java/com/hotupdater/NoBackupContext.kt +11 -0
  23. package/android/src/test/java/com/hotupdater/OkHttpDownloadServiceTest.kt +309 -23
  24. package/android/src/test/java/com/hotupdater/PreviousProcessExitTest.kt +57 -0
  25. package/ios/HotUpdater/Internal/BundleFileStorageService.swift +238 -157
  26. package/ios/HotUpdater/Internal/BundleMetadata.swift +3 -53
  27. package/ios/HotUpdater/Internal/FileManagerService.swift +48 -0
  28. package/ios/HotUpdater/Internal/HotUpdater.mm +8 -18
  29. package/ios/HotUpdater/Internal/HotUpdaterImpl.swift +48 -29
  30. package/ios/HotUpdater/Internal/KeyValueStorageService.swift +71 -0
  31. package/ios/HotUpdater/Internal/URLSessionDownloadService.swift +108 -1
  32. package/ios/HotUpdater/Internal/UpdateFailure.swift +390 -0
  33. package/lib/commonjs/appReady.js +111 -0
  34. package/lib/commonjs/appReady.js.map +1 -0
  35. package/lib/commonjs/appReady.spec.js +360 -0
  36. package/lib/commonjs/appReady.spec.js.map +1 -0
  37. package/lib/commonjs/{notifyAppReadyInsights.test-utils.js → appReady.test-utils.js} +4 -3
  38. package/lib/commonjs/appReady.test-utils.js.map +1 -0
  39. package/lib/commonjs/baseURL.js +16 -0
  40. package/lib/commonjs/baseURL.js.map +1 -0
  41. package/lib/commonjs/checkForUpdate.js +104 -56
  42. package/lib/commonjs/checkForUpdate.js.map +1 -1
  43. package/lib/commonjs/checkForUpdate.releaseCatalog.spec.js +337 -74
  44. package/lib/commonjs/checkForUpdate.releaseCatalog.spec.js.map +1 -1
  45. package/lib/commonjs/clientPlugin.js +49 -0
  46. package/lib/commonjs/clientPlugin.js.map +1 -0
  47. package/lib/commonjs/entryImportGraph.spec.js +50 -0
  48. package/lib/commonjs/entryImportGraph.spec.js.map +1 -0
  49. package/lib/commonjs/fetchJSON.js +2 -6
  50. package/lib/commonjs/fetchJSON.js.map +1 -1
  51. package/lib/commonjs/httpClient.js +15 -67
  52. package/lib/commonjs/httpClient.js.map +1 -1
  53. package/lib/commonjs/httpClient.spec.js +26 -9
  54. package/lib/commonjs/httpClient.spec.js.map +1 -1
  55. package/lib/commonjs/index.js +49 -25
  56. package/lib/commonjs/index.js.map +1 -1
  57. package/lib/commonjs/index.spec.js +126 -40
  58. package/lib/commonjs/index.spec.js.map +1 -1
  59. package/lib/commonjs/native.js +67 -48
  60. package/lib/commonjs/native.js.map +1 -1
  61. package/lib/commonjs/native.spec.js +80 -58
  62. package/lib/commonjs/native.spec.js.map +1 -1
  63. package/lib/commonjs/pluginHost.js +208 -0
  64. package/lib/commonjs/pluginHost.js.map +1 -0
  65. package/lib/commonjs/pluginHost.spec.js +292 -0
  66. package/lib/commonjs/pluginHost.spec.js.map +1 -0
  67. package/lib/commonjs/plugins/insights/eventId.js +129 -0
  68. package/lib/commonjs/plugins/insights/eventId.js.map +1 -0
  69. package/lib/commonjs/plugins/insights/eventId.spec.js +47 -0
  70. package/lib/commonjs/plugins/insights/eventId.spec.js.map +1 -0
  71. package/lib/commonjs/plugins/insights/index.js +272 -0
  72. package/lib/commonjs/plugins/insights/index.js.map +1 -0
  73. package/lib/commonjs/plugins/insights/insights.spec.js +640 -0
  74. package/lib/commonjs/plugins/insights/insights.spec.js.map +1 -0
  75. package/lib/commonjs/plugins/insights/sender.js +128 -0
  76. package/lib/commonjs/plugins/insights/sender.js.map +1 -0
  77. package/lib/commonjs/plugins/insights/sender.spec.js +265 -0
  78. package/lib/commonjs/plugins/insights/sender.spec.js.map +1 -0
  79. package/lib/commonjs/plugins/insights/state.js +145 -0
  80. package/lib/commonjs/plugins/insights/state.js.map +1 -0
  81. package/lib/commonjs/releaseCatalogCache.js +13 -2
  82. package/lib/commonjs/releaseCatalogCache.js.map +1 -1
  83. package/lib/commonjs/releaseCatalogCache.spec.js +18 -0
  84. package/lib/commonjs/releaseCatalogCache.spec.js.map +1 -1
  85. package/lib/commonjs/sdkVersion.js +1 -1
  86. package/lib/commonjs/specs/NativeHotUpdater.js.map +1 -1
  87. package/lib/commonjs/updateError.js +132 -0
  88. package/lib/commonjs/updateError.js.map +1 -0
  89. package/lib/commonjs/updateError.spec.js +149 -0
  90. package/lib/commonjs/updateError.spec.js.map +1 -0
  91. package/lib/commonjs/wrap.js +43 -25
  92. package/lib/commonjs/wrap.js.map +1 -1
  93. package/lib/commonjs/wrap.progress.spec.js +195 -0
  94. package/lib/commonjs/wrap.progress.spec.js.map +1 -0
  95. package/lib/commonjs/wrap.spec.js +94 -230
  96. package/lib/commonjs/wrap.spec.js.map +1 -1
  97. package/lib/module/appReady.js +103 -0
  98. package/lib/module/appReady.js.map +1 -0
  99. package/lib/module/appReady.spec.js +359 -0
  100. package/lib/module/appReady.spec.js.map +1 -0
  101. package/lib/module/{notifyAppReadyInsights.test-utils.js → appReady.test-utils.js} +4 -3
  102. package/lib/module/appReady.test-utils.js.map +1 -0
  103. package/lib/module/baseURL.js +11 -0
  104. package/lib/module/baseURL.js.map +1 -0
  105. package/lib/module/checkForUpdate.js +103 -57
  106. package/lib/module/checkForUpdate.js.map +1 -1
  107. package/lib/module/checkForUpdate.releaseCatalog.spec.js +337 -74
  108. package/lib/module/checkForUpdate.releaseCatalog.spec.js.map +1 -1
  109. package/lib/module/clientPlugin.js +44 -0
  110. package/lib/module/clientPlugin.js.map +1 -0
  111. package/lib/module/entryImportGraph.spec.js +50 -0
  112. package/lib/module/entryImportGraph.spec.js.map +1 -0
  113. package/lib/module/fetchJSON.js +2 -6
  114. package/lib/module/fetchJSON.js.map +1 -1
  115. package/lib/module/httpClient.js +14 -66
  116. package/lib/module/httpClient.js.map +1 -1
  117. package/lib/module/httpClient.spec.js +26 -9
  118. package/lib/module/httpClient.spec.js.map +1 -1
  119. package/lib/module/index.js +44 -27
  120. package/lib/module/index.js.map +1 -1
  121. package/lib/module/index.spec.js +127 -41
  122. package/lib/module/index.spec.js.map +1 -1
  123. package/lib/module/native.js +63 -44
  124. package/lib/module/native.js.map +1 -1
  125. package/lib/module/native.spec.js +80 -58
  126. package/lib/module/native.spec.js.map +1 -1
  127. package/lib/module/pluginHost.js +200 -0
  128. package/lib/module/pluginHost.js.map +1 -0
  129. package/lib/module/pluginHost.spec.js +291 -0
  130. package/lib/module/pluginHost.spec.js.map +1 -0
  131. package/lib/module/plugins/insights/eventId.js +122 -0
  132. package/lib/module/plugins/insights/eventId.js.map +1 -0
  133. package/lib/module/plugins/insights/eventId.spec.js +47 -0
  134. package/lib/module/plugins/insights/eventId.spec.js.map +1 -0
  135. package/lib/module/plugins/insights/index.js +268 -0
  136. package/lib/module/plugins/insights/index.js.map +1 -0
  137. package/lib/module/plugins/insights/insights.spec.js +639 -0
  138. package/lib/module/plugins/insights/insights.spec.js.map +1 -0
  139. package/lib/module/plugins/insights/sender.js +123 -0
  140. package/lib/module/plugins/insights/sender.js.map +1 -0
  141. package/lib/module/plugins/insights/sender.spec.js +265 -0
  142. package/lib/module/plugins/insights/sender.spec.js.map +1 -0
  143. package/lib/module/plugins/insights/state.js +139 -0
  144. package/lib/module/plugins/insights/state.js.map +1 -0
  145. package/lib/module/releaseCatalogCache.js +13 -2
  146. package/lib/module/releaseCatalogCache.js.map +1 -1
  147. package/lib/module/releaseCatalogCache.spec.js +18 -0
  148. package/lib/module/releaseCatalogCache.spec.js.map +1 -1
  149. package/lib/module/sdkVersion.js +1 -1
  150. package/lib/module/specs/NativeHotUpdater.js.map +1 -1
  151. package/lib/module/updateError.js +126 -0
  152. package/lib/module/updateError.js.map +1 -0
  153. package/lib/module/updateError.spec.js +149 -0
  154. package/lib/module/updateError.spec.js.map +1 -0
  155. package/lib/module/wrap.js +43 -25
  156. package/lib/module/wrap.js.map +1 -1
  157. package/lib/module/wrap.progress.spec.js +196 -0
  158. package/lib/module/wrap.progress.spec.js.map +1 -0
  159. package/lib/module/wrap.spec.js +93 -229
  160. package/lib/module/wrap.spec.js.map +1 -1
  161. package/lib/typescript/commonjs/appReady.d.ts +23 -0
  162. package/lib/typescript/commonjs/appReady.d.ts.map +1 -0
  163. package/lib/typescript/commonjs/appReady.test-utils.d.ts +9 -0
  164. package/lib/typescript/commonjs/appReady.test-utils.d.ts.map +1 -0
  165. package/lib/typescript/commonjs/baseURL.d.ts +4 -0
  166. package/lib/typescript/commonjs/baseURL.d.ts.map +1 -0
  167. package/lib/typescript/commonjs/checkForUpdate.d.ts +15 -2
  168. package/lib/typescript/commonjs/checkForUpdate.d.ts.map +1 -1
  169. package/lib/typescript/commonjs/clientPlugin.d.ts +185 -0
  170. package/lib/typescript/commonjs/clientPlugin.d.ts.map +1 -0
  171. package/lib/typescript/commonjs/fetchJSON.d.ts +2 -3
  172. package/lib/typescript/commonjs/fetchJSON.d.ts.map +1 -1
  173. package/lib/typescript/commonjs/httpClient.d.ts +2 -30
  174. package/lib/typescript/commonjs/httpClient.d.ts.map +1 -1
  175. package/lib/typescript/commonjs/index.d.ts +3 -6
  176. package/lib/typescript/commonjs/index.d.ts.map +1 -1
  177. package/lib/typescript/commonjs/native.d.ts +29 -15
  178. package/lib/typescript/commonjs/native.d.ts.map +1 -1
  179. package/lib/typescript/commonjs/pluginHost.d.ts +29 -0
  180. package/lib/typescript/commonjs/pluginHost.d.ts.map +1 -0
  181. package/lib/typescript/commonjs/plugins/insights/eventId.d.ts +18 -0
  182. package/lib/typescript/commonjs/plugins/insights/eventId.d.ts.map +1 -0
  183. package/lib/typescript/commonjs/plugins/insights/index.d.ts +40 -0
  184. package/lib/typescript/commonjs/plugins/insights/index.d.ts.map +1 -0
  185. package/lib/typescript/commonjs/plugins/insights/sender.d.ts +74 -0
  186. package/lib/typescript/commonjs/plugins/insights/sender.d.ts.map +1 -0
  187. package/lib/typescript/commonjs/plugins/insights/state.d.ts +46 -0
  188. package/lib/typescript/commonjs/plugins/insights/state.d.ts.map +1 -0
  189. package/lib/typescript/commonjs/releaseCatalogCache.d.ts +5 -1
  190. package/lib/typescript/commonjs/releaseCatalogCache.d.ts.map +1 -1
  191. package/lib/typescript/commonjs/sdkVersion.d.ts +1 -1
  192. package/lib/typescript/commonjs/specs/NativeHotUpdater.d.ts +34 -12
  193. package/lib/typescript/commonjs/specs/NativeHotUpdater.d.ts.map +1 -1
  194. package/lib/typescript/commonjs/updateError.d.ts +26 -0
  195. package/lib/typescript/commonjs/updateError.d.ts.map +1 -0
  196. package/lib/typescript/commonjs/wrap.d.ts.map +1 -1
  197. package/lib/typescript/commonjs/wrap.types.d.ts +6 -5
  198. package/lib/typescript/commonjs/wrap.types.d.ts.map +1 -1
  199. package/lib/typescript/module/appReady.d.ts +23 -0
  200. package/lib/typescript/module/appReady.d.ts.map +1 -0
  201. package/lib/typescript/module/appReady.test-utils.d.ts +9 -0
  202. package/lib/typescript/module/appReady.test-utils.d.ts.map +1 -0
  203. package/lib/typescript/module/baseURL.d.ts +4 -0
  204. package/lib/typescript/module/baseURL.d.ts.map +1 -0
  205. package/lib/typescript/module/checkForUpdate.d.ts +15 -2
  206. package/lib/typescript/module/checkForUpdate.d.ts.map +1 -1
  207. package/lib/typescript/module/clientPlugin.d.ts +185 -0
  208. package/lib/typescript/module/clientPlugin.d.ts.map +1 -0
  209. package/lib/typescript/module/fetchJSON.d.ts +2 -3
  210. package/lib/typescript/module/fetchJSON.d.ts.map +1 -1
  211. package/lib/typescript/module/httpClient.d.ts +2 -30
  212. package/lib/typescript/module/httpClient.d.ts.map +1 -1
  213. package/lib/typescript/module/index.d.ts +3 -6
  214. package/lib/typescript/module/index.d.ts.map +1 -1
  215. package/lib/typescript/module/native.d.ts +29 -15
  216. package/lib/typescript/module/native.d.ts.map +1 -1
  217. package/lib/typescript/module/pluginHost.d.ts +29 -0
  218. package/lib/typescript/module/pluginHost.d.ts.map +1 -0
  219. package/lib/typescript/module/plugins/insights/eventId.d.ts +18 -0
  220. package/lib/typescript/module/plugins/insights/eventId.d.ts.map +1 -0
  221. package/lib/typescript/module/plugins/insights/index.d.ts +40 -0
  222. package/lib/typescript/module/plugins/insights/index.d.ts.map +1 -0
  223. package/lib/typescript/module/plugins/insights/sender.d.ts +74 -0
  224. package/lib/typescript/module/plugins/insights/sender.d.ts.map +1 -0
  225. package/lib/typescript/module/plugins/insights/state.d.ts +46 -0
  226. package/lib/typescript/module/plugins/insights/state.d.ts.map +1 -0
  227. package/lib/typescript/module/releaseCatalogCache.d.ts +5 -1
  228. package/lib/typescript/module/releaseCatalogCache.d.ts.map +1 -1
  229. package/lib/typescript/module/sdkVersion.d.ts +1 -1
  230. package/lib/typescript/module/specs/NativeHotUpdater.d.ts +34 -12
  231. package/lib/typescript/module/specs/NativeHotUpdater.d.ts.map +1 -1
  232. package/lib/typescript/module/updateError.d.ts +26 -0
  233. package/lib/typescript/module/updateError.d.ts.map +1 -0
  234. package/lib/typescript/module/wrap.d.ts.map +1 -1
  235. package/lib/typescript/module/wrap.types.d.ts +6 -5
  236. package/lib/typescript/module/wrap.types.d.ts.map +1 -1
  237. package/package.json +14 -2
  238. package/plugins/insights/package.json +8 -0
  239. package/src/appReady.spec.ts +393 -0
  240. package/src/{notifyAppReadyInsights.test-utils.ts → appReady.test-utils.ts} +7 -7
  241. package/src/appReady.ts +149 -0
  242. package/src/baseURL.ts +15 -0
  243. package/src/checkForUpdate.releaseCatalog.spec.ts +370 -76
  244. package/src/checkForUpdate.ts +135 -80
  245. package/src/clientPlugin.ts +231 -0
  246. package/src/entryImportGraph.spec.ts +65 -0
  247. package/src/fetchJSON.ts +3 -10
  248. package/src/httpClient.spec.ts +28 -9
  249. package/src/httpClient.ts +16 -109
  250. package/src/index.spec.ts +129 -36
  251. package/src/index.ts +67 -37
  252. package/src/native.spec.ts +98 -72
  253. package/src/native.ts +93 -77
  254. package/src/pluginHost.spec.ts +297 -0
  255. package/src/pluginHost.ts +262 -0
  256. package/src/plugins/insights/eventId.spec.ts +60 -0
  257. package/src/plugins/insights/eventId.ts +171 -0
  258. package/src/plugins/insights/index.ts +367 -0
  259. package/src/plugins/insights/insights.spec.ts +732 -0
  260. package/src/plugins/insights/sender.spec.ts +287 -0
  261. package/src/plugins/insights/sender.ts +221 -0
  262. package/src/plugins/insights/state.ts +184 -0
  263. package/src/releaseCatalogCache.spec.ts +22 -0
  264. package/src/releaseCatalogCache.ts +19 -4
  265. package/src/sdkVersion.ts +1 -1
  266. package/src/specs/NativeHotUpdater.ts +34 -13
  267. package/src/updateError.spec.ts +144 -0
  268. package/src/updateError.ts +153 -0
  269. package/src/wrap.progress.spec.ts +232 -0
  270. package/src/wrap.spec.ts +100 -283
  271. package/src/wrap.tsx +57 -26
  272. package/src/wrap.types.ts +6 -5
  273. package/lib/commonjs/notifyAppReadyInsights.boundary.spec.js +0 -225
  274. package/lib/commonjs/notifyAppReadyInsights.boundary.spec.js.map +0 -1
  275. package/lib/commonjs/notifyAppReadyInsights.js +0 -188
  276. package/lib/commonjs/notifyAppReadyInsights.js.map +0 -1
  277. package/lib/commonjs/notifyAppReadyInsights.spec.js +0 -378
  278. package/lib/commonjs/notifyAppReadyInsights.spec.js.map +0 -1
  279. package/lib/commonjs/notifyAppReadyInsights.test-utils.js.map +0 -1
  280. package/lib/commonjs/notifyAppReadyInsights.transport.spec.js +0 -124
  281. package/lib/commonjs/notifyAppReadyInsights.transport.spec.js.map +0 -1
  282. package/lib/module/notifyAppReadyInsights.boundary.spec.js +0 -224
  283. package/lib/module/notifyAppReadyInsights.boundary.spec.js.map +0 -1
  284. package/lib/module/notifyAppReadyInsights.js +0 -181
  285. package/lib/module/notifyAppReadyInsights.js.map +0 -1
  286. package/lib/module/notifyAppReadyInsights.spec.js +0 -377
  287. package/lib/module/notifyAppReadyInsights.spec.js.map +0 -1
  288. package/lib/module/notifyAppReadyInsights.test-utils.js.map +0 -1
  289. package/lib/module/notifyAppReadyInsights.transport.spec.js +0 -123
  290. package/lib/module/notifyAppReadyInsights.transport.spec.js.map +0 -1
  291. package/lib/typescript/commonjs/notifyAppReadyInsights.d.ts +0 -25
  292. package/lib/typescript/commonjs/notifyAppReadyInsights.d.ts.map +0 -1
  293. package/lib/typescript/commonjs/notifyAppReadyInsights.test-utils.d.ts +0 -8
  294. package/lib/typescript/commonjs/notifyAppReadyInsights.test-utils.d.ts.map +0 -1
  295. package/lib/typescript/module/notifyAppReadyInsights.d.ts +0 -25
  296. package/lib/typescript/module/notifyAppReadyInsights.d.ts.map +0 -1
  297. package/lib/typescript/module/notifyAppReadyInsights.test-utils.d.ts +0 -8
  298. package/lib/typescript/module/notifyAppReadyInsights.test-utils.d.ts.map +0 -1
  299. package/src/notifyAppReadyInsights.boundary.spec.ts +0 -242
  300. package/src/notifyAppReadyInsights.spec.ts +0 -362
  301. package/src/notifyAppReadyInsights.transport.spec.ts +0 -138
  302. package/src/notifyAppReadyInsights.ts +0 -293
@@ -0,0 +1,220 @@
1
+ package com.hotupdater
2
+
3
+ import org.junit.Assert.assertEquals
4
+ import org.junit.Assert.assertNull
5
+ import org.junit.Assert.assertSame
6
+ import org.junit.Test
7
+ import java.io.IOException
8
+ import java.io.InterruptedIOException
9
+ import java.net.ConnectException
10
+ import java.net.MalformedURLException
11
+ import java.net.NoRouteToHostException
12
+ import java.net.SocketException
13
+ import java.net.SocketTimeoutException
14
+ import java.net.UnknownHostException
15
+ import javax.net.ssl.SSLHandshakeException
16
+ import javax.net.ssl.SSLPeerUnverifiedException
17
+
18
+ class HotUpdaterExceptionTest {
19
+ private val invalidResponse = UpdateFailure.download(UpdateFailureReason.INVALID_RESPONSE)
20
+ private val network = UpdateFailure.download(UpdateFailureReason.NETWORK)
21
+ private val signature = UpdateFailure.download(UpdateFailureReason.SIGNATURE)
22
+ private val storage = UpdateFailure.install(UpdateFailureReason.STORAGE)
23
+
24
+ @Test
25
+ fun `enum values match the JS contract`() {
26
+ assertEquals(listOf("download", "install"), UpdateFailureStage.entries.map { it.value })
27
+ assertEquals(
28
+ listOf(
29
+ "network",
30
+ "http",
31
+ "invalid_response",
32
+ "hash_mismatch",
33
+ "signature",
34
+ "patch",
35
+ "extract",
36
+ "storage",
37
+ "unknown",
38
+ ),
39
+ UpdateFailureReason.entries.map { it.value },
40
+ )
41
+ assertEquals(listOf("manifest", "file", "patch", "archive"), UpdateFailureResource.entries.map { it.value })
42
+ assertEquals(
43
+ listOf("timeout", "dns", "tls", "connection", "offline", "cancelled"),
44
+ UpdateFailureTransport.entries.map { it.value },
45
+ )
46
+ }
47
+
48
+ @Test
49
+ fun `userInfo carries each optional key only with its reason`() {
50
+ assertEquals(
51
+ mapOf("stage" to "download", "reason" to "http", "httpStatus" to 404),
52
+ UpdateFailure.http(404).toUserInfo(),
53
+ )
54
+ assertEquals(
55
+ mapOf(
56
+ "stage" to "download",
57
+ "reason" to "http",
58
+ "resource" to "manifest",
59
+ "httpStatus" to 403,
60
+ "originCode" to "AccessDenied",
61
+ ),
62
+ UpdateFailure.http(403, originCode = "AccessDenied").copy(resource = UpdateFailureResource.MANIFEST).toUserInfo(),
63
+ )
64
+ assertEquals(mapOf("stage" to "install", "reason" to "storage"), storage.toUserInfo())
65
+ assertEquals(
66
+ mapOf("stage" to "download", "reason" to "network", "resource" to "file", "transport" to "timeout"),
67
+ UpdateFailure(
68
+ UpdateFailureStage.DOWNLOAD,
69
+ UpdateFailureReason.NETWORK,
70
+ httpStatus = 500,
71
+ resource = UpdateFailureResource.FILE,
72
+ transport = UpdateFailureTransport.TIMEOUT,
73
+ originCode = "Ignored",
74
+ ).toUserInfo(),
75
+ )
76
+ assertEquals(
77
+ mapOf("stage" to "download", "reason" to "http", "httpStatus" to 502),
78
+ UpdateFailure.http(502).copy(transport = UpdateFailureTransport.DNS).toUserInfo(),
79
+ )
80
+ }
81
+
82
+ @Test
83
+ fun `download errors are classified by their cause`() {
84
+ val cases =
85
+ listOf(
86
+ HttpStatusException(403, "Forbidden") to UpdateFailure.http(403),
87
+ HttpStatusException(404, "Not Found", originCode = "NoSuchKey") to UpdateFailure.http(404, originCode = "NoSuchKey"),
88
+ UnknownHostException("updates.example.com") to network.copy(transport = UpdateFailureTransport.DNS),
89
+ SocketTimeoutException("connect timed out") to network.copy(transport = UpdateFailureTransport.TIMEOUT),
90
+ InterruptedIOException("timeout") to network.copy(transport = UpdateFailureTransport.TIMEOUT),
91
+ SSLHandshakeException("Chain validation failed") to network.copy(transport = UpdateFailureTransport.TLS),
92
+ SSLPeerUnverifiedException("Hostname not verified") to network.copy(transport = UpdateFailureTransport.TLS),
93
+ ConnectException("Connection refused") to network.copy(transport = UpdateFailureTransport.CONNECTION),
94
+ NoRouteToHostException("No route to host") to network.copy(transport = UpdateFailureTransport.CONNECTION),
95
+ SocketException("Connection reset") to network.copy(transport = UpdateFailureTransport.CONNECTION),
96
+ IOException("Canceled") to network.copy(transport = UpdateFailureTransport.CANCELLED),
97
+ IOException("unexpected end of stream") to network,
98
+ IncompleteDownloadException(expectedSize = 10, actualSize = 4) to network,
99
+ LocalStorageException(IOException("No space left on device")) to storage,
100
+ MalformedURLException("no protocol: bundle") to invalidResponse,
101
+ IllegalArgumentException("Expected URL scheme 'http' or 'https' but was 'ftp'") to invalidResponse,
102
+ Exception("Response body is null") to UpdateFailure.download(UpdateFailureReason.UNKNOWN),
103
+ )
104
+
105
+ cases.forEach { (cause, failure) ->
106
+ val error = HotUpdaterException.downloadFailed(cause)
107
+ assertEquals("DOWNLOAD_FAILED", error.code)
108
+ assertEquals("$cause", failure, error.failure)
109
+ }
110
+ assertEquals(network, HotUpdaterException.incompleteDownload(expectedSize = 10, actualSize = 4).failure)
111
+ }
112
+
113
+ @Test
114
+ fun `a failed host lookup without an active network is offline`() {
115
+ assertEquals(
116
+ network.copy(transport = UpdateFailureTransport.OFFLINE),
117
+ UpdateFailure.ofDownloadError(UnknownHostException("updates.example.com")) { true },
118
+ )
119
+ assertEquals(
120
+ network.copy(transport = UpdateFailureTransport.TIMEOUT),
121
+ UpdateFailure.ofDownloadError(SocketTimeoutException("timeout")) { error("Only a failed host lookup asks") },
122
+ )
123
+ }
124
+
125
+ @Test
126
+ fun `verification errors report hash mismatch storage or signature`() {
127
+ val cases =
128
+ listOf(
129
+ SignatureVerificationException.FileHashMismatch() to UpdateFailure.download(UpdateFailureReason.HASH_MISMATCH),
130
+ SignatureVerificationException.FileReadFailed() to storage,
131
+ SignatureVerificationException.PublicKeyNotConfigured() to signature,
132
+ SignatureVerificationException.InvalidPublicKeyFormat() to signature,
133
+ SignatureVerificationException.MissingFileHash() to signature,
134
+ SignatureVerificationException.InvalidSignatureFormat() to signature,
135
+ SignatureVerificationException.SignatureVerificationFailed() to signature,
136
+ SignatureVerificationException.UnsignedNotAllowed() to signature,
137
+ SignatureVerificationException.SecurityFrameworkError(IllegalStateException("provider")) to signature,
138
+ )
139
+
140
+ cases.forEach { (cause, failure) ->
141
+ val error = HotUpdaterException.signatureVerificationFailed(cause)
142
+ assertEquals("SIGNATURE_VERIFICATION_FAILED", error.code)
143
+ assertEquals("$cause", failure, error.failure)
144
+ }
145
+ }
146
+
147
+ @Test
148
+ fun `parameter validation keeps its codes and reports an invalid response`() {
149
+ val cases =
150
+ listOf(
151
+ Triple(HotUpdaterException.missingBundleId(), "MISSING_BUNDLE_ID", "Missing or empty 'bundleId'") to invalidResponse,
152
+ Triple(HotUpdaterException.invalidManifestParams(), "INVALID_MANIFEST", "Manifest URL, hash, and assets are required") to
153
+ invalidResponse.copy(resource = UpdateFailureResource.MANIFEST),
154
+ Triple(
155
+ HotUpdaterException.invalidFileUrl("archiveUrl", "not a url", UpdateFailureResource.ARCHIVE),
156
+ "INVALID_FILE_URL",
157
+ "Invalid 'archiveUrl' provided: not a url",
158
+ ) to invalidResponse.copy(resource = UpdateFailureResource.ARCHIVE),
159
+ )
160
+
161
+ cases.forEach { (rejection, failure) ->
162
+ val (error, code, message) = rejection
163
+ assertEquals(code, error.code)
164
+ assertEquals(message, error.message)
165
+ assertEquals(failure, error.failure)
166
+ }
167
+ assertEquals(invalidResponse, HotUpdaterException.invalidBundle().failure)
168
+ }
169
+
170
+ @Test
171
+ fun `storage failures report install storage`() {
172
+ val cases =
173
+ listOf(
174
+ HotUpdaterException.directoryCreationFailed() to "DIRECTORY_CREATION_FAILED",
175
+ HotUpdaterException.moveOperationFailed() to "MOVE_OPERATION_FAILED",
176
+ HotUpdaterException.insufficientDiskSpace(required = 10, available = 1) to "INSUFFICIENT_DISK_SPACE",
177
+ HotUpdaterException.storageFailed("Failed to persist bundle metadata") to "UNKNOWN_ERROR",
178
+ )
179
+
180
+ cases.forEach { (error, code) ->
181
+ assertEquals(code, error.code)
182
+ assertEquals(storage, error.failure)
183
+ }
184
+ }
185
+
186
+ @Test
187
+ fun `unexpected errors keep the UNKNOWN_ERROR code and message`() {
188
+ val cause = IllegalStateException("boom")
189
+ val error = HotUpdaterException.unexpected(cause, UpdateFailure.ofUnexpectedError(cause, UpdateFailureStage.INSTALL))
190
+
191
+ assertEquals("UNKNOWN_ERROR", error.code)
192
+ assertEquals("boom", error.message)
193
+ assertSame(cause, error.cause)
194
+ assertEquals(UpdateFailure.install(UpdateFailureReason.UNKNOWN), error.failure)
195
+ assertEquals(storage, UpdateFailure.ofUnexpectedError(IOException("read failed"), UpdateFailureStage.DOWNLOAD))
196
+ assertEquals(invalidResponse, UpdateFailure.ofUnexpectedError(MalformedURLException(), UpdateFailureStage.DOWNLOAD))
197
+ }
198
+
199
+ @Test
200
+ fun `withResource names the resource only when none is named`() {
201
+ val cause = IOException("reset")
202
+ val error = HotUpdaterException.downloadFailed(cause)
203
+
204
+ val named = error.withResource(UpdateFailureResource.FILE)
205
+ assertEquals("DOWNLOAD_FAILED", named.code)
206
+ assertEquals(error.message, named.message)
207
+ assertSame(cause, named.cause)
208
+ assertEquals(error.failure?.copy(resource = UpdateFailureResource.FILE), named.failure)
209
+
210
+ assertSame(named, named.withResource(UpdateFailureResource.MANIFEST))
211
+ assertSame(error, error.withResource(null))
212
+ val crashed = HotUpdaterException.bundleInCrashedHistory("crashed-bundle")
213
+ assertSame(crashed, crashed.withResource(UpdateFailureResource.FILE))
214
+ }
215
+
216
+ @Test
217
+ fun `a bundle in the crashed history is not an update failure`() {
218
+ assertNull(HotUpdaterException.bundleInCrashedHistory("crashed-bundle").failure)
219
+ }
220
+ }
@@ -3,9 +3,15 @@ package com.hotupdater
3
3
  import org.junit.Assert.assertEquals
4
4
  import org.junit.Assert.assertNull
5
5
  import org.junit.Assert.assertTrue
6
+ import org.junit.Rule
6
7
  import org.junit.Test
8
+ import org.junit.rules.TemporaryFolder
9
+ import java.io.File
7
10
 
8
11
  class HotUpdaterImplTest {
12
+ @get:Rule
13
+ val temporaryFolder = TemporaryFolder()
14
+
9
15
  @Test
10
16
  fun `getBundleId returns built in while native launch selection is missing`() {
11
17
  val impl =
@@ -126,20 +132,22 @@ class HotUpdaterImplTest {
126
132
  }
127
133
 
128
134
  @Test
129
- fun `getInstallId and setUser delegate to bundle storage`() {
130
- val storage = FakeBundleStorageService(bundleId = null)
135
+ fun `install id and storage items come from their no backup stores`() {
136
+ val identityFile = File(temporaryFolder.root, "hot-updater/install-identity.json")
137
+ val storageFile = File(temporaryFolder.root, "hot-updater/storage.json")
131
138
  val impl = allocateWithoutConstructor<HotUpdaterImpl>()
132
- setField(impl, "bundleStorage", storage)
139
+ setField(impl, "installIdentity", InstallIdentityService(identityFile))
140
+ setField(impl, "keyValueStorage", KeyValueStorageService(storageFile))
133
141
 
134
- assertEquals("test-install-id", impl.getInstallId())
142
+ val installId = impl.getInstallId()
143
+ assertEquals(installId, InstallationIdentity.loadFromFile(identityFile)?.installId)
135
144
 
136
- impl.setUser("user-123", "alice")
137
- assertEquals("user-123", storage.lastUserId)
138
- assertEquals("alice", storage.lastUsername)
145
+ impl.setStorageItem("plugins/insights/queue", "[]")
146
+ assertEquals("[]", impl.getStorageItem("plugins/insights/queue"))
147
+ assertEquals("[]", KeyValueStorageService(storageFile).getItem("plugins/insights/queue"))
139
148
 
140
- impl.setUser(null, null)
141
- assertNull(storage.lastUserId)
142
- assertNull(storage.lastUsername)
149
+ impl.setStorageItem("plugins/insights/queue", null)
150
+ assertNull(impl.getStorageItem("plugins/insights/queue"))
143
151
  }
144
152
 
145
153
  private fun createImpl(
@@ -198,8 +206,6 @@ class HotUpdaterImplTest {
198
206
  private val launchedBundleBaseURLs: Map<String, String> = emptyMap(),
199
207
  ) : BundleStorageService {
200
208
  var lastCompletedBundleId: String? = null
201
- var lastUserId: String? = null
202
- var lastUsername: String? = null
203
209
 
204
210
  override fun setBundleURL(localPath: String?): Boolean = true
205
211
 
@@ -221,7 +227,7 @@ class HotUpdaterImplTest {
221
227
  assets: Map<String, ChangedAssetDescriptor>,
222
228
  archiveUrl: String?,
223
229
  progressCallback: (UpdateProgressPayload) -> Unit,
224
- ) = Unit
230
+ ) = UpdateBundleResult(BundleDelivery.MANIFEST, patchFallback = false)
225
231
 
226
232
  override fun markLaunchCompleted(currentBundleId: String?) {
227
233
  lastCompletedBundleId = currentBundleId
@@ -229,20 +235,6 @@ class HotUpdaterImplTest {
229
235
 
230
236
  override fun notifyAppReady(): Map<String, Any?> = mapOf("status" to "UNCHANGED")
231
237
 
232
- override fun getInstallId(): String = "test-install-id"
233
-
234
- override fun getUserId(): String? = lastUserId
235
-
236
- override fun getUsername(): String? = lastUsername
237
-
238
- override fun setUser(
239
- userId: String?,
240
- username: String?,
241
- ) {
242
- lastUserId = userId
243
- lastUsername = username
244
- }
245
-
246
238
  override fun getCrashHistory(): CrashedHistory = CrashedHistory()
247
239
 
248
240
  override fun clearCrashHistory(): Boolean = true
@@ -0,0 +1,71 @@
1
+ package com.hotupdater
2
+
3
+ import org.json.JSONObject
4
+ import org.junit.Assert.assertEquals
5
+ import org.junit.Assert.assertNotEquals
6
+ import org.junit.Rule
7
+ import org.junit.Test
8
+ import org.junit.rules.TemporaryFolder
9
+ import java.io.File
10
+ import java.util.UUID
11
+
12
+ class InstallIdentityServiceTest {
13
+ @get:Rule
14
+ val temporaryFolder = TemporaryFolder()
15
+
16
+ @Test
17
+ fun `creates a random install id once and keeps it across instances`() {
18
+ val identityFile = File(temporaryFolder.root, "hot-updater/install-identity.json")
19
+ val service = InstallIdentityService(identityFile)
20
+
21
+ val installId = service.getInstallId()
22
+
23
+ assertEquals(installId, UUID.fromString(installId).toString())
24
+ assertEquals(installId, service.getInstallId())
25
+ assertEquals(installId, InstallIdentityService(identityFile).getInstallId())
26
+ assertEquals(
27
+ listOf("installId"),
28
+ JSONObject(identityFile.readText()).keys().asSequence().toList(),
29
+ )
30
+ }
31
+
32
+ @Test
33
+ fun `lives under noBackupFilesDir and never reads the old bundle store identity`() {
34
+ val noBackupDirectory = temporaryFolder.newFolder("no_backup")
35
+ val oldIdentityJson = "{\"installId\":\"old-install-id\",\"userId\":\"user-1\",\"username\":\"alice\"}"
36
+ val oldIdentityFile =
37
+ File(temporaryFolder.newFolder("files"), "bundle-store/identity.json").apply {
38
+ parentFile?.mkdirs()
39
+ writeText(oldIdentityJson)
40
+ }
41
+
42
+ val installId = InstallIdentityService.create(NoBackupContext(noBackupDirectory)).getInstallId()
43
+
44
+ assertNotEquals("old-install-id", installId)
45
+ assertEquals(
46
+ InstallationIdentity(installId),
47
+ InstallationIdentity.loadFromFile(File(noBackupDirectory, "hot-updater/install-identity.json")),
48
+ )
49
+ assertEquals(oldIdentityJson, oldIdentityFile.readText())
50
+ }
51
+
52
+ @Test
53
+ fun `replaces an unreadable identity with a new id`() {
54
+ val identityFile = File(temporaryFolder.root, "install-identity.json").apply { writeText("{") }
55
+
56
+ val installId = InstallIdentityService(identityFile).getInstallId()
57
+
58
+ assertEquals(InstallationIdentity(installId), InstallationIdentity.loadFromFile(identityFile))
59
+ }
60
+
61
+ @Test
62
+ fun `keeps the install id for the process after its file becomes unreadable`() {
63
+ val identityFile = File(temporaryFolder.root, "install-identity.json")
64
+ val service = InstallIdentityService(identityFile)
65
+ val installId = service.getInstallId()
66
+
67
+ identityFile.writeText("{")
68
+
69
+ assertEquals(installId, service.getInstallId())
70
+ }
71
+ }
@@ -0,0 +1,106 @@
1
+ package com.hotupdater
2
+
3
+ import org.json.JSONObject
4
+ import org.junit.Assert.assertEquals
5
+ import org.junit.Assert.assertNull
6
+ import org.junit.Rule
7
+ import org.junit.Test
8
+ import org.junit.rules.TemporaryFolder
9
+ import java.io.File
10
+
11
+ class KeyValueStorageServiceTest {
12
+ @get:Rule
13
+ val temporaryFolder = TemporaryFolder()
14
+
15
+ @Test
16
+ fun `sets replaces and removes values`() {
17
+ val storage = KeyValueStorageService(storageFile())
18
+ assertNull(storage.getItem("plugins/insights/queue"))
19
+
20
+ storage.setItem("plugins/insights/queue", "[1]")
21
+ storage.setItem("plugins/insights/day", "2026-09-30")
22
+ storage.setItem("plugins/insights/queue", "[1,2]")
23
+ storage.setItem("plugins/other/empty", "")
24
+ assertEquals("[1,2]", storage.getItem("plugins/insights/queue"))
25
+ assertEquals("", storage.getItem("plugins/other/empty"))
26
+
27
+ storage.setItem("plugins/insights/queue", null)
28
+ storage.setItem("plugins/never-set", null)
29
+ assertNull(storage.getItem("plugins/insights/queue"))
30
+ assertEquals("2026-09-30", storage.getItem("plugins/insights/day"))
31
+ }
32
+
33
+ @Test
34
+ fun `values persist across instances`() {
35
+ val file = storageFile()
36
+ KeyValueStorageService(file).apply {
37
+ setItem("plugins/insights/queue", "[{\"id\":\"é\",\"note\":\"line\\nbreak\"}]")
38
+ setItem("plugins/insights/day", "2026-09-30")
39
+ setItem("plugins/insights/day", null)
40
+ }
41
+
42
+ val reloaded = KeyValueStorageService(file)
43
+
44
+ assertEquals("[{\"id\":\"é\",\"note\":\"line\\nbreak\"}]", reloaded.getItem("plugins/insights/queue"))
45
+ assertNull(reloaded.getItem("plugins/insights/day"))
46
+ }
47
+
48
+ @Test
49
+ fun `a corrupt file reads as empty and the next set replaces it`() {
50
+ val file = storageFile().apply { writeText("{\"plugins/a\":") }
51
+ val storage = KeyValueStorageService(file)
52
+
53
+ assertNull(storage.getItem("plugins/a"))
54
+ storage.setItem("plugins/b", "value")
55
+
56
+ assertEquals(mapOf("plugins/b" to "value"), readStored(file))
57
+ }
58
+
59
+ @Test
60
+ fun `values that are not strings read as missing`() {
61
+ val file = storageFile().apply { writeText("{\"plugins/a\":1,\"plugins/b\":\"kept\",\"plugins/c\":null}") }
62
+ val storage = KeyValueStorageService(file)
63
+
64
+ assertNull(storage.getItem("plugins/a"))
65
+ assertEquals("kept", storage.getItem("plugins/b"))
66
+ assertNull(storage.getItem("plugins/c"))
67
+ }
68
+
69
+ @Test
70
+ fun `concurrent sets are all persisted`() {
71
+ val file = storageFile()
72
+ val storage = KeyValueStorageService(file)
73
+
74
+ (0 until 4)
75
+ .map { worker ->
76
+ Thread {
77
+ repeat(25) { index -> storage.setItem("plugins/worker-$worker/$index", "$worker:$index") }
78
+ }.apply { start() }
79
+ }.forEach(Thread::join)
80
+
81
+ val stored = readStored(file)
82
+ assertEquals(100, stored.size)
83
+ (0 until 4).forEach { worker ->
84
+ repeat(25) { index -> assertEquals("$worker:$index", stored["plugins/worker-$worker/$index"]) }
85
+ }
86
+ }
87
+
88
+ @Test
89
+ fun `is stored under noBackupFilesDir`() {
90
+ val noBackupDirectory = temporaryFolder.newFolder("no_backup")
91
+
92
+ KeyValueStorageService.create(NoBackupContext(noBackupDirectory)).setItem("plugins/insights/queue", "[]")
93
+
94
+ assertEquals(
95
+ mapOf("plugins/insights/queue" to "[]"),
96
+ readStored(File(noBackupDirectory, "hot-updater/storage.json")),
97
+ )
98
+ }
99
+
100
+ private fun storageFile(): File = File(temporaryFolder.newFolder(), "hot-updater/storage.json").apply { parentFile?.mkdirs() }
101
+
102
+ private fun readStored(file: File): Map<String, String> {
103
+ val json = JSONObject(file.readText())
104
+ return json.keys().asSequence().associateWith { json.getString(it) }
105
+ }
106
+ }
@@ -0,0 +1,11 @@
1
+ package com.hotupdater
2
+
3
+ import android.content.ContextWrapper
4
+ import java.io.File
5
+
6
+ /** A context whose `noBackupFilesDir` is a test directory. */
7
+ internal class NoBackupContext(
8
+ private val noBackupDirectory: File,
9
+ ) : ContextWrapper(null) {
10
+ override fun getNoBackupFilesDir(): File = noBackupDirectory
11
+ }