All checks were successful
CD Pipeline / workflow-shape (push) Successful in 1s
CD Pipeline / cancel-stale-cd (push) Has been skipped
CD Pipeline / tests (push) Successful in 2m27s
CD Pipeline / build-and-deploy (push) Successful in 6m39s
CD Pipeline / post-deploy-checks (push) Successful in 1m55s
307 lines
12 KiB
Python
307 lines
12 KiB
Python
from __future__ import annotations
|
||
|
||
from src.services.telegram_gateway import (
|
||
_agent99_actionable_outbound_metadata,
|
||
_agent99_alert_from_actionable_outbound,
|
||
_agent99_alert_from_ai_automation_card,
|
||
_merge_outbound_source_envelope_extra,
|
||
_outbound_source_envelope,
|
||
_sanitize_telegram_error,
|
||
format_aiops_signal_alert_card,
|
||
)
|
||
|
||
|
||
def test_telegram_gateway_sanitizes_bot_token_url() -> None:
|
||
raw = "Client error for https://api.telegram.org/bot123456:SECRET/sendMessage"
|
||
|
||
sanitized = _sanitize_telegram_error(raw)
|
||
|
||
assert "SECRET" not in sanitized
|
||
assert "bot<redacted>" in sanitized
|
||
|
||
|
||
def test_outbound_source_envelope_keeps_replay_context_without_raw_payload() -> None:
|
||
payload = {
|
||
"chat_id": "-100123",
|
||
"text": (
|
||
"ACTION REQUIRED INC-20260513-9B082D "
|
||
"<code>7f858956</code> token "
|
||
"1234567890:abcdefghijklmnopqrstuvwxyzABCDEFGH"
|
||
),
|
||
"parse_mode": "HTML",
|
||
"reply_markup": {
|
||
"inline_keyboard": [
|
||
[
|
||
{"text": "批准", "callback_data": "approve:approval-id-secret"},
|
||
{"text": "詳情", "callback_data": "details:approval-id-secret"},
|
||
]
|
||
]
|
||
},
|
||
}
|
||
|
||
envelope = _outbound_source_envelope("sendMessage", payload)
|
||
|
||
assert envelope["adapter"] == "legacy_telegram_gateway"
|
||
assert envelope["method"] == "sendMessage"
|
||
assert envelope["payload_sha256"]
|
||
assert envelope["payload_keys"] == ["chat_id", "parse_mode", "reply_markup", "text"]
|
||
assert envelope["parse_mode"] == "HTML"
|
||
assert envelope["reply_markup"]["button_count"] == 2
|
||
assert envelope["reply_markup"]["buttons"][0]["callback_prefix"] == "approve"
|
||
assert envelope["reply_markup"]["buttons"][1]["callback_prefix"] == "details"
|
||
assert envelope["source_refs"]["incident_ids"] == ["INC-20260513-9B082D"]
|
||
assert envelope["source_refs"]["code_refs"] == ["7f858956"]
|
||
assert "approval-id-secret" not in str(envelope)
|
||
assert "1234567890:" not in str(envelope)
|
||
assert "ACTION REQUIRED" not in str(envelope)
|
||
|
||
|
||
def test_outbound_source_envelope_reads_incident_refs_from_buttons() -> None:
|
||
payload = {
|
||
"chat_id": "-100123",
|
||
"text": "ACTION REQUIRED without incident id in visible text",
|
||
"reply_markup": {
|
||
"inline_keyboard": [
|
||
[
|
||
{"text": "詳情", "callback_data": "detail:INC-20260525-ABC123"},
|
||
{"text": "歷史", "callback_data": "history:INC-20260525-ABC123"},
|
||
],
|
||
[
|
||
{"text": "重診", "callback_data": "reanalyze:INC-20260525-DEF456"},
|
||
],
|
||
],
|
||
},
|
||
}
|
||
|
||
envelope = _outbound_source_envelope("sendMessage", payload)
|
||
|
||
assert envelope["source_refs"]["incident_ids"] == [
|
||
"INC-20260525-ABC123",
|
||
"INC-20260525-DEF456",
|
||
]
|
||
assert envelope["reply_markup"]["buttons"][0]["callback_prefix"] == "detail"
|
||
assert "detail:INC-20260525-ABC123" not in str(envelope)
|
||
|
||
|
||
def test_outbound_source_envelope_reads_meta_event_ref_from_text() -> None:
|
||
payload = {
|
||
"chat_id": "-100123",
|
||
"text": (
|
||
"⚙️ META SYSTEM\n"
|
||
"📋 <code>META-20260525201300</code>\n"
|
||
"異常元件:AI 自健診異常"
|
||
),
|
||
"parse_mode": "HTML",
|
||
"reply_markup": {
|
||
"inline_keyboard": [
|
||
[{"text": "靜默", "callback_data": "silence:opaque-secret"}]
|
||
],
|
||
},
|
||
}
|
||
|
||
envelope = _outbound_source_envelope("sendMessage", payload)
|
||
|
||
assert envelope["source_refs"]["event_ids"] == ["META-20260525201300"]
|
||
assert envelope["source_refs"]["incident_ids"] == []
|
||
assert envelope["reply_markup"]["buttons"][0]["callback_prefix"] == "silence"
|
||
assert "opaque-secret" not in str(envelope)
|
||
|
||
|
||
def test_outbound_source_envelope_reads_ai_advisory_refs_without_raw_callback() -> None:
|
||
payload = {
|
||
"chat_id": "-100123",
|
||
"text": "Coverage 缺口分析",
|
||
"reply_markup": {
|
||
"inline_keyboard": [
|
||
[
|
||
{
|
||
"text": "已處理",
|
||
"callback_data": (
|
||
"ai_advisory_handled:coverage_gap:auto_rule_creation"
|
||
),
|
||
},
|
||
{
|
||
"text": "忽略",
|
||
"callback_data": (
|
||
"ai_advisory_snooze:coverage_gap:auto_rule_creation"
|
||
),
|
||
},
|
||
]
|
||
],
|
||
},
|
||
}
|
||
|
||
envelope = _outbound_source_envelope("sendMessage", payload)
|
||
|
||
assert envelope["source_refs"]["event_ids"] == [
|
||
"ai_advisory:coverage_gap:auto_rule_creation"
|
||
]
|
||
assert envelope["source_refs"]["advisory_ids"] == [
|
||
"coverage_gap:auto_rule_creation"
|
||
]
|
||
assert envelope["source_refs"]["alert_ids"] == ["coverage_gap"]
|
||
assert envelope["source_refs"]["fingerprints"] == [
|
||
"ai_advisory:coverage_gap:auto_rule_creation"
|
||
]
|
||
assert "ai_advisory_handled:coverage_gap:auto_rule_creation" not in str(envelope)
|
||
|
||
|
||
def test_outbound_source_envelope_marks_wazuh_ai_alert_card_for_awooop_readback() -> None:
|
||
raw_alert = """
|
||
wazuh_dashboard_api_readback_degraded dashboard agent list disappeared
|
||
POST /api/check-stored-api status=429 POST /api/check-api status=500
|
||
https://127.0.0.1:55000 is unreachable manager registry readback blocked
|
||
full_log=/var/ossec/logs/alerts/alerts.json Authorization: Bearer abcdefghijklmnopqrstuvwxyz
|
||
"""
|
||
card = format_aiops_signal_alert_card(raw_alert)
|
||
payload = {
|
||
"chat_id": "-100123",
|
||
"text": card,
|
||
"parse_mode": "HTML",
|
||
}
|
||
|
||
envelope = _outbound_source_envelope("sendMessage", payload)
|
||
|
||
card_metadata = envelope["ai_automation_alert_card"]
|
||
assert card_metadata["schema_version"] == "ai_automation_alert_card_mirror_v1"
|
||
assert card_metadata["card_schema"] == "ai_automation_alert_card_v1"
|
||
assert card_metadata["event_type"] == "wazuh_dashboard_api_readback_degraded"
|
||
assert card_metadata["lane"] == "siem_observability_readback_degraded"
|
||
assert card_metadata["target"] == "wazuh_dashboard_api"
|
||
assert card_metadata["gates"] == [
|
||
"controlled_playbook_queue",
|
||
"runtime_write_gate=controlled",
|
||
]
|
||
assert card_metadata["controlled_playbook_queue"] is True
|
||
assert card_metadata["runtime_write_gate_state"] == "controlled"
|
||
assert card_metadata["runtime_write_gate_count"] == 1
|
||
assert card_metadata["delivery_receipt_readback_required"] is True
|
||
assert envelope["source_refs"]["alert_ids"] == [
|
||
"wazuh_dashboard_api_readback_degraded"
|
||
]
|
||
assert envelope["source_refs"]["fingerprints"] == [
|
||
"ai_automation_alert_card:wazuh_dashboard_api_readback_degraded:siem_observability_readback_degraded"
|
||
]
|
||
assert "127.0.0.1:55000" not in str(envelope)
|
||
assert "/var/ossec" not in str(envelope)
|
||
assert "abcdefghijkl" not in str(envelope)
|
||
|
||
|
||
def test_ai_alert_card_builds_agent99_provider_freshness_signal() -> None:
|
||
raw_alert = """
|
||
SourceProviderIngestionStale provider_freshness_signal freshness window timeout
|
||
Sentry / SigNoz provider freshness last seen missing direct ref verifier readback
|
||
Authorization: Bearer abcdefghijklmnopqrstuvwxyz
|
||
"""
|
||
card = format_aiops_signal_alert_card(raw_alert)
|
||
|
||
payload = _agent99_alert_from_ai_automation_card(
|
||
card,
|
||
source="unit-test",
|
||
)
|
||
|
||
assert payload is not None
|
||
assert payload["source"] == "awoooi-api-telegram-gateway"
|
||
assert str(payload["id"]).startswith(
|
||
"awoooi-telegram-alert-card-provider_freshness_signal-"
|
||
)
|
||
assert payload["kind"] == "provider_freshness_signal"
|
||
assert payload["suggestedMode"] == "ProviderFreshness"
|
||
assert payload["controlledApply"] is False
|
||
assert payload["routing"]["routeSource"] == "structured_kind"
|
||
assert payload["awoooi"]["alertCategory"] == "source_provider_freshness_triage"
|
||
assert payload["awoooi"]["notificationType"] == "ai_automation_alert_card_v1"
|
||
assert "component=telegram_gateway" in payload["labels"]
|
||
assert "source=unit-test" in payload["labels"]
|
||
assert "provider-freshness" in payload["labels"]
|
||
assert "no-false-green" in payload["labels"]
|
||
assert "abcdefghijklmnopqrstuvwxyz" not in str(payload)
|
||
|
||
|
||
def test_action_required_cold_start_card_routes_to_agent99_recover() -> None:
|
||
card = (
|
||
"ℹ️ ACTION REQUIRED | <b>低風險</b>\n"
|
||
"──────────────────────\n"
|
||
"📋 <code>INC-20260710-5700F7</code>\n"
|
||
"🎯 資源:<code>cold-start-gate</code>\n"
|
||
"🏷️ 分類:<b>general</b>\n"
|
||
"🧭 處置狀態:<b>AI 選擇不執行修復</b>\n"
|
||
"🤖 <b>AI 自動化鏈路</b>"
|
||
)
|
||
|
||
metadata = _agent99_actionable_outbound_metadata(card)
|
||
payload = _agent99_alert_from_actionable_outbound(card, source="unit-test")
|
||
|
||
assert metadata is not None
|
||
assert metadata["card_schema"] == "awooop_action_required_v1"
|
||
assert metadata["incident_id"] == "INC-20260710-5700F7"
|
||
assert metadata["target"] == "cold-start-gate"
|
||
assert payload is not None
|
||
assert payload["kind"] == "host_recovery"
|
||
assert payload["suggestedMode"] == "Recover"
|
||
assert payload["controlledApply"] is True
|
||
assert payload["resolution"]["policy"] == "mode_verifier_can_resolve"
|
||
assert payload["awoooi"]["incidentId"] == "INC-20260710-5700F7"
|
||
|
||
|
||
def test_failed_cicd_card_routes_to_agent99_status_with_commit_receipt() -> None:
|
||
card = (
|
||
"❌ <b>[AWOOOI CI/CD]</b> | build-and-deploy\n"
|
||
"📦 AWOOOI 部署失敗\n"
|
||
"📋 <code>85159f25</code>\n"
|
||
"📝 fix(security): restrict executor SSH egress\n"
|
||
"🔗 <a href='https://gitea.example.invalid/run'>Workflow</a>"
|
||
)
|
||
|
||
metadata = _agent99_actionable_outbound_metadata(card)
|
||
payload = _agent99_alert_from_actionable_outbound(card, source="unit-test")
|
||
|
||
assert metadata is not None
|
||
assert metadata["card_schema"] == "awoooi_cicd_failure_v1"
|
||
assert metadata["commit_sha"] == "85159f25"
|
||
assert payload is not None
|
||
assert payload["kind"] == "cicd_failure"
|
||
assert payload["suggestedMode"] == "Status"
|
||
assert payload["controlledApply"] is False
|
||
assert payload["resolution"]["policy"] == "external_source_receipt_required"
|
||
assert payload["awoooi"]["commitSha"] == "85159f25"
|
||
assert "gitea.example.invalid" not in str(payload)
|
||
|
||
|
||
def test_agent99_dispatch_receipt_is_persisted_without_raw_transport_body() -> None:
|
||
envelope = _outbound_source_envelope(
|
||
"sendMessage",
|
||
{
|
||
"chat_id": "chat",
|
||
"text": (
|
||
"ℹ️ ACTION REQUIRED | 低風險\n"
|
||
"📋 INC-20260710-5700F7\n"
|
||
"🎯 資源:cold-start-gate\n"
|
||
"🤖 AI 自動化鏈路"
|
||
),
|
||
},
|
||
)
|
||
receipt = {
|
||
"schema_version": "telegram_agent99_dispatch_receipt_v1",
|
||
"status": "accepted_inbox_triggered",
|
||
"transport": "relay",
|
||
"alert_id": "awoooi-telegram-actionable-awooop_action_required-safehash",
|
||
"kind": "host_recovery",
|
||
"suggested_mode": "Recover",
|
||
"accepted": True,
|
||
"inbox_triggered": True,
|
||
"stores_raw_response": False,
|
||
}
|
||
|
||
merged = _merge_outbound_source_envelope_extra(
|
||
envelope,
|
||
{"agent99_dispatch_receipt": receipt},
|
||
)
|
||
|
||
assert merged["agent99_dispatch_receipt"] == receipt
|
||
assert merged["agent99_actionable_outbound"]["incident_id"] == (
|
||
"INC-20260710-5700F7"
|
||
)
|
||
assert "incomingFile" not in str(merged)
|
||
assert "private-path" not in str(merged)
|