90 lines
5.0 KiB
Markdown
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.
|