Files
awoooi/apps/api/tests/test_telegram_canonical_sender_gate.py
ogt ff8d917247
Some checks failed
CD Pipeline / workflow-shape (push) Successful in 1s
CD Pipeline / cancel-stale-cd (push) Has been skipped
CD Pipeline / build-and-deploy (push) Has been cancelled
CD Pipeline / tests (push) Has been cancelled
CD Pipeline / post-deploy-checks (push) Has been cancelled
fix(telegram): bind product alerts to canonical routes
2026-07-15 01:08:05 +08:00

877 lines
28 KiB
Python

from __future__ import annotations
import runpy
from contextlib import asynccontextmanager
from pathlib import Path
from types import SimpleNamespace
from unittest.mock import AsyncMock
import pytest
import src.db.base as db_base
import src.services.approval_db as approval_db
import src.services.telegram_gateway as gateway_module
from src.services.telegram_gateway import TelegramGateway
_REPO_ROOT = Path(__file__).resolve().parents[3]
class _FakeResponse:
def __init__(self, payload: dict) -> None:
self._payload = payload
def raise_for_status(self) -> None:
return None
def json(self) -> dict:
return {
"ok": True,
"result": {
"message_id": 11,
"chat": {"id": self._payload.get("chat_id")},
},
}
class _FakeHttpClient:
def __init__(self) -> None:
self.posts: list[tuple[str, dict]] = []
async def post(self, url: str, json: dict) -> _FakeResponse:
self.posts.append((url, json))
return _FakeResponse(json)
def _prepared_gateway(
monkeypatch: pytest.MonkeyPatch,
) -> tuple[TelegramGateway, _FakeHttpClient]:
gateway = TelegramGateway()
client = _FakeHttpClient()
gateway._initialized = True
gateway._http_client = client # type: ignore[assignment]
monkeypatch.setattr(gateway_module.settings, "SRE_GROUP_CHAT_ID", "test-room")
monkeypatch.setattr(gateway, "_attach_incident_thread_reply", AsyncMock())
monkeypatch.setattr(gateway, "_mirror_outbound_message", AsyncMock())
monkeypatch.setattr(
gateway._security,
"generate_callback_nonce",
lambda approval_id, action: f"{action}:test",
)
return gateway, client
def _notification_message():
from src.services.notifications.base import ExecutionStatus, NotificationMessage
return NotificationMessage(
execution_status=ExecutionStatus.SUCCESS,
action_title="bounded test",
action_description="no provider egress",
approval_id="APR-TEST",
)
@pytest.mark.asyncio
async def test_notification_provider_does_not_report_success_for_gateway_no_send(
monkeypatch: pytest.MonkeyPatch,
) -> None:
from src.services.notifications import telegram as provider_module
from src.services.notifications.base import NotificationStatus
monkeypatch.setattr(provider_module.settings, "OPENCLAW_TG_BOT_TOKEN", "configured")
monkeypatch.setattr(provider_module.settings, "SRE_GROUP_CHAT_ID", "test-room")
gateway = SimpleNamespace(
send_alert_notification=AsyncMock(
return_value={
"ok": False,
"_awooop_delivery_status": "blocked_no_egress",
"_awooop_provider_send_performed": False,
}
)
)
monkeypatch.setattr(gateway_module, "get_telegram_gateway", lambda: gateway)
result = await provider_module.TelegramWebhookProvider().send(
_notification_message()
)
assert result.status is NotificationStatus.SKIPPED
assert result.message == "Telegram notification blocked before provider send"
assert result.response_data == {
"ok": False,
"_awooop_delivery_status": "blocked_no_egress",
"_awooop_provider_send_performed": False,
}
gateway.send_alert_notification.assert_awaited_once()
@pytest.mark.asyncio
async def test_notification_provider_requires_affirmative_delivery_ack(
monkeypatch: pytest.MonkeyPatch,
) -> None:
from src.services.notifications import telegram as provider_module
from src.services.notifications.base import NotificationStatus
monkeypatch.setattr(provider_module.settings, "OPENCLAW_TG_BOT_TOKEN", "configured")
monkeypatch.setattr(provider_module.settings, "SRE_GROUP_CHAT_ID", "test-room")
gateway = SimpleNamespace(
send_alert_notification=AsyncMock(
return_value={"ok": False, "description": "provider rejected"}
)
)
monkeypatch.setattr(gateway_module, "get_telegram_gateway", lambda: gateway)
result = await provider_module.TelegramWebhookProvider().send(
_notification_message()
)
assert result.status is NotificationStatus.FAILED
assert result.message == "Telegram provider did not acknowledge delivery"
assert result.error == "delivery_not_acknowledged"
@pytest.mark.asyncio
async def test_notification_connection_probe_rejects_structured_no_provider_send(
monkeypatch: pytest.MonkeyPatch,
) -> None:
from src.services.notifications import telegram as provider_module
monkeypatch.setattr(provider_module.settings, "OPENCLAW_TG_BOT_TOKEN", "configured")
monkeypatch.setattr(provider_module.settings, "SRE_GROUP_CHAT_ID", "test-room")
gateway = SimpleNamespace(
send_alert_notification=AsyncMock(
return_value={
"ok": True,
"_awooop_delivery_status": "blocked_no_egress",
"_awooop_provider_send_performed": False,
"_awoooi_canonical_route_receipt": {
"decision": "blocked_no_egress",
"provider_send_performed": False,
},
}
)
)
monkeypatch.setattr(gateway_module, "get_telegram_gateway", lambda: gateway)
assert await provider_module.TelegramWebhookProvider().test_connection() is False
gateway.send_alert_notification.assert_awaited_once()
@pytest.mark.asyncio
async def test_notification_connection_probe_accepts_complete_canonical_delivery(
monkeypatch: pytest.MonkeyPatch,
) -> None:
from src.services.notifications import telegram as provider_module
monkeypatch.setattr(provider_module.settings, "OPENCLAW_TG_BOT_TOKEN", "configured")
monkeypatch.setattr(provider_module.settings, "SRE_GROUP_CHAT_ID", "test-room")
chat_id = -1001
destination_binding = gateway_module._telegram_destination_binding(chat_id)
gateway = SimpleNamespace(
send_alert_notification=AsyncMock(
return_value={
"ok": True,
"_awooop_delivery_status": "sent",
"_awooop_provider_send_performed": True,
"result": {"message_id": 42, "chat": {"id": chat_id}},
"_awoooi_canonical_route_receipt": {
"schema_version": "telegram_canonical_egress_receipt_v1",
"decision": "allowed",
"destination_alias": "awoooi_sre_war_room",
"destination_binding": destination_binding,
"sender_bot_alias": "tsenyang_bot",
"provider_send_performed": True,
},
gateway_module._DELIVERY_CONTEXT_KEY: {
"schema_version": "telegram_delivery_context_v1",
"method": "sendMessage",
"sender_bot_alias": "tsenyang_bot",
"destination_alias": "awoooi_sre_war_room",
"destination_binding": destination_binding,
"payload_destination_binding": destination_binding,
"provider_destination_binding": destination_binding,
"destination_binding_verified": True,
},
}
)
)
monkeypatch.setattr(gateway_module, "get_telegram_gateway", lambda: gateway)
assert await provider_module.TelegramWebhookProvider().test_connection() is True
gateway.send_alert_notification.assert_awaited_once()
@pytest.mark.asyncio
async def test_unclassified_send_message_is_blocked_before_provider(
monkeypatch: pytest.MonkeyPatch,
) -> None:
gateway, client = _prepared_gateway(monkeypatch)
result = await gateway._send_request(
"sendMessage",
{"chat_id": "test-room", "text": "unclassified"},
)
assert result["_awooop_delivery_status"] == "blocked_no_egress"
assert result["_awooop_provider_send_performed"] is False
assert client.posts == []
@pytest.mark.asyncio
@pytest.mark.parametrize(
"method",
sorted(gateway_module._GUARDED_TELEGRAM_BOT_METHODS),
)
async def test_every_guarded_method_without_context_is_blocked_before_provider(
monkeypatch: pytest.MonkeyPatch,
method: str,
) -> None:
gateway, client = _prepared_gateway(monkeypatch)
result = await gateway._send_request(
method,
{
"chat_id": "test-room",
"text": "unclassified",
"caption": "unclassified",
"photo": "file-placeholder",
"document": "file-placeholder",
"media": [],
"message_id": 42,
},
)
assert result["_awooop_delivery_status"] == "blocked_no_egress"
assert result["_awooop_provider_send_performed"] is False
assert client.posts == []
@pytest.mark.asyncio
async def test_guarded_photo_accepts_only_a_valid_canonical_route(
monkeypatch: pytest.MonkeyPatch,
) -> None:
gateway, client = _prepared_gateway(monkeypatch)
result = await gateway._send_request(
"sendPhoto",
{
"chat_id": "test-room",
"photo": "file-placeholder",
gateway_module._CANONICAL_ROUTE_CONTEXT_KEY: {
"product_id": "awoooi",
"signal_family": "security_incident",
"severity": "P1",
},
},
)
assert result["_awooop_delivery_status"] == "sent"
assert gateway_module._telegram_send_delivery_succeeded(result) is True
assert len(client.posts) == 1
@pytest.mark.asyncio
@pytest.mark.parametrize(
("method", "method_payload"),
[
("sendDocument", {"document": "file-placeholder", "reply_to_message_id": 42}),
("editMessageText", {"text": "updated", "message_id": 42}),
],
)
async def test_guarded_interaction_method_requires_exact_inbound_binding(
monkeypatch: pytest.MonkeyPatch,
method: str,
method_payload: dict,
) -> None:
gateway, client = _prepared_gateway(monkeypatch)
result = await gateway._send_request(
method,
{
"chat_id": "operator-room",
**method_payload,
gateway_module._NON_MONITORING_INTERACTION_KEY: {
"interaction_kind": "callback_reply",
"inbound_chat_id": "operator-room",
"inbound_message_id": 42,
},
},
)
assert result["_awooop_delivery_status"] == "sent"
assert gateway_module._telegram_send_delivery_succeeded(result) is True
assert len(client.posts) == 1
@pytest.mark.asyncio
async def test_info_and_business_methods_are_blocked_before_provider(
monkeypatch: pytest.MonkeyPatch,
) -> None:
gateway, client = _prepared_gateway(monkeypatch)
info = await gateway.send_info_notification(
incident_id="INC-TEST",
title="FYI",
message="healthy",
)
business = await gateway.send_business_alert(
incident_id="INC-TEST",
alertname="BusinessMetric",
business_domain="test",
metric_name="rate",
current_value="1",
threshold="2",
)
assert info["_awooop_delivery_status"] == "blocked_no_egress"
assert business["_awooop_delivery_status"] == "blocked_no_egress"
assert client.posts == []
@pytest.mark.asyncio
async def test_wrong_group_override_is_blocked_before_provider(
monkeypatch: pytest.MonkeyPatch,
) -> None:
gateway, client = _prepared_gateway(monkeypatch)
result = await gateway.send_escalation_card(
incident_id="INC-TEST",
original_alertname="Outage",
duration_min=15,
group_chat_id="wrong-room",
)
assert result["_awooop_delivery_status"] == "blocked_no_egress"
assert result["_awoooi_canonical_route_receipt"]["classifier"] == (
"caller_destination_mismatch"
)
assert client.posts == []
@pytest.mark.asyncio
@pytest.mark.parametrize("method_name", ["meta", "secops", "escalation"])
async def test_allowed_legacy_monitoring_methods_use_canonical_provider_once(
monkeypatch: pytest.MonkeyPatch,
method_name: str,
) -> None:
gateway, client = _prepared_gateway(monkeypatch)
if method_name == "meta":
result = await gateway.send_meta_alert(
incident_id="INC-TEST",
approval_id="APR-TEST",
alertname="AlertChainBroken",
alert_category="alertchain_health",
diagnosis="test",
)
elif method_name == "secops":
result = await gateway.send_secops_card(
incident_id="INC-TEST",
approval_id="APR-TEST",
alertname="Threat",
threat_level="critical",
)
else:
result = await gateway.send_escalation_card(
incident_id="INC-TEST",
original_alertname="Outage",
duration_min=15,
)
assert result["ok"] is True
assert result["_awoooi_canonical_route_receipt"]["decision"] == "allowed"
assert result["_awoooi_canonical_route_receipt"]["provider_send_performed"] is True
assert gateway_module._telegram_send_delivery_succeeded(result) is True
assert len(client.posts) == 1
assert client.posts[0][1]["chat_id"] == "test-room"
@pytest.mark.asyncio
async def test_provider_ok_without_message_id_is_non_terminal(
monkeypatch: pytest.MonkeyPatch,
) -> None:
gateway, client = _prepared_gateway(monkeypatch)
monkeypatch.setattr(
_FakeResponse,
"json",
lambda _self: {"ok": True, "result": {}},
)
result = await gateway.send_escalation_card(
incident_id="INC-TEST",
original_alertname="Outage",
duration_min=15,
)
assert result["_awooop_delivery_status"] == "provider_message_id_missing"
assert result["_awooop_provider_send_performed"] is True
assert result["_awoooi_canonical_route_receipt"]["provider_send_performed"] is True
assert gateway_module._telegram_send_delivery_succeeded(result) is False
assert len(client.posts) == 1
@pytest.mark.asyncio
async def test_provider_chat_mismatch_is_non_terminal_delivery(
monkeypatch: pytest.MonkeyPatch,
) -> None:
gateway, client = _prepared_gateway(monkeypatch)
monkeypatch.setattr(
_FakeResponse,
"json",
lambda _self: {
"ok": True,
"result": {"message_id": 11, "chat": {"id": "wrong-room"}},
},
)
result = await gateway.send_escalation_card(
incident_id="INC-TEST",
original_alertname="Outage",
duration_min=15,
)
assert result["_awooop_delivery_status"] == "provider_destination_mismatch"
assert result["_awooop_provider_send_performed"] is True
assert (
result[gateway_module._DELIVERY_CONTEXT_KEY][
"destination_binding_verified"
]
is False
)
assert gateway_module._telegram_send_delivery_succeeded(result) is False
assert len(client.posts) == 1
@pytest.mark.asyncio
async def test_openclaw_cannot_own_tsenyang_monitoring_route(
monkeypatch: pytest.MonkeyPatch,
) -> None:
gateway, client = _prepared_gateway(monkeypatch)
monkeypatch.setattr(
gateway_module.settings,
"OPENCLAW_BOT_TOKEN",
"test-token-placeholder",
)
result = await gateway._send_as_bot(
token="test-token-placeholder",
text="must not send",
product_id="awoooi",
signal_family="security_incident",
severity="P1",
actual_bot_alias="openclaw_bot",
)
assert result["_awooop_delivery_status"] == "blocked_no_egress"
assert result["_awoooi_canonical_route_receipt"]["classifier"] == (
"sender_bot_alias_not_authorized_for_route"
)
assert client.posts == []
@pytest.mark.asyncio
async def test_alternate_bot_uses_only_canonical_send_request(
monkeypatch: pytest.MonkeyPatch,
) -> None:
gateway = TelegramGateway()
canonical_send = AsyncMock(
return_value={
"ok": True,
"_awooop_delivery_status": "sent",
"_awooop_provider_send_performed": True,
"result": {"message_id": 11},
"_awoooi_canonical_route_receipt": {
"schema_version": "telegram_canonical_egress_receipt_v1",
"decision": "allowed",
"provider_send_performed": True,
},
}
)
monkeypatch.setattr(gateway, "_send_request", canonical_send)
result = await gateway._send_as_bot(
token="test-token-placeholder",
text="bounded reply",
reply_to_message_id=11,
actual_bot_alias="openclaw_bot",
inbound_chat_id="verified-room",
)
assert result["_awooop_delivery_status"] == "sent"
canonical_send.assert_awaited_once()
method, payload = canonical_send.await_args.args
assert method == "sendMessage"
assert payload[gateway_module._ACTUAL_BOT_ALIAS_KEY] == "openclaw_bot"
assert payload[gateway_module._BOT_TOKEN_OVERRIDE_KEY] == ("test-token-placeholder")
assert payload[gateway_module._NON_MONITORING_INTERACTION_KEY] == {
"interaction_kind": "bot_discussion_reply",
"inbound_chat_id": "verified-room",
"inbound_message_id": 11,
}
@pytest.mark.asyncio
async def test_alternate_bot_binding_mismatch_blocks_before_http(
monkeypatch: pytest.MonkeyPatch,
) -> None:
gateway, client = _prepared_gateway(monkeypatch)
monkeypatch.setattr(
gateway_module.settings,
"OPENCLAW_BOT_TOKEN",
"expected-placeholder",
)
result = await gateway._send_as_bot(
token="mismatched-placeholder",
text="must not send",
reply_to_message_id=11,
actual_bot_alias="openclaw_bot",
inbound_chat_id="test-room",
)
assert result["_awooop_delivery_status"] == "blocked_no_egress"
assert result["_awoooi_canonical_route_receipt"]["classifier"] == (
"sender_bot_binding_mismatch"
)
assert client.posts == []
def test_readability_guard_uses_complete_ast_function_boundary() -> None:
guard = runpy.run_path(
str(_REPO_ROOT / "scripts" / "security" / "telegram-alert-readability-guard.py")
)
long_body = "\n".join(f" value_{index} = {index}" for index in range(300))
source = (
'DECOY = "async def _send_request"\n'
"class Gateway:\n"
" async def _send_request(self, payload):\n"
f"{long_body}\n"
" return normalize_telegram_send_message_payload(\n"
' "sendMessage", payload\n'
" )\n\n"
" async def unrelated(self):\n"
" return False\n"
)
segment = guard["function_segment"](
source,
"async def _send_request",
)
assert len(segment) > 2200
assert "normalize_telegram_send_message_payload" in segment
assert "async def unrelated" not in segment
@pytest.mark.asyncio
async def test_operator_reply_lane_and_runtime_edit_binding_remain_available(
monkeypatch: pytest.MonkeyPatch,
) -> None:
gateway, client = _prepared_gateway(monkeypatch)
assert gateway.chat_id == "test-room"
assert gateway.alert_chat_id == "test-room"
unverified = await gateway.send_notification(
"must not infer an operator reply",
chat_id="operator-room",
)
assert unverified["_awooop_delivery_status"] == "blocked_no_egress"
assert unverified["_awoooi_canonical_route_receipt"]["classifier"] == (
"missing_product_signal_severity_or_interaction_context"
)
assert client.posts == []
result = await gateway.send_notification(
"command reply",
chat_id="operator-room",
interaction_kind="operator_command_reply",
inbound_chat_id="operator-room",
inbound_message_id=42,
)
assert result["ok"] is True
receipt = result["_awoooi_canonical_route_receipt"]
assert receipt["classifier"] == "non_monitoring_interaction_allowed"
assert receipt["destination_alias"] == "inbound_interaction_reply_target"
assert client.posts[0][1]["chat_id"] == "operator-room"
assert client.posts[0][1]["reply_to_message_id"] == 42
@pytest.mark.asyncio
async def test_interaction_reply_must_match_verified_inbound_context(
monkeypatch: pytest.MonkeyPatch,
) -> None:
gateway, client = _prepared_gateway(monkeypatch)
result = await gateway._send_request(
"sendMessage",
{
"chat_id": "operator-room",
"text": "mismatched reply",
"reply_to_message_id": 43,
gateway_module._NON_MONITORING_INTERACTION_KEY: {
"interaction_kind": "operator_command_reply",
"inbound_chat_id": "operator-room",
"inbound_message_id": 42,
},
},
)
assert result["_awooop_delivery_status"] == "blocked_no_egress"
assert result["_awoooi_canonical_route_receipt"]["classifier"] == (
"interaction_reply_context_missing_or_mismatched"
)
assert client.posts == []
@pytest.mark.asyncio
async def test_t1_through_t6_use_only_canonical_monitoring_routes(
monkeypatch: pytest.MonkeyPatch,
) -> None:
gateway = TelegramGateway()
canonical_send = AsyncMock(return_value={"ok": True})
monkeypatch.setattr(gateway, "send_canonical_message", canonical_send)
await gateway.send_guardrail_blocked("api", "HighCpu", "protected")
await gateway.send_preflight_failed("api", 25.0, 24.0, "backup-1")
await gateway.send_backup_result("backup-1", True)
await gateway.send_multisig_waiting("repair", "api", 1, 2, "APR-1")
await gateway.send_multisig_approved("repair", "api")
await gateway.send_change_applied("operator", "repair", "2026-07-14T00:00:00Z")
assert canonical_send.await_count == 6
assert [
(
call.kwargs["product_id"],
call.kwargs["signal_family"],
call.kwargs["severity"],
)
for call in canonical_send.await_args_list
] == [
("awoooi", "security_incident", "P1"),
("awoooi", "disaster_recovery", "P1"),
("awoooi", "disaster_recovery", "P1"),
("awoooi", "incident_lifecycle", "P1"),
("awoooi", "incident_lifecycle", "P1"),
("awoooi", "incident_lifecycle", "P1"),
]
@pytest.mark.asyncio
async def test_image_analysis_reply_preserves_original_inbound_message_context(
monkeypatch: pytest.MonkeyPatch,
) -> None:
from src.services.image_analysis_service import ImageAnalysisService
gateway = SimpleNamespace(
initialize=AsyncMock(),
send_as_openclaw=AsyncMock(return_value={"ok": True}),
)
service = ImageAnalysisService()
monkeypatch.setattr(
gateway_module,
"get_telegram_gateway",
lambda: gateway,
)
monkeypatch.setattr(
service,
"_download_telegram_file",
AsyncMock(return_value=b"image"),
)
monkeypatch.setattr(
service,
"analyze",
AsyncMock(return_value="analysis"),
)
await service.download_and_analyze(
chat_id="test-room",
file_id="file-1",
original_message_id=77,
)
gateway.initialize.assert_awaited_once()
gateway.send_as_openclaw.assert_awaited_once_with(
"🖼️ <b>圖片分析</b>\nanalysis",
reply_to_message_id=77,
inbound_chat_id="test-room",
)
@pytest.mark.asyncio
async def test_manual_repair_callback_replies_to_verified_original_message(
monkeypatch: pytest.MonkeyPatch,
) -> None:
gateway, client = _prepared_gateway(monkeypatch)
monkeypatch.setattr(
gateway._security,
"parse_callback_data",
lambda _data: {
"action": "log_manual_fix",
"approval_id": "APR-1",
"is_info_action": False,
"nonce": "nonce-1",
},
)
monkeypatch.setattr(
gateway._security,
"verify_callback",
AsyncMock(return_value={"id": 123, "username": "operator"}),
)
monkeypatch.setattr(
gateway,
"_check_incident_state_guard",
AsyncMock(return_value=None),
)
monkeypatch.setattr(gateway, "_answer_callback", AsyncMock())
monkeypatch.setattr(gateway_module, "get_redis", lambda: _FakeRedis())
result = await gateway.handle_callback(
callback_query_id="callback-1",
callback_data="log_manual_fix:APR-1:nonce-1",
user_id=123,
message_id=77,
chat_id="operator-room",
username="operator",
)
assert result["success"] is True
assert result["waiting_for_manual_fix"] is True
assert len(client.posts) == 1
payload = client.posts[0][1]
assert payload["chat_id"] == "operator-room"
assert payload["reply_to_message_id"] == 77
@pytest.mark.asyncio
async def test_manual_repair_flow_blocks_before_side_effect_without_inbound_context(
monkeypatch: pytest.MonkeyPatch,
) -> None:
gateway, client = _prepared_gateway(monkeypatch)
result = await gateway.handle_manual_fix_done(
user_id=123,
username="operator",
fix_steps="restart",
)
assert result == {
"success": False,
"reason": "missing_verified_interaction_context",
}
assert client.posts == []
class _FakeDb:
def __init__(self) -> None:
self.params: list[dict] = []
async def execute(self, statement: object, params: dict) -> None:
del statement
self.params.append(params)
class _FakeRedis:
async def setex(self, key: str, ttl: int, value: str) -> None:
del key, ttl, value
def _patch_approval_dependencies(
monkeypatch: pytest.MonkeyPatch,
gateway: TelegramGateway,
) -> _FakeDb:
fake_db = _FakeDb()
@asynccontextmanager
async def fake_db_context(*args, **kwargs):
del args, kwargs
yield fake_db
monkeypatch.setattr(db_base, "get_db_context", fake_db_context)
monkeypatch.setattr(
approval_db,
"get_approval_service",
lambda: SimpleNamespace(update_telegram_message=True),
)
monkeypatch.setattr(gateway_module, "get_redis", lambda: _FakeRedis())
monkeypatch.setattr(
gateway_module,
"_fetch_remediation_summary_for_card",
AsyncMock(return_value=None),
)
monkeypatch.setattr(gateway, "_build_inline_keyboard", AsyncMock(return_value={}))
return fake_db
@pytest.mark.asyncio
async def test_approval_missing_type_is_blocked_without_provider_call(
monkeypatch: pytest.MonkeyPatch,
) -> None:
gateway, client = _prepared_gateway(monkeypatch)
_patch_approval_dependencies(monkeypatch, gateway)
result = await gateway.send_approval_card(
approval_id="APR-TEST",
risk_level="critical",
resource_name="resource",
root_cause="cause",
suggested_action="review",
notification_type="",
)
assert result["_awooop_delivery_status"] == "blocked_no_egress"
assert client.posts == []
@pytest.mark.asyncio
async def test_allowed_type3_approval_persists_runtime_chat_without_public_id(
monkeypatch: pytest.MonkeyPatch,
) -> None:
gateway, client = _prepared_gateway(monkeypatch)
fake_db = _patch_approval_dependencies(monkeypatch, gateway)
monkeypatch.setattr(gateway, "canonical_destination_chat_id", lambda **kwargs: "7")
result = await gateway.send_approval_card(
approval_id="APR-TEST",
risk_level="critical",
resource_name="resource",
root_cause="cause",
suggested_action="review",
notification_type="TYPE-3",
)
assert result["ok"] is True
assert len(client.posts) == 1
assert any(params.get("cid") == 7 for params in fake_db.params)
receipt = result["_awoooi_canonical_route_receipt"]
assert receipt["destination_alias"] == "awoooi_sre_war_room"
assert "chat_id" not in receipt
@pytest.mark.asyncio
async def test_explicit_blocked_product_route_supersedes_legacy_type3(
monkeypatch: pytest.MonkeyPatch,
) -> None:
gateway, client = _prepared_gateway(monkeypatch)
_patch_approval_dependencies(monkeypatch, gateway)
result = await gateway.send_approval_card(
approval_id="APR-PRODUCT",
risk_level="medium",
resource_name="momo-crawler",
root_cause="freshness degraded",
suggested_action="inspect evidence",
notification_type="TYPE-3",
product_id="momo-pro",
signal_family="crawler_and_data_freshness",
route_severity="P2",
)
assert result["_awooop_delivery_status"] == "blocked_no_egress"
assert result["_awooop_provider_send_performed"] is False
assert client.posts == []
receipt = result["_awoooi_canonical_route_receipt"]
assert receipt["product_id"] == "momo-pro"
assert receipt["signal_family"] == "crawler_and_data_freshness"