Skip to main content
Glama
test_graph_modes.py10 kB
"""Unit tests for consolidator graph storage mode switching. NOTE: These tests are scaffolding for Issue #279 Phase 2 implementation. Some tests are marked as xfail until the storage mode switching is fully implemented. """ import pytest import os import tempfile import shutil import sqlite3 from datetime import datetime from unittest.mock import AsyncMock, patch, MagicMock from mcp_memory_service.consolidation.base import MemoryAssociation, ConsolidationConfig from mcp_memory_service.models.memory import Memory from mcp_memory_service.storage.graph import GraphStorage @pytest.mark.unit class TestGraphStorageModes: """Test consolidator storage mode switching between memories_only, dual_write, and graph_only.""" @pytest.fixture def temp_db_path(self): """Create temporary database for mode testing.""" temp_dir = tempfile.mkdtemp() db_path = os.path.join(temp_dir, "test_modes.db") # Initialize both memories and graph tables conn = sqlite3.connect(db_path) # Memories table (simplified schema) conn.execute(""" CREATE TABLE IF NOT EXISTS memories ( content_hash TEXT PRIMARY KEY, content TEXT NOT NULL, tags TEXT, memory_type TEXT, metadata TEXT, created_at REAL ) """) # Graph table conn.execute(""" CREATE TABLE IF NOT EXISTS memory_graph ( source_hash TEXT NOT NULL, target_hash TEXT NOT NULL, similarity REAL NOT NULL, connection_types TEXT NOT NULL, metadata TEXT, created_at REAL NOT NULL, PRIMARY KEY (source_hash, target_hash) ) """) conn.commit() conn.close() yield db_path # Cleanup shutil.rmtree(temp_dir, ignore_errors=True) @pytest.fixture def sample_association(self): """Create sample association for testing.""" return MemoryAssociation( source_memory_hashes=["hash_test_a", "hash_test_b"], similarity_score=0.75, connection_type="semantic", discovery_method="test_engine", discovery_date=datetime.now(), metadata={"test": "data"} ) @pytest.mark.asyncio async def test_graph_storage_mode_env_variable(self): """Test that GRAPH_STORAGE_MODE environment variable is read correctly. Validates: - Config reads MCP_GRAPH_STORAGE_MODE from environment - Default value is 'dual_write' - Valid modes: memories_only, dual_write, graph_only """ from mcp_memory_service.config import GRAPH_STORAGE_MODE, VALID_GRAPH_MODES # Verify mode is one of valid values assert GRAPH_STORAGE_MODE in VALID_GRAPH_MODES, \ f"GRAPH_STORAGE_MODE should be one of {VALID_GRAPH_MODES}" # Verify VALID_GRAPH_MODES contains all expected values assert 'memories_only' in VALID_GRAPH_MODES assert 'dual_write' in VALID_GRAPH_MODES assert 'graph_only' in VALID_GRAPH_MODES @pytest.mark.asyncio async def test_mode_configuration_validation(self): """Test configuration validation for invalid storage modes. Validates: - Invalid mode values trigger warning - Default 'dual_write' is used as fallback - Warning logged for invalid configuration """ # This test verifies the validation logic exists from mcp_memory_service.config import VALID_GRAPH_MODES # Valid modes should be a list/tuple assert isinstance(VALID_GRAPH_MODES, (list, tuple)), \ "VALID_GRAPH_MODES should be a list or tuple" # Should have exactly 3 modes assert len(VALID_GRAPH_MODES) == 3, \ "Should have 3 valid graph storage modes" @pytest.mark.asyncio async def test_graph_storage_basic_operations(self, temp_db_path, sample_association): """Test GraphStorage basic operations work independently. This validates the graph storage layer is functional, regardless of consolidator integration. Validates: - Can create GraphStorage instance - Can store associations in graph table - Can query associations back """ storage = GraphStorage(temp_db_path) # Store association result = await storage.store_association( source_hash=sample_association.source_memory_hashes[0], target_hash=sample_association.source_memory_hashes[1], similarity=sample_association.similarity_score, connection_types=[sample_association.connection_type], metadata=sample_association.metadata ) assert result is True, "Should successfully store association" # Query back via find_connected connected = await storage.find_connected( sample_association.source_memory_hashes[0], max_hops=1 ) assert len(connected) > 0, "Should find stored association" assert connected[0][0] == sample_association.source_memory_hashes[1], \ "Should find the target hash" @pytest.mark.asyncio async def test_storage_size_comparison_concept(self, temp_db_path): """Test storage size comparison concept: graph vs memory objects. Validates: - Graph table schema is more efficient than Memory objects - Can measure database file size - Provides baseline for 97% reduction target NOTE: Actual size comparison will be validated when consolidator integration is complete in Phase 2. """ # Create 100 associations in graph table storage = GraphStorage(temp_db_path) for i in range(100): await storage.store_association( source_hash=f"hash_{i:03d}", target_hash=f"hash_{i+1:03d}", similarity=0.65, connection_types=["semantic"], metadata={"test": "data"} ) # Measure database file size db_size = os.path.getsize(temp_db_path) # Verify database was created and has reasonable size assert db_size > 0, "Database should have data" assert db_size < 1024 * 1024, "100 associations should be < 1MB" # Calculate approximate Memory object size for comparison # Each association as Memory object ~500-1000 bytes estimated_memory_size = 100 * 750 # Conservative estimate # Graph table should be more efficient print(f"\nStorage comparison (100 associations):") print(f" Graph table (db file): {db_size:,} bytes") print(f" Estimated Memory objects: {estimated_memory_size:,} bytes") print(f" Reduction: {(1 - db_size/estimated_memory_size)*100:.1f}%") @pytest.mark.xfail(reason="Awaiting Phase 2 consolidator integration (Issue #279)") @pytest.mark.asyncio async def test_memories_only_mode(self): """Test memories_only mode - stores only in memories table (legacy behavior). This test is scaffolding for Phase 2 implementation. Will pass once consolidator._store_associations() is updated. """ pytest.fail("Phase 2 implementation pending") @pytest.mark.xfail(reason="Awaiting Phase 2 consolidator integration (Issue #279)") @pytest.mark.asyncio async def test_dual_write_mode(self): """Test dual_write mode - stores in both memories and graph tables. This test is scaffolding for Phase 2 implementation. Will pass once consolidator integration is complete. """ pytest.fail("Phase 2 implementation pending") @pytest.mark.xfail(reason="Awaiting Phase 2 consolidator integration (Issue #279)") @pytest.mark.asyncio async def test_graph_only_mode(self): """Test graph_only mode - stores only in graph table (recommended). This test is scaffolding for Phase 2 implementation. Will pass once consolidator integration is complete. """ pytest.fail("Phase 2 implementation pending") @pytest.mark.xfail(reason="Awaiting Phase 2 consolidator integration (Issue #279)") @pytest.mark.asyncio async def test_memories_only_backward_compat(self): """Test backward compatibility with existing memories_only deployments. This test is scaffolding for Phase 2 implementation. Will pass once legacy behavior is preserved in new code. """ pytest.fail("Phase 2 implementation pending") @pytest.mark.xfail(reason="Awaiting Phase 2 consolidator integration (Issue #279)") @pytest.mark.asyncio async def test_dual_write_consistency(self): """Test consistency between memories and graph storage in dual_write mode. This test is scaffolding for Phase 2 implementation. Will pass once dual_write logic is implemented. """ pytest.fail("Phase 2 implementation pending") @pytest.mark.xfail(reason="Awaiting Phase 2 consolidator integration (Issue #279)") @pytest.mark.asyncio async def test_graph_only_no_memory_pollution(self): """Test that graph_only mode doesn't pollute memories table. This test is scaffolding for Phase 2 implementation. Will pass once graph_only mode is fully implemented. """ pytest.fail("Phase 2 implementation pending") @pytest.mark.xfail(reason="Awaiting Phase 2 consolidator integration (Issue #279)") @pytest.mark.asyncio async def test_mode_switching_runtime(self): """Test that storage mode can be changed at runtime via environment variable. This test is scaffolding for Phase 2 implementation. Will pass once mode switching logic is complete. """ pytest.fail("Phase 2 implementation pending")

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/doobidoo/mcp-memory-service'

If you have feedback or need assistance with the MCP directory API, please join our Discord server