Move docs/ package mirror into document-navigator plugin
Relocate the ~6k-file docs tree from the repo root into plugins/document-navigator/docs/ so the plugin is self-contained and portable. docs/superpowers/ (specs and plans) stays at the repo root. Moved via cp+rm rather than git mv because directory rename() fails with EPERM on this WSL /mnt DrvFs mount; git rename-detection pairs the identical-content files at commit time. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
# `testing/conformance`
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
The `testing/conformance` module exports `runRouterConformance(factory)` —
|
||||
the canonical conformance harness every concrete router adapter MUST pass.
|
||||
Stages 4-7 deliver adapters (`api-router-hono`, `api-router-in-memory`, the
|
||||
worker-bridge router); each pins to this harness so behaviour stays
|
||||
identical across the matrix.
|
||||
|
||||
The harness exercises every required surface: verb registration, mount
|
||||
rewriting, middleware chain ordering, route-collision rejection, param
|
||||
parsing, blue-green slot lifecycle, and error propagation. Adapters that
|
||||
add their own behaviours layer those tests on top — never instead of — the
|
||||
harness suite.
|
||||
|
||||
## Features
|
||||
|
||||
- `RouterConformanceFactory` — `{ router: () => Router, slot: (plugin, handler) => BlueGreenSlot, label: string }`.
|
||||
- One `describe` block per factory invocation, labelled with `factory.label`.
|
||||
- Eight required `it` cases — verb declarations, mount, middleware
|
||||
ordering, collision rejection, param parse, slot lifecycle, flip-with-no-shadow,
|
||||
error propagation.
|
||||
- Reuses `composeMiddleware` and the reference `Request`/`Response` so
|
||||
adapters get the canonical chain semantics for free.
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
import { runRouterConformance } from "@suaveplan/api-router/testing";
|
||||
import { Router } from "@suaveplan/api-router/router";
|
||||
import { InMemoryBlueGreenSlot } from "@suaveplan/api-router/slot";
|
||||
|
||||
runRouterConformance({
|
||||
label: "InMemory",
|
||||
router: () => new Router({ plugin: "test" }),
|
||||
slot: (plugin, handler) => new InMemoryBlueGreenSlot(plugin, handler),
|
||||
});
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
```ts
|
||||
// Inside an adapter package (e.g. api-router-hono):
|
||||
import { runRouterConformance } from "@suaveplan/api-router/testing";
|
||||
import { HonoRouter } from "./hono-router.js";
|
||||
import { InMemoryBlueGreenSlot } from "@suaveplan/api-router/slot";
|
||||
|
||||
// Adapter-specific tests
|
||||
describe("HonoRouter — adapter-specific", () => {
|
||||
/* … hono-only assertions … */
|
||||
});
|
||||
|
||||
// Then run the canonical suite
|
||||
runRouterConformance({
|
||||
label: "Hono",
|
||||
router: () => new HonoRouter({ plugin: "test" }),
|
||||
slot: (plugin, handler) => new InMemoryBlueGreenSlot(plugin, handler),
|
||||
});
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `runRouterConformance(factory: RouterConformanceFactory): void` — drops
|
||||
a fully-formed `describe` block into the host test file. The block
|
||||
contains eight `it` cases covering every required surface.
|
||||
- `RouterConformanceFactory` — `{ router, slot, label }`.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The harness imports `@suaveplan/testing/runner` so the same source file
|
||||
runs under both Vitest and `bun:test` (per the project's runner
|
||||
abstraction). Each `it` carries the standard `{ timeout: 5000 }` envelope
|
||||
so any deadlock in an adapter's implementation surfaces as a clean
|
||||
timeout failure rather than an indefinitely-hanging test run.
|
||||
|
||||
The `slot` factory parameter exists so adapters that have their own
|
||||
`BlueGreenSlot` implementation (e.g. a worker-RPC-backed one) can wire it
|
||||
into the same lifecycle tests. Adapters without a custom slot
|
||||
implementation pass `InMemoryBlueGreenSlot` from this package — the
|
||||
reference impl is intentionally complete enough to be used directly in
|
||||
production-equivalent test scenarios.
|
||||
|
||||
The harness deliberately uses synthetic `RouterRequest` objects built
|
||||
with `MapHeaders` — the conformance contract is router-level, not
|
||||
adapter-intake-level. Adapters that want to exercise their wire intake
|
||||
(parsing, body decoding) add those tests separately.
|
||||
@@ -0,0 +1,115 @@
|
||||
# `pipeline-router-conformance` — adapter test harness
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
`runPipelineRouterConformance(factory)` is the canonical contract suite
|
||||
every concrete `PipelineRouter` adapter MUST pass. It is the
|
||||
complement of `runRouterConformance` from `@suaveplan/api-router/testing`
|
||||
— that harness covers the abstract `Router` surface; this one covers the
|
||||
host-pipeline surface that `@suaveplan/api-host`'s `RequestPipeline`
|
||||
actually consumes at runtime. The harness lives in `@suaveplan/api-router`
|
||||
because the `PipelineRouter` contract itself lives in
|
||||
`@suaveplan/api-types/pipeline-router` — the zero-runtime contract
|
||||
package every adapter and the host orchestration core depend on. The
|
||||
harness shipping from api-router (rather than from api-host) keeps every
|
||||
router adapter (Hono, in-memory, Stage-7 engines) free of a build-graph
|
||||
edge on the orchestration core; api-host re-exports the harness from
|
||||
`./testing` for back-compat.
|
||||
|
||||
## Features
|
||||
|
||||
- **Factory-driven** — the harness asks the adapter for a fresh
|
||||
`(router, register)` pair on every test, so cross-test state can never
|
||||
leak.
|
||||
- **Reload support** — an optional `reload()` hook on the factory lets
|
||||
the harness verify plugin ownership survives a hot-reload cycle.
|
||||
- **Six required cases** — register → match, 404, statusMap propagation,
|
||||
plugin ownership across reload, param parsing, collision detection.
|
||||
- **No runtime peer-dep** — the harness imports only
|
||||
`@suaveplan/testing/runner` plus the contract's structural types, so
|
||||
any adapter (Hono, in-memory, worker bridge) can wire it in without
|
||||
pulling extra Hono / Bun runtime peer.
|
||||
- **Back-compat aliased** — re-exported from
|
||||
`@suaveplan/api-host/testing` so existing adapter wiring continues to
|
||||
work without code changes.
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
import { runPipelineRouterConformance } from "@suaveplan/api-router/testing/pipeline-router-conformance";
|
||||
import { HonoRouter } from "@suaveplan/api-router-hono/adapter";
|
||||
|
||||
runPipelineRouterConformance({
|
||||
label: "HonoRouter pipeline facet",
|
||||
build: () => {
|
||||
const r = new HonoRouter();
|
||||
return {
|
||||
router: { match: (method, url) => r.matchPipeline(method, url) },
|
||||
register: (reg) => r.registerPipelineRoute(reg),
|
||||
};
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
```ts
|
||||
import { runPipelineRouterConformance } from "@suaveplan/api-router/testing/pipeline-router-conformance";
|
||||
import { HonoRouter } from "@suaveplan/api-router-hono/adapter";
|
||||
|
||||
// Verify the reload-cycle path — the harness replays registrations
|
||||
// against a fresh router and asserts plugin ownership survives.
|
||||
runPipelineRouterConformance({
|
||||
label: "HonoRouter pipeline facet w/ reload",
|
||||
build: () => {
|
||||
let r = new HonoRouter();
|
||||
const replay: Array<Parameters<typeof r.registerPipelineRoute>[0]> = [];
|
||||
return {
|
||||
router: {
|
||||
// Bind `match` to the CURRENT `r` so the call goes through
|
||||
// the post-reload instance every time.
|
||||
match: (method, url) => r.matchPipeline(method, url),
|
||||
},
|
||||
register: (reg) => {
|
||||
r.registerPipelineRoute(reg);
|
||||
replay.push(reg);
|
||||
},
|
||||
reload: () => {
|
||||
r = new HonoRouter();
|
||||
for (const reg of replay) r.registerPipelineRoute(reg);
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `interface PipelineRouterConformanceFactory` —
|
||||
- `label: string`
|
||||
- `build(): { router: PipelineRouter; register: (reg: PipelineRouteRegistration) => void; reload?: () => void }`
|
||||
- `interface PipelineRouteRegistration` — `{ plugin, method, path, handler, options?, statusMap? }`.
|
||||
- `function runPipelineRouterConformance(factory): void` — declares one
|
||||
`describe()` block with six required `it()` cases.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The harness deliberately uses the structural `PipelineRouter` interface
|
||||
exported from `@suaveplan/api-types/pipeline-router` — adapters do not need to
|
||||
instance-check, and bag types like a "router with `registerPipelineRoute`"
|
||||
never appear in the contract. This keeps the harness portable across
|
||||
radically different adapter shapes (Hono, radix, worker-bridge).
|
||||
|
||||
The reload-cycle assertion is gated behind `factory.reload?.()` — adapters
|
||||
that have no concept of reload (e.g. a purely-static in-memory table)
|
||||
still pass the suite by omitting the hook. The harness then asserts the
|
||||
match against the same single-build router, which already covers the
|
||||
plugin-ownership invariant for static cases.
|
||||
|
||||
All `it()` cases declare an explicit `{ timeout: 5000 }` per
|
||||
testing rules. No `vi.mock`/`jest.mock` or other mock primitives are used
|
||||
— the harness drives a real adapter through real registrations.
|
||||
Reference in New Issue
Block a user