mirror of
https://git.hiabuto.net/C3MA/CCMA.git
synced 2026-07-01 11:14:52 +02:00
125 lines
4.7 KiB
Python
125 lines
4.7 KiB
Python
from __future__ import annotations
|
|
|
|
from datetime import date
|
|
from xml.etree import ElementTree
|
|
|
|
import pytest
|
|
|
|
import ccma.services.documents as document_module
|
|
from ccma.services.documents import DocumentError, DocumentService, _replace_xml_placeholders
|
|
from ccma.services.housekeeper import Housekeeper
|
|
from ccma.storage.repository import MemberRepository, RepositoryError
|
|
|
|
|
|
def test_placeholder_replacement_crosses_formatted_odf_spans() -> None:
|
|
source = b"""<?xml version="1.0" encoding="UTF-8"?>
|
|
<office:document xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0"
|
|
xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0">
|
|
<office:body><office:text><text:p>Hello {{member.<text:span>full</text:span>_name}}!</text:p>
|
|
</office:text></office:body></office:document>"""
|
|
|
|
rendered = _replace_xml_placeholders(source, {"member.full_name": "Ada Lovelace"})
|
|
text = "".join(ElementTree.fromstring(rendered).itertext())
|
|
|
|
assert "Hello Ada Lovelace!" in text
|
|
assert "{{" not in text
|
|
|
|
|
|
def test_unknown_placeholder_is_reported() -> None:
|
|
source = b"""<document><p>{{member.unknown}}</p></document>"""
|
|
|
|
with pytest.raises(DocumentError, match="member.unknown"):
|
|
_replace_xml_placeholders(source, {})
|
|
|
|
|
|
def test_default_templates_are_seeded_without_overwriting_store_version(tmp_path) -> None:
|
|
repository = MemberRepository(tmp_path)
|
|
repository.initialize()
|
|
template = repository.root / "templates" / "Mitglied.fodt"
|
|
template.write_text("custom", encoding="utf-8")
|
|
|
|
repository.initialize()
|
|
|
|
assert template.read_text(encoding="utf-8") == "custom"
|
|
assert {path.name for path in (repository.root / "templates").iterdir()} == {
|
|
"Forderung.fodt",
|
|
"Mahnung.fodt",
|
|
"Mitglied.fodt",
|
|
}
|
|
|
|
|
|
def test_templates_are_filtered_by_available_context(tmp_path) -> None:
|
|
repository = MemberRepository(tmp_path)
|
|
repository.initialize()
|
|
service = DocumentService(repository)
|
|
|
|
member_templates = {
|
|
item.path.name for item in service.compatible_templates(has_claim=False, has_reminder=False)
|
|
}
|
|
claim_templates = {
|
|
item.path.name for item in service.compatible_templates(has_claim=True, has_reminder=False)
|
|
}
|
|
reminder_templates = {
|
|
item.path.name for item in service.compatible_templates(has_claim=True, has_reminder=True)
|
|
}
|
|
|
|
assert member_templates == {"Mitglied.fodt"}
|
|
assert claim_templates == {"Forderung.fodt", "Mitglied.fodt"}
|
|
assert reminder_templates == {"Forderung.fodt", "Mahnung.fodt", "Mitglied.fodt"}
|
|
|
|
|
|
def test_generated_reminder_pdf_is_stored_audited_and_linked(tmp_path, monkeypatch) -> None:
|
|
repository = MemberRepository(tmp_path)
|
|
repository.initialize()
|
|
member = repository.create_member(first_name="Ada", last_name="Lovelace")
|
|
member.status = "active"
|
|
member.accepted_at = "2026-01-01"
|
|
member.membership_started_at = "2026-01-01"
|
|
repository.save_member(member)
|
|
Housekeeper(repository).run(today=date(2026, 1, 1))
|
|
claim = repository.get_contributions(member.member_id).claims[0]
|
|
claim_id = str(claim["claim_id"])
|
|
reminder = repository.create_reminder_draft(
|
|
member.member_id,
|
|
claim_id,
|
|
level=1,
|
|
name="Zahlungserinnerung",
|
|
payment_deadline_days=14,
|
|
)
|
|
|
|
def fake_convert(source, output_directory):
|
|
destination = output_directory / f"{source.stem}.pdf"
|
|
destination.write_bytes(b"%PDF-1.7\ntest")
|
|
return destination
|
|
|
|
monkeypatch.setattr(document_module, "_convert_to_pdf", fake_convert)
|
|
service = DocumentService(repository)
|
|
template = next(item for item in service.list_templates() if item.path.name == "Mahnung.fodt")
|
|
|
|
generated = service.generate(
|
|
template,
|
|
member.member_id,
|
|
output_name="Mahnung.pdf",
|
|
claim_id=claim_id,
|
|
reminder_id=str(reminder["reminder_id"]),
|
|
)
|
|
|
|
assert generated.path.read_bytes().startswith(b"%PDF-")
|
|
assert generated.relative_path == "documents/Mahnung.pdf"
|
|
stored = repository.get_contributions(member.member_id)
|
|
updated = next(item for item in stored.reminders if item["reminder_id"] == reminder["reminder_id"])
|
|
assert updated["status"] == "generated"
|
|
assert updated["document"]["path"] == generated.relative_path
|
|
assert updated["document"]["sha256"] == generated.sha256
|
|
assert repository.get_events(member.member_id)[-1].event_type == "reminder_document_generated"
|
|
|
|
with pytest.raises(RepositoryError, match="innerhalb"):
|
|
repository.register_reminder_document(
|
|
member.member_id,
|
|
claim_id,
|
|
str(reminder["reminder_id"]),
|
|
relative_path="../outside.pdf",
|
|
sha256="invalid",
|
|
template="Mahnung.fodt",
|
|
)
|