aurweb/test/test_routes.py
2022-08-22 22:40:45 +02:00

155 lines
4.9 KiB
Python

import re
import urllib.parse
from http import HTTPStatus
import lxml.etree
import pytest
from fastapi.testclient import TestClient
from aurweb import db
from aurweb.asgi import app
from aurweb.models.account_type import USER_ID
from aurweb.models.user import User
from aurweb.testing.requests import Request
@pytest.fixture(autouse=True)
def setup(db_test):
return
@pytest.fixture
def client() -> TestClient:
yield TestClient(app=app)
@pytest.fixture
def user() -> User:
with db.begin():
user = db.create(
User,
Username="test",
Email="test@example.org",
RealName="Test User",
Passwd="testPassword",
AccountTypeID=USER_ID,
)
yield user
def test_index(client: TestClient):
"""Test the index route at '/'."""
with client as req:
response = req.get("/")
assert response.status_code == int(HTTPStatus.OK)
def test_index_security_headers(client: TestClient):
"""Check for the existence of CSP, XCTO, XFO and RP security headers.
CSP: Content-Security-Policy
XCTO: X-Content-Type-Options
RP: Referrer-Policy
XFO: X-Frame-Options
"""
# Use `with` to trigger FastAPI app events.
with client as req:
response = req.get("/")
assert response.status_code == int(HTTPStatus.OK)
assert response.headers.get("Content-Security-Policy") is not None
assert response.headers.get("X-Content-Type-Options") == "nosniff"
assert response.headers.get("Referrer-Policy") == "same-origin"
assert response.headers.get("X-Frame-Options") == "SAMEORIGIN"
def test_favicon(client: TestClient):
"""Test the favicon route at '/favicon.ico'."""
with client as request:
response1 = request.get("/static/images/favicon.ico")
response2 = request.get("/favicon.ico")
assert response1.status_code == int(HTTPStatus.OK)
assert response1.content == response2.content
def test_language(client: TestClient):
"""Test the language post route as a guest user."""
post_data = {"set_lang": "de", "next": "/"}
with client as req:
response = req.post("/language", data=post_data)
assert response.status_code == int(HTTPStatus.SEE_OTHER)
def test_language_invalid_next(client: TestClient):
"""Test an invalid next route at '/language'."""
post_data = {"set_lang": "de", "next": "https://evil.net"}
with client as req:
response = req.post("/language", data=post_data)
assert response.status_code == int(HTTPStatus.BAD_REQUEST)
def test_user_language(client: TestClient, user: User):
"""Test the language post route as an authenticated user."""
post_data = {"set_lang": "de", "next": "/"}
sid = user.login(Request(), "testPassword")
assert sid is not None
with client as req:
response = req.post("/language", data=post_data, cookies={"AURSID": sid})
assert response.status_code == int(HTTPStatus.SEE_OTHER)
assert user.LangPreference == "de"
def test_language_query_params(client: TestClient):
"""Test the language post route with query params."""
next = urllib.parse.quote_plus("/")
post_data = {"set_lang": "de", "next": "/", "q": f"next={next}"}
q = post_data.get("q")
with client as req:
response = req.post("/language", data=post_data)
assert response.headers.get("location") == f"/?{q}"
assert response.status_code == int(HTTPStatus.SEE_OTHER)
def test_error_messages(client: TestClient):
with client as request:
response1 = request.get("/thisroutedoesnotexist")
response2 = request.get("/raisefivethree")
assert response1.status_code == int(HTTPStatus.NOT_FOUND)
assert response2.status_code == int(HTTPStatus.SERVICE_UNAVAILABLE)
def test_nonce_csp(client: TestClient):
with client as request:
response = request.get("/")
data = response.headers.get("Content-Security-Policy")
nonce = next(field for field in data.split("; ") if "nonce" in field)
match = re.match(r"^script-src .*'nonce-([a-fA-F0-9]{8})' .*$", nonce)
nonce = match.group(1)
assert nonce is not None and len(nonce) == 8
parser = lxml.etree.HTMLParser(recover=True)
root = lxml.etree.fromstring(response.text, parser=parser)
nonce_verified = False
scripts = root.xpath("//script")
for script in scripts:
if script.text is not None:
assert "nonce" in script.keys()
if not (nonce_verified := (script.get("nonce") == nonce)):
break
assert nonce_verified is True
def test_id_redirect(client: TestClient):
with client as request:
response = request.get(
"/",
params={
"id": "test", # This param will be rewritten into Location.
"key": "value", # Test that this param persists.
"key2": "value2", # And this one.
},
allow_redirects=False,
)
assert response.headers.get("location") == "/test?key=value&key2=value2"