security: harden Campaign delivery effects and reconciliation
This commit is contained in:
@@ -2,6 +2,7 @@ from __future__ import annotations
|
||||
|
||||
import unittest
|
||||
from types import SimpleNamespace
|
||||
from unittest.mock import patch
|
||||
|
||||
from govoplan_campaign.backend.db.models import (
|
||||
JobBuildStatus,
|
||||
@@ -10,8 +11,10 @@ from govoplan_campaign.backend.db.models import (
|
||||
JobValidationStatus,
|
||||
)
|
||||
from govoplan_campaign.backend.sending.jobs import (
|
||||
SendJobResult,
|
||||
_queue_validation_statuses,
|
||||
_select_campaign_jobs_for_queue,
|
||||
_send_claimed_campaign_job,
|
||||
)
|
||||
|
||||
|
||||
@@ -115,6 +118,78 @@ class CampaignQueueSelectionTests(unittest.TestCase):
|
||||
self.assertEqual(warning.send_status, JobSendStatus.NOT_QUEUED.value)
|
||||
self.assertEqual(session.added, [])
|
||||
|
||||
def test_post_smtp_persistence_failure_is_outcome_unknown_not_retryable_failure(self):
|
||||
job = SimpleNamespace(
|
||||
id="job-1",
|
||||
tenant_id="tenant-1",
|
||||
campaign_id="campaign-1",
|
||||
campaign_version_id="version-1",
|
||||
imap_status="not_requested",
|
||||
resolved_recipients={"from": {"email": "sender@example.test"}},
|
||||
)
|
||||
snapshot = SimpleNamespace(
|
||||
mail_profile_id="profile-1",
|
||||
smtp_transport_revision="frozen",
|
||||
delivery=SimpleNamespace(
|
||||
rate_limit=SimpleNamespace(messages_per_minute=60),
|
||||
),
|
||||
)
|
||||
context = SimpleNamespace(
|
||||
snapshot=snapshot,
|
||||
message_bytes=b"message",
|
||||
envelope_from="sender@example.test",
|
||||
envelope_recipients=["recipient@example.test"],
|
||||
)
|
||||
current = SimpleNamespace(id="job-1")
|
||||
|
||||
class Session:
|
||||
def __init__(self) -> None:
|
||||
self.rolled_back = False
|
||||
|
||||
def rollback(self) -> None:
|
||||
self.rolled_back = True
|
||||
|
||||
def get(self, _model, _id):
|
||||
return current
|
||||
|
||||
class Mail:
|
||||
def wait_for_rate_limit(self, **_kwargs):
|
||||
return None
|
||||
|
||||
def send_campaign_email_bytes(self, *_args, **_kwargs):
|
||||
return SimpleNamespace(accepted_count=1)
|
||||
|
||||
session = Session()
|
||||
expected = SendJobResult(
|
||||
job_id="job-1",
|
||||
status=JobSendStatus.OUTCOME_UNKNOWN.value,
|
||||
attempt_number=1,
|
||||
)
|
||||
with (
|
||||
patch("govoplan_campaign.backend.sending.jobs.mail_integration", return_value=Mail()),
|
||||
patch("govoplan_campaign.backend.sending.jobs._record_attempt_start", return_value=object()),
|
||||
patch(
|
||||
"govoplan_campaign.backend.sending.jobs._record_smtp_send_success",
|
||||
side_effect=OSError("storage unavailable"),
|
||||
),
|
||||
patch(
|
||||
"govoplan_campaign.backend.sending.jobs.mark_job_outcome_unknown",
|
||||
return_value=expected,
|
||||
) as mark_unknown,
|
||||
):
|
||||
result = _send_claimed_campaign_job(
|
||||
session, # type: ignore[arg-type]
|
||||
job=job, # type: ignore[arg-type]
|
||||
claim_token="claim-1",
|
||||
context=context, # type: ignore[arg-type]
|
||||
use_rate_limit=False,
|
||||
enqueue_imap_task=False,
|
||||
)
|
||||
|
||||
self.assertIs(result, expected)
|
||||
self.assertTrue(session.rolled_back)
|
||||
self.assertIn("Automatic retry is stopped", mark_unknown.call_args.kwargs["reason"])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user