Alle *.pdf im template-Ordner werden ausgeliefert; Dateien mit '_' oder '.' am Anfang sowie andere Dateitypen werden ignoriert. Veraltete PDFs im Build-Ordner werden entfernt. Tests entsprechend angepasst. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
122 lines
4.9 KiB
Python
122 lines
4.9 KiB
Python
"""End-to-end checks using the fixed 2026 workbook and the PDF template."""
|
|
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
import fitz
|
|
import pytest
|
|
|
|
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
|
|
|
|
CONFIG_DIR = PROJECT_ROOT / "config"
|
|
INPUT_FILE = Path(__file__).resolve().parent / "fixture" / "2026 JM.xlsx"
|
|
TEMPLATE_FILE = PROJECT_ROOT / "template" / "06-2026 Tätigkeitsnachweise Stellenförderung.pdf"
|
|
|
|
|
|
@pytest.fixture(scope="module")
|
|
def app_config():
|
|
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
|
|
|
|
|
|
@pytest.fixture(scope="module")
|
|
def parsed_records(app_config):
|
|
assert INPUT_FILE.is_file(), f"Testdatei nicht gefunden: {INPUT_FILE}"
|
|
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):
|
|
out = tmp_path / "output"
|
|
out.mkdir()
|
|
return out
|
|
|
|
|
|
class TestEndToEndFirstPerson:
|
|
def test_input_file_exists(self):
|
|
assert INPUT_FILE.is_file(), f"Testdatei nicht gefunden: {INPUT_FILE}"
|
|
|
|
def test_template_file_exists(self):
|
|
assert TEMPLATE_FILE.is_file(), f"PDF-Template nicht gefunden: {TEMPLATE_FILE}"
|
|
|
|
def test_template_has_field_markers(self, app_config):
|
|
with fitz.open(TEMPLATE_FILE) as doc:
|
|
missing = [
|
|
mapping.pdf_field
|
|
for mapping in app_config.output_mapping
|
|
if mapping.pdf_field is not None
|
|
and not any(page.search_for(mapping.pdf_field) for page in doc)
|
|
]
|
|
assert not missing, f"Folgende _Field-Platzhalter fehlen im PDF: {missing}"
|
|
|
|
def test_parse_real_input(self, parsed_records):
|
|
# Fixed expectations for tests/fixture/2026 JM.xlsx.
|
|
assert len(parsed_records) == 100
|
|
employees = get_unique_employees(parsed_records)
|
|
assert len(employees) == 17
|
|
assert employees[0] == "Bardaus, Madalina"
|
|
first_records = group_by_employee(parsed_records)[employees[0]]
|
|
assert [record.monat for record in first_records] == [
|
|
"01.2026", "02.2026", "03.2026", "04.2026", "05.2026"
|
|
]
|
|
for record in first_records:
|
|
assert record.antragsnummer == "100781516"
|
|
assert record.funktion == "Psychologin"
|
|
assert record.stellenanteil == "50"
|
|
assert record.qualifikation == ""
|
|
assert record.taetigkeit.startswith("Psychologische Betreuung")
|
|
|
|
def test_full_pipeline_first_person(self, app_config, parsed_records, output_dir):
|
|
grouped = group_by_employee(parsed_records)
|
|
assert len(grouped) == 17
|
|
first_person = sorted(grouped)[0]
|
|
assert first_person == "Bardaus, Madalina"
|
|
assert len(grouped[first_person]) == 5
|
|
generated = generate_activity_reports(
|
|
employee_records={first_person: grouped[first_person]},
|
|
template_path=TEMPLATE_FILE,
|
|
mapping=app_config.output_mapping,
|
|
output_dir=output_dir,
|
|
filename_pattern=app_config.filename_pattern,
|
|
fontsize=app_config.fontsize,
|
|
)
|
|
assert len(generated) == 1
|
|
output_file = generated[0]
|
|
assert output_file.is_file()
|
|
assert output_file.name == "Bardaus_Madalina_Tätigkeitsnachweis.pdf"
|
|
with fitz.open(output_file) as doc:
|
|
assert doc.page_count == 10 # Five months, two pages per month.
|
|
assert "_Field" not in "".join(page.get_text() for page in doc)
|
|
# Independently specified month ends include February's 28 days.
|
|
expected_months = [
|
|
("01.2026", "31.01.2026"),
|
|
("02.2026", "28.02.2026"),
|
|
("03.2026", "31.03.2026"),
|
|
("04.2026", "30.04.2026"),
|
|
("05.2026", "31.05.2026"),
|
|
]
|
|
for index, (month, month_end) in enumerate(expected_months):
|
|
data_text = doc[index * 2].get_text()
|
|
signature_text = doc[index * 2 + 1].get_text()
|
|
for value in [
|
|
"100781516", "Bardaus, Madalina", "50,00", month,
|
|
"Psychologin", "Psychologische Betreuung",
|
|
]:
|
|
assert value in data_text, f"{value!r} fehlt für {month}"
|
|
assert "100781516" not in signature_text
|
|
assert "Dresden" in signature_text
|
|
assert month_end in signature_text
|