Refactor campaign recipient and review presentation

This commit is contained in:
2026-07-31 02:48:56 +02:00
parent 5f7503598c
commit e689fdf495
30 changed files with 2175 additions and 1977 deletions
+140
View File
@@ -0,0 +1,140 @@
from __future__ import annotations
from datetime import datetime, timedelta, timezone
from types import SimpleNamespace
from unittest.mock import MagicMock
from govoplan_campaign.backend.retention import (
_apply_eml_retention,
_apply_raw_json_retention,
_apply_report_detail_retention,
)
def _querying(items: list[object]) -> MagicMock:
session = MagicMock()
session.query.return_value.order_by.return_value.all.return_value = items
session.query.return_value.filter.return_value.order_by.return_value.all.return_value = items
return session
def test_raw_json_retention_preserves_hash_for_final_version() -> None:
now = datetime.now(timezone.utc)
version = SimpleNamespace(
campaign_id="campaign-1",
updated_at=now - timedelta(days=10),
workflow_state="completed",
raw_json={"version": "1.0", "campaign": {"name": "Sensitive"}},
schema_version="1.0",
execution_snapshot={"campaign_json_sha256": "sealed-hash"},
)
session = _querying([version])
policy = SimpleNamespace(
store_raw_campaign_json=True,
raw_campaign_json_retention_days=1,
)
result = _apply_raw_json_retention(
session,
dry_run=False,
now=now,
policy_for_campaign_id=lambda _campaign_id: policy,
)
assert result["redacted"] == 1
assert version.raw_json == {
"version": "1.0",
"_retention": {
"raw_json_redacted": True,
"redacted_at": now.isoformat(),
"original_sha256": "sealed-hash",
},
}
session.add.assert_called_once_with(version)
def test_raw_json_retention_does_not_redact_unfinished_version() -> None:
now = datetime.now(timezone.utc)
original = {"version": "1.0", "campaign": {"name": "Editable"}}
version = SimpleNamespace(
campaign_id="campaign-1",
updated_at=now - timedelta(days=10),
workflow_state="editing",
raw_json=original,
schema_version="1.0",
execution_snapshot=None,
)
session = _querying([version])
policy = SimpleNamespace(
store_raw_campaign_json=True,
raw_campaign_json_retention_days=1,
)
result = _apply_raw_json_retention(
session,
dry_run=False,
now=now,
policy_for_campaign_id=lambda _campaign_id: policy,
)
assert result["skipped_not_final"] == 1
assert version.raw_json is original
session.add.assert_not_called()
def test_report_detail_retention_keeps_aggregate_evidence() -> None:
now = datetime.now(timezone.utc)
version = SimpleNamespace(
campaign_id="campaign-1",
updated_at=now - timedelta(days=10),
validation_summary={"ready": 4, "issues": [{"entry_id": "secret"}]},
build_summary={"built": 3, "jobs": [{"recipient": "person@example.test"}]},
)
session = _querying([version])
policy = SimpleNamespace(stored_report_detail_retention_days=1)
result = _apply_report_detail_retention(
session,
dry_run=False,
now=now,
policy_for_campaign_id=lambda _campaign_id: policy,
)
assert result["summaries_redacted"] == 2
assert version.validation_summary["ready"] == 4
assert "issues" not in version.validation_summary
assert version.build_summary["built"] == 3
assert "jobs" not in version.build_summary
assert version.validation_summary["_retention"]["report_detail_redacted"] is True
session.add.assert_called_once_with(version)
def test_eml_retention_removes_only_terminal_artifact(tmp_path) -> None:
now = datetime.now(timezone.utc)
eml_path = tmp_path / "message.eml"
eml_path.write_bytes(b"message")
job = SimpleNamespace(
campaign_id="campaign-1",
updated_at=now - timedelta(days=10),
queue_status="draft",
send_status="smtp_accepted",
imap_status="appended",
eml_local_path=str(eml_path),
eml_storage_key="campaigns/message.eml",
)
session = _querying([job])
policy = SimpleNamespace(generated_eml_retention_days=1)
result = _apply_eml_retention(
session,
dry_run=False,
now=now,
policy_for_campaign_id=lambda _campaign_id: policy,
)
assert result["files_deleted"] == 1
assert result["metadata_cleared"] == 1
assert not eml_path.exists()
assert job.eml_local_path is None
assert job.eml_storage_key is None
session.add.assert_called_once_with(job)