Files
awoooi/apps/api/tests/test_incident_lifecycle_reconciler.py
ogt af4cb2fd43
All checks were successful
CD Pipeline / workflow-shape (push) Successful in 0s
CD Pipeline / cancel-stale-cd (push) Has been skipped
CD Pipeline / tests (push) Successful in 2m25s
CD Pipeline / build-and-deploy (push) Successful in 14m30s
CD Pipeline / post-deploy-checks (push) Successful in 1m57s
AWOOOI Harbor 110 Local Repair / workflow-shape (push) Successful in 1s
AWOOOI Harbor 110 Local Repair / harbor-110-local-repair (push) Successful in 12s
fix(runtime): bind lifecycle tenant context
2026-07-15 03:57:32 +08:00

83 lines
2.6 KiB
Python

import asyncio
from types import SimpleNamespace
from unittest.mock import AsyncMock
import pytest
from src.core.context import get_current_project_context
from src.jobs import incident_lifecycle_reconciler as reconciler
from src.jobs.incident_lifecycle_reconciler import (
LifecycleCandidate,
reconcile_stuck_incidents,
)
@pytest.mark.asyncio
async def test_reconcile_stuck_incidents_resolves_strong_evidence(monkeypatch):
service = SimpleNamespace(resolve_incident=AsyncMock(return_value=object()))
monkeypatch.setattr(
"src.jobs.incident_lifecycle_reconciler._fetch_candidates",
AsyncMock(
return_value=[
LifecycleCandidate(
incident_id="INC-EXEC-SUCCESS",
resolution_type="auto_repair",
reason="approval_execution_success",
),
LifecycleCandidate(
incident_id="INC-TIMEOUT",
resolution_type="timeout",
reason="approval_expired",
),
]
),
)
monkeypatch.setattr(
"src.services.incident_service.get_incident_service",
lambda: service,
)
resolved, errors = await reconcile_stuck_incidents(limit=2)
assert (resolved, errors) == (2, 0)
assert service.resolve_incident.await_args_list[0].args == (
"INC-EXEC-SUCCESS",
)
assert service.resolve_incident.await_args_list[0].kwargs == {
"resolution_type": "auto_repair",
"emit_postmortem": False,
}
assert service.resolve_incident.await_args_list[1].args == ("INC-TIMEOUT",)
assert service.resolve_incident.await_args_list[1].kwargs == {
"resolution_type": "timeout",
"emit_postmortem": False,
}
@pytest.mark.asyncio
async def test_reconciler_loop_binds_and_clears_project_context(monkeypatch):
observed_contexts: list[dict[str, str | None]] = []
async def fake_reconcile() -> tuple[int, int]:
observed_contexts.append(get_current_project_context())
return 0, 0
async def cancel_after_first_pass(_seconds: int) -> None:
raise asyncio.CancelledError
monkeypatch.setattr(reconciler, "reconcile_stuck_incidents", fake_reconcile)
monkeypatch.setattr(reconciler.asyncio, "sleep", cancel_after_first_pass)
with pytest.raises(asyncio.CancelledError):
await reconciler.run_incident_lifecycle_reconciler_loop()
assert observed_contexts == [
{
"project_id": "awoooi",
"source": "incident_lifecycle_reconciler",
"request_id": None,
}
]
assert get_current_project_context()["project_id"] is None