Files
govoplan-campaign/tests/test_campaign_reports.py

282 lines
9.6 KiB
Python

from __future__ import annotations
from datetime import UTC, datetime
from types import SimpleNamespace
import unittest
from unittest.mock import patch
from govoplan_core.core.postbox import PostboxDeliveryReceiptSummaryRef
from govoplan_campaign.backend.reports.campaigns import (
_campaign_postbox_receipt_summary,
_job_evidence_row,
_latest_by_job_id,
_load_delivery_info,
_review_decision_summary,
_review_decisions_by_job,
generate_campaign_report,
)
def _dt() -> datetime:
return datetime(2026, 7, 8, 12, 30, tzinfo=UTC)
def test_job_evidence_row_contains_transport_and_message_evidence() -> None:
job = SimpleNamespace(
id="job-1",
campaign_id="campaign-1",
campaign_version_id="version-1",
entry_index=0,
entry_id="recipient-1",
recipient_email="recipient@govoplan.test",
subject="Evidence test",
message_id_header="<message-1@govoplan.test>",
eml_storage_key="eml/job-1.eml",
eml_local_path="/tmp/job-1.eml",
eml_size_bytes=1234,
eml_sha256="abc123",
build_status="built",
validation_status="ready",
queue_status="queued",
send_status="smtp_accepted",
imap_status="appended",
attempt_count=2,
queued_at=_dt(),
claimed_at=None,
smtp_started_at=_dt(),
outcome_unknown_at=None,
sent_at=_dt(),
last_error=None,
resolved_recipients={
"from": [{"name": "Sender", "email": "sender@govoplan.test"}],
"to": [{"name": "Recipient", "email": "recipient@govoplan.test"}],
"cc": [],
"bcc": [],
"reply_to": [{"email": "reply@govoplan.test"}],
},
resolved_attachments=[
{
"matches": [{"filename": "notice.pdf"}, "/tmp/evidence.xlsx"],
"zip_filename": "bundle.zip",
"message_filenames": ["notice.pdf"],
}
],
issues_snapshot=[],
created_at=_dt(),
updated_at=_dt(),
)
smtp = SimpleNamespace(
job_id="job-1",
attempt_number=2,
status="success",
smtp_status_code=250,
smtp_response="2.0.0 queued",
error_type=None,
error_message=None,
started_at=_dt(),
finished_at=_dt(),
)
imap = SimpleNamespace(
job_id="job-1",
attempt_number=1,
status="appended",
folder="Sent",
error_message=None,
created_at=_dt(),
updated_at=_dt(),
)
row = _job_evidence_row(
job,
latest_smtp=smtp,
latest_imap=imap,
review_decision={
"decision": "accept",
"reason": "Recipient confirmed no attachment was expected.",
"actor_user_id": "reviewer-1",
"decided_at": "2026-07-08T12:30:00+00:00",
"review_key": "recipient-1",
"message_sha256": "abc123",
"issue_fingerprint": "def456",
"issue_codes": ["missing_optional_attachment"],
},
)
assert row["campaign_id"] == "campaign-1"
assert row["campaign_version_id"] == "version-1"
assert row["message_id_header"] == "<message-1@govoplan.test>"
assert row["from"] == "Sender <sender@govoplan.test>"
assert row["to"] == "Recipient <recipient@govoplan.test>"
assert row["reply_to"] == "reply@govoplan.test"
assert "bundle.zip" in row["attachment_names"]
assert "notice.pdf" in row["attachment_names"]
assert row["latest_smtp_status_code"] == 250
assert "latest_smtp_response" not in row
assert "latest_smtp_error_type" not in row
assert "latest_smtp_error_message" not in row
assert row["latest_imap_status"] == "appended"
assert row["latest_imap_folder"] == "Sent"
assert "latest_imap_error_message" not in row
assert "eml_storage_key" not in row
assert "eml_local_path" not in row
assert row["review_decision"] == "accept"
assert row["review_actor_user_id"] == "reviewer-1"
assert row["review_issue_codes"] == "missing_optional_attachment"
assert "review_message_sha256" not in row
def test_report_uses_only_review_decisions_for_the_current_build() -> None:
decision = {
"job_id": "job-1",
"decision": "accept",
"issue_codes": ["missing_optional_attachment"],
}
version = SimpleNamespace(
build_summary={"build_token": "build-2"},
editor_state={
"review_send": {
"build_token": "build-2",
"inspection_complete": True,
"issue_decisions": [decision],
}
},
)
decisions = _review_decisions_by_job(version)
assert decisions == {"job-1": decision}
assert _review_decision_summary(decisions) == {
"exception_decision_count": 1,
"by_issue_code": {"missing_optional_attachment": 1},
}
version.editor_state["review_send"]["build_token"] = "stale-build"
assert _review_decisions_by_job(version) == {}
def test_latest_by_job_id_keeps_highest_attempt_number() -> None:
attempts = [
SimpleNamespace(job_id="job-1", attempt_number=1),
SimpleNamespace(job_id="job-1", attempt_number=3),
SimpleNamespace(job_id="job-2", attempt_number=2),
]
latest = _latest_by_job_id(attempts)
assert latest["job-1"].attempt_number == 3
assert latest["job-2"].attempt_number == 2
def test_invalid_legacy_snapshot_never_echoes_validation_details() -> None:
version = SimpleNamespace(
execution_snapshot={
"snapshot_version": "legacy",
"smtp": {"host": "smtp.internal.example", "password": "provider-secret"},
},
execution_snapshot_hash=None,
execution_snapshot_at=None,
)
delivery = _load_delivery_info(version, [])
assert delivery["load_error"] == "Execution snapshot is invalid; rebuild the selected version before delivery."
assert "provider-secret" not in repr(delivery)
assert "smtp.internal.example" not in repr(delivery)
assert "background_workers_enabled" not in delivery
assert "smtp_transport_revision" not in delivery
operator_delivery = _load_delivery_info(version, [], include_diagnostics=True)
assert "background_workers_enabled" in operator_delivery
assert "smtp_transport_revision" in operator_delivery
def test_aggregate_report_omits_recipient_level_failures_by_default() -> None:
campaign = SimpleNamespace(id="campaign-1")
version = SimpleNamespace(id="version-1")
with (
patch("govoplan_campaign.backend.reports.campaigns._get_campaign", return_value=campaign),
patch("govoplan_campaign.backend.reports.campaigns._selected_version", return_value=version),
patch("govoplan_campaign.backend.reports.campaigns._report_jobs", return_value=[]),
patch(
"govoplan_campaign.backend.reports.campaigns._campaign_report_payload",
return_value={"cards": {}},
) as payload,
):
report = generate_campaign_report(
object(), # type: ignore[arg-type]
tenant_id="tenant-1",
campaign_id="campaign-1",
)
assert report == {"cards": {}}
assert payload.call_args.kwargs["include_recent_failures"] is False
def test_postbox_receipt_report_aggregates_provider_state_without_identities() -> None:
class _Query:
def join(self, *args, **kwargs):
return self
def filter(self, *args, **kwargs):
return self
def all(self):
return [("delivery-1",), ("delivery-2",), ("delivery-1",)]
integration = SimpleNamespace(
receipt_evidence_available=True,
delivery_receipt_summaries=lambda session, **kwargs: {
"delivery-1": PostboxDeliveryReceiptSummaryRef(
delivery_id="delivery-1",
message_id="message-1",
postbox_id="postbox-1",
delivery_status="accepted",
accepted_at=_dt(),
currently_readable=True,
read_receipt_count=2,
acknowledged_receipt_count=1,
routed_message_count=1,
),
"delivery-2": PostboxDeliveryReceiptSummaryRef(
delivery_id="delivery-2",
message_id="message-2",
postbox_id="postbox-2",
delivery_status="accepted_vacant",
accepted_at=_dt(),
currently_readable=False,
expired_message_count=1,
),
},
)
session = SimpleNamespace(query=lambda *args: _Query())
with patch(
"govoplan_campaign.backend.reports.campaigns.postbox_integration",
return_value=integration,
):
report = _campaign_postbox_receipt_summary(
session, # type: ignore[arg-type]
tenant_id="tenant-1",
campaign_id="campaign-1",
version=SimpleNamespace(id="version-1"),
)
assert report["delivery_count"] == 2
assert report["currently_readable_delivery_count"] == 1
assert report["read_delivery_count"] == 1
assert report["acknowledged_delivery_count"] == 1
assert report["routed_message_count"] == 1
assert report["expired_message_count"] == 1
assert "account_id" not in repr(report)
assert "identity_id" not in repr(report)
class CampaignReportProjectionTests(unittest.TestCase):
def test_evidence_projection(self) -> None:
test_job_evidence_row_contains_transport_and_message_evidence()
def test_latest_attempt_projection(self) -> None:
test_latest_by_job_id_keeps_highest_attempt_number()
if __name__ == "__main__":
unittest.main()