from __future__ import annotations from types import SimpleNamespace from unittest.mock import MagicMock from jarvis.runtime_observability_snapshot import publish_observability_snapshot def test_publish_observability_snapshot_respects_interval_guard() -> None: observability = SimpleNamespace( record_snapshot=MagicMock(), record_tool_summaries=MagicMock(), detect_failure_burst=MagicMock(return_value=[]), detect_budget_violations=MagicMock(return_value=[]), ) runtime = SimpleNamespace( _observability=observability, _last_observability_snapshot_at=100.0, config=SimpleNamespace(observability_snapshot_interval_sec=5.0), _telemetry_snapshot=lambda: {"turns": 1.0}, _publish_observability_status=MagicMock(), ) publish_observability_snapshot( runtime, force=False, list_summaries_fn=lambda limit=100: [], logger=SimpleNamespace(warning=MagicMock()), now_monotonic_fn=lambda: 102.0, ) observability.record_snapshot.assert_not_called() runtime._publish_observability_status.assert_not_called() def test_publish_observability_snapshot_records_snapshot_summaries_and_alerts() -> None: observability = SimpleNamespace( record_snapshot=MagicMock(), record_tool_summaries=MagicMock(), detect_failure_burst=MagicMock(return_value=[{"type": "failure_burst"}]), detect_budget_violations=MagicMock(return_value=[{"type": "latency_budget_exceeded"}]), ) logger = SimpleNamespace(warning=MagicMock()) runtime = SimpleNamespace( _observability=observability, _last_observability_snapshot_at=0.0, config=SimpleNamespace( observability_snapshot_interval_sec=5.0, observability_latency_budget_p95_ms=3200.0, observability_tokens_budget_per_hour=0.0, observability_cost_budget_usd_per_hour=0.0, observability_budget_window_sec=1800.0, observability_alert_cooldown_sec=120.0, ), _telemetry_snapshot=lambda: {"turns": 2.0}, _publish_observability_status=MagicMock(), ) publish_observability_snapshot( runtime, force=False, list_summaries_fn=lambda limit=100: [{"name": "tool_summary", "status": "ok"}], logger=logger, now_monotonic_fn=lambda: 20.0, ) observability.record_snapshot.assert_called_once_with({"turns": 2.0}) observability.record_tool_summaries.assert_called_once() observability.detect_budget_violations.assert_called_once() logger.warning.assert_called_once() runtime._publish_observability_status.assert_called_once() def test_publish_observability_snapshot_noop_when_observability_missing() -> None: runtime = SimpleNamespace( _observability=None, _last_observability_snapshot_at=0.0, config=SimpleNamespace(observability_snapshot_interval_sec=5.0), _telemetry_snapshot=lambda: {"turns": 0.0}, _publish_observability_status=MagicMock(), ) publish_observability_snapshot( runtime, force=True, list_summaries_fn=lambda limit=100: [], logger=SimpleNamespace(warning=MagicMock()), now_monotonic_fn=lambda: 1.0, ) runtime._publish_observability_status.assert_not_called()