Skip to main content
Glama

MCP Memory Service

test_api_memories_chronological.py11.2 kB
""" Test chronological ordering and pagination for the /api/memories endpoint. Tests verify that the GitHub issue #79 has been properly resolved by ensuring: 1. Memories are returned in chronological order (newest first) 2. Pagination works correctly with chronological ordering 3. All storage backends support the new ordering """ import pytest import asyncio import time import tempfile from datetime import datetime, timedelta from typing import List, Dict, Any import os # Import project modules # Note: This assumes the project is installed in editable mode with `pip install -e .` # or PYTHONPATH is configured correctly for the test environment from mcp_memory_service.models.memory import Memory from mcp_memory_service.storage.sqlite_vec import SqliteVecMemoryStorage class TestChronologicalOrdering: """Test chronological ordering functionality across all storage backends.""" async def create_test_memories(self, storage) -> List[Memory]: """Create test memories with different timestamps.""" memories = [] base_time = time.time() - 3600 # Start 1 hour ago # Create 5 test memories with 10-minute intervals for i in range(5): timestamp = base_time + (i * 600) # 10-minute intervals memory = Memory( content=f"Test memory {i + 1}", content_hash=f"hash_{i + 1}", tags=[f"tag{i + 1}", "test"], memory_type="test", metadata={"index": i + 1}, created_at=timestamp, updated_at=timestamp ) memories.append(memory) success, message = await storage.store(memory) assert success, f"Failed to store memory {i + 1}: {message}" return memories @pytest.mark.asyncio async def test_get_all_memories_chronological_order_sqlite(self): """Test that get_all_memories returns memories in chronological order (SQLite).""" with tempfile.TemporaryDirectory() as tmp_dir: storage = SqliteVecMemoryStorage(os.path.join(tmp_dir, "test.db")) await storage.initialize() # Create test memories original_memories = await self.create_test_memories(storage) # Get all memories retrieved_memories = await storage.get_all_memories() # Verify we got all memories assert len(retrieved_memories) == 5, f"Expected 5 memories, got {len(retrieved_memories)}" # Verify chronological order (newest first) for i in range(len(retrieved_memories) - 1): current_time = retrieved_memories[i].created_at or 0 next_time = retrieved_memories[i + 1].created_at or 0 assert current_time >= next_time, f"Memory at index {i} is older than memory at index {i + 1}" # Verify the actual order matches expectations (newest first) expected_order = [5, 4, 3, 2, 1] # Newest to oldest actual_order = [int(mem.content.split()[-1]) for mem in retrieved_memories] assert actual_order == expected_order, f"Expected order {expected_order}, got {actual_order}" @pytest.mark.asyncio async def test_pagination_with_chronological_order_sqlite(self): """Test pagination maintains chronological order (SQLite).""" with tempfile.TemporaryDirectory() as tmp_dir: storage = SqliteVecMemoryStorage(os.path.join(tmp_dir, "test.db")) await storage.initialize() # Create test memories await self.create_test_memories(storage) # Test pagination: Get first 2 memories first_page = await storage.get_all_memories(limit=2, offset=0) assert len(first_page) == 2 # Test pagination: Get next 2 memories second_page = await storage.get_all_memories(limit=2, offset=2) assert len(second_page) == 2 # Test pagination: Get last memory third_page = await storage.get_all_memories(limit=2, offset=4) assert len(third_page) == 1 # Verify chronological order across pages all_paginated = first_page + second_page + third_page # Should be in chronological order (newest first) for i in range(len(all_paginated) - 1): current_time = all_paginated[i].created_at or 0 next_time = all_paginated[i + 1].created_at or 0 assert current_time >= next_time, f"Pagination broke chronological order at position {i}" # Verify content order expected_content_order = ["Test memory 5", "Test memory 4", "Test memory 3", "Test memory 2", "Test memory 1"] actual_content_order = [mem.content for mem in all_paginated] assert actual_content_order == expected_content_order @pytest.mark.asyncio async def test_count_all_memories_sqlite(self): """Test count_all_memories method (SQLite).""" with tempfile.TemporaryDirectory() as tmp_dir: storage = SqliteVecMemoryStorage(os.path.join(tmp_dir, "test.db")) await storage.initialize() # Initially should be empty initial_count = await storage.count_all_memories() assert initial_count == 0 # Create test memories await self.create_test_memories(storage) # Should now have 5 memories final_count = await storage.count_all_memories() assert final_count == 5 @pytest.mark.asyncio async def test_empty_storage_handling_sqlite(self): """Test handling of empty storage (SQLite).""" with tempfile.TemporaryDirectory() as tmp_dir: storage = SqliteVecMemoryStorage(os.path.join(tmp_dir, "test.db")) await storage.initialize() # Test get_all_memories on empty storage memories = await storage.get_all_memories() assert memories == [] # Test with pagination on empty storage paginated = await storage.get_all_memories(limit=10, offset=0) assert paginated == [] # Test count on empty storage count = await storage.count_all_memories() assert count == 0 @pytest.mark.asyncio async def test_offset_beyond_total_sqlite(self): """Test offset beyond total records (SQLite).""" with tempfile.TemporaryDirectory() as tmp_dir: storage = SqliteVecMemoryStorage(os.path.join(tmp_dir, "test.db")) await storage.initialize() # Create test memories await self.create_test_memories(storage) # Test offset beyond total records memories = await storage.get_all_memories(limit=10, offset=100) assert memories == [] @pytest.mark.asyncio async def test_large_limit_sqlite(self): """Test large limit parameter (SQLite).""" with tempfile.TemporaryDirectory() as tmp_dir: storage = SqliteVecMemoryStorage(os.path.join(tmp_dir, "test.db")) await storage.initialize() # Create test memories await self.create_test_memories(storage) # Test limit larger than total records memories = await storage.get_all_memories(limit=100, offset=0) assert len(memories) == 5 # Should return all 5 memories @pytest.mark.asyncio async def test_mixed_timestamps_ordering_sqlite(self): """Test ordering with mixed/unsorted timestamps (SQLite).""" with tempfile.TemporaryDirectory() as tmp_dir: storage = SqliteVecMemoryStorage(os.path.join(tmp_dir, "test.db")) await storage.initialize() # Create memories with deliberately mixed timestamps base_time = time.time() timestamps = [base_time + 300, base_time + 100, base_time + 500, base_time + 200, base_time + 400] for i, timestamp in enumerate(timestamps): memory = Memory( content=f"Mixed memory {i + 1}", content_hash=f"mixed_hash_{i + 1}", tags=["mixed", "test"], memory_type="mixed", metadata={"timestamp": timestamp}, created_at=timestamp, updated_at=timestamp ) success, message = await storage.store(memory) assert success, f"Failed to store mixed memory {i + 1}: {message}" # Retrieve all memories memories = await storage.get_all_memories() # Should be ordered by timestamp (newest first) expected_order = [base_time + 500, base_time + 400, base_time + 300, base_time + 200, base_time + 100] actual_timestamps = [mem.created_at for mem in memories] assert actual_timestamps == expected_order, f"Expected {expected_order}, got {actual_timestamps}" class TestAPIChronologicalIntegration: """Integration tests that would test the actual API endpoints. These tests are structured to be easily adaptable for testing the actual FastAPI endpoints when a test client is available. """ def test_api_endpoint_structure(self): """Test that the API endpoint imports and structure are correct.""" # Import the API router to ensure it loads correctly from mcp_memory_service.web.api.memories import router # Verify the router exists and has the expected endpoints routes = [route.path for route in router.routes] assert "/memories" in routes assert "/memories/{content_hash}" in routes def test_memory_response_model(self): """Test that the response models include necessary fields for chronological ordering.""" from mcp_memory_service.web.api.memories import MemoryResponse, MemoryListResponse # Verify MemoryResponse has timestamp fields response_fields = MemoryResponse.__fields__.keys() assert "created_at" in response_fields assert "created_at_iso" in response_fields assert "updated_at" in response_fields assert "updated_at_iso" in response_fields # Verify MemoryListResponse has pagination fields list_fields = MemoryListResponse.__fields__.keys() assert "memories" in list_fields assert "total" in list_fields assert "page" in list_fields assert "page_size" in list_fields assert "has_more" in list_fields def test_storage_backend_type_compatibility(self): """Test that the API endpoints use the correct base storage type.""" from mcp_memory_service.web.api.memories import list_memories import inspect # Get the signature of the list_memories function sig = inspect.signature(list_memories) storage_param = sig.parameters['storage'] # Check that it uses the base MemoryStorage type, not a specific implementation assert 'MemoryStorage' in str(storage_param.annotation) if __name__ == "__main__": # Run tests directly pytest.main([__file__, "-v"])

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