#!/usr/bin/env python3 """Phase 5.2 Graph Analytics tests.""" import asyncio import sys from pathlib import Path sys.path.insert(0, str(Path(__file__).parent / "ontology_platform")) from ont_platform.core.graph.graph_analytics import GraphAnalytics, Community class MockAdapter: """Mock Neo4j adapter for testing.""" async def execute_cypher(self, cypher: str, params=None): """Mock Cypher execution.""" params = params or {} # Degree centrality if "size((" in cypher and "out_degree" not in cypher: return [ {"result": {"entity_id": 1, "label": "Hub", "centrality_score": 10, "type": "degree"}}, {"result": {"entity_id": 2, "label": "Node_2", "centrality_score": 5, "type": "degree"}}, {"result": {"entity_id": 3, "label": "Node_3", "centrality_score": 3, "type": "degree"}}, ] # Pagerank centrality if "out_degree" in cypher or "in_degree" in cypher: return [ { "result": { "entity_id": 1, "label": "Hub", "centrality_score": 0.45, "in_degree": 8, "out_degree": 2, "type": "pagerank", } }, { "result": { "entity_id": 2, "label": "Node_2", "centrality_score": 0.30, "in_degree": 5, "out_degree": 3, "type": "pagerank", } }, ] # Communities (Louvain) if "communityId" in cypher or "algo.louvain" in cypher: return [ { "result": { "community_id": 0, "entities": [1, 2, 3, 4], "labels": ["Entity_1", "Entity_2", "Entity_3", "Entity_4"], "size": 4, } }, { "result": { "community_id": 1, "entities": [5, 6, 7], "labels": ["Entity_5", "Entity_6", "Entity_7"], "size": 3, } }, ] # Graph statistics if "node_count" in cypher or "edge_count" in cypher: return [ { "stats": { "total_nodes": 20, "total_edges": 45, "avg_degree": 4.5, "density": 0.118, "max_possible_edges": 190, } } ] # Diameter if "diameter" in cypher: return [{"diameter": 5}] # Components if "componentId" in cypher or "algo.unionFind" in cypher: return [{"num_components": 1}] return [] async def test_calculate_centrality_degree(): """Test degree centrality calculation.""" print("\n[TEST 1] Degree Centrality") adapter = MockAdapter() analytics = GraphAnalytics(adapter) entities = await analytics.calculate_centrality(centrality_type="degree", top_n=10) assert len(entities) > 0, "Should find entities" assert all("entity_id" in e and "centrality_score" in e for e in entities), "Should have required fields" assert all(e["centrality_score"] > 0 for e in entities), "Centrality scores should be positive" print(f" [OK] Found {len(entities)} entities by degree") top_entity = entities[0] print(f" [OK] Top entity: {top_entity['label']} (degree={top_entity['centrality_score']})") print(" [PASS]") async def test_calculate_centrality_pagerank(): """Test PageRank centrality calculation.""" print("\n[TEST 2] PageRank Centrality") adapter = MockAdapter() analytics = GraphAnalytics(adapter) entities = await analytics.calculate_centrality(centrality_type="pagerank", top_n=10) assert len(entities) > 0, "Should find entities" assert all(0 < e["centrality_score"] <= 1 for e in entities), "PageRank should be 0-1" print(f" [OK] Found {len(entities)} entities by PageRank") top_entity = entities[0] print(f" [OK] Top entity: {top_entity['label']} (score={top_entity['centrality_score']:.3f})") print(" [PASS]") async def test_invalid_centrality_type(): """Test invalid centrality type.""" print("\n[TEST 3] Invalid Centrality Type") adapter = MockAdapter() analytics = GraphAnalytics(adapter) result = await analytics.calculate_centrality(centrality_type="invalid", top_n=10) assert isinstance(result, dict) and "error" in result, "Should return error" print(f" [OK] Correctly rejects invalid type: {result['error']}") print(" [PASS]") async def test_detect_communities(): """Test community detection.""" print("\n[TEST 4] Community Detection") adapter = MockAdapter() analytics = GraphAnalytics(adapter) communities = await analytics.detect_communities(algorithm="louvain") assert isinstance(communities, list), "Should return list" if communities: assert all("community_id" in c and "entities" in c for c in communities), "Should have required fields" assert all(isinstance(c["size"], int) for c in communities), "Should have size" print(f" [OK] Detected {len(communities)} communities") for comm in communities: print(f" Community {comm['community_id']}: {comm['size']} entities") print(" [PASS]") async def test_get_graph_statistics(): """Test graph statistics.""" print("\n[TEST 5] Graph Statistics") adapter = MockAdapter() analytics = GraphAnalytics(adapter) stats = await analytics.get_graph_statistics() if stats: assert "total_nodes" in stats, "Should have total_nodes" assert "total_edges" in stats, "Should have total_edges" assert "avg_degree" in stats, "Should have avg_degree" assert "density" in stats, "Should have density" print(f" [OK] Total nodes: {stats['total_nodes']}") print(f" [OK] Total edges: {stats['total_edges']}") print(f" [OK] Average degree: {stats['avg_degree']:.2f}") print(f" [OK] Density: {stats['density']:.4f}") print(f" [OK] Is connected: {stats.get('is_connected', False)}") print(" [PASS]") async def test_find_influential_entities(): """Test influential entity detection.""" print("\n[TEST 6] Influential Entities") adapter = MockAdapter() analytics = GraphAnalytics(adapter) influential = await analytics.find_influential_entities(top_n=10) assert isinstance(influential, list), "Should return list" if influential: assert all("entity_id" in e and "composite_score" in e for e in influential), "Should have required fields" assert all(0 <= e["composite_score"] <= 1 for e in influential), "Scores should be 0-1" print(f" [OK] Found {len(influential)} influential entities") for idx, entity in enumerate(influential[:3], 1): print( f" {idx}. {entity['label']} (score={entity['composite_score']:.3f})" ) print(" [PASS]") async def test_community_object(): """Test Community data class.""" print("\n[TEST 7] Community Object") community = Community( community_id=1, entities=[1, 2, 3, 4, 5], size=5, density=0.75, modularity=0.42, ) assert community.community_id == 1, "ID should be preserved" assert len(community.entities) == 5, "Should have 5 entities" assert community.size == 5, "Size should be 5" community_dict = community.to_dict() assert "community_id" in community_dict, "Dict should have community_id" assert community_dict["size"] == 5, "Dict should have size" print(" [OK] Community object creation and conversion") print(" [PASS]") async def test_centrality_ranking(): """Test that centrality results are ranked.""" print("\n[TEST 8] Centrality Ranking") adapter = MockAdapter() analytics = GraphAnalytics(adapter) entities = await analytics.calculate_centrality(centrality_type="degree", top_n=10) if len(entities) > 1: assert all("rank" in e for e in entities), "Should have rank field" assert entities[0]["rank"] == 1, "Top entity should have rank 1" assert entities[1]["rank"] == 2, "Second entity should have rank 2" print(f" [OK] Entities ranked correctly") for entity in entities[:3]: print(f" Rank {entity['rank']}: {entity['label']}") print(" [PASS]") async def main(): """Run all tests.""" print("=" * 70) print("Phase 5.2 Graph Analytics Tests") print("=" * 70) try: await test_calculate_centrality_degree() await test_calculate_centrality_pagerank() await test_invalid_centrality_type() await test_detect_communities() await test_get_graph_statistics() await test_find_influential_entities() await test_community_object() await test_centrality_ranking() print("\n" + "=" * 70) print("All tests passed!") print("=" * 70) print("\nPhase 5.2 Graph Analytics capabilities:") print(" [OK] Degree centrality calculation") print(" [OK] PageRank centrality calculation") print(" [OK] Community detection (Louvain)") print(" [OK] Graph statistics (density, diameter, components)") print(" [OK] Influential entity detection") print(" [OK] Input validation") return True except AssertionError as e: print(f"\nTest failed: {e}") import traceback traceback.print_exc() return False except Exception as e: print(f"\nUnexpected error: {e}") import traceback traceback.print_exc() return False if __name__ == "__main__": success = asyncio.run(main()) sys.exit(0 if success else 1)