Some checks are pending
verifier / verifier (push) Waiting to run
LA VUE. La nomenclature etait le seul registre que le GUI ne savait pas ecrire du tout : ajouter une fonction exigeait d ouvrir le YAML. Elle a sa vue, et son formulaire est GENERE depuis le schema. Deuxieme registre sur six. couverture_gui verifier passe : les 28 champs des plans reels sont editables. Elle n est pas un registre comme les autres : elle decrit la REGLE dont VMID, VLAN, adresse et passerelle se derivent. Chaque fonction montre ce qu elle derive et les VM qui la portent ; l index est montre mais pas editable, parce qu il est alloue par le site ; valider_nomenclature refuse de retirer une fonction encore portee, ou de designer une zone non declaree. DEUX FAUTES, ET POURQUOI MES BANCS NE LES VOYAIENT PAS. Le formulaire des bases, livre la veille, etait casse dans un navigateur. Il lisait data.schema, or il n existe aucun data global : c est une const locale de charger(). ReferenceError a l ouverture, et zone morte dans sauvegarderBases. Je l avais eprouve sous node EN LUI PASSANT data : le banc reproduisait la fonction, pas sa portee. D ou test_rendu_gui.py, qui charge le JS entier dans un DOM simule et dessine les douze vues, avec son controle negatif. Le schema decrivait reservations comme une table de zones ; le fichier reel est un bloc plat. P61 comparait des NOMS aplatis, donc ne voyait rien. Elle compare desormais aussi la FORME. ECRIRE SANS DEPLACER UN COMMENTAIRE. _fusion_chirurgicale remplace le bloc entier des qu une valeur change : quinze entrees compactes devenaient 42 lignes, et le commentaire du poste d exploitation se retrouvait en tete du bloc, ou il affirmait que collab etait le poste d exploitation. Un commentaire deplace n est pas laid, il est faux. _fusion_table edite les tables ligne a ligne ; le diff fait trois lignes. Au passage : sort_keys triait le schema, donc l ordre des cases a l ecran (reserve_max avant reserve_min) ; et _ecrire_index_nomenclature ecrivait encore par write_text, oubliee au passage des ecritures atomiques. LIMITE : deux registres sur six sont generes, et je n ai toujours pas ouvert cette page dans un navigateur. make prouver : CONFORME, 60 OK, 0 echec, 1 saute. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
177 lines
8.3 KiB
Python
177 lines
8.3 KiB
Python
#!/usr/bin/env python3
|
|
"""Le GUI REND-IL ? Chaque vue est dessinee pour de vrai, dans un DOM simule.
|
|
|
|
CE QUE `verifier_gui.py` NE POUVAIT PAS VOIR (2026-09-08). Il passe le JS a
|
|
`node --check` : la syntaxe, donc, et rien d'autre. Le formulaire genere des bases
|
|
lisait `data.schema` — or il n'existe aucun `data` global dans cette page ; c'est une
|
|
const locale de `charger()`. Syntaxe parfaite, `ReferenceError` a l'ouverture. Pire dans
|
|
`sauvegarderBases`, ou `const data` est declare PLUS BAS dans la meme fonction : zone
|
|
morte temporelle, l'enregistrement jetait avant meme d'envoyer.
|
|
|
|
Le defaut a survecu a une verification sous node parce que le banc APPELAIT la fonction
|
|
en lui passant `data` — il reproduisait la fonction, pas sa PORTEE. Une garantie qu'on
|
|
n'a jamais vue echouer n'est pas une garantie.
|
|
|
|
Ce banc-ci charge le JS entier, tel que la page le recoit, dans un DOM simule, appelle
|
|
`charger()` sur la VRAIE reponse de l'API, puis dessine CHAQUE vue. Toute reference a un
|
|
identifiant qui n'existe pas leve, et le test echoue.
|
|
|
|
Ce qu'il ne fait PAS : juger l'apparence. Il dit que ca rend, pas que c'est beau.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import re
|
|
import shutil
|
|
import subprocess
|
|
import sys
|
|
import tempfile
|
|
import unittest
|
|
from pathlib import Path
|
|
|
|
RACINE = Path(__file__).resolve().parents[2]
|
|
sys.path.insert(0, str(RACINE / "scripts"))
|
|
|
|
import inventory_gui as g # noqa: E402
|
|
|
|
# Un DOM juste assez reel : tout element repond, retient son innerHTML, et n'invente
|
|
# rien. Un `getElementById` qui rendrait `null` masquerait les fautes qu'on cherche.
|
|
BANC = r"""
|
|
'use strict';
|
|
function Elem(id) {
|
|
this.id = id; this.innerHTML = ''; this.textContent = ''; this.value = '';
|
|
this.hidden = false; this.disabled = false; this.title = ''; this.className = '';
|
|
this.style = {}; this.dataset = {}; this.children = [];
|
|
this.classList = {add(){}, remove(){}, toggle(){}, contains(){ return false; }};
|
|
}
|
|
Elem.prototype.appendChild = function (e) { this.children.push(e); return e; };
|
|
Elem.prototype.removeChild = function (e) { return e; };
|
|
Elem.prototype.setAttribute = function () {};
|
|
Elem.prototype.getAttribute = function () { return ''; };
|
|
Elem.prototype.addEventListener = function () {};
|
|
Elem.prototype.querySelector = function () { return null; };
|
|
Elem.prototype.querySelectorAll = function () { return []; };
|
|
Elem.prototype.focus = function () {};
|
|
Elem.prototype.remove = function () {};
|
|
Elem.prototype.closest = function () { return null; };
|
|
Elem.prototype.scrollIntoView = function () {};
|
|
|
|
var __elems = {};
|
|
var document = {
|
|
getElementById: function (id) { return __elems[id] || (__elems[id] = new Elem(id)); },
|
|
createElement: function () { return new Elem('cree'); },
|
|
querySelector: function () { return null; },
|
|
querySelectorAll: function () { return []; },
|
|
addEventListener: function () {},
|
|
body: new Elem('body'),
|
|
documentElement: new Elem('html'),
|
|
};
|
|
var window = {addEventListener: function () {}, location: {href: '', reload: function () {}},
|
|
matchMedia: function () { return {matches: false, addEventListener: function () {}}; }};
|
|
var navigator = {clipboard: {writeText: function () { return Promise.resolve(); }}};
|
|
function MutationObserver(f) { this.observe = function () {}; this.disconnect = function () {}; }
|
|
function requestAnimationFrame(f) { return 0; }
|
|
function cancelAnimationFrame() {}
|
|
function EventSource() { this.addEventListener = function () {}; this.close = function () {}; }
|
|
function alert() {} function confirm() { return false; } function prompt() { return null; }
|
|
var localStorage = {getItem: function () { return null; }, setItem: function () {}, removeItem: function () {}};
|
|
var __PAYLOAD__ = JSON.parse(process.env.PAYLOAD);
|
|
// `fetch` rend la VRAIE reponse de /api/inventaire ; les autres routes rendent un objet
|
|
// vide plutot que de jeter, pour que les sondes de fond ne masquent pas l'erreur cherchee.
|
|
function fetch(url) {
|
|
var corps = String(url).indexOf('/api/inventaire') === 0 ? __PAYLOAD__ : {};
|
|
return Promise.resolve({ok: true, status: 200, json: function () { return Promise.resolve(corps); },
|
|
text: function () { return Promise.resolve(''); }});
|
|
}
|
|
var __erreurs = [];
|
|
function __essayer(quoi, f) { try { f(); } catch (e) { __erreurs.push(quoi + ' : ' + e); } }
|
|
"""
|
|
|
|
APRES = r"""
|
|
// --- L'EPREUVE -----------------------------------------------------------------
|
|
charger().then(function () {
|
|
['serveurs', 'applications', 'bases', 'domaines', 'nomenclature',
|
|
'integrations', 'flux', 'couches', 'reseau'].forEach(function (v) {
|
|
__essayer('setVue(' + v + ')', function () { setVue(v); });
|
|
});
|
|
// Les vues a maitre-detail ne montrent leur formulaire QU'UNE FOIS une entree
|
|
// choisie : dessiner la grille seule ne prouve rien du formulaire genere.
|
|
__essayer('bases + selection', function () {
|
|
setVue('bases');
|
|
if (typeof bdApplis !== 'undefined' && bdApplis.length) { choisirBase('bd:0'); }
|
|
if (typeof bdServeurs !== 'undefined' && bdServeurs.length) { choisirBase('srv:0'); }
|
|
});
|
|
__essayer('nomenclature + selection', function () {
|
|
setVue('nomenclature');
|
|
var f = Object.keys(nomEdit.fonctions || {});
|
|
var c = Object.keys(nomEdit.categories || {});
|
|
if (f.length) { choisirNom('fon:' + f[0]); }
|
|
if (c.length) { choisirNom('cat:' + c[0]); }
|
|
});
|
|
__essayer('domaines + selection', function () { setVue('domaines'); if (domaines.length) choisirDomaine(0); });
|
|
__essayer('applications + selection', function () { setVue('applications'); if (applications.length) ouvrirApp(applications[0].id); });
|
|
console.log('__RESULTAT__' + JSON.stringify(__erreurs));
|
|
}).catch(function (e) {
|
|
console.log('__RESULTAT__' + JSON.stringify(['charger() : ' + (e && e.stack || e)]));
|
|
});
|
|
"""
|
|
|
|
|
|
class TestRenduGui(unittest.TestCase):
|
|
"""Chaque vue du GUI se dessine sans lever."""
|
|
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
if not shutil.which("node"):
|
|
raise unittest.SkipTest("node absent")
|
|
html = g.HTML.replace("__JETON__", "TEST")
|
|
blocs = re.findall(r"<script>(.*?)</script>", html, re.S)
|
|
assert blocs, "aucun bloc <script>"
|
|
cls.js = BANC + "\n".join(blocs) + APRES
|
|
|
|
def _jouer(self, js: str, payload: dict) -> list:
|
|
with tempfile.NamedTemporaryFile("w", suffix=".js", delete=False, encoding="utf-8") as f:
|
|
f.write(js)
|
|
chemin = f.name
|
|
try:
|
|
r = subprocess.run(["node", chemin], capture_output=True, text=True, timeout=120,
|
|
env={**__import__("os").environ, "PAYLOAD": json.dumps(payload)})
|
|
finally:
|
|
Path(chemin).unlink(missing_ok=True)
|
|
marque = "__RESULTAT__"
|
|
for ligne in r.stdout.splitlines():
|
|
if ligne.startswith(marque):
|
|
return json.loads(ligne[len(marque):])
|
|
self.fail(f"le banc n'a rien rendu.\nstdout: {r.stdout[-2000:]}\nstderr: {r.stderr[-2000:]}")
|
|
|
|
def test_toutes_les_vues_rendent(self):
|
|
"""La vraie reponse de l'API, les neuf vues, zero exception."""
|
|
payload = g.inventaire_api(g.INVENTAIRE_DEFAUT)
|
|
erreurs = self._jouer(self.js, payload)
|
|
self.assertEqual(erreurs, [], "des vues ont leve :\n - " + "\n - ".join(erreurs))
|
|
|
|
def test_controle_negatif_reference_absente(self):
|
|
"""LE CONTROLE. Une lecture d'identifiant inexistant DOIT faire echouer le banc.
|
|
|
|
Sans lui, ce test passerait aussi bien sur un banc qui n'execute rien. On rejoue
|
|
exactement la faute du 2026-09-08 : le formulaire des bases lit un global qui
|
|
n'existe pas.
|
|
"""
|
|
casse = self.js.replace("const reg = schemaPlan[nomRegistre] || null;",
|
|
"const reg = (donneesQuiNexistentPas.schema || {})[nomRegistre] || null;")
|
|
self.assertNotEqual(casse, self.js, "le motif du controle negatif n'existe plus")
|
|
payload = g.inventaire_api(g.INVENTAIRE_DEFAUT)
|
|
erreurs = self._jouer(casse, payload)
|
|
self.assertTrue(any("donneesQuiNexistentPas" in e for e in erreurs),
|
|
f"le banc n'a pas vu la reference absente : {erreurs}")
|
|
|
|
def test_le_schema_est_bien_servi(self):
|
|
"""Le formulaire ne peut rien generer si l'API n'envoie pas le schema."""
|
|
payload = g.inventaire_api(g.INVENTAIRE_DEFAUT)
|
|
self.assertIn("schema", payload, "l'API ne sert pas le schema du plan")
|
|
self.assertIn("nomenclature", payload["schema"])
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|