mirror of
https://git.hiabuto.net/C3MA/CCMA.git
synced 2026-09-06 03:20:49 +02:00
"Erneut prüfen" refreshed the banner but left the window title and the status bar reading "NUR LESEN" -- both were built once and never updated -- while the banner itself asked for a restart that the recheck exists to avoid. Title, status bar and banner are refreshed from one place now, so none of them can be left behind, and the main window owns the title instead of app.py setting it once at startup. The banner points at "Erneut prüfen" and says outright that no restart is needed. Since the startup pass was skipped, its task list is empty rather than current, so a successful recheck offers the housekeeper run that fills it. UI tests cover both directions of the recheck; their Tk root moved into a shared conftest fixture, because a second root in another module invalidates the icon images bound to the first. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
162 lines
5.8 KiB
Python
162 lines
5.8 KiB
Python
from email.parser import BytesParser
|
|
from email.policy import default
|
|
|
|
import pytest
|
|
|
|
pytest.importorskip("tkinter")
|
|
|
|
from ccma.config import AppConfig # noqa: E402
|
|
from ccma.domain.mail_templates import placeholder_entries # noqa: E402
|
|
from ccma.services.welcome_mail import generate_and_send_welcome_mail # noqa: E402
|
|
from ccma.storage.repository import MemberRepository, RepositoryError # noqa: E402
|
|
|
|
|
|
@pytest.fixture
|
|
def repository(tmp_path):
|
|
repository = MemberRepository(tmp_path / "store")
|
|
repository.initialize()
|
|
organization = repository.get_configuration()["organization"]
|
|
organization.update(
|
|
{
|
|
"name": "Chaos Computer Club Mannheim e.V.",
|
|
"email": "verwaltung@example.org",
|
|
"iban": "DE98670505050038907751",
|
|
"bic": "MANSDE66XXX",
|
|
}
|
|
)
|
|
repository.save_organization(organization)
|
|
return repository
|
|
|
|
|
|
def _open_options(tk_root, repository):
|
|
from ccma.ui.options_dialog import OptionsDialog
|
|
|
|
dialog = OptionsDialog(tk_root, AppConfig(store_path=str(repository.root)), repository)
|
|
tk_root.update()
|
|
return dialog
|
|
|
|
|
|
def _block_row(dialog):
|
|
for row in dialog.mail_template_placeholders.get_children():
|
|
label = str(dialog.mail_template_placeholders.item(row, "values")[0])
|
|
if label.startswith("{{#claims}}"):
|
|
return row
|
|
raise AssertionError("Der Wiederholungsblock fehlt in der Platzhalterliste.")
|
|
|
|
|
|
def test_double_click_on_a_block_is_wired_up(tk_root, repository):
|
|
dialog = _open_options(tk_root, repository)
|
|
try:
|
|
assert dialog.mail_template_placeholders.bind("<Double-1>")
|
|
assert dialog.mail_template_snippets[_block_row(dialog)] == next(
|
|
entry.snippet
|
|
for entry in placeholder_entries("welcome")
|
|
if entry.label.startswith("{{#claims}}")
|
|
)
|
|
finally:
|
|
dialog.grab_release()
|
|
dialog.destroy()
|
|
|
|
|
|
def test_inserting_a_block_puts_editable_template_code_into_the_body(tk_root, repository):
|
|
dialog = _open_options(tk_root, repository)
|
|
try:
|
|
dialog.mail_template_body.delete("1.0", "end")
|
|
dialog.mail_template_body.insert("1.0", "Offen sind aktuell:")
|
|
dialog.mail_template_placeholders.selection_set(_block_row(dialog))
|
|
dialog._insert_mail_placeholder()
|
|
tk_root.update()
|
|
body = dialog.mail_template_body.get("1.0", "end-1c")
|
|
finally:
|
|
dialog.grab_release()
|
|
dialog.destroy()
|
|
|
|
assert "…" not in body
|
|
assert "{{#claims}}" in body and "{{/claims}}" in body
|
|
assert "{{claim.description}}" in body
|
|
# The block was appended behind existing text and still starts on its own line.
|
|
assert body.splitlines()[0] == "Offen sind aktuell:"
|
|
assert body.splitlines()[1] == "{{#claims}}"
|
|
|
|
|
|
def test_edited_template_survives_saving_and_produces_real_lines_in_the_mail(
|
|
tk_root, repository, tmp_path
|
|
):
|
|
member = repository.create_member(
|
|
first_name="Ada", last_name="Lovelace", birth_date="1990-01-01"
|
|
)
|
|
member.email = "ada@example.org"
|
|
member.accepted_at = "2026-08-20"
|
|
repository.save_member(member)
|
|
repository.create_manual_claim(
|
|
member.member_id, title="Aufnahmegebühr", amount="15.00", due_date="2026-09-17"
|
|
)
|
|
|
|
dialog = _open_options(tk_root, repository)
|
|
try:
|
|
dialog.mail_template_subject_var.set("Willkommen, {{member.first_name}}")
|
|
dialog.mail_template_body.delete("1.0", "end")
|
|
dialog.mail_template_body.insert("1.0", "Offen sind aktuell:\n")
|
|
dialog.mail_template_placeholders.selection_set(_block_row(dialog))
|
|
dialog._insert_mail_placeholder()
|
|
tk_root.update()
|
|
dialog._save_mail_templates()
|
|
finally:
|
|
dialog.grab_release()
|
|
dialog.destroy()
|
|
|
|
generated = generate_and_send_welcome_mail(
|
|
repository,
|
|
member.member_id,
|
|
delivery_mode="local",
|
|
output_path=tmp_path / "Willkommen.eml",
|
|
sender_name="Verwaltung C3MA",
|
|
sender_email="verwaltung@example.org",
|
|
signature="Der Vorstand",
|
|
)
|
|
|
|
content = BytesParser(policy=default).parsebytes(
|
|
generated.export_path.read_bytes()
|
|
).get_content()
|
|
assert "Aufnahmegebühr (fällig 17.09.2026): 15.00 Euro" in content
|
|
assert "…" not in content
|
|
|
|
|
|
def test_the_dialog_refuses_to_save_an_unbalanced_block(tk_root, repository):
|
|
stored = repository.mail_templates_root / "willkommen.txt"
|
|
before = stored.read_text(encoding="utf-8")
|
|
|
|
dialog = _open_options(tk_root, repository)
|
|
try:
|
|
dialog.mail_template_body.delete("1.0", "end")
|
|
dialog.mail_template_body.insert("1.0", "Offen sind aktuell:\n{{#claims}}\n{{claim.title}}")
|
|
with pytest.raises(RepositoryError, match="wird nicht geschlossen"):
|
|
dialog._save_mail_templates()
|
|
finally:
|
|
dialog.grab_release()
|
|
dialog.destroy()
|
|
|
|
assert stored.read_text(encoding="utf-8") == before
|
|
|
|
|
|
def test_a_broken_template_does_not_half_save_the_others(tk_root, repository):
|
|
reminder_file = repository.mail_templates_root / "mahnung.txt"
|
|
before = reminder_file.read_text(encoding="utf-8")
|
|
|
|
dialog = _open_options(tk_root, repository)
|
|
try:
|
|
# Edit a valid template first, then break the second one.
|
|
dialog.mail_template_var.set("Mahnung")
|
|
dialog._select_mail_template()
|
|
dialog.mail_template_body.insert("end", "\nP.S. Bitte Mitgliedsnummer angeben.")
|
|
dialog.mail_template_var.set("Willkommen & Erstrechnung")
|
|
dialog._select_mail_template()
|
|
dialog.mail_template_body.insert("end", "\n{{#claims}}\n{{claim.title}}")
|
|
with pytest.raises(RepositoryError, match="wird nicht geschlossen"):
|
|
dialog._save_mail_templates()
|
|
finally:
|
|
dialog.grab_release()
|
|
dialog.destroy()
|
|
|
|
assert reminder_file.read_text(encoding="utf-8") == before
|