import os from datetime import datetime, timedelta import pytest from dotenv import load_dotenv from core.edges import EntityEdge from core.llm_client import LLMConfig, OpenAIClient from core.nodes import EntityNode from core.utils.maintenance.temporal_operations import ( invalidate_edges, ) load_dotenv() def setup_llm_client(): return OpenAIClient( LLMConfig( api_key=os.getenv("TEST_OPENAI_API_KEY"), model=os.getenv("TEST_OPENAI_MODEL"), base_url="https://api.openai.com/v1", ) ) # Helper function to create test data def create_test_data(): now = datetime.now() # Create nodes node1 = EntityNode(uuid="1", name="Alice", labels=["Person"], created_at=now) node2 = EntityNode(uuid="2", name="Bob", labels=["Person"], created_at=now) # Create edges edge1 = EntityEdge( uuid="e1", source_node_uuid="1", target_node_uuid="2", name="LIKES", fact="Alice likes Bob", created_at=now - timedelta(days=1), ) edge2 = EntityEdge( uuid="e2", source_node_uuid="1", target_node_uuid="2", name="DISLIKES", fact="Alice dislikes Bob", created_at=now, ) existing_edge = (node1, edge1, node2) new_edge = (node1, edge2, node2) return existing_edge, new_edge @pytest.mark.asyncio @pytest.mark.integration async def test_invalidate_edges(): existing_edge, new_edge = create_test_data() invalidated_edges = await invalidate_edges( setup_llm_client(), [existing_edge], [new_edge] ) assert len(invalidated_edges) == 1 assert invalidated_edges[0].uuid == existing_edge[1].uuid assert invalidated_edges[0].expired_at is not None @pytest.mark.asyncio @pytest.mark.integration async def test_invalidate_edges_no_invalidation(): existing_edge, _ = create_test_data() invalidated_edges = await invalidate_edges(setup_llm_client(), [existing_edge], []) assert len(invalidated_edges) == 0 @pytest.mark.asyncio @pytest.mark.integration async def test_invalidate_edges_multiple_existing(): existing_edge1, new_edge = create_test_data() existing_edge2, _ = create_test_data() existing_edge2[1].uuid = "e3" existing_edge2[1].name = "KNOWS" existing_edge2[1].fact = "Alice knows Bob" invalidated_edges = await invalidate_edges( setup_llm_client(), [existing_edge1, existing_edge2], [new_edge] ) assert len(invalidated_edges) == 1 assert invalidated_edges[0].uuid == existing_edge1[1].uuid assert invalidated_edges[0].expired_at is not None # Helper function to create more complex test data def create_complex_test_data(): now = datetime.now() # Create nodes node1 = EntityNode(uuid="1", name="Alice", labels=["Person"], created_at=now) node2 = EntityNode(uuid="2", name="Bob", labels=["Person"], created_at=now) node3 = EntityNode(uuid="3", name="Charlie", labels=["Person"], created_at=now) node4 = EntityNode( uuid="4", name="Company XYZ", labels=["Organization"], created_at=now ) # Create edges edge1 = EntityEdge( uuid="e1", source_node_uuid="1", target_node_uuid="2", name="LIKES", fact="Alice likes Bob", created_at=now - timedelta(days=5), ) edge2 = EntityEdge( uuid="e2", source_node_uuid="1", target_node_uuid="3", name="FRIENDS_WITH", fact="Alice is friends with Charlie", created_at=now - timedelta(days=3), ) edge3 = EntityEdge( uuid="e3", source_node_uuid="2", target_node_uuid="4", name="WORKS_FOR", fact="Bob works for Company XYZ", created_at=now - timedelta(days=2), ) existing_edge1 = (node1, edge1, node2) existing_edge2 = (node1, edge2, node3) existing_edge3 = (node2, edge3, node4) return [existing_edge1, existing_edge2, existing_edge3], [ node1, node2, node3, node4, ] @pytest.mark.asyncio @pytest.mark.integration async def test_invalidate_edges_complex(): existing_edges, nodes = create_complex_test_data() # Create a new edge that contradicts an existing one new_edge = ( nodes[0], EntityEdge( uuid="e4", source_node_uuid="1", target_node_uuid="2", name="DISLIKES", fact="Alice dislikes Bob", created_at=datetime.now(), ), nodes[1], ) invalidated_edges = await invalidate_edges( setup_llm_client(), existing_edges, [new_edge] ) assert len(invalidated_edges) == 1 assert invalidated_edges[0].uuid == "e1" assert invalidated_edges[0].expired_at is not None @pytest.mark.asyncio @pytest.mark.integration async def test_invalidate_edges_temporal_update(): existing_edges, nodes = create_complex_test_data() # Create a new edge that updates an existing one with new information new_edge = ( nodes[1], EntityEdge( uuid="e5", source_node_uuid="2", target_node_uuid="4", name="LEFT_JOB", fact="Bob left his job at Company XYZ", created_at=datetime.now(), ), nodes[3], ) invalidated_edges = await invalidate_edges( setup_llm_client(), existing_edges, [new_edge] ) assert len(invalidated_edges) == 1 assert invalidated_edges[0].uuid == "e3" assert invalidated_edges[0].expired_at is not None @pytest.mark.asyncio @pytest.mark.integration async def test_invalidate_edges_multiple_invalidations(): existing_edges, nodes = create_complex_test_data() # Create new edges that invalidate multiple existing edges new_edge1 = ( nodes[0], EntityEdge( uuid="e6", source_node_uuid="1", target_node_uuid="2", name="ENEMIES_WITH", fact="Alice and Bob are now enemies", created_at=datetime.now(), ), nodes[1], ) new_edge2 = ( nodes[0], EntityEdge( uuid="e7", source_node_uuid="1", target_node_uuid="3", name="ENDED_FRIENDSHIP", fact="Alice ended her friendship with Charlie", created_at=datetime.now(), ), nodes[2], ) invalidated_edges = await invalidate_edges( setup_llm_client(), existing_edges, [new_edge1, new_edge2] ) assert len(invalidated_edges) == 2 assert set(edge.uuid for edge in invalidated_edges) == {"e1", "e2"} for edge in invalidated_edges: assert edge.expired_at is not None @pytest.mark.asyncio @pytest.mark.integration async def test_invalidate_edges_no_effect(): existing_edges, nodes = create_complex_test_data() # Create a new edge that doesn't invalidate any existing edges new_edge = ( nodes[2], EntityEdge( uuid="e8", source_node_uuid="3", target_node_uuid="4", name="APPLIED_TO", fact="Charlie applied to Company XYZ", created_at=datetime.now(), ), nodes[3], ) invalidated_edges = await invalidate_edges( setup_llm_client(), existing_edges, [new_edge] ) assert len(invalidated_edges) == 0 @pytest.mark.asyncio @pytest.mark.integration async def test_invalidate_edges_partial_update(): existing_edges, nodes = create_complex_test_data() # Create a new edge that partially updates an existing one new_edge = ( nodes[1], EntityEdge( uuid="e9", source_node_uuid="2", target_node_uuid="4", name="CHANGED_POSITION", fact="Bob changed his position at Company XYZ", created_at=datetime.now(), ), nodes[3], ) invalidated_edges = await invalidate_edges( setup_llm_client(), existing_edges, [new_edge] ) assert ( len(invalidated_edges) == 0 ) # The existing edge is not invalidated, just updated @pytest.mark.asyncio @pytest.mark.integration async def test_invalidate_edges_empty_inputs(): invalidated_edges = await invalidate_edges(setup_llm_client(), [], []) assert len(invalidated_edges) == 0