fix(release): сбой переименования agent.py не удаляет прежний agent.py (T2-I1) (#32)
Откат удаляет agent.py только при первой публикации; при сбое до переименования прежний файл не трогается. Тест: os.replace падает на agent.py.prev — release/ байт-в-байт прежний. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
f977d9bd34
commit
57934383e7
@ -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)
|
||||
|
||||
@ -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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user