"""End-to-End-Test: Reale Input-Datei + reales Template → Excel-Output für erste Person.""" import sys from pathlib import Path import pytest from openpyxl import load_workbook # 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.xlsx" 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 # ────────────────────────────────────────────── # End-to-End Tests # ────────────────────────────────────────────── class TestEndToEndFirstPerson: """End-to-End: Reale Input-Datei + reales 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 Template vorhanden ist.""" assert TEMPLATE_FILE.exists(), f"Template nicht gefunden: {TEMPLATE_FILE}" 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 == ".xlsx" erwarteter_name = "Draßdo_Cornelia_Tätigkeitsnachweis.xlsx" assert output_file.name == erwarteter_name, ( f"Erwartet '{erwarteter_name}', aber '{output_file.name}'" ) # 5. Output-Datei öffnen und Inhalt validieren wb = load_workbook(output_file, data_only=True) # 12 sichtbare Sheets, Rest (36 - 12 = 24) ausgeblendet visible_sheets = [s for s in wb.worksheets if s.sheet_state == "visible"] assert len(visible_sheets) == 12, ( f"Erwartet 12 sichtbare Sheets, aber {len(visible_sheets)}" ) # Prüfe Sheet-Namen für alle 12 Monate expected_titles = { "01_Januar_Anl", "02_Februar_Anl", "03_März_Anl", "04_April_Anl", "05_Mai_Anl", "06_Juni_Anl", "07_Juli_Anl", "08_August_Anl", "09_September_Anl", "10_Oktober_Anl", "11_November_Anl", "12_Dezember_Anl", } actual_titles = {ws.title for ws in visible_sheets} assert actual_titles == expected_titles, ( f"Erwartet {expected_titles},\n aber {actual_titles}" ) # Prüfe exemplarisch das Januar-Sheet (01_Januar_Anl) jan = wb["01_Januar_Anl"] assert jan["B3"].value == "100740439", ( f"Erwartet '100740439' in B3, aber '{jan['B3'].value}'" ) assert jan["B17"].value == "Draßdo, Cornelia", ( f"Erwartet 'Draßdo, Cornelia' in B17, aber '{jan['B17'].value}'" ) # Stellenanteil: 0.5 → vom Parser zu "50" konvertiert (0 < x ≤ 1 → *100) assert jan["R17"].value == "50", ( f"Erwartet '50' in R17, aber '{jan['R17'].value}'" ) assert jan["B21"].value == "Meisterabschluß Küche", ( f"Falsche Qualifikation in B21: '{jan['B21'].value}'" ) assert jan["R21"].value == "01.2025", ( f"Erwartet '01.2025' in R21, aber '{jan['R21'].value}'" ) assert jan["B25"].value == "Anleiterin / Ausbilderin", ( f"Falsche Funktion in B25: '{jan['B25'].value}'" ) # Tätigkeit beginnt mit "Tätigkeiten im Praxisbereich Garten…" taetigkeit = jan["B31"].value or "" assert taetigkeit.startswith("Tätigkeiten im Praxisbereich Garten"), ( f"Tätigkeit in B31 beginnt nicht wie erwartet: '{taetigkeit[:60]}…'" ) # Ort wird hartkodiert auf "Dresden" gesetzt assert jan["B95"].value == "Dresden", ( f"Erwartet 'Dresden' in B95, aber '{jan['B95'].value}'" ) # Datum: letzter Tag des Monats Januar → 31.01.2025 assert jan["J95"].value == "31.01.2025", ( f"Erwartet '31.01.2025' in J95, aber '{jan['J95'].value}'" ) # Prüfe exemplarisch das Juli-Sheet (Stellenanteil wechselt auf 40%) jul = wb["07_Juli_Anl"] assert jul["R17"].value == "40", ( f"Erwartet '40' in R17 (Juli), aber '{jul['R17'].value}'" ) assert jul["R21"].value == "07.2025", ( f"Erwartet '07.2025' in R21, aber '{jul['R21'].value}'" ) # Datum Juli → 31.07.2025 assert jul["J95"].value == "31.07.2025", ( f"Erwartet '31.07.2025' in J95, aber '{jul['J95'].value}'" ) # Prüfe Dezember-Sheet (Datum: 31.12.2025) dez = wb["12_Dezember_Anl"] assert dez["J95"].value == "31.12.2025", ( f"Erwartet '31.12.2025' in J95, aber '{dez['J95'].value}'" ) wb.close()