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

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 (240) hide show
  1. package/lib/commonjs/checkForUpdate.js +7 -9
  2. package/lib/commonjs/checkForUpdate.js.map +1 -1
  3. package/lib/commonjs/clientPlugin.js +7 -43
  4. package/lib/commonjs/clientPlugin.js.map +1 -1
  5. package/lib/commonjs/httpClient.js +7 -9
  6. package/lib/commonjs/httpClient.js.map +1 -1
  7. package/lib/commonjs/index.js +10 -0
  8. package/lib/commonjs/index.js.map +1 -1
  9. package/lib/commonjs/native.js +4 -4
  10. package/lib/commonjs/native.js.map +1 -1
  11. package/lib/commonjs/pluginHost.js +26 -190
  12. package/lib/commonjs/pluginHost.js.map +1 -1
  13. package/lib/commonjs/releaseCatalogCache.js +7 -59
  14. package/lib/commonjs/releaseCatalogCache.js.map +1 -1
  15. package/lib/commonjs/sdkVersion.js +1 -1
  16. package/lib/module/checkForUpdate.js +1 -3
  17. package/lib/module/checkForUpdate.js.map +1 -1
  18. package/lib/module/clientPlugin.js +1 -41
  19. package/lib/module/clientPlugin.js.map +1 -1
  20. package/lib/module/httpClient.js +1 -3
  21. package/lib/module/httpClient.js.map +1 -1
  22. package/lib/module/index.js +2 -0
  23. package/lib/module/index.js.map +1 -1
  24. package/lib/module/native.js +1 -1
  25. package/lib/module/native.js.map +1 -1
  26. package/lib/module/pluginHost.js +22 -187
  27. package/lib/module/pluginHost.js.map +1 -1
  28. package/lib/module/releaseCatalogCache.js +3 -54
  29. package/lib/module/releaseCatalogCache.js.map +1 -1
  30. package/lib/module/sdkVersion.js +1 -1
  31. package/lib/typescript/commonjs/checkForUpdate.d.ts.map +1 -1
  32. package/lib/typescript/commonjs/clientPlugin.d.ts +1 -184
  33. package/lib/typescript/commonjs/clientPlugin.d.ts.map +1 -1
  34. package/lib/typescript/commonjs/httpClient.d.ts +1 -2
  35. package/lib/typescript/commonjs/httpClient.d.ts.map +1 -1
  36. package/lib/typescript/commonjs/index.d.ts +1 -0
  37. package/lib/typescript/commonjs/index.d.ts.map +1 -1
  38. package/lib/typescript/commonjs/native.d.ts +1 -1
  39. package/lib/typescript/commonjs/native.d.ts.map +1 -1
  40. package/lib/typescript/commonjs/pluginHost.d.ts +3 -28
  41. package/lib/typescript/commonjs/pluginHost.d.ts.map +1 -1
  42. package/lib/typescript/commonjs/releaseCatalogCache.d.ts +1 -13
  43. package/lib/typescript/commonjs/releaseCatalogCache.d.ts.map +1 -1
  44. package/lib/typescript/commonjs/sdkVersion.d.ts +1 -1
  45. package/lib/typescript/commonjs/types.d.ts +1 -1
  46. package/lib/typescript/commonjs/types.d.ts.map +1 -1
  47. package/lib/typescript/commonjs/wrap.types.d.ts +2 -2
  48. package/lib/typescript/module/checkForUpdate.d.ts.map +1 -1
  49. package/lib/typescript/module/clientPlugin.d.ts +1 -184
  50. package/lib/typescript/module/clientPlugin.d.ts.map +1 -1
  51. package/lib/typescript/module/httpClient.d.ts +1 -2
  52. package/lib/typescript/module/httpClient.d.ts.map +1 -1
  53. package/lib/typescript/module/index.d.ts +1 -0
  54. package/lib/typescript/module/index.d.ts.map +1 -1
  55. package/lib/typescript/module/native.d.ts +1 -1
  56. package/lib/typescript/module/native.d.ts.map +1 -1
  57. package/lib/typescript/module/pluginHost.d.ts +3 -28
  58. package/lib/typescript/module/pluginHost.d.ts.map +1 -1
  59. package/lib/typescript/module/releaseCatalogCache.d.ts +1 -13
  60. package/lib/typescript/module/releaseCatalogCache.d.ts.map +1 -1
  61. package/lib/typescript/module/sdkVersion.d.ts +1 -1
  62. package/lib/typescript/module/types.d.ts +1 -1
  63. package/lib/typescript/module/types.d.ts.map +1 -1
  64. package/lib/typescript/module/wrap.types.d.ts +2 -2
  65. package/package.json +7 -16
  66. package/src/checkForUpdate.ts +4 -6
  67. package/src/clientPlugin.ts +17 -231
  68. package/src/httpClient.ts +4 -4
  69. package/src/index.ts +7 -0
  70. package/src/native.ts +1 -1
  71. package/src/pluginHost.ts +24 -241
  72. package/src/releaseCatalogCache.ts +7 -136
  73. package/src/sdkVersion.ts +1 -1
  74. package/src/types.ts +1 -1
  75. package/src/wrap.types.ts +2 -2
  76. package/android/src/test/java/com/hotupdater/BsdiffPatchTest.kt +0 -46
  77. package/android/src/test/java/com/hotupdater/BundleFileStorageServiceTest.kt +0 -2236
  78. package/android/src/test/java/com/hotupdater/HotUpdaterExceptionTest.kt +0 -220
  79. package/android/src/test/java/com/hotupdater/HotUpdaterImplTest.kt +0 -264
  80. package/android/src/test/java/com/hotupdater/HotUpdaterRecoveryStateTest.kt +0 -97
  81. package/android/src/test/java/com/hotupdater/InstallIdentityServiceTest.kt +0 -71
  82. package/android/src/test/java/com/hotupdater/KeyValueStorageServiceTest.kt +0 -106
  83. package/android/src/test/java/com/hotupdater/NativeConfigUtilsTest.kt +0 -20
  84. package/android/src/test/java/com/hotupdater/NoBackupContext.kt +0 -11
  85. package/android/src/test/java/com/hotupdater/OkHttpDownloadServiceTest.kt +0 -397
  86. package/android/src/test/java/com/hotupdater/PendingCrashRecoveryTest.kt +0 -44
  87. package/android/src/test/java/com/hotupdater/PreviousProcessExitTest.kt +0 -57
  88. package/android/src/test/java/com/hotupdater/RelativePathResolverTest.kt +0 -41
  89. package/android/src/test/java/com/hotupdater/ReleaseCatalogCacheServiceTest.kt +0 -118
  90. package/android/src/test/java/com/hotupdater/TarBrArchiveExtractorTest.kt +0 -206
  91. package/lib/commonjs/appReady.spec.js +0 -360
  92. package/lib/commonjs/appReady.spec.js.map +0 -1
  93. package/lib/commonjs/appReady.test-utils.js +0 -25
  94. package/lib/commonjs/appReady.test-utils.js.map +0 -1
  95. package/lib/commonjs/baseURL.js +0 -16
  96. package/lib/commonjs/baseURL.js.map +0 -1
  97. package/lib/commonjs/catalogCacheNative.spec.js +0 -31
  98. package/lib/commonjs/catalogCacheNative.spec.js.map +0 -1
  99. package/lib/commonjs/checkForUpdate.releaseCatalog.spec.js +0 -936
  100. package/lib/commonjs/checkForUpdate.releaseCatalog.spec.js.map +0 -1
  101. package/lib/commonjs/entryImportGraph.spec.js +0 -50
  102. package/lib/commonjs/entryImportGraph.spec.js.map +0 -1
  103. package/lib/commonjs/httpClient.spec.js +0 -171
  104. package/lib/commonjs/httpClient.spec.js.map +0 -1
  105. package/lib/commonjs/index.spec.js +0 -395
  106. package/lib/commonjs/index.spec.js.map +0 -1
  107. package/lib/commonjs/iosInterop.spec.js +0 -18
  108. package/lib/commonjs/iosInterop.spec.js.map +0 -1
  109. package/lib/commonjs/native.spec.js +0 -910
  110. package/lib/commonjs/native.spec.js.map +0 -1
  111. package/lib/commonjs/pluginHost.spec.js +0 -292
  112. package/lib/commonjs/pluginHost.spec.js.map +0 -1
  113. package/lib/commonjs/plugins/insights/eventId.js +0 -129
  114. package/lib/commonjs/plugins/insights/eventId.js.map +0 -1
  115. package/lib/commonjs/plugins/insights/eventId.spec.js +0 -47
  116. package/lib/commonjs/plugins/insights/eventId.spec.js.map +0 -1
  117. package/lib/commonjs/plugins/insights/index.js +0 -272
  118. package/lib/commonjs/plugins/insights/index.js.map +0 -1
  119. package/lib/commonjs/plugins/insights/insights.spec.js +0 -640
  120. package/lib/commonjs/plugins/insights/insights.spec.js.map +0 -1
  121. package/lib/commonjs/plugins/insights/sender.js +0 -128
  122. package/lib/commonjs/plugins/insights/sender.js.map +0 -1
  123. package/lib/commonjs/plugins/insights/sender.spec.js +0 -265
  124. package/lib/commonjs/plugins/insights/sender.spec.js.map +0 -1
  125. package/lib/commonjs/plugins/insights/state.js +0 -145
  126. package/lib/commonjs/plugins/insights/state.js.map +0 -1
  127. package/lib/commonjs/releaseCatalogCache.spec.js +0 -213
  128. package/lib/commonjs/releaseCatalogCache.spec.js.map +0 -1
  129. package/lib/commonjs/releaseCatalogCacheNativeContract.spec.js +0 -64
  130. package/lib/commonjs/releaseCatalogCacheNativeContract.spec.js.map +0 -1
  131. package/lib/commonjs/store.notification.spec.js +0 -84
  132. package/lib/commonjs/store.notification.spec.js.map +0 -1
  133. package/lib/commonjs/store.spec.js +0 -182
  134. package/lib/commonjs/store.spec.js.map +0 -1
  135. package/lib/commonjs/updateError.spec.js +0 -149
  136. package/lib/commonjs/updateError.spec.js.map +0 -1
  137. package/lib/commonjs/wrap.progress.spec.js +0 -195
  138. package/lib/commonjs/wrap.progress.spec.js.map +0 -1
  139. package/lib/commonjs/wrap.spec.js +0 -232
  140. package/lib/commonjs/wrap.spec.js.map +0 -1
  141. package/lib/module/appReady.spec.js +0 -359
  142. package/lib/module/appReady.spec.js.map +0 -1
  143. package/lib/module/appReady.test-utils.js +0 -19
  144. package/lib/module/appReady.test-utils.js.map +0 -1
  145. package/lib/module/baseURL.js +0 -11
  146. package/lib/module/baseURL.js.map +0 -1
  147. package/lib/module/catalogCacheNative.spec.js +0 -31
  148. package/lib/module/catalogCacheNative.spec.js.map +0 -1
  149. package/lib/module/checkForUpdate.releaseCatalog.spec.js +0 -935
  150. package/lib/module/checkForUpdate.releaseCatalog.spec.js.map +0 -1
  151. package/lib/module/entryImportGraph.spec.js +0 -50
  152. package/lib/module/entryImportGraph.spec.js.map +0 -1
  153. package/lib/module/httpClient.spec.js +0 -171
  154. package/lib/module/httpClient.spec.js.map +0 -1
  155. package/lib/module/index.spec.js +0 -394
  156. package/lib/module/index.spec.js.map +0 -1
  157. package/lib/module/iosInterop.spec.js +0 -18
  158. package/lib/module/iosInterop.spec.js.map +0 -1
  159. package/lib/module/native.spec.js +0 -909
  160. package/lib/module/native.spec.js.map +0 -1
  161. package/lib/module/pluginHost.spec.js +0 -291
  162. package/lib/module/pluginHost.spec.js.map +0 -1
  163. package/lib/module/plugins/insights/eventId.js +0 -122
  164. package/lib/module/plugins/insights/eventId.js.map +0 -1
  165. package/lib/module/plugins/insights/eventId.spec.js +0 -47
  166. package/lib/module/plugins/insights/eventId.spec.js.map +0 -1
  167. package/lib/module/plugins/insights/index.js +0 -268
  168. package/lib/module/plugins/insights/index.js.map +0 -1
  169. package/lib/module/plugins/insights/insights.spec.js +0 -639
  170. package/lib/module/plugins/insights/insights.spec.js.map +0 -1
  171. package/lib/module/plugins/insights/sender.js +0 -123
  172. package/lib/module/plugins/insights/sender.js.map +0 -1
  173. package/lib/module/plugins/insights/sender.spec.js +0 -265
  174. package/lib/module/plugins/insights/sender.spec.js.map +0 -1
  175. package/lib/module/plugins/insights/state.js +0 -139
  176. package/lib/module/plugins/insights/state.js.map +0 -1
  177. package/lib/module/releaseCatalogCache.spec.js +0 -213
  178. package/lib/module/releaseCatalogCache.spec.js.map +0 -1
  179. package/lib/module/releaseCatalogCacheNativeContract.spec.js +0 -64
  180. package/lib/module/releaseCatalogCacheNativeContract.spec.js.map +0 -1
  181. package/lib/module/store.notification.spec.js +0 -85
  182. package/lib/module/store.notification.spec.js.map +0 -1
  183. package/lib/module/store.spec.js +0 -181
  184. package/lib/module/store.spec.js.map +0 -1
  185. package/lib/module/updateError.spec.js +0 -149
  186. package/lib/module/updateError.spec.js.map +0 -1
  187. package/lib/module/wrap.progress.spec.js +0 -196
  188. package/lib/module/wrap.progress.spec.js.map +0 -1
  189. package/lib/module/wrap.spec.js +0 -233
  190. package/lib/module/wrap.spec.js.map +0 -1
  191. package/lib/typescript/commonjs/appReady.test-utils.d.ts +0 -9
  192. package/lib/typescript/commonjs/appReady.test-utils.d.ts.map +0 -1
  193. package/lib/typescript/commonjs/baseURL.d.ts +0 -4
  194. package/lib/typescript/commonjs/baseURL.d.ts.map +0 -1
  195. package/lib/typescript/commonjs/plugins/insights/eventId.d.ts +0 -18
  196. package/lib/typescript/commonjs/plugins/insights/eventId.d.ts.map +0 -1
  197. package/lib/typescript/commonjs/plugins/insights/index.d.ts +0 -40
  198. package/lib/typescript/commonjs/plugins/insights/index.d.ts.map +0 -1
  199. package/lib/typescript/commonjs/plugins/insights/sender.d.ts +0 -74
  200. package/lib/typescript/commonjs/plugins/insights/sender.d.ts.map +0 -1
  201. package/lib/typescript/commonjs/plugins/insights/state.d.ts +0 -46
  202. package/lib/typescript/commonjs/plugins/insights/state.d.ts.map +0 -1
  203. package/lib/typescript/module/appReady.test-utils.d.ts +0 -9
  204. package/lib/typescript/module/appReady.test-utils.d.ts.map +0 -1
  205. package/lib/typescript/module/baseURL.d.ts +0 -4
  206. package/lib/typescript/module/baseURL.d.ts.map +0 -1
  207. package/lib/typescript/module/plugins/insights/eventId.d.ts +0 -18
  208. package/lib/typescript/module/plugins/insights/eventId.d.ts.map +0 -1
  209. package/lib/typescript/module/plugins/insights/index.d.ts +0 -40
  210. package/lib/typescript/module/plugins/insights/index.d.ts.map +0 -1
  211. package/lib/typescript/module/plugins/insights/sender.d.ts +0 -74
  212. package/lib/typescript/module/plugins/insights/sender.d.ts.map +0 -1
  213. package/lib/typescript/module/plugins/insights/state.d.ts +0 -46
  214. package/lib/typescript/module/plugins/insights/state.d.ts.map +0 -1
  215. package/plugins/insights/package.json +0 -8
  216. package/src/appReady.spec.ts +0 -393
  217. package/src/appReady.test-utils.ts +0 -31
  218. package/src/baseURL.ts +0 -15
  219. package/src/catalogCacheNative.spec.ts +0 -63
  220. package/src/checkForUpdate.releaseCatalog.spec.ts +0 -966
  221. package/src/entryImportGraph.spec.ts +0 -65
  222. package/src/httpClient.spec.ts +0 -216
  223. package/src/index.spec.ts +0 -434
  224. package/src/iosInterop.spec.ts +0 -30
  225. package/src/native.spec.ts +0 -978
  226. package/src/pluginHost.spec.ts +0 -297
  227. package/src/plugins/insights/eventId.spec.ts +0 -60
  228. package/src/plugins/insights/eventId.ts +0 -171
  229. package/src/plugins/insights/index.ts +0 -367
  230. package/src/plugins/insights/insights.spec.ts +0 -732
  231. package/src/plugins/insights/sender.spec.ts +0 -287
  232. package/src/plugins/insights/sender.ts +0 -221
  233. package/src/plugins/insights/state.ts +0 -184
  234. package/src/releaseCatalogCache.spec.ts +0 -304
  235. package/src/releaseCatalogCacheNativeContract.spec.ts +0 -80
  236. package/src/store.notification.spec.ts +0 -102
  237. package/src/store.spec.ts +0 -215
  238. package/src/updateError.spec.ts +0 -144
  239. package/src/wrap.progress.spec.ts +0 -232
  240. package/src/wrap.spec.ts +0 -262
@@ -1,220 +0,0 @@
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
- }
@@ -1,264 +0,0 @@
1
- package com.hotupdater
2
-
3
- import org.junit.Assert.assertEquals
4
- import org.junit.Assert.assertNull
5
- import org.junit.Assert.assertTrue
6
- import org.junit.Rule
7
- import org.junit.Test
8
- import org.junit.rules.TemporaryFolder
9
- import java.io.File
10
-
11
- class HotUpdaterImplTest {
12
- @get:Rule
13
- val temporaryFolder = TemporaryFolder()
14
-
15
- @Test
16
- fun `getBundleId returns built in while native launch selection is missing`() {
17
- val impl =
18
- createImpl(
19
- storageBundleId = "staged-bundle",
20
- storageManifest = mapOf("bundleId" to "staged-bundle"),
21
- storageBaseURL = "file:///bundle-store/staged-bundle",
22
- )
23
-
24
- assertNull(impl.getBundleId())
25
- assertTrue(impl.getManifest().isEmpty())
26
- assertNull(impl.getBaseURL())
27
- }
28
-
29
- @Test
30
- fun `getBundleId returns current launched bundle over staged metadata`() {
31
- val impl = createImpl(storageBundleId = "staged-bundle")
32
-
33
- setCurrentLaunchSelection(
34
- impl,
35
- LaunchSelection(
36
- bundleUrl = "file:///bundle-store/launched-bundle/index.android.bundle",
37
- launchedBundleId = "launched-bundle",
38
- shouldRollbackOnCrash = false,
39
- ),
40
- )
41
-
42
- assertEquals("launched-bundle", impl.getBundleId())
43
- }
44
-
45
- @Test
46
- fun `getBundleId returns null for built in launch even when staged metadata exists`() {
47
- val impl = createImpl(storageBundleId = "staged-bundle")
48
-
49
- setCurrentLaunchSelection(
50
- impl,
51
- LaunchSelection(
52
- bundleUrl = "assets://index.android.bundle",
53
- launchedBundleId = null,
54
- shouldRollbackOnCrash = false,
55
- ),
56
- )
57
-
58
- assertNull(impl.getBundleId())
59
- }
60
-
61
- @Test
62
- fun `getManifest and getBaseURL return current launched bundle over staged metadata`() {
63
- val impl =
64
- createImpl(
65
- storageBundleId = "staged-bundle",
66
- storageManifest = mapOf("bundleId" to "staged-bundle"),
67
- storageBaseURL = "file:///bundle-store/staged-bundle",
68
- launchedBundleManifests =
69
- mapOf(
70
- "launched-bundle" to mapOf("bundleId" to "launched-bundle"),
71
- ),
72
- launchedBundleBaseURLs =
73
- mapOf(
74
- "launched-bundle" to "file:///bundle-store/launched-bundle",
75
- ),
76
- )
77
-
78
- setCurrentLaunchSelection(
79
- impl,
80
- LaunchSelection(
81
- bundleUrl = "file:///bundle-store/launched-bundle/index.android.bundle",
82
- launchedBundleId = "launched-bundle",
83
- shouldRollbackOnCrash = false,
84
- ),
85
- )
86
-
87
- assertEquals(
88
- mapOf("bundleId" to "launched-bundle"),
89
- impl.getManifest(),
90
- )
91
- assertEquals("file:///bundle-store/launched-bundle", impl.getBaseURL())
92
- }
93
-
94
- @Test
95
- fun `getManifest and getBaseURL return built in values for built in launch`() {
96
- val impl =
97
- createImpl(
98
- storageBundleId = "staged-bundle",
99
- storageManifest = mapOf("bundleId" to "staged-bundle"),
100
- storageBaseURL = "file:///bundle-store/staged-bundle",
101
- )
102
-
103
- setCurrentLaunchSelection(
104
- impl,
105
- LaunchSelection(
106
- bundleUrl = "assets://index.android.bundle",
107
- launchedBundleId = null,
108
- shouldRollbackOnCrash = false,
109
- ),
110
- )
111
-
112
- assertTrue(impl.getManifest().isEmpty())
113
- assertNull(impl.getBaseURL())
114
- }
115
-
116
- @Test
117
- fun `notifyAppReady reads the report without completing the current launch`() {
118
- val storage = FakeBundleStorageService(bundleId = null)
119
- val impl = allocateWithoutConstructor<HotUpdaterImpl>()
120
- setField(impl, "bundleStorage", storage)
121
- setCurrentLaunchSelection(
122
- impl,
123
- LaunchSelection(
124
- bundleUrl = "file:///bundle-store/launched-bundle/index.android.bundle",
125
- launchedBundleId = "launched-bundle",
126
- shouldRollbackOnCrash = false,
127
- ),
128
- )
129
-
130
- assertEquals(mapOf("status" to "UNCHANGED"), impl.notifyAppReady())
131
- assertNull(storage.lastCompletedBundleId)
132
- }
133
-
134
- @Test
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")
138
- val impl = allocateWithoutConstructor<HotUpdaterImpl>()
139
- setField(impl, "installIdentity", InstallIdentityService(identityFile))
140
- setField(impl, "keyValueStorage", KeyValueStorageService(storageFile))
141
-
142
- val installId = impl.getInstallId()
143
- assertEquals(installId, InstallationIdentity.loadFromFile(identityFile)?.installId)
144
-
145
- impl.setStorageItem("plugins/insights/queue", "[]")
146
- assertEquals("[]", impl.getStorageItem("plugins/insights/queue"))
147
- assertEquals("[]", KeyValueStorageService(storageFile).getItem("plugins/insights/queue"))
148
-
149
- impl.setStorageItem("plugins/insights/queue", null)
150
- assertNull(impl.getStorageItem("plugins/insights/queue"))
151
- }
152
-
153
- private fun createImpl(
154
- storageBundleId: String?,
155
- storageManifest: Map<String, Any?> = emptyMap(),
156
- storageBaseURL: String = "",
157
- launchedBundleManifests: Map<String, Map<String, Any?>> = emptyMap(),
158
- launchedBundleBaseURLs: Map<String, String> = emptyMap(),
159
- ): HotUpdaterImpl =
160
- allocateWithoutConstructor<HotUpdaterImpl>().also { impl ->
161
- setField(
162
- impl,
163
- "bundleStorage",
164
- FakeBundleStorageService(
165
- bundleId = storageBundleId,
166
- manifest = storageManifest,
167
- baseURL = storageBaseURL,
168
- launchedBundleManifests = launchedBundleManifests,
169
- launchedBundleBaseURLs = launchedBundleBaseURLs,
170
- ),
171
- )
172
- }
173
-
174
- private fun setCurrentLaunchSelection(
175
- impl: HotUpdaterImpl,
176
- selection: LaunchSelection,
177
- ) {
178
- setField(impl, "currentLaunchSelection", selection)
179
- }
180
-
181
- private fun setField(
182
- target: Any,
183
- fieldName: String,
184
- value: Any?,
185
- ) {
186
- val field = HotUpdaterImpl::class.java.getDeclaredField(fieldName)
187
- field.isAccessible = true
188
- field.set(target, value)
189
- }
190
-
191
- private inline fun <reified T> allocateWithoutConstructor(): T {
192
- val field = Class.forName("sun.misc.Unsafe").getDeclaredField("theUnsafe")
193
- field.isAccessible = true
194
- val unsafe = field.get(null)
195
- val allocateInstance = unsafe.javaClass.getMethod("allocateInstance", Class::class.java)
196
- @Suppress("UNCHECKED_CAST")
197
- return allocateInstance.invoke(unsafe, T::class.java) as T
198
- }
199
-
200
- private class FakeBundleStorageService(
201
- private val bundleId: String?,
202
- private val manifest: Map<String, Any?> = emptyMap(),
203
- private val baseURL: String = "",
204
- private val launchedBundleManifests: Map<String, Map<String, Any?>> =
205
- emptyMap(),
206
- private val launchedBundleBaseURLs: Map<String, String> = emptyMap(),
207
- ) : BundleStorageService {
208
- var lastCompletedBundleId: String? = null
209
-
210
- override fun setBundleURL(localPath: String?): Boolean = true
211
-
212
- override fun getCachedBundleURL(): String? = null
213
-
214
- override fun getFallbackBundleURL(): String = "assets://index.android.bundle"
215
-
216
- override fun prepareLaunch(pendingRecovery: PendingCrashRecovery?): LaunchSelection =
217
- LaunchSelection(
218
- bundleUrl = "assets://index.android.bundle",
219
- launchedBundleId = null,
220
- shouldRollbackOnCrash = false,
221
- )
222
-
223
- override suspend fun updateBundle(
224
- bundleId: String,
225
- manifestUrl: String,
226
- manifestFileHash: String,
227
- assets: Map<String, ChangedAssetDescriptor>,
228
- archiveUrl: String?,
229
- progressCallback: (UpdateProgressPayload) -> Unit,
230
- ) = UpdateBundleResult(BundleDelivery.MANIFEST, patchFallback = false)
231
-
232
- override fun markLaunchCompleted(currentBundleId: String?) {
233
- lastCompletedBundleId = currentBundleId
234
- }
235
-
236
- override fun notifyAppReady(): Map<String, Any?> = mapOf("status" to "UNCHANGED")
237
-
238
- override fun getCrashHistory(): CrashedHistory = CrashedHistory()
239
-
240
- override fun clearCrashHistory(): Boolean = true
241
-
242
- override fun getBaseURL(): String = baseURL
243
-
244
- override fun getBaseURLForBundle(bundleId: String?): String =
245
- if (bundleId == null) {
246
- ""
247
- } else {
248
- launchedBundleBaseURLs[bundleId] ?: ""
249
- }
250
-
251
- override fun getBundleId(): String? = bundleId
252
-
253
- override fun getManifest(): Map<String, Any?> = manifest
254
-
255
- override fun getManifestForBundle(bundleId: String?): Map<String, Any?> =
256
- if (bundleId == null) {
257
- emptyMap()
258
- } else {
259
- launchedBundleManifests[bundleId] ?: emptyMap()
260
- }
261
-
262
- override suspend fun resetChannel(): Boolean = true
263
- }
264
- }
@@ -1,97 +0,0 @@
1
- package com.hotupdater
2
-
3
- import org.junit.Assert.assertEquals
4
- import org.junit.Assert.assertFalse
5
- import org.junit.Assert.assertNotNull
6
- import org.junit.Assert.assertNull
7
- import org.junit.Assert.assertTrue
8
- import org.junit.Test
9
- import java.util.concurrent.CountDownLatch
10
- import java.util.concurrent.TimeUnit
11
- import java.util.concurrent.atomic.AtomicInteger
12
-
13
- class HotUpdaterRecoveryStateTest {
14
- @Test
15
- fun `content queued after a fatal error cannot verify the failed bundle`() {
16
- val state = HotUpdaterRecoveryState()
17
- state.start("crashed-bundle", true)
18
-
19
- val request = requireNotNull(state.requestRecovery())
20
- assertNull(state.completeLaunch())
21
- state.finishRecoveryRequest(request, true)
22
-
23
- assertNull(state.completeLaunch())
24
- assertEquals(HotUpdaterRecoveryState.Launch("crashed-bundle", true), state.snapshot())
25
- }
26
-
27
- @Test
28
- fun `a fatal error after first content preserves the verified bundle`() {
29
- val state = HotUpdaterRecoveryState()
30
- state.start("verified-bundle", true)
31
-
32
- assertNotNull(state.completeLaunch())
33
-
34
- assertNull(state.requestRecovery())
35
- assertNull(state.completeLaunch())
36
- assertEquals(HotUpdaterRecoveryState.Launch("verified-bundle", false), state.snapshot())
37
- }
38
-
39
- @Test
40
- fun `concurrent fatal errors schedule only one restart while the request is in flight`() {
41
- val state = HotUpdaterRecoveryState()
42
- state.start("crashed-bundle", true)
43
- val begin = CountDownLatch(1)
44
- val finished = CountDownLatch(2)
45
- val restartRequests = AtomicInteger()
46
- val handledErrors = AtomicInteger()
47
- val workers =
48
- List(2) {
49
- Thread {
50
- begin.await()
51
- val request = state.requestRecovery()
52
- if (request != null) {
53
- handledErrors.incrementAndGet()
54
- if (request.shouldRestart) restartRequests.incrementAndGet()
55
- }
56
- finished.countDown()
57
- }.apply { start() }
58
- }
59
- begin.countDown()
60
- assertTrue(finished.await(5, TimeUnit.SECONDS))
61
- workers.forEach { it.join() }
62
-
63
- assertEquals(2, handledErrors.get())
64
- assertEquals(1, restartRequests.get())
65
- assertNull(state.completeLaunch())
66
- }
67
-
68
- @Test
69
- fun `a failed restart keeps the crash unverified and permits another restart request`() {
70
- val state = HotUpdaterRecoveryState()
71
- state.start("crashed-bundle", true)
72
- val request = requireNotNull(state.requestRecovery())
73
-
74
- state.finishRecoveryRequest(request, false)
75
-
76
- assertNull(state.completeLaunch())
77
- val retry = requireNotNull(state.requestRecovery())
78
- assertTrue(retry.shouldRestart)
79
- state.finishRecoveryRequest(retry, true)
80
- assertFalse(requireNotNull(state.requestRecovery()).shouldRestart)
81
- }
82
-
83
- @Test
84
- fun `a previous launch restart completion cannot change the next launch`() {
85
- val state = HotUpdaterRecoveryState()
86
- state.start("crashed-bundle", true)
87
- val request = requireNotNull(state.requestRecovery())
88
- state.start("safe-bundle", false)
89
-
90
- state.finishRecoveryRequest(request, true)
91
-
92
- assertEquals("safe-bundle", state.completeLaunch()?.bundleId)
93
- state.stop()
94
- assertNull(state.snapshot())
95
- assertNull(state.requestRecovery())
96
- }
97
- }
@@ -1,71 +0,0 @@
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
- }