first commit

This commit is contained in:
2026-05-29 08:20:56 +01:00
commit 949299f58d
6368 changed files with 527176 additions and 0 deletions

View File

@@ -0,0 +1,101 @@
# ServiceFixture
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
`ServiceFixture<TConfig>` is the foundational contract for integration test service fixtures in Suaveplan. It defines a three-phase lifecycle — `start`, `healthCheck`, and `stop` — that allows `TestEnvironment` to manage any number of external services (containers, mock servers, in-process stores, etc.) uniformly. By coding against this interface rather than concrete implementations, consumers can swap fixture strategies without changing test setup code.
## Features
- **Generic configuration output** — `start()` returns a caller-defined `TConfig` (e.g. `{ url: string; port: number }`) so consuming tests receive strongly-typed connection details.
- **Three-phase lifecycle** — explicit `start` / `stop` / `healthCheck` methods map directly to `TestEnvironment`'s parallel startup and reverse-order teardown algorithms.
- **`InferConfig<F>` utility type** — extracts the `TConfig` type parameter from any `ServiceFixture` implementation at compile time, enabling generic helper functions.
- **Zero runtime code** — the module is types-only; no JavaScript is emitted for this file, so it contributes nothing to bundle size.
- **Idempotent stop contract** — implementations are required to tolerate repeated `stop()` calls without throwing.
## Usage: Basic Example
```ts
import type { ServiceFixture } from "@suaveplan/testing-integration/fixture/service-fixture";
interface RedisConfig {
url: string;
port: number;
}
class MemoryRedisFixture implements ServiceFixture<RedisConfig> {
readonly name = "memory-redis";
async start(): Promise<RedisConfig> {
// spin up an in-process redis-compatible store
return { url: "redis://localhost", port: 6380 };
}
async stop(): Promise<void> {
// flush and close the store
}
async healthCheck(): Promise<boolean> {
return true; // in-process is always ready
}
}
```
## Usage: Advanced Example
Using `InferConfig` to write generic helpers that work with any fixture:
```ts
import type {
InferConfig,
ServiceFixture,
} from "@suaveplan/testing-integration/fixture/service-fixture";
interface PostgresConfig {
host: string;
port: number;
database: string;
}
interface PostgresFixture extends ServiceFixture<PostgresConfig> {
readonly runMigrations: () => Promise<void>;
}
// InferConfig pulls PostgresConfig out of PostgresFixture at compile time
type Config = InferConfig<PostgresFixture>; // { host: string; port: number; database: string }
function buildConnectionString<F extends ServiceFixture<Record<string, unknown>>>(
config: InferConfig<F> & { host: string; port: number },
): string {
return `${config.host}:${config.port}`;
}
```
## API Reference
### `ServiceFixture<TConfig>`
| Member | Type | Description |
|---|---|---|
| `name` | `readonly string` | Human-readable identifier used in error messages and the fixture registry. |
| `start()` | `() => Promise<TConfig>` | Starts the service and returns connection config. Called by `TestEnvironment.setup()`. |
| `stop()` | `() => Promise<void>` | Stops the service and releases resources. Must be idempotent. |
| `healthCheck()` | `() => Promise<boolean>` | Returns `true` when the service is ready; `false` otherwise. Must not throw. |
### `InferConfig<F>`
Extracts `TConfig` from `ServiceFixture<TConfig>`. Returns `never` for any type that is not a `ServiceFixture` subtype.
**Type parameters:**
- `F` — the concrete fixture type to inspect
## Implementation Notes
`TConfig` is constrained to `Record<string, unknown>` rather than a plain `object` to ensure callers always return a plain serialisable map — not a class instance or primitive. This constraint is enforced at the type level only; no runtime validation is performed here (validation lives in `TestEnvironment`).
The `healthCheck` method is intentionally separate from `start` to support fixtures that need to start a process first and then wait for it to become healthy (e.g. a Docker container). `HealthPoller` uses this method in a polling loop with configurable timeout and interval.
`InferConfig` is implemented as a conditional type (`F extends ServiceFixture<infer C> ? C : never`) rather than a mapped type to keep inference simple and to return `never` for non-fixture types — a common pattern when building generic test helpers.