Files
suaveplan/openspec/archive/2026-04-19-enterprise-testing/tasks.md
2026-05-27 23:29:53 +01:00

11 KiB
Raw Blame History

Archived 2026-04-19: all tasks retroactively ticked; package shipped under universal/dev/testing.

1. Surface inventory & catalog setup

  • 1.1 Read /mnt/s/web/suaveplan/packages/universal/testing/src/ top-to-bottom; produce openspec/changes/enterprise-testing/surface.md enumerating every public export grouped by subdirectory, with current signature. This is the preservation contract for the rewrite.
  • 1.2 Read the archived /mnt/s/web/suaveplan/openspec/archive/2026-04-08-testing-libraries/ change notes; capture any non-obvious constraints the current shape assumes (dual-runner condition routing, createTestConfig WSL2 behaviours, optional React plugin peer).
  • 1.3 Extend root package.json catalogs: add zod@^4.1.5 to catalogs.forms, create catalogs.observability with @opentelemetry/api@^1.9.0, add fast-check@^3.23.2 and mitata@^1.0.34 to catalogs.testing. Run bun install, confirm lockfile updates cleanly.

2. Package skeleton

  • 2.1 Create packages/universal/testing/ with package.json (version 0.1.0, type: "module", sideEffects: false, dual-runner exports map from design D5, external catalog deps only per spec "No @suaveplan/* workspace dependencies")
  • 2.2 Create tsconfig.lib.json extending ../../../tsconfig.base.json with outDir: ./dist and include: ["src"]
  • 2.3 Create vite.config.ts producing preserveModules: true ESM output so every source file maps to a dist file (required for subpath exports to resolve)
  • 2.4 Create vitest.config.ts using a minimal inline config (cannot self-reference createTestConfig yet — this is the package that defines it)
  • 2.5 Create README.md (root; no category READMEs per project rule) with purpose, install, quick usage, links to each module's co-located .md
  • 2.6 Add @suaveplan/testing row to CLAUDE.md package listing (when the section is re-populated) and to STRUCTURE.yml (when the file exists)

3. Foundational primitives (context, errors, observability)

  • 3.1 Implement src/context/context.ts: Clock and Rng interfaces; defaultClock (reads Date.now / performance.now); cryptoRng (reads crypto.getRandomValues); seededRng(seed) (xorshift or similar deterministic)
  • 3.2 Co-locate src/context/context.test.ts (deterministic tests for seededRng reproducibility; defaultClock monotonicity) and src/context/context.md (≥ 200 words, simple + advanced examples)
  • 3.3 Co-locate src/context/context.schemas.ts with Zod schemas for Clock / Rng shapes (for callers who want to runtime-check they've passed a compatible object)
  • 3.4 Implement src/errors/testing-error.ts: TestingError base class + per-domain subclasses (ClockError, FixturesError, FactoryError, StoreError, HelpersError, BuilderError, ConfigError) as per design D2
  • 3.5 Implement src/errors/codes.ts: string-literal-union constants for every TESTING.<DOMAIN>.<REASON> code used in the package
  • 3.6 Co-locate tests (instanceof branch coverage, cause / context round-trip) + .md docs + *.schemas.ts
  • 3.7 Implement src/observability/tracer.ts and src/observability/with-observability.ts per design D4: counters, histograms, spans via @opentelemetry/api; span-only-for-iterative gate
  • 3.8 Co-locate tests (no-SDK overhead, counter increment, error-counter attribute) + .md docs

4. Public-surface modules (implementation, tests, docs, schemas)

Repeat the pattern for every module — implementation, test, schemas, md — preserving the public surface from §1.1:

  • 4.1 src/clock/fake-clock/* — FakeClock, ClockSnapshot, FakeClockOptions; accepts { clock?, rng?, signal? }; throws ClockError("TESTING.CLOCK.*", …); implements Clock interface
  • 4.2 src/builders/request-builder/* — RequestBuilder, get/post/put/patch/del; state-machine transitions throw BuilderError on invalid state
  • 4.3 src/builders/response-builder/* — ResponseBuilder, ok/created/noContent/badRequest/unauthorized/notFound/serverError
  • 4.4 src/factories/factory/* — defineFactory, Factory, BatchBuildResult, FactoryBuildOptions, FactoryDefaults, FactoryDefinition; traits plugin surface per design D7's factory analogue; buildBatch(n, { signal }) checks abort every 256 items
  • 4.5 src/fixtures/fixture-manager/* — FixtureManager, SetupAllResult, TeardownAllResult, FixtureDefinition, FixtureState, FixtureManagerPlugin; setupAll/teardownAll accept { signal }; plugin hooks fire around each fixture
  • 4.6 src/storage/memory-store/* — MemoryStore, MemoryStoreEvent, MemoryStoreStats, MemoryStoreOptions, StoreEntry; TTL expiry via injected clock; eviction sweeps honour signal
  • 4.7 src/helpers/test-helpers.ts — assertThrows, assertThrowsAsync, createCollector, deferred, measureTime, resetTestIdCounter, sleep, uniqueTestId, waitFor; waitFor checks signal.aborted every poll; all reads of time/randomness routed through context
  • 4.8 src/constants/constants.ts — DEFAULT_CLOCK_OPTIONS, DEFAULT_HTTP_STATUS, DEFAULT_RESPONSE_LATENCY_MS, DEFAULT_STORE_OPTIONS, DEFAULT_WAIT_OPTIONS, MAX_FIXTURES, MAX_STORE_ENTRIES, MAX_TIMERS
  • 4.9 src/types/testing.types.ts — shared public types; co-locate testing.types.md (types/ subdirectory requires .md per project convention)

5. Runner and config subpaths

  • 5.1 src/runner/vitest.ts — re-export vitest primitives under the default condition
  • 5.2 src/runner/bun-test.ts — bun-test shim exposing the same surface under the bun condition
  • 5.3 src/runner/mocks.ts — spy, stub, fn, mock, fakeClock, restoreMocks, restoreTimers backed by the host runner
  • 5.4 src/runner/index.ts — combined barrel for the default vitest path
  • 5.5 src/config/index.ts — createTestConfig() factory per design D9
  • 5.6 src/config/vitest-config.ts — vitest config builder (pool: forks, polyfills, fileParallelism)
  • 5.7 src/config/bun-test-config.ts — bun-test config builder
  • 5.8 src/config/detect-environment.ts, src/config/detect-runner.ts — runner + environment detection
  • 5.9 src/setup/polyfills.ts — side-effecting module installing happy-dom-needed globals on WSL2
  • 5.10 src/utils/index.ts — re-export every non-runner symbol for single-import convenience

6. Root barrel

  • 6.1 src/index.ts — explicit named re-exports (no export *) covering the full public surface; in the order of §1.1 so diffing against the surface inventory is trivial

7. Property-based tests (fast-check)

  • 7.1 FakeClock arithmetic properties (advance + schedule commute within a tick, no timer duplication)
  • 7.2 RequestBuilder / ResponseBuilder state-machine transitions (associativity of preset composition)
  • 7.3 Factory determinism under fixed rng
  • 7.4 MemoryStore TTL expiry monotonicity + capacity-limit invariant
  • 7.5 waitFor polling correctness under arbitrary predicate schedules
  • 7.6 seededRng reproducibility (same seed → same sequence)

8. Benchmarks (mitata)

  • 8.1 src/__benchmarks__/clock.bench.ts — FakeClock advance/schedule throughput
  • 8.2 src/__benchmarks__/builders.bench.ts — RequestBuilder and ResponseBuilder build throughput
  • 8.3 src/__benchmarks__/factory.bench.ts — Factory single build + batch build
  • 8.4 src/__benchmarks__/memory-store.bench.ts — set/get/evict under load
  • 8.5 src/__benchmarks__/helpers.bench.ts — waitFor overhead; assertThrows overhead
  • 8.6 src/__benchmarks__/observability.bench.ts — OTEL-wrapped vs unwrapped overhead comparison (enforces <5% in CI)
  • 8.7 benchmarks/baselines.json — initial baseline commit (reviewer-approved, separate from functional changes)
  • 8.8 CI script: run mitata 3×, take fastest, compare to baseline, fail >10% regression

9. Examples

  • 9.1 examples/basic-usage.ts — runnable script using Factory, FakeClock, waitFor
  • 9.2 examples/fixture-plugin.ts — runnable script registering a FixtureManagerPlugin
  • 9.3 examples/deterministic-factory.ts — runnable script demonstrating seededRng reproducibility
  • 9.4 examples/custom-clock.ts — runnable script swapping defaultClock for FakeClock end-to-end
  • 9.5 Verify every example runs via bun examples/<name>.ts without error

10. Tree-shake and dep-graph guards

  • 10.1 src/__tests__/tree-shake.test.ts — asserts that ./uint/clock (for example) does not pull MemoryStore, RequestBuilder, or Factory identifiers into its built output
  • 10.2 src/__tests__/dep-graph.test.ts — reads package.json and asserts no @suaveplan/* appears in dependencies, peerDependencies, or devDependencies
  • 10.3 src/__tests__/no-disallowed.test.ts — greps src/ for any, ts-ignore, ts-expect-error, TODO, FIXME, @deprecated, .skip, .only; fails on any match

11. Docs

  • 11.1 Verify every implementation file has a sibling .md of ≥ 200 words, simple + advanced examples, API reference, implementation notes
  • 11.2 Rewrite packages/universal/testing/README.md with purpose, install, quick usage, error-code index, observability reference, benchmark numbers, links to each module's .md
  • 11.3 Run a docs-coverage check script (inline in scripts/ or ad-hoc) confirming ≥ 90%

12. Security review

  • 12.1 Invoke the security-review skill on the final diff
  • 12.2 Address every finding (prototype pollution in MemoryStore.set, ReDoS in any regex helper, payload bombs in Factory.buildBatch); re-run until clean
  • 12.3 bun audit clean — zero advisories introduced by catalog additions

13. Pre-publish gate + publish + archive

  • 13.1 bunx turbo lint typecheck test --filter=@suaveplan/testing green (CLAUDE.md non-negotiable)
  • 13.2 bunx turbo test:coverage --filter=@suaveplan/testing meets ≥ 95% statement + branch
  • 13.3 bunx turbo lint typecheck across all catalogs reaching @suaveplan/testing — green (only @suaveplan/testing on genesis today; confirm build graph is clean)
  • 13.4 bun scripts/publish.ts --package @suaveplan/testing --dry-run produces a sane diff
  • 13.5 bun scripts/publish.ts --package @suaveplan/testing — publish @suaveplan/testing@0.0.0 to Verdaccio
  • 13.6 openspec validate enterprise-testing --type change — passes
  • 13.7 Fold the specs/testing/spec.md delta into openspec/specs/testing/spec.md (create) and move this change folder to openspec/archive/<yyyy-mm-dd>-enterprise-testing/
  • 13.8 Mark @suaveplan/testing ✅ Enterprise in the genesis README.md (when the file exists) and in openspec/project.md package taxonomy