"""Tests für die FastAPI-Validation-Helper aus app.main. Issue #57 Befund #3 (Path-Traversal via drucksache) und #7 (Query-DoS). Wir importieren die Helper direkt — keine TestClient-Roundtrip nötig, weil die Funktionen reine Validierung sind und die FastAPI-Integration in app.main den richtigen Aufrufpfad bereits vorgibt. """ from __future__ import annotations import pytest from fastapi import HTTPException from app.validators import ( MAX_SEARCH_QUERY_LEN, validate_drucksache, validate_search_query, ) # ───────────────────────────────────────────────────────────────────────────── # validate_drucksache (Befund #3 — Path-Traversal-Härtung) # ───────────────────────────────────────────────────────────────────────────── class TestValidateDrucksache: @pytest.mark.parametrize( "valid", [ "8/6390", "18/12345", "23/3700", "20/4309", "8/6390(neu)", "23/3700-A", ], ) def test_accepts_known_real_formats(self, valid): assert validate_drucksache(valid) == valid @pytest.mark.parametrize( "evil", [ "../../etc/passwd", "8/6390/../../../etc/shadow", "8/6390;rm -rf /", "8/6390?bundesland=NRW", "", "8/6390 OR 1=1", "", "/", "8//6390", "abc/def", "8/abc", ], ) def test_rejects_path_traversal_and_injection(self, evil): with pytest.raises(HTTPException) as exc: validate_drucksache(evil) assert exc.value.status_code == 400 # ───────────────────────────────────────────────────────────────────────────── # validate_search_query (Befund #7 — Query-Length-DoS) # ───────────────────────────────────────────────────────────────────────────── class TestValidateSearchQuery: def test_short_query_passes(self): assert validate_search_query("Klimaschutz") == "Klimaschutz" def test_at_limit_passes(self): q = "x" * MAX_SEARCH_QUERY_LEN assert validate_search_query(q) == q def test_over_limit_rejected(self): q = "x" * (MAX_SEARCH_QUERY_LEN + 1) with pytest.raises(HTTPException) as exc: validate_search_query(q) assert exc.value.status_code == 400 assert "zu lang" in exc.value.detail.lower() def test_none_query_rejected(self): with pytest.raises(HTTPException) as exc: validate_search_query(None) # type: ignore[arg-type] assert exc.value.status_code == 400 def test_empty_string_passes(self): # Eine leere Query ist semantisch ok (Frontend nutzt sie für # "alle Drucksachen") — die Längen-Validierung soll nur den # absurd großen Fall blocken, nicht den leeren. assert validate_search_query("") == ""