This repository has been archived on 2026-02-15. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
breakpilot-pwa/backend/tests/test_services/test_pdf_service.py
Benjamin Admin bfdaf63ba9 fix: Restore all files lost during destructive rebase
A previous `git pull --rebase origin main` dropped 177 local commits,
losing 3400+ files across admin-v2, backend, studio-v2, website,
klausur-service, and many other services. The partial restore attempt
(660295e2) only recovered some files.

This commit restores all missing files from pre-rebase ref 98933f5e
while preserving post-rebase additions (night-scheduler, night-mode UI,
NightModeWidget dashboard integration).

Restored features include:
- AI Module Sidebar (FAB), OCR Labeling, OCR Compare
- GPU Dashboard, RAG Pipeline, Magic Help
- Klausur-Korrektur (8 files), Abitur-Archiv (5+ files)
- Companion, Zeugnisse-Crawler, Screen Flow
- Full backend, studio-v2, website, klausur-service
- All compliance SDKs, agent-core, voice-service
- CI/CD configs, documentation, scripts

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 09:51:32 +01:00

483 lines
17 KiB
Python

"""
Tests für den PDF Service.
Testet:
- Elternbrief-Generierung
- Zeugnis-Generierung
- Korrektur-Übersicht-Generierung
Note: These tests require WeasyPrint which needs system libraries (libgobject).
Tests are skipped if WeasyPrint cannot be loaded.
"""
import pytest
import sys
import os
from datetime import datetime
# Add parent directory to path
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
# Mark all tests in this module as requiring WeasyPrint
# These tests will be automatically skipped in CI via conftest.py
pytestmark = pytest.mark.requires_weasyprint
class TestPDFServiceImport:
"""Tests für PDF Service Import und Initialisierung."""
def test_import_pdf_service(self):
"""Test that PDFService can be imported."""
from services.pdf_service import PDFService
assert PDFService is not None
def test_import_data_classes(self):
"""Test that data classes can be imported."""
from services.pdf_service import (
LetterData,
CertificateData,
CorrectionData,
SchoolInfo
)
assert LetterData is not None
assert CertificateData is not None
assert CorrectionData is not None
assert SchoolInfo is not None
def test_import_convenience_functions(self):
"""Test that convenience functions can be imported."""
from services.pdf_service import (
generate_letter_pdf,
generate_certificate_pdf,
generate_correction_pdf,
get_pdf_service
)
assert callable(generate_letter_pdf)
assert callable(generate_certificate_pdf)
assert callable(generate_correction_pdf)
assert callable(get_pdf_service)
class TestPDFServiceInitialization:
"""Tests für PDF Service Initialisierung."""
def test_create_pdf_service_instance(self):
"""Test creating a PDFService instance."""
from services.pdf_service import PDFService
service = PDFService()
assert service is not None
assert service.templates_dir.exists()
def test_get_pdf_service_singleton(self):
"""Test that get_pdf_service returns a singleton."""
from services.pdf_service import get_pdf_service
service1 = get_pdf_service()
service2 = get_pdf_service()
assert service1 is service2
class TestLetterPDFGeneration:
"""Tests für Elternbrief-PDF-Generierung."""
def test_generate_simple_letter(self):
"""Test generating a simple letter PDF."""
from services.pdf_service import PDFService, LetterData
service = PDFService()
letter_data = LetterData(
recipient_name="Familie Müller",
recipient_address="Musterstraße 1\n12345 Musterstadt",
student_name="Max Müller",
student_class="5a",
subject="Einladung zum Elternsprechtag",
content="Sehr geehrte Familie Müller,\n\nhiermit laden wir Sie herzlich zum Elternsprechtag ein.",
date="15.01.2025",
teacher_name="Frau Schmidt",
teacher_title="Klassenlehrerin",
letter_type="elternabend",
tone="professional"
)
pdf_bytes = service.generate_letter_pdf(letter_data)
assert pdf_bytes is not None
assert len(pdf_bytes) > 0
# PDF magic number check
assert pdf_bytes[:4] == b'%PDF'
def test_generate_letter_with_school_info(self):
"""Test generating letter with school information."""
from services.pdf_service import PDFService, LetterData, SchoolInfo
service = PDFService()
school_info = SchoolInfo(
name="Musterschule",
address="Schulweg 10, 12345 Musterstadt",
phone="0123-456789",
email="info@musterschule.de",
website="www.musterschule.de",
principal="Dr. Hans Meier"
)
letter_data = LetterData(
recipient_name="Familie Schmidt",
recipient_address="Hauptstraße 5\n12345 Musterstadt",
student_name="Lisa Schmidt",
student_class="7b",
subject="Halbjahresbericht",
content="Sehr geehrte Eltern,\n\nanbei erhalten Sie den Halbjahresbericht.",
date="20.01.2025",
teacher_name="Herr Weber",
school_info=school_info,
letter_type="halbjahr",
tone="formal"
)
pdf_bytes = service.generate_letter_pdf(letter_data)
assert pdf_bytes is not None
assert len(pdf_bytes) > 0
assert pdf_bytes[:4] == b'%PDF'
def test_generate_letter_with_legal_references(self):
"""Test generating letter with legal references."""
from services.pdf_service import PDFService, LetterData
service = PDFService()
letter_data = LetterData(
recipient_name="Familie Braun",
recipient_address="Gartenstraße 20\n12345 Musterstadt",
student_name="Tim Braun",
student_class="8c",
subject="Fehlzeiten",
content="Sehr geehrte Eltern,\n\nwir möchten Sie über die Fehlzeiten informieren.",
date="25.01.2025",
teacher_name="Frau Lehmann",
letter_type="fehlzeiten",
tone="concerned",
legal_references=[
{"law": "SchulG NRW", "paragraph": "§ 42", "title": "Pflichten der Eltern"},
{"law": "SchulG NRW", "paragraph": "§ 43", "title": "Schulpflicht"}
],
gfk_principles_applied=["Beobachtung", "Bedürfnis", "Bitte"]
)
pdf_bytes = service.generate_letter_pdf(letter_data)
assert pdf_bytes is not None
assert len(pdf_bytes) > 0
assert pdf_bytes[:4] == b'%PDF'
def test_generate_letter_convenience_function(self):
"""Test the convenience function for letter generation."""
from services.pdf_service import generate_letter_pdf
letter_dict = {
"recipient_name": "Familie Test",
"recipient_address": "Testweg 1\n12345 Teststadt",
"student_name": "Test Kind",
"student_class": "3a",
"subject": "Test-Brief",
"content": "Dies ist ein Testbrief.",
"date": "01.02.2025",
"teacher_name": "Herr Test"
}
pdf_bytes = generate_letter_pdf(letter_dict)
assert pdf_bytes is not None
assert len(pdf_bytes) > 0
assert pdf_bytes[:4] == b'%PDF'
class TestCertificatePDFGeneration:
"""Tests für Zeugnis-PDF-Generierung."""
def test_generate_halbjahreszeugnis(self):
"""Test generating a half-year certificate."""
from services.pdf_service import PDFService, CertificateData
service = PDFService()
cert_data = CertificateData(
student_name="Anna Beispiel",
student_birthdate="15.05.2012",
student_class="6b",
school_year="2024/2025",
certificate_type="halbjahr",
subjects=[
{"name": "Deutsch", "grade": "2", "points": None},
{"name": "Mathematik", "grade": "2", "points": None},
{"name": "Englisch", "grade": "1", "points": None},
{"name": "Geschichte", "grade": "2", "points": None},
{"name": "Biologie", "grade": "3", "points": None},
{"name": "Sport", "grade": "1", "points": None},
],
attendance={"days_absent": 5, "days_excused": 4, "days_unexcused": 1},
class_teacher="Frau Mustermann",
principal="Dr. Hans Direktor",
issue_date="31.01.2025",
social_behavior="B",
work_behavior="A"
)
pdf_bytes = service.generate_certificate_pdf(cert_data)
assert pdf_bytes is not None
assert len(pdf_bytes) > 0
assert pdf_bytes[:4] == b'%PDF'
def test_generate_jahreszeugnis(self):
"""Test generating a full-year certificate."""
from services.pdf_service import PDFService, CertificateData
service = PDFService()
cert_data = CertificateData(
student_name="Peter Schüler",
student_birthdate="20.03.2011",
student_class="7a",
school_year="2024/2025",
certificate_type="jahres",
subjects=[
{"name": "Deutsch", "grade": "3", "points": None},
{"name": "Mathematik", "grade": "2", "points": None},
{"name": "Englisch", "grade": "2", "points": None},
],
attendance={"days_absent": 10, "days_excused": 10, "days_unexcused": 0},
remarks="Versetzung in die Klasse 8a.",
class_teacher="Herr Lehrer",
principal="Frau Direktorin",
issue_date="15.07.2025"
)
pdf_bytes = service.generate_certificate_pdf(cert_data)
assert pdf_bytes is not None
assert len(pdf_bytes) > 0
assert pdf_bytes[:4] == b'%PDF'
def test_generate_certificate_convenience_function(self):
"""Test the convenience function for certificate generation."""
from services.pdf_service import generate_certificate_pdf
cert_dict = {
"student_name": "Test Schüler",
"student_birthdate": "01.01.2012",
"student_class": "5a",
"school_year": "2024/2025",
"certificate_type": "halbjahr",
"subjects": [
{"name": "Deutsch", "grade": "2"},
{"name": "Mathe", "grade": "3"}
],
"attendance": {"days_absent": 3, "days_excused": 3, "days_unexcused": 0},
"class_teacher": "Herr Test",
"principal": "Frau Test"
}
pdf_bytes = generate_certificate_pdf(cert_dict)
assert pdf_bytes is not None
assert len(pdf_bytes) > 0
assert pdf_bytes[:4] == b'%PDF'
class TestCorrectionPDFGeneration:
"""Tests für Korrektur-PDF-Generierung."""
def test_generate_correction_overview(self):
"""Test generating a correction overview PDF."""
from services.pdf_service import PDFService, CorrectionData, StudentInfo
service = PDFService()
student = StudentInfo(
student_id="student-001",
name="Maria Musterschülerin",
class_name="9a"
)
correction_data = CorrectionData(
student=student,
exam_title="Klassenarbeit Nr. 3",
date="10.01.2025",
subject="Mathematik",
max_points=50,
achieved_points=42,
grade="2",
percentage=84.0,
grade_distribution={"1": 3, "2": 8, "3": 10, "4": 5, "5": 2, "6": 0},
class_average=2.8,
corrections=[
{
"question": "Lineare Gleichungen lösen",
"answer": "",
"points": 10,
"feedback": "Alle Aufgaben korrekt gelöst."
},
{
"question": "Textaufgabe: Geschwindigkeit",
"answer": "",
"points": 12,
"feedback": "Ansatz richtig, kleiner Rechenfehler am Ende."
},
{
"question": "Geometrie: Flächenberechnung",
"answer": "",
"points": 20,
"feedback": "Formeln korrekt angewendet, eine Teilaufgabe fehlt."
}
],
teacher_notes="Insgesamt eine gute Leistung. Weiter so!"
)
pdf_bytes = service.generate_correction_pdf(correction_data)
assert pdf_bytes is not None
assert len(pdf_bytes) > 0
assert pdf_bytes[:4] == b'%PDF'
def test_generate_correction_without_feedback(self):
"""Test generating correction PDF without individual feedback."""
from services.pdf_service import PDFService, CorrectionData, StudentInfo
service = PDFService()
student = StudentInfo(
student_id="student-002",
name="Tom Test",
class_name="10b"
)
correction_data = CorrectionData(
student=student,
exam_title="Vokabeltest",
date="20.01.2025",
subject="Englisch",
max_points=20,
achieved_points=18,
grade="1",
percentage=90.0,
grade_distribution={"1": 5, "2": 10, "3": 8, "4": 2, "5": 0, "6": 0},
class_average=2.3,
corrections=[
{"question": "Teil 1: Vokabeln DE-EN", "answer": "", "points": 9},
{"question": "Teil 2: Vokabeln EN-DE", "answer": "", "points": 9}
]
)
pdf_bytes = service.generate_correction_pdf(correction_data)
assert pdf_bytes is not None
assert len(pdf_bytes) > 0
assert pdf_bytes[:4] == b'%PDF'
def test_generate_correction_convenience_function(self):
"""Test the convenience function for correction generation."""
from services.pdf_service import generate_correction_pdf
correction_dict = {
"student_id": "student-003",
"student_name": "Test Student",
"student_class": "8a",
"exam_title": "Test Klausur",
"date": "15.01.2025",
"subject": "Physik",
"max_points": 30,
"achieved_points": 24,
"grade": "2",
"percentage": 80.0,
"grade_distribution": {"1": 2, "2": 5, "3": 8, "4": 3, "5": 1, "6": 0},
"class_average": 2.9,
"corrections": [
{"question": "Aufgabe 1", "answer": "", "points": 12, "feedback": "Gut gelöst"},
{"question": "Aufgabe 2", "answer": "", "points": 12, "feedback": "Korrekt"}
],
"teacher_notes": "Insgesamt gute Arbeit."
}
pdf_bytes = generate_correction_pdf(correction_dict)
assert pdf_bytes is not None
assert len(pdf_bytes) > 0
assert pdf_bytes[:4] == b'%PDF'
class TestPDFServiceHelpers:
"""Tests für Hilfsfunktionen des PDF Service."""
def test_date_format_filter(self):
"""Test the date format filter."""
from services.pdf_service import PDFService
service = PDFService()
# ISO date format
result = service._date_format("2025-01-15")
assert result == "15.01.2025"
# Empty value
result = service._date_format("")
assert result == ""
# Already formatted
result = service._date_format("15.01.2025")
assert result == "15.01.2025"
def test_grade_color_filter(self):
"""Test the grade color filter."""
from services.pdf_service import PDFService
service = PDFService()
# German grades
assert service._grade_color("1") == "#27ae60"
assert service._grade_color("2") == "#2ecc71"
assert service._grade_color("3") == "#f1c40f"
assert service._grade_color("4") == "#e67e22"
assert service._grade_color("5") == "#e74c3c"
assert service._grade_color("6") == "#c0392b"
# Behavior grades
assert service._grade_color("A") == "#27ae60"
assert service._grade_color("B") == "#2ecc71"
# Unknown grade
assert service._grade_color("X") == "#333333"
class TestPDFTemplates:
"""Tests für PDF Templates."""
def test_templates_directory_created(self):
"""Test that templates directory is created."""
from services.pdf_service import PDFService
from pathlib import Path
service = PDFService()
assert service.templates_dir.exists()
assert service.templates_dir.is_dir()
def test_inline_templates_work(self):
"""Test that inline templates work as fallback."""
from services.pdf_service import PDFService
service = PDFService()
# Test letter template
template_html = service._get_letter_template_html()
assert "{{ data.subject }}" in template_html
assert "{{ data.content" in template_html
# Test certificate template
template_html = service._get_certificate_template_html()
assert "{{ data.student_name }}" in template_html
# data.subjects is used in a for loop, not direct output
assert "data.subjects" in template_html
# Test correction template
template_html = service._get_correction_template_html()
assert "{{ data.exam_title }}" in template_html
# data.corrections is used in a for loop, not direct output
assert "data.corrections" in template_html
# Run tests if executed directly
if __name__ == "__main__":
pytest.main([__file__, "-v"])