@hot-updater/react-native 1.0.0-rc.16 → 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.
- package/android/src/main/java/com/hotupdater/BundleFileStorageService.kt +491 -388
- package/android/src/main/java/com/hotupdater/BundleMetadata.kt +5 -11
- package/android/src/main/java/com/hotupdater/FileManagerService.kt +45 -0
- package/android/src/main/java/com/hotupdater/HotUpdaterException.kt +290 -12
- package/android/src/main/java/com/hotupdater/HotUpdaterImpl.kt +35 -19
- package/android/src/main/java/com/hotupdater/InstallIdentityService.kt +37 -0
- package/android/src/main/java/com/hotupdater/KeyValueStorageService.kt +86 -0
- package/android/src/main/java/com/hotupdater/NetworkState.kt +31 -0
- package/android/src/main/java/com/hotupdater/NoBackupStorage.kt +17 -0
- package/android/src/main/java/com/hotupdater/OkHttpDownloadService.kt +110 -52
- package/android/src/main/java/com/hotupdater/PreviousProcessExit.kt +89 -0
- package/android/src/main/java/com/hotupdater/ReactNativeValueConverters.kt +34 -0
- package/android/src/newarch/HotUpdaterModule.kt +74 -69
- package/android/src/oldarch/HotUpdaterModule.kt +63 -59
- package/android/src/oldarch/HotUpdaterSpec.kt +4 -6
- package/android/src/test/java/com/hotupdater/BsdiffPatchTest.kt +1 -1
- package/android/src/test/java/com/hotupdater/BundleFileStorageServiceTest.kt +446 -61
- package/android/src/test/java/com/hotupdater/HotUpdaterExceptionTest.kt +220 -0
- package/android/src/test/java/com/hotupdater/HotUpdaterImplTest.kt +19 -27
- package/android/src/test/java/com/hotupdater/InstallIdentityServiceTest.kt +71 -0
- package/android/src/test/java/com/hotupdater/KeyValueStorageServiceTest.kt +106 -0
- package/android/src/test/java/com/hotupdater/NoBackupContext.kt +11 -0
- package/android/src/test/java/com/hotupdater/OkHttpDownloadServiceTest.kt +309 -23
- package/android/src/test/java/com/hotupdater/PreviousProcessExitTest.kt +57 -0
- package/ios/HotUpdater/Internal/BundleFileStorageService.swift +238 -157
- package/ios/HotUpdater/Internal/BundleMetadata.swift +3 -53
- package/ios/HotUpdater/Internal/FileManagerService.swift +48 -0
- package/ios/HotUpdater/Internal/HotUpdater.mm +8 -18
- package/ios/HotUpdater/Internal/HotUpdaterImpl.swift +48 -29
- package/ios/HotUpdater/Internal/KeyValueStorageService.swift +71 -0
- package/ios/HotUpdater/Internal/URLSessionDownloadService.swift +108 -1
- package/ios/HotUpdater/Internal/UpdateFailure.swift +390 -0
- package/lib/commonjs/appReady.js +111 -0
- package/lib/commonjs/appReady.js.map +1 -0
- package/lib/commonjs/appReady.spec.js +360 -0
- package/lib/commonjs/appReady.spec.js.map +1 -0
- package/lib/commonjs/{notifyAppReadyInsights.test-utils.js → appReady.test-utils.js} +4 -3
- package/lib/commonjs/appReady.test-utils.js.map +1 -0
- package/lib/commonjs/baseURL.js +16 -0
- package/lib/commonjs/baseURL.js.map +1 -0
- package/lib/commonjs/checkForUpdate.js +104 -56
- package/lib/commonjs/checkForUpdate.js.map +1 -1
- package/lib/commonjs/checkForUpdate.releaseCatalog.spec.js +337 -74
- package/lib/commonjs/checkForUpdate.releaseCatalog.spec.js.map +1 -1
- package/lib/commonjs/clientPlugin.js +49 -0
- package/lib/commonjs/clientPlugin.js.map +1 -0
- package/lib/commonjs/entryImportGraph.spec.js +50 -0
- package/lib/commonjs/entryImportGraph.spec.js.map +1 -0
- package/lib/commonjs/fetchJSON.js +2 -6
- package/lib/commonjs/fetchJSON.js.map +1 -1
- package/lib/commonjs/httpClient.js +18 -165
- package/lib/commonjs/httpClient.js.map +1 -1
- package/lib/commonjs/httpClient.spec.js +26 -199
- package/lib/commonjs/httpClient.spec.js.map +1 -1
- package/lib/commonjs/index.js +49 -36
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/index.spec.js +108 -123
- package/lib/commonjs/index.spec.js.map +1 -1
- package/lib/commonjs/native.js +67 -48
- package/lib/commonjs/native.js.map +1 -1
- package/lib/commonjs/native.spec.js +80 -58
- package/lib/commonjs/native.spec.js.map +1 -1
- package/lib/commonjs/pluginHost.js +208 -0
- package/lib/commonjs/pluginHost.js.map +1 -0
- package/lib/commonjs/pluginHost.spec.js +292 -0
- package/lib/commonjs/pluginHost.spec.js.map +1 -0
- package/lib/commonjs/plugins/insights/eventId.js +129 -0
- package/lib/commonjs/plugins/insights/eventId.js.map +1 -0
- package/lib/commonjs/plugins/insights/eventId.spec.js +47 -0
- package/lib/commonjs/plugins/insights/eventId.spec.js.map +1 -0
- package/lib/commonjs/plugins/insights/index.js +272 -0
- package/lib/commonjs/plugins/insights/index.js.map +1 -0
- package/lib/commonjs/plugins/insights/insights.spec.js +640 -0
- package/lib/commonjs/plugins/insights/insights.spec.js.map +1 -0
- package/lib/commonjs/plugins/insights/sender.js +128 -0
- package/lib/commonjs/plugins/insights/sender.js.map +1 -0
- package/lib/commonjs/plugins/insights/sender.spec.js +265 -0
- package/lib/commonjs/plugins/insights/sender.spec.js.map +1 -0
- package/lib/commonjs/plugins/insights/state.js +145 -0
- package/lib/commonjs/plugins/insights/state.js.map +1 -0
- package/lib/commonjs/releaseCatalogCache.js +13 -2
- package/lib/commonjs/releaseCatalogCache.js.map +1 -1
- package/lib/commonjs/releaseCatalogCache.spec.js +18 -0
- package/lib/commonjs/releaseCatalogCache.spec.js.map +1 -1
- package/lib/commonjs/sdkVersion.js +1 -1
- package/lib/commonjs/specs/NativeHotUpdater.js.map +1 -1
- package/lib/commonjs/updateError.js +132 -0
- package/lib/commonjs/updateError.js.map +1 -0
- package/lib/commonjs/updateError.spec.js +149 -0
- package/lib/commonjs/updateError.spec.js.map +1 -0
- package/lib/commonjs/wrap.js +43 -25
- package/lib/commonjs/wrap.js.map +1 -1
- package/lib/commonjs/wrap.progress.spec.js +195 -0
- package/lib/commonjs/wrap.progress.spec.js.map +1 -0
- package/lib/commonjs/wrap.spec.js +94 -230
- package/lib/commonjs/wrap.spec.js.map +1 -1
- package/lib/module/appReady.js +103 -0
- package/lib/module/appReady.js.map +1 -0
- package/lib/module/appReady.spec.js +359 -0
- package/lib/module/appReady.spec.js.map +1 -0
- package/lib/module/{notifyAppReadyInsights.test-utils.js → appReady.test-utils.js} +4 -3
- package/lib/module/appReady.test-utils.js.map +1 -0
- package/lib/module/baseURL.js +11 -0
- package/lib/module/baseURL.js.map +1 -0
- package/lib/module/checkForUpdate.js +103 -57
- package/lib/module/checkForUpdate.js.map +1 -1
- package/lib/module/checkForUpdate.releaseCatalog.spec.js +337 -74
- package/lib/module/checkForUpdate.releaseCatalog.spec.js.map +1 -1
- package/lib/module/clientPlugin.js +44 -0
- package/lib/module/clientPlugin.js.map +1 -0
- package/lib/module/entryImportGraph.spec.js +50 -0
- package/lib/module/entryImportGraph.spec.js.map +1 -0
- package/lib/module/fetchJSON.js +2 -6
- package/lib/module/fetchJSON.js.map +1 -1
- package/lib/module/httpClient.js +18 -165
- package/lib/module/httpClient.js.map +1 -1
- package/lib/module/httpClient.spec.js +27 -200
- package/lib/module/httpClient.spec.js.map +1 -1
- package/lib/module/index.js +44 -38
- package/lib/module/index.js.map +1 -1
- package/lib/module/index.spec.js +108 -123
- package/lib/module/index.spec.js.map +1 -1
- package/lib/module/native.js +63 -44
- package/lib/module/native.js.map +1 -1
- package/lib/module/native.spec.js +80 -58
- package/lib/module/native.spec.js.map +1 -1
- package/lib/module/pluginHost.js +200 -0
- package/lib/module/pluginHost.js.map +1 -0
- package/lib/module/pluginHost.spec.js +291 -0
- package/lib/module/pluginHost.spec.js.map +1 -0
- package/lib/module/plugins/insights/eventId.js +122 -0
- package/lib/module/plugins/insights/eventId.js.map +1 -0
- package/lib/module/plugins/insights/eventId.spec.js +47 -0
- package/lib/module/plugins/insights/eventId.spec.js.map +1 -0
- package/lib/module/plugins/insights/index.js +268 -0
- package/lib/module/plugins/insights/index.js.map +1 -0
- package/lib/module/plugins/insights/insights.spec.js +639 -0
- package/lib/module/plugins/insights/insights.spec.js.map +1 -0
- package/lib/module/plugins/insights/sender.js +123 -0
- package/lib/module/plugins/insights/sender.js.map +1 -0
- package/lib/module/plugins/insights/sender.spec.js +265 -0
- package/lib/module/plugins/insights/sender.spec.js.map +1 -0
- package/lib/module/plugins/insights/state.js +139 -0
- package/lib/module/plugins/insights/state.js.map +1 -0
- package/lib/module/releaseCatalogCache.js +13 -2
- package/lib/module/releaseCatalogCache.js.map +1 -1
- package/lib/module/releaseCatalogCache.spec.js +18 -0
- package/lib/module/releaseCatalogCache.spec.js.map +1 -1
- package/lib/module/sdkVersion.js +1 -1
- package/lib/module/specs/NativeHotUpdater.js.map +1 -1
- package/lib/module/updateError.js +126 -0
- package/lib/module/updateError.js.map +1 -0
- package/lib/module/updateError.spec.js +149 -0
- package/lib/module/updateError.spec.js.map +1 -0
- package/lib/module/wrap.js +43 -25
- package/lib/module/wrap.js.map +1 -1
- package/lib/module/wrap.progress.spec.js +196 -0
- package/lib/module/wrap.progress.spec.js.map +1 -0
- package/lib/module/wrap.spec.js +93 -229
- package/lib/module/wrap.spec.js.map +1 -1
- package/lib/typescript/commonjs/appReady.d.ts +23 -0
- package/lib/typescript/commonjs/appReady.d.ts.map +1 -0
- package/lib/typescript/commonjs/appReady.test-utils.d.ts +9 -0
- package/lib/typescript/commonjs/appReady.test-utils.d.ts.map +1 -0
- package/lib/typescript/commonjs/baseURL.d.ts +4 -0
- package/lib/typescript/commonjs/baseURL.d.ts.map +1 -0
- package/lib/typescript/commonjs/checkForUpdate.d.ts +15 -2
- package/lib/typescript/commonjs/checkForUpdate.d.ts.map +1 -1
- package/lib/typescript/commonjs/clientPlugin.d.ts +185 -0
- package/lib/typescript/commonjs/clientPlugin.d.ts.map +1 -0
- package/lib/typescript/commonjs/fetchJSON.d.ts +2 -3
- package/lib/typescript/commonjs/fetchJSON.d.ts.map +1 -1
- package/lib/typescript/commonjs/httpClient.d.ts +2 -36
- package/lib/typescript/commonjs/httpClient.d.ts.map +1 -1
- package/lib/typescript/commonjs/index.d.ts +3 -6
- package/lib/typescript/commonjs/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/native.d.ts +29 -15
- package/lib/typescript/commonjs/native.d.ts.map +1 -1
- package/lib/typescript/commonjs/pluginHost.d.ts +29 -0
- package/lib/typescript/commonjs/pluginHost.d.ts.map +1 -0
- package/lib/typescript/commonjs/plugins/insights/eventId.d.ts +18 -0
- package/lib/typescript/commonjs/plugins/insights/eventId.d.ts.map +1 -0
- package/lib/typescript/commonjs/plugins/insights/index.d.ts +40 -0
- package/lib/typescript/commonjs/plugins/insights/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/plugins/insights/sender.d.ts +74 -0
- package/lib/typescript/commonjs/plugins/insights/sender.d.ts.map +1 -0
- package/lib/typescript/commonjs/plugins/insights/state.d.ts +46 -0
- package/lib/typescript/commonjs/plugins/insights/state.d.ts.map +1 -0
- package/lib/typescript/commonjs/releaseCatalogCache.d.ts +5 -1
- package/lib/typescript/commonjs/releaseCatalogCache.d.ts.map +1 -1
- package/lib/typescript/commonjs/sdkVersion.d.ts +1 -1
- package/lib/typescript/commonjs/specs/NativeHotUpdater.d.ts +34 -12
- package/lib/typescript/commonjs/specs/NativeHotUpdater.d.ts.map +1 -1
- package/lib/typescript/commonjs/updateError.d.ts +26 -0
- package/lib/typescript/commonjs/updateError.d.ts.map +1 -0
- package/lib/typescript/commonjs/wrap.d.ts.map +1 -1
- package/lib/typescript/commonjs/wrap.types.d.ts +6 -10
- package/lib/typescript/commonjs/wrap.types.d.ts.map +1 -1
- package/lib/typescript/module/appReady.d.ts +23 -0
- package/lib/typescript/module/appReady.d.ts.map +1 -0
- package/lib/typescript/module/appReady.test-utils.d.ts +9 -0
- package/lib/typescript/module/appReady.test-utils.d.ts.map +1 -0
- package/lib/typescript/module/baseURL.d.ts +4 -0
- package/lib/typescript/module/baseURL.d.ts.map +1 -0
- package/lib/typescript/module/checkForUpdate.d.ts +15 -2
- package/lib/typescript/module/checkForUpdate.d.ts.map +1 -1
- package/lib/typescript/module/clientPlugin.d.ts +185 -0
- package/lib/typescript/module/clientPlugin.d.ts.map +1 -0
- package/lib/typescript/module/fetchJSON.d.ts +2 -3
- package/lib/typescript/module/fetchJSON.d.ts.map +1 -1
- package/lib/typescript/module/httpClient.d.ts +2 -36
- package/lib/typescript/module/httpClient.d.ts.map +1 -1
- package/lib/typescript/module/index.d.ts +3 -6
- package/lib/typescript/module/index.d.ts.map +1 -1
- package/lib/typescript/module/native.d.ts +29 -15
- package/lib/typescript/module/native.d.ts.map +1 -1
- package/lib/typescript/module/pluginHost.d.ts +29 -0
- package/lib/typescript/module/pluginHost.d.ts.map +1 -0
- package/lib/typescript/module/plugins/insights/eventId.d.ts +18 -0
- package/lib/typescript/module/plugins/insights/eventId.d.ts.map +1 -0
- package/lib/typescript/module/plugins/insights/index.d.ts +40 -0
- package/lib/typescript/module/plugins/insights/index.d.ts.map +1 -0
- package/lib/typescript/module/plugins/insights/sender.d.ts +74 -0
- package/lib/typescript/module/plugins/insights/sender.d.ts.map +1 -0
- package/lib/typescript/module/plugins/insights/state.d.ts +46 -0
- package/lib/typescript/module/plugins/insights/state.d.ts.map +1 -0
- package/lib/typescript/module/releaseCatalogCache.d.ts +5 -1
- package/lib/typescript/module/releaseCatalogCache.d.ts.map +1 -1
- package/lib/typescript/module/sdkVersion.d.ts +1 -1
- package/lib/typescript/module/specs/NativeHotUpdater.d.ts +34 -12
- package/lib/typescript/module/specs/NativeHotUpdater.d.ts.map +1 -1
- package/lib/typescript/module/updateError.d.ts +26 -0
- package/lib/typescript/module/updateError.d.ts.map +1 -0
- package/lib/typescript/module/wrap.d.ts.map +1 -1
- package/lib/typescript/module/wrap.types.d.ts +6 -10
- package/lib/typescript/module/wrap.types.d.ts.map +1 -1
- package/package.json +14 -2
- package/plugins/insights/package.json +8 -0
- package/src/appReady.spec.ts +393 -0
- package/src/{notifyAppReadyInsights.test-utils.ts → appReady.test-utils.ts} +7 -7
- package/src/appReady.ts +149 -0
- package/src/baseURL.ts +15 -0
- package/src/checkForUpdate.releaseCatalog.spec.ts +370 -76
- package/src/checkForUpdate.ts +135 -80
- package/src/clientPlugin.ts +231 -0
- package/src/entryImportGraph.spec.ts +65 -0
- package/src/fetchJSON.ts +3 -10
- package/src/httpClient.spec.ts +30 -227
- package/src/httpClient.ts +20 -256
- package/src/index.spec.ts +117 -93
- package/src/index.ts +67 -52
- package/src/native.spec.ts +98 -72
- package/src/native.ts +93 -77
- package/src/pluginHost.spec.ts +297 -0
- package/src/pluginHost.ts +262 -0
- package/src/plugins/insights/eventId.spec.ts +60 -0
- package/src/plugins/insights/eventId.ts +171 -0
- package/src/plugins/insights/index.ts +367 -0
- package/src/plugins/insights/insights.spec.ts +732 -0
- package/src/plugins/insights/sender.spec.ts +287 -0
- package/src/plugins/insights/sender.ts +221 -0
- package/src/plugins/insights/state.ts +184 -0
- package/src/releaseCatalogCache.spec.ts +22 -0
- package/src/releaseCatalogCache.ts +19 -4
- package/src/sdkVersion.ts +1 -1
- package/src/specs/NativeHotUpdater.ts +34 -13
- package/src/updateError.spec.ts +144 -0
- package/src/updateError.ts +153 -0
- package/src/wrap.progress.spec.ts +232 -0
- package/src/wrap.spec.ts +100 -283
- package/src/wrap.tsx +57 -26
- package/src/wrap.types.ts +6 -12
- package/lib/commonjs/notifyAppReadyInsights.boundary.spec.js +0 -225
- package/lib/commonjs/notifyAppReadyInsights.boundary.spec.js.map +0 -1
- package/lib/commonjs/notifyAppReadyInsights.js +0 -188
- package/lib/commonjs/notifyAppReadyInsights.js.map +0 -1
- package/lib/commonjs/notifyAppReadyInsights.spec.js +0 -378
- package/lib/commonjs/notifyAppReadyInsights.spec.js.map +0 -1
- package/lib/commonjs/notifyAppReadyInsights.test-utils.js.map +0 -1
- package/lib/commonjs/notifyAppReadyInsights.transport.spec.js +0 -171
- package/lib/commonjs/notifyAppReadyInsights.transport.spec.js.map +0 -1
- package/lib/commonjs/uuidv7.js +0 -41
- package/lib/commonjs/uuidv7.js.map +0 -1
- package/lib/commonjs/uuidv7.spec.js +0 -30
- package/lib/commonjs/uuidv7.spec.js.map +0 -1
- package/lib/module/notifyAppReadyInsights.boundary.spec.js +0 -224
- package/lib/module/notifyAppReadyInsights.boundary.spec.js.map +0 -1
- package/lib/module/notifyAppReadyInsights.js +0 -181
- package/lib/module/notifyAppReadyInsights.js.map +0 -1
- package/lib/module/notifyAppReadyInsights.spec.js +0 -377
- package/lib/module/notifyAppReadyInsights.spec.js.map +0 -1
- package/lib/module/notifyAppReadyInsights.test-utils.js.map +0 -1
- package/lib/module/notifyAppReadyInsights.transport.spec.js +0 -170
- package/lib/module/notifyAppReadyInsights.transport.spec.js.map +0 -1
- package/lib/module/uuidv7.js +0 -36
- package/lib/module/uuidv7.js.map +0 -1
- package/lib/module/uuidv7.spec.js +0 -30
- package/lib/module/uuidv7.spec.js.map +0 -1
- package/lib/typescript/commonjs/notifyAppReadyInsights.d.ts +0 -25
- package/lib/typescript/commonjs/notifyAppReadyInsights.d.ts.map +0 -1
- package/lib/typescript/commonjs/notifyAppReadyInsights.test-utils.d.ts +0 -8
- package/lib/typescript/commonjs/notifyAppReadyInsights.test-utils.d.ts.map +0 -1
- package/lib/typescript/commonjs/uuidv7.d.ts +0 -10
- package/lib/typescript/commonjs/uuidv7.d.ts.map +0 -1
- package/lib/typescript/module/notifyAppReadyInsights.d.ts +0 -25
- package/lib/typescript/module/notifyAppReadyInsights.d.ts.map +0 -1
- package/lib/typescript/module/notifyAppReadyInsights.test-utils.d.ts +0 -8
- package/lib/typescript/module/notifyAppReadyInsights.test-utils.d.ts.map +0 -1
- package/lib/typescript/module/uuidv7.d.ts +0 -10
- package/lib/typescript/module/uuidv7.d.ts.map +0 -1
- package/src/notifyAppReadyInsights.boundary.spec.ts +0 -242
- package/src/notifyAppReadyInsights.spec.ts +0 -362
- package/src/notifyAppReadyInsights.transport.spec.ts +0 -196
- package/src/notifyAppReadyInsights.ts +0 -293
- package/src/uuidv7.spec.ts +0 -25
- package/src/uuidv7.ts +0 -38
|
@@ -14,11 +14,16 @@ import org.junit.Rule
|
|
|
14
14
|
import org.junit.Test
|
|
15
15
|
import org.junit.rules.TemporaryFolder
|
|
16
16
|
import java.io.File
|
|
17
|
+
import java.io.IOException
|
|
18
|
+
import java.net.ConnectException
|
|
19
|
+
import java.net.SocketTimeoutException
|
|
17
20
|
import java.net.URL
|
|
21
|
+
import java.net.UnknownHostException
|
|
18
22
|
import java.util.Base64
|
|
19
23
|
import java.util.concurrent.CopyOnWriteArrayList
|
|
20
24
|
import java.util.concurrent.Executors
|
|
21
25
|
import java.util.concurrent.atomic.AtomicInteger
|
|
26
|
+
import javax.net.ssl.SSLHandshakeException
|
|
22
27
|
|
|
23
28
|
class BundleFileStorageServiceTest {
|
|
24
29
|
@get:Rule
|
|
@@ -352,59 +357,6 @@ class BundleFileStorageServiceTest {
|
|
|
352
357
|
)
|
|
353
358
|
}
|
|
354
359
|
|
|
355
|
-
@Test
|
|
356
|
-
fun `install identity persists across service instances and user clear keeps install id`() {
|
|
357
|
-
val rootDir = temporaryFolder.newFolder("install-identity")
|
|
358
|
-
val firstService = createService(rootDir)
|
|
359
|
-
|
|
360
|
-
val firstInstallId = firstService.getInstallId()
|
|
361
|
-
assertTrue(firstInstallId.isNotBlank())
|
|
362
|
-
|
|
363
|
-
firstService.setUser("user-123", "alice")
|
|
364
|
-
assertEquals(
|
|
365
|
-
InstallationIdentity(
|
|
366
|
-
installId = firstInstallId,
|
|
367
|
-
userId = "user-123",
|
|
368
|
-
username = "alice",
|
|
369
|
-
),
|
|
370
|
-
loadInstallationIdentity(rootDir),
|
|
371
|
-
)
|
|
372
|
-
|
|
373
|
-
val restartedService = createService(rootDir)
|
|
374
|
-
assertEquals(firstInstallId, restartedService.getInstallId())
|
|
375
|
-
|
|
376
|
-
restartedService.setUser(null, null)
|
|
377
|
-
assertEquals(
|
|
378
|
-
InstallationIdentity(
|
|
379
|
-
installId = firstInstallId,
|
|
380
|
-
userId = null,
|
|
381
|
-
username = null,
|
|
382
|
-
),
|
|
383
|
-
loadInstallationIdentity(rootDir),
|
|
384
|
-
)
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
@Test
|
|
388
|
-
fun `user update keeps cached install id when persisted identity becomes unreadable`() {
|
|
389
|
-
val rootDir = temporaryFolder.newFolder("install-identity-corruption")
|
|
390
|
-
val service = createService(rootDir)
|
|
391
|
-
val installId = service.getInstallId()
|
|
392
|
-
val identityFile = File(bundleStoreDir(rootDir), InstallationIdentity.IDENTITY_FILENAME)
|
|
393
|
-
identityFile.writeText("{")
|
|
394
|
-
|
|
395
|
-
service.setUser("user-123", "alice")
|
|
396
|
-
|
|
397
|
-
assertEquals(installId, service.getInstallId())
|
|
398
|
-
assertEquals(
|
|
399
|
-
InstallationIdentity(
|
|
400
|
-
installId = installId,
|
|
401
|
-
userId = "user-123",
|
|
402
|
-
username = "alice",
|
|
403
|
-
),
|
|
404
|
-
loadInstallationIdentity(rootDir),
|
|
405
|
-
)
|
|
406
|
-
}
|
|
407
|
-
|
|
408
360
|
@Test
|
|
409
361
|
fun `manifest driven install reuses matching built in asset before first OTA`() =
|
|
410
362
|
runBlocking {
|
|
@@ -841,7 +793,11 @@ class BundleFileStorageServiceTest {
|
|
|
841
793
|
) {}
|
|
842
794
|
}
|
|
843
795
|
|
|
844
|
-
|
|
796
|
+
assertUpdateFailure(
|
|
797
|
+
result.exceptionOrNull(),
|
|
798
|
+
"MOVE_OPERATION_FAILED",
|
|
799
|
+
UpdateFailure.install(UpdateFailureReason.STORAGE),
|
|
800
|
+
)
|
|
845
801
|
assertEquals("old", oldBundleFile.readText())
|
|
846
802
|
assertFalse(File(bundleStoreDir(root), "target.install-backup").exists())
|
|
847
803
|
assertEquals(oldBundleFile.absolutePath, preferences.getItem("HotUpdaterBundleURL"))
|
|
@@ -891,7 +847,11 @@ class BundleFileStorageServiceTest {
|
|
|
891
847
|
) {}
|
|
892
848
|
}
|
|
893
849
|
|
|
894
|
-
|
|
850
|
+
assertUpdateFailure(
|
|
851
|
+
result.exceptionOrNull(),
|
|
852
|
+
"UNKNOWN_ERROR",
|
|
853
|
+
UpdateFailure.install(UpdateFailureReason.STORAGE),
|
|
854
|
+
)
|
|
895
855
|
assertEquals("old", oldBundleFile.readText())
|
|
896
856
|
assertFalse(File(bundleStoreDir(root), "target.install-backup").exists())
|
|
897
857
|
assertEquals(oldBundleFile.absolutePath, preferences.getItem("HotUpdaterBundleURL"))
|
|
@@ -951,7 +911,11 @@ class BundleFileStorageServiceTest {
|
|
|
951
911
|
) {}
|
|
952
912
|
}
|
|
953
913
|
|
|
954
|
-
|
|
914
|
+
assertUpdateFailure(
|
|
915
|
+
result.exceptionOrNull(),
|
|
916
|
+
"UNKNOWN_ERROR",
|
|
917
|
+
UpdateFailure.install(UpdateFailureReason.STORAGE),
|
|
918
|
+
)
|
|
955
919
|
assertEquals(0, metadataWrites)
|
|
956
920
|
assertEquals("old", oldBundleFile.readText())
|
|
957
921
|
assertEquals(oldBundleFile.absolutePath, preferences.getItem("HotUpdaterBundleURL"))
|
|
@@ -1493,6 +1457,423 @@ class BundleFileStorageServiceTest {
|
|
|
1493
1457
|
}
|
|
1494
1458
|
}
|
|
1495
1459
|
|
|
1460
|
+
@Test
|
|
1461
|
+
fun `download failures of the manifest or a file report network http or storage`() =
|
|
1462
|
+
runBlocking {
|
|
1463
|
+
val network = UpdateFailure.download(UpdateFailureReason.NETWORK)
|
|
1464
|
+
val cases =
|
|
1465
|
+
listOf(
|
|
1466
|
+
Triple(HttpStatusException(404, "Not Found"), "DOWNLOAD_FAILED", UpdateFailure.http(404)),
|
|
1467
|
+
Triple(
|
|
1468
|
+
HttpStatusException(403, "Forbidden", originCode = "AccessDenied"),
|
|
1469
|
+
"DOWNLOAD_FAILED",
|
|
1470
|
+
UpdateFailure.http(403, originCode = "AccessDenied"),
|
|
1471
|
+
),
|
|
1472
|
+
Triple(
|
|
1473
|
+
UnknownHostException("example.com"),
|
|
1474
|
+
"DOWNLOAD_FAILED",
|
|
1475
|
+
network.copy(transport = UpdateFailureTransport.DNS),
|
|
1476
|
+
),
|
|
1477
|
+
Triple(
|
|
1478
|
+
SocketTimeoutException("timeout"),
|
|
1479
|
+
"DOWNLOAD_FAILED",
|
|
1480
|
+
network.copy(transport = UpdateFailureTransport.TIMEOUT),
|
|
1481
|
+
),
|
|
1482
|
+
Triple(
|
|
1483
|
+
ConnectException("Connection refused"),
|
|
1484
|
+
"DOWNLOAD_FAILED",
|
|
1485
|
+
network.copy(transport = UpdateFailureTransport.CONNECTION),
|
|
1486
|
+
),
|
|
1487
|
+
Triple(
|
|
1488
|
+
SSLHandshakeException("Chain validation failed"),
|
|
1489
|
+
"DOWNLOAD_FAILED",
|
|
1490
|
+
network.copy(transport = UpdateFailureTransport.TLS),
|
|
1491
|
+
),
|
|
1492
|
+
Triple(IncompleteDownloadException(expectedSize = 10, actualSize = 4), "INCOMPLETE_DOWNLOAD", network),
|
|
1493
|
+
Triple(
|
|
1494
|
+
LocalStorageException(IOException("No space left on device")),
|
|
1495
|
+
"DOWNLOAD_FAILED",
|
|
1496
|
+
UpdateFailure.install(UpdateFailureReason.STORAGE),
|
|
1497
|
+
),
|
|
1498
|
+
)
|
|
1499
|
+
val resources = mapOf(MANIFEST_URL to UpdateFailureResource.MANIFEST, BUNDLE_URL to UpdateFailureResource.FILE)
|
|
1500
|
+
|
|
1501
|
+
cases.forEachIndexed { index, (error, code, failure) ->
|
|
1502
|
+
resources.forEach { (failingUrl, resource) ->
|
|
1503
|
+
val root = temporaryFolder.newFolder("download-failure-$index-${resource.value}")
|
|
1504
|
+
val result = runOneFileUpdate(root, failures = mapOf(failingUrl to error))
|
|
1505
|
+
|
|
1506
|
+
assertUpdateFailure(result, code, failure.copy(resource = resource))
|
|
1507
|
+
assertNull(loadMetadata(root)?.stagingBundleId)
|
|
1508
|
+
}
|
|
1509
|
+
}
|
|
1510
|
+
}
|
|
1511
|
+
|
|
1512
|
+
@Test
|
|
1513
|
+
fun `verification failures report hash mismatch signature or invalid response`() =
|
|
1514
|
+
runBlocking {
|
|
1515
|
+
val hashMismatch = UpdateFailure.download(UpdateFailureReason.HASH_MISMATCH)
|
|
1516
|
+
val hashMismatchRoot = temporaryFolder.newFolder("manifest-hash-mismatch")
|
|
1517
|
+
assertUpdateFailure(
|
|
1518
|
+
runOneFileUpdate(hashMismatchRoot, manifestFileHash = sha256(hashMismatchRoot, "another manifest")),
|
|
1519
|
+
"SIGNATURE_VERIFICATION_FAILED",
|
|
1520
|
+
hashMismatch.copy(resource = UpdateFailureResource.MANIFEST),
|
|
1521
|
+
)
|
|
1522
|
+
|
|
1523
|
+
assertUpdateFailure(
|
|
1524
|
+
runOneFileUpdate(temporaryFolder.newFolder("file-hash-mismatch"), servedFile = "tampered".toByteArray()),
|
|
1525
|
+
"SIGNATURE_VERIFICATION_FAILED",
|
|
1526
|
+
hashMismatch.copy(resource = UpdateFailureResource.FILE),
|
|
1527
|
+
)
|
|
1528
|
+
|
|
1529
|
+
HotUpdaterConfig.publicKey = "configured-public-key"
|
|
1530
|
+
try {
|
|
1531
|
+
assertUpdateFailure(
|
|
1532
|
+
runOneFileUpdate(temporaryFolder.newFolder("unsigned-with-public-key")),
|
|
1533
|
+
"SIGNATURE_VERIFICATION_FAILED",
|
|
1534
|
+
UpdateFailure.download(UpdateFailureReason.SIGNATURE).copy(resource = UpdateFailureResource.MANIFEST),
|
|
1535
|
+
)
|
|
1536
|
+
} finally {
|
|
1537
|
+
HotUpdaterConfig.publicKey = null
|
|
1538
|
+
}
|
|
1539
|
+
|
|
1540
|
+
val invalidManifest =
|
|
1541
|
+
UpdateFailure.download(UpdateFailureReason.INVALID_RESPONSE).copy(resource = UpdateFailureResource.MANIFEST)
|
|
1542
|
+
val otherBundleRoot = temporaryFolder.newFolder("manifest-for-other-bundle")
|
|
1543
|
+
assertUpdateFailure(
|
|
1544
|
+
runOneFileUpdate(
|
|
1545
|
+
otherBundleRoot,
|
|
1546
|
+
manifest = manifestJson("other-bundle", mapOf("index.android.bundle" to sha256(otherBundleRoot, BUNDLE_CONTENT))),
|
|
1547
|
+
),
|
|
1548
|
+
"INVALID_BUNDLE",
|
|
1549
|
+
invalidManifest,
|
|
1550
|
+
)
|
|
1551
|
+
assertUpdateFailure(
|
|
1552
|
+
runOneFileUpdate(temporaryFolder.newFolder("unparsable-manifest"), manifest = "not json"),
|
|
1553
|
+
"INVALID_BUNDLE",
|
|
1554
|
+
invalidManifest,
|
|
1555
|
+
)
|
|
1556
|
+
assertUpdateFailure(
|
|
1557
|
+
runOneFileUpdate(temporaryFolder.newFolder("missing-platform-bundle"), assetPath = "assets/image.png"),
|
|
1558
|
+
"INVALID_BUNDLE",
|
|
1559
|
+
invalidManifest,
|
|
1560
|
+
)
|
|
1561
|
+
val malformedUrlRoot = temporaryFolder.newFolder("malformed-file-url")
|
|
1562
|
+
assertUpdateFailure(
|
|
1563
|
+
runOneFileUpdate(
|
|
1564
|
+
malformedUrlRoot,
|
|
1565
|
+
descriptor = ChangedAssetDescriptor("not a url", sha256(malformedUrlRoot, BUNDLE_CONTENT)),
|
|
1566
|
+
),
|
|
1567
|
+
"UNKNOWN_ERROR",
|
|
1568
|
+
invalidManifest.copy(resource = UpdateFailureResource.FILE),
|
|
1569
|
+
)
|
|
1570
|
+
}
|
|
1571
|
+
|
|
1572
|
+
@Test
|
|
1573
|
+
fun `install failures report extract or storage`() =
|
|
1574
|
+
runBlocking {
|
|
1575
|
+
val brotliRoot = temporaryFolder.newFolder("corrupt-brotli-file")
|
|
1576
|
+
val truncatedBrotli = Base64.getDecoder().decode(TarBrArchiveExtractorTest.VALID).let { it.copyOf(it.size / 2) }
|
|
1577
|
+
assertUpdateFailure(
|
|
1578
|
+
runOneFileUpdate(
|
|
1579
|
+
brotliRoot,
|
|
1580
|
+
descriptor = ChangedAssetDescriptor(BUNDLE_URL, sha256(brotliRoot, BUNDLE_CONTENT), fileCompression = "br"),
|
|
1581
|
+
servedFile = truncatedBrotli,
|
|
1582
|
+
),
|
|
1583
|
+
"DOWNLOAD_FAILED",
|
|
1584
|
+
UpdateFailure.install(UpdateFailureReason.EXTRACT).copy(resource = UpdateFailureResource.FILE),
|
|
1585
|
+
)
|
|
1586
|
+
|
|
1587
|
+
val stagingRoot = temporaryFolder.newFolder("staging-directory-blocked")
|
|
1588
|
+
File(bundleStoreDir(stagingRoot), "target.tmp").writeText("a file where the staging directory belongs")
|
|
1589
|
+
assertUpdateFailure(
|
|
1590
|
+
runOneFileUpdate(stagingRoot),
|
|
1591
|
+
"DIRECTORY_CREATION_FAILED",
|
|
1592
|
+
UpdateFailure.install(UpdateFailureReason.STORAGE),
|
|
1593
|
+
)
|
|
1594
|
+
}
|
|
1595
|
+
|
|
1596
|
+
@Test
|
|
1597
|
+
fun `unanticipated failures report unknown in the stage they happened in`() =
|
|
1598
|
+
runBlocking {
|
|
1599
|
+
val downloadRoot = temporaryFolder.newFolder("unknown-download-failure")
|
|
1600
|
+
val downloadResult =
|
|
1601
|
+
runCatching {
|
|
1602
|
+
createService(downloadRoot).updateBundle("target", MANIFEST_URL, "hash", emptyMap()) {}
|
|
1603
|
+
}.exceptionOrNull()
|
|
1604
|
+
assertUpdateFailure(
|
|
1605
|
+
downloadResult,
|
|
1606
|
+
"UNKNOWN_ERROR",
|
|
1607
|
+
UpdateFailure.download(UpdateFailureReason.UNKNOWN).copy(resource = UpdateFailureResource.MANIFEST),
|
|
1608
|
+
)
|
|
1609
|
+
assertEquals("downloadFile should not be called in these tests", downloadResult?.message)
|
|
1610
|
+
|
|
1611
|
+
val installResult =
|
|
1612
|
+
runOneFileUpdate(
|
|
1613
|
+
temporaryFolder.newFolder("unknown-install-failure"),
|
|
1614
|
+
directoryRenamer = { source, destination ->
|
|
1615
|
+
if (source.name == "target.tmp") throw SecurityException("Promotion denied")
|
|
1616
|
+
source.renameTo(destination)
|
|
1617
|
+
},
|
|
1618
|
+
)
|
|
1619
|
+
assertUpdateFailure(installResult, "UNKNOWN_ERROR", UpdateFailure.install(UpdateFailureReason.UNKNOWN))
|
|
1620
|
+
assertEquals("Promotion denied", installResult?.message)
|
|
1621
|
+
}
|
|
1622
|
+
|
|
1623
|
+
@Test
|
|
1624
|
+
fun `a bundle built from changed files reports manifest delivery`() =
|
|
1625
|
+
runBlocking {
|
|
1626
|
+
val root = temporaryFolder.newFolder("manifest-delivery")
|
|
1627
|
+
val hash = sha256(root, BUNDLE_CONTENT)
|
|
1628
|
+
val manifest = manifestJson("target", mapOf("index.android.bundle" to hash))
|
|
1629
|
+
val downloads =
|
|
1630
|
+
BinaryMappingDownloadService(mapOf(MANIFEST_URL to manifest.toByteArray(), BUNDLE_URL to BUNDLE_CONTENT.toByteArray()))
|
|
1631
|
+
val service = createService(root, downloadService = downloads, builtInAssetResolver = MappingBuiltInAssetResolver(emptyMap()))
|
|
1632
|
+
val assets = mapOf("index.android.bundle" to ChangedAssetDescriptor(BUNDLE_URL, hash))
|
|
1633
|
+
|
|
1634
|
+
val downloaded = service.updateBundle("target", MANIFEST_URL, sha256(root, manifest), assets) {}
|
|
1635
|
+
// Installing it again reuses the staged file, so nothing is downloaded.
|
|
1636
|
+
val reused = service.updateBundle("target", MANIFEST_URL, sha256(root, manifest), assets) {}
|
|
1637
|
+
|
|
1638
|
+
assertEquals(UpdateBundleResult(BundleDelivery.MANIFEST, patchFallback = false), downloaded)
|
|
1639
|
+
assertEquals(UpdateBundleResult(BundleDelivery.MANIFEST, patchFallback = false), reused)
|
|
1640
|
+
assertEquals(1, downloads.calls.count { it == BUNDLE_URL })
|
|
1641
|
+
}
|
|
1642
|
+
|
|
1643
|
+
@Test
|
|
1644
|
+
fun `a bundle installed from the archive reports archive delivery`() =
|
|
1645
|
+
runBlocking {
|
|
1646
|
+
val root = temporaryFolder.newFolder("archive-delivery")
|
|
1647
|
+
val archiveBytes = Base64.getDecoder().decode(TarBrArchiveExtractorTest.VALID)
|
|
1648
|
+
val contents = mapOf("assets/image.png" to "image", "index.android.bundle" to "bundle")
|
|
1649
|
+
val hashes = contents.mapValues { sha256(root, it.value) }
|
|
1650
|
+
val manifest =
|
|
1651
|
+
manifestJson(
|
|
1652
|
+
bundleId = "target",
|
|
1653
|
+
assets = hashes,
|
|
1654
|
+
byteSizes = contents.mapValues { it.value.length.toLong() },
|
|
1655
|
+
downloadByteSizes = mapOf("assets/image.png" to 50, "index.android.bundle" to 55),
|
|
1656
|
+
archive = archiveJson(root, archiveBytes, tarByteSize = 3072),
|
|
1657
|
+
)
|
|
1658
|
+
val downloads =
|
|
1659
|
+
BinaryMappingDownloadService(mapOf(MANIFEST_URL to manifest.toByteArray(), ARCHIVE_URL to archiveBytes))
|
|
1660
|
+
|
|
1661
|
+
val result =
|
|
1662
|
+
createService(root, downloadService = downloads, builtInAssetResolver = MappingBuiltInAssetResolver(emptyMap()))
|
|
1663
|
+
.updateBundle(
|
|
1664
|
+
bundleId = "target",
|
|
1665
|
+
manifestUrl = MANIFEST_URL,
|
|
1666
|
+
manifestFileHash = sha256(root, manifest),
|
|
1667
|
+
assets = hashes.mapValues { (path, hash) -> ChangedAssetDescriptor("https://example.com/$path", hash) },
|
|
1668
|
+
archiveUrl = ARCHIVE_URL,
|
|
1669
|
+
progressCallback = {},
|
|
1670
|
+
)
|
|
1671
|
+
|
|
1672
|
+
assertEquals(UpdateBundleResult(BundleDelivery.ARCHIVE, patchFallback = false), result)
|
|
1673
|
+
assertEquals(listOf(MANIFEST_URL, ARCHIVE_URL), downloads.calls)
|
|
1674
|
+
}
|
|
1675
|
+
|
|
1676
|
+
@Test
|
|
1677
|
+
fun `a file produced by a patch reports patch delivery`() =
|
|
1678
|
+
runBlocking {
|
|
1679
|
+
val patchUpdate = PatchUpdate(temporaryFolder.newFolder("patch-delivery"))
|
|
1680
|
+
|
|
1681
|
+
val result = patchUpdate.run(servedPatch = patchUpdate.patchBytes)
|
|
1682
|
+
|
|
1683
|
+
assertEquals(UpdateBundleResult(BundleDelivery.PATCH, patchFallback = false), result.getOrThrow())
|
|
1684
|
+
assertEquals(listOf(MANIFEST_URL, PATCH_URL), patchUpdate.downloads.calls)
|
|
1685
|
+
assertEquals(PATCHED_CONTENT, File(bundleStoreDir(patchUpdate.root), "target/index.android.bundle").readText())
|
|
1686
|
+
}
|
|
1687
|
+
|
|
1688
|
+
@Test
|
|
1689
|
+
fun `a patch that cannot produce its file falls back to the file`() =
|
|
1690
|
+
runBlocking {
|
|
1691
|
+
val corruptPatch = PatchUpdate(temporaryFolder.newFolder("patch-fallback-corrupt"))
|
|
1692
|
+
assertEquals(
|
|
1693
|
+
UpdateBundleResult(BundleDelivery.MANIFEST, patchFallback = true),
|
|
1694
|
+
corruptPatch.run(servedPatch = "not a bsdiff patch".toByteArray()).getOrThrow(),
|
|
1695
|
+
)
|
|
1696
|
+
assertEquals(listOf(MANIFEST_URL, PATCH_URL, BUNDLE_URL), corruptPatch.downloads.calls)
|
|
1697
|
+
|
|
1698
|
+
val unreachablePatch = PatchUpdate(temporaryFolder.newFolder("patch-fallback-http"))
|
|
1699
|
+
assertEquals(
|
|
1700
|
+
UpdateBundleResult(BundleDelivery.MANIFEST, patchFallback = true),
|
|
1701
|
+
unreachablePatch.run(servedPatch = null).getOrThrow(),
|
|
1702
|
+
)
|
|
1703
|
+
|
|
1704
|
+
val corruptBase = PatchUpdate(temporaryFolder.newFolder("patch-fallback-base"))
|
|
1705
|
+
File(bundleStoreDir(corruptBase.root), "base-bundle/index.android.bundle").writeText("corrupt base")
|
|
1706
|
+
assertEquals(
|
|
1707
|
+
UpdateBundleResult(BundleDelivery.MANIFEST, patchFallback = true),
|
|
1708
|
+
corruptBase.run(servedPatch = corruptBase.patchBytes).getOrThrow(),
|
|
1709
|
+
)
|
|
1710
|
+
assertFalse(corruptBase.downloads.calls.contains(PATCH_URL))
|
|
1711
|
+
|
|
1712
|
+
// A patch for a bundle other than the running one is never attempted.
|
|
1713
|
+
val otherBase = PatchUpdate(temporaryFolder.newFolder("patch-for-other-base"))
|
|
1714
|
+
assertEquals(
|
|
1715
|
+
UpdateBundleResult(BundleDelivery.MANIFEST, patchFallback = false),
|
|
1716
|
+
otherBase.run(servedPatch = otherBase.patchBytes, patchBaseBundleId = "other-bundle").getOrThrow(),
|
|
1717
|
+
)
|
|
1718
|
+
assertEquals(listOf(MANIFEST_URL, BUNDLE_URL), otherBase.downloads.calls)
|
|
1719
|
+
}
|
|
1720
|
+
|
|
1721
|
+
/**
|
|
1722
|
+
* Bundle "target" whose index.android.bundle can be produced by a bsdiff
|
|
1723
|
+
* patch against the running bundle "base-bundle", or downloaded whole.
|
|
1724
|
+
*/
|
|
1725
|
+
private inner class PatchUpdate(
|
|
1726
|
+
val root: File,
|
|
1727
|
+
) {
|
|
1728
|
+
val patchBytes: ByteArray = Base64.getDecoder().decode(BsdiffPatchTest.BSDIFF_PATCH_FIXTURE_BASE64)
|
|
1729
|
+
private val preferences = InMemoryPreferencesService()
|
|
1730
|
+
lateinit var downloads: BinaryMappingDownloadService
|
|
1731
|
+
|
|
1732
|
+
init {
|
|
1733
|
+
val base = createBundleDir(root, "base-bundle")
|
|
1734
|
+
val baseFile = writeFile(base, "index.android.bundle", BASE_CONTENT)
|
|
1735
|
+
File(base, "manifest.json").writeText(
|
|
1736
|
+
manifestJson("base-bundle", mapOf("index.android.bundle" to sha256(root, BASE_CONTENT))),
|
|
1737
|
+
)
|
|
1738
|
+
writeMetadata(root, BundleMetadata(isolationKey = TEST_ISOLATION_KEY, stagingBundleId = "base-bundle"))
|
|
1739
|
+
preferences.setItem("HotUpdaterBundleURL", baseFile.absolutePath)
|
|
1740
|
+
}
|
|
1741
|
+
|
|
1742
|
+
/** Serves [servedPatch] with its hash, or a 404 for the patch when it is null. */
|
|
1743
|
+
suspend fun run(
|
|
1744
|
+
servedPatch: ByteArray?,
|
|
1745
|
+
patchBaseBundleId: String = "base-bundle",
|
|
1746
|
+
): Result<UpdateBundleResult> {
|
|
1747
|
+
val targetHash = sha256(root, PATCHED_CONTENT)
|
|
1748
|
+
val manifest = manifestJson("target", mapOf("index.android.bundle" to targetHash))
|
|
1749
|
+
downloads =
|
|
1750
|
+
BinaryMappingDownloadService(
|
|
1751
|
+
buildMap {
|
|
1752
|
+
put(MANIFEST_URL, manifest.toByteArray())
|
|
1753
|
+
put(BUNDLE_URL, PATCHED_CONTENT.toByteArray())
|
|
1754
|
+
servedPatch?.let { put(PATCH_URL, it) }
|
|
1755
|
+
},
|
|
1756
|
+
failures = if (servedPatch == null) mapOf(PATCH_URL to HttpStatusException(404, "Not Found")) else emptyMap(),
|
|
1757
|
+
)
|
|
1758
|
+
val service =
|
|
1759
|
+
createService(
|
|
1760
|
+
root,
|
|
1761
|
+
preferences = preferences,
|
|
1762
|
+
downloadService = downloads,
|
|
1763
|
+
builtInAssetResolver = MappingBuiltInAssetResolver(emptyMap()),
|
|
1764
|
+
)
|
|
1765
|
+
val descriptor =
|
|
1766
|
+
ChangedAssetDescriptor(
|
|
1767
|
+
fileUrl = BUNDLE_URL,
|
|
1768
|
+
fileHash = targetHash,
|
|
1769
|
+
patch =
|
|
1770
|
+
BsdiffPatchDescriptor(
|
|
1771
|
+
algorithm = "bsdiff",
|
|
1772
|
+
baseBundleId = patchBaseBundleId,
|
|
1773
|
+
baseFileHash = sha256(root, BASE_CONTENT),
|
|
1774
|
+
patchFileHash = sha256(root, servedPatch ?: ByteArray(0)),
|
|
1775
|
+
patchUrl = PATCH_URL,
|
|
1776
|
+
),
|
|
1777
|
+
)
|
|
1778
|
+
return runCatching {
|
|
1779
|
+
service.updateBundle("target", MANIFEST_URL, sha256(root, manifest), mapOf("index.android.bundle" to descriptor)) {}
|
|
1780
|
+
}
|
|
1781
|
+
}
|
|
1782
|
+
}
|
|
1783
|
+
|
|
1784
|
+
@Test
|
|
1785
|
+
fun `rejections that are not update failures carry no classification`() =
|
|
1786
|
+
runBlocking {
|
|
1787
|
+
val crashedRoot = temporaryFolder.newFolder("crashed-bundle")
|
|
1788
|
+
assertTrue(
|
|
1789
|
+
CrashedHistory(mutableListOf(CrashedBundleEntry(bundleId = "target", crashedAt = 1)))
|
|
1790
|
+
.saveToFile(File(bundleStoreDir(crashedRoot), CrashedHistory.CRASHED_HISTORY_FILENAME)),
|
|
1791
|
+
)
|
|
1792
|
+
assertUpdateFailure(
|
|
1793
|
+
runOneFileUpdate(crashedRoot),
|
|
1794
|
+
"BUNDLE_IN_CRASHED_HISTORY",
|
|
1795
|
+
null,
|
|
1796
|
+
)
|
|
1797
|
+
|
|
1798
|
+
val staleRoot = temporaryFolder.newFolder("stale-selection")
|
|
1799
|
+
val hash = sha256(staleRoot, BUNDLE_CONTENT)
|
|
1800
|
+
val manifest = manifestJson("target", mapOf("index.android.bundle" to hash))
|
|
1801
|
+
val served =
|
|
1802
|
+
BinaryMappingDownloadService(
|
|
1803
|
+
mapOf(MANIFEST_URL to manifest.toByteArray(), BUNDLE_URL to BUNDLE_CONTENT.toByteArray()),
|
|
1804
|
+
)
|
|
1805
|
+
lateinit var service: BundleFileStorageService
|
|
1806
|
+
val downloads =
|
|
1807
|
+
object : DownloadService {
|
|
1808
|
+
override suspend fun downloadFile(
|
|
1809
|
+
fileUrl: URL,
|
|
1810
|
+
destination: File,
|
|
1811
|
+
fileSizeCallback: ((Long) -> Unit)?,
|
|
1812
|
+
progressCallback: (DownloadProgress) -> Unit,
|
|
1813
|
+
): DownloadResult {
|
|
1814
|
+
if (fileUrl.toString() == BUNDLE_URL) {
|
|
1815
|
+
// A newer catalog is accepted while the selected bundle downloads.
|
|
1816
|
+
service.acceptReleaseCatalog("project-a", "scope-production", 2, "hash-2", "production", "context-2")
|
|
1817
|
+
}
|
|
1818
|
+
return served.downloadFile(fileUrl, destination, fileSizeCallback, progressCallback)
|
|
1819
|
+
}
|
|
1820
|
+
}
|
|
1821
|
+
service = createService(staleRoot, downloadService = downloads, builtInAssetResolver = MappingBuiltInAssetResolver(emptyMap()))
|
|
1822
|
+
assertTrue(service.acceptReleaseCatalog("project-a", "scope-production", 1, "hash-1", "production", "context-1"))
|
|
1823
|
+
assertTrue(service.stageReleaseSelection(releaseSelection("release-one", "target", 1, "hash-1", "context-1")))
|
|
1824
|
+
|
|
1825
|
+
val staleResult =
|
|
1826
|
+
runCatching {
|
|
1827
|
+
service.updateBundle(
|
|
1828
|
+
"target",
|
|
1829
|
+
MANIFEST_URL,
|
|
1830
|
+
sha256(staleRoot, manifest),
|
|
1831
|
+
mapOf("index.android.bundle" to ChangedAssetDescriptor(BUNDLE_URL, hash)),
|
|
1832
|
+
) {}
|
|
1833
|
+
}.exceptionOrNull()
|
|
1834
|
+
|
|
1835
|
+
assertTrue("Expected a stale selection, got $staleResult", staleResult is StaleReleaseSelectionException)
|
|
1836
|
+
assertNull(loadMetadata(staleRoot)?.stagingBundleId)
|
|
1837
|
+
}
|
|
1838
|
+
|
|
1839
|
+
/** Installs bundle "target" with one file, returning the failure, if any. */
|
|
1840
|
+
private suspend fun runOneFileUpdate(
|
|
1841
|
+
root: File,
|
|
1842
|
+
assetPath: String = "index.android.bundle",
|
|
1843
|
+
manifest: String = manifestJson("target", mapOf(assetPath to sha256(root, BUNDLE_CONTENT))),
|
|
1844
|
+
manifestFileHash: String = sha256(root, manifest),
|
|
1845
|
+
descriptor: ChangedAssetDescriptor = ChangedAssetDescriptor(BUNDLE_URL, sha256(root, BUNDLE_CONTENT)),
|
|
1846
|
+
servedFile: ByteArray = BUNDLE_CONTENT.toByteArray(),
|
|
1847
|
+
failures: Map<String, Exception> = emptyMap(),
|
|
1848
|
+
directoryRenamer: (File, File) -> Boolean = { source, destination -> source.renameTo(destination) },
|
|
1849
|
+
): Throwable? {
|
|
1850
|
+
val service =
|
|
1851
|
+
createService(
|
|
1852
|
+
root,
|
|
1853
|
+
downloadService =
|
|
1854
|
+
BinaryMappingDownloadService(
|
|
1855
|
+
mapOf(MANIFEST_URL to manifest.toByteArray(), BUNDLE_URL to servedFile),
|
|
1856
|
+
failures,
|
|
1857
|
+
),
|
|
1858
|
+
builtInAssetResolver = MappingBuiltInAssetResolver(emptyMap()),
|
|
1859
|
+
directoryRenamer = directoryRenamer,
|
|
1860
|
+
)
|
|
1861
|
+
return runCatching {
|
|
1862
|
+
service.updateBundle("target", MANIFEST_URL, manifestFileHash, mapOf(assetPath to descriptor)) {}
|
|
1863
|
+
}.exceptionOrNull()
|
|
1864
|
+
}
|
|
1865
|
+
|
|
1866
|
+
private fun assertUpdateFailure(
|
|
1867
|
+
error: Throwable?,
|
|
1868
|
+
code: String,
|
|
1869
|
+
failure: UpdateFailure?,
|
|
1870
|
+
) {
|
|
1871
|
+
assertTrue("Expected HotUpdaterException, got $error", error is HotUpdaterException)
|
|
1872
|
+
val updateError = error as HotUpdaterException
|
|
1873
|
+
assertEquals(code, updateError.code)
|
|
1874
|
+
assertEquals(failure, updateError.failure)
|
|
1875
|
+
}
|
|
1876
|
+
|
|
1496
1877
|
private fun createService(
|
|
1497
1878
|
rootDir: File,
|
|
1498
1879
|
preferences: InMemoryPreferencesService = InMemoryPreferencesService(),
|
|
@@ -1586,11 +1967,6 @@ class BundleFileStorageServiceTest {
|
|
|
1586
1967
|
TEST_ISOLATION_KEY,
|
|
1587
1968
|
)
|
|
1588
1969
|
|
|
1589
|
-
private fun loadInstallationIdentity(rootDir: File): InstallationIdentity? =
|
|
1590
|
-
InstallationIdentity.loadFromFile(
|
|
1591
|
-
File(bundleStoreDir(rootDir), InstallationIdentity.IDENTITY_FILENAME),
|
|
1592
|
-
)
|
|
1593
|
-
|
|
1594
1970
|
private fun writeFile(
|
|
1595
1971
|
rootDir: File,
|
|
1596
1972
|
relativePath: String,
|
|
@@ -1727,6 +2103,7 @@ class BundleFileStorageServiceTest {
|
|
|
1727
2103
|
|
|
1728
2104
|
private class BinaryMappingDownloadService(
|
|
1729
2105
|
private val contents: Map<String, ByteArray>,
|
|
2106
|
+
private val failures: Map<String, Exception> = emptyMap(),
|
|
1730
2107
|
) : DownloadService {
|
|
1731
2108
|
val calls = CopyOnWriteArrayList<String>()
|
|
1732
2109
|
|
|
@@ -1751,6 +2128,7 @@ class BundleFileStorageServiceTest {
|
|
|
1751
2128
|
progressCallback: (DownloadProgress) -> Unit,
|
|
1752
2129
|
): DownloadResult {
|
|
1753
2130
|
calls += fileUrl.toString()
|
|
2131
|
+
failures[fileUrl.toString()]?.let { return DownloadResult.Error(it) }
|
|
1754
2132
|
val bytes = contents[fileUrl.toString()] ?: return DownloadResult.Error(IllegalArgumentException("Unexpected URL: $fileUrl"))
|
|
1755
2133
|
destination.parentFile?.mkdirs()
|
|
1756
2134
|
destination.writeBytes(bytes)
|
|
@@ -1847,5 +2225,12 @@ class BundleFileStorageServiceTest {
|
|
|
1847
2225
|
private const val TEST_ISOLATION_KEY = "test-isolation-key"
|
|
1848
2226
|
private const val MANIFEST_URL = "https://example.com/manifest.json"
|
|
1849
2227
|
private const val ARCHIVE_URL = "https://example.com/bundle.tar.br"
|
|
2228
|
+
private const val BUNDLE_URL = "https://example.com/index.android.bundle"
|
|
2229
|
+
private const val BUNDLE_CONTENT = "bundle"
|
|
2230
|
+
private const val PATCH_URL = "https://example.com/index.android.bundle.bsdiff"
|
|
2231
|
+
|
|
2232
|
+
// The base and output of BsdiffPatchTest.BSDIFF_PATCH_FIXTURE_BASE64.
|
|
2233
|
+
private const val BASE_CONTENT = "console.log(\"base bundle\");\n"
|
|
2234
|
+
private const val PATCHED_CONTENT = "console.log(\"patched bundle\");\n"
|
|
1850
2235
|
}
|
|
1851
2236
|
}
|