159 lines
5.4 KiB
Python
159 lines
5.4 KiB
Python
"""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)
|