diff --git a/src/jira_hub/release.py b/src/jira_hub/release.py index 1f1c7d6..14101a1 100644 --- a/src/jira_hub/release.py +++ b/src/jira_hub/release.py @@ -74,7 +74,7 @@ def publish(release_dir, source) -> dict: manifest = {"version": version, "sha256": hashlib.sha256(data).hexdigest(), "size": len(data), "published_at": datetime.now(timezone.utc).replace(microsecond=0).isoformat()} agent, prev_file = rd / "agent.py", rd / "agent.py.prev" - moved = False + existed, moved = agent.exists(), False try: secure_dir(rd) if agent.exists(): @@ -83,7 +83,7 @@ def publish(release_dir, source) -> dict: _write_atomic(agent, data) _write_atomic(rd / "manifest.json", json.dumps(manifest, ensure_ascii=False).encode("utf-8")) except OSError as e: - _undo(agent, prev_file, moved) + _undo(agent, prev_file, moved, existed) raise PublishError(f"не удалось записать публикацию: {e}") from e try: prev_file.unlink(missing_ok=True) # и прежний, и оставленный упавшей публикацией (риск в design) @@ -92,13 +92,14 @@ def publish(release_dir, source) -> dict: return manifest -def _undo(agent: Path, prev_file: Path, moved: bool) -> None: +def _undo(agent: Path, prev_file: Path, moved: bool, existed: bool) -> None: """Вернуть release/ к прежней публикации после сбоя записи: agent.py.prev на место agent.py; если прежнего не было — - убрать записанный agent.py. Сбой возврата — в лог: команда всё равно завершается сообщением.""" + убрать записанный agent.py. Прежний agent.py, которого не сдвинули (сбой до переименования), не трогаем. + Сбой возврата — в лог: команда всё равно завершается сообщением.""" try: if moved: os.replace(prev_file, agent) - else: + elif not existed: agent.unlink(missing_ok=True) except OSError as e: log.error("release: прежняя публикация не восстановлена (%s): %s", prev_file if moved else agent, e) diff --git a/tests/test_release.py b/tests/test_release.py index 1d777db..597089f 100644 --- a/tests/test_release.py +++ b/tests/test_release.py @@ -247,3 +247,23 @@ def test_load_non_utf8_with_matching_sha_is_none(tmp_path, caplog): assert release.current(rd)["version"] == 6 assert release.load(rd) is None assert "agent.py не в UTF-8" in caplog.text + + +def test_move_failure_keeps_previous_agent(tmp_path, monkeypatch): + """T2-I1: сбой переименования agent.py -> agent.py.prev при существующем agent.py — прежний agent.py не трогаем.""" + rd = tmp_path / "release" + release.publish(rd, agent_file(tmp_path, 8)) + before = _listing(rd) + real = release.os.replace + + def fake(src, dst, *a, **kw): + if Path(dst).name == "agent.py.prev": + raise OSError(13, "Permission denied") + return real(src, dst, *a, **kw) + + monkeypatch.setattr(release.os, "replace", fake) + with pytest.raises(release.PublishError, match=r"^не удалось записать публикацию: "): + release.publish(rd, agent_file(tmp_path, 9)) + monkeypatch.undo() + assert _listing(rd) == before + assert release.load(rd)["version"] == 8