@hot-updater/test-utils 1.0.0-rc.16 → 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/README.md +104 -17
- package/dist/index.d.mts +236 -158
- package/dist/index.mjs +3051 -1704
- package/dist/node.cjs +210 -0
- package/dist/node.d.cts +49 -4
- package/dist/node.d.mts +49 -4
- package/dist/node.mjs +203 -1
- package/dist/react-native.cjs +177 -0
- package/dist/react-native.d.cts +104 -0
- package/dist/react-native.d.mts +104 -0
- package/dist/react-native.mjs +174 -0
- package/package.json +44 -6
package/README.md
CHANGED
|
@@ -1,37 +1,49 @@
|
|
|
1
1
|
# @hot-updater/test-utils
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
Test suites and helpers for Hot Updater storage adapters, database adapters,
|
|
4
|
+
providers, server plugins, client plugins and servers. The official adapters,
|
|
5
|
+
providers, plugins and example servers run these same suites.
|
|
5
6
|
|
|
6
7
|
```bash
|
|
7
8
|
pnpm add -D @hot-updater/test-utils @hot-updater/server vitest
|
|
8
9
|
```
|
|
9
10
|
|
|
11
|
+
It uses the other Hot Updater packages only through their public API, and
|
|
12
|
+
takes them as peer dependencies. `@hot-updater/server` and
|
|
13
|
+
`@hot-updater/plugin-insights` are optional: the database suites load the
|
|
14
|
+
server when they run, and the Insights suites use the plugin's types. The SQL
|
|
15
|
+
test executors of `@hot-updater/test-utils/node` take `pg`, `mysql2` or
|
|
16
|
+
`@electric-sql/pglite`, whichever you test on.
|
|
17
|
+
|
|
10
18
|
## Database adapters and providers
|
|
11
19
|
|
|
12
20
|
Register three suites against a disposable test database:
|
|
13
21
|
|
|
14
|
-
- `setupDatabaseAdapterConformanceSuite` runs the
|
|
22
|
+
- `setupDatabaseAdapterConformanceSuite` runs the database adapter contract case
|
|
15
23
|
by case: value round-trips, ordering, guards, all-or-nothing writes, unique
|
|
16
24
|
and multi-valued indexes, paging under capped native pages, and concurrent
|
|
17
25
|
writers.
|
|
18
26
|
- `setupReadBudgetTestSuite` checks what each read of core and the built-in
|
|
19
|
-
plugins costs at the adapter,
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
27
|
+
plugins costs at the adapter, on `createMeasuredDatabase`: `createHotUpdater`
|
|
28
|
+
over `meterReads({ name, adapter: verifyAdapter(adapter) })` from
|
|
29
|
+
`@hot-updater/plugin-core`. `postgresRowsExamined` and `mysqlRowsExamined`
|
|
30
|
+
also count the rows a SQL database examined.
|
|
31
|
+
- `setupDatabaseTestSuite` runs core, Bundles and the Release Catalog contract
|
|
32
|
+
through the admin and client HTTP APIs of `createHotUpdater`, then the suites
|
|
33
|
+
of the server plugins listed in `plugins`, such as the Insights plugin's
|
|
34
|
+
`insightsTestSuite()`.
|
|
24
35
|
|
|
25
36
|
Connect your database's lifecycle and the real server:
|
|
26
37
|
|
|
27
38
|
```ts
|
|
28
39
|
import { createHotUpdater } from "@hot-updater/server";
|
|
29
|
-
import { createDatabasePluginApis } from "@hot-updater/server/db";
|
|
30
40
|
import {
|
|
31
41
|
createInsightsModel,
|
|
32
42
|
insights,
|
|
33
43
|
} from "@hot-updater/server/plugins/insights";
|
|
44
|
+
|
|
34
45
|
import {
|
|
46
|
+
insightsTestSuite,
|
|
35
47
|
setupDatabaseTestSuite,
|
|
36
48
|
startHttpTestServer,
|
|
37
49
|
} from "@hot-updater/test-utils";
|
|
@@ -57,10 +69,20 @@ setupDatabaseTestSuite({
|
|
|
57
69
|
clientAccess: "public",
|
|
58
70
|
}).handlers,
|
|
59
71
|
),
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
72
|
+
// The server above runs insights(), so its suite runs too.
|
|
73
|
+
plugins: [
|
|
74
|
+
insightsTestSuite({
|
|
75
|
+
// The plugin's API on the database, as a server running it assembles it.
|
|
76
|
+
createModel: (database) =>
|
|
77
|
+
createInsightsModel(
|
|
78
|
+
createHotUpdater({
|
|
79
|
+
database,
|
|
80
|
+
plugins: [insights()],
|
|
81
|
+
clientAccess: "public",
|
|
82
|
+
}).api.insights,
|
|
83
|
+
),
|
|
84
|
+
}),
|
|
85
|
+
],
|
|
64
86
|
});
|
|
65
87
|
```
|
|
66
88
|
|
|
@@ -72,15 +94,80 @@ client selector and keep a simulated device's state across updates, rollbacks
|
|
|
72
94
|
and the built-in fallback. Native download, activation and restart remain
|
|
73
95
|
device end-to-end responsibilities.
|
|
74
96
|
|
|
97
|
+
## Storage adapters
|
|
98
|
+
|
|
99
|
+
`setupStorageAdapterTestSuite` runs the storage adapter contract that deploy,
|
|
100
|
+
patch, the Console, the server and `storage prune` rely on: the canonical URI
|
|
101
|
+
of each key below the base path, streamed bodies, failed uploads that leave no
|
|
102
|
+
object, exact deletes, URIs of another bucket or protocol, download URLs, and
|
|
103
|
+
listing. A storage adapter's specs don't need `@hot-updater/server`.
|
|
104
|
+
|
|
105
|
+
```ts
|
|
106
|
+
import { setupStorageAdapterTestSuite } from "@hot-updater/test-utils";
|
|
107
|
+
|
|
108
|
+
import { myStorage } from "./myStorage";
|
|
109
|
+
|
|
110
|
+
setupStorageAdapterTestSuite({
|
|
111
|
+
name: "myStorage",
|
|
112
|
+
createStorage: async () => ({
|
|
113
|
+
storage: myStorage({ bucket: "bundles", basePath: "ota" }),
|
|
114
|
+
basePath: "ota",
|
|
115
|
+
}),
|
|
116
|
+
operations: ["put", "get", "getDownloadUrl", "exists", "delete"],
|
|
117
|
+
});
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
`createStorage` returns the adapter over an empty bucket or base path for each
|
|
121
|
+
case, and the `cleanup` it may return runs after the case. `operations` lists
|
|
122
|
+
what the adapter implements: the suite fails when one of them is missing and
|
|
123
|
+
skips the cases of the others.
|
|
124
|
+
|
|
75
125
|
## Plugins and servers
|
|
76
126
|
|
|
77
|
-
- `createPluginTestHarness` runs one server plugin
|
|
78
|
-
|
|
127
|
+
- `createPluginTestHarness` runs one server plugin on `createEngine` from
|
|
128
|
+
`@hot-updater/plugin-core`, the engine `createHotUpdater` runs plugins on, on
|
|
129
|
+
a memory adapter by default, and measures what each API call reads.
|
|
79
130
|
- `setupReleaseCatalogTestSuite` with `createHttpTestClient` tests a running
|
|
80
131
|
server by URL. Use `createHandlerHttpTestClient` inside a Workers Vitest pool.
|
|
81
132
|
- Server processes can import `createReleaseCatalogTestStorage` from
|
|
82
133
|
`@hot-updater/test-utils/node` to install the suites' storage fixtures.
|
|
83
134
|
|
|
135
|
+
## Client plugins
|
|
136
|
+
|
|
137
|
+
`@hot-updater/test-utils/react-native` runs client plugins the way the app
|
|
138
|
+
does. `setupClientPlugin(plugin, options)` and
|
|
139
|
+
`setupClientPlugins(plugins, options)` set plugins up as `HotUpdater.init`
|
|
140
|
+
does, on the plugin host from `@hot-updater/protocol`, the one the SDK runs,
|
|
141
|
+
against an in-memory device and a server the test answers. The entry loads
|
|
142
|
+
neither React Native nor Vitest, so plugin specs run in plain Node under
|
|
143
|
+
Vitest or Jest when the plugin imports `defineClientPlugin` from
|
|
144
|
+
`@hot-updater/protocol`; the SDK's root entry loads React Native.
|
|
145
|
+
|
|
146
|
+
```ts
|
|
147
|
+
import { setupClientPlugin } from "@hot-updater/test-utils/react-native";
|
|
148
|
+
|
|
149
|
+
const runtime = setupClientPlugin(myPlugin(), {
|
|
150
|
+
requestHeaders: { "x-api-key": "client-key" },
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
runtime.hooks.onAppReady(launch);
|
|
154
|
+
await runtime.settled();
|
|
155
|
+
|
|
156
|
+
expect(runtime.requests[0]?.json()).toEqual(expectedEvent);
|
|
157
|
+
expect(runtime.errors).toEqual([]);
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
`hooks` calls each hook as the SDK does, without waiting for it, and
|
|
161
|
+
`settled()` waits for what the hooks started. `requests` records what plugins
|
|
162
|
+
sent with `context.fetch`, and the `respond` option answers each request,
|
|
163
|
+
`204 No Content` by default. `errors` holds what the SDK reports to `onError`.
|
|
164
|
+
`storage` is the device's plugin storage: pass it to the next setup to start
|
|
165
|
+
the plugins again on the same device, or seed one with `createTestStorage()`.
|
|
166
|
+
|
|
84
167
|
See the
|
|
85
|
-
[
|
|
86
|
-
|
|
168
|
+
[storage adapter guide](https://hot-updater.dev/docs/storage-adapters/custom-storage#test-the-adapter),
|
|
169
|
+
the
|
|
170
|
+
[database adapter guide](https://hot-updater.dev/docs/database-adapters/custom-database),
|
|
171
|
+
and the
|
|
172
|
+
[client plugin guide](https://hot-updater.dev/docs/react-native-api/client-plugins#test-the-plugin)
|
|
173
|
+
for the contracts and complete specs.
|