test_serve_app_js_parcours_initial.py
Rama main · réplica de lectura del repositorio original.
"""Audit du 26/09/2026, B04 : une page rechargée avec ?setup=1 après la
validation de la première configuration (jeton périmé au remplacement du
serveur, voir lireJSON()) rouvrait le dialogue obligatoire, sans Fermer ni
Échap, alors que le serveur la tenait pour terminée.
lancerParcoursInitial() est extraite de serve_app.js et exécutée par node,
avec une API et un navigateur simulés.
"""
from __future__ import annotations
import json
import re
import shutil
import subprocess
import unittest
from pathlib import Path
class TestsParcoursInitial(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.node = shutil.which("node")
if cls.node is None:
raise unittest.SkipTest("node introuvable")
source = (Path(__file__).parent / "serve_app.js").read_text(encoding="utf-8")
parcours = re.search(
r"^\(async function lancerParcoursInitial\(\) \{.*?^\}\)\(\);",
source, re.DOTALL | re.MULTILINE,
)
lire_json = re.search(
r"^async function lireJSON\(.*?^\}", source, re.DOTALL | re.MULTILINE,
)
if parcours is None or lire_json is None:
raise AssertionError("lancerParcoursInitial() ou lireJSON() introuvable")
# Sans l'appel final : le script de test attend lui-même la promesse.
cls.parcours = parcours.group(0)[:-len("();")]
cls.lire_json = lire_json.group(0)
def _executer(self, *, recherche: str, statut) -> dict:
script = f"""
const ouvertures = [], remplacements = [];
let rechargements = 0;
globalThis.location = {{
search: {json.dumps(recherche)}, pathname: "/",
reload: () => {{ rechargements += 1; }},
}};
globalThis.history = {{ replaceState: (_etat, _titre, url) => remplacements.push(url) }};
async function authenticate() {{ return true; }}
async function ouvrirReglages(obligatoire) {{ ouvertures.push(obligatoire); }}
const statut = {json.dumps(statut)};
globalThis.fetch = async () => {{
if (statut === "reseau") throw new TypeError("Serveur arrêté");
return {{ json: async () => statut }};
}};
{self.lire_json}
const parcours = {self.parcours};
parcours().then(() => process.stdout.write(JSON.stringify({{
ouvertures, remplacements, rechargements,
}})));
"""
resultat = subprocess.run(
[self.node, "-e", script], capture_output=True, text=True,
encoding="utf-8", timeout=60,
)
if resultat.returncode != 0:
raise AssertionError(resultat.stderr)
return json.loads(resultat.stdout)
def test_configuration_deja_validee_ne_rouvre_pas_le_dialogue(self):
sortie = self._executer(recherche="?setup=1", statut={"initial_setup": False})
self.assertEqual(sortie["ouvertures"], [])
self.assertEqual(sortie["remplacements"], ["/"])
def test_configuration_attendue_ouvre_le_dialogue_obligatoire(self):
sortie = self._executer(recherche="?setup=1", statut={"initial_setup": True})
self.assertEqual(sortie["ouvertures"], [True])
self.assertEqual(sortie["remplacements"], [])
def test_statut_injoignable_garde_le_dialogue_obligatoire(self):
sortie = self._executer(recherche="?setup=1", statut="reseau")
self.assertEqual(sortie["ouvertures"], [True])
def test_sans_setup_rien_ne_s_ouvre(self):
sortie = self._executer(recherche="", statut={"initial_setup": True})
self.assertEqual(sortie["ouvertures"], [])
self.assertEqual(sortie["remplacements"], [])
if __name__ == "__main__":
unittest.main()