import unittest import sys import os import networkx as nx # Add project root to path sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../.."))) from semantica.kg.centrality_calculator import CentralityCalculator from semantica.kg.community_detector import CommunityDetector from semantica.kg.connectivity_analyzer import ConnectivityAnalyzer class TestCentralityCalculator(unittest.TestCase): def setUp(self): self.calculator = CentralityCalculator() self.graph = { "entities": [ {"id": "A"}, {"id": "B"}, {"id": "C"}, {"id": "D"}, {"id": "E"} ], "relationships": [ {"source": "A", "target": "B"}, {"source": "A", "target": "C"}, {"source": "A", "target": "D"}, {"source": "A", "target": "E"} ] } # This is a star graph with center A. # A should have highest degree centrality. def test_degree_centrality(self): result = self.calculator.calculate_degree_centrality(self.graph) centrality = result["centrality"] # A connects to 4 nodes (B, C, D, E). Total nodes = 5. # Degree centrality for A = 4 / (5-1) = 1.0 self.assertAlmostEqual(centrality["A"], 1.0) # Leaves have degree 1. 1 / 4 = 0.25 self.assertAlmostEqual(centrality["B"], 0.25) def test_betweenness_centrality(self): result = self.calculator.calculate_betweenness_centrality(self.graph) centrality = result["centrality"] # A is on all shortest paths between any pair of leaves. # It should have high betweenness. self.assertGreater(centrality["A"], centrality["B"]) def test_closeness_centrality(self): result = self.calculator.calculate_closeness_centrality(self.graph) centrality = result["centrality"] # A is distance 1 from everyone. Closeness = 1.0 self.assertAlmostEqual(centrality["A"], 1.0) def test_eigenvector_centrality(self): result = self.calculator.calculate_eigenvector_centrality(self.graph) centrality = result["centrality"] # A should be highest self.assertEqual(max(centrality, key=centrality.get), "A") class TestCommunityDetector(unittest.TestCase): def setUp(self): self.detector = CommunityDetector() # Create two cliques connected by a single edge # Clique 1: 1, 2, 3 # Clique 2: 4, 5, 6 # Edge: 3-4 self.graph = { "entities": [ {"id": "1"}, {"id": "2"}, {"id": "3"}, {"id": "4"}, {"id": "5"}, {"id": "6"} ], "relationships": [ # Clique 1 {"source": "1", "target": "2"}, {"source": "2", "target": "3"}, {"source": "3", "target": "1"}, # Clique 2 {"source": "4", "target": "5"}, {"source": "5", "target": "6"}, {"source": "6", "target": "4"}, # Bridge {"source": "3", "target": "4"} ] } def test_louvain_communities(self): # Louvain should find 2 communities result = self.detector.detect_communities(self.graph, algorithm="louvain") communities = result["communities"] # We expect 2 communities, but small graphs can be tricky for heuristics. # Let's just check structure. self.assertTrue(len(communities) > 0) # Check that nodes in same clique are likely in same community # communities is a list of lists/sets comm_map = {} for c_id, nodes in enumerate(communities): for node in nodes: comm_map[node] = c_id self.assertEqual(comm_map["1"], comm_map["2"]) self.assertEqual(comm_map["4"], comm_map["5"]) class TestConnectivityAnalyzer(unittest.TestCase): def setUp(self): self.analyzer = ConnectivityAnalyzer() # Disconnected graph # Component 1: A-B # Component 2: C-D self.graph = { "entities": [{"id": "A"}, {"id": "B"}, {"id": "C"}, {"id": "D"}], "relationships": [ {"source": "A", "target": "B"}, {"source": "C", "target": "D"} ] } def test_connected_components(self): result = self.analyzer.find_connected_components(self.graph) self.assertEqual(result["num_components"], 2) # Components are just lists of nodes, not dicts with size # Wait, let's check find_connected_components return value # It returns { "components": [[...], [...]], ... } # So c is a list of nodes. sizes = [len(c) for c in result["components"]] self.assertIn(2, sizes) def test_shortest_path(self): graph = { "entities": [{"id": "A"}, {"id": "B"}, {"id": "C"}], "relationships": [ {"source": "A", "target": "B"}, {"source": "B", "target": "C"} ] } result = self.analyzer.calculate_shortest_paths(graph, source="A", target="C") # When source and target are provided, it returns specific keys self.assertEqual(result["distance"], 2) self.assertEqual(result["path"], ["A", "B", "C"]) def test_bridges(self): # A-B-C. Both edges are bridges. graph = { "entities": [{"id": "A"}, {"id": "B"}, {"id": "C"}], "relationships": [ {"source": "A", "target": "B"}, {"source": "B", "target": "C"} ] } result = self.analyzer.identify_bridges(graph) self.assertEqual(len(result["bridges"]), 2) if __name__ == "__main__": unittest.main()