Set-OPS-Public/scripts/tests/test_rendu_gui.py

381 lines
20 KiB
Python
Raw Permalink Normal View History

GUI : la vue Nomenclature, et deux fautes que mes bancs ne voyaient pas 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
2026-09-08 17:08:45 -04:00
#!/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; };
GUI : les six registres ont un formulaire genere, et la sauvegarde aussi CHAMPS_ECRITS_A_LA_MAIN est vide. Serveurs et applications, les deux plus gros, sont passes au generateur — chargement, rendu et sauvegarde. L EPREUVE QUI COMPTE. Ouvrir chaque vue et enregistrer sans rien toucher doit renvoyer exactement le plan qu on vient de lire : 14 serveurs, 25 applications, 2 domaines, 4 bases, IDENTIQUE partout. C est ce qui separe un formulaire genere d un formulaire qui en a l air — un champ visible a l ecran et perdu en silence a l enregistrement serait le pire des deux mondes. test_rendu_gui.py le mesure a chaque make prouver. TROIS DEFAUTS TROUVES EN CHEMIN. Le formulaire annoncait des defauts INVENTES : 2048 Mo, 2 coeurs, 16G. Il n existe aucun defaut fixe — deriver_ressources calcule depuis les roles portes (1024 et 1 pour infra-pki-01, 5632 et 4 pour collab-01). Un repere faux fait croire qu on connait la valeur. Le schema nomme le champ derive, et l ecran montre la valeur reelle de cet hote. L option vide d un select dit desormais ce qu elle produira : « (defaut : asgard) ». Une SECONDE occurrence du defaut d hier dormait dans sourceDeValeurs : elle lisait encore data.nomenclature. Elle n avait jamais leve parce que la vue Serveurs, seule a emprunter cette source, avait un formulaire ecrit a la main. Elle a leve a la seconde ou le generateur l a prise. Le banc ne voit que les chemins vivants : verifier_gui.py fait donc aussi une verification STATIQUE, qui voit ce qui dort. La validation client s accrochait a data-v, pose a la main sur trois champs. Le formulaire genere l aurait perdu et la validation serait passee au vert sur ZERO champ. Le generateur marque chaque controle, et la sauvegarde refuse si elle n en inspecte aucun. DEUX CHAMPS GARDENT LEUR EDITEUR, et le schema le dit (x-editeur) : la matrice des integrations montre les universelles et les exemptions, et l editeur de liens contraint le role a meta/liens.yml. Le generateur s efface plutot que de remplacer un editeur qui en sait plus que lui. LIMITE : je n ai toujours pas ouvert ces pages dans un navigateur. make prouver : CONFORME, 61 OK, 0 echec, 1 saute. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-08 18:26:33 -04:00
// `querySelectorAll('[data-champ]')` rend vraiment les controles du dernier rendu : le
// GUI s'en sert pour valider avant d'enregistrer, et un stub qui rend toujours [] ferait
// passer cette validation sur ZERO champ — le banc mesurerait alors moins que rien.
Elem.prototype.querySelectorAll = function (sel) {
if (!/data-champ/.test(String(sel))) { return []; }
var html = this.innerHTML || '';
var out = [], re = /<(input|select)([^>]*?)data-champ="([^"]*)"([^>]*)>/g, m;
while ((m = re.exec(html)) !== null) {
var attrs = m[2] + m[4];
var v = /\svalue="([^"]*)"/.exec(attrs);
out.push({__champ: m[3], value: v ? v[1] : '',
getAttribute: function (n) { return n === 'data-champ' ? this.__champ : null; },
classList: {add: function () {}, remove: function () {}, toggle: function () {}},
focus: function () {}});
}
return out;
};
GUI : la vue Nomenclature, et deux fautes que mes bancs ne voyaient pas 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
2026-09-08 17:08:45 -04:00
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; },
GUI : les six registres ont un formulaire genere, et la sauvegarde aussi CHAMPS_ECRITS_A_LA_MAIN est vide. Serveurs et applications, les deux plus gros, sont passes au generateur — chargement, rendu et sauvegarde. L EPREUVE QUI COMPTE. Ouvrir chaque vue et enregistrer sans rien toucher doit renvoyer exactement le plan qu on vient de lire : 14 serveurs, 25 applications, 2 domaines, 4 bases, IDENTIQUE partout. C est ce qui separe un formulaire genere d un formulaire qui en a l air — un champ visible a l ecran et perdu en silence a l enregistrement serait le pire des deux mondes. test_rendu_gui.py le mesure a chaque make prouver. TROIS DEFAUTS TROUVES EN CHEMIN. Le formulaire annoncait des defauts INVENTES : 2048 Mo, 2 coeurs, 16G. Il n existe aucun defaut fixe — deriver_ressources calcule depuis les roles portes (1024 et 1 pour infra-pki-01, 5632 et 4 pour collab-01). Un repere faux fait croire qu on connait la valeur. Le schema nomme le champ derive, et l ecran montre la valeur reelle de cet hote. L option vide d un select dit desormais ce qu elle produira : « (defaut : asgard) ». Une SECONDE occurrence du defaut d hier dormait dans sourceDeValeurs : elle lisait encore data.nomenclature. Elle n avait jamais leve parce que la vue Serveurs, seule a emprunter cette source, avait un formulaire ecrit a la main. Elle a leve a la seconde ou le generateur l a prise. Le banc ne voit que les chemins vivants : verifier_gui.py fait donc aussi une verification STATIQUE, qui voit ce qui dort. La validation client s accrochait a data-v, pose a la main sur trois champs. Le formulaire genere l aurait perdu et la validation serait passee au vert sur ZERO champ. Le generateur marque chaque controle, et la sauvegarde refuse si elle n en inspecte aucun. DEUX CHAMPS GARDENT LEUR EDITEUR, et le schema le dit (x-editeur) : la matrice des integrations montre les universelles et les exemptions, et l editeur de liens contraint le role a meta/liens.yml. Le generateur s efface plutot que de remplacer un editeur qui en sait plus que lui. LIMITE : je n ai toujours pas ouvert ces pages dans un navigateur. make prouver : CONFORME, 61 OK, 0 echec, 1 saute. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-08 18:26:33 -04:00
// `#detail [data-champ]` : le GUI interroge le DOCUMENT, pas l'element. Un stub qui
// rendait [] ici faisait croire que le formulaire n'etait pas marque — et la
// sauvegarde des serveurs refusait, silencieusement, dans le banc seul.
querySelectorAll: function (sel) {
var m = /^#([\w-]+)\s+(.+)$/.exec(String(sel));
if (m && __elems[m[1]]) { return __elems[m[1]].querySelectorAll(m[2]); }
return [];
},
GUI : la vue Nomenclature, et deux fautes que mes bancs ne voyaient pas 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
2026-09-08 17:08:45 -04:00
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);
GUI : les six registres ont un formulaire genere, et la sauvegarde aussi CHAMPS_ECRITS_A_LA_MAIN est vide. Serveurs et applications, les deux plus gros, sont passes au generateur — chargement, rendu et sauvegarde. L EPREUVE QUI COMPTE. Ouvrir chaque vue et enregistrer sans rien toucher doit renvoyer exactement le plan qu on vient de lire : 14 serveurs, 25 applications, 2 domaines, 4 bases, IDENTIQUE partout. C est ce qui separe un formulaire genere d un formulaire qui en a l air — un champ visible a l ecran et perdu en silence a l enregistrement serait le pire des deux mondes. test_rendu_gui.py le mesure a chaque make prouver. TROIS DEFAUTS TROUVES EN CHEMIN. Le formulaire annoncait des defauts INVENTES : 2048 Mo, 2 coeurs, 16G. Il n existe aucun defaut fixe — deriver_ressources calcule depuis les roles portes (1024 et 1 pour infra-pki-01, 5632 et 4 pour collab-01). Un repere faux fait croire qu on connait la valeur. Le schema nomme le champ derive, et l ecran montre la valeur reelle de cet hote. L option vide d un select dit desormais ce qu elle produira : « (defaut : asgard) ». Une SECONDE occurrence du defaut d hier dormait dans sourceDeValeurs : elle lisait encore data.nomenclature. Elle n avait jamais leve parce que la vue Serveurs, seule a emprunter cette source, avait un formulaire ecrit a la main. Elle a leve a la seconde ou le generateur l a prise. Le banc ne voit que les chemins vivants : verifier_gui.py fait donc aussi une verification STATIQUE, qui voit ce qui dort. La validation client s accrochait a data-v, pose a la main sur trois champs. Le formulaire genere l aurait perdu et la validation serait passee au vert sur ZERO champ. Le generateur marque chaque controle, et la sauvegarde refuse si elle n en inspecte aucun. DEUX CHAMPS GARDENT LEUR EDITEUR, et le schema le dit (x-editeur) : la matrice des integrations montre les universelles et les exemptions, et l editeur de liens contraint le role a meta/liens.yml. Le generateur s efface plutot que de remplacer un editeur qui en sait plus que lui. LIMITE : je n ai toujours pas ouvert ces pages dans un navigateur. make prouver : CONFORME, 61 OK, 0 echec, 1 saute. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-08 18:26:33 -04:00
var __postsCaptures = {};
GUI : la vue Nomenclature, et deux fautes que mes bancs ne voyaient pas 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
2026-09-08 17:08:45 -04:00
// `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.
GUI : les six registres ont un formulaire genere, et la sauvegarde aussi CHAMPS_ECRITS_A_LA_MAIN est vide. Serveurs et applications, les deux plus gros, sont passes au generateur — chargement, rendu et sauvegarde. L EPREUVE QUI COMPTE. Ouvrir chaque vue et enregistrer sans rien toucher doit renvoyer exactement le plan qu on vient de lire : 14 serveurs, 25 applications, 2 domaines, 4 bases, IDENTIQUE partout. C est ce qui separe un formulaire genere d un formulaire qui en a l air — un champ visible a l ecran et perdu en silence a l enregistrement serait le pire des deux mondes. test_rendu_gui.py le mesure a chaque make prouver. TROIS DEFAUTS TROUVES EN CHEMIN. Le formulaire annoncait des defauts INVENTES : 2048 Mo, 2 coeurs, 16G. Il n existe aucun defaut fixe — deriver_ressources calcule depuis les roles portes (1024 et 1 pour infra-pki-01, 5632 et 4 pour collab-01). Un repere faux fait croire qu on connait la valeur. Le schema nomme le champ derive, et l ecran montre la valeur reelle de cet hote. L option vide d un select dit desormais ce qu elle produira : « (defaut : asgard) ». Une SECONDE occurrence du defaut d hier dormait dans sourceDeValeurs : elle lisait encore data.nomenclature. Elle n avait jamais leve parce que la vue Serveurs, seule a emprunter cette source, avait un formulaire ecrit a la main. Elle a leve a la seconde ou le generateur l a prise. Le banc ne voit que les chemins vivants : verifier_gui.py fait donc aussi une verification STATIQUE, qui voit ce qui dort. La validation client s accrochait a data-v, pose a la main sur trois champs. Le formulaire genere l aurait perdu et la validation serait passee au vert sur ZERO champ. Le generateur marque chaque controle, et la sauvegarde refuse si elle n en inspecte aucun. DEUX CHAMPS GARDENT LEUR EDITEUR, et le schema le dit (x-editeur) : la matrice des integrations montre les universelles et les exemptions, et l editeur de liens contraint le role a meta/liens.yml. Le generateur s efface plutot que de remplacer un editeur qui en sait plus que lui. LIMITE : je n ai toujours pas ouvert ces pages dans un navigateur. make prouver : CONFORME, 61 OK, 0 echec, 1 saute. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-08 18:26:33 -04:00
function fetch(url, opts) {
if (opts && opts.method === 'POST') { __postsCaptures[String(url)] = JSON.parse(opts.body); }
// Un POST rend la MEME charge que l'inventaire : le GUI recharge apres sauvegarde, et
// lui rendre {} le ferait repartir d'un plan vide — un faux vert, pas un test.
var corps = (String(url).indexOf('/api/inventaire') === 0 || (opts && opts.method === 'POST'))
? __PAYLOAD__ : {};
GUI : la vue Nomenclature, et deux fautes que mes bancs ne voyaient pas 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
2026-09-08 17:08:45 -04:00
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)]));
});
"""
console de site : un vide doit avoir la forme de ce qu'il remplace La console du runner de SITE-Chezlepro ne montrait rien. Elle a douze machines. inventaire_api sert serveurs en LISTE, inventaire_api_du_site le servait en TABLE. Les deux sont vides et la page ne les lit pas pareil : (data.serveurs || []).map trouve {} — truthy, sans .map — et charger() leve. La console d'un site mourait avant sa premiere vue, et le message accusait une methode manquante plutot qu'une forme qui ment. Et la vue regardait au mauvais endroit : le registre vide est voulu, mais la page en tirait le message d'un locataire sans serveurs et les douze machines servies dans hotes n'etaient dessinees nulle part. Une console de site dessine desormais SES machines, en lecture seule, avec leur adresse, leur VMID, leurs roles et leur point de vie ; le panneau droit dit ce qu'elle peut et renvoie aux Assistants. C'est le defaut du 16 septembre par une autre porte : la premiere fois l'inventaire etait vide et la page dessinait ce vide comme un plan vide ; cette fois il est plein et c'est la vue qui regarde ailleurs. Ce qui a trouve la cause n'est pas une lecture mais le BANC : en donnant pour la premiere fois a test_rendu_gui la charge d'une console de site, il a leve sur charger(). Sans lui j'aurais livre une vue juste par dessus un charger() qui leve. Valide : node --check sur le bloc script, 7 tests de rendu dont deux neufs, make test a 0 echec, runbooks.py verifier a 0 ecart, P81 et P83 vertes. P02 reste en echec pour la raison anterieure. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-20 20:36:00 -04:00
# LE BANC D'UNE CONSOLE DE SITE. On ne rejoue pas les neuf vues : on regarde CE QUE
# la vue d'accueil dessine, parce que c'est la qu'un site ne montrait rien.
APRES_SITE = r"""
charger().then(function () {
var sortie = {erreurs: [], grille: '', detail: '', detailChoisi: '', hotes: 0};
try {
sortie.hotes = (typeof hotes !== 'undefined' && hotes) ? hotes.length : 0;
setVue('serveurs');
sortie.grille = document.getElementById('grilles').innerHTML;
sortie.detail = document.getElementById('detail').innerHTML;
if (sortie.hotes) { choisirMachine(0); sortie.detailChoisi = document.getElementById('detail').innerHTML; }
} catch (e) { sortie.erreurs.push(String((e && e.stack) || e)); }
console.log('__RESULTAT__' + JSON.stringify(sortie));
}).catch(function (e) {
console.log('__RESULTAT__' + JSON.stringify({erreurs: ['charger() : ' + ((e && e.stack) || e)]}));
});
"""
class TestFormeDesCharges(unittest.TestCase):
"""Les deux charges que la page consomme ont-elles la MEME FORME ?
console : un tronc, deux branches — assembler deux fois est ce qui fait deriver Il y avait deux assembleurs de charge, un par sorte de console. Ils ont derive deux fois le meme jour : en forme (serveurs valait [] d'un cote et {} de l'autre, et charger() levait), puis en contenu (cinq registres servis vides alors que le site a SON plan — 9 serveurs, 21 applications, 2 bases, 1 domaine, tous invisibles). Console assemble, une seule fois, et fixe les clefs et leurs formes. Les branches ne decident que de ce qui leur appartient : d'ou vient le plan, d'ou vient l'inventaire, quels pouvoirs elles portent. ConsoleLocataire configure, ConsoleSite materialise et sert desormais son propre plan, ConsolePoste herite du locataire et sait en plus sur quelle fabric poser. Le role et la portee ne se confondent pas : le role est une propriete de la classe, la portee se calcule depuis les pouvoirs. Un poste prive de la voute du site reste l'atelier du mainteneur et n'engendre pourtant rien. Le decoupage a montre un trou aussitot : ConsolePoste heritait du refus d'un locataire — « elle ne sait pas sur quelle fabric poser » — alors qu'il monte la carte ; ce qui lui manque est la voute, et accuser la mauvaise absence fait chercher au mauvais endroit. Le jugement des assistants remonte dans le tronc : il etait ecrit dans la route qui liste ET dans celle qui execute, et celle qui se trompe est toujours celle qui execute. Valide : make test a 0 echec, 8 tests de rendu sous node dont deux neufs, console lancee pour de vrai (13 serveurs, 24 applications, 17 runbooks, 127 etapes, 0 ecart). make verifier a aussi attrape une faute que j'avais laissee passer sur depots_perimes.yml — risky-shell-pipe, corrige. P02 et P60 restent, et P60 demande une republication du wiki. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-20 21:55:49 -04:00
`ConsoleLocataire` et `ConsoleSite` servent la meme page : ce qu'elle sait lire d'un
cote, elle doit pouvoir le lire de l'autre. Depuis le decoupage en classes, c'est le
TRONC qui assemble — une seule `charge()` pour les deux — donc cette garde ne devrait
plus jamais se declencher. On la garde parce qu'une branche peut surcharger `charge()`
un jour, et parce qu'un test qui ne se declenche plus est precisement celui qui
prouve que la refonte tient.
console de site : un vide doit avoir la forme de ce qu'il remplace La console du runner de SITE-Chezlepro ne montrait rien. Elle a douze machines. inventaire_api sert serveurs en LISTE, inventaire_api_du_site le servait en TABLE. Les deux sont vides et la page ne les lit pas pareil : (data.serveurs || []).map trouve {} — truthy, sans .map — et charger() leve. La console d'un site mourait avant sa premiere vue, et le message accusait une methode manquante plutot qu'une forme qui ment. Et la vue regardait au mauvais endroit : le registre vide est voulu, mais la page en tirait le message d'un locataire sans serveurs et les douze machines servies dans hotes n'etaient dessinees nulle part. Une console de site dessine desormais SES machines, en lecture seule, avec leur adresse, leur VMID, leurs roles et leur point de vie ; le panneau droit dit ce qu'elle peut et renvoie aux Assistants. C'est le defaut du 16 septembre par une autre porte : la premiere fois l'inventaire etait vide et la page dessinait ce vide comme un plan vide ; cette fois il est plein et c'est la vue qui regarde ailleurs. Ce qui a trouve la cause n'est pas une lecture mais le BANC : en donnant pour la premiere fois a test_rendu_gui la charge d'une console de site, il a leve sur charger(). Sans lui j'aurais livre une vue juste par dessus un charger() qui leve. Valide : node --check sur le bloc script, 7 tests de rendu dont deux neufs, make test a 0 echec, runbooks.py verifier a 0 ecart, P81 et P83 vertes. P02 reste en echec pour la raison anterieure. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-20 20:36:00 -04:00
Le 2026-09-20, `serveurs` valait `[]` d'un cote et `{}` de l'autre. Les deux sont
« vides », et la page ne les lit pas pareil : `(data.serveurs || []).map(...)` trouve
une table — truthy, sans `.map` — et `charger()` LEVE. La console d'un site mourait
avant de dessiner, et l'erreur accusait une methode manquante plutot qu'une forme.
Un vide doit avoir la FORME de ce qu'il remplace. Ce test ne demande pas que les deux
charges portent les memes VALEURS — un site n'a pas de plan de locataire — seulement
qu'elles portent les memes TYPES. Ce test n'a pas besoin de node.
"""
# Ce que les deux charges ne partagent legitimement pas, et pourquoi.
ECARTS_ADMIS = {
"contexte": "ajoute par la route GET pour un locataire, deja present pour un site",
"intrants": "un site n'a pas de plan de locataire dont tirer des intrants",
}
def test_les_deux_charges_portent_les_memes_types(self):
if not g.FICHIER_UNDERLAY.exists():
self.skipTest("aucune fabric montee : pas de charge de site a comparer")
console : un tronc, deux branches — assembler deux fois est ce qui fait deriver Il y avait deux assembleurs de charge, un par sorte de console. Ils ont derive deux fois le meme jour : en forme (serveurs valait [] d'un cote et {} de l'autre, et charger() levait), puis en contenu (cinq registres servis vides alors que le site a SON plan — 9 serveurs, 21 applications, 2 bases, 1 domaine, tous invisibles). Console assemble, une seule fois, et fixe les clefs et leurs formes. Les branches ne decident que de ce qui leur appartient : d'ou vient le plan, d'ou vient l'inventaire, quels pouvoirs elles portent. ConsoleLocataire configure, ConsoleSite materialise et sert desormais son propre plan, ConsolePoste herite du locataire et sait en plus sur quelle fabric poser. Le role et la portee ne se confondent pas : le role est une propriete de la classe, la portee se calcule depuis les pouvoirs. Un poste prive de la voute du site reste l'atelier du mainteneur et n'engendre pourtant rien. Le decoupage a montre un trou aussitot : ConsolePoste heritait du refus d'un locataire — « elle ne sait pas sur quelle fabric poser » — alors qu'il monte la carte ; ce qui lui manque est la voute, et accuser la mauvaise absence fait chercher au mauvais endroit. Le jugement des assistants remonte dans le tronc : il etait ecrit dans la route qui liste ET dans celle qui execute, et celle qui se trompe est toujours celle qui execute. Valide : make test a 0 echec, 8 tests de rendu sous node dont deux neufs, console lancee pour de vrai (13 serveurs, 24 applications, 17 runbooks, 127 etapes, 0 ecart). make verifier a aussi attrape une faute que j'avais laissee passer sur depots_perimes.yml — risky-shell-pipe, corrige. P02 et P60 restent, et P60 demande une republication du wiki. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-20 21:55:49 -04:00
site = g.ConsoleSite(g._depot_hebergeur(), True).charge()
tenant = g.ConsoleLocataire(g.INSTANCE, g.INVENTAIRE_DEFAUT, True).charge()
console de site : un vide doit avoir la forme de ce qu'il remplace La console du runner de SITE-Chezlepro ne montrait rien. Elle a douze machines. inventaire_api sert serveurs en LISTE, inventaire_api_du_site le servait en TABLE. Les deux sont vides et la page ne les lit pas pareil : (data.serveurs || []).map trouve {} — truthy, sans .map — et charger() leve. La console d'un site mourait avant sa premiere vue, et le message accusait une methode manquante plutot qu'une forme qui ment. Et la vue regardait au mauvais endroit : le registre vide est voulu, mais la page en tirait le message d'un locataire sans serveurs et les douze machines servies dans hotes n'etaient dessinees nulle part. Une console de site dessine desormais SES machines, en lecture seule, avec leur adresse, leur VMID, leurs roles et leur point de vie ; le panneau droit dit ce qu'elle peut et renvoie aux Assistants. C'est le defaut du 16 septembre par une autre porte : la premiere fois l'inventaire etait vide et la page dessinait ce vide comme un plan vide ; cette fois il est plein et c'est la vue qui regarde ailleurs. Ce qui a trouve la cause n'est pas une lecture mais le BANC : en donnant pour la premiere fois a test_rendu_gui la charge d'une console de site, il a leve sur charger(). Sans lui j'aurais livre une vue juste par dessus un charger() qui leve. Valide : node --check sur le bloc script, 7 tests de rendu dont deux neufs, make test a 0 echec, runbooks.py verifier a 0 ecart, P81 et P83 vertes. P02 reste en echec pour la raison anterieure. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-20 20:36:00 -04:00
ecarts = []
for clef in sorted(set(tenant) & set(site)):
if clef in self.ECARTS_ADMIS:
continue
a, b = type(tenant[clef]).__name__, type(site[clef]).__name__
if a != b:
ecarts.append(f"{clef} : locataire={a}, site={b}")
self.assertEqual(ecarts, [],
"des clefs changent de type selon la console :\n - "
+ "\n - ".join(ecarts))
def test_un_ecart_admis_doit_encore_exister(self):
"""Une exemption qui survit a son objet est un mensonge tranquille."""
tenant = g.inventaire_api(g.INVENTAIRE_DEFAUT)
for clef in self.ECARTS_ADMIS:
self.assertTrue(clef in tenant or clef == "contexte",
f"{clef} n'existe plus : son exemption n'a plus d'objet")
GUI : la vue Nomenclature, et deux fautes que mes bancs ne voyaient pas 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
2026-09-08 17:08:45 -04:00
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>"
console de site : un vide doit avoir la forme de ce qu'il remplace La console du runner de SITE-Chezlepro ne montrait rien. Elle a douze machines. inventaire_api sert serveurs en LISTE, inventaire_api_du_site le servait en TABLE. Les deux sont vides et la page ne les lit pas pareil : (data.serveurs || []).map trouve {} — truthy, sans .map — et charger() leve. La console d'un site mourait avant sa premiere vue, et le message accusait une methode manquante plutot qu'une forme qui ment. Et la vue regardait au mauvais endroit : le registre vide est voulu, mais la page en tirait le message d'un locataire sans serveurs et les douze machines servies dans hotes n'etaient dessinees nulle part. Une console de site dessine desormais SES machines, en lecture seule, avec leur adresse, leur VMID, leurs roles et leur point de vie ; le panneau droit dit ce qu'elle peut et renvoie aux Assistants. C'est le defaut du 16 septembre par une autre porte : la premiere fois l'inventaire etait vide et la page dessinait ce vide comme un plan vide ; cette fois il est plein et c'est la vue qui regarde ailleurs. Ce qui a trouve la cause n'est pas une lecture mais le BANC : en donnant pour la premiere fois a test_rendu_gui la charge d'une console de site, il a leve sur charger(). Sans lui j'aurais livre une vue juste par dessus un charger() qui leve. Valide : node --check sur le bloc script, 7 tests de rendu dont deux neufs, make test a 0 echec, runbooks.py verifier a 0 ecart, P81 et P83 vertes. P02 reste en echec pour la raison anterieure. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-20 20:36:00 -04:00
cls.blocs = blocs
GUI : la vue Nomenclature, et deux fautes que mes bancs ne voyaient pas 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
2026-09-08 17:08:45 -04:00
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))
console de site : un vide doit avoir la forme de ce qu'il remplace La console du runner de SITE-Chezlepro ne montrait rien. Elle a douze machines. inventaire_api sert serveurs en LISTE, inventaire_api_du_site le servait en TABLE. Les deux sont vides et la page ne les lit pas pareil : (data.serveurs || []).map trouve {} — truthy, sans .map — et charger() leve. La console d'un site mourait avant sa premiere vue, et le message accusait une methode manquante plutot qu'une forme qui ment. Et la vue regardait au mauvais endroit : le registre vide est voulu, mais la page en tirait le message d'un locataire sans serveurs et les douze machines servies dans hotes n'etaient dessinees nulle part. Une console de site dessine desormais SES machines, en lecture seule, avec leur adresse, leur VMID, leurs roles et leur point de vie ; le panneau droit dit ce qu'elle peut et renvoie aux Assistants. C'est le defaut du 16 septembre par une autre porte : la premiere fois l'inventaire etait vide et la page dessinait ce vide comme un plan vide ; cette fois il est plein et c'est la vue qui regarde ailleurs. Ce qui a trouve la cause n'est pas une lecture mais le BANC : en donnant pour la premiere fois a test_rendu_gui la charge d'une console de site, il a leve sur charger(). Sans lui j'aurais livre une vue juste par dessus un charger() qui leve. Valide : node --check sur le bloc script, 7 tests de rendu dont deux neufs, make test a 0 echec, runbooks.py verifier a 0 ecart, P81 et P83 vertes. P02 reste en echec pour la raison anterieure. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-20 20:36:00 -04:00
def test_une_console_de_site_montre_ses_machines(self):
"""UN SITE A DOUZE MACHINES ET SA CONSOLE N'EN MONTRAIT AUCUNE (2026-09-20).
`inventaire_api_du_site` sert volontairement `serveurs: {}` — un site declare ses
machines dans SON plan, avec `ip` et `vmid` ecrits, la ou un locataire les derive.
Mais la vue d'accueil tirait de ce registre vide le message d'un locataire sans
serveurs (« + Serveur », `make serveurs-bootstrap`), et les machines servies dans
`hotes` n'etaient dessinees nulle part.
C'est le defaut du 2026-09-16 par une autre porte : la premiere fois l'inventaire
etait vide ; cette fois il est PLEIN et c'est la vue qui regarde au mauvais endroit.
"""
if not g.FICHIER_UNDERLAY.exists():
self.skipTest("aucune fabric montee : pas de console de site a simuler")
console : un tronc, deux branches — assembler deux fois est ce qui fait deriver Il y avait deux assembleurs de charge, un par sorte de console. Ils ont derive deux fois le meme jour : en forme (serveurs valait [] d'un cote et {} de l'autre, et charger() levait), puis en contenu (cinq registres servis vides alors que le site a SON plan — 9 serveurs, 21 applications, 2 bases, 1 domaine, tous invisibles). Console assemble, une seule fois, et fixe les clefs et leurs formes. Les branches ne decident que de ce qui leur appartient : d'ou vient le plan, d'ou vient l'inventaire, quels pouvoirs elles portent. ConsoleLocataire configure, ConsoleSite materialise et sert desormais son propre plan, ConsolePoste herite du locataire et sait en plus sur quelle fabric poser. Le role et la portee ne se confondent pas : le role est une propriete de la classe, la portee se calcule depuis les pouvoirs. Un poste prive de la voute du site reste l'atelier du mainteneur et n'engendre pourtant rien. Le decoupage a montre un trou aussitot : ConsolePoste heritait du refus d'un locataire — « elle ne sait pas sur quelle fabric poser » — alors qu'il monte la carte ; ce qui lui manque est la voute, et accuser la mauvaise absence fait chercher au mauvais endroit. Le jugement des assistants remonte dans le tronc : il etait ecrit dans la route qui liste ET dans celle qui execute, et celle qui se trompe est toujours celle qui execute. Valide : make test a 0 echec, 8 tests de rendu sous node dont deux neufs, console lancee pour de vrai (13 serveurs, 24 applications, 17 runbooks, 127 etapes, 0 ecart). make verifier a aussi attrape une faute que j'avais laissee passer sur depots_perimes.yml — risky-shell-pipe, corrige. P02 et P60 restent, et P60 demande une republication du wiki. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-20 21:55:49 -04:00
payload = g.ConsoleSite(g._depot_hebergeur(), True).charge()
console de site : un vide doit avoir la forme de ce qu'il remplace La console du runner de SITE-Chezlepro ne montrait rien. Elle a douze machines. inventaire_api sert serveurs en LISTE, inventaire_api_du_site le servait en TABLE. Les deux sont vides et la page ne les lit pas pareil : (data.serveurs || []).map trouve {} — truthy, sans .map — et charger() leve. La console d'un site mourait avant sa premiere vue, et le message accusait une methode manquante plutot qu'une forme qui ment. Et la vue regardait au mauvais endroit : le registre vide est voulu, mais la page en tirait le message d'un locataire sans serveurs et les douze machines servies dans hotes n'etaient dessinees nulle part. Une console de site dessine desormais SES machines, en lecture seule, avec leur adresse, leur VMID, leurs roles et leur point de vie ; le panneau droit dit ce qu'elle peut et renvoie aux Assistants. C'est le defaut du 16 septembre par une autre porte : la premiere fois l'inventaire etait vide et la page dessinait ce vide comme un plan vide ; cette fois il est plein et c'est la vue qui regarde ailleurs. Ce qui a trouve la cause n'est pas une lecture mais le BANC : en donnant pour la premiere fois a test_rendu_gui la charge d'une console de site, il a leve sur charger(). Sans lui j'aurais livre une vue juste par dessus un charger() qui leve. Valide : node --check sur le bloc script, 7 tests de rendu dont deux neufs, make test a 0 echec, runbooks.py verifier a 0 ecart, P81 et P83 vertes. P02 reste en echec pour la raison anterieure. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-20 20:36:00 -04:00
if not payload["hotes"]:
self.skipTest("l'inventaire du site ne rend aucune machine ici")
js = BANC + "\n".join(self.blocs) + APRES_SITE
r = self._jouer(js, payload)
self.assertEqual(r["erreurs"], [], "la vue a leve :\n - " + "\n - ".join(r["erreurs"]))
self.assertEqual(r["hotes"], len(payload["hotes"]))
noms = [h["nom"] for h in payload["hotes"]]
manquants = [n for n in noms if n not in r["grille"]]
self.assertEqual(manquants, [],
f"des machines du site ne sont pas dessinees : {manquants}")
# Et surtout : plus le message d'un locataire vide, qui envoyait l'exploitant
# vers un amorcage de plan que ce site n'a pas.
self.assertNotIn("serveurs-bootstrap", r["grille"])
self.assertIn("Machines de", r["grille"])
# Le panneau droit dit ce que cette console PEUT, au lieu de rester muet.
self.assertIn("matérialise", r["detail"])
self.assertIn(noms[0], r["detailChoisi"])
console : un tronc, deux branches — assembler deux fois est ce qui fait deriver Il y avait deux assembleurs de charge, un par sorte de console. Ils ont derive deux fois le meme jour : en forme (serveurs valait [] d'un cote et {} de l'autre, et charger() levait), puis en contenu (cinq registres servis vides alors que le site a SON plan — 9 serveurs, 21 applications, 2 bases, 1 domaine, tous invisibles). Console assemble, une seule fois, et fixe les clefs et leurs formes. Les branches ne decident que de ce qui leur appartient : d'ou vient le plan, d'ou vient l'inventaire, quels pouvoirs elles portent. ConsoleLocataire configure, ConsoleSite materialise et sert desormais son propre plan, ConsolePoste herite du locataire et sait en plus sur quelle fabric poser. Le role et la portee ne se confondent pas : le role est une propriete de la classe, la portee se calcule depuis les pouvoirs. Un poste prive de la voute du site reste l'atelier du mainteneur et n'engendre pourtant rien. Le decoupage a montre un trou aussitot : ConsolePoste heritait du refus d'un locataire — « elle ne sait pas sur quelle fabric poser » — alors qu'il monte la carte ; ce qui lui manque est la voute, et accuser la mauvaise absence fait chercher au mauvais endroit. Le jugement des assistants remonte dans le tronc : il etait ecrit dans la route qui liste ET dans celle qui execute, et celle qui se trompe est toujours celle qui execute. Valide : make test a 0 echec, 8 tests de rendu sous node dont deux neufs, console lancee pour de vrai (13 serveurs, 24 applications, 17 runbooks, 127 etapes, 0 ecart). make verifier a aussi attrape une faute que j'avais laissee passer sur depots_perimes.yml — risky-shell-pipe, corrige. P02 et P60 restent, et P60 demande une republication du wiki. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-20 21:55:49 -04:00
def test_une_console_de_site_sert_son_propre_plan(self):
"""UN SITE A UN PLAN, ET ON LE LUI SERVAIT VIDE (2026-09-20).
Cinq registres partaient vides « pour ne pas fabriquer un faux plan de tenant ».
Le site n'a pas de faux plan : il a LE SIEN, au meme format, que les lecteurs du
moteur lisent sans broncher. Mesure du jour : 9 serveurs, 21 applications.
"""
if not g.FICHIER_UNDERLAY.exists():
self.skipTest("aucune fabric montee")
depot = g._depot_hebergeur()
charge = g.ConsoleSite(depot, True).charge()
for registre, fichier in (("applications", "applications.yml"),
("bases", "bases-donnees.yml"),
("domaines", "domaines.yml")):
if not (depot / "plan" / fichier).is_file():
continue
interne = charge[registre].get(registre) or charge[registre]
self.assertTrue(interne,
f"{registre} est servi vide alors que {fichier} en declare")
if (depot / "plan/serveurs.yml").is_file():
self.assertTrue(charge["serveurs"],
"le site declare des serveurs et la charge n'en porte aucun")
# Et leur FORME est celle que la page sait lire, pas la table du plan.
self.assertIsInstance(charge["serveurs"], list)
premier = charge["serveurs"][0]
for clef in ("nom", "etat", "adresse_ip", "vmid", "statut"):
self.assertIn(clef, premier)
GUI : la vue Nomenclature, et deux fautes que mes bancs ne voyaient pas 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
2026-09-08 17:08:45 -04:00
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}")
GUI : les six registres ont un formulaire genere, et la sauvegarde aussi CHAMPS_ECRITS_A_LA_MAIN est vide. Serveurs et applications, les deux plus gros, sont passes au generateur — chargement, rendu et sauvegarde. L EPREUVE QUI COMPTE. Ouvrir chaque vue et enregistrer sans rien toucher doit renvoyer exactement le plan qu on vient de lire : 14 serveurs, 25 applications, 2 domaines, 4 bases, IDENTIQUE partout. C est ce qui separe un formulaire genere d un formulaire qui en a l air — un champ visible a l ecran et perdu en silence a l enregistrement serait le pire des deux mondes. test_rendu_gui.py le mesure a chaque make prouver. TROIS DEFAUTS TROUVES EN CHEMIN. Le formulaire annoncait des defauts INVENTES : 2048 Mo, 2 coeurs, 16G. Il n existe aucun defaut fixe — deriver_ressources calcule depuis les roles portes (1024 et 1 pour infra-pki-01, 5632 et 4 pour collab-01). Un repere faux fait croire qu on connait la valeur. Le schema nomme le champ derive, et l ecran montre la valeur reelle de cet hote. L option vide d un select dit desormais ce qu elle produira : « (defaut : asgard) ». Une SECONDE occurrence du defaut d hier dormait dans sourceDeValeurs : elle lisait encore data.nomenclature. Elle n avait jamais leve parce que la vue Serveurs, seule a emprunter cette source, avait un formulaire ecrit a la main. Elle a leve a la seconde ou le generateur l a prise. Le banc ne voit que les chemins vivants : verifier_gui.py fait donc aussi une verification STATIQUE, qui voit ce qui dort. La validation client s accrochait a data-v, pose a la main sur trois champs. Le formulaire genere l aurait perdu et la validation serait passee au vert sur ZERO champ. Le generateur marque chaque controle, et la sauvegarde refuse si elle n en inspecte aucun. DEUX CHAMPS GARDENT LEUR EDITEUR, et le schema le dit (x-editeur) : la matrice des integrations montre les universelles et les exemptions, et l editeur de liens contraint le role a meta/liens.yml. Le generateur s efface plutot que de remplacer un editeur qui en sait plus que lui. LIMITE : je n ai toujours pas ouvert ces pages dans un navigateur. make prouver : CONFORME, 61 OK, 0 echec, 1 saute. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-08 18:26:33 -04:00
def test_charger_puis_sauvegarder_ne_change_pas_le_plan(self):
"""L'EPREUVE DECISIVE : ouvrir une vue et enregistrer sans rien toucher.
Les six formulaires sont GENERES, et leur sauvegarde l'est aussi : les champs
ecrits sont ceux que le schema declare. C'est ce qui evite le pire des deux
mondes — un champ visible a l'ecran et perdu en silence a l'enregistrement.
Encore faut-il le mesurer. On charge la vraie reponse de l'API, on ouvre chaque
vue, on appelle sa sauvegarde, on intercepte le POST, et on compare la charge au
plan REEL lu par le moteur. Aucune entite perdue, aucune en trop, aucun champ
different : sinon un simple aller-retour appauvrirait le plan.
"""
js = self.js.replace(APRES, r"""
charger()
.then(function () { setVue('serveurs'); choisirServeur(0); return sauvegarderServeurs(); })
.then(function () { setVue('applications'); if (applications.length) ouvrirApp(applications[0].id); return sauvegarderApplications(); })
.then(function () { setVue('domaines'); if (domaines.length) choisirDomaine(0); return sauvegarderDomaines(); })
.then(function () { setVue('bases'); return sauvegarderBases(); })
.then(function () { console.log('__RESULTAT__' + JSON.stringify(__postsCaptures)); })
.catch(function (e) { console.log('__RESULTAT__' + JSON.stringify({erreur: String(e && e.stack || e)})); });
""")
payload = g.inventaire_api(g.INVENTAIRE_DEFAUT)
posts = self._jouer(js, payload)
self.assertNotIn("erreur", posts, posts.get("erreur", ""))
attendu = {
"/api/serveurs": (g.charger_serveurs(g.FICHIER_SERVEURS), "serveurs"),
"/api/applications": (g.charger_applications(g.FICHIER_APPLICATIONS), "applications"),
"/api/domaines": (g.charger_domaines(g.FICHIER_DOMAINES), "domaines_publics"),
"/api/bases": (g.charger_bases_donnees(g.FICHIER_BASES), "bases_donnees"),
}
for url, (plan, racine) in attendu.items():
with self.subTest(route=url):
envoye = (posts.get(url) or {}).get(racine)
self.assertIsNotNone(envoye, f"{url} n'a pas ete appele — la sauvegarde a-t-elle refuse ?")
reel = plan.get(racine) or {}
self.assertEqual(sorted(envoye), sorted(reel), "entites perdues ou ajoutees")
for cle in sorted(reel):
self.assertEqual(envoye[cle], reel[cle],
f"{racine}.{cle} : l'aller-retour a change l'entite")
GUI : la vue Nomenclature, et deux fautes que mes bancs ne voyaient pas 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
2026-09-08 17:08:45 -04:00
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()