first commit
This commit is contained in:
43
docs/testkit/testing/testing-integration-postgres/README.md
Normal file
43
docs/testkit/testing/testing-integration-postgres/README.md
Normal file
@@ -0,0 +1,43 @@
|
||||
# @suaveplan/testing-integration-postgres - PostgreSQL 16 testcontainers fixture for @suaveplan/testing-integration
|
||||
|
||||
## Summary
|
||||
|
||||
PostgreSQL 16 testcontainers fixture for `@suaveplan/testing-integration`.
|
||||
|
||||
Implements `ServiceFixture<PostgresConfig>` — manages a PostgreSQL container lifecycle with env-bridge passthrough and `pg_isready` health checks. Returns a plain config; consumers bring their own `pg` client.
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
## Documentation tree
|
||||
|
||||
- [fixture](./src/fixture/postgres-fixture.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
|
||||
- [postgres-fixture](./src/fixture/postgres-fixture.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
|
||||
<!-- end:auto-docs --> - [postgres-fixture](./src/fixture/postgres-fixture.md) — <!-- begin:auto-docs --> _(no submodules)_- [postgres-fixture](./src/fixture/postgres-fixture.md) — <!-- begin:auto-docs --> _(no submodules)_- [postgres-fixture](./src/fixture/postgres-fixture.md) — <!-- begin:auto-docs --> _(no submodules)_- [postgres-fixture](./src/fixture/postgres-fixture.md) — <!-- begin:auto-docs --> _(no submodules)_
|
||||
## Install
|
||||
|
||||
```bash
|
||||
bun add -D @suaveplan/testing-integration-postgres
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
|
||||
```typescript
|
||||
import { PostgresFixture } from "@suaveplan/testing-integration-postgres";
|
||||
|
||||
const fixture = new PostgresFixture();
|
||||
const config = await fixture.start();
|
||||
// config.url, config.host, config.port, config.database, config.username, config.password
|
||||
await fixture.stop();
|
||||
```
|
||||
|
||||
## Env-bridge passthrough
|
||||
|
||||
Set the env var to skip container startup:
|
||||
|
||||
```text
|
||||
POSTGRES_URL=postgresql://test:test@localhost:5432/test
|
||||
```
|
||||
|
||||
## Docs
|
||||
|
||||
- [PostgresFixture](src/fixture/postgres-fixture.md)
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
# PostgresFixture
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
## Purpose
|
||||
|
||||
`PostgresFixture` is an integration test fixture that manages the full lifecycle of a PostgreSQL 16 container using testcontainers. It implements the `ServiceFixture<PostgresConfig>` interface from `@suaveplan/testing-integration`, providing a ready-to-use PostgreSQL instance for tests with env-bridge passthrough, in-container health checking via `pg_isready`, and full configuration control over database name, username, and password.
|
||||
|
||||
The fixture intentionally excludes the `pg` client from its dependencies — it returns a plain `PostgresConfig` object containing a connection URL and parsed components. Consumers provide their own PostgreSQL client, keeping the fixture lightweight and client-agnostic.
|
||||
|
||||
## Features
|
||||
|
||||
- **Env-bridge passthrough**: when `POSTGRES_URL` is set and non-empty, the fixture parses the URL into a `PostgresConfig` and returns immediately without starting Docker.
|
||||
- **`pg_isready` health check**: runs `pg_isready -U postgres` inside the container — no external pg client or TCP connection required.
|
||||
- **Injectable exec function**: the `execFn` option allows unit tests to stub the pg_isready call without a real PostgreSQL container.
|
||||
- **Injectable container factory**: the `containerFactory` option allows unit tests to swap in a fake container without Docker.
|
||||
- **Configurable db/user/password**: override defaults via options; values flow into container environment variables and the returned connection URL.
|
||||
- **URL parsing for passthrough**: `new URL(url)` extracts host, port, database, username, and password from the `POSTGRES_URL` env var.
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```typescript
|
||||
import { PostgresFixture } from "@suaveplan/testing-integration-postgres";
|
||||
|
||||
const fixture = new PostgresFixture();
|
||||
const config = await fixture.start();
|
||||
|
||||
// config.url → "postgresql://test:test@localhost:<randomPort>/test"
|
||||
// config.host → "localhost"
|
||||
// config.port → random ephemeral port
|
||||
// config.database → "test"
|
||||
// config.username → "test"
|
||||
// config.password → "test"
|
||||
|
||||
await fixture.stop();
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```typescript
|
||||
import { PostgresFixture } from "@suaveplan/testing-integration-postgres";
|
||||
import postgres from "postgres";
|
||||
|
||||
const fixture = new PostgresFixture({
|
||||
database: "myapp",
|
||||
username: "appuser",
|
||||
password: "s3cr3t",
|
||||
});
|
||||
|
||||
const config = await fixture.start();
|
||||
const sql = postgres(config.url);
|
||||
|
||||
// Run migrations, seed data, run tests...
|
||||
await sql`CREATE TABLE users (id serial PRIMARY KEY, name text)`;
|
||||
await sql.end();
|
||||
|
||||
await fixture.stop();
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `class PostgresFixture`
|
||||
|
||||
Implements `ServiceFixture<PostgresConfig>`.
|
||||
|
||||
#### `constructor(options?: PostgresFixtureOptions)`
|
||||
|
||||
Creates a new fixture instance. All options are optional.
|
||||
|
||||
#### `name: string`
|
||||
|
||||
Always `"postgres"`. Used by `TestEnvironment` as the registry key.
|
||||
|
||||
#### `start(): Promise<PostgresConfig>`
|
||||
|
||||
Starts the service. Returns the runtime configuration. In passthrough mode (when `POSTGRES_URL` env var is set), returns immediately with parsed config. Otherwise starts a testcontainers container with the configured environment variables.
|
||||
|
||||
#### `stop(): Promise<void>`
|
||||
|
||||
Stops the container. No-op in passthrough mode or when not started.
|
||||
|
||||
#### `healthCheck(): Promise<boolean>`
|
||||
|
||||
Runs `pg_isready -U postgres` inside the container and returns `true` when exit code is `0`. Returns `false` if no container is active.
|
||||
|
||||
### `interface PostgresConfig`
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `url` | `string` | Full connection URL: `postgresql://user:pass@localhost:<port>/db` |
|
||||
| `host` | `string` | Hostname |
|
||||
| `port` | `number` | Port number |
|
||||
| `database` | `string` | Database name |
|
||||
| `username` | `string` | Username |
|
||||
| `password` | `string` | Password |
|
||||
|
||||
### `interface PostgresFixtureOptions`
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `image?` | `string` | Docker image override. Defaults to `postgres:16-alpine`. |
|
||||
| `database?` | `string` | Database name override. Defaults to `"test"`. |
|
||||
| `username?` | `string` | Username override. Defaults to `"test"`. |
|
||||
| `password?` | `string` | Password override. Defaults to `"test"`. |
|
||||
| `containerFactory?` | `(image: string) => GenericContainer` | Injectable factory for unit testing. |
|
||||
| `execFn?` | `(cmd: string[]) => Promise<{ exitCode: number }>` | Injectable executor for health check unit testing. |
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
- **Why `pg_isready` over TCP**: `pg_isready` is the canonical PostgreSQL health check tool, shipped in the official `postgres:16-alpine` image. It validates that PostgreSQL is accepting connections at the protocol level, which is more reliable than a bare TCP connect. It also avoids requiring a pg client in this fixture.
|
||||
- **Dynamic import of testcontainers**: `GenericContainer` is deferred to `start()` time so unit tests that provide a `containerFactory` do not trigger testcontainers initialization.
|
||||
- **URL construction**: the connection URL uses `postgresql://` (the PostgreSQL standard scheme) rather than `postgres://` to ensure compatibility with clients like `postgres` (node-postgres) and Prisma.
|
||||
- **Passthrough URL parsing**: `pathname.slice(1)` strips the leading `/` from the URL path to extract the database name correctly.
|
||||
Reference in New Issue
Block a user