testsuite fuer mailmerge
This commit is contained in:
@@ -0,0 +1,206 @@
|
||||
"""
|
||||
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 ("'" -> "'")
|
||||
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
|
||||
Reference in New Issue
Block a user