Initial Commit
This commit is contained in:
@@ -0,0 +1,97 @@
|
||||
# `testing`
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
This module provides the in-memory test harness for
|
||||
`@suaveplan/api-db-worker-proxy`. It exposes `createInMemoryAdminDb`
|
||||
— a deterministic `AdminDatabaseLike` backed by a `Map` — and
|
||||
`createDbProxyHarness`, which pairs that adapter with a real
|
||||
`MessageChannel`, a `createDbProxyServer` on one port, and a
|
||||
`createDbProxyClient` on the other. Tests can drive the full
|
||||
client-port-server-adapter chain end-to-end without a real DB and
|
||||
without any mocking framework.
|
||||
|
||||
The harness exists in `src/testing/` (rather than `tests/`) so it
|
||||
can be imported by downstream Stage-4 adapter packages from the
|
||||
package's `"./testing"` subpath export.
|
||||
|
||||
## Features
|
||||
|
||||
- `createInMemoryAdminDb({ engine?, seed?, cursorDelayMs? })` — a
|
||||
full `AdminDatabaseLike` implementation including `prepare`,
|
||||
`transaction`, and a synthetic `SELECT-CURSOR *` that returns an
|
||||
`AsyncIterable<MemoryRow>` for cursor streaming tests.
|
||||
- `createDbProxyHarness(opts)` — bundles adapter + ports + server +
|
||||
client into one disposable handle.
|
||||
- Tiny command DSL (`INSERT <id>`, `DELETE <id>`, `SELECT *`,
|
||||
`SELECT-CURSOR *`, `COUNT`, `SELECT-SCALAR`, `THROW`) chosen so
|
||||
every code path in the proxy can be exercised without parsing real
|
||||
SQL.
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
import { createDbProxyHarness } from "@suaveplan/api-db-worker-proxy/testing";
|
||||
|
||||
const h = createDbProxyHarness({ seed: [["a", { v: 1 }]] });
|
||||
try {
|
||||
expect(await h.client.query("SELECT *")).toEqual([{ v: 1 }]);
|
||||
} finally {
|
||||
await h.client.close();
|
||||
h.close();
|
||||
}
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
To assert that cursor cancellation propagates, set a per-row delay
|
||||
on the in-memory cursor and abort partway through:
|
||||
|
||||
```ts
|
||||
const h = createDbProxyHarness({
|
||||
seed: Array.from({ length: 100 }, (_, i) => [String(i), { i }]),
|
||||
cursorDelayMs: 5,
|
||||
});
|
||||
try {
|
||||
const ac = new AbortController();
|
||||
let n = 0;
|
||||
for await (const _row of h.client.cursor("SELECT-CURSOR *", undefined, {
|
||||
signal: ac.signal,
|
||||
})) {
|
||||
if (++n >= 3) {
|
||||
ac.abort();
|
||||
break;
|
||||
}
|
||||
}
|
||||
expect(n).toBe(3);
|
||||
} finally {
|
||||
await h.client.close();
|
||||
h.close();
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `createInMemoryAdminDb(opts?): InMemoryAdminDb` — `AdminDatabaseLike`
|
||||
plus `rows: ReadonlyMap<string, MemoryRow>` and `closed(): boolean`
|
||||
for assertions.
|
||||
- `createDbProxyHarness(opts?): DbProxyHarness` — `{ db, server,
|
||||
client, ports, close() }`.
|
||||
- `InMemoryAdminDbOptions` — `{ engine?, seed?, cursorDelayMs? }`.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The in-memory adapter ignores parameter placeholders for `INSERT` /
|
||||
`DELETE` and instead encodes the row key in the SQL string. This is
|
||||
intentional — the harness exists to exercise the proxy plumbing, not
|
||||
to validate a real query language. Concrete adapters (Stage 4) ship
|
||||
their own conformance suites under their own engine semantics.
|
||||
|
||||
The harness's `close()` does NOT call `client.close()` so tests can
|
||||
choose between observing graceful close (`await client.close()`) and
|
||||
abrupt disconnect (`client.dispose()`, then `harness.close()`). Both
|
||||
paths are first-class.
|
||||
Reference in New Issue
Block a user