""" Tests unitaires pour le module utils.graph_utils. Ces tests vérifient les fonctions d'extraction et de traitement des graphes. """ import pytest import networkx as nx import pandas as pd from utils.graph_utils import ( extraire_chemins_depuis, extraire_chemins_vers, 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 assert len(chemins) >= 0 # 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(Exception): 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("Chine_geographique" == chemin[-1] 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