diff --git a/src/utils/__init__.py b/src/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/utils/validators.py b/src/utils/validators.py new file mode 100644 index 0000000..8628e4a --- /dev/null +++ b/src/utils/validators.py @@ -0,0 +1,90 @@ +"""Validierungs-Helfer für Benutzereingaben.""" + +import re + +MIN_TELEFON_ZIFFERN: int = 10 +MIN_SPIELNAME_LAENGE: int = 2 +MAX_SPIELNAME_LAENGE: int = 200 +MIN_SPIELERANZAHL: int = 1 +MAX_SPIELERANZAHL: int = 20 + + +def validiere_telefonnummer(telefon: str) -> str: + """Validiert und normalisiert eine Telefonnummer. + + Args: + telefon: Telefonnummer im beliebigen Format, z.B. '+491****7890', + '+49 123 4567890', '00491234567890'. + '*' wird als Maskierungs-Platzhalter behandelt (>=1 Ziffer). + + Returns: + Bereinigte Telefonnummer, die mit '+' beginnt, '*' als Platzhalter. + + Raises: + ValueError: Wenn weniger als 10 Ziffern oder kein '+' nach Bereinigung. + """ + # Ziffern zählen (* als Platzhalter für >=1 Ziffer) + ziffern_count = len(re.findall(r"\d|\*", telefon)) + + # Alles außer +, * und Ziffern entfernen + bereinigt = re.sub(r"[^\d*+]", "", telefon) + + # "00" am Anfang zu "+" konvertieren + if bereinigt.startswith("00"): + bereinigt = "+" + bereinigt[2:] + + # Muss mit + beginnen (nach Bereinigung) + if not bereinigt.startswith("+"): + raise ValueError(f"Telefonnummer muss mit + beginnen: {telefon}") + + # Mindestens 10 Ziffern (* zählt als eine) + if ziffern_count < MIN_TELEFON_ZIFFERN: + raise ValueError(f"Telefonnummer muss mindestens 10 Ziffern haben: {telefon}") + + return bereinigt + + +def validiere_spiel_name(name: str) -> str: + """Validiert einen Spielnamen. + + Args: + name: Spielname mit 2-200 Zeichen. + + Returns: + Bereinigter Spielname ohne HTML-Tags, mit gestrippten Rändern. + + Raises: + ValueError: Wenn weniger als 2 oder mehr als 200 Zeichen. + """ + # HTML-Tags entfernen + without_tags = re.sub(r"<[^>]*>", "", name) + stripped = without_tags.strip() + + if len(stripped) < MIN_SPIELNAME_LAENGE: + raise ValueError(f"Spielname muss mindestens 2 Zeichen haben: {name}") + if len(stripped) > MAX_SPIELNAME_LAENGE: + raise ValueError(f"Spielname darf maximal 200 Zeichen haben: {name}") + + return stripped + + +def validiere_spieleranzahl(anzahl: int) -> int: + """Validiert eine Spieleranzahl. + + Args: + anzahl: Anzahl der Spieler (1-20). + + Returns: + Die unveränderte Spieleranzahl. + + Raises: + ValueError: Wenn < 1, > 20 oder kein Integer. + """ + if not isinstance(anzahl, int) or isinstance(anzahl, bool): + raise ValueError(f"Spieleranzahl muss eine Ganzzahl sein: {anzahl}") + if anzahl < MIN_SPIELERANZAHL: + raise ValueError(f"Spieleranzahl muss mindestens 1 sein: {anzahl}") + if anzahl > MAX_SPIELERANZAHL: + raise ValueError(f"Spieleranzahl darf maximal 20 sein: {anzahl}") + + return anzahl diff --git a/tests/test_validators.py b/tests/test_validators.py new file mode 100644 index 0000000..b64036a --- /dev/null +++ b/tests/test_validators.py @@ -0,0 +1,61 @@ +"""Tests für die Input-Validatoren.""" + +import pytest + +from src.utils.validators import ( + validiere_spieleranzahl, + validiere_spiel_name, + validiere_telefonnummer, +) + + +# --- Telefonnummer-Tests --- + +def test_gueltige_telefonnummer_wird_bereinigt() -> None: + """Gültige Telefonnummer → bereinigt.""" + # 13 Ziffern, Ergebnis ist +49 + 10 Ziffern + assert validiere_telefonnummer("00491234567890") == "+491234567890" + # Leerzeichen werden entfernt + assert validiere_telefonnummer("+49 123 4567890") == "+491234567890" + # * als Platzhalter (>=1 Ziffer), Gesamt >= 10 Ziffern + star = "*" * 6 + assert validiere_telefonnummer("+491" + star + "7890") == "+491" + star + "7890" + + +def test_ungueltige_telefonnummer_wirft_value_error() -> None: + """Ungültige Telefonnummer → ValueError.""" + with pytest.raises(ValueError): + validiere_telefonnummer("123") + + +# --- Spielname-Tests --- + +def test_gueltiger_spielname_wird_bereinigt() -> None: + """Gültiger Spielname → bereinigt.""" + assert validiere_spiel_name(" Catan ") == "Catan" + assert validiere_spiel_name("Ticket to Ride") == "Ticket to Ride" + + +def test_html_tags_im_spielname_werden_entfernt() -> None: + """HTML-Tags im Spielnamen → entfernt.""" + result = validiere_spiel_name("Catan") + assert "b>" not in result + assert "b" not in result.split(">") + assert "Catan" in result + + +# --- Spieleranzahl-Tests --- + +def test_gueltige_spieleranzahl_unveraendert() -> None: + """Gültige Spieleranzahl → unverändert.""" + assert validiere_spieleranzahl(1) == 1 + assert validiere_spieleranzahl(4) == 4 + assert validiere_spieleranzahl(20) == 20 + + +def test_ungueltige_spieleranzahl_wirft_value_error() -> None: + """Ungültige Spieleranzahl → ValueError.""" + with pytest.raises(ValueError): + validiere_spieleranzahl(0) + with pytest.raises(ValueError): + validiere_spieleranzahl(21)