import os from datetime import datetime from pathlib import Path from zoneinfo import ZoneInfo import pytest from beaver_agent.memory import handouts from beaver_agent.memory.handouts import deploy_mark ZONE = ZoneInfo("Europe/Warsaw") NOW = "2026-09-17 06:30" def _at(text: str) -> float: return datetime.fromisoformat(text).replace(tzinfo=ZONE).timestamp() def _touch(path: Path, when: str) -> Path: path.parent.mkdir(parents=True, exist_ok=True) path.write_text("", encoding="utf-8") os.utime(path, (_at(when), _at(when))) return path def _bundle(tmp_path: Path, started: str, code: str) -> tuple[Path, Path]: proc = _touch(tmp_path / "proc" / "1", started) package = tmp_path / "beaver_agent" _touch(package / "memory" / "handouts.py", code) _touch(package / "vault.py", "2026-09-10 12:00") # мусор компилятора не должен выглядеть свежим кодом _touch(package / "memory" / "__pycache__" / "handouts.py", "2026-09-17 06:00") return proc, package @pytest.fixture(autouse=True) def _now(monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.setattr(handouts.time, "time", lambda: _at(NOW)) def test_code_older_than_start(tmp_path: Path) -> None: proc, package = _bundle(tmp_path, "2026-09-16 03:11", "2026-09-16 03:03") assert deploy_mark(proc, package) == ( "Выкат: процесс поднят 2026-09-16 03:11 (27 ч назад), свежесть кода " "2026-09-16 03:03 - код старше старта, значит крутится то, что выкатывали." ) def test_code_newer_than_start(tmp_path: Path) -> None: proc, package = _bundle(tmp_path, "2026-09-16 03:11", "2026-09-17 06:20") assert deploy_mark(proc, package) == ( "Выкат: процесс поднят 2026-09-16 03:11 (27 ч назад), свежесть кода " "2026-09-17 06:20 - код НОВЕЕ старта: правка на диске есть, " "рестарта после неё не было." ) def test_same_minute(tmp_path: Path) -> None: proc, package = _bundle(tmp_path, "2026-09-17 06:00", "2026-09-17 06:00:30") assert deploy_mark(proc, package) == ( "Выкат: процесс поднят 2026-09-17 06:00 (30 мин назад), свежесть кода " "2026-09-17 06:00 - выкат и правка в одну минуту." ) def test_no_source(tmp_path: Path) -> None: proc, package = _bundle(tmp_path, "2026-09-16 03:11", "2026-09-16 03:03") assert deploy_mark(tmp_path / "нет", package) is None assert deploy_mark(proc, tmp_path / "пусто") is None only_cache = tmp_path / "кеш" _touch(only_cache / "__pycache__" / "handouts.py", "2026-09-17 06:00") assert deploy_mark(proc, only_cache) is None