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

90 lines
5.0 KiB
Markdown

# testing-integration: spec authoring + generics hardening
**Package**: `@suaveplan/testing-integration` and the six variant fixtures
(`@suaveplan/testing-integration-{surrealdb,redis,postgres,mailpit,typesense,garage-s3}`)
**Status**: PROPOSED
**Type**: spec-conformance + refactor
## Why
The seven `testing-integration*` packages shipped without folded specs and with
several latent issues uncovered by the Slice-3 audit:
- **Rule 8 violation** — every variant listed `@suaveplan/testing-integration` in
`dependencies`, not `peerDependencies`. The core's API is meant to be brought
by the consumer; treating it as a hard dep duplicates copies in user
`node_modules` and makes the `instanceof TestEnvironmentError` checks fail
across realms.
- **`vitest` runtime import in core, devDep only** — `create-integration-setup.ts`
imported `inject` from `vitest`. With `vitest` only in `devDependencies`, an
installed-from-registry consumer would load a broken module.
- **Generic erasure** — `TestEnvironment.setup()`, `HealthPoller.poll()`, and
`skipIfUnavailable()` typed their fixture surface as
`Record<string, unknown>`, throwing away precise per-fixture config types.
- **`assertMail()` cast-based parsing** — the Mailpit response shape was
`as { messages?: ... }`-cast; a server payload regression silently surfaced
as a runtime `TypeError` deep in `m.To.some(...)`.
- **`GarageS3Fixture.waitForHealth` reimplemented `HealthPoller`** — divergent
timeout semantics + bespoke error type.
- **`_fixtureRegistry` and `_createIntegrationSetupWithInject` exported from
the public barrel** — leading-underscore naming is convention only; nothing
prevented downstream from importing them and pinning the package shape.
This change folds the seven packages' contracts into seven `openspec/specs/*`
files with `Verified by:` lines on every requirement, fixes the plumbing, and
threads precise generics end-to-end.
## What changes
- ADDED: `openspec/specs/testing-integration/spec.md` — folded spec for the core.
- ADDED: `openspec/specs/testing-integration-{surrealdb,redis,postgres,mailpit,typesense,garage-s3}/spec.md` — six folded specs for the variants.
- ADDED: `src/internal/` subdirectory (NOT in `package.json` `exports`) housing
`fixtureRegistry`, `createIntegrationSetupWithInject`, and
`createSkipIfUnavailableWithInject`. Public barrel no longer re-exports them.
- ADDED: `src/environment/test-environment.spec-conformance.test.ts` — pins
generic threading on `TestEnvironment.setup()`, `HealthPoller.poll()`,
`createIntegrationSetup`'s `inject()`, and `skipIfUnavailable()`.
- MODIFIED: `TestEnvironment` is now generic over `TFixtures extends DefaultFixtureMap`;
`setup()` returns `{ configs: { [K in keyof TFixtures]?: InferConfig<TFixtures[K]> }; failed: string[] }`. Defaults preserve compat.
- MODIFIED: `HealthPoller.poll()` is now generic over `TConfig` and accepts
`Pick<ServiceFixture<TConfig>, "name" | "healthCheck">` instead of widening to
`ServiceFixture<Record<string, unknown>>`.
- MODIFIED: `skipIfUnavailable<TName extends string = string>()` is now generic
over the fixture-name keyset for typed-registry pairing.
- MODIFIED: `assertMail()` validates Mailpit responses with a Zod schema and
throws `TestEnvironmentError` on shape mismatch, non-2xx, or timeout.
- MODIFIED: `GarageS3Fixture.waitForHealth()` delegates to `HealthPoller.poll(this)`.
- MODIFIED: every variant `package.json` moves `@suaveplan/testing-integration` and
`testcontainers` from `dependencies` to `peerDependencies` (and adds them to
`devDependencies` so workspace symlinks resolve in the variant's own tests).
- MODIFIED: core `package.json` adds `vitest` to `peerDependencies`.
- REMOVED: `_fixtureRegistry`, `_createIntegrationSetupWithInject`, and
`_createSkipIfUnavailableWithInject` from the public barrel. Replaced by the
package-private `internal/` modules.
## Out of scope
- Adding new fixture variants (Kafka, ClickHouse, etc.) — deferred.
- Changing the Vitest global-setup contract — deferred until Vitest 5 lands.
- Migrating `assertMail` to a generic mail-API client — deferred.
## Verification approach
Each requirement in every spec carries a `**Verified by:**` line naming an
existing `it(...)` title. Every claim about a generic is pinned by an
`expectTypeOf` assertion in
`src/environment/test-environment.spec-conformance.test.ts`. Coverage stays at
100/100/100/100 across all seven packages.
## Migration impact
- Variant packages (six fixtures) — consumers who imported
`_fixtureRegistry` / `_createIntegrationSetupWithInject` /
`_createSkipIfUnavailableWithInject` from `@suaveplan/testing-integration`
must migrate to the public `createIntegrationSetup` / `skipIfUnavailable`
API. The leading-underscore symbols were never part of the supported surface
(their JSDoc explicitly said "for testing only"); no in-repo consumer
imports them.
- `TestEnvironment` and `HealthPoller` callers — non-breaking; the new generics
default to the prior erased contract.