New router intelligence modules (26 files): alert_ingest/store, audit_store, architecture_pressure, backlog_generator/store, cost_analyzer, data_governance, dependency_scanner, drift_analyzer, incident_* (5 files), llm_enrichment, platform_priority_digest, provider_budget, release_check_runner, risk_* (6 files), signature_state_store, sofiia_auto_router, tool_governance New services: - sofiia-console: Dockerfile, adapters/, monitor/nodes/ops/voice modules, launchd, react static - memory-service: integration_endpoints, integrations, voice_endpoints, static UI - aurora-service: full app suite (analysis, job_store, orchestrator, reporting, schemas, subagents) - sofiia-supervisor: new supervisor service - aistalk-bridge-lite: Telegram bridge lite - calendar-service: CalDAV calendar service with reminders - mlx-stt-service / mlx-tts-service: Apple Silicon speech services - binance-bot-monitor: market monitor service - node-worker: STT/TTS memory providers New tools (9): agent_email, browser_tool, contract_tool, observability_tool, oncall_tool, pr_reviewer_tool, repo_tool, safe_code_executor, secure_vault New crews: agromatrix_crew (10 modules: depth_classifier, doc_facts, doc_focus, farm_state, light_reply, llm_factory, memory_manager, proactivity, reflection_engine, session_context, style_adapter, telemetry) Tests: 85+ test files for all new modules Made-with: Cursor
211 lines
8.2 KiB
Python
211 lines
8.2 KiB
Python
"""
|
|
tests/test_risk_digest.py — Unit tests for risk_digest.daily_digest.
|
|
|
|
Tests:
|
|
- JSON and markdown content generated correctly
|
|
- Markdown clamped to max_chars
|
|
- Top risky services and regressions included
|
|
- Action list deterministic from report state
|
|
"""
|
|
import datetime
|
|
import sys
|
|
import tempfile
|
|
from pathlib import Path
|
|
import pytest
|
|
|
|
sys.path.insert(0, str(Path(__file__).resolve().parent.parent / "services" / "router"))
|
|
|
|
from risk_digest import daily_digest, _build_action_list, _clamp
|
|
from risk_engine import _builtin_defaults, _reload_policy
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def reset_policy_cache():
|
|
_reload_policy()
|
|
yield
|
|
_reload_policy()
|
|
|
|
|
|
@pytest.fixture
|
|
def policy():
|
|
return _builtin_defaults()
|
|
|
|
|
|
def _report(service, score, band=None, delta_24h=None, delta_7d=None,
|
|
reg_warn=False, reg_fail=False,
|
|
overdue_p1=0, slo_violations=0):
|
|
from risk_engine import score_to_band, _builtin_defaults
|
|
p = _builtin_defaults()
|
|
b = band or score_to_band(score, p)
|
|
return {
|
|
"service": service,
|
|
"env": "prod",
|
|
"score": score,
|
|
"band": b,
|
|
"components": {
|
|
"followups": {"P0": 0, "P1": overdue_p1, "other": 0, "points": overdue_p1 * 12},
|
|
"slo": {"violations": slo_violations, "points": slo_violations * 10},
|
|
},
|
|
"reasons": [],
|
|
"recommendations": [],
|
|
"updated_at": "2026-02-23T00:00:00",
|
|
"trend": {
|
|
"delta_24h": delta_24h,
|
|
"delta_7d": delta_7d,
|
|
"slope_per_day": None,
|
|
"volatility": None,
|
|
"regression": {"warn": reg_warn, "fail": reg_fail},
|
|
},
|
|
}
|
|
|
|
|
|
# ─── _clamp ───────────────────────────────────────────────────────────────────
|
|
|
|
class TestClamp:
|
|
def test_no_clamp_if_short(self):
|
|
text = "hello world"
|
|
assert _clamp(text, 100) == text
|
|
|
|
def test_clamps_to_max_chars(self):
|
|
text = "x" * 500
|
|
result = _clamp(text, 100)
|
|
assert len(result) <= 200 # includes truncation notice
|
|
assert "truncated" in result
|
|
|
|
def test_exact_limit_not_clamped(self):
|
|
text = "a" * 100
|
|
assert _clamp(text, 100) == text
|
|
|
|
|
|
# ─── _build_action_list ───────────────────────────────────────────────────────
|
|
|
|
class TestActionList:
|
|
def test_no_actions_for_low_risk(self):
|
|
reports = [_report("svc", 5, band="low")]
|
|
actions = _build_action_list(reports)
|
|
assert actions == []
|
|
|
|
def test_critical_band_generates_oncall_action(self):
|
|
reports = [_report("gateway", 90, band="critical")]
|
|
actions = _build_action_list(reports)
|
|
assert any("Critical" in a or "critical" in a.lower() for a in actions)
|
|
|
|
def test_high_band_generates_coordinate_action(self):
|
|
reports = [_report("router", 60, band="high")]
|
|
actions = _build_action_list(reports)
|
|
assert any("oncall" in a.lower() or "High" in a for a in actions)
|
|
|
|
def test_regression_fail_generates_freeze_action(self):
|
|
reports = [_report("gateway", 70, band="high", delta_24h=25, reg_fail=True)]
|
|
actions = _build_action_list(reports)
|
|
assert any("Regression" in a or "Freeze" in a for a in actions)
|
|
|
|
def test_overdue_followups_action(self):
|
|
reports = [_report("router", 40, band="medium", overdue_p1=2)]
|
|
actions = _build_action_list(reports)
|
|
assert any("follow-up" in a.lower() or "overdue" in a.lower() for a in actions)
|
|
|
|
def test_slo_violation_action(self):
|
|
reports = [_report("router", 40, band="medium", slo_violations=1)]
|
|
actions = _build_action_list(reports)
|
|
assert any("SLO" in a for a in actions)
|
|
|
|
|
|
# ─── daily_digest (no file write) ────────────────────────────────────────────
|
|
|
|
class TestDailyDigest:
|
|
def test_returns_json_and_markdown(self, policy):
|
|
reports = [
|
|
_report("gateway", 80, band="high", delta_24h=15, reg_warn=True),
|
|
_report("router", 30, band="medium"),
|
|
]
|
|
result = daily_digest(
|
|
env="prod",
|
|
service_reports=reports,
|
|
policy=policy,
|
|
date_str="2026-02-23",
|
|
write_files=False,
|
|
)
|
|
assert result["date"] == "2026-02-23"
|
|
assert result["env"] == "prod"
|
|
assert "markdown" in result
|
|
assert "json_data" in result
|
|
assert result["json_path"] is None # not written
|
|
|
|
def test_markdown_contains_service_name(self, policy):
|
|
reports = [_report("gateway", 80, band="high")]
|
|
result = daily_digest(env="prod", service_reports=reports, policy=policy,
|
|
write_files=False)
|
|
assert "gateway" in result["markdown"]
|
|
|
|
def test_markdown_contains_band_summary(self, policy):
|
|
reports = [
|
|
_report("a", 85, band="critical"),
|
|
_report("b", 60, band="high"),
|
|
]
|
|
result = daily_digest(env="prod", service_reports=reports, policy=policy,
|
|
write_files=False)
|
|
assert "critical" in result["markdown"].lower()
|
|
assert "high" in result["markdown"].lower()
|
|
|
|
def test_markdown_clamped_to_max_chars(self, policy):
|
|
"""max_chars in builtin_defaults is 8000; generate a large report."""
|
|
reports = [_report(f"service-{i:03d}", 80 - i, band="high") for i in range(50)]
|
|
modified_policy = {**policy, "digest": {**policy.get("digest", {}),
|
|
"markdown_max_chars": 200}}
|
|
result = daily_digest(env="prod", service_reports=reports,
|
|
policy=modified_policy, write_files=False)
|
|
assert len(result["markdown"]) <= 400 # clamped + truncation note
|
|
|
|
def test_top_regressions_in_json(self, policy):
|
|
reports = [
|
|
_report("gateway", 80, delta_24h=25, reg_fail=True),
|
|
_report("router", 50, delta_24h=5), # both > 0: both appear
|
|
]
|
|
result = daily_digest(env="prod", service_reports=reports, policy=policy,
|
|
write_files=False)
|
|
top_reg = result["json_data"]["top_regressions"]
|
|
# gateway has the highest delta
|
|
assert top_reg[0]["service"] == "gateway"
|
|
assert top_reg[0]["delta_24h"] == 25
|
|
# router also included (delta > 0)
|
|
assert any(r["service"] == "router" for r in top_reg)
|
|
|
|
def test_improving_services_in_json(self, policy):
|
|
reports = [
|
|
_report("gateway", 30, delta_7d=-20),
|
|
_report("router", 50, delta_7d=5),
|
|
]
|
|
result = daily_digest(env="prod", service_reports=reports, policy=policy,
|
|
write_files=False)
|
|
improving = result["json_data"]["improving_services"]
|
|
assert any(s["service"] == "gateway" for s in improving)
|
|
|
|
def test_files_written_to_tempdir(self, policy, tmp_path):
|
|
reports = [_report("gateway", 60, band="high")]
|
|
result = daily_digest(
|
|
env="prod",
|
|
service_reports=reports,
|
|
policy=policy,
|
|
date_str="2026-02-23",
|
|
output_dir=str(tmp_path),
|
|
write_files=True,
|
|
)
|
|
assert result["json_path"] is not None
|
|
assert Path(result["json_path"]).exists()
|
|
assert result["md_path"] is not None
|
|
assert Path(result["md_path"]).exists()
|
|
|
|
def test_empty_reports_does_not_crash(self, policy):
|
|
result = daily_digest(env="prod", service_reports=[], policy=policy,
|
|
write_files=False)
|
|
assert result["date"] is not None
|
|
assert "markdown" in result
|
|
|
|
def test_top_n_limits_services(self, policy):
|
|
reports = [_report(f"svc{i}", 100 - i) for i in range(15)]
|
|
modified = {**policy, "digest": {**policy.get("digest", {}), "top_n": 5}}
|
|
result = daily_digest(env="prod", service_reports=reports,
|
|
policy=modified, write_files=False)
|
|
assert len(result["json_data"]["top_services"]) == 5
|