Merge pull request #1215 from arnoldwender/fix/entity-registry-atomic-write
fix(entity_registry): atomic write to prevent partial corruption on crash
This commit is contained in:
@@ -2,6 +2,8 @@
|
||||
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
from mempalace.entity_registry import (
|
||||
COMMON_ENGLISH_WORDS,
|
||||
PERSON_CONTEXT_PATTERNS,
|
||||
@@ -71,6 +73,50 @@ def test_save_creates_file(tmp_path):
|
||||
assert (tmp_path / "entity_registry.json").exists()
|
||||
|
||||
|
||||
def test_save_is_atomic_does_not_leave_tmp(tmp_path):
|
||||
# Atomic write must not leave the .tmp sidecar file after a successful save.
|
||||
registry = EntityRegistry.load(config_dir=tmp_path)
|
||||
registry.save()
|
||||
leftover = list(tmp_path.glob("entity_registry.json.tmp*"))
|
||||
assert leftover == [], f"atomic write leaked tmp file(s): {leftover}"
|
||||
|
||||
|
||||
def test_save_preserves_previous_on_serialization_failure(tmp_path, monkeypatch):
|
||||
# If serialization fails mid-write, the previous registry must remain
|
||||
# intact — this is the whole point of atomic write vs truncating in place.
|
||||
registry = EntityRegistry.load(config_dir=tmp_path)
|
||||
registry.seed(
|
||||
mode="personal",
|
||||
people=[{"name": "Alice", "relationship": "friend", "context": "personal"}],
|
||||
projects=[],
|
||||
)
|
||||
registry.save()
|
||||
target = tmp_path / "entity_registry.json"
|
||||
original = target.read_text(encoding="utf-8")
|
||||
|
||||
# Force os.replace to raise — simulates filesystem full / permission flip
|
||||
# AFTER the temp file is written but BEFORE the rename completes.
|
||||
import os as _os
|
||||
|
||||
real_replace = _os.replace
|
||||
|
||||
def boom(src, dst):
|
||||
raise OSError("simulated rename failure")
|
||||
|
||||
monkeypatch.setattr(_os, "replace", boom)
|
||||
with pytest.raises(OSError):
|
||||
registry.seed(
|
||||
mode="personal",
|
||||
people=[{"name": "Bob", "relationship": "friend", "context": "personal"}],
|
||||
projects=[],
|
||||
)
|
||||
registry.save()
|
||||
|
||||
# Restore os.replace before reading so the assertion can rely on it.
|
||||
monkeypatch.setattr(_os, "replace", real_replace)
|
||||
assert target.read_text(encoding="utf-8") == original
|
||||
|
||||
|
||||
# ── seed ────────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user