- 9 nouveaux fichiers de tests (persistance, translations, fiches, indices, IHH) - Enrichissement des tests existants (graph_utils, gitea, widgets, tickets) - 67→448 tests, tous passent Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
856 lines
33 KiB
Python
856 lines
33 KiB
Python
"""Tests unitaires pour le module utils.graph_utils.
|
|
|
|
Ces tests vérifient les fonctions d'extraction et de traitement des graphes.
|
|
"""
|
|
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
import networkx as nx
|
|
import pandas as pd
|
|
import pytest
|
|
|
|
from utils.graph_utils import (
|
|
charger_graphe,
|
|
couleur_noeud,
|
|
determiner_couleur_par_seuil,
|
|
extraire_chemins_depuis,
|
|
extraire_chemins_vers,
|
|
load_seuils_config,
|
|
recuperer_donnees,
|
|
recuperer_donnees_2,
|
|
)
|
|
|
|
|
|
class TestExtraireCheminsDepuis:
|
|
"""Tests pour la fonction extraire_chemins_depuis."""
|
|
|
|
def test_chemin_simple(self, simple_graph):
|
|
"""Test l'extraction d'un chemin simple depuis un nœud."""
|
|
chemins = extraire_chemins_depuis(simple_graph, "ProduitA")
|
|
|
|
assert len(chemins) > 0
|
|
# Vérifier qu'il existe au moins un chemin qui commence par ProduitA
|
|
assert any(chemin[0] == "ProduitA" for chemin in chemins)
|
|
|
|
def test_chemin_depuis_noeud_terminal(self, simple_graph):
|
|
"""Test l'extraction depuis un nœud sans successeurs."""
|
|
chemins = extraire_chemins_depuis(simple_graph, "Chine_geographique")
|
|
|
|
assert len(chemins) == 1
|
|
assert chemins[0] == ["Chine_geographique"]
|
|
|
|
def test_chemins_multiples(self, complex_graph):
|
|
"""Test l'extraction de multiples chemins depuis un nœud."""
|
|
chemins = extraire_chemins_depuis(complex_graph, "ProduitX")
|
|
|
|
# ProduitX a 2 composants, chacun menant à au moins 1 minerai
|
|
assert len(chemins) >= 3 # Y→Z1, Y→Z2, W→Z1
|
|
|
|
def test_detection_cycles(self):
|
|
"""Test que les cycles sont correctement évités."""
|
|
G = nx.DiGraph()
|
|
G.add_edges_from([("A", "B"), ("B", "C"), ("C", "A")]) # Cycle
|
|
|
|
chemins = extraire_chemins_depuis(G, "A")
|
|
|
|
# Ne doit pas boucler infiniment, le résultat doit être une liste
|
|
assert isinstance(chemins, list)
|
|
# Vérifier qu'aucun chemin ne contient de doublons
|
|
for chemin in chemins:
|
|
assert len(chemin) == len(set(chemin))
|
|
|
|
def test_graphe_vide(self):
|
|
"""Test avec un graphe vide."""
|
|
G = nx.DiGraph()
|
|
|
|
with pytest.raises(nx.NetworkXError):
|
|
extraire_chemins_depuis(G, "noeud_inexistant")
|
|
|
|
|
|
class TestExtraireCheminsVers:
|
|
"""Tests pour la fonction extraire_chemins_vers."""
|
|
|
|
def test_chemins_vers_cible(self, simple_graph):
|
|
"""Test l'extraction des chemins vers un nœud cible."""
|
|
chemins = extraire_chemins_vers(simple_graph, "Chine_geographique", niveau_demande=0)
|
|
|
|
# Doit trouver au moins un chemin depuis ProduitA (niveau 0)
|
|
assert len(chemins) > 0
|
|
assert all(chemin[-1] == "Chine_geographique" for chemin in chemins)
|
|
|
|
def test_chemins_vers_avec_niveau_filtre(self, complex_graph):
|
|
"""Test que seuls les chemins avec le niveau demandé sont retournés."""
|
|
chemins = extraire_chemins_vers(complex_graph, "MineraiZ1", niveau_demande=0)
|
|
|
|
# Tous les chemins doivent contenir un nœud de niveau 0 (ProduitX)
|
|
assert len(chemins) > 0
|
|
for chemin in chemins:
|
|
niveaux = [complex_graph.nodes[n].get("niveau", -1) for n in chemin]
|
|
assert 0 in niveaux
|
|
|
|
def test_chemins_vers_noeud_source(self, simple_graph):
|
|
"""Test vers un nœud sans prédécesseurs."""
|
|
chemins = extraire_chemins_vers(simple_graph, "ProduitA", niveau_demande=0)
|
|
|
|
assert len(chemins) == 1
|
|
assert chemins[0] == ["ProduitA"]
|
|
|
|
|
|
class TestRecupererDonnees:
|
|
"""Tests pour la fonction recuperer_donnees."""
|
|
|
|
def test_recuperation_donnees_valides(self, simple_graph):
|
|
"""Test la récupération de données depuis des nœuds valides."""
|
|
noeuds = ["Fabrication_ComposantB"]
|
|
|
|
df = recuperer_donnees(simple_graph, noeuds)
|
|
|
|
assert isinstance(df, pd.DataFrame)
|
|
assert not df.empty
|
|
assert "categorie" in df.columns
|
|
assert "nom" in df.columns
|
|
assert "ihh_pays" in df.columns
|
|
|
|
def test_recuperation_avec_noeud_invalide(self, simple_graph, caplog):
|
|
"""Test la gestion d'un nœud avec format invalide."""
|
|
noeuds = ["NoeudInvalide"] # Pas de '_' dans le nom
|
|
|
|
df = recuperer_donnees(simple_graph, noeuds)
|
|
|
|
# Doit retourner un DataFrame vide ou partiel
|
|
assert isinstance(df, pd.DataFrame)
|
|
# Vérifier qu'un warning a été émis
|
|
assert "Nom de nœud inattendu" in caplog.text or df.empty
|
|
|
|
def test_calcul_ics_moyen(self):
|
|
"""Test le calcul de l'ICS moyen pour un minerai."""
|
|
G = nx.DiGraph()
|
|
G.add_node("Traitement_MineraiTest")
|
|
G.add_node("MineraiTest", niveau=2)
|
|
G.add_node("CompA", niveau=1)
|
|
G.add_node("CompB", niveau=1)
|
|
G.add_edge("CompA", "MineraiTest", ics=0.6)
|
|
G.add_edge("CompB", "MineraiTest", ics=0.8)
|
|
|
|
noeuds = ["Traitement_MineraiTest"]
|
|
df = recuperer_donnees(G, noeuds)
|
|
|
|
# ICS moyen devrait être (60 + 80) / 2 = 70
|
|
if not df.empty:
|
|
assert "ics_minerai" in df.columns
|
|
|
|
|
|
class TestRecupererDonnees2:
|
|
"""Tests pour la fonction recuperer_donnees_2."""
|
|
|
|
def test_recuperation_donnees_minerais(self, complex_graph):
|
|
"""Test la récupération des données IVC/IHH pour les minerais."""
|
|
minerais = ["MineraiZ1"]
|
|
|
|
donnees = recuperer_donnees_2(complex_graph, minerais)
|
|
|
|
assert isinstance(donnees, list)
|
|
assert len(donnees) == 1
|
|
assert donnees[0]["nom"] == "MineraiZ1"
|
|
assert "ivc" in donnees[0]
|
|
|
|
def test_minerai_manquant(self, simple_graph, caplog):
|
|
"""Test avec un minerai dont les nœuds sont manquants."""
|
|
minerais = ["MineraiInexistant"]
|
|
|
|
with caplog.at_level("WARNING", logger="utils.graph_utils"):
|
|
donnees = recuperer_donnees_2(simple_graph, minerais)
|
|
|
|
# Doit retourner une liste vide et logger un warning
|
|
assert isinstance(donnees, list)
|
|
assert len(donnees) == 0
|
|
# Vérifier soit le caplog soit la fonction a fonctionné
|
|
assert "manquants" in caplog.text.lower() or len(donnees) == 0
|
|
|
|
def test_minerai_avec_extraction_et_reserves(self):
|
|
"""Test avec un minerai ayant Extraction_ et Reserves_."""
|
|
G = nx.DiGraph()
|
|
G.add_node("MineraiTest", niveau=2, ivc=50)
|
|
G.add_node("Extraction_MineraiTest", niveau=10, ihh_pays=40)
|
|
G.add_node("Reserves_MineraiTest", niveau=10, ihh_pays=60)
|
|
|
|
minerais = ["MineraiTest"]
|
|
donnees = recuperer_donnees_2(G, minerais)
|
|
|
|
assert len(donnees) == 1
|
|
assert donnees[0]["ivc"] == 50
|
|
assert donnees[0]["ihh_extraction"] == 40
|
|
assert donnees[0]["ihh_reserves"] == 60
|
|
|
|
def test_minerai_extraction_manquante(self):
|
|
"""Test avec un minerai dont le nœud Extraction_ est manquant."""
|
|
G = nx.DiGraph()
|
|
G.add_node("MineraiTest", niveau=2, ivc=50)
|
|
G.add_node("Reserves_MineraiTest", niveau=10, ihh_pays=60)
|
|
|
|
minerais = ["MineraiTest"]
|
|
donnees = recuperer_donnees_2(G, minerais)
|
|
|
|
# Le minerai doit être ignoré car Extraction_ manque
|
|
assert len(donnees) == 0
|
|
|
|
def test_minerai_reserves_manquante(self):
|
|
"""Test avec un minerai dont le nœud Reserves_ est manquant."""
|
|
G = nx.DiGraph()
|
|
G.add_node("MineraiTest", niveau=2, ivc=50)
|
|
G.add_node("Extraction_MineraiTest", niveau=10, ihh_pays=40)
|
|
|
|
minerais = ["MineraiTest"]
|
|
donnees = recuperer_donnees_2(G, minerais)
|
|
|
|
# Le minerai doit être ignoré car Reserves_ manque
|
|
assert len(donnees) == 0
|
|
|
|
def test_erreur_exception_dans_boucle(self):
|
|
"""Test la gestion d'erreur dans la boucle de recuperer_donnees_2."""
|
|
G = nx.DiGraph()
|
|
G.add_node("MineraiTest", niveau=2, ivc="invalide")
|
|
G.add_node("Extraction_MineraiTest", niveau=10, ihh_pays=40)
|
|
G.add_node("Reserves_MineraiTest", niveau=10, ihh_pays=60)
|
|
|
|
minerais = ["MineraiTest"]
|
|
donnees = recuperer_donnees_2(G, minerais)
|
|
|
|
# ivc="invalide" va provoquer une ValueError dans int(), capturée par le except
|
|
assert isinstance(donnees, list)
|
|
|
|
|
|
class TestRecupererDonneesIcsCalcul:
|
|
"""Tests complémentaires pour le calcul ICS dans recuperer_donnees."""
|
|
|
|
def test_ics_calcul_avec_valeurs_non_vides(self):
|
|
"""Test le calcul ICS quand des valeurs existent sur les arêtes."""
|
|
G = nx.MultiDiGraph()
|
|
G.add_node("Traitement_Cuivre", ihh_pays=0, ihh_acteurs=0)
|
|
G.add_node("Cuivre", niveau=2)
|
|
G.add_node("FabA", niveau=1)
|
|
G.add_node("FabB", niveau=1)
|
|
G.add_edge("FabA", "Cuivre", ics=0.4)
|
|
G.add_edge("FabB", "Cuivre", ics=0.6)
|
|
|
|
noeuds = ["Traitement_Cuivre"]
|
|
df = recuperer_donnees(G, noeuds)
|
|
|
|
assert not df.empty
|
|
# ICS moyen = (40 + 60) / 2 = 50
|
|
assert df.iloc[0]["ics_minerai"] == 50
|
|
assert df.iloc[0]["ics_cat"] == 2 # 50 > 33 et 50 <= 66
|
|
|
|
def test_ics_cat_faible(self):
|
|
"""Test la catégorisation ICS pour une valeur faible (<=33)."""
|
|
G = nx.MultiDiGraph()
|
|
G.add_node("Traitement_Nickel", ihh_pays=10, ihh_acteurs=5)
|
|
G.add_node("Nickel", niveau=2)
|
|
G.add_node("Fab1", niveau=1)
|
|
G.add_edge("Fab1", "Nickel", ics=0.2)
|
|
|
|
noeuds = ["Traitement_Nickel"]
|
|
df = recuperer_donnees(G, noeuds)
|
|
|
|
assert not df.empty
|
|
# ICS = 20, catégorie 1 (<=33)
|
|
assert df.iloc[0]["ics_minerai"] == 20
|
|
assert df.iloc[0]["ics_cat"] == 1
|
|
|
|
def test_ics_cat_elevee(self):
|
|
"""Test la catégorisation ICS pour une valeur élevée (>66)."""
|
|
G = nx.MultiDiGraph()
|
|
G.add_node("Traitement_Or", ihh_pays=80, ihh_acteurs=60)
|
|
G.add_node("Or", niveau=2)
|
|
G.add_node("Fab1", niveau=1)
|
|
G.add_edge("Fab1", "Or", ics=0.9)
|
|
|
|
noeuds = ["Traitement_Or"]
|
|
df = recuperer_donnees(G, noeuds)
|
|
|
|
assert not df.empty
|
|
# ICS = 90, catégorie 3 (>66)
|
|
assert df.iloc[0]["ics_minerai"] == 90
|
|
assert df.iloc[0]["ics_cat"] == 3
|
|
|
|
def test_ics_sans_predecesseurs(self):
|
|
"""Test le calcul ICS quand il n'y a pas de prédécesseurs pour le minerai."""
|
|
G = nx.MultiDiGraph()
|
|
G.add_node("Traitement_Fer", ihh_pays=30, ihh_acteurs=20)
|
|
G.add_node("Fer", niveau=2)
|
|
# Pas de prédécesseurs pour Fer
|
|
|
|
noeuds = ["Traitement_Fer"]
|
|
df = recuperer_donnees(G, noeuds)
|
|
|
|
assert not df.empty
|
|
# ICS par défaut = 50 (pas de valeurs calculées)
|
|
assert df.iloc[0]["ics_minerai"] == 50
|
|
|
|
def test_ics_exception_criticite(self):
|
|
"""Test la gestion d'erreur lors du calcul de criticité."""
|
|
G = nx.MultiDiGraph()
|
|
G.add_node("Traitement_Zinc", ihh_pays=25, ihh_acteurs=15)
|
|
# Le nœud Zinc n'existe PAS dans le graphe, donc graph.predecessors() va échouer
|
|
# Mais le minerai est quand même extrait dans la 2ème boucle
|
|
|
|
noeuds = ["Traitement_Zinc"]
|
|
df = recuperer_donnees(G, noeuds)
|
|
|
|
# L'exception est attrapée, ics par défaut = 50
|
|
assert isinstance(df, pd.DataFrame)
|
|
|
|
|
|
class TestLoadSeuilsConfig:
|
|
"""Tests pour la fonction load_seuils_config."""
|
|
|
|
def test_chargement_fichier_valide(self, sample_config_yaml):
|
|
"""Test le chargement d'un fichier YAML valide."""
|
|
seuils = load_seuils_config(str(sample_config_yaml))
|
|
|
|
assert "ISG" in seuils
|
|
assert "IHH" in seuils
|
|
assert "IVC" in seuils
|
|
assert seuils["ISG"]["vert"]["max"] == 40
|
|
assert seuils["IHH"]["rouge"]["min"] == 25
|
|
|
|
def test_chargement_fichier_inexistant(self):
|
|
"""Test le fallback quand le fichier n'existe pas."""
|
|
seuils = load_seuils_config("/chemin/inexistant/config.yaml")
|
|
|
|
# Doit retourner les valeurs par défaut
|
|
assert "ISG" in seuils
|
|
assert "IHH" in seuils
|
|
assert "IVC" in seuils
|
|
assert seuils["ISG"]["vert"]["max"] == 40
|
|
assert seuils["IHH"]["vert"]["max"] == 15
|
|
assert seuils["IVC"]["rouge"]["min"] == 60
|
|
|
|
def test_chargement_fichier_yaml_invalide(self, tmp_path):
|
|
"""Test le fallback quand le fichier YAML est mal formé."""
|
|
bad_yaml = tmp_path / "bad_config.yaml"
|
|
bad_yaml.write_text("{{{{invalide yaml!!!!}", encoding="utf-8")
|
|
|
|
seuils = load_seuils_config(str(bad_yaml))
|
|
|
|
# Doit retourner les valeurs par défaut
|
|
assert "ISG" in seuils
|
|
assert "IHH" in seuils
|
|
|
|
def test_chargement_fichier_sans_cle_seuils(self, tmp_path):
|
|
"""Test avec un YAML valide mais sans la clé 'seuils'."""
|
|
yaml_sans_seuils = tmp_path / "no_seuils.yaml"
|
|
yaml_sans_seuils.write_text("autre_cle: valeur\n", encoding="utf-8")
|
|
|
|
seuils = load_seuils_config(str(yaml_sans_seuils))
|
|
|
|
# data.get("seuils", {}) retourne {} car la clé n'existe pas
|
|
assert seuils == {}
|
|
|
|
|
|
class TestDeterminerCouleurParSeuil:
|
|
"""Tests pour la fonction determiner_couleur_par_seuil."""
|
|
|
|
@pytest.fixture
|
|
def seuils_isg(self):
|
|
"""Seuils ISG standards pour les tests."""
|
|
return {
|
|
"vert": {"max": 40},
|
|
"orange": {"min": 40, "max": 70},
|
|
"rouge": {"min": 70}
|
|
}
|
|
|
|
@pytest.fixture
|
|
def seuils_ihh(self):
|
|
"""Seuils IHH standards pour les tests."""
|
|
return {
|
|
"vert": {"max": 15},
|
|
"orange": {"min": 15, "max": 25},
|
|
"rouge": {"min": 25}
|
|
}
|
|
|
|
def test_valeur_negative_retourne_gris(self, seuils_isg):
|
|
"""Test qu'une valeur négative retourne 'gray'."""
|
|
assert determiner_couleur_par_seuil(-1, seuils_isg) == "gray"
|
|
assert determiner_couleur_par_seuil(-100, seuils_isg) == "gray"
|
|
|
|
def test_valeur_zone_verte(self, seuils_isg):
|
|
"""Test qu'une valeur dans la zone verte retourne 'darkgreen'."""
|
|
assert determiner_couleur_par_seuil(0, seuils_isg) == "darkgreen"
|
|
assert determiner_couleur_par_seuil(20, seuils_isg) == "darkgreen"
|
|
assert determiner_couleur_par_seuil(39, seuils_isg) == "darkgreen"
|
|
|
|
def test_valeur_zone_orange(self, seuils_isg):
|
|
"""Test qu'une valeur dans la zone orange retourne 'orange'."""
|
|
assert determiner_couleur_par_seuil(40, seuils_isg) == "orange"
|
|
assert determiner_couleur_par_seuil(55, seuils_isg) == "orange"
|
|
assert determiner_couleur_par_seuil(69, seuils_isg) == "orange"
|
|
|
|
def test_valeur_zone_rouge(self, seuils_isg):
|
|
"""Test qu'une valeur dans la zone rouge retourne 'darkred'."""
|
|
assert determiner_couleur_par_seuil(70, seuils_isg) == "darkred"
|
|
assert determiner_couleur_par_seuil(100, seuils_isg) == "darkred"
|
|
|
|
def test_valeur_limite_vert_orange(self, seuils_ihh):
|
|
"""Test la limite exacte entre vert et orange."""
|
|
# Valeur 14 juste sous le seuil orange, donc vert
|
|
assert determiner_couleur_par_seuil(14, seuils_ihh) == "darkgreen"
|
|
# Valeur 15 au seuil orange, donc orange
|
|
assert determiner_couleur_par_seuil(15, seuils_ihh) == "orange"
|
|
|
|
def test_valeur_limite_orange_rouge(self, seuils_ihh):
|
|
"""Test la limite exacte entre orange et rouge."""
|
|
# Valeur 24 juste sous le seuil rouge, donc orange
|
|
assert determiner_couleur_par_seuil(24, seuils_ihh) == "orange"
|
|
# Valeur 25 au seuil rouge, donc rouge
|
|
assert determiner_couleur_par_seuil(25, seuils_ihh) == "darkred"
|
|
|
|
def test_seuils_incomplets_sans_vert(self):
|
|
"""Test avec des seuils sans la clé 'vert'."""
|
|
seuils = {
|
|
"orange": {"min": 40, "max": 70},
|
|
"rouge": {"min": 70}
|
|
}
|
|
# Valeur en zone rouge
|
|
assert determiner_couleur_par_seuil(80, seuils) == "darkred"
|
|
# Valeur en zone orange
|
|
assert determiner_couleur_par_seuil(50, seuils) == "orange"
|
|
# Valeur basse : pas de vert défini, tombe dans le défaut orange
|
|
assert determiner_couleur_par_seuil(10, seuils) == "orange"
|
|
|
|
def test_seuils_incomplets_sans_rouge(self):
|
|
"""Test avec des seuils sans la clé 'rouge'."""
|
|
seuils = {
|
|
"vert": {"max": 40},
|
|
"orange": {"min": 40, "max": 70},
|
|
}
|
|
assert determiner_couleur_par_seuil(10, seuils) == "darkgreen"
|
|
assert determiner_couleur_par_seuil(50, seuils) == "orange"
|
|
# Valeur haute sans seuil rouge : tombe dans le défaut orange
|
|
assert determiner_couleur_par_seuil(80, seuils) == "orange"
|
|
|
|
def test_seuils_vides(self):
|
|
"""Test avec un dictionnaire de seuils vide."""
|
|
assert determiner_couleur_par_seuil(50, {}) == "orange"
|
|
assert determiner_couleur_par_seuil(0, {}) == "orange"
|
|
|
|
def test_seuils_sans_min_dans_orange(self):
|
|
"""Test avec 'orange' présent mais sans clé 'min'."""
|
|
seuils = {
|
|
"vert": {"max": 40},
|
|
"orange": {"max": 70},
|
|
"rouge": {"min": 70}
|
|
}
|
|
# La condition orange a besoin de min ET max, donc skip orange
|
|
# Valeur 50 : pas rouge (sous 70), pas orange (manque min), pas vert (au dessus de 40)
|
|
# Tombe dans le défaut orange
|
|
assert determiner_couleur_par_seuil(50, seuils) == "orange"
|
|
# Valeur 10 : sous le seuil vert (40), donc darkgreen
|
|
assert determiner_couleur_par_seuil(10, seuils) == "darkgreen"
|
|
|
|
def test_valeur_zero(self, seuils_isg):
|
|
"""Test avec la valeur zéro."""
|
|
assert determiner_couleur_par_seuil(0, seuils_isg) == "darkgreen"
|
|
|
|
|
|
class TestCouleurNoeud:
|
|
"""Tests pour la fonction couleur_noeud."""
|
|
|
|
@pytest.fixture
|
|
def config_yaml(self):
|
|
"""Retourne les seuils de configuration pour les tests."""
|
|
return {
|
|
"ISG": {"vert": {"max": 40}, "orange": {"min": 40, "max": 70}, "rouge": {"min": 70}},
|
|
"IHH": {"vert": {"max": 15}, "orange": {"min": 15, "max": 25}, "rouge": {"min": 25}},
|
|
"IVC": {"vert": {"max": 15}, "orange": {"min": 15, "max": 60}, "rouge": {"min": 60}}
|
|
}
|
|
|
|
def test_pays_geographique_isg_vert(self, config_yaml):
|
|
"""Test couleur d'un pays géographique avec ISG faible (vert)."""
|
|
G = nx.DiGraph()
|
|
G.add_node("Australie_geographique", niveau=99, isg=25)
|
|
niveaux = {"Australie_geographique": 99}
|
|
|
|
with patch("utils.graph_utils.load_seuils_config", return_value=config_yaml):
|
|
couleur = couleur_noeud("Australie_geographique", niveaux, G)
|
|
|
|
assert couleur == "darkgreen"
|
|
|
|
def test_pays_geographique_isg_orange(self, config_yaml):
|
|
"""Test couleur d'un pays géographique avec ISG moyen (orange)."""
|
|
G = nx.DiGraph()
|
|
G.add_node("Chine_geographique", niveau=99, isg=54)
|
|
niveaux = {"Chine_geographique": 99}
|
|
|
|
with patch("utils.graph_utils.load_seuils_config", return_value=config_yaml):
|
|
couleur = couleur_noeud("Chine_geographique", niveaux, G)
|
|
|
|
assert couleur == "orange"
|
|
|
|
def test_pays_geographique_isg_rouge(self, config_yaml):
|
|
"""Test couleur d'un pays géographique avec ISG élevé (rouge)."""
|
|
G = nx.DiGraph()
|
|
G.add_node("RDC_geographique", niveau=99, isg=85)
|
|
niveaux = {"RDC_geographique": 99}
|
|
|
|
with patch("utils.graph_utils.load_seuils_config", return_value=config_yaml):
|
|
couleur = couleur_noeud("RDC_geographique", niveaux, G)
|
|
|
|
assert couleur == "darkred"
|
|
|
|
def test_pays_geographique_sans_isg(self, config_yaml):
|
|
"""Test couleur d'un pays géographique sans attribut ISG."""
|
|
G = nx.DiGraph()
|
|
G.add_node("Inconnu_geographique", niveau=99)
|
|
niveaux = {"Inconnu_geographique": 99}
|
|
|
|
with patch("utils.graph_utils.load_seuils_config", return_value=config_yaml):
|
|
couleur = couleur_noeud("Inconnu_geographique", niveaux, G)
|
|
|
|
# isg = -1 par défaut, donc gray
|
|
assert couleur == "gray"
|
|
|
|
def test_pays_operation_niveau_11_connecte_pays(self, config_yaml):
|
|
"""Test couleur d'un nœud niveau 11 connecté à un pays géographique."""
|
|
G = nx.DiGraph()
|
|
G.add_node("Chine_Fabrication_Batterie", niveau=11)
|
|
G.add_node("Chine_geographique", niveau=99, isg=54)
|
|
G.add_edge("Chine_Fabrication_Batterie", "Chine_geographique")
|
|
niveaux = {"Chine_Fabrication_Batterie": 11, "Chine_geographique": 99}
|
|
|
|
with patch("utils.graph_utils.load_seuils_config", return_value=config_yaml):
|
|
couleur = couleur_noeud("Chine_Fabrication_Batterie", niveaux, G)
|
|
|
|
assert couleur == "orange"
|
|
|
|
def test_pays_operation_niveau_12(self, config_yaml):
|
|
"""Test couleur d'un nœud niveau 12 connecté à un pays géographique."""
|
|
G = nx.DiGraph()
|
|
G.add_node("Australie_Reserves_Lithium", niveau=12)
|
|
G.add_node("Australie_geographique", niveau=99, isg=25)
|
|
G.add_edge("Australie_Reserves_Lithium", "Australie_geographique")
|
|
niveaux = {"Australie_Reserves_Lithium": 12, "Australie_geographique": 99}
|
|
|
|
with patch("utils.graph_utils.load_seuils_config", return_value=config_yaml):
|
|
couleur = couleur_noeud("Australie_Reserves_Lithium", niveaux, G)
|
|
|
|
assert couleur == "darkgreen"
|
|
|
|
def test_pays_operation_niveau_1011(self, config_yaml):
|
|
"""Test couleur d'un nœud niveau 1011 connecté à un pays géographique."""
|
|
G = nx.DiGraph()
|
|
G.add_node("Noeud_1011", niveau=1011)
|
|
G.add_node("Pays_geographique", niveau=99, isg=80)
|
|
G.add_edge("Noeud_1011", "Pays_geographique")
|
|
niveaux = {"Noeud_1011": 1011, "Pays_geographique": 99}
|
|
|
|
with patch("utils.graph_utils.load_seuils_config", return_value=config_yaml):
|
|
couleur = couleur_noeud("Noeud_1011", niveaux, G)
|
|
|
|
assert couleur == "darkred"
|
|
|
|
def test_pays_operation_niveau_1012(self, config_yaml):
|
|
"""Test couleur d'un nœud niveau 1012 connecté à un pays géographique."""
|
|
G = nx.DiGraph()
|
|
G.add_node("Noeud_1012", niveau=1012)
|
|
G.add_node("Pays_geo", niveau=99, isg=10)
|
|
G.add_edge("Noeud_1012", "Pays_geo")
|
|
niveaux = {"Noeud_1012": 1012, "Pays_geo": 99}
|
|
|
|
with patch("utils.graph_utils.load_seuils_config", return_value=config_yaml):
|
|
couleur = couleur_noeud("Noeud_1012", niveaux, G)
|
|
|
|
assert couleur == "darkgreen"
|
|
|
|
def test_pays_operation_niveau_11_sans_pays_geo(self, config_yaml):
|
|
"""Test couleur d'un nœud niveau 11 sans successeur pays géographique."""
|
|
G = nx.DiGraph()
|
|
G.add_node("Chine_Fabrication", niveau=11)
|
|
G.add_node("Autre_Noeud", niveau=10)
|
|
G.add_edge("Chine_Fabrication", "Autre_Noeud")
|
|
niveaux = {"Chine_Fabrication": 11, "Autre_Noeud": 10}
|
|
|
|
with patch("utils.graph_utils.load_seuils_config", return_value=config_yaml):
|
|
couleur = couleur_noeud("Chine_Fabrication", niveaux, G)
|
|
|
|
# Pas de successeur niveau 99, tombe dans lightblue
|
|
assert couleur == "lightblue"
|
|
|
|
def test_pays_operation_niveau_11_pays_geo_sans_isg(self, config_yaml):
|
|
"""Test couleur d'un nœud niveau 11 connecté à un pays sans ISG."""
|
|
G = nx.DiGraph()
|
|
G.add_node("Noeud_11", niveau=11)
|
|
G.add_node("Pays_geo", niveau=99) # Pas d'attribut isg
|
|
G.add_edge("Noeud_11", "Pays_geo")
|
|
niveaux = {"Noeud_11": 11, "Pays_geo": 99}
|
|
|
|
with patch("utils.graph_utils.load_seuils_config", return_value=config_yaml):
|
|
couleur = couleur_noeud("Noeud_11", niveaux, G)
|
|
|
|
# isg = -1, donc gray
|
|
assert couleur == "gray"
|
|
|
|
def test_operation_niveau_10_ihh_vert(self, config_yaml):
|
|
"""Test couleur d'une opération niveau 10 avec IHH faible."""
|
|
G = nx.DiGraph()
|
|
G.add_node("Fabrication_Test", niveau=10, ihh_pays=10)
|
|
niveaux = {"Fabrication_Test": 10}
|
|
|
|
with patch("utils.graph_utils.load_seuils_config", return_value=config_yaml):
|
|
couleur = couleur_noeud("Fabrication_Test", niveaux, G)
|
|
|
|
assert couleur == "darkgreen"
|
|
|
|
def test_operation_niveau_10_ihh_orange(self, config_yaml):
|
|
"""Test couleur d'une opération niveau 10 avec IHH moyen."""
|
|
G = nx.DiGraph()
|
|
G.add_node("Fabrication_Test", niveau=10, ihh_pays=20)
|
|
niveaux = {"Fabrication_Test": 10}
|
|
|
|
with patch("utils.graph_utils.load_seuils_config", return_value=config_yaml):
|
|
couleur = couleur_noeud("Fabrication_Test", niveaux, G)
|
|
|
|
assert couleur == "orange"
|
|
|
|
def test_operation_niveau_10_ihh_rouge(self, config_yaml):
|
|
"""Test couleur d'une opération niveau 10 avec IHH élevé."""
|
|
G = nx.DiGraph()
|
|
G.add_node("Fabrication_Test", niveau=10, ihh_pays=30)
|
|
niveaux = {"Fabrication_Test": 10}
|
|
|
|
with patch("utils.graph_utils.load_seuils_config", return_value=config_yaml):
|
|
couleur = couleur_noeud("Fabrication_Test", niveaux, G)
|
|
|
|
assert couleur == "darkred"
|
|
|
|
def test_operation_niveau_1010(self, config_yaml):
|
|
"""Test couleur d'une opération niveau 1010 avec IHH."""
|
|
G = nx.DiGraph()
|
|
G.add_node("Operation_1010", niveau=1010, ihh_pays=20)
|
|
niveaux = {"Operation_1010": 1010}
|
|
|
|
with patch("utils.graph_utils.load_seuils_config", return_value=config_yaml):
|
|
couleur = couleur_noeud("Operation_1010", niveaux, G)
|
|
|
|
assert couleur == "orange"
|
|
|
|
def test_operation_niveau_10_sans_ihh(self, config_yaml):
|
|
"""Test couleur d'une opération niveau 10 sans attribut ihh_pays."""
|
|
G = nx.DiGraph()
|
|
G.add_node("Fabrication_Test", niveau=10)
|
|
niveaux = {"Fabrication_Test": 10}
|
|
|
|
with patch("utils.graph_utils.load_seuils_config", return_value=config_yaml):
|
|
couleur = couleur_noeud("Fabrication_Test", niveaux, G)
|
|
|
|
# Pas d'ihh_pays, tombe dans lightblue
|
|
assert couleur == "lightblue"
|
|
|
|
def test_minerai_niveau_2_ivc_vert(self, config_yaml):
|
|
"""Test couleur d'un minerai niveau 2 avec IVC faible."""
|
|
G = nx.DiGraph()
|
|
G.add_node("Lithium", niveau=2, ivc=10)
|
|
niveaux = {"Lithium": 2}
|
|
|
|
with patch("utils.graph_utils.load_seuils_config", return_value=config_yaml):
|
|
couleur = couleur_noeud("Lithium", niveaux, G)
|
|
|
|
assert couleur == "darkgreen"
|
|
|
|
def test_minerai_niveau_2_ivc_orange(self, config_yaml):
|
|
"""Test couleur d'un minerai niveau 2 avec IVC moyen."""
|
|
G = nx.DiGraph()
|
|
G.add_node("Cobalt", niveau=2, ivc=30)
|
|
niveaux = {"Cobalt": 2}
|
|
|
|
with patch("utils.graph_utils.load_seuils_config", return_value=config_yaml):
|
|
couleur = couleur_noeud("Cobalt", niveaux, G)
|
|
|
|
assert couleur == "orange"
|
|
|
|
def test_minerai_niveau_2_ivc_rouge(self, config_yaml):
|
|
"""Test couleur d'un minerai niveau 2 avec IVC élevé."""
|
|
G = nx.DiGraph()
|
|
G.add_node("Indium", niveau=2, ivc=65)
|
|
niveaux = {"Indium": 2}
|
|
|
|
with patch("utils.graph_utils.load_seuils_config", return_value=config_yaml):
|
|
couleur = couleur_noeud("Indium", niveaux, G)
|
|
|
|
assert couleur == "darkred"
|
|
|
|
def test_minerai_niveau_2_sans_ivc(self, config_yaml):
|
|
"""Test couleur d'un minerai niveau 2 sans attribut IVC."""
|
|
G = nx.DiGraph()
|
|
G.add_node("MineraiSansIvc", niveau=2)
|
|
niveaux = {"MineraiSansIvc": 2}
|
|
|
|
with patch("utils.graph_utils.load_seuils_config", return_value=config_yaml):
|
|
couleur = couleur_noeud("MineraiSansIvc", niveaux, G)
|
|
|
|
# Pas d'ivc, tombe dans lightblue
|
|
assert couleur == "lightblue"
|
|
|
|
def test_noeud_sans_niveau_connu(self, config_yaml):
|
|
"""Test couleur d'un nœud avec un niveau inconnu."""
|
|
G = nx.DiGraph()
|
|
G.add_node("NoeudInconnu", niveau=5)
|
|
niveaux = {"NoeudInconnu": 5}
|
|
|
|
with patch("utils.graph_utils.load_seuils_config", return_value=config_yaml):
|
|
couleur = couleur_noeud("NoeudInconnu", niveaux, G)
|
|
|
|
assert couleur == "lightblue"
|
|
|
|
def test_noeud_absent_du_dictionnaire_niveaux(self, config_yaml):
|
|
"""Test couleur d'un nœud absent du dictionnaire des niveaux."""
|
|
G = nx.DiGraph()
|
|
G.add_node("NoeudOrphelin", isg=50)
|
|
niveaux = {} # Pas de niveau défini
|
|
|
|
with patch("utils.graph_utils.load_seuils_config", return_value=config_yaml):
|
|
couleur = couleur_noeud("NoeudOrphelin", niveaux, G)
|
|
|
|
# niveau par défaut = 99, isg=50 -> orange
|
|
assert couleur == "orange"
|
|
|
|
def test_operation_niveau_10_ihh_fallback_sans_cle_ihh(self):
|
|
"""Test le fallback IHH quand la clé IHH n'est pas dans les seuils."""
|
|
G = nx.DiGraph()
|
|
G.add_node("Fabrication_Test", niveau=10, ihh_pays=20)
|
|
niveaux = {"Fabrication_Test": 10}
|
|
|
|
seuils_sans_ihh = {
|
|
"ISG": {"vert": {"max": 40}, "orange": {"min": 40, "max": 70}, "rouge": {"min": 70}},
|
|
"IVC": {"vert": {"max": 15}, "orange": {"min": 15, "max": 60}, "rouge": {"min": 60}}
|
|
}
|
|
|
|
with patch("utils.graph_utils.load_seuils_config", return_value=seuils_sans_ihh):
|
|
couleur = couleur_noeud("Fabrication_Test", niveaux, G)
|
|
|
|
# Fallback: 20 <= 25 -> orange
|
|
assert couleur == "orange"
|
|
|
|
def test_operation_niveau_10_ihh_fallback_vert(self):
|
|
"""Test le fallback IHH pour une valeur faible (<=15)."""
|
|
G = nx.DiGraph()
|
|
G.add_node("Fabrication_Test", niveau=10, ihh_pays=10)
|
|
niveaux = {"Fabrication_Test": 10}
|
|
|
|
with patch("utils.graph_utils.load_seuils_config", return_value={}):
|
|
couleur = couleur_noeud("Fabrication_Test", niveaux, G)
|
|
|
|
assert couleur == "darkgreen"
|
|
|
|
def test_operation_niveau_10_ihh_fallback_rouge(self):
|
|
"""Test le fallback IHH pour une valeur élevée (>25)."""
|
|
G = nx.DiGraph()
|
|
G.add_node("Fabrication_Test", niveau=10, ihh_pays=30)
|
|
niveaux = {"Fabrication_Test": 10}
|
|
|
|
with patch("utils.graph_utils.load_seuils_config", return_value={}):
|
|
couleur = couleur_noeud("Fabrication_Test", niveaux, G)
|
|
|
|
assert couleur == "darkred"
|
|
|
|
def test_minerai_niveau_2_ivc_fallback_sans_cle_ivc(self):
|
|
"""Test le fallback IVC quand la clé IVC n'est pas dans les seuils."""
|
|
G = nx.DiGraph()
|
|
G.add_node("Lithium", niveau=2, ivc=20)
|
|
niveaux = {"Lithium": 2}
|
|
|
|
seuils_sans_ivc = {
|
|
"ISG": {"vert": {"max": 40}, "orange": {"min": 40, "max": 70}, "rouge": {"min": 70}},
|
|
}
|
|
|
|
with patch("utils.graph_utils.load_seuils_config", return_value=seuils_sans_ivc):
|
|
couleur = couleur_noeud("Lithium", niveaux, G)
|
|
|
|
# Fallback IVC: 20 > 15 et <= 30 -> orange
|
|
assert couleur == "orange"
|
|
|
|
def test_minerai_niveau_2_ivc_fallback_vert(self):
|
|
"""Test le fallback IVC pour une valeur faible (<=15)."""
|
|
G = nx.DiGraph()
|
|
G.add_node("Lithium", niveau=2, ivc=10)
|
|
niveaux = {"Lithium": 2}
|
|
|
|
with patch("utils.graph_utils.load_seuils_config", return_value={}):
|
|
couleur = couleur_noeud("Lithium", niveaux, G)
|
|
|
|
assert couleur == "darkgreen"
|
|
|
|
def test_minerai_niveau_2_ivc_fallback_rouge(self):
|
|
"""Test le fallback IVC pour une valeur élevée (>30)."""
|
|
G = nx.DiGraph()
|
|
G.add_node("Lithium", niveau=2, ivc=50)
|
|
niveaux = {"Lithium": 2}
|
|
|
|
with patch("utils.graph_utils.load_seuils_config", return_value={}):
|
|
couleur = couleur_noeud("Lithium", niveaux, G)
|
|
|
|
assert couleur == "darkred"
|
|
|
|
|
|
class TestChargerGraphe:
|
|
"""Tests pour la fonction charger_graphe."""
|
|
|
|
def test_charger_graphe_deja_en_session(self):
|
|
"""Test que le graphe n'est pas rechargé s'il est déjà en session."""
|
|
mock_graph = nx.DiGraph()
|
|
mock_session = {"G_temp": mock_graph, "G_temp_ivc": mock_graph.copy()}
|
|
|
|
with patch("utils.graph_utils.st") as mock_st:
|
|
mock_st.session_state = mock_session
|
|
resultat = charger_graphe()
|
|
|
|
assert resultat is True
|
|
|
|
def test_charger_graphe_succes(self, tmp_path):
|
|
"""Test le chargement réussi d'un graphe DOT."""
|
|
dot_file = tmp_path / "test.dot"
|
|
dot_file.write_text('digraph { A -> B; }', encoding="utf-8")
|
|
|
|
mock_session = {}
|
|
mock_graph = nx.MultiDiGraph()
|
|
mock_graph.add_edge("A", "B")
|
|
|
|
with patch("utils.graph_utils.st") as mock_st, \
|
|
patch("utils.graph_utils.charger_schema_depuis_gitea", return_value=True), \
|
|
patch("utils.graph_utils.read_dot", return_value=mock_graph), \
|
|
patch("utils.graph_utils.DOT_FILE", str(dot_file)):
|
|
mock_st.session_state = mock_session
|
|
resultat = charger_graphe()
|
|
|
|
assert resultat is True
|
|
assert "G_temp" in mock_session
|
|
assert "G_temp_ivc" in mock_session
|
|
|
|
def test_charger_graphe_gitea_echoue(self):
|
|
"""Test quand le chargement depuis Gitea échoue."""
|
|
mock_session = {}
|
|
|
|
with patch("utils.graph_utils.st") as mock_st, \
|
|
patch("utils.graph_utils.charger_schema_depuis_gitea", return_value=False):
|
|
mock_st.session_state = mock_session
|
|
mock_st.error = MagicMock()
|
|
resultat = charger_graphe()
|
|
|
|
assert resultat is False
|
|
mock_st.error.assert_called()
|
|
|
|
def test_charger_graphe_exception_lecture_dot(self):
|
|
"""Test quand la lecture du fichier DOT lève une exception."""
|
|
mock_session = {}
|
|
|
|
with patch("utils.graph_utils.st") as mock_st, \
|
|
patch("utils.graph_utils.charger_schema_depuis_gitea", return_value=True), \
|
|
patch("utils.graph_utils.read_dot", side_effect=Exception("Fichier corrompu")):
|
|
mock_st.session_state = mock_session
|
|
mock_st.error = MagicMock()
|
|
resultat = charger_graphe()
|
|
|
|
assert resultat is False
|
|
# Vérifie que st.error a été appelé au moins une fois
|
|
assert mock_st.error.call_count >= 1
|