Memory MCP Server
by evangstav
- tests
"""Tests for KnowledgeGraphManager."""
import asyncio
from typing import List
import pytest
from memory_mcp_server.interfaces import Entity, Relation
from memory_mcp_server.knowledge_graph_manager import KnowledgeGraphManager
from memory_mcp_server.validation import EntityValidationError, ValidationError
@pytest.mark.asyncio(scope="function")
async def test_create_entities(
knowledge_graph_manager: KnowledgeGraphManager,
) -> None:
"""Test the creation of new entities in the knowledge graph.
This test verifies that:
1. Entities can be created successfully
2. The created entities are stored in the graph
3. Entity attributes are preserved correctly
"""
print("\nStarting test_create_entities")
entities = [
Entity(
name="john-doe",
entityType="person",
observations=["loves pizza"],
)
]
created_entities = await knowledge_graph_manager.create_entities(entities)
print("Created entities")
assert len(created_entities) == 1
graph = await knowledge_graph_manager.read_graph()
print("Read graph")
assert len(graph.entities) == 1
assert graph.entities[0].name == "john-doe"
print("test_create_entities: Complete")
@pytest.mark.asyncio(scope="function")
async def test_create_relations(
knowledge_graph_manager: KnowledgeGraphManager,
) -> None:
"""Test the creation of relations between entities.
This test verifies that:
1. Relations can be created between existing entities
2. Relations are stored properly in the graph
3. Relation properties (from, to, type) are preserved
"""
print("\nStarting test_create_relations")
entities = [
Entity(name="alice-smith", entityType="person", observations=["test"]),
Entity(name="bob-jones", entityType="person", observations=["test"]),
]
await knowledge_graph_manager.create_entities(entities)
print("Created entities")
relations = [Relation(from_="alice-smith", to="bob-jones", relationType="knows")]
created_relations = await knowledge_graph_manager.create_relations(relations)
print("Created relations")
assert len(created_relations) == 1
assert created_relations[0].from_ == "alice-smith"
assert created_relations[0].to == "bob-jones"
print("test_create_relations: Complete")
@pytest.mark.asyncio(scope="function")
async def test_search_functionality(
knowledge_graph_manager: KnowledgeGraphManager,
) -> None:
"""Test the search functionality across different criteria.
This test verifies searching by:
1. Entity name
2. Entity type
3. Observation content
4. Case insensitivity
"""
# Create test entities with varied data
entities = [
Entity(
name="search-test-1",
entityType="project",
observations=["keyword1", "unique1"],
),
Entity(name="search-test-2", entityType="project", observations=["keyword2"]),
Entity(name="different-type", entityType="document", observations=["keyword1"]),
]
await knowledge_graph_manager.create_entities(entities)
# Test search by name
name_result = await knowledge_graph_manager.search_nodes("search-test")
assert len(name_result.entities) == 2
assert all("search-test" in e.name for e in name_result.entities)
# Test search by type
type_result = await knowledge_graph_manager.search_nodes("document")
assert len(type_result.entities) == 1
assert type_result.entities[0].name == "different-type"
# Test search by observation
obs_result = await knowledge_graph_manager.search_nodes("keyword1")
assert len(obs_result.entities) == 2
assert any(e.name == "search-test-1" for e in obs_result.entities)
assert any(e.name == "different-type" for e in obs_result.entities)
@pytest.mark.asyncio(scope="function")
async def test_error_handling(
knowledge_graph_manager: KnowledgeGraphManager,
) -> None:
"""Test error handling in various scenarios.
This test verifies proper error handling for:
1. Invalid entity names
2. Non-existent entities in relations
3. Empty delete requests
4. Deleting non-existent entities
"""
# Test invalid entity name
with pytest.raises(EntityValidationError, match="Invalid entity name"):
await knowledge_graph_manager.create_entities(
[Entity(name="Invalid Name", entityType="person", observations=[])]
)
# Test relation with non-existent entities
with pytest.raises(ValidationError, match="Entities not found"):
await knowledge_graph_manager.create_relations(
[
Relation(
from_="non-existent", to="also-non-existent", relationType="knows"
)
]
)
# Test deleting empty list
with pytest.raises(ValueError, match="cannot be empty"):
await knowledge_graph_manager.delete_entities([])
# Test deleting non-existent entities
result = await knowledge_graph_manager.delete_entities(["non-existent"])
assert result == []
@pytest.mark.asyncio(scope="function")
async def test_graph_persistence(
knowledge_graph_manager: KnowledgeGraphManager,
) -> None:
"""Test that graph changes persist after reloading.
This test verifies that:
1. Created entities persist after a graph reload
2. Added relations persist after a graph reload
3. New observations persist after a graph reload
"""
# Create initial data
entity = Entity(
name="persistence-test", entityType="project", observations=["initial"]
)
await knowledge_graph_manager.create_entities([entity])
# Force a reload of the graph by clearing the cache
knowledge_graph_manager._cache = None # type: ignore
# Verify data persists
graph = await knowledge_graph_manager.read_graph()
assert len(graph.entities) == 1
assert graph.entities[0].name == "persistence-test"
assert "initial" in graph.entities[0].observations
@pytest.mark.asyncio(scope="function")
async def test_concurrent_operations(
knowledge_graph_manager: KnowledgeGraphManager,
) -> None:
"""Test handling of concurrent operations.
This test verifies that:
1. Multiple concurrent entity creations/deletions are handled properly
2. Cache remains consistent under concurrent operations
3. No data is lost during concurrent writes
"""
# Create multiple entities concurrently
async def create_entity(index: int) -> List[Entity]:
entity = Entity(
name=f"concurrent-{index}",
entityType="project",
observations=[f"obs{index}"],
)
return await knowledge_graph_manager.create_entities([entity])
# Delete entities concurrently
async def delete_entity(index: int) -> List[str]:
return await knowledge_graph_manager.delete_entities([f"concurrent-{index}"])
# First create 5 entities
create_tasks = [create_entity(i) for i in range(5)]
create_results = await asyncio.gather(*create_tasks)
assert all(len(r) == 1 for r in create_results)
# Then concurrently delete 3 of them while creating 2 more
delete_tasks = [delete_entity(i) for i in range(3)]
create_tasks = [create_entity(i) for i in range(5, 7)]
delete_results, create_results = await asyncio.gather(
asyncio.gather(*delete_tasks), asyncio.gather(*create_tasks)
)
# Verify deletions
assert all(len(r) == 1 for r in delete_results)
# Verify creations
assert all(len(r) == 1 for r in create_results)
# Verify final state
graph = await knowledge_graph_manager.read_graph()
expected_names = {"concurrent-5", "concurrent-6", "concurrent-3", "concurrent-4"}
assert len(graph.entities) == 4
assert all(e.name in expected_names for e in graph.entities)