fix(campaign): mark excluded delivery as skipped
This commit is contained in:
@@ -123,6 +123,9 @@ class CampaignAttachmentBuildTests(unittest.TestCase):
|
||||
message = result.report.messages[0]
|
||||
self.assertEqual(message.build_status.value, build_status)
|
||||
self.assertEqual(message.validation_status.value, validation_status)
|
||||
if validation_status == "excluded":
|
||||
self.assertEqual(message.send_status.value, "skipped")
|
||||
self.assertEqual(message.imap_status.value, "skipped")
|
||||
coverage_issues = [issue for issue in message.issues if issue.code == "missing_attachment_coverage"]
|
||||
if issue_behavior is None:
|
||||
self.assertEqual(coverage_issues, [])
|
||||
|
||||
67
tests/test_excluded_delivery_status.py
Normal file
67
tests/test_excluded_delivery_status.py
Normal file
@@ -0,0 +1,67 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from importlib import import_module
|
||||
from unittest.mock import patch
|
||||
|
||||
from sqlalchemy import create_engine, text
|
||||
|
||||
from govoplan_campaign.backend.campaign.models import BuildStatus, SendStatus
|
||||
from govoplan_campaign.backend.messages.models import ImapStatus, MessageDraft, MessageValidationStatus
|
||||
from govoplan_campaign.backend.persistence.campaigns import _job_from_message
|
||||
|
||||
|
||||
def test_excluded_message_persists_explicit_skipped_transport_states() -> None:
|
||||
message = MessageDraft(
|
||||
entry_index=0,
|
||||
entry_id="excluded-entry",
|
||||
active=True,
|
||||
build_status=BuildStatus.BUILT,
|
||||
validation_status=MessageValidationStatus.EXCLUDED,
|
||||
send_status=SendStatus.SKIPPED,
|
||||
imap_status=ImapStatus.SKIPPED,
|
||||
)
|
||||
|
||||
job = _job_from_message(
|
||||
tenant_id="tenant-1",
|
||||
campaign_id="campaign-1",
|
||||
version_id="version-1",
|
||||
message=message,
|
||||
)
|
||||
|
||||
assert job.send_status == "skipped"
|
||||
assert job.imap_status == "skipped"
|
||||
|
||||
|
||||
def test_status_migration_only_normalizes_excluded_rows_without_transport_evidence() -> None:
|
||||
migration = import_module(
|
||||
"govoplan_campaign.backend.migrations.versions."
|
||||
"d8b3e2c1f4a5_v0110_excluded_delivery_skipped"
|
||||
)
|
||||
engine = create_engine("sqlite+pysqlite:///:memory:")
|
||||
with engine.begin() as connection:
|
||||
connection.execute(text(
|
||||
"CREATE TABLE campaign_jobs ("
|
||||
"id TEXT PRIMARY KEY, validation_status TEXT NOT NULL, "
|
||||
"send_status TEXT NOT NULL, imap_status TEXT NOT NULL)"
|
||||
))
|
||||
connection.execute(
|
||||
text(
|
||||
"INSERT INTO campaign_jobs (id, validation_status, send_status, imap_status) VALUES "
|
||||
"('untouched', 'excluded', 'not_queued', 'pending'), "
|
||||
"('evidence', 'excluded', 'smtp_accepted', 'appended'), "
|
||||
"('queueable', 'ready', 'not_queued', 'pending')"
|
||||
)
|
||||
)
|
||||
with patch.object(migration.op, "get_bind", return_value=connection):
|
||||
migration.upgrade()
|
||||
|
||||
rows = {
|
||||
row.id: (row.send_status, row.imap_status)
|
||||
for row in connection.execute(
|
||||
text("SELECT id, send_status, imap_status FROM campaign_jobs ORDER BY id")
|
||||
)
|
||||
}
|
||||
|
||||
assert rows["untouched"] == ("skipped", "skipped")
|
||||
assert rows["evidence"] == ("smtp_accepted", "appended")
|
||||
assert rows["queueable"] == ("not_queued", "pending")
|
||||
@@ -40,6 +40,7 @@ class CampaignJobListQueryTests(unittest.TestCase):
|
||||
(3, "ordinary-3@example.test", "General notice", "blocked", "draft", "failed_permanent", "failed", 3),
|
||||
(4, "target-b@example.test", "Target notice B", "ready", "draft", "failed_temporary", "not_requested", 4),
|
||||
(5, "target-a@example.test", "Target notice A", "ready", "draft", "outcome_unknown", "outcome_unknown", 5),
|
||||
(6, "excluded@example.test", "Excluded notice", "excluded", "draft", "skipped", "skipped", 0),
|
||||
]
|
||||
for entry_index, recipient, subject, validation, queue, send, imap, attempts in rows:
|
||||
self.session.add(CampaignJob(
|
||||
@@ -89,7 +90,7 @@ class CampaignJobListQueryTests(unittest.TestCase):
|
||||
)
|
||||
|
||||
self.assertEqual(page.total, 3)
|
||||
self.assertEqual(page.total_unfiltered, 6)
|
||||
self.assertEqual(page.total_unfiltered, 7)
|
||||
self.assertEqual(page.pages, 2)
|
||||
self.assertEqual(
|
||||
[row["recipient_email"] for row in page.jobs],
|
||||
@@ -118,6 +119,31 @@ class CampaignJobListQueryTests(unittest.TestCase):
|
||||
|
||||
self.assertEqual(raised.exception.status_code, 422)
|
||||
|
||||
def test_skipped_transport_filters_and_counts_remain_separate(self) -> None:
|
||||
base_filters = [CampaignJob.tenant_id == "tenant-1", CampaignJob.campaign_id == "campaign-1"]
|
||||
grid_filters = {"send": 'list:["skipped"]', "imap": 'list:["skipped"]'}
|
||||
filtered = [*base_filters, *_campaign_jobs_grid_filter_expressions(grid_filters)]
|
||||
|
||||
page = _campaign_jobs_page_response(
|
||||
self.session,
|
||||
campaign_id="campaign-1",
|
||||
version_id="version-1",
|
||||
base_filters=base_filters,
|
||||
filtered=filtered,
|
||||
reviewed_keys=set(),
|
||||
review_metadata={},
|
||||
page=1,
|
||||
page_size=20,
|
||||
grid_filters=grid_filters,
|
||||
)
|
||||
|
||||
self.assertEqual([row["id"] for row in page.jobs], ["job-6"])
|
||||
self.assertEqual(page.counts["send"]["skipped"], 1)
|
||||
self.assertEqual(page.counts["send"]["not_queued"], 2)
|
||||
self.assertEqual(page.counts["imap"]["skipped"], 1)
|
||||
self.assertEqual(page.filtered_counts["send"], {"skipped": 1})
|
||||
self.assertEqual(page.filtered_counts["imap"], {"skipped": 1})
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
@@ -53,6 +53,13 @@ class CampaignQueueControlTests(unittest.TestCase):
|
||||
self._add_job("unknown", queue="draft", send="outcome_unknown")
|
||||
self._add_job("accepted", queue="draft", send="smtp_accepted")
|
||||
self._add_job("claimed", queue="sending", send="claimed")
|
||||
self._add_job(
|
||||
"excluded",
|
||||
queue="draft",
|
||||
send="skipped",
|
||||
validation="excluded",
|
||||
imap="skipped",
|
||||
)
|
||||
self._add_job(
|
||||
"other-tenant",
|
||||
tenant_id="tenant-2",
|
||||
@@ -111,8 +118,10 @@ class CampaignQueueControlTests(unittest.TestCase):
|
||||
|
||||
self.assertEqual(first["cancelled_count"], 3)
|
||||
self.assertEqual(first["protected_count"], 3)
|
||||
self.assertEqual(first["skipped_count"], 1)
|
||||
self.assertEqual(second["cancelled_count"], 0)
|
||||
self.assertEqual(second["protected_count"], 3)
|
||||
self.assertEqual(second["skipped_count"], 1)
|
||||
self.session.expire_all()
|
||||
for job_id in ("queued", "paused", "failed"):
|
||||
job = self.session.get(CampaignJob, job_id)
|
||||
@@ -121,6 +130,8 @@ class CampaignQueueControlTests(unittest.TestCase):
|
||||
self.assertEqual(self.session.get(CampaignJob, "unknown").send_status, JobSendStatus.OUTCOME_UNKNOWN.value)
|
||||
self.assertEqual(self.session.get(CampaignJob, "accepted").send_status, JobSendStatus.SMTP_ACCEPTED.value)
|
||||
self.assertEqual(self.session.get(CampaignJob, "claimed").send_status, JobSendStatus.CLAIMED.value)
|
||||
self.assertEqual(self.session.get(CampaignJob, "excluded").send_status, JobSendStatus.SKIPPED.value)
|
||||
self.assertEqual(self.session.get(CampaignJob, "excluded").queue_status, JobQueueStatus.DRAFT.value)
|
||||
self.assertEqual(self.session.get(CampaignJob, "other-tenant").send_status, JobSendStatus.QUEUED.value)
|
||||
|
||||
def test_controls_fail_closed_for_a_campaign_owned_by_another_tenant(self) -> None:
|
||||
@@ -148,6 +159,8 @@ class CampaignQueueControlTests(unittest.TestCase):
|
||||
self.assertEqual(cards["retryable"], 1)
|
||||
self.assertEqual(cards["queueable_unattempted"], 0)
|
||||
self.assertEqual(cards["cancellable"], 3)
|
||||
self.assertEqual(cards["skipped"], 1)
|
||||
self.assertEqual(cards["imap_skipped"], 1)
|
||||
self.assertEqual(projected_version["delivery_mode"], "worker_queue")
|
||||
self.assertIn("delivery_mode_selected_at", projected_version)
|
||||
self.assertNotIn("execution_snapshot", projected_version)
|
||||
@@ -179,6 +192,8 @@ class CampaignQueueControlTests(unittest.TestCase):
|
||||
tenant_id: str = "tenant-1",
|
||||
campaign_id: str = "campaign-1",
|
||||
version_id: str = "version-1",
|
||||
validation: str = JobValidationStatus.READY.value,
|
||||
imap: str = "not_requested",
|
||||
) -> None:
|
||||
self.session.add(CampaignJob(
|
||||
id=job_id,
|
||||
@@ -188,9 +203,10 @@ class CampaignQueueControlTests(unittest.TestCase):
|
||||
entry_index=len(self.session.new),
|
||||
entry_id=f"entry-{job_id}",
|
||||
build_status=JobBuildStatus.BUILT.value,
|
||||
validation_status=JobValidationStatus.READY.value,
|
||||
validation_status=validation,
|
||||
queue_status=queue,
|
||||
send_status=send,
|
||||
imap_status=imap,
|
||||
resolved_attachments=[],
|
||||
issues_snapshot=[],
|
||||
))
|
||||
|
||||
Reference in New Issue
Block a user