# 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`, 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 }; failed: string[] }`. Defaults preserve compat. - MODIFIED: `HealthPoller.poll()` is now generic over `TConfig` and accepts `Pick, "name" | "healthCheck">` instead of widening to `ServiceFixture>`. - MODIFIED: `skipIfUnavailable()` 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.