slop game oder so
This commit is contained in:
@@ -0,0 +1,127 @@
|
||||
"""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()
|
||||
Reference in New Issue
Block a user