"""FlowDeck — réglages d'accès : inscription locale (fusion #146, #144). Contrat de la phase 1 du programme « compte partout » : - inscription locale **fermée par défaut** (décision de Bruno) : le parcours public d'inscription est refusé proprement et nomme le Hub Dracodev, là où les comptes se créent ; - réglage **réversible par un administrateur** (API ``/api/admin/reglages-acces``) : ouverte, l'inscription redevient exactement celle d'aujourd'hui ; - la connexion des comptes existants (locaux et draco) n'est **jamais** affectée par le réglage ; - #144 : l'échec du formulaire de connexion locale suggère la voie ObsiGate (SSO). Le harnais de test ouvre l'inscription pour les suites historiques (voir conftest) ; chaque test pose donc explicitement l'état qu'il vérifie via le service des réglages. """ from __future__ import annotations from conftest import anon_csrf from app.services import reglages_acces def _fermer(): reglages_acces.set_reglages_acces(inscription_locale_ouverte=False) def _ouvrir(): reglages_acces.set_reglages_acces(inscription_locale_ouverte=True) def _creer_compte_local(login: str, mot_de_passe: str, admin: int = 0) -> None: from app.db import get_conn from app.password_utils import hash_password with get_conn() as conn: conn.execute( "INSERT OR REPLACE INTO users (login, full_name, email," " password_hash, is_admin) VALUES (?, ?, ?, ?, ?)", (login, login, f"{login}@test.dev", hash_password(mot_de_passe), admin), ) conn.commit() # ── Réglage fermé : inscription impossible + message ──────────────── def test_inscription_fermee_par_defaut_dans_le_code(): """Le défaut du code (hors toute ligne en base) est « fermée ».""" from app.config import settings assert settings.inscription_locale_ouverte is False def test_inscription_fermee_post_refuse_avec_message_hub(client): _fermer() r = client.post("/auth/register", json={ "email": "visiteur@test.dev", "password": "secret123", "name": "Visiteur"}) assert r.status_code == 403 erreur = r.json()["error"] assert "Hub Dracodev" in erreur # Lien dérivé vers le Hub fourni à l'écran comme aux API. assert r.json()["hub_url"] # Aucun compte n'a été créé. from app.db import get_conn with get_conn() as conn: assert conn.execute( "SELECT COUNT(*) FROM users WHERE login='visiteur@test.dev'" ).fetchone()[0] == 0 def test_inscription_fermee_page_refus_propre(client): _fermer() r = anon_csrf(client).get("/auth/register", follow_redirects=False) assert r.status_code == 403 assert "Hub Dracodev" in r.text assert "les comptes se créent" in r.text def test_inscription_fermee_page_connexion_sans_onglet_register(client): _fermer() r = anon_csrf(client).get("/auth/login?provider=local") assert r.status_code == 200 assert 'id="tab-register"' not in r.text # L'avis nomme le Hub et la voie SSO reste visible. assert "Hub Dracodev" in r.text assert "Login with ObsiGate (SSO)" in r.text # ── Réglage ouvert : inscription comme aujourd'hui ────────────────── def test_inscription_ouverte_post_comme_avant(client): _ouvrir() r = client.post("/auth/register", json={ "email": "nouveau@test.dev", "password": "secret123", "name": "Nouveau"}) assert r.status_code == 200 assert r.json()["status"] == "ok" def test_inscription_ouverte_page_register_affichee(client): _ouvrir() r = anon_csrf(client).get("/auth/register", follow_redirects=False) assert r.status_code == 200 assert 'id="tab-register"' in r.text # ── Connexion des comptes existants : jamais affectée ─────────────── def test_connexion_compte_local_existant_inscription_fermee(client): _creer_compte_local("ancien", "secret123") _fermer() r = anon_csrf(client).post("/auth/local-login", json={ "email": "ancien", "password": "secret123"}) assert r.status_code == 200 assert r.json()["status"] == "ok" def test_connexion_compte_draco_sans_mot_de_passe_inchangee(client): """Un compte draco (sans mot de passe local) reste refusé en local de la même façon, réglage ouvert ou fermé : le réglage ne change rien à l'authentification.""" from app.db import get_conn with get_conn() as conn: conn.execute( "INSERT OR REPLACE INTO users (login, full_name, email," " password_hash, auth_method) VALUES" " ('draco-42', 'Dan Test', 'dan@test.dev', NULL, 'draco')") conn.commit() for ouvrir in (True, False): reglages_acces.set_reglages_acces( inscription_locale_ouverte=ouvrir) r = anon_csrf(client).post("/auth/local-login", json={ "email": "draco-42", "password": "nimporte"}) assert r.status_code == 401 # ── #144 : l'échec local suggère la voie ObsiGate (SSO) ───────────── def test_echec_connexion_locale_suggere_sso(client): _creer_compte_local("quelquun", "secret123") r = anon_csrf(client).post("/auth/local-login", json={ "email": "quelquun", "password": "mauvais-mot-de-passe"}) assert r.status_code == 401 assert "ObsiGate (SSO)" in r.json()["error"] assert "Hub Dracodev" in r.json()["error"] def test_echec_connexion_compte_inconnu_suggere_sso(client): r = anon_csrf(client).post("/auth/local-login", json={ "email": "inconnu@test.dev", "password": "nimporte"}) assert r.status_code == 401 assert "ObsiGate (SSO)" in r.json()["error"] # ── Administration du réglage ──────────────────────────────────────── def test_api_admin_reglages_acces_aller_retour(client): """Le client de test est administrateur : lecture + écriture.""" _fermer() r = client.get("/api/admin/reglages-acces") assert r.status_code == 200 assert r.json()["inscription_locale_ouverte"] is False r = client.put("/api/admin/reglages-acces", json={"inscription_locale_ouverte": True}) assert r.status_code == 200 assert r.json()["inscription_locale_ouverte"] is True assert reglages_acces.inscription_locale_ouverte() is True def test_api_admin_reglages_acces_refusee_aux_anonymes(client): r = anon_csrf(client).get("/api/admin/reglages-acces") assert r.status_code in (401, 403)