from __future__ import annotations import json import subprocess from pathlib import Path from types import SimpleNamespace import pytest import pyro_mcp.daily_loop_smoke as smoke_module class _FakePyro: def __init__(self) -> None: self.workspace_id = "ws-1" self.message = "broken\n" self.deleted = False def create_workspace( self, *, environment: str, seed_path: Path, name: str | None = None, labels: dict[str, str] | None = None, ) -> dict[str, object]: assert environment == "debian:12" assert seed_path.is_dir() assert name == "daily-loop" assert labels == {"suite": "daily-loop-smoke"} return {"workspace_id": self.workspace_id} def exec_workspace(self, workspace_id: str, *, command: str) -> dict[str, object]: assert workspace_id == self.workspace_id if command != "sh check.sh": raise AssertionError(f"unexpected command: {command}") if self.message == "fixed\n": return {"exit_code": 0, "stdout": "fixed\n"} return {"exit_code": 1, "stderr": "expected fixed got broken\n"} def apply_workspace_patch(self, workspace_id: str, *, patch: str) -> dict[str, object]: assert workspace_id == self.workspace_id assert "+fixed" in patch self.message = "fixed\n" return {"changed": True} def export_workspace( self, workspace_id: str, path: str, *, output_path: Path, ) -> dict[str, object]: assert workspace_id == self.workspace_id assert path == "message.txt" output_path.parent.mkdir(parents=True, exist_ok=True) output_path.write_text(self.message, encoding="utf-8") return {"artifact_type": "file"} def reset_workspace(self, workspace_id: str) -> dict[str, object]: assert workspace_id == self.workspace_id self.message = "broken\n" return {"reset_count": 1} def read_workspace_file(self, workspace_id: str, path: str) -> dict[str, object]: assert workspace_id == self.workspace_id assert path == "message.txt" return {"content": self.message} def delete_workspace(self, workspace_id: str) -> dict[str, object]: assert workspace_id == self.workspace_id self.deleted = True return {"workspace_id": workspace_id, "deleted": True} def test_run_prepare_parses_json(monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.setattr( subprocess, "run", lambda *args, **kwargs: SimpleNamespace( returncode=0, stdout=json.dumps({"prepared": True}), stderr="", ), ) payload = smoke_module._run_prepare("debian:12") assert payload == {"prepared": True} def test_run_prepare_raises_on_failure(monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.setattr( subprocess, "run", lambda *args, **kwargs: SimpleNamespace( returncode=1, stdout="", stderr="prepare failed", ), ) with pytest.raises(RuntimeError, match="prepare failed"): smoke_module._run_prepare("debian:12") def test_run_daily_loop_smoke_happy_path(monkeypatch: pytest.MonkeyPatch) -> None: prepare_calls: list[str] = [] fake_pyro = _FakePyro() def fake_run_prepare(environment: str) -> dict[str, object]: prepare_calls.append(environment) return {"prepared": True, "reused": len(prepare_calls) > 1} monkeypatch.setattr(smoke_module, "_run_prepare", fake_run_prepare) monkeypatch.setattr(smoke_module, "Pyro", lambda: fake_pyro) smoke_module.run_daily_loop_smoke(environment="debian:12") assert prepare_calls == ["debian:12", "debian:12"] assert fake_pyro.deleted is True def test_main_runs_selected_environment( monkeypatch: pytest.MonkeyPatch, ) -> None: observed: list[str] = [] monkeypatch.setattr( smoke_module, "run_daily_loop_smoke", lambda *, environment: observed.append(environment), ) monkeypatch.setattr( smoke_module, "build_arg_parser", lambda: SimpleNamespace( parse_args=lambda: SimpleNamespace(environment="debian:12-build") ), ) smoke_module.main() assert observed == ["debian:12-build"]