fix(incidents): prefer durable closure readback

This commit is contained in:
ogt
2026-07-11 19:53:21 +08:00
parent 118b8b5ab4
commit 8a0c916d82
3 changed files with 212 additions and 5 deletions

View File

@@ -0,0 +1,104 @@
"""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()