"""Tests für Namensbereinigung und Plausibilitätsprüfung (ohne Flask).""" from __future__ import annotations import pytest from app.config import GAME_RULE_DEFAULTS from app.validation import ( NAME_MAX_LENGTH, PlausibilityRules, ScoreSubmission, SubmissionError, check_plausibility, parse_submission, sanitize_name, ) RULES = PlausibilityRules.from_config(GAME_RULE_DEFAULTS) @pytest.mark.parametrize( ("raw", "expected"), [ ("Ada", "Ada"), (" Max Muster ", "Max Muster"), ("Jürg Größli", "Jürg Größli"), ("Fullwidth", "Fullwidth"), ("Null\x00Byte", "NullByte"), ("Zero\u200bWidth", "ZeroWidth"), ("Bidi\u202eTrick", "BidiTrick"), ("Tab\tNeu\nZeile", "TabNeuZeile"), ("", "scriptalert1script"), ("O'Neil-Test!?", "O'Neil-Test!?"), ("Emoji 🚀 Held", "Emoji Held"), ("", ""), ], ) def test_sanitize_name(raw: str, expected: str) -> None: assert sanitize_name(raw) == expected def _payload(**overrides: object) -> dict: payload: dict = {"name": "Ada", "score": 100, "wave": 2, "duration_s": 60, "run_token": "t"} payload.update(overrides) return payload def test_parse_submission_valid() -> None: submission = parse_submission(_payload(name=" Ada "), RULES) assert submission == ScoreSubmission("Ada", 100, 2, 60, "t") def test_parse_submission_name_exactly_max_length() -> None: name = "N" * NAME_MAX_LENGTH assert parse_submission(_payload(name=name), RULES).name == name def test_parse_submission_collects_errors() -> None: with pytest.raises(SubmissionError) as info: parse_submission(_payload(name="", score=-1, wave=0), RULES) assert set(info.value.fields) == {"name", "score", "wave"} def _submission(score: int, wave: int, duration_s: int) -> ScoreSubmission: return ScoreSubmission("Ada", score, wave, duration_s, "t") def test_plausible_submission() -> None: assert check_plausibility(_submission(2_000, 3, 120), elapsed_s=150, rules=RULES) is None def test_duration_may_be_shorter_than_elapsed_because_of_pauses() -> None: assert check_plausibility(_submission(500, 2, 60), elapsed_s=3_000, rules=RULES) is None def test_duration_longer_than_elapsed_is_implausible() -> None: assert check_plausibility(_submission(500, 2, 600), elapsed_s=100, rules=RULES) is not None def test_duration_within_tolerance_is_accepted() -> None: tolerance = RULES.duration_tolerance_seconds assert check_plausibility(_submission(500, 2, 100 + tolerance), 100, RULES) is None def test_score_ceiling_depends_on_duration() -> None: ceiling = RULES.score_flat_allowance + 100 * RULES.score_max_points_per_second assert check_plausibility(_submission(ceiling, 5, 100), 200, RULES) is None assert check_plausibility(_submission(ceiling + 1, 5, 100), 200, RULES) is not None def test_high_wave_needs_minimum_time() -> None: assert check_plausibility(_submission(10, 50, 10), 500, RULES) is not None min_time = 49 * RULES.wave_min_seconds assert check_plausibility(_submission(10, 50, min_time), min_time + 10, RULES) is None