Files
govoplan-campaign/tests/test_shared_artifact_storage.py
T

130 lines
4.0 KiB
Python

from __future__ import annotations
import hashlib
from datetime import datetime, timedelta, timezone
from pathlib import Path
from types import SimpleNamespace
from unittest.mock import MagicMock, patch
import pytest
from govoplan_core.core.object_storage import (
LocalFilesystemStorageBackend,
StorageBackendError,
)
from govoplan_campaign.backend.persistence.campaigns import (
CampaignPersistenceError,
_persist_built_eml_artifacts,
)
from govoplan_campaign.backend.retention import _apply_eml_retention
from govoplan_campaign.backend.sending.jobs import _load_eml_bytes_for_job
class _FailingStorage:
name = "test"
def __init__(self) -> None:
self.objects: dict[str, bytes] = {}
self.deleted: list[str] = []
def put_bytes(self, key: str, data: bytes, **_kwargs) -> None:
if self.objects:
raise StorageBackendError("object service unavailable")
self.objects[key] = data
def delete(self, key: str) -> None:
self.deleted.append(key)
self.objects.pop(key, None)
def test_partial_campaign_artifact_write_is_compensated(tmp_path: Path) -> None:
first = tmp_path / "first.eml"
second = tmp_path / "second.eml"
first.write_bytes(b"Message-ID: <first@example.test>\r\n\r\nfirst")
second.write_bytes(b"Message-ID: <second@example.test>\r\n\r\nsecond")
messages = [
SimpleNamespace(
draft=SimpleNamespace(
entry_index=1,
eml_path=str(first),
eml_size_bytes=None,
)
),
SimpleNamespace(
draft=SimpleNamespace(
entry_index=2,
eml_path=str(second),
eml_size_bytes=None,
)
),
]
storage = _FailingStorage()
with pytest.raises(CampaignPersistenceError, match="could not be persisted"):
_persist_built_eml_artifacts(
storage=storage, # type: ignore[arg-type]
tenant_id="tenant-1",
campaign_id="campaign-1",
version_id="version-1",
build_id="build-1",
built_messages=messages,
)
assert storage.objects == {}
assert len(storage.deleted) == 1
def test_worker_loads_and_verifies_campaign_eml_from_object_storage(
tmp_path: Path,
) -> None:
payload = b"Message-ID: <message@example.test>\r\n\r\nbody"
storage = LocalFilesystemStorageBackend(tmp_path)
storage.put_bytes("campaign-artifacts/message.eml", payload)
job = SimpleNamespace(
id="job-1",
eml_storage_key="campaign-artifacts/message.eml",
eml_local_path=None,
eml_size_bytes=len(payload),
eml_sha256=hashlib.sha256(payload).hexdigest(),
message_id_header="<message@example.test>",
)
with patch(
"govoplan_campaign.backend.sending.jobs.configured_storage_backend",
return_value=storage,
):
assert _load_eml_bytes_for_job(job) == payload
def test_retention_preserves_reference_when_object_store_is_unavailable() -> None:
job = SimpleNamespace(
campaign_id="campaign-1",
updated_at=datetime.now(timezone.utc) - timedelta(days=10),
queue_status="draft",
send_status="smtp_accepted",
imap_status="appended",
eml_local_path=None,
eml_storage_key="campaign-artifacts/message.eml",
)
session = MagicMock()
session.query.return_value.filter.return_value.order_by.return_value.all.return_value = [
job
]
storage = MagicMock()
storage.exists.side_effect = StorageBackendError("object service unavailable")
result = _apply_eml_retention(
session,
dry_run=False,
now=datetime.now(timezone.utc),
policy_for_campaign_id=lambda _campaign_id: SimpleNamespace(
generated_eml_retention_days=1
),
storage=storage,
)
assert result["delete_failed"] == 1
assert result["metadata_cleared"] == 0
assert job.eml_storage_key == "campaign-artifacts/message.eml"
session.add.assert_not_called()