Files
state-hub/tests/test_consistency_sweep.py
tegwick ab14e77e77 feat(STATE-WP-0064): start parallel week with source-tagged sweep runners
Tag consistency_sweep_remote_all progress events by source, route the local
timer through the API, add a parallel-week comparison script, and document
the 2026-06-21 to 2026-06-28 observation window for T03.
2026-06-21 21:46:43 +02:00

201 lines
6.8 KiB
Python

from __future__ import annotations
import json
from datetime import UTC, datetime
from types import SimpleNamespace
from unittest.mock import AsyncMock
import pytest
from api.schemas.consistency_sweep import (
ConsistencySweepIssueSummary,
ConsistencySweepRemoteAllRun,
ConsistencySweepRepoResult,
)
from api.services import consistency_sweep as sweep_service
@pytest.mark.asyncio
async def test_remote_all_sweep_records_progress_and_parses_stderr(client, monkeypatch):
async def fake_run_remote_all_sweep(session, *, max_seconds: int, source: str = "api"):
return ConsistencySweepRemoteAllRun(
max_seconds=max_seconds,
source=source,
exit_code=0,
lock_skipped=False,
repos_processed=[
ConsistencySweepRepoResult(
repo_slug="state-hub",
repo_path="/home/worsch/state-hub",
result="pass",
summary=ConsistencySweepIssueSummary(info=1),
fixes_applied=["pull: already up to date"],
)
],
skipped_clean=["demo-service"],
skipped_missing=["remote-only"],
skipped_budget=[],
progress_event_id=None,
started_at=datetime(2026, 6, 21, 12, 0, tzinfo=UTC),
completed_at=datetime(2026, 6, 21, 12, 1, tzinfo=UTC),
)
monkeypatch.setattr(
"api.routers.consistency_sweep.run_remote_all_sweep",
AsyncMock(side_effect=fake_run_remote_all_sweep),
)
response = await client.post("/consistency/sweep/remote-all", json={"max_seconds": 300})
assert response.status_code == 201, response.text
body = response.json()
assert body["exit_code"] == 0
assert body["lock_skipped"] is False
assert body["repos_processed"][0]["repo_slug"] == "state-hub"
assert body["skipped_clean"] == ["demo-service"]
assert body["skipped_missing"] == ["remote-only"]
@pytest.mark.asyncio
async def test_remote_all_sweep_lock_skip_is_idempotent(client, monkeypatch):
async def fake_run_remote_all_sweep(session, *, max_seconds: int, source: str = "api"):
return ConsistencySweepRemoteAllRun(
max_seconds=max_seconds,
source=source,
exit_code=0,
lock_skipped=True,
repos_processed=[],
skipped_clean=[],
skipped_missing=[],
skipped_budget=[],
progress_event_id=None,
started_at=datetime(2026, 6, 21, 12, 0, tzinfo=UTC),
completed_at=datetime(2026, 6, 21, 12, 0, 1, tzinfo=UTC),
)
monkeypatch.setattr(
"api.routers.consistency_sweep.run_remote_all_sweep",
AsyncMock(side_effect=fake_run_remote_all_sweep),
)
response = await client.post("/consistency/sweep/remote-all", json={})
assert response.status_code == 201, response.text
assert response.json()["lock_skipped"] is True
assert response.json()["repos_processed"] == []
def test_parse_stderr_extracts_skip_lists():
stderr = (
" CLEAN (skipped): alpha, beta\n"
" NOT ON THIS HOST (skipped): gamma\n"
" BUDGET EXHAUSTED after 300s (skipped): delta, epsilon\n"
)
parsed = sweep_service._parse_stderr(stderr)
assert parsed == {
"skipped_clean": ["alpha", "beta"],
"skipped_missing": ["gamma"],
"skipped_budget": ["delta", "epsilon"],
}
def test_extract_json_payload_skips_human_readable_prefix_lines():
stdout = (
" CLEAN (skipped): quiet-repo\n"
" BUDGET EXHAUSTED after 30s (skipped): other-repo\n"
'{\n "repo_slug": "state-hub",\n "repo_path": "/home/worsch/state-hub",\n'
' "result": "pass",\n "summary": {"fail": 0, "warn": 0, "info": 0},\n'
' "fixes_applied": []\n}\n'
)
payload = sweep_service._extract_json_payload(stdout)
assert payload["repo_slug"] == "state-hub"
def test_parse_stdout_handles_single_and_batch_payloads():
single = json.dumps(
{
"repo_slug": "state-hub",
"repo_path": "/home/worsch/state-hub",
"result": "warn",
"summary": {"fail": 0, "warn": 1, "info": 0},
"fixes_applied": [],
}
)
batch = json.dumps(
[
{
"repo_slug": "alpha",
"repo_path": "/tmp/alpha",
"result": "pass",
"summary": {"fail": 0, "warn": 0, "info": 0},
"fixes_applied": [],
},
{
"repo_slug": "beta",
"repo_path": "/tmp/beta",
"result": "fail",
"summary": {"fail": 1, "warn": 0, "info": 0},
"fixes_applied": [],
},
]
)
single_result = sweep_service._parse_stdout(single)
batch_result = sweep_service._parse_stdout(batch)
assert len(single_result) == 1
assert single_result[0].result == "warn"
assert [repo.repo_slug for repo in batch_result] == ["alpha", "beta"]
assert batch_result[1].result == "fail"
@pytest.mark.asyncio
async def test_run_remote_all_sweep_invokes_script_and_logs_progress(client, monkeypatch):
captured: dict[str, object] = {}
def fake_run(cmd, capture_output, text):
captured["cmd"] = cmd
return SimpleNamespace(
stdout=json.dumps(
[
{
"repo_slug": "state-hub",
"repo_path": "/home/worsch/state-hub",
"result": "pass",
"summary": {"fail": 0, "warn": 0, "info": 0},
"fixes_applied": [],
}
]
),
stderr=" CLEAN (skipped): quiet-repo\n",
returncode=0,
)
async def fake_to_thread(fn, *args, **kwargs):
return fn(*args, **kwargs)
monkeypatch.setattr(sweep_service.asyncio, "to_thread", fake_to_thread)
monkeypatch.setattr(sweep_service.subprocess, "run", fake_run)
response = await client.post(
"/consistency/sweep/remote-all",
json={"max_seconds": 120, "source": "local-timer"},
)
assert response.status_code == 201, response.text
body = response.json()
assert body["source"] == "local-timer"
assert "--remote" in captured["cmd"]
assert "--all" in captured["cmd"]
assert captured["cmd"][captured["cmd"].index("--max-seconds") + 1] == "120"
assert body["exit_code"] == 0
assert body["skipped_clean"] == ["quiet-repo"]
assert body["progress_event_id"] is not None
events = await client.get(
"/progress/",
params={"event_type": "consistency_sweep_remote_all"},
)
assert events.status_code == 200, events.text
assert events.json()[0]["detail"]["skipped_clean"] == ["quiet-repo"]
assert events.json()[0]["detail"]["source"] == "local-timer"