test(campaign): complete delivery fault drills

This commit is contained in:
2026-07-22 18:26:28 +02:00
parent e3cc476508
commit 2fa91bb943
2 changed files with 743 additions and 28 deletions

View File

@@ -2,6 +2,7 @@ from __future__ import annotations
import importlib.util
import json
import smtplib
import sys
from pathlib import Path
from typing import Any
@@ -317,6 +318,16 @@ def test_evidence_projection_rejects_unknown_status_keys() -> None:
}
)
with pytest.raises(runner.AcceptanceError, match="durable attempt status"):
runner._durable_state_evidence(
{
"job_count": 1,
"send_status_counts": {"sending": 1},
"attempt_status_counts": {"provider-secret": 1},
"unfinished_attempt_count": 1,
}
)
with pytest.raises(runner.AcceptanceError, match="synchronous delivery mode"):
runner._send_evidence(
{
@@ -337,6 +348,85 @@ def test_evidence_projection_rejects_unknown_status_keys() -> None:
)
def _open_fault_smtp(endpoint):
client = smtplib.SMTP(endpoint.host, endpoint.port, timeout=5)
client.ehlo()
client.login("acceptance-user", "acceptance-password")
return client
def test_explicit_temporary_smtp_response_occurs_after_complete_data() -> None:
with runner.smtp_fault_endpoint("temporary_data_response") as endpoint:
client = _open_fault_smtp(endpoint)
try:
with pytest.raises(smtplib.SMTPDataError) as captured:
client.sendmail(
"sender@example.test",
["recipient@example.test"],
b"Subject: temporary\r\n\r\nmessage",
)
finally:
client.close()
assert captured.value.smtp_code == 451
assert endpoint.evidence() == {
"connection_count": 1,
"accepted_rcpt_commands": 1,
"refused_rcpt_commands": 0,
"data_transactions": 1,
}
def test_partial_recipient_refusal_retains_one_accepted_envelope() -> None:
with runner.smtp_fault_endpoint("partial_recipient_refusal") as endpoint:
client = _open_fault_smtp(endpoint)
try:
refused = client.sendmail(
"sender@example.test",
["accepted@example.test", "refused@example.test"],
b"Subject: partial\r\n\r\nmessage",
)
finally:
client.quit()
assert set(refused) == {"refused@example.test"}
assert endpoint.evidence() == {
"connection_count": 1,
"accepted_rcpt_commands": 1,
"refused_rcpt_commands": 1,
"data_transactions": 1,
}
def test_post_data_disconnect_is_a_real_ambiguous_protocol_boundary() -> None:
with runner.smtp_fault_endpoint("post_data_disconnect") as endpoint:
client = _open_fault_smtp(endpoint)
try:
with pytest.raises(smtplib.SMTPServerDisconnected):
client.sendmail(
"sender@example.test",
["recipient@example.test"],
b"Subject: ambiguous\r\n\r\nmessage",
)
finally:
client.close()
assert endpoint.wait_for_data(1)
assert endpoint.evidence()["data_transactions"] == 1
def test_partial_refusal_fixture_adds_a_second_distinct_recipient() -> None:
raw, _subject = runner.materialize_campaign_fixture(
FIXTURE_PATH,
profile_id="profile-1",
settings=_settings(),
scenario="partial_envelope_refusal",
run_token="0123456789ab",
additional_envelope_recipient=True,
)
recipients = raw["entries"]["inline"][0]["to"]
assert len(recipients) == 2
assert recipients[0]["email"] != recipients[1]["email"]
assert recipients[1]["email"].endswith("@govoplan.test")
def test_fixture_contains_no_transport_credentials() -> None:
raw = json.loads(FIXTURE_PATH.read_text(encoding="utf-8"))
runner._assert_no_forbidden_campaign_keys(raw)
@@ -350,7 +440,7 @@ def test_fixture_composition_versions_are_complete_and_exact() -> None:
"core": "0.1.13",
"access": "0.1.11",
"audit": "0.1.8",
"campaigns": "0.1.10",
"campaigns": "0.1.11",
"mail": "0.1.10",
"files": "0.1.9",
}
@@ -359,7 +449,7 @@ def test_fixture_composition_versions_are_complete_and_exact() -> None:
"core": "0.1.13",
"access": "0.1.11",
"audit": "0.1.8",
"campaigns": "0.1.10",
"campaigns": "0.1.11",
"mail": "0.1.10",
}
@@ -371,7 +461,7 @@ def test_fixture_composition_fails_closed_when_a_required_version_is_missing() -
{
"core": "0.1.13",
"access": "0.1.11",
"campaigns": "0.1.10",
"campaigns": "0.1.11",
"mail": "0.1.10",
},
)
@@ -382,7 +472,9 @@ def test_testbed_documentation_distinguishes_proven_and_open_failure_drills() ->
runbook = (REPOSITORY_ROOT / "docs" / "CAMPAIGN_DELIVERY_RUNBOOK.md").read_text(encoding="utf-8")
assert "second ordinary send must be rejected before another provider effect" in testbed
assert "post-DATA connection loss" in testbed
assert "real worker restart" in testbed
assert "does not simulate a connection loss after SMTP DATA" in runbook
assert "connection loss after complete DATA is frozen" in testbed
assert "dedicated OS process" in testbed
assert "Redis/Celery delivery" in runbook
assert "broker redelivery" in runbook
assert "celery_broker_redelivery" in testbed
assert "raw provider diagnostics" in runbook