Files
2026-05-27 23:29:53 +01:00

6.3 KiB

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