Ctrl K
ComunidadGuías…

test_serve_app_js_direct_continu.py

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
"""« Direct continu » (demandé le 2026-09-28) : le choix par caméra est retenu,
et le budget de dix minutes des reprises ne s'applique plus tant que la case
est cochée. Le vrai code de serve_app.js, exécuté par node avec un faux
localStorage et de fausses minuteries.
"""

from __future__ import annotations

import json
import re
import shutil
import subprocess
import unittest
from pathlib import Path

RACINE = Path(__file__).parent


def extraire(source: str, motif: str) -> str:
    trouve = re.search(motif, source, re.DOTALL | re.MULTILINE)
    if trouve is None:
        raise AssertionError(f"introuvable dans serve_app.js : {motif}")
    return trouve.group(0)


class TestsDirectContinu(unittest.TestCase):
    @classmethod
    def setUpClass(cls):
        cls.node = shutil.which("node")
        if cls.node is None:
            raise unittest.SkipTest("node introuvable")
        cls.source = (RACINE / "serve_app.js").read_text(encoding="utf-8")
        debut = cls.source.index("const CLE_DIRECT_CONTINU")
        fin = cls.source.index("// Un seul identifiant par watchMse()", debut)
        cls.code = cls.source[debut:fin]

    def executer(self, corps: str, stockage: dict | None = None) -> dict:
        script = (
            f"const stockage = {json.dumps(stockage or {})};\n"
            "globalThis.localStorage = {getItem: (c) => stockage[c] ?? null,"
            " setItem: (c, v) => { stockage[c] = String(v); }};\n"
            "const minuteries = [];\n"
            "globalThis.setTimeout = (f, d) => { minuteries.push({f, d}); return minuteries.length; };\n"
            + self.code + "\n"
            "const sortie = {};\n" + corps + "\n"
            "console.log(JSON.stringify({stockage, sortie}));\n"
        )
        resultat = subprocess.run([self.node, "-e", script], capture_output=True,
                                  text=True, encoding="utf-8", check=True)
        return json.loads(resultat.stdout)

    def test_decochee_par_defaut_puis_retenue(self):
        sortie = self.executer(
            "sortie.avant = directContinu('Salon');"
            " choisirDirectContinu('Salon', true);"
            " sortie.apres = directContinu('Salon');"
            " sortie.autre = directContinu('Bureau');")
        self.assertEqual(sortie["sortie"], {"avant": False, "apres": True, "autre": False})
        self.assertEqual(json.loads(sortie["stockage"]["blink2video.directContinu"]), ["Salon"])

    def test_choix_relu_au_chargement_et_decoche(self):
        sortie = self.executer(
            "sortie.relu = directContinu('Salon');"
            " choisirDirectContinu('Salon', false);"
            " sortie.decoche = directContinu('Salon');",
            stockage={"blink2video.directContinu": '["Salon"]'})
        self.assertEqual(sortie["sortie"], {"relu": True, "decoche": False})
        self.assertEqual(json.loads(sortie["stockage"]["blink2video.directContinu"]), [])

    def test_stockage_illisible_ou_absent(self):
        for stockage in ({"blink2video.directContinu": "pas du json"},
                         {"blink2video.directContinu": '{"Salon": true}'}):
            with self.subTest(stockage=stockage):
                sortie = self.executer("sortie.v = directContinu('Salon');", stockage)
                self.assertFalse(sortie["sortie"]["v"])

    def test_budget_ordinaire_conclut_a_echeance(self):
        sortie = self.executer(
            "let fini = 0;"
            " armerBudget('Salon', 600000, () => { fini++; });"
            " sortie.premier = minuteries[0].d;"
            " minuteries[0].f();"
            " sortie.fini = fini; sortie.rearmees = minuteries.length - 1;")
        self.assertEqual(sortie["sortie"], {"premier": 600000, "fini": 1, "rearmees": 0})

    def test_direct_continu_sans_budget_puis_decoche(self):
        sortie = self.executer(
            "let fini = 0;"
            " choisirDirectContinu('Salon', true);"
            " const budget = armerBudget('Salon', 600000, () => { fini++; });"
            " minuteries[0].f();"
            " sortie.apresBudget = fini;"
            " sortie.revérification = minuteries[1].d;"
            " sortie.id = budget.id;"
            " choisirDirectContinu('Salon', false);"
            " minuteries[1].f();"
            " sortie.apresDecoche = fini;")
        self.assertEqual(sortie["sortie"], {"apresBudget": 0, "revérification": 60000,
                                            "id": 2, "apresDecoche": 1})

    def test_boucles_et_carte_branchees(self):
        s = self.source
        self.assertEqual(s.count("directContinu(name) || performance.now() - t0 <"), 2)
        self.assertIn("if (!directContinu(name)) break;", s)
        self.assertIn('data-action="continu"', s)
        self.assertIn('case "continu":', s)
        for cle in ("camera.continuous", "camera.continuous.title",
                    "camera.continuous.title.battery"):
            self.assertEqual(s.count(f'"{cle}":'), 2, cle)


if __name__ == "__main__":
    unittest.main()