"""Unit tests for telemetry after the passive-collection refactor. The application no longer pushes OTLP. This module verifies that telemetry setup/shutdown are safe no-ops and that structured logging still reaches the configured root handler. """ from __future__ import annotations import logging import pytest from contract_check.core.logging import configure_logging, get_logger from contract_check.core.telemetry import get_meter, get_tracer, setup_telemetry, shutdown_telemetry @pytest.fixture(autouse=True) def _reset_logging(): """Configure logging before each test and clean handlers after.""" configure_logging("INFO", json_output=True, service="test-service", env="test") yield root = logging.getLogger() root.handlers.clear() def test_setup_telemetry_is_safe_noop() -> None: """setup_telemetry() runs without error and does not add an OTLP handler.""" root = logging.getLogger() before = list(root.handlers) setup_telemetry("test") try: after = list(root.handlers) # No new handlers are attached because OTLP is no longer initialized. assert after == before finally: shutdown_telemetry() def test_shutdown_telemetry_is_safe_noop() -> None: """shutdown_telemetry() can be called even when setup_telemetry() was skipped.""" shutdown_telemetry() shutdown_telemetry() # idempotent def test_otlp_endpoint_env_does_not_initialize_exporters(monkeypatch: pytest.MonkeyPatch) -> None: """A legacy OTLP endpoint env var must not cause the application to import OTel SDK.""" monkeypatch.setenv("OTEL_EXPORTER_OTLP_ENDPOINT", "http://collector:4318") from contract_check.core.config import get_settings # Settings explicitly ignores unknown env vars, but a stale env var must not # be picked up by telemetry code. Refresh the cached settings instance. get_settings.cache_clear() root = logging.getLogger() before = list(root.handlers) setup_telemetry("test") try: after = list(root.handlers) assert after == before assert not any(type(h).__name__.startswith(("OTLP", "StructlogOTLP")) for h in after) finally: shutdown_telemetry() get_settings.cache_clear() def test_get_tracer_and_meter_are_safe_noop() -> None: """Tracer/meter accessors return objects that do not require OTel SDK.""" tracer = get_tracer("test") meter = get_meter("test") # They should be truthy and accept the expected creation calls without raising. assert tracer assert meter span = tracer.start_span("ignored") span.end() counter = meter.create_counter("ignored") counter.add(1) def test_configure_logging_delivers_events_to_root_stream_handler() -> None: """After configure_logging, structlog events propagate to the root StreamHandler.""" root = logging.getLogger() assert any(isinstance(h, logging.StreamHandler) for h in root.handlers) log = get_logger("test") log.info("smoke_test", answer=42)