Some checks failed
CD Pipeline / tests (push) Successful in 1m21s
Code Review / ai-code-review (push) Successful in 13s
Type Sync Check / check-type-sync (push) Failing after 40s
CD Pipeline / build-and-deploy (push) Successful in 5m22s
CD Pipeline / post-deploy-checks (push) Successful in 2m19s
123 lines
3.9 KiB
Python
123 lines
3.9 KiB
Python
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)
|