Onboarding guidé systématique + tableau de bord simplifié
Build and deploy / test-python (push) Failing after 1m48s
Build and deploy / test-js (push) Successful in 53s
Build and deploy / build-and-push (push) Skipped
Build and deploy / deploy (push) Skipped

Comportement SYSTÉMATIQUE à chaque création de jeu (admin compris), pas
une formalité réservée à l'inscription : /onboarding (routes/onboarding/)
devient le point d'entrée unique — 4 cartes retournables (survol =
explication au dos), défilement horizontal animé vers le nom du jeu. Un
admin y repasse à volonté (pas de project_slug dédié, jamais bloqué/
redirigé vers un projet précédent) ; un compte "user" n'en a plus qu'un
créé d'office (routes/auth/register_2fa.py), guidé ici à la place.

- db/games/game_type_catalog.py : catalogue des 4 types (Quiz/
  Embranchement-escape game/RPG/Créer mon jeu de A à Z), _meta['onboarding_type']
  décide du "kind" du premier écran créé et si le tableau de bord complet
  reste accessible.
- routes/games/game_dashboard.py, templates/game_dashboard_simple.html :
  un type restreint (quiz/embranchement/rpg) voit désormais SON tableau
  de bord (même route que "custom"), rendu en version simplifiée — juste
  ses écrans en cartes avec un aperçu RÉEL du contenu (scène mise à
  l'échelle par container query CSS, adaptée à la largeur réelle de la
  carte). "+ Ajouter un écran" n'y propose pas de choix de type : imposé
  par le projet (routes/screens/screens_new.py), verrouillé aussi côté
  serveur.
- core/auth_guard.py : plus de blocage de game_dashboard par type — la
  restriction se fait au rendu, pas à l'accès à la route.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
william
2026-09-02 06:22:45 +02:00
co-authored by Claude Sonnet 5
parent 0f23024882
commit 76a50fa87a
23 changed files with 876 additions and 52 deletions
+45 -9
View File
@@ -34,19 +34,41 @@ def _register(client, email, prenom="Alice", nom="Test", password="Sup3r$ecret!"
def _confirm_2fa(client):
"""Récupère la clé TOTP affichée sur la page de confirmation et
soumet le code du moment — comme un humain qui vient de scanner le
QR code avec son application."""
QR code avec son application. Un compte "user" n'a plus de projet
créé d'office ici (voir routes/onboarding/onboarding_new.py) — la
redirection renvoie désormais vers /onboarding, pas /game/<slug> ;
voir _complete_onboarding ci-dessous pour les tests qui ont besoin
d'un vrai projet ensuite."""
html = client.get("/register/2fa").get_data(as_text=True)
secret = re.search(r"<code>([A-Z0-9]+)</code>", html).group(1)
code = pyotp.TOTP(secret).now()
return client.post("/register/2fa", data={"code": code}, follow_redirects=False), secret
def _complete_onboarding(client, name="Mon jeu", onboarding_type="custom"):
"""Termine l'onboarding guidé (voir routes/onboarding/onboarding_new.py)
— la plupart des tests de ce fichier n'ont pas besoin de tester
l'onboarding lui-même (voir test_onboarding.py), juste d'obtenir un
project_slug réel pour la suite ; "custom" est le choix le plus proche
du comportement historique (tableau de bord complet)."""
return client.post(
"/onboarding", data={"onboarding_type": onboarding_type, "name": name}, follow_redirects=False
)
def _cleanup_project(email):
game_dir = db.game_dir(db.slugify(email))
import os
import shutil
if os.path.isdir(game_dir):
shutil.rmtree(game_dir)
user = auth.get_user_by_email(email)
if user and user.get("project_slug"):
if os.path.isdir(db.game_dir(user["project_slug"])):
# db.delete_game (voir db/games/project_slug.py — structure de
# dossiers par utilisateur) nettoie aussi le dossier
# propriétaire s'il devient vide, contrairement à un simple
# shutil.rmtree(game_dir) — sans ça, des dossiers propriétaires
# vides s'accumulent sous projects/ d'un run de tests à l'autre
# (déjà observé : ça a fini par provoquer une collision de nom
# dans db.move_game, un dossier vide comptant comme "occupé").
db.delete_game(user["project_slug"])
def test_anonymous_request_redirects_to_login(anon_client):
@@ -100,13 +122,23 @@ def test_full_registration_flow_requires_totp_before_account_is_usable(anon_clie
user = auth.get_user_by_email("flow@example.com")
assert user["totp_confirmed"] == 1
assert user["role"] == "user" # jamais le premier compte de la session de tests
assert user["project_slug"] == db.slugify("flow@example.com")
# Onboarding guidé (voir routes/onboarding/onboarding_new.py) : plus
# de projet créé d'office ici — connecté mais sans project_slug tant
# qu'il n'a pas choisi un type de jeu.
assert user["project_slug"] is None
try:
assert resp.headers["Location"] == "/game/" + user["project_slug"]
# Connecté pour de vrai maintenant.
assert resp.headers["Location"] == "/onboarding"
# Connecté pour de vrai maintenant, mais renvoyé vers l'onboarding
# tant qu'aucun projet n'existe (voir core/auth_guard.py).
resp2 = anon_client.get("/", follow_redirects=False)
assert resp2.status_code == 302
assert resp2.headers["Location"] == "/game/" + user["project_slug"]
assert resp2.headers["Location"] == "/onboarding"
onboard_resp = _complete_onboarding(anon_client)
assert onboard_resp.status_code == 302
user = auth.get_user_by_email("flow@example.com")
assert user["project_slug"] is not None
assert onboard_resp.headers["Location"] == "/game/" + user["project_slug"]
finally:
_cleanup_project("flow@example.com")
@@ -123,6 +155,7 @@ def test_wrong_totp_code_is_rejected(anon_client):
def test_login_requires_correct_password_then_totp(anon_client):
_register(anon_client, "login@example.com")
resp, secret = _confirm_2fa(anon_client)
_complete_onboarding(anon_client)
slug = auth.get_user_by_email("login@example.com")["project_slug"]
try:
anon_client.post("/logout")
@@ -146,6 +179,7 @@ def test_login_requires_correct_password_then_totp(anon_client):
def test_non_admin_user_is_isolated_to_their_own_project(anon_client):
_register(anon_client, "isolated@example.com")
_confirm_2fa(anon_client)
_complete_onboarding(anon_client)
user = auth.get_user_by_email("isolated@example.com")
try:
# Un autre projet (celui de l'admin partagé par conftest.py, ou
@@ -173,6 +207,7 @@ def test_non_admin_user_cannot_delete_their_only_project(anon_client):
retour — bloqué plutôt que risqué."""
_register(anon_client, "nodelete@example.com")
_confirm_2fa(anon_client)
_complete_onboarding(anon_client)
user = auth.get_user_by_email("nodelete@example.com")
try:
resp = anon_client.post(f"/game/{user['project_slug']}/delete")
@@ -281,6 +316,7 @@ def test_recovery_codes_are_shown_once_after_confirming_2fa(anon_client):
def test_login_with_a_recovery_code_instead_of_totp(anon_client):
_register(anon_client, "recoverylogin@example.com")
_confirm_2fa(anon_client)
_complete_onboarding(anon_client)
user = auth.get_user_by_email("recoverylogin@example.com")
slug = user["project_slug"]
codes = auth.generate_recovery_codes(user["id"])