Files
awoooi/apps/api/tests/test_backup_restore_alertmanager_ingress.py

502 lines
18 KiB
Python

from __future__ import annotations
import hashlib
from datetime import UTC, datetime
from types import SimpleNamespace
from unittest.mock import AsyncMock
from uuid import UUID
import pytest
from fastapi import BackgroundTasks
from starlette.requests import Request
from src.api.v1 import webhooks
from src.services import backup_restore_alertmanager_ingress as ingress
from src.services.agent99_controlled_dispatch_ledger import (
build_agent99_dispatch_receipt_envelope,
)
from src.services.agent99_sre_bridge import bridge_alertmanager_to_agent99
def _request() -> Request:
return Request(
{
"type": "http",
"method": "POST",
"path": "/api/v1/webhooks/alertmanager",
"scheme": "http",
"server": ("testserver", 80),
"client": ("127.0.0.1", 50000),
"query_string": b"",
"headers": [],
}
)
def _signal_kwargs() -> dict[str, object]:
return {
"project_id": "awoooi",
"alert_id": "backup-alert-1",
"alertname": "BackupCredentialEscrowEvidenceMissing",
"severity": "critical",
"namespace": "awoooi-prod",
"target_resource": "backup_restore",
"message": "backup freshness and escrow evidence missing",
"labels": {
"event_type": "backup_restore_escrow_signal",
"lane": "backup_restore_escrow_triage",
},
"annotations": {"summary": "readback verifier required"},
"source_fingerprint": "legacy-backup-occurrence:" + ("a" * 64),
"source_event_fingerprint": "alertmanager-native-fingerprint",
"source_started_at": "2026-07-11T10:00:00Z",
"alert_category": "backup",
"notification_type": "TYPE-1",
"source_url": "http://alertmanager/graph",
}
def test_backup_ingress_owner_is_stable_per_source_occurrence() -> None:
first = ingress.build_backup_restore_ingress_owner(
project_id="awoooi",
source_fingerprint="source-fingerprint",
source_event_fingerprint="native-fingerprint",
source_started_at="2026-07-11T10:00:00Z",
)
replay = ingress.build_backup_restore_ingress_owner(
project_id="awoooi",
source_fingerprint="source-fingerprint",
source_event_fingerprint="native-fingerprint",
source_started_at="2026-07-11T10:00:00Z",
)
new_firing = ingress.build_backup_restore_ingress_owner(
project_id="awoooi",
source_fingerprint="source-fingerprint",
source_event_fingerprint="native-fingerprint",
source_started_at="2026-07-12T10:00:00Z",
)
assert first == replay
assert new_firing["approval_id"] != first["approval_id"]
assert first["route_id"] == "agent99:backup_health:BackupCheck"
assert first["single_dispatch_owner"] == "alertmanager_raw_signal"
assert first["telegram_role"] == "receipt_projection_only"
assert first["controlled_apply"] is False
assert first["read_only"] is True
def test_backup_approval_fingerprint_is_domain_separated_and_bounded() -> None:
short = "source-fingerprint"
long = "legacy-backup-occurrence:" + ("a" * 64)
legacy_long_digest = hashlib.sha256(long.encode("utf-8")).hexdigest()
stored_short = ingress._approval_storage_fingerprint(short)
stored_long = ingress._approval_storage_fingerprint(long)
stored_deliberate_raw = ingress._approval_storage_fingerprint(
legacy_long_digest
)
assert stored_short != short
assert stored_long != legacy_long_digest
assert stored_long != stored_deliberate_raw
assert len(stored_short) == ingress.APPROVAL_FINGERPRINT_MAX_LENGTH
assert len(stored_long) == ingress.APPROVAL_FINGERPRINT_MAX_LENGTH
assert ingress._approval_storage_fingerprint(long) == stored_long
assert ingress._approval_fingerprint_lookup_candidates(short) == (
stored_short,
short,
)
assert ingress._approval_fingerprint_lookup_candidates(long) == (
stored_long,
legacy_long_digest,
)
@pytest.mark.asyncio
async def test_backup_approval_legacy_lookup_rejects_deliberate_collision() -> None:
long = "legacy-backup-occurrence:" + ("a" * 64)
deliberate_raw = hashlib.sha256(long.encode("utf-8")).hexdigest()
canonical, legacy = ingress._approval_fingerprint_lookup_candidates(long)
assert legacy == deliberate_raw
wrong_source = SimpleNamespace(
id=UUID("11111111-1111-4111-8111-111111111111"),
incident_id="INC-WRONG-SOURCE",
metadata={"source_fingerprint": deliberate_raw},
)
calls: list[str] = []
async def find_by_fingerprint(*, fingerprint: str, debounce_minutes: int):
calls.append(fingerprint)
assert debounce_minutes == 30
return wrong_source if fingerprint == legacy else None
result = await ingress._find_compatible_source_approval(
SimpleNamespace(find_by_fingerprint=find_by_fingerprint),
source_fingerprint=long,
)
assert result is None
assert calls == [canonical, legacy]
@pytest.mark.asyncio
async def test_backup_approval_legacy_89_char_lookup_remains_compatible() -> None:
source = "legacy-backup-occurrence:" + ("a" * 64)
canonical, legacy = ingress._approval_fingerprint_lookup_candidates(source)
existing = SimpleNamespace(
id=UUID("22222222-2222-4222-8222-222222222222"),
incident_id="INC-LEGACY-89-CHAR",
metadata={"source_fingerprint": source},
)
calls: list[str] = []
async def find_by_fingerprint(*, fingerprint: str, debounce_minutes: int):
calls.append(fingerprint)
assert debounce_minutes == 30
return existing if fingerprint == legacy else None
result = await ingress._find_compatible_source_approval(
SimpleNamespace(find_by_fingerprint=find_by_fingerprint),
source_fingerprint=source,
)
assert result is existing
assert result.incident_id == "INC-LEGACY-89-CHAR"
assert calls == [canonical, legacy]
@pytest.mark.asyncio
async def test_alertmanager_type1_backup_queues_durable_owner_before_info_shortcut(
monkeypatch: pytest.MonkeyPatch,
) -> None:
grouped = SimpleNamespace(is_grouped=False)
grouping = SimpleNamespace(evaluate=AsyncMock(return_value=grouped))
process = AsyncMock(return_value={"status": "queued"})
record = AsyncMock()
append = AsyncMock()
monkeypatch.setattr(webhooks, "get_alert_grouping_service", lambda: grouping)
monkeypatch.setattr(
"src.repositories.alert_operation_log_repository.get_alert_operation_log_repository",
lambda: SimpleNamespace(append=append),
)
monkeypatch.setattr(webhooks, "record_alertmanager_event", record)
monkeypatch.setattr(
webhooks,
"process_backup_restore_alertmanager_signal",
process,
)
monkeypatch.setattr(
webhooks,
"get_approval_service",
lambda: (_ for _ in ()).throw(
AssertionError("generic approval/TYPE-1 branch must not run")
),
)
tasks = BackgroundTasks()
payload = webhooks.AlertmanagerPayload(
status="firing",
alerts=[
webhooks.AlertmanagerAlert(
status="firing",
labels={
"alertname": "HostBackupFailed",
"severity": "warning",
"component": "backup_restore",
"namespace": "awoooi-prod",
},
annotations={
"summary": "backup freshness missing; readback required"
},
startsAt=datetime.now(UTC).isoformat(),
fingerprint="native-backup-fingerprint",
generatorURL="http://alertmanager/graph",
)
],
)
response = await webhooks.alertmanager_webhook(_request(), payload, tasks)
assert response.success is True
assert "durable read-only BackupCheck" in response.message
assert response.approval_created is False
assert process.await_count == 0
await tasks()
process.assert_awaited_once()
call = process.await_args.kwargs
assert call["alertname"] == "HostBackupFailed"
assert call["source_event_fingerprint"] == "native-backup-fingerprint"
assert call["notification_type"] == "TYPE-1"
assert call["alert_category"] == "backup"
assert call["target_resource"] == "backup_restore"
class _FakeApprovalService:
def __init__(self, calls: list[str]) -> None:
self.calls = calls
self.approval: SimpleNamespace | None = None
self.created_request = None
async def get_approval(self, approval_id: UUID):
self.calls.append("owner_read")
if self.approval is not None:
assert self.approval.id == approval_id
return self.approval
async def find_by_fingerprint(self, **_kwargs):
self.calls.append("legacy_owner_read")
expected = ingress._approval_fingerprint_lookup_candidates(
str(_signal_kwargs()["source_fingerprint"])
)
assert _kwargs["fingerprint"] in expected
return None
async def create_approval_with_fingerprint(self, *, request, fingerprint):
self.calls.append("owner_create")
self.created_request = request
assert fingerprint == ingress._approval_storage_fingerprint(
str(_signal_kwargs()["source_fingerprint"])
)
self.approval = SimpleNamespace(
id=UUID(request.metadata["preallocated_approval_id"]),
incident_id=request.incident_id,
hit_count=1,
risk_level=request.risk_level,
)
return self.approval
async def increment_hit_count(self, approval_id: UUID):
self.calls.append("owner_recurrence")
assert self.approval is not None
assert self.approval.id == approval_id
self.approval.hit_count += 1
return self.approval
async def update_incident_id(self, approval_id: UUID, incident_id: str):
self.calls.append("incident_bind")
assert self.approval is not None
assert self.approval.id == approval_id
self.approval.incident_id = incident_id
@pytest.mark.asyncio
async def test_backup_ingress_binds_canonical_incident_then_dispatches_and_reads_back(
monkeypatch: pytest.MonkeyPatch,
) -> None:
calls: list[str] = []
approval_service = _FakeApprovalService(calls)
incident_id = "INC-20260711-BACKUP"
identity: dict[str, str] = {}
async def create_incident(**_kwargs) -> str:
calls.append("incident_create")
return incident_id
async def canonical_readback(value: str, *, project_id: str):
calls.append("incident_canonical_readback")
assert value == incident_id
assert project_id == "awoooi"
return SimpleNamespace(status="investigating", persisted_to_pg=True)
async def dispatch(**kwargs):
calls.append("agent99_dispatch")
assert kwargs["incident_id"] == incident_id
assert kwargs["route_id"] == "agent99:backup_health:BackupCheck"
assert kwargs["fingerprint"] == _signal_kwargs()["source_fingerprint"]
identity.update({
"incident_id": incident_id,
"run_id": "12345678-1234-5678-9234-567812345678",
"trace_id": "00-11111111111111111111111111111111-2222222222222222-01",
"work_item_id": kwargs["work_item_id"],
})
return {
"status": "dispatched",
"dispatchPerformed": True,
"identity": dict(identity),
}
async def readback(**kwargs):
calls.append("dispatch_receipt_readback")
assert kwargs == {"project_id": "awoooi", "incident_id": incident_id}
return {
"status": "dispatch_accepted_verifier_pending",
"identity": dict(identity),
"dispatch_receipt": {
"kind": "backup_health",
"accepted": True,
"inbox_triggered": True,
},
"verifier": {"status": "pending"},
"runtime_execution_authorized": False,
"runtime_closure_verified": False,
}
monkeypatch.setattr(ingress, "get_approval_service", lambda: approval_service)
monkeypatch.setattr(ingress, "create_incident_for_approval", create_incident)
monkeypatch.setattr(
ingress,
"get_incident_service",
lambda: SimpleNamespace(get_for_readback=canonical_readback),
)
monkeypatch.setattr(ingress, "bridge_alertmanager_to_agent99", dispatch)
monkeypatch.setattr(ingress, "read_agent99_dispatch_receipt", readback)
monkeypatch.setattr(ingress, "record_alertmanager_event", AsyncMock())
result = await ingress.process_backup_restore_alertmanager_signal(
**_signal_kwargs()
)
assert approval_service.approval is not None
approval_service.approval.fingerprint = hashlib.sha256(
str(_signal_kwargs()["source_fingerprint"]).encode("utf-8")
).hexdigest()
replay = await ingress.process_backup_restore_alertmanager_signal(
**_signal_kwargs()
)
assert calls.index("owner_create") < calls.index("incident_create")
assert calls.index("incident_create") < calls.index("incident_bind")
assert calls.index("incident_bind") < calls.index("incident_canonical_readback")
assert calls.index("incident_canonical_readback") < calls.index(
"agent99_dispatch"
)
assert calls.index("agent99_dispatch") < calls.index(
"dispatch_receipt_readback"
)
assert result["status"] == "backupcheck_dispatched_verifier_pending"
assert result["receipt_persisted"] is True
assert result["accepted"] is True
assert result["verifier"]["status"] == "pending"
assert result["verifier"]["terminal"] is False
assert result["runtime_execution_authorized"] is False
assert result["runtime_closure_verified"] is False
assert result["production_write_performed"] is False
assert replay["owner_created"] is False
assert calls.count("owner_create") == 1
assert calls.count("incident_create") == 1
assert calls.count("owner_recurrence") == 1
assert approval_service.approval.fingerprint == hashlib.sha256(
str(_signal_kwargs()["source_fingerprint"]).encode("utf-8")
).hexdigest()
assert approval_service.created_request.risk_level.value == "low"
assert approval_service.created_request.metadata["telegram_role"] == (
"receipt_projection_only"
)
assert "run_restore" in result["prohibited_actions"]
class _FakeDispatchLedger:
def __init__(self) -> None:
self.receipt = None
self.complete_calls = 0
async def reserve(self, *, identity, payload):
if self.receipt is not None:
return {
"status": "idempotent_waiting_tool",
"claimed": False,
"identity": identity.public_dict(),
"receipt": self.receipt,
}
return {
"status": "reserved",
"claimed": True,
"claim_token": "claim-token-1",
"identity": identity.public_dict(),
"receipt": None,
}
async def complete(self, *, identity, dispatch_receipt, **_kwargs):
self.complete_calls += 1
self.receipt = {
**build_agent99_dispatch_receipt_envelope(
identity=identity,
dispatch_receipt=dispatch_receipt,
),
"receipt_persisted": True,
}
return self.receipt
async def mark_delivery_attempt_started(self, *, identity, **_kwargs):
self.receipt = {
"status": "dispatch_delivery_unknown_reconcile_only",
"identity": identity.public_dict(),
"retry_policy": {"safe_to_retry": False, "reconcile_only": True},
"receipt_persisted": True,
"runtime_closure_verified": False,
}
return self.receipt
@pytest.mark.asyncio
async def test_backup_bridge_postgres_identity_deduplicates_transport(
monkeypatch: pytest.MonkeyPatch,
) -> None:
ledger = _FakeDispatchLedger()
transported: list[dict[str, object]] = []
async def lock(*_args, **_kwargs):
return {"acquired": True, "reason": "acquired"}
monkeypatch.setattr(
"src.services.agent99_sre_bridge.get_agent99_dispatch_ledger",
lambda: ledger,
)
monkeypatch.setattr(
"src.services.agent99_sre_bridge.acquire_agent99_sre_single_flight",
lock,
)
monkeypatch.setattr(
"src.services.agent99_sre_bridge.dispatch_agent99_sre_alert_with_receipt",
lambda payload: transported.append(payload)
or {
"schema_version": "agent99_sre_dispatch_receipt_v1",
"status": "accepted_inbox_triggered",
"transport": "relay",
"alert_id": str(payload["id"]),
"kind": str(payload["kind"]),
"accepted": True,
"inbox_triggered": True,
"delivery_certainty": "delivered",
},
)
common = {
"alertname": "BackupCredentialEscrowEvidenceMissing",
"severity": "critical",
"namespace": "awoooi-prod",
"target_resource": "backup_restore",
"message": "backup freshness and escrow evidence missing",
"labels": {"event_type": "backup_restore_escrow_signal"},
"fingerprint": "stable-backup-source-fingerprint",
"project_id": "awoooi",
"incident_id": "INC-20260711-BACKUP",
"approval_id": "12345678-1234-5678-9234-567812345678",
"route_id": "agent99:backup_health:BackupCheck",
"work_item_id": "backupcheck:awoooi:stable",
}
first = await bridge_alertmanager_to_agent99(
alert_id="backup-alert-1",
**common,
)
replay = await bridge_alertmanager_to_agent99(
alert_id="backup-alert-2",
**common,
)
assert first["status"] == "dispatched"
assert replay["status"] == "deduplicated"
assert first["identity"] == replay["identity"]
assert first["dispatchPerformed"] is True
assert replay["dispatchPerformed"] is False
assert ledger.complete_calls == 1
assert len(transported) == 1
assert transported[0]["kind"] == "backup_health"
assert transported[0]["suggestedMode"] == "BackupCheck"
assert transported[0]["controlledApply"] is False
assert "do not run backup/restore" in str(transported[0]["instruction"])