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

6.7 KiB

Why

@suaveplan/testing is the shared test infrastructure every other @suaveplan/* package imports from. As Layer 0 in the genesis dependency graph (zero @suaveplan/* workspace deps — rule #9 of openspec/project.md), every package downstream depends on it. Enterprise-quality testing unblocks enterprise quality everywhere else.

The enterprise specification at /mnt/s/web/suaveplan/enterprise/testing.md sets the bar; this change translates that spec into genesis form, preserving the full public surface established in the archived 2026-04-08-testing-libraries change (RequestBuilder, ResponseBuilder, FakeClock, Factory, FixtureManager, MemoryStore, helpers, dual-runner conditional exports, createTestConfig — full inventory in design.md) while upgrading internals to enterprise grade.

This is the first implementation change on the genesis branch. There is no prior genesis code to audit — the "current state" is empty. Design intent comes from the enterprise doc; surface intent comes from the existing implementation on main at /mnt/s/web/suaveplan/packages/universal/testing/src/.

What Changes

All requirements from enterprise/testing.md §3 are carried forward, with four explicit substitutions forced by genesis rule #9 (testing packages may not depend on any @suaveplan/* workspace package). Each substitution is called out inline below.

Carried forward verbatim

  • Universal purity — zero browser/Node/React deps in src/. Runs in Bun, Node, and modern browsers. Enforced by a tree-shake test that inspects the built bundle.
  • Zod schemas for all public function inputs and outputs, exported from sibling *.schemas.ts files alongside each implementation file. Zod is an external catalog dep (catalog:forms), not routed through @suaveplan/schema-registry.
  • Property-based tests (fast-check) for pure functions (clock arithmetic, builder composition, factory deterministic output given fixed RNG, memory-store TTL math, semver-ish helpers).
  • Deterministic behaviour — no Math.random() / Date.now() / performance.now() at module scope. Every function that reads time or randomness accepts an optional { clock?, rng?, signal? } options parameter. Zero-config defaults preserved.
  • Backpressure & cancellation — AbortSignal on every iterative / async helper (waitFor, FixtureManager.setupAll/teardownAll, Factory.build in batch mode, MemoryStore eviction sweeps), checked at bounded intervals (≤ 256 items or ≤ 1 KB).
  • Idempotency & retry primitives wired for any helper with network-ish retry semantics (e.g. waitFor polling).
  • Feature flags — any risky new behaviour gated behind an options flag with safe defaults.
  • Structured errors with stable codes of the form TESTING.<DOMAIN>.<REASON> (e.g. TESTING.CLOCK.MAX_TIMERS_EXCEEDED, TESTING.FIXTURES.SETUP_FAILED). Every thrown error carries { code, cause?, context? }.
  • Benchmarks via mitata (external catalog dep) under src/__benchmarks__/<domain>.bench.ts, baseline in benchmarks/baselines.json, CI gate at 10% regression floor.
  • Plugin / extension interface — FixtureManager accepts plugins implementing beforeEach/afterEach/onError hooks; Factory accepts traits that downstream packages can contribute without forking.
  • Observability surface — counters, histograms, spans for every public entrypoint. Gated: no-op when no OTEL SDK is registered; live when one is. Useful for diagnosing flaky test environments in CI without touching test source.
  • Security — input validation at the boundary via Zod, no prototype-pollution vectors in MemoryStore, dependency audit clean.
  • Migration story — N/A; genesis starts fresh at version 0.1.0.
  • Example / story — examples/ directory with runnable usage of each public entrypoint.

Substitutions (enterprise-doc → genesis form)

Enterprise doc requires Genesis substitution Why
Errors extend @suaveplan/error Local TestingError extends Error with the same { code, cause, context } shape, stable TESTING.<DOMAIN>.<REASON> codes Rule #9 — can't take @suaveplan/* deps. Shape-compatible so a future @suaveplan/error adapter can bridge without API change.
OTEL via @suaveplan/opentelemetry @opentelemetry/api (external, ~2 KB, no transitive Node deps). Spans/counters/histograms created via the standard API, no-op without a registered SDK Rule #9. @opentelemetry/api is the canonical public API spec; the internal package will wrap this at a later layer.
Schemas via @suaveplan/schema-registry zod direct (catalog:forms). Schemas exported from *.schemas.ts under stable names (e.g. BuilderSchemas.request.input) so a future schema-registry adapter can enumerate them Rule #9.
Clock via @suaveplan/datetime Local Clock interface in src/context/context.ts: { now(): number; hrtime(): bigint }. defaultClock reads Date.now() / performance.now() directly. FakeClock implements the same interface. Rule #9.
i18n via @suaveplan/i18n or hard-coded message keys English-only message fields. code is the machine contract; message is developer-facing. Matches the utils D10 precedent. Rule #9. Test helpers emit messages to developers in logs, not to end users.

Public surface preserved

Every export enumerated in design.md §Surface Inventory is retained unchanged in shape. Internals are rewritten; signatures gain the optional { clock?, rng?, signal? } options parameter where appropriate (purely additive — zero-config callers are unaffected).

Capabilities

New Capabilities

  • testing: shared test infrastructure for the genesis monorepo — test primitives (vitest + bun-test conditional export), mock API, request/response builders, FakeClock, Factory, FixtureManager, MemoryStore, async helpers, vitest config factory, polyfills. Layer 0 leaf.

Modified Capabilities

(none — genesis has no prior spec)

Impact

  • Affected code: /mnt/s/web/genesis/packages/universal/testing/** (fresh implementation).
  • Affected consumers: none on genesis yet; every subsequent genesis package depends on this one.
  • Version: 0.1.0 (genesis baseline; no bump until graduation cut).
  • Catalogs added / extended in root package.json: zod to catalogs.forms, @opentelemetry/api to a new catalogs.observability, fast-check and mitata to catalogs.testing.
  • Root package.json devDependencies addition: @types/bun already present; tooling catalogs unchanged.
  • New capability spec: openspec/specs/testing/spec.md (created on archive).
  • Pre-publish gate: full turbo lint typecheck test on @suaveplan/testing + istanbul coverage ≥ 95% before bun scripts/publish.ts --package @suaveplan/testing.