Files
2026-07-13 12:08:54 +08:00

138 lines
3.8 KiB
Python

import pytest
from lightrag.kg.memgraph_impl import MemgraphStorage
pytestmark = pytest.mark.offline
class _FakeNode(dict):
def __init__(self, node_id: int, entity_id: str, **properties):
super().__init__(entity_id=entity_id, **properties)
self.id = node_id
class _FakeResult:
def __init__(self, record):
self._record = record
async def single(self):
return self._record
async def consume(self):
return None
class _FakeSession:
def __init__(self, record, calls):
self._record = record
self._calls = calls
async def __aenter__(self):
return self
async def __aexit__(self, exc_type, exc, tb):
return False
async def run(self, query, parameters=None, **kwargs):
if parameters is None:
parameters = kwargs
self._calls.append((query, parameters))
return _FakeResult(self._record)
class _FakeDriver:
def __init__(self, record, calls):
self._record = record
self._calls = calls
def session(self, **kwargs):
return _FakeSession(self._record, self._calls)
def _make_storage(record):
calls = []
storage = MemgraphStorage(
namespace="chunk_entity_relation",
global_config={"max_graph_nodes": 1000},
embedding_func=None,
workspace="test",
)
storage._driver = _FakeDriver(record, calls)
storage._DATABASE = "memgraph"
return storage, calls
@pytest.mark.asyncio
async def test_get_knowledge_graph_preserves_isolated_start_node():
start_node = _FakeNode(1, "Start", description="isolated")
storage, calls = _make_storage(
{
"node_info": [{"node": start_node}],
"relationships": [],
"is_truncated": False,
}
)
result = await storage.get_knowledge_graph("Start", max_depth=0, max_nodes=1)
# Verify result data: isolated node must appear with correct labels and properties
assert len(result.nodes) == 1
assert result.nodes[0].labels == ["Start"]
assert result.nodes[0].properties["entity_id"] == "Start"
assert result.edges == []
assert result.is_truncated is False
# Verify query parameters: max_other_nodes must reserve a slot for the start node
assert len(calls) == 1
_, params = calls[0]
assert params["entity_id"] == "Start"
assert params["max_nodes"] == 1
assert (
params["max_other_nodes"] == 0
) # max_nodes - 1 = 0, start node occupies the only slot
@pytest.mark.asyncio
async def test_get_knowledge_graph_reserves_capacity_for_start_node_when_truncating():
start_node = _FakeNode(1, "Start")
storage, calls = _make_storage(
{
"node_info": [{"node": start_node}],
"relationships": [],
"is_truncated": True,
}
)
result = await storage.get_knowledge_graph("Start", max_depth=2, max_nodes=2)
# Verify truncation is reflected in result
assert result.is_truncated is True
assert len(result.nodes) == 1
assert result.edges == []
# Verify max_other_nodes leaves exactly one slot for the start node
assert len(calls) == 1
_, params = calls[0]
assert params["max_nodes"] == 2
assert (
params["max_other_nodes"] == 1
) # max_nodes - 1 = 1, start node always included
@pytest.mark.asyncio
async def test_get_knowledge_graph_max_nodes_zero_does_not_underflow():
"""max_other_nodes must not go negative when max_nodes=0."""
storage, calls = _make_storage(
{
"node_info": [],
"relationships": [],
"is_truncated": False,
}
)
await storage.get_knowledge_graph("Start", max_depth=1, max_nodes=0)
_, params = calls[0]
assert params["max_other_nodes"] == 0 # max(0 - 1, 0) = 0, no underflow