"""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.""" from collections.abc import Generator from typing import Any import pytest from flask.testing import FlaskClient from core.flask_app import app as flask_app @pytest.fixture def csrf_client() -> Generator[FlaskClient, None, None]: """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: FlaskClient) -> Any: with client.session_transaction() as sess: return sess.get("csrf_token") def test_get_requests_are_never_blocked_by_csrf(csrf_client: FlaskClient) -> None: resp = csrf_client.get("/") assert resp.status_code == 200 def test_post_without_any_csrf_token_is_rejected(csrf_client: FlaskClient) -> None: 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: FlaskClient) -> None: 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: FlaskClient) -> None: # 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 os import shutil import db 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: FlaskClient) -> None: """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 os import shutil import db game_dir = db.game_dir(slug) if os.path.isdir(game_dir): shutil.rmtree(game_dir)