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

5.6 KiB

ADDED Requirements

Requirement: Image policy

The @suaveplan/testing-integration-otel-collector package SHALL launch the otel/opentelemetry-collector-contrib container pinned to a specific minor version (e.g. 0.<minor>). A digest-pin migration MUST be tracked as a follow-up task; bare latest tags are forbidden.

Scenario: Tag pin enforced

  • WHEN the fixture is constructed without a config override
  • THEN the resolved image string MUST match otel/opentelemetry-collector-contrib:0.<minor> AND MUST NOT equal otel/opentelemetry-collector-contrib:latest

Scenario: Image override accepted

  • WHEN the fixture constructor is given { image: 'otel/opentelemetry-collector-contrib:0.99.0@sha256:...' }
  • THEN the launched container uses the supplied digest-pinned reference verbatim

Requirement: Env-bridge contract

The fixture SHALL set OTEL_EXPORTER_OTLP_ENDPOINT and OTEL_EXPORTER_OTLP_PROTOCOL (http/protobuf by default, grpc when the config requests otlp-grpc) on the host process during setup(), and MAY set OTEL_EXPORTER_OTLP_HEADERS when the config supplies headers. All values MUST be restored to their prior state on teardown().

Scenario: Endpoint published on setup

  • WHEN setup() resolves
  • THEN process.env.OTEL_EXPORTER_OTLP_ENDPOINT is a URL pointing at the running collector's OTLP port AND process.env.OTEL_EXPORTER_OTLP_PROTOCOL === 'http/protobuf'

Scenario: Prior env-state restored on teardown

  • WHEN OTEL_EXPORTER_OTLP_ENDPOINT was set to 'http://prior' before setup() and teardown() later runs
  • THEN process.env.OTEL_EXPORTER_OTLP_ENDPOINT === 'http://prior' after teardown() resolves

Requirement: Health-check contract

The fixture SHALL consider the collector ready only when an HTTP GET on the collector's :13133/health/status endpoint (port configurable) returns 200 within the configured timeout (default 60 s). Health polling MUST use the HealthPoller from @suaveplan/testing-integration.

Scenario: Health endpoint returns 200

  • WHEN the collector is fully started AND the health endpoint returns HTTP 200
  • THEN setup() resolves AND getSpans() becomes callable

Scenario: Health timeout exceeded

  • WHEN the health endpoint never returns 200 within the configured timeout
  • THEN setup() rejects with IntegrationError whose code === "INTEGRATION.OTEL_COLLECTOR.HEALTH_TIMEOUT" AND whose context.timeoutMs matches the configured timeout

Scenario: Injectable fetch for unit-testing the fixture

  • WHEN the fixture is constructed with { fetchFn }
  • THEN all health-check requests MUST use the supplied fetchFn AND MUST NOT touch the global fetch

Requirement: Config shape and synthesis

The fixture config object SHALL conform to { receivers: ('otlp-grpc' | 'otlp-http')[]; exporters: ('logging' | 'memory' | 'jaeger' | 'file')[]; configFile?: string }. When configFile is omitted the fixture MUST synthesise a minimal otelcol-config.yaml from the receivers and exporters lists and mount it into the container. When configFile is supplied the fixture MUST mount that file verbatim and MUST NOT synthesise.

Scenario: Synthesised config wires the requested receivers and exporters

  • WHEN setup({ receivers: ['otlp-http'], exporters: ['memory'] }) runs
  • THEN the mounted otelcol-config.yaml declares an OTLP HTTP receiver AND an in-memory exporter AND a single pipeline routing the receiver to the exporter

Scenario: External config file is mounted verbatim

  • WHEN setup({ receivers: [], exporters: [], configFile: '/abs/path/custom.yaml' }) runs
  • THEN the container's /etc/otelcol-contrib/config.yaml is the byte-identical contents of /abs/path/custom.yaml AND no synthesis occurs

Requirement: Span query helper

The fixture SHALL expose getSpans({ filter }) which returns ReadableSpan[] from the collector's in-memory exporter. The helper MUST throw IntegrationError("INTEGRATION.OTEL_COLLECTOR.MEMORY_EXPORTER_NOT_CONFIGURED", …) when the active config does not include the memory exporter.

Scenario: Returns spans matching filter

  • WHEN a producer has emitted spans named op.foo and op.bar AND getSpans({ filter: span => span.name === 'op.foo' }) is called
  • THEN the result contains every op.foo span AND no op.bar spans

Scenario: Helper unavailable without memory exporter

  • WHEN the fixture was started with exporters: ['logging'] AND getSpans() is called
  • THEN the call throws IntegrationError with code === "INTEGRATION.OTEL_COLLECTOR.MEMORY_EXPORTER_NOT_CONFIGURED"

Requirement: Error code prefix

Every error thrown by the package SHALL be an IntegrationError (or subclass) whose code begins with INTEGRATION.OTEL_COLLECTOR.. Plain Error / TypeError MUST NOT be thrown from any public code path.

Scenario: Code namespace enforced

  • WHEN any public method on the fixture throws
  • THEN the thrown value instanceof IntegrationError === true AND error.code.startsWith("INTEGRATION.OTEL_COLLECTOR.") === true

Requirement: Coverage gate

Istanbul statement, branch, function, and line coverage across packages/testing/testing-integration-otel-collector/src/ (excluding *.bench.ts and *.schemas.ts) MUST be 100%. The pre-publish gate (bunx turbo test:coverage --filter=@suaveplan/testing-integration-otel-collector) MUST hard-fail below 100%.

Scenario: Coverage gate trips below 100%

  • WHEN any line, branch, function, or statement is uncovered
  • THEN the pre-publish gate fails AND the package MUST NOT be published