testsuite fuer mailmerge
This commit is contained in:
@@ -0,0 +1,129 @@
|
||||
"""
|
||||
Gemeinsame Fixtures für die Mailmerge-Tests.
|
||||
|
||||
Liefert ein minimales DOCX-Template, eine passende CSV und einen
|
||||
authentifizierten Django-Testclient.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import io
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.core.files.uploadedfile import SimpleUploadedFile
|
||||
from docx import Document
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# DOCX-Helfer
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def _build_minimal_docx(placeholders: list[str]) -> bytes:
|
||||
"""Erzeugt ein DOCX im Speicher mit den übergebenen Jinja-Platzhaltern."""
|
||||
doc = Document()
|
||||
doc.add_paragraph("Sehr {{ anrede_brief }} {{ nachname }},")
|
||||
doc.add_paragraph(" ".join(f"{{{{ {p} }}}}" for p in placeholders))
|
||||
doc.add_paragraph("Mit freundlichen Grüßen")
|
||||
buf = io.BytesIO()
|
||||
doc.save(buf)
|
||||
return buf.getvalue()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def template_placeholders() -> list[str]:
|
||||
return ["anrede_brief", "nachname", "vorname", "ort"]
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def docx_bytes(template_placeholders) -> bytes:
|
||||
return _build_minimal_docx(template_placeholders)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def docx_file_on_disk(tmp_path, docx_bytes) -> Path:
|
||||
p = tmp_path / "template.docx"
|
||||
p.write_bytes(docx_bytes)
|
||||
return p
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# CSV-Helfer
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
CSV_VALID = (
|
||||
"anrede_brief,nachname,vorname,ort\n"
|
||||
"geehrte Frau,Huber,Andrea,Eisenstadt\n"
|
||||
"geehrter Herr,Müller,Bernhard,Neusiedl\n"
|
||||
)
|
||||
|
||||
CSV_MISSING_COLUMN = (
|
||||
"anrede_brief,nachname,vorname\n" # 'ort' fehlt
|
||||
"geehrte Frau,Huber,Andrea\n"
|
||||
)
|
||||
|
||||
CSV_EXTRA_COLUMN = (
|
||||
"anrede_brief,nachname,vorname,ort,personalnr\n"
|
||||
"geehrte Frau,Huber,Andrea,Eisenstadt,12345\n"
|
||||
)
|
||||
|
||||
CSV_NO_DATA = "anrede_brief,nachname,vorname,ort\n"
|
||||
|
||||
CSV_NO_HEADER = ""
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def csv_valid_bytes() -> bytes:
|
||||
return CSV_VALID.encode("utf-8")
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def csv_uploaded_valid(csv_valid_bytes) -> SimpleUploadedFile:
|
||||
return SimpleUploadedFile(
|
||||
"recipients.csv", csv_valid_bytes, content_type="text/csv"
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Django-User / Client
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
@pytest.fixture
|
||||
def user(db):
|
||||
User = get_user_model()
|
||||
return User.objects.create_user(
|
||||
username="testuser",
|
||||
password="topsecret-123",
|
||||
email="test@example.com",
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def auth_client(client, user):
|
||||
client.force_login(user)
|
||||
return client
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# LetterTemplate-Fixture (DB-persistent, mit echter DOCX-Datei)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
@pytest.fixture
|
||||
def letter_template(db, user, docx_bytes, tmp_path, settings):
|
||||
"""Erzeugt ein LetterTemplate-DB-Objekt inkl. hochgeladener DOCX-Datei."""
|
||||
from mailmerge.models import LetterTemplate
|
||||
|
||||
# MEDIA_ROOT auf tmp_path lenken, damit Testdateien isoliert bleiben
|
||||
settings.MEDIA_ROOT = str(tmp_path / "media")
|
||||
|
||||
tpl = LetterTemplate(
|
||||
name="Testvorlage",
|
||||
description="Pytest-Fixture",
|
||||
created_by=user,
|
||||
)
|
||||
tpl.file.save(
|
||||
"test-template.docx",
|
||||
SimpleUploadedFile("test-template.docx", docx_bytes),
|
||||
save=True,
|
||||
)
|
||||
return tpl
|
||||
@@ -0,0 +1,42 @@
|
||||
"""
|
||||
Integrationstest mit echtem LibreOffice.
|
||||
|
||||
Per Default übersprungen — nur aktiv, wenn pytest explizit mit
|
||||
'-m integration' aufgerufen wird ODER 'soffice' im PATH gefunden wird.
|
||||
|
||||
Beispielaufruf im Container:
|
||||
docker compose exec web pytest -m integration mailmerge/tests/test_preview_integration.py
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import io
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from mailmerge.services.preview import build_preview
|
||||
from mailmerge.tests.conftest import CSV_VALID
|
||||
|
||||
|
||||
pytestmark = [
|
||||
pytest.mark.integration,
|
||||
pytest.mark.skipif(
|
||||
shutil.which("soffice") is None,
|
||||
reason="LibreOffice (soffice) nicht im PATH – Integrationstest übersprungen",
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
def test_real_libreoffice_produces_pdf(docx_file_on_disk):
|
||||
"""End-to-End mit echtem LibreOffice. Erwartet gültige PDF-Bytes."""
|
||||
csv_buf = io.BytesIO(CSV_VALID.encode("utf-8"))
|
||||
result = build_preview(docx_file_on_disk, csv_buf)
|
||||
|
||||
# PDF-Magic-Bytes
|
||||
assert result.pdf_bytes.startswith(b"%PDF-"), (
|
||||
f"Kein PDF zurückbekommen, erste 8 Bytes: {result.pdf_bytes[:8]!r}"
|
||||
)
|
||||
|
||||
# Eine Vorschau sollte mindestens ein paar KB groß sein – sonst stimmt was nicht
|
||||
assert len(result.pdf_bytes) > 500
|
||||
@@ -0,0 +1,173 @@
|
||||
"""
|
||||
Unit-Tests für mailmerge.services.preview.
|
||||
|
||||
LibreOffice wird gemockt – die Tests prüfen nur die Logik:
|
||||
CSV-Parsing, Header-Validierung, Fehlerpfade, Result-Aufbau.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import io
|
||||
from pathlib import Path
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
from mailmerge.services.preview import (
|
||||
PreviewError,
|
||||
PreviewResult,
|
||||
_read_first_row,
|
||||
build_preview,
|
||||
)
|
||||
from mailmerge.tests.conftest import (
|
||||
CSV_EXTRA_COLUMN,
|
||||
CSV_MISSING_COLUMN,
|
||||
CSV_NO_DATA,
|
||||
CSV_NO_HEADER,
|
||||
CSV_VALID,
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# _read_first_row
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class TestReadFirstRow:
|
||||
def test_reads_header_and_first_row(self):
|
||||
f = io.BytesIO(CSV_VALID.encode("utf-8"))
|
||||
columns, row = _read_first_row(f)
|
||||
|
||||
assert columns == ["anrede_brief", "nachname", "vorname", "ort"]
|
||||
assert row["nachname"] == "Huber"
|
||||
assert row["vorname"] == "Andrea"
|
||||
assert row["ort"] == "Eisenstadt"
|
||||
|
||||
def test_strips_utf8_bom(self):
|
||||
"""CSV mit BOM darf nicht zu '\\ufeffanrede_brief' als erstem Header führen."""
|
||||
f = io.BytesIO(("\ufeff" + CSV_VALID).encode("utf-8"))
|
||||
columns, _ = _read_first_row(f)
|
||||
assert columns[0] == "anrede_brief"
|
||||
|
||||
def test_accepts_path(self, tmp_path):
|
||||
p = tmp_path / "r.csv"
|
||||
p.write_text(CSV_VALID, encoding="utf-8")
|
||||
columns, row = _read_first_row(p)
|
||||
assert row["nachname"] == "Huber"
|
||||
assert "ort" in columns
|
||||
|
||||
def test_no_header_raises(self):
|
||||
f = io.BytesIO(CSV_NO_HEADER.encode("utf-8"))
|
||||
with pytest.raises(PreviewError, match="keine Header"):
|
||||
_read_first_row(f)
|
||||
|
||||
def test_no_data_row_raises(self):
|
||||
f = io.BytesIO(CSV_NO_DATA.encode("utf-8"))
|
||||
with pytest.raises(PreviewError, match="keine Datenzeile"):
|
||||
_read_first_row(f)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# build_preview – Header-Validierung
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class TestBuildPreviewValidation:
|
||||
def test_missing_column_is_reported(self, docx_file_on_disk):
|
||||
csv_buf = io.BytesIO(CSV_MISSING_COLUMN.encode("utf-8"))
|
||||
with pytest.raises(PreviewError, match="fehlen aber als Spalte"):
|
||||
build_preview(docx_file_on_disk, csv_buf)
|
||||
|
||||
def test_missing_column_lists_specific_name(self, docx_file_on_disk):
|
||||
csv_buf = io.BytesIO(CSV_MISSING_COLUMN.encode("utf-8"))
|
||||
with pytest.raises(PreviewError) as excinfo:
|
||||
build_preview(docx_file_on_disk, csv_buf)
|
||||
assert "ort" in str(excinfo.value)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# build_preview – Happy Path (mit gemocktem LibreOffice)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
FAKE_PDF_BYTES = b"%PDF-1.4 fake test pdf"
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def patched_libreoffice(monkeypatch, tmp_path):
|
||||
"""Ersetzt docx_to_pdf durch eine Funktion, die eine Dummy-PDF-Datei
|
||||
in das Out-Dir legt und deren Pfad zurückgibt."""
|
||||
def fake_docx_to_pdf(docx_path: Path, out_dir: Path) -> Path:
|
||||
out_dir.mkdir(parents=True, exist_ok=True)
|
||||
pdf_path = out_dir / (docx_path.stem + ".pdf")
|
||||
pdf_path.write_bytes(FAKE_PDF_BYTES)
|
||||
return pdf_path
|
||||
|
||||
monkeypatch.setattr(
|
||||
"mailmerge.services.preview.docx_to_pdf", fake_docx_to_pdf
|
||||
)
|
||||
|
||||
|
||||
class TestBuildPreviewHappyPath:
|
||||
def test_returns_preview_result(self, docx_file_on_disk, patched_libreoffice):
|
||||
csv_buf = io.BytesIO(CSV_VALID.encode("utf-8"))
|
||||
result = build_preview(docx_file_on_disk, csv_buf)
|
||||
|
||||
assert isinstance(result, PreviewResult)
|
||||
assert result.pdf_bytes == FAKE_PDF_BYTES
|
||||
|
||||
def test_first_row_is_used(self, docx_file_on_disk, patched_libreoffice):
|
||||
csv_buf = io.BytesIO(CSV_VALID.encode("utf-8"))
|
||||
result = build_preview(docx_file_on_disk, csv_buf)
|
||||
|
||||
assert result.used_row["nachname"] == "Huber"
|
||||
assert result.used_row["vorname"] == "Andrea"
|
||||
|
||||
def test_placeholders_are_listed(
|
||||
self, docx_file_on_disk, patched_libreoffice, template_placeholders
|
||||
):
|
||||
csv_buf = io.BytesIO(CSV_VALID.encode("utf-8"))
|
||||
result = build_preview(docx_file_on_disk, csv_buf)
|
||||
assert set(result.placeholders) == set(template_placeholders)
|
||||
|
||||
def test_extra_columns_are_reported(self, docx_file_on_disk, patched_libreoffice):
|
||||
csv_buf = io.BytesIO(CSV_EXTRA_COLUMN.encode("utf-8"))
|
||||
result = build_preview(docx_file_on_disk, csv_buf)
|
||||
|
||||
assert "personalnr" in result.extra_columns
|
||||
assert result.missing_columns == []
|
||||
|
||||
def test_calls_libreoffice_exactly_once(
|
||||
self, docx_file_on_disk, monkeypatch, tmp_path
|
||||
):
|
||||
"""Sicherstellen, dass auch wirklich nur EIN Render-Vorgang läuft."""
|
||||
calls = []
|
||||
|
||||
def fake_docx_to_pdf(docx_path: Path, out_dir: Path) -> Path:
|
||||
calls.append(docx_path)
|
||||
out_dir.mkdir(parents=True, exist_ok=True)
|
||||
pdf_path = out_dir / (docx_path.stem + ".pdf")
|
||||
pdf_path.write_bytes(FAKE_PDF_BYTES)
|
||||
return pdf_path
|
||||
|
||||
monkeypatch.setattr(
|
||||
"mailmerge.services.preview.docx_to_pdf", fake_docx_to_pdf
|
||||
)
|
||||
|
||||
csv_buf = io.BytesIO(CSV_VALID.encode("utf-8"))
|
||||
build_preview(docx_file_on_disk, csv_buf)
|
||||
assert len(calls) == 1
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# build_preview – LibreOffice-Fehler propagieren
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class TestBuildPreviewLibreOfficeFailure:
|
||||
def test_runtime_error_bubbles_up(self, docx_file_on_disk, monkeypatch):
|
||||
def fake_docx_to_pdf(docx_path, out_dir):
|
||||
raise RuntimeError("LibreOffice ist abgestürzt")
|
||||
|
||||
monkeypatch.setattr(
|
||||
"mailmerge.services.preview.docx_to_pdf", fake_docx_to_pdf
|
||||
)
|
||||
|
||||
csv_buf = io.BytesIO(CSV_VALID.encode("utf-8"))
|
||||
with pytest.raises(RuntimeError, match="LibreOffice"):
|
||||
build_preview(docx_file_on_disk, csv_buf)
|
||||
@@ -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
|
||||
+5
-1
@@ -14,4 +14,8 @@ ignore = ["E501", "S101"]
|
||||
[tool.pytest.ini_options]
|
||||
DJANGO_SETTINGS_MODULE = "config.settings.dev"
|
||||
python_files = ["test_*.py", "*_test.py", "tests.py"]
|
||||
addopts = "--reuse-db -ra"
|
||||
# integration-Tests standardmäßig ausschließen, nur via '-m integration' aktiv
|
||||
addopts = "--reuse-db -ra -m 'not integration'"
|
||||
markers = [
|
||||
"integration: Test benötigt echte externe Tools (z.B. LibreOffice)",
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user