"""Actual Neo4j rollback after DELETE and partial replacement; isolated scopes.""" import os import uuid import pytest from tools.graphrag import build_graphrag as builder from tools.graphrag.graph_db import create_neo4j_driver, get_neo4j_settings pytestmark = pytest.mark.skipif(os.getenv('RUN_NEO4J_TESTS') != '1', reason='Requires local Neo4j.') @pytest.fixture def managed_graph(monkeypatch): scope = 'atomic-rebuild-' + uuid.uuid4().hex monkeypatch.setattr(builder, 'GRAPH_SCOPE', scope) settings = get_neo4j_settings() with create_neo4j_driver(settings) as driver, driver.session(database=settings.database) as session: session.run('CREATE (:Person {id:$id, name:"Original", graph_scope:$scope})', id=scope+'old', scope=scope).consume() session.run('CREATE (:IndexState {id:$scope, metadata_json:$metadata})', scope=scope, metadata='{"version":"old"}').consume() try: yield session, scope finally: session.run('MATCH (n) WHERE n.graph_scope=$scope DETACH DELETE n', scope=scope).consume() session.run('MATCH (s:IndexState {id:$scope}) DELETE s', scope=scope).consume() def replacement(scope): payload = {key: [] for key in [*builder.NODE_QUERIES, *builder.RELATIONSHIP_QUERIES]} payload['people'] = [{'id': scope+'new', 'name': 'Replacement', 'graph_scope': scope}] payload['documents'] = [{'path': scope+'/p.md', 'graph_scope': scope}] payload['person_documents'] = [{'person_id': scope+'new', 'document_path': scope+'/p.md'}] return payload def test_failed_replacement_restores_deleted_graph_and_version(managed_graph): session, scope = managed_graph payload = replacement(scope) payload['documents'].append({'path': None, 'graph_scope': scope}) with pytest.raises(Exception): with session.begin_transaction() as tx: builder.write_graph_transaction(tx, payload, version={'version': 'new'}) tx.commit() rows = session.run('MATCH (n) WHERE n.graph_scope=$scope RETURN n.name AS name', scope=scope).data() assert rows == [{'name': 'Original'}] assert builder.graph_version(session) == {'version': 'old'} def test_successful_replacement_commits_graph_and_version_together(managed_graph): session, scope = managed_graph payload = replacement(scope); version = {'version': 'new', 'git_revision': 'new-commit'} for _ in range(2): with session.begin_transaction() as tx: builder.write_graph_transaction(tx, payload, version=version) tx.commit() assert builder.graph_version(session) == version nodes, relationships = builder.graph_counts(session) assert dict(nodes) == {'Person': 1, 'Document': 1} assert dict(relationships) == {'HAS_DOCUMENT': 1}