Files
awoooi/apps/api/tests/test_telegram_webhook_execution_handoff.py
Your Name 32e4beca06
All checks were successful
CD Pipeline / tests (push) Successful in 1m27s
Code Review / ai-code-review (push) Successful in 14s
CD Pipeline / build-and-deploy (push) Successful in 4m24s
CD Pipeline / post-deploy-checks (push) Successful in 1m29s
fix(api): connect approval execution truth chain
2026-06-11 13:03:54 +08:00

325 lines
10 KiB
Python

from types import SimpleNamespace
from uuid import UUID
import pytest
from src.api.v1 import telegram as telegram_api
class _FakeGateway:
def __init__(self, result: dict) -> None:
self.result = result
self.mirror_calls: list[dict] = []
async def handle_callback(self, **_kwargs):
return self.result
async def mirror_callback_query_received(self, **kwargs):
self.mirror_calls.append(kwargs)
return "event-1"
class _FakeApprovalService:
def __init__(
self,
approval,
execution_triggered: bool,
sign_message: str = "Approval complete",
) -> None:
self.approval = approval
self.execution_triggered = execution_triggered
self.sign_message = sign_message
async def sign_approval(self, **_kwargs):
return self.approval, self.sign_message, self.execution_triggered
async def reject_approval(self, **_kwargs):
return self.approval, "Approval rejected"
class _FakeAlertOperationLogRepository:
def __init__(self) -> None:
self.rows: list[dict] = []
async def append(self, *args, **kwargs):
self.rows.append({"args": args, "kwargs": kwargs})
def _callback_update(callback_data: str) -> telegram_api.TelegramUpdate:
return telegram_api.TelegramUpdate(
update_id=123,
callback_query={
"id": "callback-1",
"data": callback_data,
"from": {"id": 42, "username": "ops"},
"message": {"message_id": 99, "text": "ACTION REQUIRED"},
},
)
@pytest.mark.asyncio
async def test_telegram_approval_schedules_executor_after_required_signature(monkeypatch):
approval_id = "11111111-1111-1111-1111-111111111111"
approval = SimpleNamespace(
id=UUID(approval_id),
status=SimpleNamespace(value="approved"),
incident_id="INC-20260513-TGEXEC",
)
finalizer_calls: list[dict] = []
op_log_repo = _FakeAlertOperationLogRepository()
async def fake_finalize(*, approval, execution_triggered: bool) -> bool:
finalizer_calls.append({
"approval_id": str(approval.id),
"incident_id": approval.incident_id,
"execution_triggered": execution_triggered,
})
return True
fake_gateway = _FakeGateway({
"success": True,
"action": "approve",
"approval_id": approval_id,
"user": {"id": 42, "username": "ops"},
})
monkeypatch.setattr(telegram_api, "get_telegram_gateway", lambda: fake_gateway)
monkeypatch.setattr(
telegram_api,
"get_approval_service",
lambda: _FakeApprovalService(approval, execution_triggered=True),
)
monkeypatch.setattr(telegram_api, "_finalize_telegram_approval", fake_finalize)
monkeypatch.setattr(
"src.repositories.alert_operation_log_repository.get_alert_operation_log_repository",
lambda: op_log_repo,
)
result = await telegram_api.telegram_webhook(_callback_update(f"approve:{approval_id}:ts:nonce"))
assert result["ok"] is True
assert result["message"] == "Approved"
assert result["execution_triggered"] is True
assert result["execution_scheduled"] is True
assert fake_gateway.mirror_calls == [{
"update_id": 123,
"callback_query_id": "callback-1",
"callback_data": f"approve:{approval_id}:ts:nonce",
"user_id": 42,
"username": "ops",
"message_id": 99,
"chat_id": None,
}]
assert finalizer_calls == [{
"approval_id": approval_id,
"incident_id": "INC-20260513-TGEXEC",
"execution_triggered": True,
}]
assert op_log_repo.rows[0]["kwargs"]["action_detail"] == "approve"
@pytest.mark.asyncio
async def test_telegram_approval_suppresses_executor_for_no_action(monkeypatch):
approval_id = "55555555-5555-5555-5555-555555555555"
approval = SimpleNamespace(
id=UUID(approval_id),
status=SimpleNamespace(value="approved"),
incident_id="INC-20260611-NOEXEC",
action="NO_ACTION - REPAIR_CANDIDATE_MISSING: LLM 分析失敗",
)
finalizer_calls: list[dict] = []
op_log_repo = _FakeAlertOperationLogRepository()
async def fake_finalize(*, approval, execution_triggered: bool) -> bool:
finalizer_calls.append({
"approval_id": str(approval.id),
"execution_triggered": execution_triggered,
})
return False
fake_gateway = _FakeGateway({
"success": True,
"action": "approve",
"approval_id": approval_id,
"user": {"id": 42, "username": "ops"},
})
monkeypatch.setattr(telegram_api, "get_telegram_gateway", lambda: fake_gateway)
monkeypatch.setattr(
telegram_api,
"get_approval_service",
lambda: _FakeApprovalService(approval, execution_triggered=True),
)
monkeypatch.setattr(telegram_api, "_finalize_telegram_approval", fake_finalize)
monkeypatch.setattr(
"src.repositories.alert_operation_log_repository.get_alert_operation_log_repository",
lambda: op_log_repo,
)
result = await telegram_api.telegram_webhook(_callback_update(f"approve:{approval_id}:ts:nonce"))
assert result["ok"] is True
assert result["message"] == "ApprovedWithoutExecution"
assert result["execution_triggered"] is True
assert result["execution_scheduled"] is False
assert result["execution_suppressed"] is True
assert finalizer_calls == [{
"approval_id": approval_id,
"execution_triggered": True,
}]
@pytest.mark.asyncio
async def test_telegram_approval_duplicate_does_not_schedule_executor(monkeypatch):
approval_id = "33333333-3333-3333-3333-333333333333"
approval = SimpleNamespace(
id=UUID(approval_id),
status=SimpleNamespace(value="execution_success"),
incident_id="INC-20260531-DUPE",
)
finalizer_calls: list[dict] = []
op_log_repo = _FakeAlertOperationLogRepository()
async def fake_finalize(*, approval, execution_triggered: bool) -> bool:
finalizer_calls.append({
"approval_id": str(approval.id),
"execution_triggered": execution_triggered,
})
return True
monkeypatch.setattr(
telegram_api,
"get_telegram_gateway",
lambda: _FakeGateway({
"success": True,
"action": "approve",
"approval_id": approval_id,
"user": {"id": 42, "username": "ops"},
}),
)
monkeypatch.setattr(
telegram_api,
"get_approval_service",
lambda: _FakeApprovalService(
approval,
execution_triggered=False,
sign_message="Cannot sign: status is execution_success",
),
)
monkeypatch.setattr(telegram_api, "_finalize_telegram_approval", fake_finalize)
monkeypatch.setattr(
"src.repositories.alert_operation_log_repository.get_alert_operation_log_repository",
lambda: op_log_repo,
)
result = await telegram_api.telegram_webhook(_callback_update(f"approve:{approval_id}:ts:nonce"))
assert result["ok"] is True
assert result["message"] == "Already processed"
assert result["execution_triggered"] is False
assert result["execution_scheduled"] is False
assert finalizer_calls == []
assert op_log_repo.rows[0]["kwargs"]["action_detail"] == "approve_duplicate"
@pytest.mark.asyncio
async def test_telegram_rejection_syncs_incident_state(monkeypatch):
approval_id = "22222222-2222-2222-2222-222222222222"
approval = SimpleNamespace(
id=UUID(approval_id),
status=SimpleNamespace(value="rejected"),
incident_id="INC-20260513-TGREJ",
)
sync_calls: list[str] = []
op_log_repo = _FakeAlertOperationLogRepository()
async def fake_sync(rejected_approval_id: str) -> bool:
sync_calls.append(rejected_approval_id)
return True
monkeypatch.setattr(
telegram_api,
"get_telegram_gateway",
lambda: _FakeGateway({
"success": True,
"action": "reject",
"approval_id": approval_id,
"user": {"id": 42, "username": "ops"},
}),
)
monkeypatch.setattr(
telegram_api,
"get_approval_service",
lambda: _FakeApprovalService(approval, execution_triggered=False),
)
monkeypatch.setattr(telegram_api, "_sync_telegram_rejection", fake_sync)
monkeypatch.setattr(
"src.repositories.alert_operation_log_repository.get_alert_operation_log_repository",
lambda: op_log_repo,
)
result = await telegram_api.telegram_webhook(_callback_update(f"reject:{approval_id}:ts:nonce"))
assert result["ok"] is True
assert result["message"] == "Rejected"
assert result["incident_synced"] is True
assert sync_calls == [approval_id]
assert op_log_repo.rows[0]["kwargs"]["action_detail"] == "reject"
@pytest.mark.asyncio
async def test_finalize_telegram_approval_runs_executor_task(monkeypatch):
executed: list[str] = []
approval = SimpleNamespace(
id=UUID("33333333-3333-3333-3333-333333333333"),
incident_id="INC-20260513-TGRUN",
)
class _FakeExecutionService:
async def execute_approved_action(self, received_approval):
executed.append(str(received_approval.id))
return True
monkeypatch.setattr(
telegram_api,
"get_execution_service",
lambda: _FakeExecutionService(),
)
scheduled = await telegram_api._finalize_telegram_approval(
approval=approval,
execution_triggered=True,
)
assert scheduled is True
await telegram_api.asyncio.sleep(0)
assert executed == ["33333333-3333-3333-3333-333333333333"]
@pytest.mark.asyncio
async def test_finalize_telegram_approval_does_not_schedule_no_action(monkeypatch):
executed: list[str] = []
approval = SimpleNamespace(
id=UUID("66666666-6666-6666-6666-666666666666"),
incident_id="INC-20260611-NOOP",
action="OBSERVE",
)
class _FakeExecutionService:
async def execute_approved_action(self, received_approval):
executed.append(str(received_approval.id))
return True
monkeypatch.setattr(
telegram_api,
"get_execution_service",
lambda: _FakeExecutionService(),
)
scheduled = await telegram_api._finalize_telegram_approval(
approval=approval,
execution_triggered=True,
)
assert scheduled is False
await telegram_api.asyncio.sleep(0)
assert executed == []