study-buddy / tests /observability /test_observability_summary.py
GitHub Actions
deploy d092bea3608b7a29952f16357fda39b7a29e399b
2e818da
Raw
History Blame Contribute Delete
6.72 kB
"""Status-vocabulary and aggregation tests for the Control Room observability
projection (`app.services.observability_summary.observability_summary`).
Uses real `bootstrap.initialize_observability(...)` + real
`LocalObservationStore` instances against a `tmp_path` artifact root -- the
same isolation pattern `tests/observability/test_rag_spans.py` and
`test_operation.py` already use -- rather than mocking the observability
layer.
"""
from __future__ import annotations
import pytest
from app.observability import bootstrap
from app.observability.config import ObservabilityConfig
from app.observability.operation import observe_operation
from app.services.observability_summary import observability_summary
@pytest.fixture(autouse=True)
def _shutdown_after():
bootstrap.reset_suppressed_failures()
yield
bootstrap.shutdown_observability(timeout_seconds=2.0)
def _config(tmp_path, mode: str) -> ObservabilityConfig:
return ObservabilityConfig(
enabled=mode != "disabled",
mode=mode, # type: ignore[arg-type]
otlp_endpoint="http://127.0.0.1:4318",
signoz_ui_url="http://127.0.0.1:8080",
artifact_root=tmp_path,
)
# --- not_instrumented ---------------------------------------------------
def test_not_instrumented_when_mode_disabled(tmp_path):
bootstrap.initialize_observability(config=_config(tmp_path, "disabled"))
summary = observability_summary()
assert summary["status"] == "not_instrumented"
assert summary["latest_trace_id"] is None
assert summary["pipeline_version"] is None
assert summary["stages"] == {}
assert summary["retrieval"] == {"success_count": 0, "empty_count": 0, "error_count": 0}
assert summary["exporter"] == {"mode": "disabled", "suppressed_failures": 0}
assert summary["signoz_url"] is None
# --- not_observed ---------------------------------------------------
def test_not_observed_when_store_empty(tmp_path):
bootstrap.initialize_observability(config=_config(tmp_path, "local"))
summary = observability_summary()
assert summary["status"] == "not_observed"
assert summary["latest_trace_id"] is None
assert summary["stages"] == {}
assert summary["retrieval"] == {"success_count": 0, "empty_count": 0, "error_count": 0}
assert summary["exporter"]["mode"] == "local"
def test_not_observed_takes_priority_over_signoz_url_in_full_mode(tmp_path):
bootstrap.initialize_observability(config=_config(tmp_path, "full"))
summary = observability_summary()
assert summary["status"] == "not_observed"
# full mode always surfaces the SigNoz link, even with nothing observed yet.
assert summary["signoz_url"] == "http://127.0.0.1:8080"
# --- degraded ---------------------------------------------------
def test_degraded_when_suppressed_failures_recorded_even_with_data(tmp_path):
bootstrap.initialize_observability(config=_config(tmp_path, "local"))
with observe_operation("rag.retrieval"):
pass
bootstrap.note_suppressed_failure()
summary = observability_summary()
assert summary["status"] == "degraded"
assert summary["exporter"]["suppressed_failures"] >= 1
# --- observed ---------------------------------------------------
def test_observed_with_stage_percentiles_and_retrieval_counts(tmp_path):
from app.observability.contracts import RetrievalOutcome
bootstrap.initialize_observability(config=_config(tmp_path, "local"))
# Three vector_search rows with distinct durations to exercise p50/p95.
for _ in range(3):
with observe_operation("chroma.vector_search"):
pass
with observe_operation("rag.retrieval") as op:
op.set_retrieval(RetrievalOutcome.success([{"text": "a"}]))
with observe_operation("rag.retrieval") as op:
op.set_retrieval(RetrievalOutcome.success([]))
with observe_operation("rag.retrieval") as op:
op.set_retrieval(RetrievalOutcome.failure("vector_search_failed"))
op.mark_error("vector_search_failed")
summary = observability_summary()
assert summary["status"] == "observed"
assert summary["latest_trace_id"] is not None
assert "vector_search_ms" in summary["stages"]
assert summary["stages"]["vector_search_ms"]["count"] == 3
assert summary["stages"]["vector_search_ms"]["p50"] >= 0
assert summary["stages"]["vector_search_ms"]["p95"] >= summary["stages"]["vector_search_ms"]["p50"]
assert "retrieval_ms" in summary["stages"]
assert summary["stages"]["retrieval_ms"]["count"] == 3
assert summary["retrieval"] == {"success_count": 1, "empty_count": 1, "error_count": 1}
assert summary["exporter"] == {"mode": "local", "suppressed_failures": 0}
def test_missing_duration_never_counted_as_zero(tmp_path):
"""A row recorded via a store that never got a real duration (e.g. an
injected store bypassing the timing boundary) must not pollute p50/p95.
Exercised here by directly appending a row through the store with
`duration_ms=None`, the way a degraded/failed timing path could produce
one, and confirming it contributes zero to the stage's count.
"""
from app.observability.contracts import OperationObservation
from app.observability.local_store import LocalObservationStore
bootstrap.initialize_observability(config=_config(tmp_path, "local"))
store = bootstrap.get_state().local_store
assert isinstance(store, LocalObservationStore)
store.record(
OperationObservation(operation="chroma.vector_search", status="success", duration_ms=None)
)
summary = observability_summary()
assert summary["status"] == "observed"
assert summary["stages"] == {}
def test_pipeline_version_taken_from_latest_experiment(tmp_path):
from app.observability.contracts import ExperimentIdentity
bootstrap.initialize_observability(config=_config(tmp_path, "local"))
with observe_operation("rag.retrieval") as op:
op.set_experiment(ExperimentIdentity(pipeline_version="rag-naive-v1"))
with observe_operation("rag.retrieval"):
pass # no experiment identity -- ordinary traffic
summary = observability_summary()
# Most recent row wins; the plain ordinary-traffic row carries no
# experiment identity, so the last *tagged* row's version is used.
assert summary["pipeline_version"] == "rag-naive-v1"
def test_signoz_url_only_present_in_full_mode(tmp_path):
bootstrap.initialize_observability(config=_config(tmp_path, "full"))
with observe_operation("rag.retrieval"):
pass
summary = observability_summary()
assert summary["signoz_url"] == "http://127.0.0.1:8080"
assert summary["exporter"]["mode"] == "full"