@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/dist/index.d.mts CHANGED
@@ -1,6 +1,6 @@
1
- import { ArtifactInfo, Bundle, ReleaseCatalog } from "@hot-updater/core";
2
- import { DatabaseAdapter, DatabaseReadCount, PhysicalTable } from "@hot-updater/plugin-core/internal";
3
- import { BundleRow, ChannelRow, DeployReleasePolicy, EngineDatabase, HotUpdaterCoreApi, InsightsModel, ReleaseRow, StoragePlugin } from "@hot-updater/plugin-core";
1
+ import { ArtifactInfo, Bundle } from "@hot-updater/protocol";
2
+ import { AnyHotUpdaterPlugin, BundlePatchRow, BundleRow, ChannelRow, ClientAuth, DatabaseAdapter, DeployReleasePolicy, EngineDatabase, HotUpdaterCoreApi, HotUpdaterDatabase, KeyValueStore, ModuleSchema, PhysicalTable, PluginApis, ReadMeasurement, ReleaseRow, StorageAdapter, StorageOperation } from "@hot-updater/plugin-core";
3
+ import { BundleEventRow, InsightsModel } from "@hot-updater/plugin-insights/server";
4
4
  //#region src/httpTestClient.d.ts
5
5
  type HttpTestRequestInit = NonNullable<ConstructorParameters<typeof Request>[1]>;
6
6
  type HttpTestRequest = (path: string, init?: HttpTestRequestInit) => Promise<Response>;
@@ -71,37 +71,6 @@ declare const setupCoreAdminTestSuite: (options: {
71
71
  readonly getClient: () => HttpTestClient;
72
72
  }) => void;
73
73
  //#endregion
74
- //#region src/setupInsightsHttpTestSuite.d.ts
75
- /**
76
- * The Insights plugin's routes on a database: a client reports an event, and
77
- * the admin routes read it back. The server must run `insights()`.
78
- */
79
- declare const setupInsightsHttpTestSuite: (options: {
80
- readonly getClient: () => HttpTestClient;
81
- }) => void;
82
- //#endregion
83
- //#region src/databaseTestRunner.d.ts
84
- type Awaitable<T> = Promise<T> | T;
85
- /** How a suite gets a provider's database, and gives it back. */
86
- type DatabaseTestLifecycle<TDatabase> = {
87
- readonly name: string;
88
- /** Creates the provider's tables and settings rows, before `createDatabase`. */
89
- readonly migrate: () => Awaitable<void>;
90
- /** The provider's database, as its factory returns it. */
91
- readonly createDatabase: () => Awaitable<TDatabase>;
92
- /** Empties every table between tests. */
93
- readonly reset: (database: TDatabase) => Awaitable<void>;
94
- readonly dispose: (database: TDatabase) => Awaitable<void>;
95
- };
96
- //#endregion
97
- //#region src/setupInsightsModelTestSuite.d.ts
98
- /**
99
- * The Insights report contract on one database: pass the Insights plugin's
100
- * model over it, `createInsightsModel(createDatabasePluginApis(database,
101
- * [insights()]).insights)`, as `createDatabase`.
102
- */
103
- declare const setupInsightsModelTestSuite: (lifecycle: DatabaseTestLifecycle<InsightsModel>) => void;
104
- //#endregion
105
74
  //#region src/setupSemverSatisfiesTestSuite.d.ts
106
75
  /**
107
76
  *
@@ -124,29 +93,50 @@ declare const setupSemverSatisfiesTestSuite: ({ semverSatisfies }: {
124
93
  semverSatisfies: (targetAppVersion: string, currentVersion: string) => Promise<boolean> | boolean;
125
94
  }) => void;
126
95
  //#endregion
96
+ //#region src/databaseTestRunner.d.ts
97
+ type Awaitable<T> = Promise<T> | T;
98
+ /** How a suite gets a provider's database, and gives it back. */
99
+ type DatabaseTestLifecycle<TDatabase> = {
100
+ readonly name: string;
101
+ /** Creates the provider's tables and settings rows, before `createDatabase`. */
102
+ readonly migrate: () => Awaitable<void>;
103
+ /** The provider's database, as its factory returns it. */
104
+ readonly createDatabase: () => Awaitable<TDatabase>;
105
+ /** Empties every table between tests. */
106
+ readonly reset: (database: TDatabase) => Awaitable<void>;
107
+ readonly dispose: (database: TDatabase) => Awaitable<void>;
108
+ };
109
+ //#endregion
127
110
  //#region src/setupDatabaseTestSuite.d.ts
111
+ /**
112
+ * A server plugin's tests on a provider's database, such as the Insights
113
+ * plugin's `insightsTestSuite()`. The provider's HTTP server must run the
114
+ * plugin.
115
+ */
116
+ interface DatabasePluginTestSuite<TDatabase = unknown> {
117
+ readonly name: string;
118
+ register(context: {
119
+ readonly getClient: () => HttpTestClient;
120
+ readonly getDatabase: () => TDatabase;
121
+ }): void;
122
+ }
128
123
  type DatabaseTestSuiteOptions<TDatabase extends EngineDatabase = EngineDatabase> = DatabaseTestLifecycle<TDatabase> & {
129
124
  /**
130
- * Serves the handlers of `createHotUpdater({ database, storage, plugins:
131
- * [insights()], clientAccess: "public" })` over HTTP: in process, or in the
132
- * runtime the provider deploys to.
125
+ * Serves the handlers of `createHotUpdater({ database, storage, plugins,
126
+ * clientAccess: "public" })` over HTTP, in process or in the runtime the
127
+ * provider deploys to, with the plugins whose suites `plugins` lists.
133
128
  */
134
129
  readonly createHttpClient: (options: {
135
130
  readonly database: TDatabase;
136
- readonly storage: readonly StoragePlugin[];
131
+ readonly storage: readonly StorageAdapter[];
137
132
  }) => HttpTestServer | Promise<HttpTestServer>;
138
- /**
139
- * The Insights plugin's model on the database, to run the Insights report
140
- * contract on its tables: `(database) => createInsightsModel(
141
- * createDatabasePluginApis(database, [insights()]).insights)`.
142
- */
143
- readonly createInsightsModel?: (database: TDatabase) => InsightsModel;
133
+ /** The suites of the server plugins the provider opts in to test. */
134
+ readonly plugins?: readonly DatabasePluginTestSuite<TDatabase>[];
144
135
  };
145
136
  /**
146
137
  * A provider's database under Hot Updater's server: core's operations, the
147
- * Release Catalog contract, bundles, and Insights, all through HTTP, on the
148
- * tables the provider's tooling creates; with `createInsightsModel`, also the
149
- * Insights report contract in process.
138
+ * Release Catalog contract, and bundles, all through HTTP, on the tables the
139
+ * provider's tooling creates, then each opted-in plugin's suite.
150
140
  */
151
141
  declare const setupDatabaseTestSuite: <TDatabase extends EngineDatabase>(options: DatabaseTestSuiteOptions<TDatabase>) => void;
152
142
  //#endregion
@@ -167,9 +157,17 @@ interface DatabaseAdapterConformanceOptions {
167
157
  readonly maxOps?: number;
168
158
  /** Concurrent writers in the contention cases (default 32). */
169
159
  readonly writers?: number;
160
+ /**
161
+ * How the backend deletes rows past a table's retention: `prune` (the
162
+ * default) through the adapter, or `ttl`, natively on every item, with no
163
+ * `prune` on the adapter.
164
+ */
165
+ readonly retention?: "prune" | "ttl";
170
166
  }
171
167
  declare const conformanceItems: PhysicalTable;
172
168
  declare const conformanceCounters: PhysicalTable;
169
+ /** Rows expire 1,000 ms after `at`; a null `at` never expires. */
170
+ declare const conformanceExpiring: PhysicalTable;
173
171
  /**
174
172
  * The storage-adapter conformance contract (PRD S3 and S4 cases). Every
175
173
  * adapter runs it through `verifyAdapter`, which also checks each read and
@@ -177,9 +175,38 @@ declare const conformanceCounters: PhysicalTable;
177
175
  */
178
176
  declare const setupDatabaseAdapterConformanceSuite: (options: DatabaseAdapterConformanceOptions) => void;
179
177
  //#endregion
178
+ //#region src/setupStorageAdapterTestSuite.d.ts
179
+ interface StorageAdapterTestSuiteOptions {
180
+ readonly name: string;
181
+ /**
182
+ * Returns the adapter over an empty bucket or base path. Every case creates
183
+ * its own and calls `cleanup` after it.
184
+ */
185
+ readonly createStorage: () => Promise<{
186
+ readonly storage: StorageAdapter;
187
+ /**
188
+ * The base path the adapter stores keys below. With it, every URI `put`
189
+ * returns must have the key `<basePath>/<key>`.
190
+ */
191
+ readonly basePath?: string;
192
+ readonly cleanup?: () => Promise<void>;
193
+ }>;
194
+ /**
195
+ * The operations the adapter implements. The suite requires them, with
196
+ * `put` and `get`, and skips the cases of every other operation. Without
197
+ * it, the suite skips the cases of the operations the adapter lacks.
198
+ */
199
+ readonly operations?: readonly StorageOperation[];
200
+ }
201
+ /**
202
+ * The storage adapter contract that deploy, patch, the Console, the server,
203
+ * and `storage prune` rely on. Every official storage adapter runs it.
204
+ */
205
+ declare const setupStorageAdapterTestSuite: (options: StorageAdapterTestSuiteOptions) => void;
206
+ //#endregion
180
207
  //#region src/sqlRowsExamined.d.ts
181
208
  /**
182
- * Rows a SQL database examined for the reads a storage adapter made: what
209
+ * Rows a SQL database examined for the reads a database adapter made: what
183
210
  * read-budget suites compare with the rows the adapter read.
184
211
  */
185
212
  interface RowsExamined {
@@ -239,70 +266,17 @@ declare const mysqlRowsExamined: (query: SqlQuery) => {
239
266
  };
240
267
  //#endregion
241
268
  //#region src/setupReadBudgetTestSuite.d.ts
242
- /** Core's API with the client routes' reads, which the public API leaves out. */
243
- type ReadBudgetCore = HotUpdaterCoreApi & {
244
- getReleaseCatalog(input: {
245
- readonly strategy: "APP_VERSION";
246
- readonly platform: "ios" | "android";
247
- readonly channelKey: string;
248
- readonly appVersion: string;
249
- }): Promise<ReleaseCatalog | null>;
250
- getArtifactInfo(targetBundleId: string, currentBundleId: string, artifactProtocolVersion: 1): Promise<ArtifactInfo | null>;
251
- latestReleaseId(scopeKey: string): Promise<string | null>;
252
- };
253
- /** How core reads bundle manifests and resolves file URLs. */
254
- interface ReadBudgetStorage {
255
- readonly readStorageText?: (storageUri: string) => Promise<string | null>;
256
- readonly resolveFileUrl: (storageUri: string | null) => Promise<string | null>;
257
- }
258
- /** What `createMeasuredDatabase` returns: core and the plugins on one engine in verify mode. */
259
- interface ReadBudgetDatabase {
260
- readonly core: ReadBudgetCore;
261
- readonly api: Readonly<Record<string, unknown>>;
262
- readonly clientAuth?: {
263
- authenticate(headers: Headers): Promise<boolean>;
264
- };
265
- measureReads<T>(read: () => Promise<T>): Promise<{
266
- readonly result: T;
267
- readonly adapter: DatabaseReadCount;
268
- readonly engine: {
269
- readonly calls: number;
270
- readonly rows: number;
271
- };
272
- }>;
273
- }
274
- /**
275
- * What the suite takes from `@hot-updater/server`; pass those exports, which
276
- * keeps test-utils free of a dependency on the server.
277
- */
278
- interface ReadBudgetServer {
279
- /** `createMeasuredDatabase` from `@hot-updater/server/db`. */
280
- createMeasuredDatabase(adapter: DatabaseAdapter, plugins: readonly unknown[], options: {
281
- readonly now: () => number;
282
- readonly storage: ReadBudgetStorage;
283
- }): ReadBudgetDatabase;
284
- /** `builtInSchema` from `@hot-updater/server/database`: the tables to create. */
285
- readonly builtInSchema: {
286
- readonly tables: readonly PhysicalTable[];
287
- };
288
- /** `[insights(), apiKeys()]`, the built-in plugins whose reads have budgets. */
289
- readonly plugins: readonly unknown[];
290
- /** `targetBaseCandidateKey` from `@hot-updater/server/db`. */
291
- targetBaseCandidateKey(target: {
292
- readonly channel: string;
293
- readonly platform: "ios" | "android";
294
- readonly fingerprintHash: string | null;
295
- readonly appVersion: string | null;
296
- }): string | null;
297
- }
298
269
  interface ReadBudgetAdapterContext {
270
+ /**
271
+ * Core's tables, those of the plugins the suite measures, and the settings
272
+ * table, where the retention passes keep their lease.
273
+ */
299
274
  readonly tables: readonly PhysicalTable[];
300
275
  /** Test-only: the adapter must fetch at most this many rows per native page. */
301
276
  readonly nativePageSize: number;
302
277
  }
303
278
  interface ReadBudgetSuiteOptions {
304
279
  readonly name: string;
305
- readonly server: ReadBudgetServer;
306
280
  /** Returns an adapter over freshly created, empty tables. */
307
281
  readonly createAdapter: (context: ReadBudgetAdapterContext) => Promise<{
308
282
  readonly adapter: DatabaseAdapter;
@@ -317,14 +291,33 @@ interface ReadBudgetSuiteOptions {
317
291
  }>;
318
292
  }
319
293
  /**
320
- * The read-budget suite (PRD S4) on one backend: core and the built-in
321
- * plugins in verify mode over the backend's adapter, native pages capped at
322
- * two rows. Every API in the read-budget list reads exactly its budget at the
323
- * adapter and at the engine; with `examined`, the database examines no more
324
- * rows than the adapter read, within the backend's native multipliers.
294
+ * The read-budget suite (PRD S4) on one backend: core and the server's
295
+ * built-in plugins on `createMeasuredDatabase` over the backend's adapter,
296
+ * native pages capped at two rows. Every API in the read-budget list reads
297
+ * exactly its budget at the adapter and at the engine; with `examined`, the
298
+ * database examines no more rows than the adapter read, within the backend's
299
+ * native multipliers. It loads `@hot-updater/server`, an optional peer.
325
300
  */
326
301
  declare const setupReadBudgetTestSuite: (options: ReadBudgetSuiteOptions) => void;
327
302
  //#endregion
303
+ //#region src/setupAggregateBatchingTestSuite.d.ts
304
+ interface AggregateBatchingSuiteOptions {
305
+ readonly name: string;
306
+ /** Returns an adapter over an empty store; each case calls it once. */
307
+ readonly createAdapter: () => Promise<{
308
+ readonly adapter: DatabaseAdapter;
309
+ readonly cleanup?: () => Promise<void>;
310
+ }>;
311
+ /** Aggregate rows one transaction changes in the too-large case: more than one write takes. */
312
+ readonly oversizedRows: number;
313
+ }
314
+ /**
315
+ * Aggregates declared `batched` on a key-value store: log rows the store
316
+ * writes with each transaction and a compaction merges once, and a memory
317
+ * buffer, read back exactly as transactional writes read.
318
+ */
319
+ declare const setupAggregateBatchingTestSuite: ({ name, createAdapter, oversizedRows }: AggregateBatchingSuiteOptions) => void;
320
+ //#endregion
328
321
  //#region src/runContentionHarness.d.ts
329
322
  /** Delays every adapter call by `ms`, like a round trip to a remote database. */
330
323
  declare const withAdapterLatency: (adapter: DatabaseAdapter, ms: number) => DatabaseAdapter;
@@ -351,75 +344,160 @@ interface ContentionReport {
351
344
  */
352
345
  declare const runContentionHarness: (options: ContentionOptions) => Promise<ContentionReport>;
353
346
  //#endregion
354
- //#region src/createPluginTestHarness.d.ts
355
- interface HarnessModule {
356
- readonly id: string;
357
- readonly schema: object;
358
- readonly namespace?: string;
347
+ //#region src/createMeasuredDatabase.d.ts
348
+ /** How core reads bundle manifests and resolves the file URLs of `storage://` URIs. */
349
+ interface MeasuredDatabaseStorage {
350
+ readonly readStorageText?: (storageUri: string) => Promise<string | null>;
351
+ readonly resolveFileUrl: (storageUri: string) => Promise<string>;
359
352
  }
360
- interface HarnessEngine {
361
- database(module: HarnessModule): unknown;
362
- measureReads<T>(read: () => Promise<T>): Promise<{
363
- readonly result: T;
364
- readonly adapter: DatabaseReadCount;
365
- readonly engine: {
366
- readonly calls: number;
367
- readonly rows: number;
368
- };
369
- }>;
353
+ interface MeasuredDatabaseOptions {
354
+ /**
355
+ * The plugins' clock, which they read through `ctx.now`. Core's
356
+ * timestamps and the retention passes run on `Date.now`.
357
+ */
358
+ readonly now?: () => number;
359
+ /**
360
+ * The storage of `storage://` URIs, for reads that resolve them, such as
361
+ * artifact resolution; without it those reads fail.
362
+ */
363
+ readonly storage?: MeasuredDatabaseStorage;
364
+ }
365
+ /** Core and the plugins' APIs on `createHotUpdater`, with the database's read meter. */
366
+ interface MeasuredDatabase<TApi = Readonly<Record<string, unknown>>> {
367
+ readonly core: HotUpdaterCoreApi;
368
+ readonly api: TApi;
369
+ /** The client-route policy, when a plugin provides one. */
370
+ readonly clientAuth?: ClientAuth;
371
+ /** Runs `read` and reports what it read at the adapter and at the engine. */
372
+ measureReads<T>(read: () => Promise<T>): Promise<ReadMeasurement<T>>;
370
373
  }
371
374
  /**
372
- * What the harness takes from `@hot-updater/server/database`; pass that
373
- * module, which keeps test-utils free of a dependency on the server.
375
+ * Core and the plugins over `adapter` as `createHotUpdater` runs them: what
376
+ * read-budget suites measure. The database is `meterReads` over
377
+ * `verifyAdapter(adapter)`, so every adapter call is checked against the
378
+ * contract and `measureReads` reports each call's reads; the adapter runs
379
+ * as given, without the schema fence. It loads `@hot-updater/server`, an
380
+ * optional peer, when called.
374
381
  */
375
- interface PluginTestEngine {
376
- resolveSchema(modules: readonly HarnessModule[]): {
377
- readonly tables: readonly PhysicalTable[];
378
- };
379
- createDatabaseEngine(options: {
380
- readonly adapter: DatabaseAdapter;
381
- readonly schema: never;
382
- readonly verify?: boolean;
383
- }): HarnessEngine;
384
- }
382
+ declare const createMeasuredDatabase: <const TPlugins extends readonly AnyHotUpdaterPlugin[]>(adapter: DatabaseAdapter, plugins: TPlugins, options?: MeasuredDatabaseOptions) => Promise<MeasuredDatabase<PluginApis<TPlugins>>>;
383
+ //#endregion
384
+ //#region src/createPluginTestHarness.d.ts
385
385
  interface HarnessPlugin {
386
386
  readonly id: string;
387
- readonly schema: object;
387
+ readonly schemaVersion: string;
388
+ readonly schema: ModuleSchema;
389
+ /** `false` keeps the declared table names, as `createHotUpdater` does. */
390
+ readonly namespace?: false;
388
391
  init(context: never): {
389
392
  readonly api: unknown;
390
393
  };
391
394
  }
392
395
  interface PluginTestHarnessOptions {
393
- /** `import * as engine from "@hot-updater/server/database"`. */
394
- readonly engine: PluginTestEngine;
395
396
  /** Defaults to a fresh memory adapter. */
396
397
  readonly adapter?: DatabaseAdapter;
397
398
  /** The plugin's clock; `setNow` changes it later. */
398
399
  readonly now?: () => number;
399
400
  }
400
401
  /**
401
- * Runs one plugin the way `createHotUpdater` does, on a memory adapter in
402
- * verify mode, so tests can call its API and measure what each call reads.
402
+ * Runs one plugin on `createEngine`, the storage engine `createHotUpdater`
403
+ * runs its plugins on, so tests can call its API and measure what each call
404
+ * reads. Every adapter call is checked against the adapter contract
405
+ * (`verifyAdapter`), and `ctx.core` is empty: core's reads need a server.
406
+ * The engine gets the adapter without `prune`, as a store that expires rows
407
+ * itself would give it, so no retention pass adds to what a call reads and
408
+ * writes; rows stay past their retention.
403
409
  */
404
- declare const createPluginTestHarness: <TPlugin extends HarnessPlugin>(plugin: TPlugin, options: PluginTestHarnessOptions) => Promise<{
410
+ declare const createPluginTestHarness: <TPlugin extends HarnessPlugin>(plugin: TPlugin, options?: PluginTestHarnessOptions) => Promise<{
405
411
  api: ReturnType<TPlugin["init"]>["api"];
406
412
  instance: ReturnType<TPlugin["init"]>;
407
- /** The plugin's own database handle; cast it to its `HotUpdaterDatabase` type. */
408
- db: unknown;
413
+ /** The plugin's own database handle. */
414
+ db: HotUpdaterDatabase<TPlugin["schema"]>;
409
415
  adapter: DatabaseAdapter;
410
- tables: readonly PhysicalTable[];
411
- /** Reads at both boundaries while `read` runs. */
412
- measureReads: <T>(read: () => Promise<T>) => Promise<{
413
- readonly result: T;
414
- readonly adapter: DatabaseReadCount;
415
- readonly engine: {
416
- readonly calls: number;
417
- readonly rows: number;
418
- };
419
- }>;
420
- setNow: (now: () => number) => void;
416
+ /** The plugin's physical tables. */
417
+ tables: import("@hot-updater/plugin-core").PhysicalTable[];
418
+ /** Reads at the adapter and at the engine while `read` runs. */
419
+ measureReads: <T>(read: () => Promise<T>) => Promise<import("@hot-updater/plugin-core").ReadMeasurement<T>>;
420
+ setNow: (next: () => number) => void;
421
+ }>;
422
+ //#endregion
423
+ //#region src/databaseTestFixtures.d.ts
424
+ declare const createChannelRowFixture: (name?: string) => ChannelRow;
425
+ declare const createBundleRowFixture: (suffix: string, _channel?: string) => BundleRow;
426
+ declare const createBundlePatchRowFixture: (suffix: string, bundleId: string, baseBundleId: string, orderIndex?: number) => BundlePatchRow;
427
+ declare const createReleaseRowFixture: (suffix: string, bundle: BundleRow, channel: ChannelRow) => ReleaseRow;
428
+ declare const createBundleFixture: (suffix: string, _channel?: string) => Bundle;
429
+ //#endregion
430
+ //#region src/storeBundles.d.ts
431
+ /**
432
+ * Stores bundles and their patches with no release, as a database holds an
433
+ * artifact that no release uses. `deploy` always adds a release, and
434
+ * deleting a bundle's last release deletes the bundle, so tests store such
435
+ * bundles through the engine. A patch's base comes before it in `bundles`.
436
+ */
437
+ declare const storeBundles: (database: EngineDatabase, bundles: readonly Bundle[]) => Promise<void>;
438
+ //#endregion
439
+ //#region src/insights/expectInsightsIndex.d.ts
440
+ /** Allow committed secondary indexes to catch up; query failures still fail. */
441
+ declare const expectInsightsIndex: <T>(query: () => Promise<T>, expected: T) => Promise<void>;
442
+ //#endregion
443
+ //#region src/insights/fixtures.d.ts
444
+ /** An applied update from install `suffix`, received at `receivedAtMs`. */
445
+ declare const createBundleEventRowFixture: (suffix: string, receivedAtMs: number) => Extract<BundleEventRow, {
446
+ type: "UPDATE_APPLIED" | "RECOVERED";
421
447
  }>;
422
448
  //#endregion
449
+ //#region src/insights/setupInsightsHttpTestSuite.d.ts
450
+ /**
451
+ * The Insights plugin's routes on a database: a client reports an event, and
452
+ * the admin routes read it back. The server must run `insights()`.
453
+ */
454
+ declare const setupInsightsHttpTestSuite: (options: {
455
+ readonly getClient: () => HttpTestClient;
456
+ }) => void;
457
+ //#endregion
458
+ //#region src/insights/setupInsightsModelTestSuite.d.ts
459
+ /**
460
+ * The Insights report contract on one database: pass the Insights plugin's
461
+ * model over it, `createInsightsModel(createHotUpdater({ database, plugins:
462
+ * [insights()], clientAccess: "public" }).api.insights)`, as `createDatabase`.
463
+ */
464
+ declare const setupInsightsModelTestSuite: (lifecycle: DatabaseTestLifecycle<InsightsModel>) => void;
465
+ //#endregion
466
+ //#region src/insights/index.d.ts
467
+ /**
468
+ * The Insights plugin's tests, for `setupDatabaseTestSuite`'s `plugins`: its
469
+ * HTTP routes on the provider's server, which must run `insights()`, and
470
+ * with `createModel` its report contract on the provider's database, such as
471
+ * `(database) => createInsightsModel(createHotUpdater({ database, plugins:
472
+ * [insights()], clientAccess: "public" }).api.insights)`.
473
+ */
474
+ declare const insightsTestSuite: <TDatabase = EngineDatabase>(options?: {
475
+ readonly createModel?: (database: TDatabase) => InsightsModel;
476
+ }) => {
477
+ name: string;
478
+ register: (context: {
479
+ readonly getClient: () => HttpTestClient;
480
+ readonly getDatabase: () => TDatabase;
481
+ }) => void;
482
+ };
483
+ //#endregion
484
+ //#region src/kvTestStore.d.ts
485
+ interface MemoryKeyValueStoreOptions {
486
+ /** Test-only: the most items one `query` page returns. */
487
+ readonly nativePageSize?: number;
488
+ readonly limits?: KeyValueStore["limits"];
489
+ }
490
+ /**
491
+ * An in-memory key-value store that behaves like DynamoDB where the adapter
492
+ * can tell: a write checks every condition against the state before it,
493
+ * refuses two ops on one item, and applies all ops or none.
494
+ */
495
+ declare const createMemoryKeyValueStore: (options?: MemoryKeyValueStoreOptions) => KeyValueStore & {
496
+ readonly items: () => number;
497
+ /** Each item's expiry, as a store's native TTL would hold it, by `[pk, sk]` JSON. */
498
+ readonly expiries: () => ReadonlyMap<string, number>;
499
+ };
500
+ //#endregion
423
501
  //#region src/setupReleaseCatalogTestSuite.d.ts
424
502
  /**
425
503
  * Server conformance through HTTP only, on admin API protocol 2. The caller
@@ -447,6 +525,6 @@ declare const releaseCatalogArtifact: (bundle: BundleRow) => ArtifactInfo;
447
525
  /** Install these storage objects in the test server, independently of its DB. */
448
526
  declare const RELEASE_CATALOG_STORAGE_FIXTURES: Readonly<Record<string, string>>;
449
527
  /** Storage boundary shared by provider and separately spawned server tests. */
450
- declare const createReleaseCatalogTestStorage: () => StoragePlugin;
528
+ declare const createReleaseCatalogTestStorage: () => StorageAdapter;
451
529
  //#endregion
452
- export { AdminApiTestClient, ContentionOptions, ContentionReport, DatabaseAdapterConformanceContext, DatabaseAdapterConformanceOptions, type DatabaseTestLifecycle, DatabaseTestSuiteOptions, HttpTestClient, HttpTestHandlers, HttpTestRequest, HttpTestRequestInit, HttpTestServer, PluginTestEngine, PluginTestHarnessOptions, RELEASE_CATALOG_DOWNLOAD_HASH, RELEASE_CATALOG_MANIFEST_URIS, RELEASE_CATALOG_STORAGE_FIXTURES, ReadBudgetAdapterContext, ReadBudgetDatabase, ReadBudgetServer, ReadBudgetStorage, ReadBudgetSuiteOptions, RowsExamined, SqlExecutorLike, SqlQuery, SqlStatementLike, conformanceCounters, conformanceItems, createAdminApiTestClient, createHandlerHttpTestClient, createHttpTestClient, createPluginTestHarness, createReleaseCatalogTestStorage, jsonRequest, mysqlRowsExamined, postgresRowsExamined, releaseCatalogArtifact, releaseCatalogDownloadUrl, runContentionHarness, setupBundleMethodsTestSuite, setupCoreAdminTestSuite, setupDatabaseAdapterConformanceSuite, setupDatabaseTestSuite, setupInsightsHttpTestSuite, setupInsightsModelTestSuite, setupReadBudgetTestSuite, setupReleaseCatalogTestSuite, setupSemverSatisfiesTestSuite, startHttpTestServer, toDeployBundle, withAdapterLatency };
530
+ export { AdminApiTestClient, AggregateBatchingSuiteOptions, ContentionOptions, ContentionReport, DatabaseAdapterConformanceContext, DatabaseAdapterConformanceOptions, DatabasePluginTestSuite, type DatabaseTestLifecycle, DatabaseTestSuiteOptions, HttpTestClient, HttpTestHandlers, HttpTestRequest, HttpTestRequestInit, HttpTestServer, MeasuredDatabase, MeasuredDatabaseOptions, MeasuredDatabaseStorage, MemoryKeyValueStoreOptions, PluginTestHarnessOptions, RELEASE_CATALOG_DOWNLOAD_HASH, RELEASE_CATALOG_MANIFEST_URIS, RELEASE_CATALOG_STORAGE_FIXTURES, ReadBudgetAdapterContext, ReadBudgetSuiteOptions, RowsExamined, SqlExecutorLike, SqlQuery, SqlStatementLike, StorageAdapterTestSuiteOptions, conformanceCounters, conformanceExpiring, conformanceItems, createAdminApiTestClient, createBundleEventRowFixture, createBundleFixture, createBundlePatchRowFixture, createBundleRowFixture, createChannelRowFixture, createHandlerHttpTestClient, createHttpTestClient, createMeasuredDatabase, createMemoryKeyValueStore, createPluginTestHarness, createReleaseCatalogTestStorage, createReleaseRowFixture, expectInsightsIndex, insightsTestSuite, jsonRequest, mysqlRowsExamined, postgresRowsExamined, releaseCatalogArtifact, releaseCatalogDownloadUrl, runContentionHarness, setupAggregateBatchingTestSuite, setupBundleMethodsTestSuite, setupCoreAdminTestSuite, setupDatabaseAdapterConformanceSuite, setupDatabaseTestSuite, setupInsightsHttpTestSuite, setupInsightsModelTestSuite, setupReadBudgetTestSuite, setupReleaseCatalogTestSuite, setupSemverSatisfiesTestSuite, setupStorageAdapterTestSuite, startHttpTestServer, storeBundles, toDeployBundle, withAdapterLatency };