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

171 lines
7.2 KiB
Python
Raw Normal View History

#!/usr/bin/env python3
adressage : l'index est borne [0-255] — 10.300.0.0/16 n'est pas un reseau Rien ne bornait `index`. supernet_de(300) rendait « 10.300.0.0/16 » : une CHAINE qui ressemble a un reseau. Elle traverse tout le moteur sans bruit et n'echoue qu'au premier ip_network() qui la lit, tres loin de l'index fautif. La borne est posee A LA SOURCE (valider_index), pas dans un validateur de plan : supernet_de, base3_de et vlan_de y passent toutes, donc aucune ne peut fabriquer une adresse invalide — d'ou qu'on l'appelle : plan, GUI, devis ou test. Elle protege un SECOND plafond, moins visible : a l'index 255 le VLAN vaut 3550+zone, sous les 4094 du 802.1Q. Un index a trois chiffres debordait aussi la. Garde statique ajoutee au controle de federation (P21) : elle nomme le depot fautif au lieu de laisser l'erreur remonter d'une bibliotheque. LE TEST A TROUVE CE QUE LA RELECTURE N'AVAIT PAS VU : valider_index n'attrapait que TypeError et ValueError, or int(float('inf')) leve OverflowError — un infini faisait PLANTER la garde au lieu d'etre refuse. test_underlay_bande_basse.py -> test_adressage_derive.py (il ne parlait plus seulement de la bande basse). 12 cas, dont les refus. Piege de structure au passage : les nouveaux cas, ajoutes apres le bloc `if __name__ == "__main__"`, ne s'executaient pas — le bloc tourne avant que les fonctions suivantes ne soient definies, et le compte affichait « 7 tests » au lieu de 12. Un harnais qui compte ses propres tests doit etre lu. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-12 16:21:33 -04:00
"""Adressage derive : la bande basse de l'underlay (D-77) et les bornes de l'index.
Ce que ce test garde, et pourquoi il existe. La regle « underlay dans la bande basse »
n'est sure que parce que les zones d'un tenant commencent a `OCTET_ZONE + 1`. C'est une
propriete de `inventory_rules`, pas une place qu'on aurait constatee libre un jour : si
`OCTET_ZONE` bougeait sans que la borne suive, la validation continuerait a dire « oui »
en laissant l'underlay recouvrir les zones — et personne ne le verrait avant que deux
machines ne se disputent une adresse.
Le test derive donc sa frontiere de la MEME constante que le code, et verifie les trois
cas qui comptent, y compris ceux ou un prefixe FRANCHIT la frontiere (un /19 couvre les
octets 0 a 31 : il deborde, meme s'il commence dans la bande basse).
"""
import sys
from pathlib import Path
RACINE = Path(__file__).resolve().parent.parent.parent
sys.path.insert(0, str(RACINE / "scripts"))
import underlay as u # noqa: E402
from inventory_rules import OCTET_ZONE # noqa: E402
insemination : declarer le lien, l'emettre d'un seul cote, et un test rouge L'insemination avait un nom depuis ce matin ; elle n'avait pas de flux. Deux declarations, aux deux bouts, et rien d'autre : serveur_ops_site egress 22/tcp -> serveur_ops_tenant serveur_ops_tenant ingress 22/tcp <- runner_site partage: true ETROIT PAR CONSTRUCTION : il vise le GROUPE `serveur_ops_tenant`, qu'un ecosysteme ne pose que sur une machine. Au socle, il aurait ouvert le SSH du site vers toute la flotte du tenant. L'en-tete disait « ce role n'entre JAMAIS chez un tenant ». Frontiere intenable : `creer-vm` exige `_instance-requise`, et le runner du site avait deja du basculer son symlink `instance` sur OPS-Chezlepro pour materialiser ses VM. Declarer ne cree pas ce pouvoir — ca rend limitable un pouvoir qui s'exercait sans borne. Ce qui reste interdit n'est pas une regle mais un FAIT : il n'a pas la voute du tenant. LA REGLE EST EMISE D'UN SEUL COTE, et pas celui qu'on croit. Le paquet penetre le pare-feu par la patte du SITE, pas par le transit : la regle appartient au cote site du devis. L'emettre aussi depuis l'`ingress` du tenant aurait produit une seconde regle sur la mauvaise interface — jamais evaluee, indiscernable d'une regle utile. La declaration du tenant pose sa regle nftables, et elle seule : ip saddr { 10.0.31.11 } tcp dport 22 accept L'adresse DERIVE du plan du site. Ecrite a la main, elle aurait survecu au prochain deplacement du runner sans bruit — le site a deja deplace ses machines le 08-25. Plan de la frontiere : 2 objets a creer, 0 a retirer, 126 inchanges. RIEN D'APPLIQUE. P41 APPLIQUEE AU PLAN DU SITE : `resoudre_flux` en avait besoin a son tour ; les trois lecteurs demenagent dans `underlay` et `devis_opnsense` delegue. DEUX GARDES ONT TRAVAILLE : P33 a refuse `ingress 22` sur un hote portant deja le sshd du socle (reponse : `partage: true`, comme `serveur_backup`), et le devis a refuse d'emettre vers un alias vide. UN TEST ROUGE DEPUIS TROIS JOURS. `test_adressage_derive` construisait un site avec un `index` — or un SITE n'en a pas depuis add94f2 (08-25), remplace par `bande_basse_de`. Invisible parce que le geste quotidien est `make prouver`, qui ne joue pas les tests. Remis sur le contrat actuel, avec sa contrepartie : sans `bande_basse_de`, aucun chevauchement n'est tolere. make verifier : vert. make prouver : CONFORME, 52 OK, 0 echec. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019f91zs9SDdwSzL2CVei6on
2026-08-28 13:09:28 -04:00
SUPERNETS = ["10.11.0.0/16", "10.17.0.0/16"] # deux tenants : index 11 et 17
MIEN = "OPS-Mien" # le tenant dont ce site occupe la bande basse
MIEN_INDEX = 11
# UN SITE N'A PAS D'INDEX (add94f2, 2026-08-25). Il ne derive pas : il DECLARE ses
# adresses, parce qu'il EST le terrain. Ce qui a remplace l'index, c'est `bande_basse_de`
# — porte par CHAQUE reseau, et non par le site : deux reseaux d'un meme site peuvent
# occuper la bande basse de deux tenants differents.
#
# CE TEST A SURVECU TROIS JOURS A CE CHANGEMENT EN ETANT ROUGE (2026-08-25 -> 08-28), et
# personne ne l'a vu : le geste quotidien est `make prouver`, qui ne joue pas les tests.
# Un test rouge que rien ne lit ne garde plus rien.
def _erreurs(sous: str, mien: str | None = MIEN) -> list[str]:
reseau = {"nom": "g", "vlan": 10, "sous_reseau": sous}
if mien is not None:
reseau["bande_basse_de"] = mien
site = {"reseaux": [reseau], "tenants": {MIEN: MIEN_INDEX}}
return [e for e in u.valider(site, tenants_vlans=set(), tenants_supernets=SUPERNETS)
if "deborde" in e or "chevauche le supernet" in e]
def test_la_borne_derive_de_la_regle_des_zones():
"""La frontiere n'est pas 16 en dur : elle suit `OCTET_ZONE`."""
assert u.PREMIER_OCTET_ZONE == OCTET_ZONE + 1
def test_bande_basse_de_son_propre_supernet_acceptee():
adressage : le decalage de +10 est retire, l'index se lit dans l'adresse supernet_de rendait 10.(10+index).0.0/16. Personne ne savait plus pourquoi : ni le commentaire de la constante, ni le wiki, ni le commit fondateur 36a882b ne le justifiaient. Trois endroits consultes, zero raison ecrite. Ses deux effets constates : - il reservait 10.0-10.9 sous la plage tenant. Utile tant que l'underlay vivait la — mais D-77 l'a fait entrer dans la bande basse de son propre /16, ce qui a vide cette reserve de son role la veille ; - il eloignait le premier tenant de 10.0.0.0/16, la plage la plus repandue en reseau domestique. Ce risque revient donc aux index bas, et c'est ASSUME. En echange l'index se lit directement dans l'adresse (17 -> 10.17.x.x) et le plafond passe de 245 a 255 ecosystemes federes. Chezlepro (17) 10.27.0.0/16 -> 10.17.0.0/16 Technolibre (11) 10.21.0.0/16 -> 10.11.0.0/16 lab (1) 10.11.0.0/16 -> 10.1.0.0/16 Doc alignee : les trois pages du wiki, multi-instances.md (plafond et exemple devenus faux arithmetiquement), sdn-evpn.md, le libelle de la GUI, la docstring d'underlay.py, D-77, et le document de preparation d'un site hebergeur. Les CONSTATS DE TERRAIN dates sont laisses tels quels : ce sont des mesures. CE COMMIT NE RENUMEROTE RIEN. Il change ce que le plan DERIVE ; l'inventaire applique porte toujours 10.27.x.x et les quatorze VM tournent dessus. Appliquer sans reconstruire rendrait la flotte injoignable — le renumerotage est une operation a part, a mener a froid. Au passage, retire un debris : une copie de conflit Nextcloud de serveur_powerdns/defaults/main.yml, IDENTIQUE a l'original, commitee par accident dans 1295eea et jamais chargee par Ansible. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-12 16:16:08 -04:00
for sous in ("10.11.0.0/24", "10.11.4.0/24", "10.11.5.0/24", "10.11.15.0/24"):
assert _erreurs(sous) == [], f"{sous} devrait etre conforme (bande basse)"
def test_bande_des_zones_refusee():
adressage : le decalage de +10 est retire, l'index se lit dans l'adresse supernet_de rendait 10.(10+index).0.0/16. Personne ne savait plus pourquoi : ni le commentaire de la constante, ni le wiki, ni le commit fondateur 36a882b ne le justifiaient. Trois endroits consultes, zero raison ecrite. Ses deux effets constates : - il reservait 10.0-10.9 sous la plage tenant. Utile tant que l'underlay vivait la — mais D-77 l'a fait entrer dans la bande basse de son propre /16, ce qui a vide cette reserve de son role la veille ; - il eloignait le premier tenant de 10.0.0.0/16, la plage la plus repandue en reseau domestique. Ce risque revient donc aux index bas, et c'est ASSUME. En echange l'index se lit directement dans l'adresse (17 -> 10.17.x.x) et le plafond passe de 245 a 255 ecosystemes federes. Chezlepro (17) 10.27.0.0/16 -> 10.17.0.0/16 Technolibre (11) 10.21.0.0/16 -> 10.11.0.0/16 lab (1) 10.11.0.0/16 -> 10.1.0.0/16 Doc alignee : les trois pages du wiki, multi-instances.md (plafond et exemple devenus faux arithmetiquement), sdn-evpn.md, le libelle de la GUI, la docstring d'underlay.py, D-77, et le document de preparation d'un site hebergeur. Les CONSTATS DE TERRAIN dates sont laisses tels quels : ce sont des mesures. CE COMMIT NE RENUMEROTE RIEN. Il change ce que le plan DERIVE ; l'inventaire applique porte toujours 10.27.x.x et les quatorze VM tournent dessus. Appliquer sans reconstruire rendrait la flotte injoignable — le renumerotage est une operation a part, a mener a froid. Au passage, retire un debris : une copie de conflit Nextcloud de serveur_powerdns/defaults/main.yml, IDENTIQUE a l'original, commitee par accident dans 1295eea et jamais chargee par Ansible. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-12 16:16:08 -04:00
for sous in (f"10.11.{OCTET_ZONE + 1}.0/24", "10.11.18.0/24", "10.11.16.0/20"):
assert _erreurs(sous), f"{sous} devrait etre REFUSE (bande des zones)"
def test_un_prefixe_qui_franchit_la_frontiere_est_refuse():
"""Le piege : il COMMENCE dans la bande basse et deborde. Un test qui ne
regarderait que le premier octet le laisserait passer."""
adressage : le decalage de +10 est retire, l'index se lit dans l'adresse supernet_de rendait 10.(10+index).0.0/16. Personne ne savait plus pourquoi : ni le commentaire de la constante, ni le wiki, ni le commit fondateur 36a882b ne le justifiaient. Trois endroits consultes, zero raison ecrite. Ses deux effets constates : - il reservait 10.0-10.9 sous la plage tenant. Utile tant que l'underlay vivait la — mais D-77 l'a fait entrer dans la bande basse de son propre /16, ce qui a vide cette reserve de son role la veille ; - il eloignait le premier tenant de 10.0.0.0/16, la plage la plus repandue en reseau domestique. Ce risque revient donc aux index bas, et c'est ASSUME. En echange l'index se lit directement dans l'adresse (17 -> 10.17.x.x) et le plafond passe de 245 a 255 ecosystemes federes. Chezlepro (17) 10.27.0.0/16 -> 10.17.0.0/16 Technolibre (11) 10.21.0.0/16 -> 10.11.0.0/16 lab (1) 10.11.0.0/16 -> 10.1.0.0/16 Doc alignee : les trois pages du wiki, multi-instances.md (plafond et exemple devenus faux arithmetiquement), sdn-evpn.md, le libelle de la GUI, la docstring d'underlay.py, D-77, et le document de preparation d'un site hebergeur. Les CONSTATS DE TERRAIN dates sont laisses tels quels : ce sont des mesures. CE COMMIT NE RENUMEROTE RIEN. Il change ce que le plan DERIVE ; l'inventaire applique porte toujours 10.27.x.x et les quatorze VM tournent dessus. Appliquer sans reconstruire rendrait la flotte injoignable — le renumerotage est une operation a part, a mener a froid. Au passage, retire un debris : une copie de conflit Nextcloud de serveur_powerdns/defaults/main.yml, IDENTIQUE a l'original, commitee par accident dans 1295eea et jamais chargee par Ansible. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-12 16:16:08 -04:00
for sous in ("10.11.0.0/19", "10.11.0.0/16"):
assert _erreurs(sous), f"{sous} couvre au-dela de {OCTET_ZONE} : a refuser"
def test_supernet_d_un_autre_site_refuse():
"""C'est ce qui empeche deux sites d'etre relies — l'erreur la plus couteuse."""
adressage : le decalage de +10 est retire, l'index se lit dans l'adresse supernet_de rendait 10.(10+index).0.0/16. Personne ne savait plus pourquoi : ni le commentaire de la constante, ni le wiki, ni le commit fondateur 36a882b ne le justifiaient. Trois endroits consultes, zero raison ecrite. Ses deux effets constates : - il reservait 10.0-10.9 sous la plage tenant. Utile tant que l'underlay vivait la — mais D-77 l'a fait entrer dans la bande basse de son propre /16, ce qui a vide cette reserve de son role la veille ; - il eloignait le premier tenant de 10.0.0.0/16, la plage la plus repandue en reseau domestique. Ce risque revient donc aux index bas, et c'est ASSUME. En echange l'index se lit directement dans l'adresse (17 -> 10.17.x.x) et le plafond passe de 245 a 255 ecosystemes federes. Chezlepro (17) 10.27.0.0/16 -> 10.17.0.0/16 Technolibre (11) 10.21.0.0/16 -> 10.11.0.0/16 lab (1) 10.11.0.0/16 -> 10.1.0.0/16 Doc alignee : les trois pages du wiki, multi-instances.md (plafond et exemple devenus faux arithmetiquement), sdn-evpn.md, le libelle de la GUI, la docstring d'underlay.py, D-77, et le document de preparation d'un site hebergeur. Les CONSTATS DE TERRAIN dates sont laisses tels quels : ce sont des mesures. CE COMMIT NE RENUMEROTE RIEN. Il change ce que le plan DERIVE ; l'inventaire applique porte toujours 10.27.x.x et les quatorze VM tournent dessus. Appliquer sans reconstruire rendrait la flotte injoignable — le renumerotage est une operation a part, a mener a froid. Au passage, retire un debris : une copie de conflit Nextcloud de serveur_powerdns/defaults/main.yml, IDENTIQUE a l'original, commitee par accident dans 1295eea et jamais chargee par Ansible. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-12 16:16:08 -04:00
assert _erreurs("10.17.0.0/24"), "le supernet d'un autre site doit etre refuse"
insemination : declarer le lien, l'emettre d'un seul cote, et un test rouge L'insemination avait un nom depuis ce matin ; elle n'avait pas de flux. Deux declarations, aux deux bouts, et rien d'autre : serveur_ops_site egress 22/tcp -> serveur_ops_tenant serveur_ops_tenant ingress 22/tcp <- runner_site partage: true ETROIT PAR CONSTRUCTION : il vise le GROUPE `serveur_ops_tenant`, qu'un ecosysteme ne pose que sur une machine. Au socle, il aurait ouvert le SSH du site vers toute la flotte du tenant. L'en-tete disait « ce role n'entre JAMAIS chez un tenant ». Frontiere intenable : `creer-vm` exige `_instance-requise`, et le runner du site avait deja du basculer son symlink `instance` sur OPS-Chezlepro pour materialiser ses VM. Declarer ne cree pas ce pouvoir — ca rend limitable un pouvoir qui s'exercait sans borne. Ce qui reste interdit n'est pas une regle mais un FAIT : il n'a pas la voute du tenant. LA REGLE EST EMISE D'UN SEUL COTE, et pas celui qu'on croit. Le paquet penetre le pare-feu par la patte du SITE, pas par le transit : la regle appartient au cote site du devis. L'emettre aussi depuis l'`ingress` du tenant aurait produit une seconde regle sur la mauvaise interface — jamais evaluee, indiscernable d'une regle utile. La declaration du tenant pose sa regle nftables, et elle seule : ip saddr { 10.0.31.11 } tcp dport 22 accept L'adresse DERIVE du plan du site. Ecrite a la main, elle aurait survecu au prochain deplacement du runner sans bruit — le site a deja deplace ses machines le 08-25. Plan de la frontiere : 2 objets a creer, 0 a retirer, 126 inchanges. RIEN D'APPLIQUE. P41 APPLIQUEE AU PLAN DU SITE : `resoudre_flux` en avait besoin a son tour ; les trois lecteurs demenagent dans `underlay` et `devis_opnsense` delegue. DEUX GARDES ONT TRAVAILLE : P33 a refuse `ingress 22` sur un hote portant deja le sshd du socle (reponse : `partage: true`, comme `serveur_backup`), et le devis a refuse d'emettre vers un alias vide. UN TEST ROUGE DEPUIS TROIS JOURS. `test_adressage_derive` construisait un site avec un `index` — or un SITE n'en a pas depuis add94f2 (08-25), remplace par `bande_basse_de`. Invisible parce que le geste quotidien est `make prouver`, qui ne joue pas les tests. Remis sur le contrat actuel, avec sa contrepartie : sans `bande_basse_de`, aucun chevauchement n'est tolere. make verifier : vert. make prouver : CONFORME, 52 OK, 0 echec. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019f91zs9SDdwSzL2CVei6on
2026-08-28 13:09:28 -04:00
def test_sans_bande_basse_declaree_tout_chevauchement_est_refuse():
"""Le defaut est le refus : un reseau qui ne DIT PAS de quel tenant il occupe la
bande basse n'a droit a aucun chevauchement, pas meme sur 10.11.x. Ne pas declarer
ne peut pas valoir permission."""
assert _erreurs("10.11.0.0/24", mien=None), \
"sans `bande_basse_de`, un chevauchement doit etre refuse"
def test_hors_de_tout_supernet_accepte():
"""Le stockage (192.168.x, identique partout) et l'heritage 10.0.x avant migration."""
for sous in ("192.168.20.0/24", "192.168.31.0/24", "10.0.0.0/24"):
assert _erreurs(sous) == [], f"{sous} est hors des supernets : conforme"
adressage : l'index est borne [0-255] — 10.300.0.0/16 n'est pas un reseau Rien ne bornait `index`. supernet_de(300) rendait « 10.300.0.0/16 » : une CHAINE qui ressemble a un reseau. Elle traverse tout le moteur sans bruit et n'echoue qu'au premier ip_network() qui la lit, tres loin de l'index fautif. La borne est posee A LA SOURCE (valider_index), pas dans un validateur de plan : supernet_de, base3_de et vlan_de y passent toutes, donc aucune ne peut fabriquer une adresse invalide — d'ou qu'on l'appelle : plan, GUI, devis ou test. Elle protege un SECOND plafond, moins visible : a l'index 255 le VLAN vaut 3550+zone, sous les 4094 du 802.1Q. Un index a trois chiffres debordait aussi la. Garde statique ajoutee au controle de federation (P21) : elle nomme le depot fautif au lieu de laisser l'erreur remonter d'une bibliotheque. LE TEST A TROUVE CE QUE LA RELECTURE N'AVAIT PAS VU : valider_index n'attrapait que TypeError et ValueError, or int(float('inf')) leve OverflowError — un infini faisait PLANTER la garde au lieu d'etre refuse. test_underlay_bande_basse.py -> test_adressage_derive.py (il ne parlait plus seulement de la bande basse). 12 cas, dont les refus. Piege de structure au passage : les nouveaux cas, ajoutes apres le bloc `if __name__ == "__main__"`, ne s'executaient pas — le bloc tourne avant que les fonctions suivantes ne soient definies, et le compte affichait « 7 tests » au lieu de 12. Un harnais qui compte ses propres tests doit etre lu. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-12 16:21:33 -04:00
# --- Bornes de l'index (2026-08-12) -----------------------------------------------
# Sans elles, `supernet_de(300)` rendait « 10.300.0.0/16 » : une chaine qui RESSEMBLE
# a un reseau. Elle traverse tout le moteur sans bruit et n'echoue qu'au premier
# `ip_network()` qui la lit — tres loin de l'index fautif.
def test_les_bornes_couvrent_exactement_l_octet():
from inventory_rules import INDEX_MIN, INDEX_MAX
assert (INDEX_MIN, INDEX_MAX) == (0, 255)
def test_index_aux_bornes_accepte():
from inventory_rules import supernet_de
assert supernet_de(0) == "10.0.0.0/16"
assert supernet_de(255) == "10.255.0.0/16"
def test_index_hors_bornes_refuse():
from inventory_rules import supernet_de, base3_de, vlan_de
for fn, args in ((supernet_de, (256,)), (supernet_de, (-1,)),
(base3_de, (300, 1)), (vlan_de, (256, 1))):
try:
fn(*args)
except ValueError:
continue
raise AssertionError(f"{fn.__name__}{args} aurait du etre refuse")
def test_index_non_entier_refuse():
from inventory_rules import supernet_de
for mauvais in ("abc", None, 1.5e400):
try:
supernet_de(mauvais)
except ValueError:
continue
raise AssertionError(f"index {mauvais!r} aurait du etre refuse")
def test_le_vlan_reste_sous_le_plafond_802_1q():
"""La meme borne protege un SECOND plafond, moins visible."""
from inventory_rules import vlan_de, INDEX_MAX
assert vlan_de(INDEX_MAX, 9) < 4094
underlay : les tenants declares sont confrontes aux dossiers reels Suite du filtre de portee : `underlay.tenants` nomme des DOSSIERS FRERES, et une faute de frappe y etait invisible — le tenant disparaissait des trois devis du site, qui restaient « conformes » sur ce qu'il en restait. Sur un site a UN SEUL tenant — le cas de la prochaine implantation — la faute rend un devis VIDE : une frontiere sans regle, un commutateur sans VLAN. Rien dans le mot « conforme » ne dirait qu'on vient de dessiner le vide. L'ecart est lisible sans toucher au materiel : d'un cote une liste de noms, de l'autre les dossiers presents. Il se dit donc a `make underlay` (D-75). Quatre situations, quatre messages distincts : dossier absent ; dossier sans plan/nomenclature.yml ; nomenclature non federee (index absent, categories vide, federe: false) ; plus aucun nom qui corresponde. CE QU'UN GABARIT NE DOIT PAS SUBIR. Un modele decrit du materiel, pas un site deploye : sans garde, tout modele portant un exemple de `tenants` echouerait chez quiconque n'a pas ce dossier, et P17 deviendrait rouge sur la machine du voisin. La distinction existait deja : modeles.py passe des reperes de tenants EXPLICITES (gabarit), le site les laisse deriver. La verification ne s'applique qu'au second cas. Trois tests dans test_adressage_derive.py — nom introuvable, clef absente, gabarit epargne — avec un nom absurde pour qu'aucun test ne depende des dossiers de la machine. make test 15 + 9 ; prouver 37 OK, 0 echec, 0 saute. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 16:55:49 -04:00
def test_un_tenant_declare_introuvable_est_refuse_statiquement():
"""`tenants` nomme des dossiers freres : une faute de frappe doit se voir ICI.
Sinon elle ne se voit nulle part — le tenant disparait des trois devis du site, qui
restent « conformes » sur ce qu'il en reste. Le nom est volontairement absurde : le
test ne doit dependre d'aucun dossier reel de la machine qui l'execute.
"""
erreurs = u._erreurs_tenants_declares({"tenants": ["OPS-NExistePas-9f2c"]})
assert any("aucun dossier frere" in e for e in erreurs), erreurs
assert any("aucun nom declare ne correspond" in e for e in erreurs), erreurs
def test_sans_la_clef_tenants_rien_n_est_verifie():
"""Clef absente = toute la federation : un site unique n'a rien a declarer."""
assert u._erreurs_tenants_declares({"reseaux": []}) == []
assert u._erreurs_tenants_declares(None) == []
def test_un_gabarit_n_est_pas_confronte_aux_dossiers_freres():
"""Un modele decrit du MATERIEL : il ne peut pas nommer de tenants reels.
La distinction tient a l'appelant — `modeles.py` passe des reperes de tenants
explicites, ce qui dit « gabarit ». Sans cette garde, tout modele portant un exemple
de `tenants` echouerait chez quiconque n'a pas ce dossier.
"""
gabarit = {"tenants": ["OPS-NExistePas-9f2c"],
"reseaux": [{"nom": "g", "vlan": 10, "sous_reseau": "10.11.0.0/24"}]}
erreurs = u.valider(gabarit, tenants_vlans=set(), tenants_supernets=SUPERNETS)
assert not any("tenants:" in e for e in erreurs), erreurs
if __name__ == "__main__":
n = 0
for nom, fn in sorted(globals().items()):
if nom.startswith("test_") and callable(fn):
fn()
n += 1
adressage : l'index est borne [0-255] — 10.300.0.0/16 n'est pas un reseau Rien ne bornait `index`. supernet_de(300) rendait « 10.300.0.0/16 » : une CHAINE qui ressemble a un reseau. Elle traverse tout le moteur sans bruit et n'echoue qu'au premier ip_network() qui la lit, tres loin de l'index fautif. La borne est posee A LA SOURCE (valider_index), pas dans un validateur de plan : supernet_de, base3_de et vlan_de y passent toutes, donc aucune ne peut fabriquer une adresse invalide — d'ou qu'on l'appelle : plan, GUI, devis ou test. Elle protege un SECOND plafond, moins visible : a l'index 255 le VLAN vaut 3550+zone, sous les 4094 du 802.1Q. Un index a trois chiffres debordait aussi la. Garde statique ajoutee au controle de federation (P21) : elle nomme le depot fautif au lieu de laisser l'erreur remonter d'une bibliotheque. LE TEST A TROUVE CE QUE LA RELECTURE N'AVAIT PAS VU : valider_index n'attrapait que TypeError et ValueError, or int(float('inf')) leve OverflowError — un infini faisait PLANTER la garde au lieu d'etre refuse. test_underlay_bande_basse.py -> test_adressage_derive.py (il ne parlait plus seulement de la bande basse). 12 cas, dont les refus. Piege de structure au passage : les nouveaux cas, ajoutes apres le bloc `if __name__ == "__main__"`, ne s'executaient pas — le bloc tourne avant que les fonctions suivantes ne soient definies, et le compte affichait « 7 tests » au lieu de 12. Un harnais qui compte ses propres tests doit etre lu. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-12 16:21:33 -04:00
print(f"OK — {n} test(s) adressage derive : bande basse (OCTET_ZONE={OCTET_ZONE}) + bornes de l'index.")