Files
AI/ontology_platform/tests/unit/test_phase5_projection_graphrag.py

106 lines
3.5 KiB
Python
Raw Permalink Normal View History

2026-05-19 20:31:52 +09:00
"""Phase 5 projection and GraphRAG boundary tests."""
from __future__ import annotations
import asyncio
from datetime import datetime
import pytest
from ont_platform.core.graph.cypher_guard import ReadOnlyCypherGuard, UnsafeCypherError
from ont_platform.core.graph.search import CandidateGraphSearchService
from ont_platform.core.projection.rdf_to_neo4j import RDFToNeo4jProjector
from ont_platform.storage.candidate_repository import CandidateRepository
from ont_platform.storage.models import Base, SourceDocument
from sqlalchemy import create_engine
from sqlalchemy.orm import Session, sessionmaker
def _session() -> Session:
engine = create_engine("sqlite:///:memory:")
Base.metadata.create_all(engine)
return sessionmaker(bind=engine)()
def test_rdf_projection_keeps_neo4j_as_projection_store() -> None:
async def run():
projector = RDFToNeo4jProjector(project_id="proj_graph")
return await projector.preview_projection(
[
("http://example.test/Alice", "http://example.test/knows", "http://example.test/Bob"),
("http://example.test/Alice", "http://www.w3.org/2000/01/rdf-schema#label", "Alice"),
],
provenance={"source_url": "https://example.test/source", "evidence_ids": ["EV_graph"]},
)
result = asyncio.run(run())
payload = result.to_dict()
assert payload["contract"]["canonical_store"] == "rdf_fuseki"
assert payload["contract"]["projection_store"] == "neo4j"
assert payload["node_count"] >= 2
assert payload["relationships"][0]["provenance"]["evidence_ids"] == ["EV_graph"]
assert payload["source_graph_hash"]
def test_read_only_cypher_guard_blocks_writes_and_enforces_limit() -> None:
guard = ReadOnlyCypherGuard(max_limit=25)
sanitized = guard.sanitize("MATCH (n:Entity) RETURN n", limit=100)
assert "LIMIT 25" in sanitized.query
with pytest.raises(UnsafeCypherError):
guard.sanitize("MATCH (n) DETACH DELETE n")
def test_candidate_graph_search_returns_source_provenance() -> None:
db = _session()
document = SourceDocument(
id="doc_graph",
project_id="proj_graph",
source_url="https://example.test/source",
document_type="html",
title="Graph Source",
text="Alice works at Acme.",
content_hash="hash_graph",
fingerprint="fp_graph",
retrieved_at=datetime.utcnow(),
extracted_by="trafilatura",
)
db.add(document)
repository = CandidateRepository(db)
repository.save_lightweight_result(
project_id="proj_graph",
document_id="doc_graph",
result={
"entities": [
{
"id": "E_alice_graph",
"label": "Alice",
"type": "person",
"confidence": 0.9,
"evidence_ids": ["EV_graph"],
}
],
"relations": [],
"evidence_spans": [
{
"id": "EV_graph",
"text": "Alice works at Acme.",
"start_offset": 0,
"end_offset": 20,
}
],
},
)
results = CandidateGraphSearchService(db).search(
project_id="proj_graph",
query="alice",
limit=10,
)
assert len(results) == 1
payload = results[0].to_dict()
assert payload["provenance"]["source_url"] == "https://example.test/source"
assert payload["provenance"]["evidence_spans"][0]["id"] == "EV_graph"