"""Regression: a compliance incident is closed exactly once. resolve_incident historically overwrote status/resolved_at/resolved_by/ note unconditionally, so a double-submit (or a retried resolve after a transient client error) silently re-stamped an already-closed incident and could flip accepted<->resolved. The service now rejects the second close with a 409 so the resolution stays attributable. """ from datetime import UTC, datetime from uuid import uuid4 import pytest from app.compliance.models import ( INCIDENT_OPEN, INCIDENT_RESOLVED, SEVERITY_CRITICAL, TYPE_LEAVER_ORPHAN, ComplianceIncident, ) from app.compliance.service import ComplianceIncidentService from app.core.exceptions import AppException pytestmark = pytest.mark.asyncio async def _make_open_incident(db_session) -> ComplianceIncident: incident = ComplianceIncident( id=uuid4(), incident_type=TYPE_LEAVER_ORPHAN, severity=SEVERITY_CRITICAL, status=INCIDENT_OPEN, resource_type="asset", resource_id=uuid4(), resource_label="An asset", title="Orphan asset", detected_at=datetime.now(UTC), ) db_session.add(incident) await db_session.flush() return incident async def test_resolve_incident_is_idempotent(db_session): svc = ComplianceIncidentService(db_session) incident = await _make_open_incident(db_session) # First resolve succeeds and closes the incident. row = await svc.resolve_incident( incident.id, resolved_by_person_id=None, resolution_note="fixed", accepted=False, ) assert row.status == INCIDENT_RESOLVED first_resolved_at = row.resolved_at # Second resolve (e.g. a double-submit) must be rejected, not silently # overwrite the prior close. with pytest.raises(AppException) as exc: await svc.resolve_incident( incident.id, resolved_by_person_id=None, resolution_note="changed my mind", accepted=True, ) assert exc.value.status_code == 409 # The original close is untouched. refreshed = await svc.get_incident(incident.id) assert refreshed.status == INCIDENT_RESOLVED assert refreshed.resolved_at == first_resolved_at assert refreshed.resolution_note == "fixed"