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-alpineAND 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_URLis set - THEN
start()returns without invokingcontainerFactoryAND the returned config'surlequalsNATS_URL
Scenario: No env triggers container
- WHEN
NATS_URLis 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 codeINTEGRATION.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 EVENTSreports 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 listreportscluster: <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 tokenbad - 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.safeParseruns against aNatsConfigvalue - THEN valid configs parse OK AND mixing
user/passwordwithtokenin the sameauthobject fails withZodError
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
codestarts withINTEGRATION.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 totest.hello - THEN the subscriber receives the message within 1s
Scenario: JetStream durable consumer
- WHEN the fixture seeds
EVENTSstream +workerdurable consumer, a publisher publishes 10 messages, and the consumer pulls - THEN the consumer acks all 10 messages AND
nats consumer info EVENTS workerreportsnum_pending: 0
Scenario: Request/reply
- WHEN a responder subscribes to
svc.echoand replies with the request payload, and a client issues a request - THEN the client receives the original payload within the request timeout