@hot-updater/test-utils 1.0.0-rc.27 → 1.0.0-rc.29
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/dist/index.mjs +281 -3
- package/dist/react-native.cjs +2 -1
- package/dist/react-native.d.cts +2 -1
- package/dist/react-native.d.mts +2 -1
- package/dist/react-native.mjs +2 -1
- package/package.json +9 -9
package/dist/index.mjs
CHANGED
|
@@ -3285,6 +3285,27 @@ const READ_BUDGETS = [
|
|
|
3285
3285
|
installations: 24
|
|
3286
3286
|
}])
|
|
3287
3287
|
}),
|
|
3288
|
+
budget({
|
|
3289
|
+
api: "daily bundle share: only scoped aggregate shards in the requested days",
|
|
3290
|
+
read: ({ insights }) => insights.getDistributionHistory({
|
|
3291
|
+
channel: "production",
|
|
3292
|
+
platform: "ios",
|
|
3293
|
+
timeRange: {
|
|
3294
|
+
start: T0,
|
|
3295
|
+
end: T0 + 3 * DAY
|
|
3296
|
+
}
|
|
3297
|
+
}),
|
|
3298
|
+
adapter: reads(0, 0, 1, 15),
|
|
3299
|
+
engine: {
|
|
3300
|
+
calls: 1,
|
|
3301
|
+
rows: 2
|
|
3302
|
+
},
|
|
3303
|
+
check: ({ points }) => expect(points.map(({ bundles }) => bundles.reduce((n, row) => n + row.installations, 0))).toEqual([
|
|
3304
|
+
24,
|
|
3305
|
+
0,
|
|
3306
|
+
1
|
|
3307
|
+
])
|
|
3308
|
+
}),
|
|
3288
3309
|
budget({
|
|
3289
3310
|
api: "update failures of a release over a window: buckets × shards, all used",
|
|
3290
3311
|
read: ({ insights }) => insights.getUpdateFailures({
|
|
@@ -3388,10 +3409,10 @@ const READ_BUDGETS = [
|
|
|
3388
3409
|
install_id: "install-2",
|
|
3389
3410
|
received_at_ms: T0 + 3 * HOUR
|
|
3390
3411
|
})),
|
|
3391
|
-
adapter: reads(
|
|
3412
|
+
adapter: reads(5, 8, 0, 0),
|
|
3392
3413
|
engine: {
|
|
3393
|
-
calls:
|
|
3394
|
-
rows:
|
|
3414
|
+
calls: 3,
|
|
3415
|
+
rows: 2
|
|
3395
3416
|
},
|
|
3396
3417
|
writes: 1
|
|
3397
3418
|
})
|
|
@@ -4486,6 +4507,34 @@ const registerInsightsModelTests = (state) => {
|
|
|
4486
4507
|
failedLaunches: 1,
|
|
4487
4508
|
uniqueUsers: 2
|
|
4488
4509
|
});
|
|
4510
|
+
const hourly = await model.getReleaseActivity({
|
|
4511
|
+
releases: [releases[1]],
|
|
4512
|
+
timeRange: {
|
|
4513
|
+
start: 0,
|
|
4514
|
+
end: 108e5
|
|
4515
|
+
},
|
|
4516
|
+
intervalMs: 36e5
|
|
4517
|
+
});
|
|
4518
|
+
expect(hourly.data[0]?.metrics.series).toEqual([
|
|
4519
|
+
{
|
|
4520
|
+
startMs: 0,
|
|
4521
|
+
downloads: 1,
|
|
4522
|
+
launches: 2,
|
|
4523
|
+
failedLaunches: 1
|
|
4524
|
+
},
|
|
4525
|
+
{
|
|
4526
|
+
startMs: 36e5,
|
|
4527
|
+
downloads: 0,
|
|
4528
|
+
launches: 0,
|
|
4529
|
+
failedLaunches: 0
|
|
4530
|
+
},
|
|
4531
|
+
{
|
|
4532
|
+
startMs: 72e5,
|
|
4533
|
+
downloads: 0,
|
|
4534
|
+
launches: 0,
|
|
4535
|
+
failedLaunches: 0
|
|
4536
|
+
}
|
|
4537
|
+
]);
|
|
4489
4538
|
const usage = await model.getAppUsage({
|
|
4490
4539
|
channel: "production",
|
|
4491
4540
|
platform: "all",
|
|
@@ -4512,6 +4561,109 @@ const registerInsightsModelTests = (state) => {
|
|
|
4512
4561
|
installations: 1
|
|
4513
4562
|
}]));
|
|
4514
4563
|
});
|
|
4564
|
+
it("keeps one daily running-bundle observation through updates, recovery, retries, and later days", async () => {
|
|
4565
|
+
const model = state.getDatabase();
|
|
4566
|
+
const day = 864e5;
|
|
4567
|
+
const start = Math.floor(Date.now() / day) * day - 3 * day;
|
|
4568
|
+
const a = "00000000-0000-7000-8000-000000008001";
|
|
4569
|
+
const b = "00000000-0000-7000-8000-000000008002";
|
|
4570
|
+
const download = {
|
|
4571
|
+
...createBundleEventRowFixture("9901", start + 100),
|
|
4572
|
+
type: "UPDATE_DOWNLOADED",
|
|
4573
|
+
install_id: "daily-one",
|
|
4574
|
+
from_release_id: a,
|
|
4575
|
+
to_release_id: b
|
|
4576
|
+
};
|
|
4577
|
+
const applied = {
|
|
4578
|
+
...download,
|
|
4579
|
+
type: "UPDATE_APPLIED",
|
|
4580
|
+
id: createBundleEventRowFixture("9902", 0).id,
|
|
4581
|
+
received_at_ms: start + 200
|
|
4582
|
+
};
|
|
4583
|
+
const read = () => model.getDistributionHistory({
|
|
4584
|
+
platform: "ios",
|
|
4585
|
+
channel: "production",
|
|
4586
|
+
timeRange: {
|
|
4587
|
+
start,
|
|
4588
|
+
end: start + 3 * day
|
|
4589
|
+
}
|
|
4590
|
+
});
|
|
4591
|
+
await record(model, download);
|
|
4592
|
+
await expectInsightsIndex(async () => (await read()).points[0].bundles, [{
|
|
4593
|
+
appVersion: "1.0.0",
|
|
4594
|
+
releaseId: a,
|
|
4595
|
+
bundleKind: "release",
|
|
4596
|
+
installations: 1
|
|
4597
|
+
}]);
|
|
4598
|
+
for (const row of [
|
|
4599
|
+
applied,
|
|
4600
|
+
applied,
|
|
4601
|
+
{
|
|
4602
|
+
...applied,
|
|
4603
|
+
id: createBundleEventRowFixture("9903", 0).id,
|
|
4604
|
+
install_id: "daily-two"
|
|
4605
|
+
}
|
|
4606
|
+
]) await record(model, row);
|
|
4607
|
+
const recovered = {
|
|
4608
|
+
...applied,
|
|
4609
|
+
id: createBundleEventRowFixture("9904", 0).id,
|
|
4610
|
+
type: "RECOVERED",
|
|
4611
|
+
from_release_id: b,
|
|
4612
|
+
to_release_id: a,
|
|
4613
|
+
received_at_ms: start + 300
|
|
4614
|
+
};
|
|
4615
|
+
await record(model, recovered);
|
|
4616
|
+
await record(model, {
|
|
4617
|
+
...applied,
|
|
4618
|
+
id: createBundleEventRowFixture("9905", 0).id,
|
|
4619
|
+
received_at_ms: start + day + 100
|
|
4620
|
+
});
|
|
4621
|
+
await record(model, {
|
|
4622
|
+
...applied,
|
|
4623
|
+
id: createBundleEventRowFixture("9906", 0).id,
|
|
4624
|
+
received_at_ms: start + 250
|
|
4625
|
+
});
|
|
4626
|
+
await record(model, {
|
|
4627
|
+
...applied,
|
|
4628
|
+
id: createBundleEventRowFixture("9907", 0).id,
|
|
4629
|
+
install_id: "other-scope",
|
|
4630
|
+
channel: "preview"
|
|
4631
|
+
});
|
|
4632
|
+
await expectInsightsIndex(async () => {
|
|
4633
|
+
return (await read()).points.map((point) => ({
|
|
4634
|
+
...point,
|
|
4635
|
+
bundles: [...point.bundles].sort((x, y) => (x.releaseId ?? "").localeCompare(y.releaseId ?? ""))
|
|
4636
|
+
}));
|
|
4637
|
+
}, [
|
|
4638
|
+
{
|
|
4639
|
+
startMs: start,
|
|
4640
|
+
bundles: [{
|
|
4641
|
+
appVersion: "1.0.0",
|
|
4642
|
+
releaseId: a,
|
|
4643
|
+
bundleKind: "release",
|
|
4644
|
+
installations: 1
|
|
4645
|
+
}, {
|
|
4646
|
+
appVersion: "1.0.0",
|
|
4647
|
+
releaseId: b,
|
|
4648
|
+
bundleKind: "release",
|
|
4649
|
+
installations: 1
|
|
4650
|
+
}]
|
|
4651
|
+
},
|
|
4652
|
+
{
|
|
4653
|
+
startMs: start + day,
|
|
4654
|
+
bundles: [{
|
|
4655
|
+
appVersion: "1.0.0",
|
|
4656
|
+
releaseId: b,
|
|
4657
|
+
bundleKind: "release",
|
|
4658
|
+
installations: 1
|
|
4659
|
+
}]
|
|
4660
|
+
},
|
|
4661
|
+
{
|
|
4662
|
+
startMs: start + 2 * day,
|
|
4663
|
+
bundles: []
|
|
4664
|
+
}
|
|
4665
|
+
]);
|
|
4666
|
+
});
|
|
4515
4667
|
it("pages insights events newest first with a stable cursor", async () => {
|
|
4516
4668
|
const model = state.getDatabase();
|
|
4517
4669
|
const first = createBundleEventRowFixture("701", 100);
|
|
@@ -4713,6 +4865,132 @@ const setupInsightsHttpTestSuite = (options) => {
|
|
|
4713
4865
|
return (await (await client.admin(`/overview?platform=ios&channel=${encodeURIComponent(channel)}&window=24h`)).json()).reportingInstallations.count;
|
|
4714
4866
|
}, 1);
|
|
4715
4867
|
});
|
|
4868
|
+
it("serves daily observed bundles through the authenticated admin API", async () => {
|
|
4869
|
+
const client = options.getClient();
|
|
4870
|
+
const channel = `share-${crypto.randomUUID()}`;
|
|
4871
|
+
const day = 864e5;
|
|
4872
|
+
const start = Math.floor(Date.now() / day) * day;
|
|
4873
|
+
const event = {
|
|
4874
|
+
appVersion: "1.0.0",
|
|
4875
|
+
channel,
|
|
4876
|
+
cohort: "default",
|
|
4877
|
+
fingerprintHash: null,
|
|
4878
|
+
fromBundleId: null,
|
|
4879
|
+
fromReleaseId: null,
|
|
4880
|
+
installId: `share-${crypto.randomUUID()}`,
|
|
4881
|
+
platform: "ios",
|
|
4882
|
+
toBundleId: "00000000-0000-0000-0000-000000000000",
|
|
4883
|
+
toReleaseId: null,
|
|
4884
|
+
type: "UNCHANGED",
|
|
4885
|
+
updateStrategy: null
|
|
4886
|
+
};
|
|
4887
|
+
const reported = await client.client("/events", jsonRequest$1("POST", event));
|
|
4888
|
+
expect(reported.status).toBe(204);
|
|
4889
|
+
await reported.text();
|
|
4890
|
+
const path = `/distribution-history?platform=ios&channel=${channel}&start=${start}&end=${start + day}`;
|
|
4891
|
+
await expectInsightsIndex(async () => {
|
|
4892
|
+
const response = await client.admin(path);
|
|
4893
|
+
expect(response.status).toBe(200);
|
|
4894
|
+
return (await response.json()).points;
|
|
4895
|
+
}, [{
|
|
4896
|
+
startMs: start,
|
|
4897
|
+
bundles: [{
|
|
4898
|
+
appVersion: "1.0.0",
|
|
4899
|
+
releaseId: null,
|
|
4900
|
+
bundleKind: "builtin",
|
|
4901
|
+
installations: 1
|
|
4902
|
+
}]
|
|
4903
|
+
}]);
|
|
4904
|
+
const invalid = await client.admin(`/distribution-history?platform=ios&channel=${channel}&start=${start}&end=${start + 40 * day}`);
|
|
4905
|
+
expect(invalid.status).toBe(400);
|
|
4906
|
+
await invalid.text();
|
|
4907
|
+
});
|
|
4908
|
+
it.each([
|
|
4909
|
+
{
|
|
4910
|
+
type: "UNCHANGED",
|
|
4911
|
+
status: 200,
|
|
4912
|
+
body: "{\"releases\":[]}"
|
|
4913
|
+
},
|
|
4914
|
+
{
|
|
4915
|
+
type: "UNCHANGED",
|
|
4916
|
+
status: 304,
|
|
4917
|
+
body: ""
|
|
4918
|
+
},
|
|
4919
|
+
{
|
|
4920
|
+
type: "UPDATE_FAILED",
|
|
4921
|
+
status: 503,
|
|
4922
|
+
body: "{\"error\":\"Database unavailable\"}"
|
|
4923
|
+
},
|
|
4924
|
+
{
|
|
4925
|
+
type: "UPDATE_DOWNLOADED",
|
|
4926
|
+
status: 200,
|
|
4927
|
+
body: "{\"artifactProtocolVersion\":1}"
|
|
4928
|
+
},
|
|
4929
|
+
{
|
|
4930
|
+
type: "UPDATE_APPLIED",
|
|
4931
|
+
status: 200,
|
|
4932
|
+
body: "{}"
|
|
4933
|
+
},
|
|
4934
|
+
{
|
|
4935
|
+
type: "RECOVERED",
|
|
4936
|
+
status: 502,
|
|
4937
|
+
body: null
|
|
4938
|
+
}
|
|
4939
|
+
])("keeps HTTP $status diagnostics on an existing $type report", async ({ type, status, body }) => {
|
|
4940
|
+
const client = options.getClient();
|
|
4941
|
+
const installId = `install-${crypto.randomUUID()}`;
|
|
4942
|
+
const channel = `http-${crypto.randomUUID()}`;
|
|
4943
|
+
const bundleId = "00000000-0000-7000-8000-000000000001";
|
|
4944
|
+
const httpResponse = {
|
|
4945
|
+
resource: "catalog",
|
|
4946
|
+
path: "/release-catalogs/app-version/ios/production/1.0.0",
|
|
4947
|
+
status,
|
|
4948
|
+
body,
|
|
4949
|
+
bodyTruncated: false,
|
|
4950
|
+
receivedAtMs: Date.now() - 1e3
|
|
4951
|
+
};
|
|
4952
|
+
const event = {
|
|
4953
|
+
eventId: "01929f4e-2b7c-7a51-9d3e-5c1f0a6b8e21",
|
|
4954
|
+
type,
|
|
4955
|
+
installId,
|
|
4956
|
+
platform: "ios",
|
|
4957
|
+
appVersion: "1.0.0",
|
|
4958
|
+
channel,
|
|
4959
|
+
cohort: "1",
|
|
4960
|
+
fingerprintHash: null,
|
|
4961
|
+
sdkVersion: "1.0.0",
|
|
4962
|
+
fromBundleId: type === "UNCHANGED" ? null : bundleId,
|
|
4963
|
+
toBundleId: bundleId,
|
|
4964
|
+
fromReleaseId: null,
|
|
4965
|
+
toReleaseId: null,
|
|
4966
|
+
updateStrategy: type === "UNCHANGED" ? null : "appVersion",
|
|
4967
|
+
metadata: {
|
|
4968
|
+
httpResponse,
|
|
4969
|
+
...type === "UPDATE_FAILED" ? { failure: {
|
|
4970
|
+
stage: "check",
|
|
4971
|
+
reason: "http",
|
|
4972
|
+
httpStatus: status
|
|
4973
|
+
} } : {}
|
|
4974
|
+
}
|
|
4975
|
+
};
|
|
4976
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
4977
|
+
const response = await client.client("/events", jsonRequest$1("POST", event));
|
|
4978
|
+
expect(response.status).toBe(204);
|
|
4979
|
+
await response.text();
|
|
4980
|
+
}
|
|
4981
|
+
const installation = await client.admin(`/installations/${encodeURIComponent(installId)}`);
|
|
4982
|
+
if (type === "UPDATE_FAILED") expect(installation.status).toBe(404);
|
|
4983
|
+
else expect(await installation.json()).toMatchObject({
|
|
4984
|
+
latestStatus: type,
|
|
4985
|
+
httpResponse
|
|
4986
|
+
});
|
|
4987
|
+
const data = (await (await client.admin(`/installations/${encodeURIComponent(installId)}/events`)).json()).data;
|
|
4988
|
+
expect(data).toHaveLength(type === "UNCHANGED" ? 0 : 1);
|
|
4989
|
+
if (type !== "UNCHANGED") expect(data[0]).toMatchObject({
|
|
4990
|
+
type,
|
|
4991
|
+
httpResponse
|
|
4992
|
+
});
|
|
4993
|
+
});
|
|
4716
4994
|
it("records an update failure in installation history and the failures read", async () => {
|
|
4717
4995
|
const client = options.getClient();
|
|
4718
4996
|
const installId = `install-${crypto.randomUUID()}`;
|
package/dist/react-native.cjs
CHANGED
|
@@ -154,7 +154,8 @@ const setupClientPlugins = (plugins, options = {}) => {
|
|
|
154
154
|
onAppReady: call("onAppReady"),
|
|
155
155
|
onUpdateCheck: call("onUpdateCheck"),
|
|
156
156
|
onBundleDownloaded: call("onBundleDownloaded"),
|
|
157
|
-
onUpdateError: call("onUpdateError")
|
|
157
|
+
onUpdateError: call("onUpdateError"),
|
|
158
|
+
onHttpResponse: call("onHttpResponse")
|
|
158
159
|
},
|
|
159
160
|
listens: (name) => host.hasPluginHook(name),
|
|
160
161
|
requests,
|
package/dist/react-native.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AppReadyResult, BundleDownloadedInfo, HotUpdaterBaseURL, HotUpdaterClientPlugin, UpdateCheckResult, UpdateError } from "@hot-updater/protocol";
|
|
1
|
+
import { AppReadyResult, BundleDownloadedInfo, HotUpdaterBaseURL, HotUpdaterClientPlugin, UpdateCheckResult, UpdateError, UpdateHttpResponse } from "@hot-updater/protocol";
|
|
2
2
|
//#region src/setupClientPlugins.d.ts
|
|
3
3
|
/** A value the test fixes, or reads each time a plugin asks for it. */
|
|
4
4
|
type TestValue<T> = T | (() => T);
|
|
@@ -68,6 +68,7 @@ interface ClientPluginTestHooks {
|
|
|
68
68
|
onUpdateCheck(result: UpdateCheckResult): void;
|
|
69
69
|
onBundleDownloaded(info: BundleDownloadedInfo): void;
|
|
70
70
|
onUpdateError(error: UpdateError): void;
|
|
71
|
+
onHttpResponse(response: UpdateHttpResponse): void;
|
|
71
72
|
}
|
|
72
73
|
/** One JavaScript runtime with the plugins set up, as after `HotUpdater.init`. */
|
|
73
74
|
interface ClientPluginTestRuntime {
|
package/dist/react-native.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AppReadyResult, BundleDownloadedInfo, HotUpdaterBaseURL, HotUpdaterClientPlugin, UpdateCheckResult, UpdateError } from "@hot-updater/protocol";
|
|
1
|
+
import { AppReadyResult, BundleDownloadedInfo, HotUpdaterBaseURL, HotUpdaterClientPlugin, UpdateCheckResult, UpdateError, UpdateHttpResponse } from "@hot-updater/protocol";
|
|
2
2
|
//#region src/setupClientPlugins.d.ts
|
|
3
3
|
/** A value the test fixes, or reads each time a plugin asks for it. */
|
|
4
4
|
type TestValue<T> = T | (() => T);
|
|
@@ -68,6 +68,7 @@ interface ClientPluginTestHooks {
|
|
|
68
68
|
onUpdateCheck(result: UpdateCheckResult): void;
|
|
69
69
|
onBundleDownloaded(info: BundleDownloadedInfo): void;
|
|
70
70
|
onUpdateError(error: UpdateError): void;
|
|
71
|
+
onHttpResponse(response: UpdateHttpResponse): void;
|
|
71
72
|
}
|
|
72
73
|
/** One JavaScript runtime with the plugins set up, as after `HotUpdater.init`. */
|
|
73
74
|
interface ClientPluginTestRuntime {
|
package/dist/react-native.mjs
CHANGED
|
@@ -153,7 +153,8 @@ const setupClientPlugins = (plugins, options = {}) => {
|
|
|
153
153
|
onAppReady: call("onAppReady"),
|
|
154
154
|
onUpdateCheck: call("onUpdateCheck"),
|
|
155
155
|
onBundleDownloaded: call("onBundleDownloaded"),
|
|
156
|
-
onUpdateError: call("onUpdateError")
|
|
156
|
+
onUpdateError: call("onUpdateError"),
|
|
157
|
+
onHttpResponse: call("onHttpResponse")
|
|
157
158
|
},
|
|
158
159
|
listens: (name) => host.hasPluginHook(name),
|
|
159
160
|
requests,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hot-updater/test-utils",
|
|
3
|
-
"version": "1.0.0-rc.
|
|
3
|
+
"version": "1.0.0-rc.29",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Test utilities for Hot Updater packages",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -50,10 +50,10 @@
|
|
|
50
50
|
"mysql2": "*",
|
|
51
51
|
"pg": "*",
|
|
52
52
|
"vitest": "^4.0.0",
|
|
53
|
-
"@hot-updater/
|
|
54
|
-
"@hot-updater/
|
|
55
|
-
"@hot-updater/
|
|
56
|
-
"@hot-updater/
|
|
53
|
+
"@hot-updater/plugin-insights": "^1.0.0-rc.30",
|
|
54
|
+
"@hot-updater/server": "^1.0.0-rc.30",
|
|
55
|
+
"@hot-updater/protocol": "^1.0.0-rc.28",
|
|
56
|
+
"@hot-updater/plugin-core": "^1.0.0-rc.28"
|
|
57
57
|
},
|
|
58
58
|
"peerDependenciesMeta": {
|
|
59
59
|
"@electric-sql/pglite": {
|
|
@@ -83,11 +83,11 @@
|
|
|
83
83
|
"typescript": "npm:@typescript/typescript6@6.0.2",
|
|
84
84
|
"vitest": "4.1.4",
|
|
85
85
|
"workspace-tools": "^0.41.7",
|
|
86
|
-
"@hot-updater/plugin-insights": "1.0.0-rc.
|
|
87
|
-
"@hot-updater/protocol": "1.0.0-rc.27",
|
|
86
|
+
"@hot-updater/plugin-insights": "1.0.0-rc.30",
|
|
88
87
|
"@hot-updater/js": "1.0.0-rc.27",
|
|
89
|
-
"@hot-updater/
|
|
90
|
-
"@hot-updater/plugin-core": "1.0.0-rc.
|
|
88
|
+
"@hot-updater/protocol": "1.0.0-rc.28",
|
|
89
|
+
"@hot-updater/plugin-core": "1.0.0-rc.28",
|
|
90
|
+
"@hot-updater/server": "1.0.0-rc.30"
|
|
91
91
|
},
|
|
92
92
|
"inlinedDependencies": {
|
|
93
93
|
"@nodelib/fs.scandir": "2.1.5",
|