"""End-to-End-Test: Reale Input-Datei + reales PDF-Template → PDF-Output für erste Person.""" import sys from pathlib import Path import fitz # PyMuPDF import pytest # Projekt-Root zum Python-Path hinzufügen PROJECT_ROOT = Path(__file__).resolve().parent.parent sys.path.insert(0, str(PROJECT_ROOT)) from src.config import load_settings, load_input_schema, load_output_mapping from src.parser import parse_input_file, group_by_employee, get_unique_employees from src.writer import generate_activity_reports # ────────────────────────────────────────────── # Pfade zu den realen Fixture-Dateien # ────────────────────────────────────────────── CONFIG_DIR = PROJECT_ROOT / "config" INPUT_FILE = PROJECT_ROOT / "input" / "2025 JM.xlsx" TEMPLATE_FILE = PROJECT_ROOT / "template" / "Tätigkeitsnachweise Stellenförderung.pdf" def _load_app_config(): """Lädt die echte App-Konfiguration (Schema, Mapping, Settings).""" config = load_settings(CONFIG_DIR, PROJECT_ROOT) config.input_schema = load_input_schema(CONFIG_DIR) config.output_mapping = load_output_mapping(CONFIG_DIR) return config # ────────────────────────────────────────────── # Fixtures # ────────────────────────────────────────────── @pytest.fixture(scope="module") def app_config(): """Reale App-Konfiguration aus config/.""" return _load_app_config() @pytest.fixture(scope="module") def parsed_records(app_config): """Geparste Records aus der realen Input-Datei.""" return parse_input_file( filepath=INPUT_FILE, schema=app_config.input_schema, month_sheet_pattern=app_config.month_sheet_pattern, skip_empty_name=app_config.skip_empty_name, skip_empty_antragsnummer=app_config.skip_empty_antragsnummer, ) @pytest.fixture def output_dir(tmp_path): """Temporäres Output-Verzeichnis.""" out = tmp_path / "output" out.mkdir() return out # ────────────────────────────────────────────── # Hilfsfunktion: Text aus PDF extrahieren # ────────────────────────────────────────────── def _get_pdf_text(pdf_path: Path) -> str: """Extrahiert den gesamten Text aus einem PDF.""" doc = fitz.open(pdf_path) text = "" for page in doc: text += page.get_text() doc.close() return text def _get_page_text(pdf_path: Path, page_num: int = 0) -> str: """Extrahiert Text von einer bestimmten PDF-Seite.""" doc = fitz.open(pdf_path) text = doc[page_num].get_text() if page_num < doc.page_count else "" doc.close() return text # ────────────────────────────────────────────── # End-to-End Tests # ────────────────────────────────────────────── class TestEndToEndFirstPerson: """End-to-End: Reale Input-Datei + reales PDF-Template → nur erste Person.""" def test_input_file_exists(self): """Stellt sicher, dass die reale Input-Datei vorhanden ist.""" assert INPUT_FILE.exists(), f"Input-Datei nicht gefunden: {INPUT_FILE}" def test_template_file_exists(self): """Stellt sicher, dass das reale PDF-Template vorhanden ist.""" assert TEMPLATE_FILE.exists(), f"PDF-Template nicht gefunden: {TEMPLATE_FILE}" def test_template_has_field_markers(self, app_config): """Stellt sicher, dass alle _Field-Platzhalter im PDF-Template vorhanden sind.""" doc = fitz.open(TEMPLATE_FILE) missing = [] for m in app_config.output_mapping: if m.pdf_field is None: continue # Suche auf allen Seiten found_any = False for page_num in range(doc.page_count): if doc[page_num].search_for(m.pdf_field): found_any = True break if not found_any: missing.append(m.pdf_field) doc.close() assert len(missing) == 0, ( f"Folgende _Field-Platzhalter fehlen im PDF: {missing}" ) def test_parse_real_input(self, parsed_records): """Validiert das Parsen der realen Input-Datei.""" # Sollte 168 Einträge über 12 Monate für 19 Personen ergeben assert len(parsed_records) == 168, ( f"Erwartet 168 Records, aber {len(parsed_records)} gefunden" ) employees = get_unique_employees(parsed_records) assert len(employees) == 19, ( f"Erwartet 19 Mitarbeiter, aber {len(employees)} gefunden" ) # Erste Person alphabetisch: Draßdo, Cornelia assert employees[0] == "Draßdo, Cornelia", ( f"Erwartet 'Draßdo, Cornelia' als erste Person, aber '{employees[0]}'" ) def test_full_pipeline_first_person( self, app_config, parsed_records, output_dir ): """Kompletter Workflow für die erste gefundene Person (Draßdo, Cornelia).""" # 1. Nach Mitarbeiter gruppieren grouped = group_by_employee(parsed_records) assert len(grouped) == 19 # 2. Erste Person alphabetisch first_person = sorted(grouped.keys())[0] assert first_person == "Draßdo, Cornelia" first_records = grouped[first_person] # Draßdo hat 12 Einträge (einen pro Monat) assert len(first_records) == 12, ( f"Erwartet 12 Records für {first_person}, aber {len(first_records)}" ) # 3. Reports NUR für die erste Person generieren single_person_group = {first_person: first_records} generated = generate_activity_reports( employee_records=single_person_group, template_path=TEMPLATE_FILE, mapping=app_config.output_mapping, output_dir=output_dir, filename_pattern=app_config.filename_pattern, ) # 4. Ergebnisse prüfen assert len(generated) == 1, ( f"Erwartet 1 generierte Datei, aber {len(generated)}" ) output_file = generated[0] assert output_file.exists(), f"Output-Datei nicht gefunden: {output_file}" assert output_file.suffix == ".pdf" erwarteter_name = "Draßdo_Cornelia_Tätigkeitsnachweis.pdf" assert output_file.name == erwarteter_name, ( f"Erwartet '{erwarteter_name}', aber '{output_file.name}'" ) # 5. PDF öffnen und Inhalt validieren doc = fitz.open(output_file) assert doc.page_count == 24, ( f"Erwartet 24 Seiten (12 Monate × 2 Seiten), aber {doc.page_count}" ) # 6. Prüfe, dass KEINE _Field-Platzhalter mehr im Output sind full_text = "" for page in doc: full_text += page.get_text() assert "_Field" not in full_text, ( "Es sind noch _Field-Platzhalter im Output-PDF vorhanden!" ) # 7. Prüfe Seite 0 (Januar, Seite 1 – Stammdaten) jan0_text = doc[0].get_text() assert "100740439" in jan0_text, "Antragsnummer fehlt auf Januar-Seite 1" assert "Draßdo, Cornelia" in jan0_text, "Name fehlt auf Januar-Seite 1" assert "50" in jan0_text, "Stellenanteil fehlt auf Januar-Seite 1" assert "Meisterabschluß Küche" in jan0_text, "Qualifikation fehlt auf Januar-Seite 1" assert "01.2025" in jan0_text, "Monat fehlt auf Januar-Seite 1" assert "Anleiterin / Ausbilderin" in jan0_text, "Funktion fehlt auf Januar-Seite 1" assert "Tätigkeiten im Praxisbereich Garten" in jan0_text, "Tätigkeit fehlt auf Januar-Seite 1" # 8. Prüfe Seite 1 (Januar, Seite 2 – Unterschriftsblock mit Ort/Datum) jan1_text = doc[1].get_text() assert "100740439" not in jan1_text, "Antragsnummer sollte nicht auf Seite 2 sein" assert "Dresden" in jan1_text, "Ort 'Dresden' fehlt auf Januar-Seite 2" assert "31.01.2025" in jan1_text, "Datum '31.01.2025' fehlt auf Januar-Seite 2" # Tätigkeit wiederholt auf Seite 2 assert "Tätigkeiten im Praxisbereich Garten" in jan1_text, "Tätigkeit fehlt auf Januar-Seite 2" # 9. Prüfe Juli (Seite 12+13) – Stellenanteil wechselt auf 40% jul0_text = doc[12].get_text() assert "40" in jul0_text, "Stellenanteil 40% fehlt auf Juli-Seite 1" assert "07.2025" in jul0_text, "Monat 07.2025 fehlt auf Juli-Seite 1" jul1_text = doc[13].get_text() assert "31.07.2025" in jul1_text, "Datum 31.07.2025 fehlt auf Juli-Seite 2" doc.close()