from datetime import UTC, datetime, timedelta from types import SimpleNamespace from uuid import UUID from src.models.approval import ( ApprovalRequest, ApprovalRequestResponse, ApprovalStatus, RiskLevel, ) from src.services.approval_db import approval_record_to_request from src.services.heartbeat_report_service import ( AlertPipelineStats, DbRedisStats, FlywheelStats, HeartbeatReport, HeartbeatReportService, ) def test_approval_response_exposes_incident_delivery_and_playbook_fields(): approval = ApprovalRequest( id=UUID("11111111-1111-1111-1111-111111111111"), action="kubectl rollout restart deployment/awoooi-api", description="manual gate", risk_level=RiskLevel.MEDIUM, requested_by="OpenClaw", required_signatures=1, incident_id="INC-20260531-VISIBLE", matched_playbook_id="PB-20260531-VISIBLE", telegram_message_id=98765, telegram_chat_id=-1001234567890, ) response = ApprovalRequestResponse.from_approval(approval) assert response.incident_id == "INC-20260531-VISIBLE" assert response.matched_playbook_id == "PB-20260531-VISIBLE" assert response.telegram_message_id == 98765 assert response.telegram_chat_id == -1001234567890 def test_approval_db_converter_preserves_incident_and_telegram_fields(): now = datetime.now(UTC) record = SimpleNamespace( id="22222222-2222-2222-2222-222222222222", action="OBSERVE", description="[LLM Failed] observe only", status=ApprovalStatus.PENDING, risk_level=RiskLevel.MEDIUM, blast_radius={ "affected_pods": 0, "estimated_downtime": "0", "related_services": [], "data_impact": "none", }, dry_run_checks=[], required_signatures=1, current_signatures=0, signatures=[], requested_by="OpenClaw (fallback)", created_at=now, expires_at=now + timedelta(hours=1), resolved_at=None, rejection_reason=None, extra_metadata={"source": "fallback"}, fingerprint="abc123", hit_count=2, last_seen_at=now, incident_id="INC-20260531-LEGACY", matched_playbook_id="PB-20260531-LEGACY", telegram_message_id=45678, telegram_chat_id=-1001234567890, ) approval = approval_record_to_request(record) assert approval.incident_id == "INC-20260531-LEGACY" assert approval.matched_playbook_id == "PB-20260531-LEGACY" assert approval.telegram_message_id == 45678 assert approval.telegram_chat_id == -1001234567890 def _report_with_pipeline(stats: AlertPipelineStats) -> HeartbeatReport: return HeartbeatReport( timestamp=datetime.now(UTC), flywheel=FlywheelStats(playbook_count=1), db_redis=DbRedisStats(db_ok=True, db_status="ok", redis_ok=True, redis_status="ok"), alert_pipeline=stats, ) def test_heartbeat_does_not_warn_when_pending_backlog_is_observe_only(): report = _report_with_pipeline( AlertPipelineStats( total_24h=25, pending_approval=21, pending_actionable=1, pending_observe_only=20, ) ) warnings = HeartbeatReportService()._build_warnings(report) assert not any("待人工審核" in warning for warning in warnings) assert not any("PENDING 積壓" in warning for warning in warnings) def test_heartbeat_warns_with_frontend_route_for_actionable_backlog(): report = _report_with_pipeline( AlertPipelineStats( total_24h=25, pending_approval=21, pending_actionable=11, pending_observe_only=10, ) ) warnings = HeartbeatReportService()._build_warnings(report) assert any("/awooop/approvals" in warning for warning in warnings) assert any("觀察類 10" in warning for warning in warnings)