"""Optional real Cypher regressions; all fixtures are rolled back.""" from __future__ import annotations import os import uuid import pytest from tools.graphrag import graph_search as gs from tools.graphrag.graph_db import create_neo4j_driver, get_neo4j_settings pytestmark = pytest.mark.skipif( os.getenv("RUN_NEO4J_TESTS") != "1", reason="Set RUN_NEO4J_TESTS=1 for isolated Neo4j regressions.", ) @pytest.fixture def graph(monkeypatch): scope = "regression-" + uuid.uuid4().hex monkeypatch.setattr(gs, "GRAPH_SCOPE", scope) category = "regcat-" + uuid.uuid4().hex settings = get_neo4j_settings() with ( create_neo4j_driver(settings) as driver, driver.session(database=settings.database) as session, ): tx = session.begin_transaction() try: for suffix, name, year, tag in [ ("a", "Alena Nová", 2024, "alpha"), ("b", "Boris Iný", 2024, "beta"), ("c", "Cyril Malý", 2025, "alpha"), ]: tx.run( "CREATE (p:Person {id:$id, name:$name, source_document:$path, graph_scope:$scope}) " "CREATE (d:Document {path:$path, source_url:$url, graph_scope:$scope}) " 'CREATE (t:Thesis {id:$thesis, title:$title, year:$year, thesis_type:"bachelor_thesis", graph_scope:$scope}) ' "MERGE (k:Keyword {id:$keyword, name:$tag, graph_scope:$scope}) " "CREATE (p)-[:HAS_DOCUMENT]->(d)-[:HAS_KEYWORD]->(k) " "CREATE (p)-[:AUTHORED]->(t)-[:EVIDENCED_BY]->(d)", id=scope + suffix, name=name, path=scope + "/" + suffix + ".md", url="https://example.test/" + suffix, scope=scope, thesis=scope + "th" + suffix, title="Výskum " + suffix, year=year, keyword=scope + tag, tag=tag, ).consume() tx.run( "MATCH (p:Person {id:$person})-[:HAS_DOCUMENT]->(d) " "CREATE (c:Category {name:$category, graph_scope:$scope}) " 'CREATE (a:DocumentAuthor {id:$author, name:"Autor Správny", graph_scope:$scope}) ' "CREATE (d)-[:IN_CATEGORY]->(c) CREATE (a)-[:AUTHORED_DOCUMENT]->(d) " "CREATE (child:Document {path:$child, graph_scope:$scope}) " 'CREATE (wrong:DocumentAuthor {id:$wrong, name:"Autor Iný", graph_scope:$scope}) ' "CREATE (p)-[:HAS_DOCUMENT]->(child)<-[:AUTHORED_DOCUMENT]-(wrong)", person=scope + "a", category=category, scope=scope, author=scope + "author", child=scope + "/child.md", wrong=scope + "wrong", ).consume() yield tx, category finally: tx.rollback() def test_real_keyword_year_conjunction(graph): tx, _ = graph result = gs.search_graph_with_session( tx, "Ktorá z osôb Alena Nová a Boris Iný má kľúčové slovo alpha a jej práca je v roku 2024?" ) assert {r["person"] for r in result["facts"] if r["fact_type"] == "person_thesis"} == { "Alena Nová" } assert {r["person"] for r in result["facts"] if r["fact_type"] == "person_keyword"} == { "Alena Nová" } def test_real_count_and_rejected_candidate_provenance(graph): tx, _ = graph result = gs.search_graph_with_session( tx, "Z kandidátov Alena Nová, Boris Iný, Cyril Malý vyber osoby s prácou v roku 2024. Koľko ich je?", ) assert result["aggregation"] == { "count": 2, "people": ["Alena Nová", "Boris Iný"], "candidate_count": 3, "complete": True, } assert "https://example.test/c" in {r["source_url"] for r in result["documents"]} def test_real_author_and_category_must_share_document(graph): tx, category = graph result = gs.search_graph_with_session( tx, f"Ktorý autor dokumentu je pri osobe Alena Nová spojený s kategóriou {category}?" ) authors = [ r["document_author"] for r in result["facts"] if r["fact_type"] == "person_document_author" ] assert authors == ["Autor Správny"] def test_real_shared_year_does_not_invent_intersection(graph): tx, _ = graph result = gs.search_graph_with_session( tx, "V ktorom rovnakom roku je práca pri osobách Alena Nová a Cyril Malý?" ) assert not any(r["fact_type"] in {"person_thesis", "person_work"} for r in result["facts"]) assert ( next(r for r in result["facts"] if r["fact_type"] == "academic_year_comparison")[ "shared_years" ] == [] ) def test_real_shared_keyword_uses_requested_keyword(graph): tx, _ = graph result = gs.search_graph_with_session( tx, "Sú osoby Alena Nová a Cyril Malý prepojené cez kľúčové slovo alpha? Uveď spoločné kľúčové slovo.", ) assert [r["keyword"] for r in result["facts"] if r["fact_type"] == "shared_keyword"] == [ "alpha" ] def test_real_unknown_keyword_is_not_a_global_listing(graph): tx, _ = graph result = gs.search_graph_with_session( tx, "Aký názov práce má Alena Nová, ak má kľúčové slovo missingtag?" ) assert result["facts"] == [] @pytest.mark.parametrize( "question", [ "V ktorom rovnakom roku je práca pri osobách Alena Nová a Boris Iný?", "Aký rok práce spája osoby Alena Nová a Boris Iný?", ], ) def test_real_shared_year_paraphrases_have_same_answer(graph, question): tx, _ = graph result = gs.search_graph_with_session(tx, question) assert next(r for r in result["facts"] if r["fact_type"] == "academic_year_comparison")[ "shared_years" ] == [2024] assert {r["year"] for r in result["facts"] if r["fact_type"] == "person_thesis"} == {2024} def test_real_title_and_keyword_are_both_returned(graph): tx, _ = graph result = gs.search_graph_with_session( tx, "Aký je názov práce osoby Alena Nová, ak jej dokument obsahuje kľúčové slovo alpha?" ) assert [ (r["person"], r["title"]) for r in result["facts"] if r["fact_type"] == "person_thesis" ] == [("Alena Nová", "Výskum a")] assert [r["keyword"] for r in result["facts"] if r["fact_type"] == "person_keyword"] == [ "alpha" ] def test_real_count_is_not_cut_off_by_presentation_limit(graph): tx, _ = graph result = gs.search_graph_with_session( tx, "Z kandidátov Alena Nová, Boris Iný, Cyril Malý vyber osoby s prácou v roku 2024. Koľko ich je?", limit=1, ) assert result["aggregation"]["count"] == 2 assert len(result["facts"]) == 1 assert result["truncated"]