"""Receipt guards and full-store fingerprints use synthetic records only.""" import copy from datetime import UTC, datetime, timedelta import pytest from coulomb_social.apps.spaces.date_backfill_operation import ( _content_lock, _set_date, execute_date_operation, source_fingerprint, validate_backup, ) from coulomb_social.apps.spaces.models import Space IMAGE = "forgejo.coulomb.social/coulomb/coulomb-social@sha256:" + "a" * 64 RECIPIENT = "age1synthetic-public-recipient" NOW = datetime(2026, 10, 30, 12, 5, tzinfo=UTC) def backup(): key = "platform-pg/application-archives/coulomb-social/synthetic.tar.age" return { "schema": "platform.application-primary-archive.v1", "application": "coulomb-social", "status": "offsite_fetched_verified", "captured_at": "2026-10-30T12:00:00Z", "completed_at": "2026-10-30T12:01:00Z", "image": IMAGE, "age_recipient": RECIPIENT, "immutable_create": True, "remote_retrieval_verified": True, "version_id": "synthetic-v1", "object_key": key, "destination": "s3://railiance-platform-pg-backup/" + key, "encrypted_sha256": "b" * 64, "ciphertext_sha256": "b" * 64, "database_sha256": "c" * 64, "content_sha256": "d" * 64, "encrypted_bytes": 1234, "ciphertext_bytes": 1234, } @pytest.mark.parametrize("change", [ {"status": "uploaded"}, {"remote_retrieval_verified": False}, {"immutable_create": False}, {"version_id": "null"}, {"image": IMAGE + "0"}, {"age_recipient": "age1other"}, {"object_key": "platform-pg/application-archives/another/synthetic.tar.age"}, {"destination": "s3://another/synthetic"}, {"ciphertext_sha256": "e" * 64}, {"encrypted_bytes": True}, {"ciphertext_bytes": True}, {"database_sha256": "missing"}, {"captured_at": "2026-10-30T11:00:00Z"}, {"completed_at": "2026-10-31T12:00:00Z"}, {"captured_at": "2026-10-30T12:02:00Z"}, ]) def test_stale_unverified_or_changed_backup_metadata_refuses(change): receipt = {**backup(), **change} with pytest.raises(ValueError): validate_backup(receipt, IMAGE, RECIPIENT, now=NOW) def test_backup_binding_is_stable_and_recovery_retains_original_even_when_old(): receipt = backup() original = copy.deepcopy(receipt) digest = validate_backup(receipt, IMAGE, RECIPIENT, now=NOW) reordered = dict(reversed(list(receipt.items()))) assert validate_backup(reordered, IMAGE, RECIPIENT, now=NOW) == digest assert receipt == original with pytest.raises(ValueError): validate_backup(receipt, IMAGE, RECIPIENT, now=NOW + timedelta(days=1)) assert validate_backup(receipt, IMAGE, RECIPIENT, now=NOW + timedelta(days=1), require_fresh=False) == digest def test_legacy_slug_collision_refuses_even_when_selected_sql_identity_is_exact(): selected = {"space_id": "selected", "tenant_id": "tenant:a", "space_slug": "same"} snapshot = {"rows": {"spaces_space": [ {"id": "selected", "tenant_id": "tenant:a", "slug": "same", "created_at": NOW}, {"id": "another", "tenant_id": "tenant:b", "slug": "same", "created_at": NOW}, ]}} before = copy.deepcopy(snapshot) with pytest.raises(ValueError, match="unambiguous"): _set_date(snapshot, {"selection": selected}, "2020-01-01T00:00:00Z") assert snapshot == before @pytest.mark.django_db def test_fingerprints_cover_other_tables_and_all_content_not_only_selected_pages(tmp_path): Space.objects.create(tenant_id="tenant:synthetic", slug="synthetic") baseline = source_fingerprint(tmp_path) assert baseline["tables"] >= 4 (tmp_path / "arbitrary-unrelated-asset.bin").write_bytes(b"unrelated") after_file = source_fingerprint(tmp_path) assert after_file["tables_sha256"] == baseline["tables_sha256"] assert after_file["files_sha256"] != baseline["files_sha256"] Space.objects.update(title="another update") after_sql = source_fingerprint(tmp_path) assert after_sql["tables_sha256"] != baseline["tables_sha256"] assert after_sql["files_sha256"] == after_file["files_sha256"] @pytest.mark.django_db def test_sqlite_is_observation_only_never_an_execution_backend(tmp_path): with pytest.raises(ValueError, match="PostgreSQL execution required"): execute_date_operation(tmp_path, {}, tmp_path / "journal.json", {}, {}, image=IMAGE, recipient=RECIPIENT) assert not (tmp_path / "journal.json").exists() def test_writer_lock_refuses_another_holder_and_never_creates_missing_lock(tmp_path): with pytest.raises(FileNotFoundError), _content_lock(tmp_path): pass assert not list(tmp_path.iterdir()) (tmp_path / ".writer.lock").touch() with _content_lock(tmp_path), pytest.raises(BlockingIOError), _content_lock(tmp_path): pass