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

105 lines
3.3 KiB
Python

"""Incident readback must not regress durable closure to stale cache state."""
from datetime import UTC, datetime
from unittest.mock import AsyncMock
import pytest
from src.api.v1 import incidents as incidents_api
from src.models.incident import Incident, IncidentStatus, Severity
from src.repositories import incident_repository as incident_repository_module
from src.services.incident_service import IncidentService
def _incident(status: IncidentStatus, *, minute: int) -> Incident:
timestamp = datetime(2026, 7, 11, 11, minute, tzinfo=UTC)
return Incident(
incident_id="INC-20260711-SOURCE-TRUTH",
status=status,
severity=Severity.P2,
affected_services=["awoooi-api"],
created_at=datetime(2026, 7, 11, 10, 0, tzinfo=UTC),
updated_at=timestamp,
resolved_at=timestamp if status is IncidentStatus.RESOLVED else None,
)
@pytest.mark.asyncio
async def test_readback_prefers_durable_resolved_incident_over_stale_cache(monkeypatch):
service = IncidentService()
durable = _incident(IncidentStatus.RESOLVED, minute=38)
cached = _incident(IncidentStatus.INVESTIGATING, minute=11)
monkeypatch.setattr(
service,
"get_from_episodic_memory",
AsyncMock(return_value=durable),
)
working_read = AsyncMock(return_value=cached)
monkeypatch.setattr(service, "get_from_working_memory", working_read)
result = await service.get_for_readback(durable.incident_id)
assert result is durable
assert result.status is IncidentStatus.RESOLVED
working_read.assert_not_awaited()
@pytest.mark.asyncio
async def test_readback_falls_back_to_working_memory_when_durable_record_unavailable(
monkeypatch,
):
service = IncidentService()
cached = _incident(IncidentStatus.INVESTIGATING, minute=11)
monkeypatch.setattr(
service,
"get_from_episodic_memory",
AsyncMock(return_value=None),
)
monkeypatch.setattr(
service,
"get_from_working_memory",
AsyncMock(return_value=cached),
)
result = await service.get_for_readback(cached.incident_id)
assert result is cached
@pytest.mark.asyncio
async def test_incident_detail_endpoint_uses_durable_readback(monkeypatch):
durable = _incident(IncidentStatus.RESOLVED, minute=38)
class _Service:
get_for_readback = AsyncMock(return_value=durable)
service = _Service()
monkeypatch.setattr(incidents_api, "get_incident_service", lambda: service)
result = await incidents_api.get_incident(durable.incident_id)
assert result.incident_id == durable.incident_id
assert result.status == IncidentStatus.RESOLVED.value
service.get_for_readback.assert_awaited_once_with(durable.incident_id)
@pytest.mark.asyncio
async def test_active_incidents_come_from_durable_repository(monkeypatch):
service = IncidentService()
durable_active = _incident(IncidentStatus.INVESTIGATING, minute=38)
class _Repository:
get_active = AsyncMock(return_value=[durable_active])
repository = _Repository()
monkeypatch.setattr(
incident_repository_module,
"get_incident_repository",
lambda: repository,
)
incidents = await service.get_active_incidents()
assert incidents == [durable_active]
repository.get_active.assert_awaited_once_with()