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,112 @@
# TestEnvironment
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
`TestEnvironment` is the orchestrator for integration test suites that need several external services running concurrently. It accepts a named map of `ServiceFixture` instances, starts them all in parallel during `setup()`, records which ones failed to start, and stops the successful ones in reverse order during `teardown()`. This reverse-teardown order prevents cascading failures in services that depend on each other (e.g. stopping a connection pool before closing its database).
## Features
- **Parallel startup** — all fixtures are started with `Promise.allSettled`, so a slow service does not block faster ones.
- **Graceful failure handling** — if a fixture's `start()` throws, its name is added to the `failed` list rather than propagating the error; tests can then use `skipIfUnavailable` to opt out.
- **Reverse-order teardown** — fixtures are stopped in the reverse of their start-success order, respecting implicit dependency chains.
- **Swallowed stop errors** — `stop()` errors are caught and discarded so a single broken fixture does not prevent the rest from cleaning up.
- **Safe empty teardown** — calling `teardown()` before `setup()` (or when all fixtures failed) is a no-op.
- **Protected internals** — `fixtures`, `options`, and `started` are `protected` to allow subclassing.
## Usage: Basic Example
```ts
import { TestEnvironment } from "@suaveplan/testing-integration/environment/test-environment";
const env = new TestEnvironment({
redis: redisFixture,
postgres: postgresFixture,
});
const { configs, failed } = await env.setup();
if (failed.length > 0) {
console.warn("Some fixtures failed to start:", failed);
}
console.log(configs.redis); // { url: "redis://localhost:6380" }
console.log(configs.postgres); // { host: "localhost", port: 5433 }
await env.teardown();
```
## Usage: Advanced Example
Using `TestEnvironment` with `skipIfUnavailable` via the global-setup bridge:
```ts
// vitest.config.ts
import { createIntegrationSetup } from "@suaveplan/testing-integration/setup/create-integration-setup";
import { redisFixture } from "./fixtures/redis.js";
import { postgresFixture } from "./fixtures/postgres.js";
const { globalSetup, inject } = createIntegrationSetup({
redis: redisFixture,
postgres: postgresFixture,
});
export default defineConfig({
test: {
globalSetup: [globalSetup],
},
});
// my.test.ts
import { beforeEach } from "vitest";
import { skipIfUnavailable } from "@suaveplan/testing-integration/helpers/skip-if-unavailable";
beforeEach(skipIfUnavailable("redis"));
it("stores a value in redis", async () => {
// guaranteed: redis was available when setup ran
});
```
## API Reference
### `TestEnvironment`
**Constructor:** `new TestEnvironment(fixtures, options?)`
| Parameter | Type | Description |
|---|---|---|
| `fixtures` | `Record<string, ServiceFixture<…>>` | Named map of fixtures to manage. |
| `options` | `TestEnvironmentOptions` | Optional configuration (currently `timeoutMs` reserved). |
**Methods:**
| Method | Signature | Description |
|---|---|---|
| `setup` | `() => Promise<TestEnvironmentSetupResult>` | Starts all fixtures in parallel. Returns configs and failed list. |
| `teardown` | `() => Promise<void>` | Stops all started fixtures in reverse order. |
### `TestEnvironmentSetupResult`
| Property | Type | Description |
|---|---|---|
| `configs` | `Record<string, Record<string, unknown>>` | Config objects keyed by fixture key for successful fixtures. |
| `failed` | `string[]` | Names of fixtures whose `start()` threw. |
### `TestEnvironmentOptions`
| Property | Type | Description |
|---|---|---|
| `timeoutMs` | `number?` | Reserved for future per-fixture start timeout. |
### `TestEnvironmentError`
Re-exported from `health-poller`. Use `instanceof TestEnvironmentError` to discriminate environment errors.
## Implementation Notes
`Promise.allSettled` is used instead of `Promise.all` so that one failing fixture does not prevent others from starting. The index of each settled result maps 1:1 with the `entries` array, so the correct fixture name can be attributed to each failure.
The `started` array tracks fixture **names** (not keys) in insertion order. During teardown, names are reversed and matched against `Object.values(this.fixtures)` by name. This means duplicate names across different keys would cause a teardown ambiguity — callers should ensure fixture names are unique within a `TestEnvironment` instance.