railiance-platform/tests/test_application_archive_retention_plan.py

91 lines
3.4 KiB
Python
Raw Normal View History

"""Retention proposals preserve known recovered points and never own expiration."""
import importlib.util
import sys
from datetime import UTC, datetime, timedelta
from pathlib import Path
import pytest
@pytest.fixture
def planner(monkeypatch):
scripts = Path(__file__).resolve().parents[1] / "scripts"
monkeypatch.syspath_prepend(str(scripts))
spec = importlib.util.spec_from_file_location(
"application_archive_retention_plan", scripts / "application_archive_retention_plan.py"
)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
sys.modules.pop("primary_application_archive", None)
return module
def inventory(planner):
now = datetime(2027, 1, 12, 3, 15, tzinfo=UTC)
result = []
for day in range(70):
stamp = now - timedelta(days=day)
result.append(
{
"application": "coulomb-social",
"status": "offsite_fetched_verified",
"remote_retrieval_verified": True,
"captured_at": stamp.isoformat(),
"object_key": planner.PROFILES["coulomb-social"]
+ stamp.strftime("%Y/%m/%d/%Y%m%dT%H%M%SZ-")
+ f"{day:024x}"
+ ".tar.age",
"version_id": "version-" + str(day),
"ciphertext_sha256": "a" * 64,
"ciphertext_bytes": 5000,
}
)
recovery = dict(
result[-1], status="application_restored_verified", application_restore_verified=True
)
return now, result, recovery
def test_protects_old_recovered_point_and_deduplicates_daily_weekly(planner):
now, records, recovery = inventory(planner)
records.append(dict(records[0], application="email-connect", object_key="foreign"))
records.append(dict(records[0], status="failed", object_key="failed"))
result = planner.plan("coulomb-social", records, recovery, now)
keys = [item["object_key"] for item in result["keep"]]
assert len(keys) == len(set(keys)) <= 19
assert records[0]["object_key"] in keys and recovery["object_key"] in keys
assert all(item["object_key"] in keys for item in records[:14])
assert result["ignored"] == ["foreign", "failed"]
assert result["expiration_candidates"]
assert not result["provider_mutations"] and not result["local_mutations"]
assert result["protected"]["version_id"] == recovery["version_id"]
@pytest.mark.parametrize("change", ["missing", "version", "hash", "unproved"])
def test_refuses_missing_changed_or_unproved_application_recovery(planner, change):
now, records, recovery = inventory(planner)
if change == "missing":
records.pop()
elif change == "version":
recovery["version_id"] = "different-version"
elif change == "hash":
recovery["ciphertext_sha256"] = "b" * 64
else:
recovery["application_restore_verified"] = False
with pytest.raises(ValueError):
planner.plan("coulomb-social", records, recovery, now)
@pytest.mark.parametrize("change", ["duplicate", "future", "unversioned"])
def test_refuses_ambiguous_inventory(planner, change):
now, records, recovery = inventory(planner)
if change == "duplicate":
records.append(dict(records[0]))
elif change == "future":
records[0]["captured_at"] = (now + timedelta(days=1)).isoformat()
else:
records[0]["version_id"] = "null"
with pytest.raises(ValueError):
planner.plan("coulomb-social", records, recovery, now)