"""Tests for src/validators.py — pre-generation validation logic.""" import json import re from pathlib import Path from unittest.mock import MagicMock, patch import pytest from src.validators import validate # --------------------------------------------------------------------------- # Helpers # --------------------------------------------------------------------------- def _write_config(tmp_path: Path, overrides: dict = None) -> Path: """Write a valid config.json into tmp_path, optionally applying overrides.""" cfg = { "antragsnummer": "12345", "ort": "Teststadt", "template_pdf": "template/sab.pdf", "output_dir": "output", "excel_year_pattern": "(19\\d{2}|20\\d{2})", "excel_name_header": "Name, Vorname", "excel_name_column": 1, "excel_seq_column": 0, "excel_day_start_col": 9, "excel_day_end_col": 39, "month_mapping": {"Feb": "02", "Jan": "01"}, "valid_status_codes": ["K", "KK", "E", "F", "P", "U", "A", "V"], "plan_hours_value": "6", "pdf_field_ort": "unterschrift.ort", "pdf_field_erstellungsdatum": "unterschrift.datum", "pdf_pauschale_prefix": "pauschale_5", "pauschale_present_value": "1", } if overrides: cfg.update(overrides) cfg_path = tmp_path / "config.json" cfg_path.write_text(json.dumps(cfg), encoding="utf-8") return cfg_path def _write_dummy_xlsx(tmp_path: Path, name: str = "Anwesenheit_2026.xlsx") -> Path: """Create a minimal valid xlsx file using openpyxl.""" import openpyxl wb = openpyxl.Workbook() ws = wb.active ws.title = "Feb" # Header row ws.cell(row=1, column=2, value="Name, Vorname") # Day headers (columns J–AN = col 10–40 in 1-based) for col in range(10, 41): ws.cell(row=1, column=col, value=col - 9) # day number as header # Person row ws.cell(row=2, column=1, value=1) ws.cell(row=2, column=2, value="Mustermann, Max") ws.cell(row=2, column=10, value=6.0) # day 1 → present 6h xlsx_path = tmp_path / name wb.save(str(xlsx_path)) return xlsx_path def _write_dummy_pdf(tmp_path: Path) -> Path: """Create a 1-byte placeholder file that passes existence checks.""" pdf_path = tmp_path / "template" / "sab.pdf" pdf_path.parent.mkdir(parents=True, exist_ok=True) pdf_path.write_bytes(b"%PDF-1.4 placeholder") return pdf_path # --------------------------------------------------------------------------- # Tests # --------------------------------------------------------------------------- class TestValidate: def test_all_checks_pass(self, tmp_path): cfg_path = _write_config(tmp_path) xlsx_path = _write_dummy_xlsx(tmp_path) _write_dummy_pdf(tmp_path) # Reset config cache so the test config is used from src.config_loader import reset_config_cache reset_config_cache() errors = validate(xlsx_path, "Feb", cfg_path) assert errors == [] def test_excel_file_missing(self, tmp_path): cfg_path = _write_config(tmp_path) _write_dummy_pdf(tmp_path) from src.config_loader import reset_config_cache reset_config_cache() errors = validate(tmp_path / "nonexistent.xlsx", "Feb", cfg_path) assert any("nicht gefunden" in e for e in errors) def test_year_not_in_filename(self, tmp_path): cfg_path = _write_config(tmp_path) xlsx_path = _write_dummy_xlsx(tmp_path, name="Anwesenheit_kein_jahr.xlsx") _write_dummy_pdf(tmp_path) from src.config_loader import reset_config_cache reset_config_cache() errors = validate(xlsx_path, "Feb", cfg_path) assert any("Jahr" in e for e in errors) def test_unknown_sheet_name(self, tmp_path): cfg_path = _write_config(tmp_path) xlsx_path = _write_dummy_xlsx(tmp_path) _write_dummy_pdf(tmp_path) from src.config_loader import reset_config_cache reset_config_cache() errors = validate(xlsx_path, "Xyz", cfg_path) assert any("month_mapping" in e or "Sheet" in e for e in errors) def test_pdf_template_missing(self, tmp_path): # No _write_dummy_pdf call → template missing cfg_path = _write_config(tmp_path) xlsx_path = _write_dummy_xlsx(tmp_path) from src.config_loader import reset_config_cache reset_config_cache() errors = validate(xlsx_path, "Feb", cfg_path) assert any("Template" in e or "template" in e.lower() for e in errors) def test_config_missing_key(self, tmp_path): cfg = { "antragsnummer": "123", # omit 'ort' and other required keys } cfg_path = tmp_path / "config.json" cfg_path.write_text(json.dumps(cfg), encoding="utf-8") from src.config_loader import reset_config_cache reset_config_cache() errors = validate(tmp_path / "file.xlsx", "Feb", cfg_path) assert any("Konfigurationsfehler" in e or "fehlen" in e.lower() for e in errors) def test_non_xlsx_extension_flagged(self, tmp_path): cfg_path = _write_config(tmp_path) _write_dummy_pdf(tmp_path) csv_path = tmp_path / "Anwesenheit_2026.csv" csv_path.write_text("dummy") from src.config_loader import reset_config_cache reset_config_cache() errors = validate(csv_path, "Feb", cfg_path) assert any(".xlsx" in e or "Excel" in e for e in errors)