Files

207 lines
7.5 KiB
Python
Raw Permalink Normal View History

2026-05-22 08:40:04 +02:00
"""
Tests für die job_preview-View.
Hängt nicht am echten LibreOffice build_preview wird auf Service-Ebene
gepatcht, sodass die Tests in <1 s laufen.
"""
from __future__ import annotations
from pathlib import Path
import pytest
from django.core.files.uploadedfile import SimpleUploadedFile
from django.urls import reverse
from mailmerge.services.preview import PreviewError, PreviewResult
PREVIEW_URL_NAME = "job-preview"
FAKE_PDF = b"%PDF-1.4 fake preview\n"
# ---------------------------------------------------------------------------
# HTTP-Method / Auth
# ---------------------------------------------------------------------------
class TestPreviewAccess:
def test_login_required(self, client, db):
url = reverse(PREVIEW_URL_NAME)
response = client.post(url, {})
# Django leitet auf Login um (302)
assert response.status_code == 302
assert "/login" in response["Location"].lower() or "login" in response["Location"].lower()
def test_get_not_allowed(self, auth_client):
url = reverse(PREVIEW_URL_NAME)
response = auth_client.get(url)
assert response.status_code == 405
# ---------------------------------------------------------------------------
# Form-Validierung
# ---------------------------------------------------------------------------
class TestPreviewFormValidation:
def test_missing_files_returns_form(self, auth_client):
url = reverse(PREVIEW_URL_NAME)
response = auth_client.post(url, {})
# Kein PDF, sondern Form-Template zurück
assert response.status_code == 200
assert response["Content-Type"].startswith("text/html")
def test_rejects_non_csv_extension(
self, auth_client, letter_template, docx_bytes
):
url = reverse(PREVIEW_URL_NAME)
bogus = SimpleUploadedFile("file.txt", b"foo", content_type="text/plain")
response = auth_client.post(url, {
"template": str(letter_template.pk),
"recipients_csv": bogus,
})
assert response.status_code == 200
assert b"Nur .csv-Dateien" in response.content
# ---------------------------------------------------------------------------
# Happy Path
# ---------------------------------------------------------------------------
@pytest.fixture
def patched_build_preview(monkeypatch):
"""Ersetzt build_preview in der Views-Importebene durch einen Stub."""
def fake_build_preview(template_path: Path, csv_file):
return PreviewResult(
pdf_bytes=FAKE_PDF,
used_row={"nachname": "Huber"},
placeholders=["anrede_brief", "nachname", "vorname", "ort"],
csv_columns=["anrede_brief", "nachname", "vorname", "ort"],
missing_columns=[],
extra_columns=[],
)
monkeypatch.setattr(
"mailmerge.views.build_preview", fake_build_preview
)
return fake_build_preview
class TestPreviewHappyPath:
def test_returns_pdf_content_type(
self, auth_client, letter_template, csv_uploaded_valid, patched_build_preview
):
url = reverse(PREVIEW_URL_NAME)
response = auth_client.post(url, {
"template": str(letter_template.pk),
"recipients_csv": csv_uploaded_valid,
})
assert response.status_code == 200
assert response["Content-Type"] == "application/pdf"
def test_returns_pdf_body(
self, auth_client, letter_template, csv_uploaded_valid, patched_build_preview
):
url = reverse(PREVIEW_URL_NAME)
response = auth_client.post(url, {
"template": str(letter_template.pk),
"recipients_csv": csv_uploaded_valid,
})
assert response.content == FAKE_PDF
def test_content_disposition_inline(
self, auth_client, letter_template, csv_uploaded_valid, patched_build_preview
):
url = reverse(PREVIEW_URL_NAME)
response = auth_client.post(url, {
"template": str(letter_template.pk),
"recipients_csv": csv_uploaded_valid,
})
assert response["Content-Disposition"].startswith("inline")
def test_placeholders_header_set(
self, auth_client, letter_template, csv_uploaded_valid, patched_build_preview
):
url = reverse(PREVIEW_URL_NAME)
response = auth_client.post(url, {
"template": str(letter_template.pk),
"recipients_csv": csv_uploaded_valid,
})
assert "X-Preview-Placeholders" in response
assert "nachname" in response["X-Preview-Placeholders"]
# ---------------------------------------------------------------------------
# Extra-Spalten als Hinweis-Header
# ---------------------------------------------------------------------------
class TestPreviewExtraColumnsHeader:
@pytest.fixture
def patched_with_extras(self, monkeypatch):
def fake_build_preview(template_path, csv_file):
return PreviewResult(
pdf_bytes=FAKE_PDF,
used_row={"nachname": "Huber"},
placeholders=["nachname"],
csv_columns=["nachname", "personalnr"],
missing_columns=[],
extra_columns=["personalnr"],
)
monkeypatch.setattr(
"mailmerge.views.build_preview", fake_build_preview
)
def test_extra_columns_header_contains_value(
self, auth_client, letter_template, csv_uploaded_valid, patched_with_extras
):
url = reverse(PREVIEW_URL_NAME)
response = auth_client.post(url, {
"template": str(letter_template.pk),
"recipients_csv": csv_uploaded_valid,
})
assert response["X-Preview-Extra-Columns"] == "personalnr"
# ---------------------------------------------------------------------------
# Fehlerpfade: PreviewError und unerwartete Exceptions
# ---------------------------------------------------------------------------
class TestPreviewErrorHandling:
def test_preview_error_renders_form_with_message(
self, auth_client, letter_template, csv_uploaded_valid, monkeypatch
):
def fake_build_preview(template_path, csv_file):
raise PreviewError("Spalte 'ort' fehlt")
monkeypatch.setattr(
"mailmerge.views.build_preview", fake_build_preview
)
url = reverse(PREVIEW_URL_NAME)
response = auth_client.post(url, {
"template": str(letter_template.pk),
"recipients_csv": csv_uploaded_valid,
})
assert response.status_code == 200
assert response["Content-Type"].startswith("text/html")
# Django escaped Apostrophe in Templates ("'" -> "&#x27;")
body = response.content.decode("utf-8")
assert "Spalte" in body and "ort" in body and "fehlt" in body
def test_unexpected_exception_renders_form(
self, auth_client, letter_template, csv_uploaded_valid, monkeypatch
):
def fake_build_preview(template_path, csv_file):
raise RuntimeError("LibreOffice timeout")
monkeypatch.setattr(
"mailmerge.views.build_preview", fake_build_preview
)
url = reverse(PREVIEW_URL_NAME)
response = auth_client.post(url, {
"template": str(letter_template.pk),
"recipients_csv": csv_uploaded_valid,
})
assert response.status_code == 200
assert response["Content-Type"].startswith("text/html")
assert b"Vorschau konnte nicht erstellt werden" in response.content
assert b"LibreOffice timeout" in response.content