Set-OPS-Public/scripts/tests/test_adressage_derive.py
Daniel Allaire 8b4ad276ca 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

127 lines
5 KiB
Python

#!/usr/bin/env python3
"""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
SUPERNETS = ["10.11.0.0/16", "10.17.0.0/16"] # deux sites : index 11 et 17
MIEN = 11
def _erreurs(sous: str, index: int | None = MIEN) -> list[str]:
site = {"reseaux": [{"nom": "g", "vlan": 10, "sous_reseau": sous}]}
if index is not None:
site["index"] = 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():
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():
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."""
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."""
assert _erreurs("10.17.0.0/24"), "le supernet d'un autre site 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"
def test_sans_index_on_retombe_sur_la_regle_stricte():
"""Un underlay qui n'a pas encore migre ne declare pas d'index : aucun chevauchement
n'est alors tolere, ce qui reste le comportement sur."""
assert _erreurs("10.11.0.0/24", index=None), \
"sans index, tout chevauchement doit etre refuse"
# --- 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
if __name__ == "__main__":
n = 0
for nom, fn in sorted(globals().items()):
if nom.startswith("test_") and callable(fn):
fn()
n += 1
print(f"OK — {n} test(s) adressage derive : bande basse (OCTET_ZONE={OCTET_ZONE}) + bornes de l'index.")