@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.
- package/lib/commonjs/checkForUpdate.js +7 -9
- package/lib/commonjs/checkForUpdate.js.map +1 -1
- package/lib/commonjs/clientPlugin.js +7 -43
- package/lib/commonjs/clientPlugin.js.map +1 -1
- package/lib/commonjs/httpClient.js +7 -9
- package/lib/commonjs/httpClient.js.map +1 -1
- package/lib/commonjs/index.js +10 -0
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/native.js +4 -4
- package/lib/commonjs/native.js.map +1 -1
- package/lib/commonjs/pluginHost.js +26 -190
- package/lib/commonjs/pluginHost.js.map +1 -1
- package/lib/commonjs/releaseCatalogCache.js +7 -59
- package/lib/commonjs/releaseCatalogCache.js.map +1 -1
- package/lib/commonjs/sdkVersion.js +1 -1
- package/lib/module/checkForUpdate.js +1 -3
- package/lib/module/checkForUpdate.js.map +1 -1
- package/lib/module/clientPlugin.js +1 -41
- package/lib/module/clientPlugin.js.map +1 -1
- package/lib/module/httpClient.js +1 -3
- package/lib/module/httpClient.js.map +1 -1
- package/lib/module/index.js +2 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/native.js +1 -1
- package/lib/module/native.js.map +1 -1
- package/lib/module/pluginHost.js +22 -187
- package/lib/module/pluginHost.js.map +1 -1
- package/lib/module/releaseCatalogCache.js +3 -54
- package/lib/module/releaseCatalogCache.js.map +1 -1
- package/lib/module/sdkVersion.js +1 -1
- package/lib/typescript/commonjs/checkForUpdate.d.ts.map +1 -1
- package/lib/typescript/commonjs/clientPlugin.d.ts +1 -184
- package/lib/typescript/commonjs/clientPlugin.d.ts.map +1 -1
- package/lib/typescript/commonjs/httpClient.d.ts +1 -2
- package/lib/typescript/commonjs/httpClient.d.ts.map +1 -1
- package/lib/typescript/commonjs/index.d.ts +1 -0
- package/lib/typescript/commonjs/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/native.d.ts +1 -1
- package/lib/typescript/commonjs/native.d.ts.map +1 -1
- package/lib/typescript/commonjs/pluginHost.d.ts +3 -28
- package/lib/typescript/commonjs/pluginHost.d.ts.map +1 -1
- package/lib/typescript/commonjs/releaseCatalogCache.d.ts +1 -13
- package/lib/typescript/commonjs/releaseCatalogCache.d.ts.map +1 -1
- package/lib/typescript/commonjs/sdkVersion.d.ts +1 -1
- package/lib/typescript/commonjs/types.d.ts +1 -1
- package/lib/typescript/commonjs/types.d.ts.map +1 -1
- package/lib/typescript/commonjs/wrap.types.d.ts +2 -2
- package/lib/typescript/module/checkForUpdate.d.ts.map +1 -1
- package/lib/typescript/module/clientPlugin.d.ts +1 -184
- package/lib/typescript/module/clientPlugin.d.ts.map +1 -1
- package/lib/typescript/module/httpClient.d.ts +1 -2
- package/lib/typescript/module/httpClient.d.ts.map +1 -1
- package/lib/typescript/module/index.d.ts +1 -0
- package/lib/typescript/module/index.d.ts.map +1 -1
- package/lib/typescript/module/native.d.ts +1 -1
- package/lib/typescript/module/native.d.ts.map +1 -1
- package/lib/typescript/module/pluginHost.d.ts +3 -28
- package/lib/typescript/module/pluginHost.d.ts.map +1 -1
- package/lib/typescript/module/releaseCatalogCache.d.ts +1 -13
- package/lib/typescript/module/releaseCatalogCache.d.ts.map +1 -1
- package/lib/typescript/module/sdkVersion.d.ts +1 -1
- package/lib/typescript/module/types.d.ts +1 -1
- package/lib/typescript/module/types.d.ts.map +1 -1
- package/lib/typescript/module/wrap.types.d.ts +2 -2
- package/package.json +7 -16
- package/src/checkForUpdate.ts +4 -6
- package/src/clientPlugin.ts +17 -231
- package/src/httpClient.ts +4 -4
- package/src/index.ts +7 -0
- package/src/native.ts +1 -1
- package/src/pluginHost.ts +24 -241
- package/src/releaseCatalogCache.ts +7 -136
- package/src/sdkVersion.ts +1 -1
- package/src/types.ts +1 -1
- package/src/wrap.types.ts +2 -2
- package/android/src/test/java/com/hotupdater/BsdiffPatchTest.kt +0 -46
- package/android/src/test/java/com/hotupdater/BundleFileStorageServiceTest.kt +0 -2236
- package/android/src/test/java/com/hotupdater/HotUpdaterExceptionTest.kt +0 -220
- package/android/src/test/java/com/hotupdater/HotUpdaterImplTest.kt +0 -264
- package/android/src/test/java/com/hotupdater/HotUpdaterRecoveryStateTest.kt +0 -97
- package/android/src/test/java/com/hotupdater/InstallIdentityServiceTest.kt +0 -71
- package/android/src/test/java/com/hotupdater/KeyValueStorageServiceTest.kt +0 -106
- package/android/src/test/java/com/hotupdater/NativeConfigUtilsTest.kt +0 -20
- package/android/src/test/java/com/hotupdater/NoBackupContext.kt +0 -11
- package/android/src/test/java/com/hotupdater/OkHttpDownloadServiceTest.kt +0 -397
- package/android/src/test/java/com/hotupdater/PendingCrashRecoveryTest.kt +0 -44
- package/android/src/test/java/com/hotupdater/PreviousProcessExitTest.kt +0 -57
- package/android/src/test/java/com/hotupdater/RelativePathResolverTest.kt +0 -41
- package/android/src/test/java/com/hotupdater/ReleaseCatalogCacheServiceTest.kt +0 -118
- package/android/src/test/java/com/hotupdater/TarBrArchiveExtractorTest.kt +0 -206
- package/lib/commonjs/appReady.spec.js +0 -360
- package/lib/commonjs/appReady.spec.js.map +0 -1
- package/lib/commonjs/appReady.test-utils.js +0 -25
- package/lib/commonjs/appReady.test-utils.js.map +0 -1
- package/lib/commonjs/baseURL.js +0 -16
- package/lib/commonjs/baseURL.js.map +0 -1
- package/lib/commonjs/catalogCacheNative.spec.js +0 -31
- package/lib/commonjs/catalogCacheNative.spec.js.map +0 -1
- package/lib/commonjs/checkForUpdate.releaseCatalog.spec.js +0 -936
- package/lib/commonjs/checkForUpdate.releaseCatalog.spec.js.map +0 -1
- package/lib/commonjs/entryImportGraph.spec.js +0 -50
- package/lib/commonjs/entryImportGraph.spec.js.map +0 -1
- package/lib/commonjs/httpClient.spec.js +0 -171
- package/lib/commonjs/httpClient.spec.js.map +0 -1
- package/lib/commonjs/index.spec.js +0 -395
- package/lib/commonjs/index.spec.js.map +0 -1
- package/lib/commonjs/iosInterop.spec.js +0 -18
- package/lib/commonjs/iosInterop.spec.js.map +0 -1
- package/lib/commonjs/native.spec.js +0 -910
- package/lib/commonjs/native.spec.js.map +0 -1
- package/lib/commonjs/pluginHost.spec.js +0 -292
- package/lib/commonjs/pluginHost.spec.js.map +0 -1
- package/lib/commonjs/plugins/insights/eventId.js +0 -129
- package/lib/commonjs/plugins/insights/eventId.js.map +0 -1
- package/lib/commonjs/plugins/insights/eventId.spec.js +0 -47
- package/lib/commonjs/plugins/insights/eventId.spec.js.map +0 -1
- package/lib/commonjs/plugins/insights/index.js +0 -272
- package/lib/commonjs/plugins/insights/index.js.map +0 -1
- package/lib/commonjs/plugins/insights/insights.spec.js +0 -640
- package/lib/commonjs/plugins/insights/insights.spec.js.map +0 -1
- package/lib/commonjs/plugins/insights/sender.js +0 -128
- package/lib/commonjs/plugins/insights/sender.js.map +0 -1
- package/lib/commonjs/plugins/insights/sender.spec.js +0 -265
- package/lib/commonjs/plugins/insights/sender.spec.js.map +0 -1
- package/lib/commonjs/plugins/insights/state.js +0 -145
- package/lib/commonjs/plugins/insights/state.js.map +0 -1
- package/lib/commonjs/releaseCatalogCache.spec.js +0 -213
- package/lib/commonjs/releaseCatalogCache.spec.js.map +0 -1
- package/lib/commonjs/releaseCatalogCacheNativeContract.spec.js +0 -64
- package/lib/commonjs/releaseCatalogCacheNativeContract.spec.js.map +0 -1
- package/lib/commonjs/store.notification.spec.js +0 -84
- package/lib/commonjs/store.notification.spec.js.map +0 -1
- package/lib/commonjs/store.spec.js +0 -182
- package/lib/commonjs/store.spec.js.map +0 -1
- package/lib/commonjs/updateError.spec.js +0 -149
- package/lib/commonjs/updateError.spec.js.map +0 -1
- package/lib/commonjs/wrap.progress.spec.js +0 -195
- package/lib/commonjs/wrap.progress.spec.js.map +0 -1
- package/lib/commonjs/wrap.spec.js +0 -232
- package/lib/commonjs/wrap.spec.js.map +0 -1
- package/lib/module/appReady.spec.js +0 -359
- package/lib/module/appReady.spec.js.map +0 -1
- package/lib/module/appReady.test-utils.js +0 -19
- package/lib/module/appReady.test-utils.js.map +0 -1
- package/lib/module/baseURL.js +0 -11
- package/lib/module/baseURL.js.map +0 -1
- package/lib/module/catalogCacheNative.spec.js +0 -31
- package/lib/module/catalogCacheNative.spec.js.map +0 -1
- package/lib/module/checkForUpdate.releaseCatalog.spec.js +0 -935
- package/lib/module/checkForUpdate.releaseCatalog.spec.js.map +0 -1
- package/lib/module/entryImportGraph.spec.js +0 -50
- package/lib/module/entryImportGraph.spec.js.map +0 -1
- package/lib/module/httpClient.spec.js +0 -171
- package/lib/module/httpClient.spec.js.map +0 -1
- package/lib/module/index.spec.js +0 -394
- package/lib/module/index.spec.js.map +0 -1
- package/lib/module/iosInterop.spec.js +0 -18
- package/lib/module/iosInterop.spec.js.map +0 -1
- package/lib/module/native.spec.js +0 -909
- package/lib/module/native.spec.js.map +0 -1
- package/lib/module/pluginHost.spec.js +0 -291
- package/lib/module/pluginHost.spec.js.map +0 -1
- package/lib/module/plugins/insights/eventId.js +0 -122
- package/lib/module/plugins/insights/eventId.js.map +0 -1
- package/lib/module/plugins/insights/eventId.spec.js +0 -47
- package/lib/module/plugins/insights/eventId.spec.js.map +0 -1
- package/lib/module/plugins/insights/index.js +0 -268
- package/lib/module/plugins/insights/index.js.map +0 -1
- package/lib/module/plugins/insights/insights.spec.js +0 -639
- package/lib/module/plugins/insights/insights.spec.js.map +0 -1
- package/lib/module/plugins/insights/sender.js +0 -123
- package/lib/module/plugins/insights/sender.js.map +0 -1
- package/lib/module/plugins/insights/sender.spec.js +0 -265
- package/lib/module/plugins/insights/sender.spec.js.map +0 -1
- package/lib/module/plugins/insights/state.js +0 -139
- package/lib/module/plugins/insights/state.js.map +0 -1
- package/lib/module/releaseCatalogCache.spec.js +0 -213
- package/lib/module/releaseCatalogCache.spec.js.map +0 -1
- package/lib/module/releaseCatalogCacheNativeContract.spec.js +0 -64
- package/lib/module/releaseCatalogCacheNativeContract.spec.js.map +0 -1
- package/lib/module/store.notification.spec.js +0 -85
- package/lib/module/store.notification.spec.js.map +0 -1
- package/lib/module/store.spec.js +0 -181
- package/lib/module/store.spec.js.map +0 -1
- package/lib/module/updateError.spec.js +0 -149
- package/lib/module/updateError.spec.js.map +0 -1
- package/lib/module/wrap.progress.spec.js +0 -196
- package/lib/module/wrap.progress.spec.js.map +0 -1
- package/lib/module/wrap.spec.js +0 -233
- package/lib/module/wrap.spec.js.map +0 -1
- package/lib/typescript/commonjs/appReady.test-utils.d.ts +0 -9
- package/lib/typescript/commonjs/appReady.test-utils.d.ts.map +0 -1
- package/lib/typescript/commonjs/baseURL.d.ts +0 -4
- package/lib/typescript/commonjs/baseURL.d.ts.map +0 -1
- package/lib/typescript/commonjs/plugins/insights/eventId.d.ts +0 -18
- package/lib/typescript/commonjs/plugins/insights/eventId.d.ts.map +0 -1
- package/lib/typescript/commonjs/plugins/insights/index.d.ts +0 -40
- package/lib/typescript/commonjs/plugins/insights/index.d.ts.map +0 -1
- package/lib/typescript/commonjs/plugins/insights/sender.d.ts +0 -74
- package/lib/typescript/commonjs/plugins/insights/sender.d.ts.map +0 -1
- package/lib/typescript/commonjs/plugins/insights/state.d.ts +0 -46
- package/lib/typescript/commonjs/plugins/insights/state.d.ts.map +0 -1
- package/lib/typescript/module/appReady.test-utils.d.ts +0 -9
- package/lib/typescript/module/appReady.test-utils.d.ts.map +0 -1
- package/lib/typescript/module/baseURL.d.ts +0 -4
- package/lib/typescript/module/baseURL.d.ts.map +0 -1
- package/lib/typescript/module/plugins/insights/eventId.d.ts +0 -18
- package/lib/typescript/module/plugins/insights/eventId.d.ts.map +0 -1
- package/lib/typescript/module/plugins/insights/index.d.ts +0 -40
- package/lib/typescript/module/plugins/insights/index.d.ts.map +0 -1
- package/lib/typescript/module/plugins/insights/sender.d.ts +0 -74
- package/lib/typescript/module/plugins/insights/sender.d.ts.map +0 -1
- package/lib/typescript/module/plugins/insights/state.d.ts +0 -46
- package/lib/typescript/module/plugins/insights/state.d.ts.map +0 -1
- package/plugins/insights/package.json +0 -8
- package/src/appReady.spec.ts +0 -393
- package/src/appReady.test-utils.ts +0 -31
- package/src/baseURL.ts +0 -15
- package/src/catalogCacheNative.spec.ts +0 -63
- package/src/checkForUpdate.releaseCatalog.spec.ts +0 -966
- package/src/entryImportGraph.spec.ts +0 -65
- package/src/httpClient.spec.ts +0 -216
- package/src/index.spec.ts +0 -434
- package/src/iosInterop.spec.ts +0 -30
- package/src/native.spec.ts +0 -978
- package/src/pluginHost.spec.ts +0 -297
- package/src/plugins/insights/eventId.spec.ts +0 -60
- package/src/plugins/insights/eventId.ts +0 -171
- package/src/plugins/insights/index.ts +0 -367
- package/src/plugins/insights/insights.spec.ts +0 -732
- package/src/plugins/insights/sender.spec.ts +0 -287
- package/src/plugins/insights/sender.ts +0 -221
- package/src/plugins/insights/state.ts +0 -184
- package/src/releaseCatalogCache.spec.ts +0 -304
- package/src/releaseCatalogCacheNativeContract.spec.ts +0 -80
- package/src/store.notification.spec.ts +0 -102
- package/src/store.spec.ts +0 -215
- package/src/updateError.spec.ts +0 -144
- package/src/wrap.progress.spec.ts +0 -232
- package/src/wrap.spec.ts +0 -262
|
@@ -1,732 +0,0 @@
|
|
|
1
|
-
import { isUUIDv7 } from "@hot-updater/core";
|
|
2
|
-
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
3
|
-
|
|
4
|
-
import type {
|
|
5
|
-
AppReadyResult,
|
|
6
|
-
BundleDownloadedInfo,
|
|
7
|
-
HotUpdaterClientHooks,
|
|
8
|
-
UpdateCheckResult,
|
|
9
|
-
UpdateError,
|
|
10
|
-
} from "../../clientPlugin";
|
|
11
|
-
import type { InsightsOptions, InsightsUser } from "./index";
|
|
12
|
-
import type { InsightsEventBody } from "./sender";
|
|
13
|
-
|
|
14
|
-
vi.mock("react-native", () => ({
|
|
15
|
-
Platform: { OS: "ios" },
|
|
16
|
-
}));
|
|
17
|
-
|
|
18
|
-
const native = vi.hoisted(() => {
|
|
19
|
-
Reflect.set(globalThis, "HotUpdater", { SDK_VERSION: "test-sdk-version" });
|
|
20
|
-
const storage = new Map<string, string>();
|
|
21
|
-
return {
|
|
22
|
-
storage,
|
|
23
|
-
getAppVersion: vi.fn<() => string | null>(() => "1.0.0"),
|
|
24
|
-
getBundleId: vi.fn(() => "bundle-a"),
|
|
25
|
-
getChannel: vi.fn(() => "production"),
|
|
26
|
-
getCohort: vi.fn(() => "123"),
|
|
27
|
-
getFingerprintHash: vi.fn<() => string | null>(() => "fingerprint-hash"),
|
|
28
|
-
getInstallId: vi.fn(() => "install-id"),
|
|
29
|
-
getStorageItem: vi.fn((key: string) => storage.get(key) ?? null),
|
|
30
|
-
setStorageItem: vi.fn((key: string, value: string | null) => {
|
|
31
|
-
if (value === null) storage.delete(key);
|
|
32
|
-
else storage.set(key, value);
|
|
33
|
-
}),
|
|
34
|
-
};
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
vi.mock("../../native", () => native);
|
|
38
|
-
|
|
39
|
-
const DAY_MS = 86_400_000;
|
|
40
|
-
const MORNING = Date.UTC(2026, 8, 30, 9);
|
|
41
|
-
|
|
42
|
-
let responses: (number | (() => Response))[] = [];
|
|
43
|
-
const fetchMock = vi.fn(async (_url: string, _init?: RequestInit) => {
|
|
44
|
-
const next = responses.shift() ?? 204;
|
|
45
|
-
return typeof next === "number"
|
|
46
|
-
? new Response(null, { status: next })
|
|
47
|
-
: next();
|
|
48
|
-
});
|
|
49
|
-
const sentEvents = () =>
|
|
50
|
-
fetchMock.mock.calls.map(
|
|
51
|
-
([, init]) => JSON.parse(String(init?.body)) as InsightsEventBody,
|
|
52
|
-
);
|
|
53
|
-
const sentTypes = () => sentEvents().map(({ type }) => type);
|
|
54
|
-
|
|
55
|
-
/** A server without Insights mounts no `/events`. */
|
|
56
|
-
const disabledResponse = () => new Response(null, { status: 404 });
|
|
57
|
-
|
|
58
|
-
/** Starts a JavaScript runtime with the plugin; native storage persists across launches. */
|
|
59
|
-
const launch = async (
|
|
60
|
-
options: InsightsOptions = {},
|
|
61
|
-
beforeInit?: (
|
|
62
|
-
plugin: ReturnType<(typeof import("./index"))["insights"]>,
|
|
63
|
-
) => void,
|
|
64
|
-
) => {
|
|
65
|
-
vi.resetModules();
|
|
66
|
-
const [{ configurePlugins, emitPluginHook }, { insights }] =
|
|
67
|
-
await Promise.all([import("../../pluginHost"), import("./index")]);
|
|
68
|
-
const plugin = insights(options);
|
|
69
|
-
beforeInit?.(plugin);
|
|
70
|
-
configurePlugins([plugin], {
|
|
71
|
-
baseURL: "https://updates.example.com/hot-updater",
|
|
72
|
-
requestHeaders: { "x-api-key": "client-key" },
|
|
73
|
-
});
|
|
74
|
-
const emit = <K extends keyof HotUpdaterClientHooks>(
|
|
75
|
-
name: K,
|
|
76
|
-
payload: Parameters<NonNullable<HotUpdaterClientHooks[K]>>[0],
|
|
77
|
-
) => {
|
|
78
|
-
emitPluginHook(name, () => payload);
|
|
79
|
-
};
|
|
80
|
-
return {
|
|
81
|
-
plugin,
|
|
82
|
-
appReady: (result: AppReadyResult) => emit("onAppReady", result),
|
|
83
|
-
updateCheck: (result: UpdateCheckResult) => emit("onUpdateCheck", result),
|
|
84
|
-
downloaded: (info: BundleDownloadedInfo) =>
|
|
85
|
-
emit("onBundleDownloaded", info),
|
|
86
|
-
updateError: (error: UpdateError) => emit("onUpdateError", error),
|
|
87
|
-
};
|
|
88
|
-
};
|
|
89
|
-
|
|
90
|
-
type UnchangedLaunch = Extract<AppReadyResult, { status: "UNCHANGED" }>;
|
|
91
|
-
type TransitionLaunch = Exclude<AppReadyResult, UnchangedLaunch>;
|
|
92
|
-
|
|
93
|
-
const unchangedLaunch = (
|
|
94
|
-
overrides: Partial<UnchangedLaunch> = {},
|
|
95
|
-
): UnchangedLaunch => ({
|
|
96
|
-
status: "UNCHANGED",
|
|
97
|
-
channel: "production",
|
|
98
|
-
bundleId: "bundle-a",
|
|
99
|
-
releaseId: "release-a",
|
|
100
|
-
previousProcessExit: null,
|
|
101
|
-
...overrides,
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
const appliedLaunch: TransitionLaunch = {
|
|
105
|
-
status: "UPDATE_APPLIED",
|
|
106
|
-
channel: "production",
|
|
107
|
-
fromBundleId: "bundle-a",
|
|
108
|
-
fromReleaseId: "release-a",
|
|
109
|
-
toBundleId: "bundle-b",
|
|
110
|
-
toReleaseId: "release-b",
|
|
111
|
-
updateStrategy: "appVersion",
|
|
112
|
-
previousProcessExit: null,
|
|
113
|
-
};
|
|
114
|
-
|
|
115
|
-
const download: BundleDownloadedInfo = {
|
|
116
|
-
channel: "production",
|
|
117
|
-
fromBundleId: "bundle-a",
|
|
118
|
-
fromReleaseId: "release-a",
|
|
119
|
-
toBundleId: "bundle-b",
|
|
120
|
-
toReleaseId: "release-b",
|
|
121
|
-
updateStrategy: "appVersion",
|
|
122
|
-
delivery: "manifest",
|
|
123
|
-
patchFallback: false,
|
|
124
|
-
};
|
|
125
|
-
|
|
126
|
-
const downloadFailure = (
|
|
127
|
-
overrides: Partial<UpdateError> = {},
|
|
128
|
-
): UpdateError => ({
|
|
129
|
-
stage: "download",
|
|
130
|
-
reason: "hash_mismatch",
|
|
131
|
-
targetBundleId: "bundle-b",
|
|
132
|
-
targetReleaseId: "release-b",
|
|
133
|
-
channel: "production",
|
|
134
|
-
bundleId: "bundle-a",
|
|
135
|
-
releaseId: "release-a",
|
|
136
|
-
updateStrategy: "fingerprint",
|
|
137
|
-
cause: new Error("hash mismatch"),
|
|
138
|
-
...overrides,
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
const flush = () => vi.runAllTimersAsync();
|
|
142
|
-
|
|
143
|
-
describe("insights() client plugin", () => {
|
|
144
|
-
beforeEach(() => {
|
|
145
|
-
vi.useFakeTimers({ now: MORNING });
|
|
146
|
-
vi.spyOn(Math, "random").mockReturnValue(0.5);
|
|
147
|
-
vi.stubGlobal("fetch", fetchMock);
|
|
148
|
-
vi.stubGlobal("__DEV__", false);
|
|
149
|
-
fetchMock.mockClear();
|
|
150
|
-
responses = [];
|
|
151
|
-
native.storage.clear();
|
|
152
|
-
native.getAppVersion.mockReturnValue("1.0.0");
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
afterEach(() => {
|
|
156
|
-
vi.useRealTimers();
|
|
157
|
-
vi.unstubAllGlobals();
|
|
158
|
-
vi.restoreAllMocks();
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
it("posts a launch as UNCHANGED with the app's identity", async () => {
|
|
162
|
-
const app = await launch();
|
|
163
|
-
|
|
164
|
-
app.appReady(unchangedLaunch());
|
|
165
|
-
await flush();
|
|
166
|
-
|
|
167
|
-
expect(fetchMock).toHaveBeenCalledOnce();
|
|
168
|
-
const [url, init] = fetchMock.mock.calls[0]!;
|
|
169
|
-
expect(url).toBe("https://updates.example.com/hot-updater/events");
|
|
170
|
-
expect(new Headers(init?.headers).get("x-api-key")).toBe("client-key");
|
|
171
|
-
const [event] = sentEvents();
|
|
172
|
-
expect(isUUIDv7(event?.eventId)).toBe(true);
|
|
173
|
-
expect(event).toEqual({
|
|
174
|
-
appVersion: "1.0.0",
|
|
175
|
-
channel: "production",
|
|
176
|
-
cohort: "123",
|
|
177
|
-
eventId: event?.eventId,
|
|
178
|
-
fingerprintHash: "fingerprint-hash",
|
|
179
|
-
fromBundleId: null,
|
|
180
|
-
fromReleaseId: null,
|
|
181
|
-
installId: "install-id",
|
|
182
|
-
platform: "ios",
|
|
183
|
-
sdkVersion: "test-sdk-version",
|
|
184
|
-
toBundleId: "bundle-a",
|
|
185
|
-
toReleaseId: "release-a",
|
|
186
|
-
type: "UNCHANGED",
|
|
187
|
-
updateStrategy: null,
|
|
188
|
-
});
|
|
189
|
-
});
|
|
190
|
-
|
|
191
|
-
describe("daily launch reports", () => {
|
|
192
|
-
it("sends UNCHANGED at most once per UTC day across launches", async () => {
|
|
193
|
-
(await launch()).appReady(unchangedLaunch());
|
|
194
|
-
await flush();
|
|
195
|
-
|
|
196
|
-
vi.setSystemTime(Date.UTC(2026, 8, 30, 23, 59));
|
|
197
|
-
(await launch()).appReady(unchangedLaunch());
|
|
198
|
-
await flush();
|
|
199
|
-
expect(fetchMock).toHaveBeenCalledOnce();
|
|
200
|
-
|
|
201
|
-
vi.setSystemTime(Date.UTC(2026, 9, 1, 0, 1));
|
|
202
|
-
(await launch()).appReady(unchangedLaunch());
|
|
203
|
-
await flush();
|
|
204
|
-
expect(sentTypes()).toEqual(["UNCHANGED", "UNCHANGED"]);
|
|
205
|
-
});
|
|
206
|
-
|
|
207
|
-
it.each([
|
|
208
|
-
{ label: "channel", change: { channel: "beta" } },
|
|
209
|
-
{ label: "bundle", change: { bundleId: "bundle-c" } },
|
|
210
|
-
{ label: "Release", change: { releaseId: "release-c" } },
|
|
211
|
-
])(
|
|
212
|
-
"reports a launch again the same day when its $label changes",
|
|
213
|
-
async ({ change }) => {
|
|
214
|
-
(await launch()).appReady(unchangedLaunch());
|
|
215
|
-
await flush();
|
|
216
|
-
|
|
217
|
-
(await launch()).appReady(unchangedLaunch(change));
|
|
218
|
-
await flush();
|
|
219
|
-
|
|
220
|
-
expect(sentTypes()).toEqual(["UNCHANGED", "UNCHANGED"]);
|
|
221
|
-
},
|
|
222
|
-
);
|
|
223
|
-
|
|
224
|
-
it("reports a launch again the same day when the app version or user changes", async () => {
|
|
225
|
-
(await launch()).appReady(unchangedLaunch());
|
|
226
|
-
await flush();
|
|
227
|
-
|
|
228
|
-
native.getAppVersion.mockReturnValue("1.0.1");
|
|
229
|
-
(await launch()).appReady(unchangedLaunch());
|
|
230
|
-
await flush();
|
|
231
|
-
|
|
232
|
-
const app = await launch();
|
|
233
|
-
app.plugin.setUser({ userId: "user-1" });
|
|
234
|
-
app.appReady(unchangedLaunch());
|
|
235
|
-
await flush();
|
|
236
|
-
|
|
237
|
-
expect(
|
|
238
|
-
sentEvents().map(({ appVersion, userId }) => [appVersion, userId]),
|
|
239
|
-
).toEqual([
|
|
240
|
-
["1.0.0", undefined],
|
|
241
|
-
["1.0.1", undefined],
|
|
242
|
-
["1.0.1", "user-1"],
|
|
243
|
-
]);
|
|
244
|
-
});
|
|
245
|
-
|
|
246
|
-
it("counts a delivered apply as the day's report of its bundle", async () => {
|
|
247
|
-
(await launch()).appReady(appliedLaunch);
|
|
248
|
-
await flush();
|
|
249
|
-
|
|
250
|
-
(await launch()).appReady(
|
|
251
|
-
unchangedLaunch({ bundleId: "bundle-b", releaseId: "release-b" }),
|
|
252
|
-
);
|
|
253
|
-
await flush();
|
|
254
|
-
|
|
255
|
-
expect(sentTypes()).toEqual(["UPDATE_APPLIED"]);
|
|
256
|
-
});
|
|
257
|
-
|
|
258
|
-
it("reports the next launch after a delivered download or failure", async () => {
|
|
259
|
-
const first = await launch();
|
|
260
|
-
first.appReady(unchangedLaunch());
|
|
261
|
-
await flush();
|
|
262
|
-
first.downloaded(download);
|
|
263
|
-
await flush();
|
|
264
|
-
|
|
265
|
-
const second = await launch();
|
|
266
|
-
second.appReady(unchangedLaunch());
|
|
267
|
-
await flush();
|
|
268
|
-
second.updateError(downloadFailure());
|
|
269
|
-
await flush();
|
|
270
|
-
|
|
271
|
-
(await launch()).appReady(unchangedLaunch());
|
|
272
|
-
await flush();
|
|
273
|
-
|
|
274
|
-
expect(sentTypes()).toEqual([
|
|
275
|
-
"UNCHANGED",
|
|
276
|
-
"UPDATE_DOWNLOADED",
|
|
277
|
-
"UNCHANGED",
|
|
278
|
-
"UPDATE_FAILED",
|
|
279
|
-
"UNCHANGED",
|
|
280
|
-
]);
|
|
281
|
-
});
|
|
282
|
-
|
|
283
|
-
it("reports the launch again when the server did not store it", async () => {
|
|
284
|
-
responses = [400];
|
|
285
|
-
vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
286
|
-
(await launch()).appReady(unchangedLaunch());
|
|
287
|
-
await flush();
|
|
288
|
-
|
|
289
|
-
(await launch()).appReady(unchangedLaunch());
|
|
290
|
-
await flush();
|
|
291
|
-
|
|
292
|
-
expect(sentTypes()).toEqual(["UNCHANGED", "UNCHANGED"]);
|
|
293
|
-
});
|
|
294
|
-
|
|
295
|
-
it("always sends downloads, applies, and recoveries", async () => {
|
|
296
|
-
const first = await launch();
|
|
297
|
-
first.appReady(unchangedLaunch());
|
|
298
|
-
first.downloaded(download);
|
|
299
|
-
await flush();
|
|
300
|
-
|
|
301
|
-
(await launch()).appReady(appliedLaunch);
|
|
302
|
-
(await launch()).appReady({
|
|
303
|
-
...appliedLaunch,
|
|
304
|
-
status: "RECOVERED",
|
|
305
|
-
fromBundleId: "bundle-b",
|
|
306
|
-
toBundleId: "bundle-a",
|
|
307
|
-
});
|
|
308
|
-
await flush();
|
|
309
|
-
|
|
310
|
-
expect(sentTypes()).toEqual([
|
|
311
|
-
"UNCHANGED",
|
|
312
|
-
"UPDATE_DOWNLOADED",
|
|
313
|
-
"UPDATE_APPLIED",
|
|
314
|
-
"RECOVERED",
|
|
315
|
-
]);
|
|
316
|
-
});
|
|
317
|
-
|
|
318
|
-
it("sends one report when the launch and a no-update check both report", async () => {
|
|
319
|
-
const app = await launch();
|
|
320
|
-
|
|
321
|
-
app.appReady(unchangedLaunch());
|
|
322
|
-
app.updateCheck({
|
|
323
|
-
status: "UNCHANGED",
|
|
324
|
-
channel: "production",
|
|
325
|
-
bundleId: "bundle-a",
|
|
326
|
-
releaseId: "release-a",
|
|
327
|
-
previousReleaseId: "release-a",
|
|
328
|
-
});
|
|
329
|
-
await flush();
|
|
330
|
-
|
|
331
|
-
expect(sentTypes()).toEqual(["UNCHANGED"]);
|
|
332
|
-
});
|
|
333
|
-
|
|
334
|
-
it("reports a same-bundle Release adoption with the Release it replaced", async () => {
|
|
335
|
-
const app = await launch();
|
|
336
|
-
app.appReady(unchangedLaunch());
|
|
337
|
-
await flush();
|
|
338
|
-
|
|
339
|
-
app.updateCheck({
|
|
340
|
-
status: "UNCHANGED",
|
|
341
|
-
channel: "production",
|
|
342
|
-
bundleId: "bundle-a",
|
|
343
|
-
releaseId: "release-a2",
|
|
344
|
-
previousReleaseId: "release-a",
|
|
345
|
-
});
|
|
346
|
-
await flush();
|
|
347
|
-
|
|
348
|
-
expect(sentEvents()[1]).toMatchObject({
|
|
349
|
-
fromReleaseId: "release-a",
|
|
350
|
-
toBundleId: "bundle-a",
|
|
351
|
-
toReleaseId: "release-a2",
|
|
352
|
-
type: "UNCHANGED",
|
|
353
|
-
});
|
|
354
|
-
});
|
|
355
|
-
|
|
356
|
-
it("sends no launch report after a download in the same runtime", async () => {
|
|
357
|
-
const app = await launch();
|
|
358
|
-
|
|
359
|
-
app.downloaded(download);
|
|
360
|
-
app.downloaded(download);
|
|
361
|
-
app.updateCheck({
|
|
362
|
-
status: "UNCHANGED",
|
|
363
|
-
channel: "production",
|
|
364
|
-
bundleId: "bundle-b",
|
|
365
|
-
releaseId: "release-b",
|
|
366
|
-
previousReleaseId: "release-b",
|
|
367
|
-
});
|
|
368
|
-
await flush();
|
|
369
|
-
|
|
370
|
-
expect(sentTypes()).toEqual(["UPDATE_DOWNLOADED"]);
|
|
371
|
-
});
|
|
372
|
-
});
|
|
373
|
-
|
|
374
|
-
describe("when the server runs without Insights", () => {
|
|
375
|
-
it("sends nothing more in this runtime after a 404", async () => {
|
|
376
|
-
responses = [disabledResponse];
|
|
377
|
-
const app = await launch();
|
|
378
|
-
|
|
379
|
-
app.appReady(unchangedLaunch());
|
|
380
|
-
await flush();
|
|
381
|
-
app.downloaded(download);
|
|
382
|
-
await flush();
|
|
383
|
-
vi.setSystemTime(MORNING + 2 * DAY_MS);
|
|
384
|
-
app.updateError(downloadFailure());
|
|
385
|
-
await flush();
|
|
386
|
-
|
|
387
|
-
expect(sentTypes()).toEqual(["UNCHANGED"]);
|
|
388
|
-
});
|
|
389
|
-
|
|
390
|
-
it("pauses later launches for 24 hours, then probes again", async () => {
|
|
391
|
-
responses = [disabledResponse];
|
|
392
|
-
(await launch()).appReady(unchangedLaunch());
|
|
393
|
-
await flush();
|
|
394
|
-
|
|
395
|
-
vi.setSystemTime(MORNING + DAY_MS - 1);
|
|
396
|
-
(await launch()).appReady(appliedLaunch);
|
|
397
|
-
await flush();
|
|
398
|
-
expect(fetchMock).toHaveBeenCalledOnce();
|
|
399
|
-
|
|
400
|
-
vi.setSystemTime(MORNING + DAY_MS);
|
|
401
|
-
(await launch()).appReady(appliedLaunch);
|
|
402
|
-
await flush();
|
|
403
|
-
expect(sentTypes()).toEqual(["UNCHANGED", "UPDATE_APPLIED"]);
|
|
404
|
-
|
|
405
|
-
// The probe was stored, so Insights is on again.
|
|
406
|
-
(await launch()).appReady({ ...appliedLaunch, toBundleId: "bundle-c" });
|
|
407
|
-
await flush();
|
|
408
|
-
expect(sentTypes()).toEqual([
|
|
409
|
-
"UNCHANGED",
|
|
410
|
-
"UPDATE_APPLIED",
|
|
411
|
-
"UPDATE_APPLIED",
|
|
412
|
-
]);
|
|
413
|
-
});
|
|
414
|
-
|
|
415
|
-
it("ends a pause that starts after now, from a clock that went back", async () => {
|
|
416
|
-
native.storage.set(
|
|
417
|
-
"plugins/insights/pausedAt",
|
|
418
|
-
String(MORNING + 60 * 60 * 1000),
|
|
419
|
-
);
|
|
420
|
-
|
|
421
|
-
(await launch()).appReady(unchangedLaunch());
|
|
422
|
-
await flush();
|
|
423
|
-
|
|
424
|
-
expect(sentTypes()).toEqual(["UNCHANGED"]);
|
|
425
|
-
});
|
|
426
|
-
});
|
|
427
|
-
|
|
428
|
-
describe("update failures", () => {
|
|
429
|
-
it("reports a failure once per UTC day per stage, reason, and target", async () => {
|
|
430
|
-
const app = await launch();
|
|
431
|
-
|
|
432
|
-
app.updateError(downloadFailure({ httpStatus: undefined }));
|
|
433
|
-
app.updateError(downloadFailure());
|
|
434
|
-
await flush();
|
|
435
|
-
(await launch()).updateError(downloadFailure());
|
|
436
|
-
await flush();
|
|
437
|
-
|
|
438
|
-
expect(fetchMock).toHaveBeenCalledOnce();
|
|
439
|
-
const [event] = sentEvents();
|
|
440
|
-
expect(event).toMatchObject({
|
|
441
|
-
fromBundleId: "bundle-a",
|
|
442
|
-
fromReleaseId: "release-a",
|
|
443
|
-
metadata: { failure: { reason: "hash_mismatch", stage: "download" } },
|
|
444
|
-
toBundleId: "bundle-b",
|
|
445
|
-
toReleaseId: "release-b",
|
|
446
|
-
type: "UPDATE_FAILED",
|
|
447
|
-
updateStrategy: "fingerprint",
|
|
448
|
-
});
|
|
449
|
-
expect(Object.keys(event?.metadata?.failure ?? {}).sort()).toEqual([
|
|
450
|
-
"reason",
|
|
451
|
-
"stage",
|
|
452
|
-
]);
|
|
453
|
-
// A UUIDv7 led by the UTC day, the same on every device for one failure.
|
|
454
|
-
expect(isUUIDv7(event?.eventId)).toBe(true);
|
|
455
|
-
expect(parseInt(event!.eventId.replace(/-/g, "").slice(0, 12), 16)).toBe(
|
|
456
|
-
Date.UTC(2026, 8, 30),
|
|
457
|
-
);
|
|
458
|
-
|
|
459
|
-
(await launch()).updateError(downloadFailure({ reason: "signature" }));
|
|
460
|
-
vi.setSystemTime(MORNING + DAY_MS);
|
|
461
|
-
(await launch()).updateError(downloadFailure());
|
|
462
|
-
await flush();
|
|
463
|
-
|
|
464
|
-
const ids = sentEvents().map(({ eventId }) => eventId);
|
|
465
|
-
expect(
|
|
466
|
-
sentEvents().map(({ metadata }) => metadata?.failure?.reason),
|
|
467
|
-
).toEqual(["hash_mismatch", "signature", "hash_mismatch"]);
|
|
468
|
-
expect(new Set(ids).size).toBe(3);
|
|
469
|
-
});
|
|
470
|
-
|
|
471
|
-
it("gives a failure that was not delivered the same id when it happens again", async () => {
|
|
472
|
-
responses = [500, 500, 500];
|
|
473
|
-
vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
474
|
-
(await launch()).updateError(downloadFailure());
|
|
475
|
-
await flush();
|
|
476
|
-
|
|
477
|
-
(await launch()).updateError(downloadFailure());
|
|
478
|
-
await flush();
|
|
479
|
-
|
|
480
|
-
const ids = sentEvents().map(({ eventId }) => eventId);
|
|
481
|
-
expect(ids).toHaveLength(4);
|
|
482
|
-
expect(new Set(ids).size).toBe(1);
|
|
483
|
-
});
|
|
484
|
-
|
|
485
|
-
it("skips check failures while offline and reports the others against the running bundle", async () => {
|
|
486
|
-
const app = await launch();
|
|
487
|
-
const checkFailure = {
|
|
488
|
-
stage: "check",
|
|
489
|
-
channel: "production",
|
|
490
|
-
bundleId: "bundle-a",
|
|
491
|
-
releaseId: "release-a",
|
|
492
|
-
updateStrategy: "appVersion",
|
|
493
|
-
cause: new Error("check failed"),
|
|
494
|
-
} as const;
|
|
495
|
-
|
|
496
|
-
app.updateError({ ...checkFailure, reason: "network" });
|
|
497
|
-
app.updateError({ ...checkFailure, reason: "http", httpStatus: 503 });
|
|
498
|
-
await flush();
|
|
499
|
-
|
|
500
|
-
expect(sentEvents()).toEqual([
|
|
501
|
-
expect.objectContaining({
|
|
502
|
-
fromBundleId: "bundle-a",
|
|
503
|
-
metadata: {
|
|
504
|
-
failure: { httpStatus: 503, reason: "http", stage: "check" },
|
|
505
|
-
},
|
|
506
|
-
toBundleId: "bundle-a",
|
|
507
|
-
toReleaseId: null,
|
|
508
|
-
type: "UPDATE_FAILED",
|
|
509
|
-
}),
|
|
510
|
-
]);
|
|
511
|
-
});
|
|
512
|
-
|
|
513
|
-
it("drops a failure an older server refuses, without retrying, warning, or pausing", async () => {
|
|
514
|
-
responses = [400];
|
|
515
|
-
const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
516
|
-
const app = await launch();
|
|
517
|
-
|
|
518
|
-
app.updateError(downloadFailure());
|
|
519
|
-
await flush();
|
|
520
|
-
app.updateError(downloadFailure());
|
|
521
|
-
app.appReady(unchangedLaunch());
|
|
522
|
-
await flush();
|
|
523
|
-
|
|
524
|
-
expect(sentTypes()).toEqual(["UPDATE_FAILED", "UNCHANGED"]);
|
|
525
|
-
expect(warn).not.toHaveBeenCalled();
|
|
526
|
-
});
|
|
527
|
-
});
|
|
528
|
-
|
|
529
|
-
describe("event details", () => {
|
|
530
|
-
it("says how a download arrived, and when a patch fell back", async () => {
|
|
531
|
-
const app = await launch();
|
|
532
|
-
|
|
533
|
-
app.downloaded({ ...download, delivery: "patch", patchFallback: false });
|
|
534
|
-
app.downloaded({
|
|
535
|
-
...download,
|
|
536
|
-
toBundleId: "bundle-c",
|
|
537
|
-
delivery: "archive",
|
|
538
|
-
patchFallback: true,
|
|
539
|
-
});
|
|
540
|
-
await flush();
|
|
541
|
-
|
|
542
|
-
expect(sentEvents().map(({ metadata }) => metadata)).toEqual([
|
|
543
|
-
{ delivery: "patch" },
|
|
544
|
-
{ delivery: "archive", patchFallback: true },
|
|
545
|
-
]);
|
|
546
|
-
});
|
|
547
|
-
|
|
548
|
-
it("says why the previous process exited on a recovery only", async () => {
|
|
549
|
-
const app = await launch();
|
|
550
|
-
|
|
551
|
-
app.appReady({
|
|
552
|
-
...appliedLaunch,
|
|
553
|
-
status: "RECOVERED",
|
|
554
|
-
fromBundleId: "bundle-b",
|
|
555
|
-
toBundleId: "bundle-a",
|
|
556
|
-
previousProcessExit: "CRASH",
|
|
557
|
-
});
|
|
558
|
-
await flush();
|
|
559
|
-
(await launch()).appReady({
|
|
560
|
-
...appliedLaunch,
|
|
561
|
-
toBundleId: "bundle-c",
|
|
562
|
-
previousProcessExit: "USER_REQUESTED",
|
|
563
|
-
});
|
|
564
|
-
await flush();
|
|
565
|
-
|
|
566
|
-
expect(
|
|
567
|
-
sentEvents().map(({ type, metadata }) => [type, metadata]),
|
|
568
|
-
).toEqual([
|
|
569
|
-
["RECOVERED", { previousProcessExit: "CRASH" }],
|
|
570
|
-
["UPDATE_APPLIED", undefined],
|
|
571
|
-
]);
|
|
572
|
-
});
|
|
573
|
-
|
|
574
|
-
it("sends every failure detail it has", async () => {
|
|
575
|
-
const app = await launch();
|
|
576
|
-
|
|
577
|
-
app.updateError(
|
|
578
|
-
downloadFailure({
|
|
579
|
-
reason: "http",
|
|
580
|
-
resource: "archive",
|
|
581
|
-
httpStatus: 403,
|
|
582
|
-
originCode: "ExpiredToken",
|
|
583
|
-
previousProcessExit: "ANR",
|
|
584
|
-
}),
|
|
585
|
-
);
|
|
586
|
-
app.updateError(
|
|
587
|
-
downloadFailure({
|
|
588
|
-
reason: "network",
|
|
589
|
-
resource: "file",
|
|
590
|
-
transport: "tls",
|
|
591
|
-
}),
|
|
592
|
-
);
|
|
593
|
-
await flush();
|
|
594
|
-
|
|
595
|
-
expect(sentEvents().map(({ metadata }) => metadata?.failure)).toEqual([
|
|
596
|
-
{
|
|
597
|
-
stage: "download",
|
|
598
|
-
reason: "http",
|
|
599
|
-
resource: "archive",
|
|
600
|
-
httpStatus: 403,
|
|
601
|
-
originCode: "ExpiredToken",
|
|
602
|
-
previousProcessExit: "ANR",
|
|
603
|
-
},
|
|
604
|
-
{
|
|
605
|
-
stage: "download",
|
|
606
|
-
reason: "network",
|
|
607
|
-
resource: "file",
|
|
608
|
-
transport: "tls",
|
|
609
|
-
},
|
|
610
|
-
]);
|
|
611
|
-
});
|
|
612
|
-
});
|
|
613
|
-
|
|
614
|
-
describe("a success for a failed target", () => {
|
|
615
|
-
it("drops a queued failure for the target a download then staged", async () => {
|
|
616
|
-
responses = [503];
|
|
617
|
-
const app = await launch();
|
|
618
|
-
|
|
619
|
-
app.appReady(unchangedLaunch());
|
|
620
|
-
app.updateError(downloadFailure());
|
|
621
|
-
app.downloaded(download);
|
|
622
|
-
await flush();
|
|
623
|
-
|
|
624
|
-
expect(sentTypes()).toEqual([
|
|
625
|
-
"UNCHANGED",
|
|
626
|
-
"UNCHANGED",
|
|
627
|
-
"UPDATE_DOWNLOADED",
|
|
628
|
-
]);
|
|
629
|
-
});
|
|
630
|
-
|
|
631
|
-
it("drops a failure waiting out a backoff once its target applies", async () => {
|
|
632
|
-
responses = [503];
|
|
633
|
-
const app = await launch();
|
|
634
|
-
|
|
635
|
-
app.updateError(downloadFailure());
|
|
636
|
-
await Promise.resolve();
|
|
637
|
-
await vi.advanceTimersByTimeAsync(0);
|
|
638
|
-
app.appReady(appliedLaunch);
|
|
639
|
-
await flush();
|
|
640
|
-
|
|
641
|
-
expect(sentTypes()).toEqual(["UPDATE_FAILED", "UPDATE_APPLIED"]);
|
|
642
|
-
});
|
|
643
|
-
|
|
644
|
-
it("keeps a failure for another target", async () => {
|
|
645
|
-
const app = await launch();
|
|
646
|
-
|
|
647
|
-
app.updateError(downloadFailure({ targetBundleId: "bundle-c" }));
|
|
648
|
-
app.downloaded(download);
|
|
649
|
-
await flush();
|
|
650
|
-
|
|
651
|
-
expect(sentTypes()).toEqual(["UPDATE_FAILED", "UPDATE_DOWNLOADED"]);
|
|
652
|
-
});
|
|
653
|
-
});
|
|
654
|
-
|
|
655
|
-
describe("user", () => {
|
|
656
|
-
it("attaches a user set before init and keeps it across launches", async () => {
|
|
657
|
-
(await launch({}, (plugin) => plugin.setUser({ userId: 42 }))).appReady(
|
|
658
|
-
unchangedLaunch(),
|
|
659
|
-
);
|
|
660
|
-
await flush();
|
|
661
|
-
|
|
662
|
-
(await launch()).appReady(appliedLaunch);
|
|
663
|
-
await flush();
|
|
664
|
-
|
|
665
|
-
const app = await launch();
|
|
666
|
-
app.plugin.setUser(null);
|
|
667
|
-
app.appReady(unchangedLaunch({ bundleId: "bundle-c" }));
|
|
668
|
-
await flush();
|
|
669
|
-
|
|
670
|
-
expect(sentEvents().map(({ userId }) => userId)).toEqual([
|
|
671
|
-
"42",
|
|
672
|
-
"42",
|
|
673
|
-
undefined,
|
|
674
|
-
]);
|
|
675
|
-
expect(sentEvents().every((event) => !("username" in event))).toBe(true);
|
|
676
|
-
});
|
|
677
|
-
|
|
678
|
-
it("takes only a user id", () => {
|
|
679
|
-
const user: InsightsUser = { userId: "user-1" };
|
|
680
|
-
// @ts-expect-error Insights keeps no username.
|
|
681
|
-
const withName: InsightsUser = { userId: "user-1", username: "Alex" };
|
|
682
|
-
expect([user, withName]).toHaveLength(2);
|
|
683
|
-
});
|
|
684
|
-
});
|
|
685
|
-
|
|
686
|
-
it.each([
|
|
687
|
-
{ debug: undefined, sends: false },
|
|
688
|
-
{ debug: true, sends: true },
|
|
689
|
-
])(
|
|
690
|
-
"sends from a debug build only with debug: true ($debug)",
|
|
691
|
-
async ({ debug, sends }) => {
|
|
692
|
-
vi.stubGlobal("__DEV__", true);
|
|
693
|
-
const app = await launch(debug === undefined ? {} : { debug });
|
|
694
|
-
|
|
695
|
-
app.appReady(unchangedLaunch());
|
|
696
|
-
await flush();
|
|
697
|
-
|
|
698
|
-
expect(fetchMock).toHaveBeenCalledTimes(sends ? 1 : 0);
|
|
699
|
-
},
|
|
700
|
-
);
|
|
701
|
-
|
|
702
|
-
it("reports a failed install id read as a plugin error, not an update failure", async () => {
|
|
703
|
-
native.getInstallId.mockImplementationOnce(() => {
|
|
704
|
-
throw new Error("native install id unavailable");
|
|
705
|
-
});
|
|
706
|
-
const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
707
|
-
const app = await launch();
|
|
708
|
-
|
|
709
|
-
app.appReady(unchangedLaunch());
|
|
710
|
-
await flush();
|
|
711
|
-
|
|
712
|
-
expect(fetchMock).not.toHaveBeenCalled();
|
|
713
|
-
expect(warn).toHaveBeenCalledWith(
|
|
714
|
-
'[HotUpdater] Plugin "insights" failed in onAppReady',
|
|
715
|
-
expect.objectContaining({ message: "native install id unavailable" }),
|
|
716
|
-
);
|
|
717
|
-
});
|
|
718
|
-
|
|
719
|
-
it("skips a report without a native app version", async () => {
|
|
720
|
-
native.getAppVersion.mockReturnValue(null);
|
|
721
|
-
const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
722
|
-
const app = await launch();
|
|
723
|
-
|
|
724
|
-
app.appReady(unchangedLaunch());
|
|
725
|
-
await flush();
|
|
726
|
-
|
|
727
|
-
expect(fetchMock).not.toHaveBeenCalled();
|
|
728
|
-
expect(warn).toHaveBeenCalledWith(
|
|
729
|
-
"[HotUpdater] Insights needs the native app version; the UNCHANGED event was not sent.",
|
|
730
|
-
);
|
|
731
|
-
});
|
|
732
|
-
});
|