"""Vérification du jeton CSRF (core/csrf_guard.py) — DÉSACTIVÉE quand app.config["TESTING"] est actif (voir son propre commentaire, même convention que Flask-WTF), pour ne pas avoir à ajouter le jeton aux ~170 autres tests du moteur qui appellent les routes directement via le client de test Flask. Ces tests-ci réactivent volontairement la vérification (flask_app.config["TESTING"] = False) pour la mettre à l'épreuve pour de vrai, puis la remettent dans l'état attendu par le reste de la suite.""" import pytest from core.flask_app import app as flask_app @pytest.fixture def csrf_client(): """Comme `client` (conftest.py), mais avec la vérification CSRF RÉACTIVÉE le temps du test — connecté en tant qu'admin de test (session_transaction), pour isoler la seule variable qui nous intéresse ici (le jeton), sans se soucier de l'authentification.""" import tests.conftest as conftest_module flask_app.config["TESTING"] = False try: with flask_app.test_client() as c: with c.session_transaction() as sess: sess["user_id"] = conftest_module._TEST_ADMIN_ID yield c finally: flask_app.config["TESTING"] = True def _get_session_csrf_token(client): with client.session_transaction() as sess: return sess.get("csrf_token") def test_get_requests_are_never_blocked_by_csrf(csrf_client): resp = csrf_client.get("/") assert resp.status_code == 200 def test_post_without_any_csrf_token_is_rejected(csrf_client): resp = csrf_client.post("/games/new", data={"name": "should not be created"}) assert resp.status_code == 400 def test_post_with_a_wrong_csrf_token_is_rejected(csrf_client): resp = csrf_client.post( "/games/new", data={"name": "should not be created"}, headers={"X-CSRFToken": "not-the-right-token"}, ) assert resp.status_code == 400 def test_post_with_the_correct_csrf_token_succeeds(csrf_client): # Une requête GET fait générer/exposer le jeton de CETTE session (voir # core/csrf.py::get_csrf_token, appelé par base.html au rendu). csrf_client.get("/") token = _get_session_csrf_token(csrf_client) assert token resp = csrf_client.post( "/games/new", data={"name": "pytest_csrf_ok"}, headers={"X-CSRFToken": token}, follow_redirects=False, ) assert resp.status_code == 302 slug = resp.headers["Location"].rstrip("/").split("/")[-1] import db import os import shutil game_dir = db.game_dir(slug) if os.path.isdir(game_dir): shutil.rmtree(game_dir) def test_post_with_the_token_in_the_form_field_also_works(csrf_client): """Filet de sécurité pour un éventuel formulaire natif qui ne passerait pas par static/csrf_fetch.js (aucun cas de ce genre actuellement dans le moteur, voir core/csrf_guard.py) — le champ de formulaire "csrf_token" reste accepté en plus de l'en-tête.""" csrf_client.get("/") token = _get_session_csrf_token(csrf_client) resp = csrf_client.post( "/games/new", data={"name": "pytest_csrf_form_field", "csrf_token": token}, follow_redirects=False, ) assert resp.status_code == 302 slug = resp.headers["Location"].rstrip("/").split("/")[-1] import db import os import shutil game_dir = db.game_dir(slug) if os.path.isdir(game_dir): shutil.rmtree(game_dir)