diff --git a/docs/features/prometheus-metrics.md b/docs/features/prometheus-metrics.md index ab7a1103..2fc935f8 100644 --- a/docs/features/prometheus-metrics.md +++ b/docs/features/prometheus-metrics.md @@ -73,7 +73,8 @@ below are the **actual** series names — none carry a `cachekit_` prefix. > The `serializer` label is the tier that served the record, not the `@cache(serializer=...)` > preset: `rust` = L2 backend path, `l1_memory` = L1 in-memory hit; `unknown` is the value > recorded when the emitting call site passes no serializer (an instrumentation gap, not a -> tier). +> tier). Only failure records (`success="False"`) can carry `unknown`: every success record +> names its tier. ### Counters (always increasing) diff --git a/tests/unit/test_operation_context_tracking.py b/tests/unit/test_operation_context_tracking.py index d2e0a32a..4871ba24 100644 --- a/tests/unit/test_operation_context_tracking.py +++ b/tests/unit/test_operation_context_tracking.py @@ -14,7 +14,7 @@ from cachekit.decorators.orchestrator import FeatureOrchestrator from cachekit.reliability.async_metrics import AsyncMetricsCollector -from cachekit.reliability.circuit_breaker import CircuitBreakerConfig +from cachekit.reliability.circuit_breaker import CircuitBreaker, CircuitBreakerConfig @pytest.fixture @@ -53,16 +53,24 @@ def test_record_failure_defaults_when_context_not_set(self, recorded: list[dict[ assert [(c["operation"], c["duration_ms"]) for c in recorded] == [("cache_operation", 0.0)] - def test_record_success_emits_no_metric(self, recorded: list[dict[str, Any]]): - """Every success site records its own labelled metric; a second record here double-counts.""" + def test_record_success_feeds_breaker_and_emits_no_metric( + self, recorded: list[dict[str, Any]], monkeypatch: pytest.MonkeyPatch + ): + """Every success site records its own labelled metric; a second record here double-counts. + + Feeding the breaker is record_success()'s only job: a HALF_OPEN probe slot is released + only when its outcome reaches the breaker. + """ orchestrator = _orchestrator(circuit_breaker_enabled=True) + on_success_calls: list[CircuitBreaker] = [] + monkeypatch.setattr(CircuitBreaker, "_on_success", lambda self: on_success_calls.append(self)) for _ in range(3): orchestrator.set_operation_context("get", duration_ms=1.5) orchestrator.record_success() assert recorded == [] - assert orchestrator.circuit_breaker.get_stats()["state"] == "CLOSED" + assert on_success_calls == [orchestrator.circuit_breaker] * 3 def test_context_isolation_between_operations(self, recorded: list[dict[str, Any]]): orchestrator = _orchestrator()