Files
2026-09-24 17:57:54 +02:00

141 lines
5.0 KiB
Python

"""Tests für die Moderation der Ehrentafel: CLI-Befehle und Sperrliste für Namen."""
from __future__ import annotations
from collections.abc import Callable
from pathlib import Path
import pytest
from flask import Flask
from flask.testing import FlaskClient
from app import create_app
from app.models import get_db, insert_score, top_scores
from app.validation import is_name_blocked, load_blocklist, normalize_for_blocklist
def _seed(app: Flask, entries: list[tuple[str, int]]) -> list[int]:
with app.app_context():
return [
insert_score(get_db(), name, score, wave=1, duration_s=60) for name, score in entries
]
def _names(app: Flask) -> list[str]:
with app.app_context():
return [entry["name"] for entry in top_scores(get_db(), 100)]
# --------------------------------------------------------------------------- CLI
def test_scores_list_shows_ids_and_names(app: Flask) -> None:
ids = _seed(app, [("Ada", 300), ("Grace", 500)])
result = app.test_cli_runner().invoke(args=["scores", "list"])
assert result.exit_code == 0
lines = result.output.splitlines()
assert "Name" in lines[0]
assert str(ids[1]) in lines[1] and "Grace" in lines[1]
assert str(ids[0]) in lines[2] and "Ada" in lines[2]
def test_scores_list_when_empty(app: Flask) -> None:
result = app.test_cli_runner().invoke(args=["scores", "list"])
assert result.exit_code == 0
assert "Keine Einträge" in result.output
def test_scores_delete_by_id(app: Flask) -> None:
ids = _seed(app, [("Bleibt", 100), ("Weg", 200), ("AuchWeg", 300)])
result = app.test_cli_runner().invoke(
args=["scores", "delete", str(ids[1]), str(ids[2]), "999"]
)
assert result.exit_code == 0
assert "2 Eintrag/Einträge gelöscht" in result.output
assert _names(app) == ["Bleibt"]
def test_scores_delete_requires_ids(app: Flask) -> None:
result = app.test_cli_runner().invoke(args=["scores", "delete"])
assert result.exit_code != 0
def test_scores_delete_name_is_case_insensitive(app: Flask) -> None:
_seed(app, [("Unerwünscht", 100), ("unerwünscht", 200), ("Ada", 300)])
# Auch Umlaute zählen ohne Groß-/Kleinschreibung (SQLites NOCASE könnte das nicht).
result = app.test_cli_runner().invoke(args=["scores", "delete-name", "UNERWÜNSCHT"])
assert result.exit_code == 0
assert "2 Eintrag/Einträge" in result.output
assert _names(app) == ["Ada"]
# --------------------------------------------------------------------- Sperrliste
@pytest.mark.parametrize(
("raw", "expected"),
[
("Max Muster", "maxmuster"),
("M.a.x-Muster!", "maxmuster"),
("MAX", "max"),
("Jürg", "jürg"),
],
)
def test_normalize_for_blocklist(raw: str, expected: str) -> None:
assert normalize_for_blocklist(raw) == expected
def test_load_blocklist_ignores_comments_and_blank_lines(tmp_path: Path) -> None:
path = tmp_path / "sperre.txt"
path.write_text("# Kommentar\n\n Max Muster \nbeleidigung\n#noch einer\n", encoding="utf-8")
assert load_blocklist(path) == ["maxmuster", "beleidigung"]
def test_missing_blocklist_blocks_nothing(tmp_path: Path) -> None:
assert load_blocklist(tmp_path / "gibt-es-nicht.txt") == []
assert is_name_blocked("Irgendwer", []) is False
def test_blocked_terms_cannot_be_bypassed_by_spacing_or_case() -> None:
terms = ["maxmuster"]
assert is_name_blocked("Max Muster", terms)
assert is_name_blocked("xX.MAX.muster.Xx", terms)
assert not is_name_blocked("Max Mustang", terms)
@pytest.fixture()
def blocklist_app(tmp_path: Path) -> Flask:
blocklist = tmp_path / "name_blocklist.txt"
blocklist.write_text("Max Muster\n", encoding="utf-8")
return create_app(
{
"TESTING": True,
"SECRET_KEY": "test-secret-key",
"DATABASE_PATH": str(tmp_path / "db.sqlite3"),
"NAME_BLOCKLIST_PATH": str(blocklist),
}
)
def test_api_rejects_blocked_name(blocklist_app: Flask) -> None:
client: FlaskClient = blocklist_app.test_client()
token = client.post("/api/runs").get_json()["run_token"]
payload = {"name": "max.muster", "score": 10, "wave": 1, "duration_s": 0, "run_token": token}
response = client.post("/api/highscores", json=payload)
assert response.status_code == 400
assert response.get_json()["fields"] == {"name": "Dieser Name ist nicht zulässig."}
assert _names(blocklist_app) == []
def test_api_accepts_other_names_with_blocklist(blocklist_app: Flask) -> None:
client: FlaskClient = blocklist_app.test_client()
token = client.post("/api/runs").get_json()["run_token"]
payload = {"name": "Ada", "score": 10, "wave": 1, "duration_s": 0, "run_token": token}
assert client.post("/api/highscores", json=payload).status_code == 201
def test_api_without_blocklist_file_accepts_names(
client: FlaskClient, valid_payload: Callable[..., dict]
) -> None:
assert client.post("/api/highscores", json=valid_payload(name="Max Muster")).status_code == 201