Le rôle "admin" contournait entièrement l'isolation par projet (core/auth_guard.py) : il pouvait ouvrir/modifier/supprimer le jeu de n'importe quel autre compte en connaissant son slug, et la page d'accueil listait sans filtrage tous les projets de tous les comptes. - La propriété d'un projet se vérifie désormais sur le segment "propriétaire" du slug (id du compte), pour tous les rôles y compris admin — un slug "à plat" (sans compte associé) reste réservé à l'admin, comportement historique conservé pour ce cas précis. - routes/games/index.py ne liste plus que les projets du compte connecté. - Le dossier propriétaire d'un projet est maintenant l'id numérique du compte, plus jamais son email slugifié (visible en clair dans chaque URL auparavant) — script de migration fourni et déjà exécuté sur les données existantes. - Changer d'email ne renomme plus aucun dossier (n'en dépend plus). - Deux nouveaux tests de régression, fixtures corrigées en conséquence. - README réécrit pour refléter l'état actuel du produit (jeu 2D uniquement, plus de traces de l'ancien éditeur "document").
143 lines
6.3 KiB
Python
143 lines
6.3 KiB
Python
"""Onboarding guidé (routes/onboarding/onboarding_new.py) : un compte
|
|
"user" fraîchement inscrit choisit un type de jeu avant d'avoir accès à
|
|
quoi que ce soit — désormais un seul type, "rpg" (jeu RPG 2D), les types
|
|
Quiz/Embranchement/"Créer mon jeu de A à Z" ayant été retirés avec le
|
|
moteur d'écran "document". Réutilise les fixtures de test_auth.py
|
|
(inscription/2FA)."""
|
|
import os
|
|
|
|
import auth
|
|
import db
|
|
import screens
|
|
|
|
from tests.test_auth import _register, _confirm_2fa, _complete_onboarding, _cleanup_project, anon_client # noqa: F401
|
|
|
|
|
|
def test_account_without_a_project_is_redirected_to_onboarding(anon_client):
|
|
_register(anon_client, "needsonboarding@example.com")
|
|
_confirm_2fa(anon_client)
|
|
try:
|
|
resp = anon_client.get("/", follow_redirects=False)
|
|
assert resp.status_code == 302
|
|
assert resp.headers["Location"] == "/onboarding"
|
|
|
|
resp = anon_client.get("/onboarding")
|
|
assert resp.status_code == 200
|
|
assert "Créer un jeu 2D ludique" in resp.get_data(as_text=True)
|
|
finally:
|
|
_cleanup_project("needsonboarding@example.com")
|
|
|
|
|
|
def test_rpg_dashboard_shows_scene_preview_and_forces_jeu2d_on_new_screens(anon_client):
|
|
_register(anon_client, "rpguser@example.com")
|
|
_confirm_2fa(anon_client)
|
|
try:
|
|
resp = _complete_onboarding(anon_client, name="Mon RPG", onboarding_type="rpg")
|
|
assert resp.status_code == 302
|
|
slug = auth.get_user_by_email("rpguser@example.com")["project_slug"]
|
|
assert resp.headers["Location"] == f"/game/{slug}"
|
|
assert db.get_onboarding_type(slug) == "rpg"
|
|
screen_list = screens.list_screens(slug)
|
|
assert len(screen_list) == 1
|
|
assert screen_list[0]["kind"] == "jeu_2d"
|
|
|
|
html = anon_client.get(f"/game/{slug}").get_data(as_text=True)
|
|
assert "screenPreviewScene" in html
|
|
assert 'name="kind"' not in html
|
|
|
|
# "+ Ajouter un écran" ne propose pas de choix de type — imposé
|
|
# jeu_2d par le projet, même en tentant de forcer "document" via
|
|
# un POST direct (défense en profondeur, pas seulement l'UI).
|
|
anon_client.post(f"/game/{slug}/screens/new", data={"name": "Scène 2", "kind": "document"}, follow_redirects=False)
|
|
new_screen_list = screens.list_screens(slug)
|
|
assert len(new_screen_list) == 2
|
|
assert new_screen_list[1]["kind"] == "jeu_2d"
|
|
|
|
edit_html = anon_client.get(f"/game/{slug}/screens/{screen_list[0]['id']}/edit").get_data(as_text=True)
|
|
assert "scene-editor.js" in edit_html
|
|
finally:
|
|
_cleanup_project("rpguser@example.com")
|
|
|
|
|
|
def test_onboarding_rejects_unknown_type(anon_client):
|
|
_register(anon_client, "badtype@example.com")
|
|
_confirm_2fa(anon_client)
|
|
try:
|
|
resp = anon_client.post("/onboarding", data={"onboarding_type": "n_importe_quoi", "name": "Test"})
|
|
assert resp.status_code == 200
|
|
assert auth.get_user_by_email("badtype@example.com")["project_slug"] is None
|
|
finally:
|
|
_cleanup_project("badtype@example.com")
|
|
|
|
|
|
def test_onboarding_rejects_removed_types(anon_client):
|
|
"""Les anciens types (quiz/embranchement/custom) ne sont plus des choix
|
|
valides — voir db/games/game_type_catalog.py, ONBOARDING_TYPES ne
|
|
contient plus que "rpg"."""
|
|
_register(anon_client, "removedtype@example.com")
|
|
_confirm_2fa(anon_client)
|
|
try:
|
|
for removed_type in ("quiz", "embranchement", "custom"):
|
|
resp = anon_client.post("/onboarding", data={"onboarding_type": removed_type, "name": "Test"})
|
|
assert resp.status_code == 200
|
|
assert auth.get_user_by_email("removedtype@example.com")["project_slug"] is None
|
|
finally:
|
|
_cleanup_project("removedtype@example.com")
|
|
|
|
|
|
def test_revisiting_onboarding_after_completion_redirects_without_recreating(anon_client):
|
|
_register(anon_client, "revisit@example.com")
|
|
_confirm_2fa(anon_client)
|
|
try:
|
|
first = _complete_onboarding(anon_client, name="Premier jeu", onboarding_type="rpg")
|
|
first_slug = auth.get_user_by_email("revisit@example.com")["project_slug"]
|
|
|
|
resp = anon_client.get("/onboarding", follow_redirects=False)
|
|
assert resp.status_code == 302
|
|
assert resp.headers["Location"] == first.headers["Location"]
|
|
|
|
again = anon_client.post("/onboarding", data={"onboarding_type": "rpg", "name": "Second jeu"}, follow_redirects=False)
|
|
assert auth.get_user_by_email("revisit@example.com")["project_slug"] == first_slug
|
|
assert again.headers["Location"] == first.headers["Location"]
|
|
finally:
|
|
_cleanup_project("revisit@example.com")
|
|
|
|
|
|
def test_admin_is_never_redirected_to_onboarding(client, game):
|
|
resp = client.get("/", follow_redirects=False)
|
|
assert resp.status_code == 200
|
|
|
|
|
|
def test_admin_can_create_several_games_via_onboarding(client):
|
|
"""Comportement systématique à CHAQUE création de jeu, pas une
|
|
formalité réservée à la toute première inscription (voir le plan) —
|
|
un admin, illimité (pas de project_slug dédié), doit pouvoir repasser
|
|
par /onboarding autant de fois qu'il crée un nouveau jeu, sans jamais
|
|
être bloqué/redirigé vers un projet précédent."""
|
|
resp1 = _complete_onboarding(client, name="Jeu RPG Admin 1", onboarding_type="rpg")
|
|
assert resp1.status_code == 302
|
|
slug1 = resp1.headers["Location"].split("/game/", 1)[1].split("/", 1)[0]
|
|
slug2 = None
|
|
try:
|
|
# project_slug reste NON posé sur le compte admin (voir
|
|
# auth/create_user.py — colonne réservée à un compte "user" à
|
|
# projet unique) : repasser sur /onboarding ne redirige PAS vers
|
|
# le premier projet créé, la page de choix s'affiche à nouveau.
|
|
resp = client.get("/onboarding")
|
|
assert resp.status_code == 200
|
|
assert "Créer un jeu 2D ludique" in resp.get_data(as_text=True)
|
|
|
|
resp2 = _complete_onboarding(client, name="Jeu RPG Admin 2", onboarding_type="rpg")
|
|
assert resp2.status_code == 302
|
|
slug2 = resp2.headers["Location"].split("/game/", 1)[1].split("/", 1)[0]
|
|
assert slug2 != slug1
|
|
|
|
import tests.conftest as conftest_module
|
|
slugs = {g["slug"] for g in db.list_games(str(conftest_module._TEST_ADMIN_ID))}
|
|
assert slug1 in slugs
|
|
assert slug2 in slugs
|
|
finally:
|
|
for slug in (slug1, slug2):
|
|
if slug and os.path.isdir(db.game_dir(slug)):
|
|
db.delete_game(slug)
|