128 lines
4.8 KiB
Python
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()
|