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

128 lines
4.8 KiB
Python

"""Tests für die HTML-Seiten, den Health-Check und allgemeine HTTP-Eigenschaften."""
from __future__ import annotations
from pathlib import Path
from flask.testing import FlaskClient
from app import create_app
def test_index_serves_game_page(client: FlaskClient) -> None:
response = client.get("/")
assert response.status_code == 200
html = response.get_data(as_text=True)
assert 'id="game"' in html
assert "js/lib/phaser.min.js" in html
assert 'type="module"' in html
# Der Disclaimer-Text ist für die Einblendung im Spiel eingebettet.
assert "Satire und reine Fiktion" in html
def test_index_passes_urls_to_frontend(client: FlaskClient) -> None:
html = client.get("/").get_data(as_text=True)
assert 'data-api-highscores="/api/highscores"' in html
assert 'data-api-runs="/api/runs"' in html
assert 'data-static-base="/static/"' in html
def test_index_respects_mount_prefix(client: FlaskClient) -> None:
"""Unter cPanel kann die App unter einer Unter-URL laufen (SCRIPT_NAME)."""
html = client.get("/", base_url="http://localhost/glory").get_data(as_text=True)
assert 'data-api-highscores="/glory/api/highscores"' in html
assert 'data-static-base="/glory/static/"' in html
assert 'src="/glory/static/js/main.js"' in html
def test_disclaimer_page(client: FlaskClient) -> None:
response = client.get("/disclaimer")
assert response.status_code == 200
html = response.get_data(as_text=True)
for phrase in (
"Satire und reine Fiktion",
"frei erfunden",
"nicht beabsichtigt",
"keine politische Position",
):
assert phrase in html
assert 'href="/"' in html
def test_healthz(client: FlaskClient) -> None:
response = client.get("/healthz")
assert response.status_code == 200
assert response.get_json() == {"status": "ok"}
def test_security_headers_present(client: FlaskClient) -> None:
response = client.get("/")
csp = response.headers["Content-Security-Policy"]
assert "script-src 'self'" in csp
assert "object-src 'none'" in csp
assert response.headers["X-Content-Type-Options"] == "nosniff"
def test_unknown_page_returns_404_html(client: FlaskClient) -> None:
response = client.get("/gibt-es-nicht")
assert response.status_code == 404
assert response.mimetype == "text/html"
def test_unknown_api_route_returns_json_404(client: FlaskClient) -> None:
response = client.get("/api/gibt-es-nicht")
assert response.status_code == 404
assert response.is_json
assert response.get_json()["error"] == "not_found"
def test_wrong_method_on_api_returns_json_405(client: FlaskClient) -> None:
response = client.delete("/api/highscores")
assert response.status_code == 405
assert response.get_json()["error"] == "method_not_allowed"
def test_forwarded_headers_are_ignored_without_trusted_proxy(client: FlaskClient) -> None:
"""Ohne GLORY_TRUST_PROXY kann ein Client sein Rate-Limit nicht per Header umgehen."""
for i in range(5):
client.post("/api/highscores", json={}, headers={"X-Forwarded-For": f"198.51.100.{i}"})
blocked = client.post("/api/highscores", json={}, headers={"X-Forwarded-For": "198.51.100.99"})
assert blocked.status_code == 429
def test_trusted_proxy_uses_client_ip_but_not_host_or_prefix(tmp_path: Path) -> None:
app = create_app(
{
"TESTING": True,
"SECRET_KEY": "k",
"DATABASE_PATH": str(tmp_path / "db.sqlite3"),
"TRUST_PROXY": True,
}
)
client = app.test_client()
# Host und Präfix aus Headern dürfen nicht in die erzeugten URLs gelangen.
html = client.get(
"/", headers={"X-Forwarded-Prefix": "//evil.example", "X-Forwarded-Host": "evil.example"}
).get_data(as_text=True)
assert "evil.example" not in html
assert 'src="/static/js/main.js"' in html
# Die weitergeleitete Client-IP zählt dagegen fürs Rate-Limit.
for _ in range(5):
client.post("/api/highscores", json={}, headers={"X-Forwarded-For": "198.51.100.1"})
first = client.post("/api/highscores", json={}, headers={"X-Forwarded-For": "198.51.100.1"})
other = client.post("/api/highscores", json={}, headers={"X-Forwarded-For": "198.51.100.2"})
assert first.status_code == 429
assert other.status_code == 400
def test_api_responses_keep_field_order(client: FlaskClient) -> None:
body = client.post("/api/highscores", json={}).get_data(as_text=True)
assert body.index('"error"') < body.index('"message"') < body.index('"fields"')
def test_static_phaser_and_main_are_served(client: FlaskClient) -> None:
for path in ("/static/js/lib/phaser.min.js", "/static/js/main.js", "/static/css/site.css"):
response = client.get(path)
assert response.status_code == 200, path
response.close()