Cover archive-directory setup in the sent/delivered rollback path

archive_dir.mkdir() and the archive path lookup ran outside the
try/except that decides whether to revert mark_reminder_sent (reminder
mail) or keep a SEPA batch running for the other debits. A failure there
(read-only store, full disk) left a reminder booked as "sent" with no
mail ever having gone out, and could still abort an entire SEPA batch
for one member's directory problem.

Moved that setup inside the same try blocks so it's treated exactly like
any other pre-delivery failure: reminder_mail reverts to draft, and
sepa_mail records a warning and continues with the remaining debits.
Also moved debit_mail_bytes() into the per-debit try in sepa_mail for
the same reason. Added a targeted mkdir-failure test for each.
This commit is contained in:
Marcel Peterkau
2026-08-20 12:51:17 +02:00
parent 5fc6d7aec9
commit a4536eedd0
4 changed files with 178 additions and 38 deletions
+43
View File
@@ -370,3 +370,46 @@ def test_reminder_mail_archiving_failure_after_smtp_success_does_not_revert(tmp_
assert events[-1].event_type == "reminder_email_sent"
assert events[-1].data["archive_error"]
assert "document" not in events[-1].references
def test_reminder_mail_archive_dir_creation_failure_reverts_sent_status(tmp_path, monkeypatch):
from pathlib import Path
repository, member = _overdue_claim_repository(tmp_path / "store")
member.email = "reminder@example.org"
repository.save_member(member)
reminder = repository.create_reminder_draft(
member.member_id,
"claim-1",
level=1,
name="Zahlungserinnerung",
payment_deadline_days=14,
items=[{"description": "Mahngebühr", "amount": "5.00"}],
)
original_mkdir = Path.mkdir
def _flaky_mkdir(self, *args, **kwargs):
if self.name == "Mahnungen":
raise OSError("permission denied")
return original_mkdir(self, *args, **kwargs)
monkeypatch.setattr(Path, "mkdir", _flaky_mkdir)
with pytest.raises(OSError):
generate_and_send_reminder_mail(
repository,
member.member_id,
"claim-1",
reminder["reminder_id"],
delivery_mode="local",
output_path=tmp_path / "Zahlungserinnerung.eml",
sender_name="Verwaltung C3MA",
sender_email="verwaltung@example.org",
signature="Der Vorstand",
)
data = repository.get_contributions(member.member_id)
stored_reminder = data.reminders[0]
assert stored_reminder["status"] == "draft"
assert not stored_reminder.get("fee_item_ids")