Files
2026-05-27 23:29:53 +01:00

2.4 KiB

Spec delta: api-host

MODIFIED Requirements

Requirement: ./testing subpath ships the PipelineRouter conformance harness

The package SHALL declare ./testing as a subpath export AND ship runPipelineRouterConformance(factory) plus the supporting PipelineRouterConformanceFactory and PipelineRouteRegistration types. The harness SHALL exercise the host's PipelineRouter contract against any adapter via a factory-supplied (router, register, reload?) triple, covering: register → match round-trip, null on miss, statusMap propagation, plugin ownership across reload, :param parsing, and collision detection.

The placeholder STAGE_2_RESERVED sentinel SHALL remain exported for back-compat so existing detection logic (Object.keys(mod).length > 0 style) continues to discriminate "is the harness wired?" from "is the subpath reserved?".

The package's vite build SHALL emit dist/testing/testing.js AND dist/testing/pipeline-router-conformance/pipeline-router-conformance.js so the subpath actually resolves at runtime — these files were previously absent because the testing module was tree-shaken out of the single-entry build.

Scenario: harness drives a reference PipelineRouter through every required case

  • WHEN a test calls runPipelineRouterConformance({ label, build: () => ({ router, register, reload? }) })
  • THEN describe("PipelineRouter conformance — ${label}", ...) registers six it() cases AND all of them pass against any adapter whose register populates a backing table consulted by router.match

Scenario: harness asserts collision detection throws

  • WHEN the harness invokes register({ plugin: "p", method: "GET", path: "/x" }) followed by register({ plugin: "q", method: "GET", path: "/x" })
  • THEN the second call SHALL throw — the harness uses expect(...).toThrow() without prescribing the error class so any adapter-specific typed error satisfies the contract

Scenario: harness asserts plugin ownership survives reload

  • WHEN the factory exposes a reload() hook, the harness registers a route, calls reload(), then calls router.match(...)
  • THEN the matched route's plugin AND params survive the reload cycle

Verified by: src/testing/pipeline-router-conformance/pipeline-router-conformance.test.ts AND (downstream) @suaveplan/api-router-hono's src/pipeline/pipeline.conformance.test.ts