Initial Commit
This commit is contained in:
@@ -0,0 +1,120 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Layer 0 dep-graph isolation
|
||||
|
||||
The `@suaveplan/testing-integration-nats` package's `dependencies` MUST be exactly `@suaveplan/testing-integration` (`workspace:^`) and `testcontainers` (`catalog:integration`). No other `@suaveplan/*` runtime dep is permitted. `devDependencies` MAY include `@suaveplan/testing` for the package's own tests.
|
||||
|
||||
#### Scenario: Runtime closure clean
|
||||
|
||||
- **WHEN** `bunx turbo ls --filter=@suaveplan/testing-integration-nats...` is run
|
||||
- **THEN** the only `@suaveplan/*` package in the runtime closure is `@suaveplan/testing-integration`
|
||||
|
||||
### Requirement: Image-pinning policy
|
||||
|
||||
The default image MUST be `nats:2.10-alpine`. The `constants/index.ts` file MUST carry a `# TODO: replace with sha256:<digest>` comment until the digest is recorded.
|
||||
|
||||
#### Scenario: Pinned tag
|
||||
|
||||
- **WHEN** the constants file is read
|
||||
- **THEN** the image string equals `nats:2.10-alpine` AND a sha256 TODO line is present
|
||||
|
||||
### Requirement: Env-bridge passthrough
|
||||
|
||||
The fixture MUST consult `NATS_URL`, optional `NATS_CLUSTER_NAME`, and optional `NATS_JETSTREAM_DOMAIN`. When `NATS_URL` is present and non-empty, `start()` MUST skip container creation and return a handle whose config points at the live NATS server. Optional vars influence the returned config but are not required.
|
||||
|
||||
#### Scenario: Live NATS present
|
||||
|
||||
- **WHEN** `NATS_URL` is set
|
||||
- **THEN** `start()` returns without invoking `containerFactory` AND the returned config's `url` equals `NATS_URL`
|
||||
|
||||
#### Scenario: No env triggers container
|
||||
|
||||
- **WHEN** `NATS_URL` is unset or empty
|
||||
- **THEN** `start()` falls back to container mode
|
||||
|
||||
### Requirement: Healthcheck
|
||||
|
||||
The fixture MUST perform a TCP PING on the mapped client port (`4222` inside the container) via the injectable `tcpPingFn`. When the container exposes the monitoring endpoint (`-m 8222`), the fixture MUST also poll `HTTP GET /healthz` and treat a `200` response as ready. The default `healthTimeoutMs` MUST be `60000`. Callers MAY override.
|
||||
|
||||
#### Scenario: TCP ping succeeds
|
||||
|
||||
- **WHEN** `tcpPingFn(host, mappedClientPort)` returns true
|
||||
- **THEN** `start()` resolves once any pre-seeding has completed
|
||||
|
||||
#### Scenario: Healthcheck times out
|
||||
|
||||
- **WHEN** the TCP ping never succeeds within `healthTimeoutMs`
|
||||
- **THEN** `start()` rejects with code `INTEGRATION.NATS.HEALTHCHECK_TIMEOUT`
|
||||
|
||||
### Requirement: JetStream initialisation
|
||||
|
||||
When `jetstream: true` is supplied, the fixture MUST start `nats-server` with `-js` and pre-seed any supplied streams and durable consumers via `nats stream add` / `nats consumer add` invoked through the injectable `execFn` after the TCP-ping healthcheck passes. The fixture MUST NOT resolve `start()` until each declared stream reports `state.messages >= 0` via `nats stream info`.
|
||||
|
||||
#### Scenario: JetStream stream seeded
|
||||
|
||||
- **WHEN** `start({ jetstream: true, streams: [{ name: 'EVENTS', subjects: ['events.>'] }] })` resolves
|
||||
- **THEN** `nats stream info EVENTS` reports the stream exists with the declared subjects
|
||||
|
||||
### Requirement: Cluster mode
|
||||
|
||||
When `cluster: { name, routes }` is supplied, the fixture MUST start `nats-server` with `--cluster_name <name>` and the supplied `--routes` list. The fixture MUST verify cluster formation by calling `nats server list` via `execFn` and checking the cluster name matches.
|
||||
|
||||
#### Scenario: Cluster formed
|
||||
|
||||
- **WHEN** the fixture is started with a cluster config
|
||||
- **THEN** `nats server list` reports `cluster: <name>`
|
||||
|
||||
### Requirement: Authentication mode
|
||||
|
||||
The fixture MUST accept `auth: { user: string; password: string }` OR `auth: { token: string }` (mutually exclusive). When supplied, the fixture MUST start `nats-server` with the corresponding `--user`/`--pass` or `--auth` flags AND embed credentials in the returned `url` (`nats://user:pass@host:port` or `nats://token@host:port`).
|
||||
|
||||
#### Scenario: Token auth rejects bad token
|
||||
|
||||
- **WHEN** the fixture starts with `auth: { token: 'good' }` and a client connects with token `bad`
|
||||
- **THEN** the client connection is refused with a NATS authorization error
|
||||
|
||||
### Requirement: Config shape
|
||||
|
||||
The exported `NatsConfig` interface MUST include optional `jetstream?: boolean`, optional `cluster?: { name: string; routes: string[] }`, optional `auth?: { user: string; password: string } | { token: string }`, optional `streams?: Array<{ name: string; subjects: string[] }>`, optional `consumers?: Array<{ stream: string; durable: string }>`, and the resolved `url`. A sibling `nats-fixture.schemas.ts` MUST export a Zod `NatsConfigSchema` that validates the same shape including the `auth` discriminated union.
|
||||
|
||||
#### Scenario: Schema parity
|
||||
|
||||
- **WHEN** `NatsConfigSchema.safeParse` runs against a `NatsConfig` value
|
||||
- **THEN** valid configs parse OK AND mixing `user`/`password` with `token` in the same `auth` object fails with `ZodError`
|
||||
|
||||
### Requirement: Error-code prefix
|
||||
|
||||
All errors thrown from the fixture MUST carry a `code` matching `^INTEGRATION\.NATS\.[A-Z_]+$`.
|
||||
|
||||
#### Scenario: Coded error
|
||||
|
||||
- **WHEN** any fixture method throws
|
||||
- **THEN** `code` starts with `INTEGRATION.NATS.`
|
||||
|
||||
### Requirement: Injectable fakes
|
||||
|
||||
The fixture constructor MUST accept `containerFactory?: () => GenericContainer`, `tcpPingFn?: (host: string, port: number) => Promise<boolean>`, and `execFn?: (cmd: string[]) => Promise<{ stdout: string; stderr: string; exitCode: number }>`. Unit tests MUST drive 100% Istanbul branch coverage without Docker.
|
||||
|
||||
#### Scenario: Fake injection
|
||||
|
||||
- **WHEN** the fixture is constructed with all three fakes
|
||||
- **THEN** real Docker / real TCP / real exec are never invoked during the test
|
||||
|
||||
### Requirement: Plugin scenarios
|
||||
|
||||
The fixture MUST support, end-to-end against a real container, the following flows in its smoke test:
|
||||
|
||||
#### Scenario: Core pub/sub round-trip
|
||||
|
||||
- **WHEN** a subscriber subscribes to `test.>` and a publisher publishes to `test.hello`
|
||||
- **THEN** the subscriber receives the message within 1s
|
||||
|
||||
#### Scenario: JetStream durable consumer
|
||||
|
||||
- **WHEN** the fixture seeds `EVENTS` stream + `worker` durable consumer, a publisher publishes 10 messages, and the consumer pulls
|
||||
- **THEN** the consumer acks all 10 messages AND `nats consumer info EVENTS worker` reports `num_pending: 0`
|
||||
|
||||
#### Scenario: Request/reply
|
||||
|
||||
- **WHEN** a responder subscribes to `svc.echo` and replies with the request payload, and a client issues a request
|
||||
- **THEN** the client receives the original payload within the request timeout
|
||||
Reference in New Issue
Block a user