119 lines
4.4 KiB
Python
119 lines
4.4 KiB
Python
from datetime import datetime, timedelta, timezone
|
|
|
|
import pytest
|
|
|
|
from api.config import settings
|
|
from api.services import ops_run_projection as projection
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def reset_projection_cache() -> None:
|
|
projection.reset_ops_run_projection_cache()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_ops_run_projection_counts_and_contract(monkeypatch) -> None:
|
|
now = datetime.now(timezone.utc)
|
|
payload = {
|
|
"counts": {"open": 2, "claimed": 1, "succeeded": 9, "failed": 2},
|
|
"items": [
|
|
{
|
|
"id": "run-open",
|
|
"activity_definition_id": "definition-1",
|
|
"target_repo": "state-hub",
|
|
"state": "open",
|
|
"created_at": (now - timedelta(hours=2)).isoformat(),
|
|
"updated_at": (now - timedelta(hours=2)).isoformat(),
|
|
"result": {},
|
|
},
|
|
{
|
|
"id": "run-claimed",
|
|
"activity_definition_id": "definition-2",
|
|
"target_repo": "activity-core",
|
|
"state": "claimed",
|
|
"claim_owner": "rein-aharness@railiance01",
|
|
"lease_until": (now + timedelta(minutes=10)).isoformat(),
|
|
"attempt": 1,
|
|
"created_at": (now - timedelta(minutes=10)).isoformat(),
|
|
"updated_at": now.isoformat(),
|
|
"result": {},
|
|
},
|
|
{
|
|
"id": "run-failed",
|
|
"activity_definition_id": "definition-3",
|
|
"target_repo": "binky-control",
|
|
"state": "failed",
|
|
"created_at": (now - timedelta(hours=3)).isoformat(),
|
|
"updated_at": (now - timedelta(hours=1)).isoformat(),
|
|
"result": {"error": "executor timeout"},
|
|
},
|
|
],
|
|
}
|
|
|
|
async def fake_fetch():
|
|
return payload
|
|
|
|
monkeypatch.setattr(projection, "_fetch_ops_runs", fake_fetch)
|
|
result = await projection.get_ops_run_projection(refresh=True)
|
|
|
|
assert result.available is True
|
|
assert result.open == 2
|
|
assert result.claimed == 1
|
|
assert result.failed_24h == 1
|
|
assert result.stuck_open_or_claimed == 1
|
|
assert {item.id for item in result.items} == {"run-open", "run-claimed", "run-failed"}
|
|
failed = next(item for item in result.items if item.id == "run-failed")
|
|
assert failed.last_error == "executor timeout"
|
|
claimed = next(item for item in result.items if item.id == "run-claimed")
|
|
assert claimed.lease["owner"] == "rein-aharness@railiance01"
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_ops_run_projection_serves_stale_cache_on_refresh_failure(monkeypatch) -> None:
|
|
async def initial_fetch():
|
|
return {"counts": {"open": 1}, "items": []}
|
|
|
|
monkeypatch.setattr(projection, "_fetch_ops_runs", initial_fetch)
|
|
initial = await projection.get_ops_run_projection(refresh=True)
|
|
assert initial.available is True
|
|
|
|
async def failed_fetch():
|
|
raise RuntimeError("upstream down")
|
|
|
|
monkeypatch.setattr(projection, "_fetch_ops_runs", failed_fetch)
|
|
stale = await projection.get_ops_run_projection(refresh=True)
|
|
assert stale.available is True
|
|
assert stale.stale is True
|
|
assert "upstream down" in stale.error
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_ops_run_projection_reports_unconfigured(monkeypatch) -> None:
|
|
monkeypatch.setattr(settings, "activity_core_url", None)
|
|
result = await projection.get_ops_run_projection(refresh=True)
|
|
assert result.available is False
|
|
assert "ACTIVITY_CORE_URL is not configured" in result.error
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_ops_runs_summary_route(client, monkeypatch) -> None:
|
|
async def fake_fetch():
|
|
return {"counts": {"open": 3, "claimed": 2}, "items": []}
|
|
|
|
monkeypatch.setattr(projection, "_fetch_ops_runs", fake_fetch)
|
|
response = await client.get("/ops-runs/summary", params={"refresh": "true"})
|
|
assert response.status_code == 200
|
|
assert response.json()["open"] == 3
|
|
assert response.json()["claimed"] == 2
|
|
assert (await client.post("/ops-runs/claim", json={})).status_code == 404
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_state_summary_contains_ops_run_projection(client, monkeypatch) -> None:
|
|
async def fake_fetch():
|
|
return {"counts": {"open": 4}, "items": []}
|
|
|
|
monkeypatch.setattr(projection, "_fetch_ops_runs", fake_fetch)
|
|
response = await client.get("/state/summary", params={"refresh": "true"})
|
|
assert response.status_code == 200
|
|
assert response.json()["ops_runs"]["open"] == 4
|