c889a57b6b
Test Suites / Build CI Environment (push) Has been cancelled
Test Suites / Basic Tests (push) Has been cancelled
Test Suites / End-to-End Tests (push) Has been cancelled
Test Suites / CLI Tests (push) Has been cancelled
Test Suites / Slow End-to-End Tests (push) Has been cancelled
Test Suites / Graph Database Tests (push) Has been cancelled
Test Suites / Vector DB Tests (push) Has been cancelled
Test Suites / Temporal Graph Test (push) Has been cancelled
Test Suites / Search Test on Different DBs (push) Has been cancelled
Test Suites / Example Tests (push) Has been cancelled
Test Suites / Notebook Tests (push) Has been cancelled
Test Suites / OS and Python Tests Ubuntu (push) Has been cancelled
Test Suites / OS and Python Tests Extended (push) Has been cancelled
Test Suites / LLM Test Suite (push) Has been cancelled
Test Suites / S3 File Storage Test (push) Has been cancelled
Test Suites / Run Integration Tests (push) Has been cancelled
Test Suites / MCP Tests (push) Has been cancelled
Test Suites / Docker Compose Test (push) Has been cancelled
Test Suites / Docker CI test (push) Has been cancelled
Test Suites / Relational DB Migration Tests (push) Has been cancelled
Test Suites / Distributed Cognee Test (push) Has been cancelled
Test Suites / DB Examples Tests (push) Has been cancelled
Test Suites / Test Completion Status (push) Has been cancelled
Test Suites / Claude Code Review (push) Has been cancelled
Test Suites / basic checks (push) Has been cancelled
build | Build and Push Cognee MCP Docker Image to dockerhub / docker-build-and-push (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
build | Build and Push Docker Image to dockerhub / docker-build-and-push (push) Has been cancelled
Weighted Edges Tests / Test Weighted Edges Core Functionality (3.11) (push) Has been cancelled
Weighted Edges Tests / Test Weighted Edges Core Functionality (3.12) (push) Has been cancelled
Weighted Edges Tests / Test Weighted Edges with Different Graph Databases (kuzu, kuzu) (push) Has been cancelled
Weighted Edges Tests / Test Weighted Edges with Different Graph Databases (neo4j, neo4j) (push) Has been cancelled
Weighted Edges Tests / Test Weighted Edges Examples (push) Has been cancelled
Weighted Edges Tests / Code Quality for Weighted Edges (push) Has been cancelled
237 lines
8.1 KiB
Python
237 lines
8.1 KiB
Python
from types import SimpleNamespace
|
|
from uuid import uuid4
|
|
|
|
import pytest
|
|
|
|
from cognee.modules.cognify import rollback as rollback_module
|
|
|
|
|
|
class _FakeScalarsResult:
|
|
def __init__(self, items):
|
|
self._items = items
|
|
|
|
def all(self):
|
|
return self._items
|
|
|
|
|
|
class _FakeExecuteResult:
|
|
def __init__(self, items=None, scalar_value=None):
|
|
self._items = items if items is not None else []
|
|
self._scalar_value = scalar_value
|
|
|
|
def scalars(self):
|
|
return _FakeScalarsResult(self._items)
|
|
|
|
def scalar(self):
|
|
return self._scalar_value
|
|
|
|
|
|
class _FakeSession:
|
|
def __init__(self, execute_results, call_log=None):
|
|
self._execute_results = list(execute_results)
|
|
self._call_log = call_log if call_log is not None else []
|
|
self.committed = False
|
|
|
|
async def execute(self, statement):
|
|
if getattr(statement, "is_delete", False):
|
|
self._call_log.append("relational_delete")
|
|
return self._execute_results.pop(0)
|
|
|
|
async def commit(self):
|
|
self.committed = True
|
|
|
|
async def __aenter__(self):
|
|
return self
|
|
|
|
async def __aexit__(self, *_args):
|
|
return False
|
|
|
|
|
|
class _FakeEngine:
|
|
def __init__(self, sessions):
|
|
self._sessions = list(sessions)
|
|
self.calls = 0
|
|
|
|
def get_async_session(self):
|
|
self.calls += 1
|
|
return self._sessions.pop(0)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_cognify_rollback_deletes_graph_before_relational(monkeypatch):
|
|
pipeline_run_id = uuid4()
|
|
dataset_id = uuid4()
|
|
data_id = uuid4()
|
|
node_id = uuid4()
|
|
edge_id = uuid4()
|
|
|
|
node = SimpleNamespace(id=node_id, slug="node-1", data_id=data_id)
|
|
edge = SimpleNamespace(id=edge_id, slug="edge-1", data_id=data_id)
|
|
|
|
call_log = []
|
|
session_discovery = _FakeSession(
|
|
[
|
|
_FakeExecuteResult([node]),
|
|
_FakeExecuteResult([edge]),
|
|
_FakeExecuteResult(scalar_value=False),
|
|
_FakeExecuteResult(scalar_value=False),
|
|
]
|
|
)
|
|
data_record = SimpleNamespace(
|
|
id=data_id,
|
|
pipeline_status={"cognify_pipeline": {str(dataset_id): "DATASET_PROCESSING_STARTED"}},
|
|
)
|
|
session_mutation = _FakeSession(
|
|
[
|
|
_FakeExecuteResult(),
|
|
_FakeExecuteResult(),
|
|
_FakeExecuteResult([data_record]),
|
|
],
|
|
call_log=call_log,
|
|
)
|
|
engine = _FakeEngine([session_discovery, session_mutation])
|
|
|
|
async def _delete_from_graph_and_vector(*_args, **_kwargs):
|
|
call_log.append("graph_delete")
|
|
|
|
async def _has_nodes_in_legacy_ledger(_nodes):
|
|
return []
|
|
|
|
async def _has_edges_in_legacy_ledger(_edges):
|
|
return []
|
|
|
|
# Pin the engine as non-graph-provenance so rollback takes the relational-ledger
|
|
# path (the unified branch is gated on supports_graph_provenance_delete()).
|
|
async def _get_unified_engine():
|
|
return SimpleNamespace(supports_graph_provenance_delete=lambda: False)
|
|
|
|
monkeypatch.setattr(rollback_module, "get_unified_engine", _get_unified_engine)
|
|
monkeypatch.setattr(rollback_module, "get_relational_engine", lambda: engine)
|
|
monkeypatch.setattr(rollback_module, "multi_user_support_possible", lambda: False)
|
|
monkeypatch.setattr(rollback_module, "has_nodes_in_legacy_ledger", _has_nodes_in_legacy_ledger)
|
|
monkeypatch.setattr(rollback_module, "has_edges_in_legacy_ledger", _has_edges_in_legacy_ledger)
|
|
monkeypatch.setattr(
|
|
rollback_module, "delete_from_graph_and_vector", _delete_from_graph_and_vector
|
|
)
|
|
monkeypatch.setattr(rollback_module.orm_attributes, "flag_modified", lambda *_args: None)
|
|
|
|
await rollback_module.cognify_rollback_handler(
|
|
pipeline_run_id=pipeline_run_id,
|
|
dataset=SimpleNamespace(id=dataset_id),
|
|
)
|
|
|
|
assert call_log == ["graph_delete", "relational_delete", "relational_delete"]
|
|
assert str(dataset_id) not in data_record.pipeline_status["cognify_pipeline"]
|
|
assert session_mutation.committed is True
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_graph_provenance_rollback_resets_status_without_ingestion_info(monkeypatch):
|
|
"""Startup recovery passes no data_ingestion_info, so the graph-provenance branch
|
|
must derive the affected data ids from graph provenance and still reset the
|
|
per-data cognify status (otherwise re-cognify would skip the data)."""
|
|
from cognee.infrastructure.databases.provenance import make_source_ref_key
|
|
|
|
pipeline_run_id = uuid4()
|
|
dataset_id = uuid4()
|
|
data_id = uuid4()
|
|
source_ref_key = make_source_ref_key(dataset_id, data_id)
|
|
|
|
rolled_back = []
|
|
|
|
class _FakeGraph:
|
|
async def find_node_source_refs_by_pipeline_run(self, _run):
|
|
return {"n1": [source_ref_key]}
|
|
|
|
async def find_edge_source_refs_by_pipeline_run(self, _run):
|
|
return {}
|
|
|
|
async def _rollback(run):
|
|
rolled_back.append(run)
|
|
|
|
fake_unified = SimpleNamespace(
|
|
supports_graph_provenance_delete=lambda: True,
|
|
graph=_FakeGraph(),
|
|
rollback_by_pipeline_run_id=_rollback,
|
|
)
|
|
|
|
data_record = SimpleNamespace(
|
|
id=data_id,
|
|
pipeline_status={"cognify_pipeline": {str(dataset_id): "DATASET_PROCESSING_STARTED"}},
|
|
)
|
|
session_mutation = _FakeSession([_FakeExecuteResult([data_record])])
|
|
engine = _FakeEngine([session_mutation])
|
|
|
|
async def _get_unified_engine():
|
|
return fake_unified
|
|
|
|
async def _stores_provenance_in_graph(_graph):
|
|
return True
|
|
|
|
monkeypatch.setattr(rollback_module, "get_unified_engine", _get_unified_engine)
|
|
monkeypatch.setattr(rollback_module, "stores_provenance_in_graph", _stores_provenance_in_graph)
|
|
monkeypatch.setattr(rollback_module, "get_relational_engine", lambda: engine)
|
|
monkeypatch.setattr(rollback_module.orm_attributes, "flag_modified", lambda *_args: None)
|
|
|
|
# No data_ingestion_info — exactly how startup recovery calls it.
|
|
await rollback_module.cognify_rollback_handler(
|
|
pipeline_run_id=pipeline_run_id,
|
|
dataset=SimpleNamespace(id=dataset_id),
|
|
)
|
|
|
|
assert rolled_back == [str(pipeline_run_id)]
|
|
# Status was reset for the data id derived from the run's graph source refs.
|
|
assert str(dataset_id) not in data_record.pipeline_status["cognify_pipeline"]
|
|
assert session_mutation.committed is True
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_cognify_rollback_keeps_relational_rows_if_graph_delete_fails(monkeypatch):
|
|
pipeline_run_id = uuid4()
|
|
dataset_id = uuid4()
|
|
data_id = uuid4()
|
|
node_id = uuid4()
|
|
edge_id = uuid4()
|
|
|
|
node = SimpleNamespace(id=node_id, slug="node-1", data_id=data_id)
|
|
edge = SimpleNamespace(id=edge_id, slug="edge-1", data_id=data_id)
|
|
|
|
session_discovery = _FakeSession(
|
|
[
|
|
_FakeExecuteResult([node]),
|
|
_FakeExecuteResult([edge]),
|
|
_FakeExecuteResult(scalar_value=False),
|
|
_FakeExecuteResult(scalar_value=False),
|
|
]
|
|
)
|
|
engine = _FakeEngine([session_discovery])
|
|
|
|
async def _failing_delete(*_args, **_kwargs):
|
|
raise RuntimeError("graph delete failed")
|
|
|
|
async def _has_nodes_in_legacy_ledger(_nodes):
|
|
return []
|
|
|
|
async def _has_edges_in_legacy_ledger(_edges):
|
|
return []
|
|
|
|
# Pin the engine as non-graph-provenance so rollback takes the relational-ledger
|
|
# path (the unified branch is gated on supports_graph_provenance_delete()).
|
|
async def _get_unified_engine():
|
|
return SimpleNamespace(supports_graph_provenance_delete=lambda: False)
|
|
|
|
monkeypatch.setattr(rollback_module, "get_unified_engine", _get_unified_engine)
|
|
monkeypatch.setattr(rollback_module, "get_relational_engine", lambda: engine)
|
|
monkeypatch.setattr(rollback_module, "multi_user_support_possible", lambda: False)
|
|
monkeypatch.setattr(rollback_module, "has_nodes_in_legacy_ledger", _has_nodes_in_legacy_ledger)
|
|
monkeypatch.setattr(rollback_module, "has_edges_in_legacy_ledger", _has_edges_in_legacy_ledger)
|
|
monkeypatch.setattr(rollback_module, "delete_from_graph_and_vector", _failing_delete)
|
|
|
|
with pytest.raises(RuntimeError, match="graph delete failed"):
|
|
await rollback_module.cognify_rollback_handler(
|
|
pipeline_run_id=pipeline_run_id,
|
|
dataset=SimpleNamespace(id=dataset_id),
|
|
)
|
|
|
|
assert engine.calls == 1
|