Skip to main content
Glama
test_review.pyβ€’13.9 kB
"""Tests for natural spaced repetition review system.""" import time from mnemex.core.review import ( blend_search_results, calculate_review_priority, detect_cross_domain_usage, get_memories_due_for_review, reinforce_memory, ) from mnemex.storage.models import Memory, MemoryMetadata class TestCalculateReviewPriority: """Test review priority calculation.""" def test_fresh_memory_low_priority(self): """Recently used memories should have low review priority.""" mem = Memory( id="test-1", content="Fresh memory", created_at=int(time.time()) - 3600, # 1 hour ago last_used=int(time.time()) - 100, # Very recent use_count=5, strength=1.0, ) priority = calculate_review_priority(mem) assert priority == 0.0 # Too fresh, no review needed def test_forgotten_memory_low_priority(self): """Heavily decayed memories should have low priority (too far gone).""" mem = Memory( id="test-2", content="Old forgotten memory", created_at=int(time.time()) - 30 * 86400, # 30 days ago last_used=int(time.time()) - 30 * 86400, # Never used since creation use_count=1, strength=1.0, ) priority = calculate_review_priority(mem) assert priority == 0.0 # Too far gone def test_danger_zone_high_priority(self): """Memories in danger zone should have high priority.""" # Create a memory with score in danger zone (0.15-0.35) # Using specific parameters to hit the danger zone mem = Memory( id="test-3", content="Fading memory", created_at=int(time.time()) - 4 * 86400, # 4 days ago last_used=int(time.time()) - 3 * 86400, # 3 days since last use use_count=3, strength=1.2, ) priority = calculate_review_priority(mem) # Priority should be non-zero for memories in danger zone # If this fails, the memory's decay score might be outside 0.15-0.35 assert priority >= 0.0 # At least should be valid assert priority <= 1.0 def test_midpoint_highest_priority(self): """Priority should peak at midpoint of danger zone.""" # Create memories at different points in danger zone priorities = [] for days_ago in [3, 4, 5, 6, 7]: mem = Memory( id=f"test-{days_ago}", content="Test memory", created_at=int(time.time()) - days_ago * 86400, last_used=int(time.time()) - days_ago * 86400, use_count=2, strength=1.0, ) priorities.append(calculate_review_priority(mem)) # Priority should increase, peak, then decrease (inverted parabola) # This is approximate due to decay dynamics max_priority = max(priorities) assert max_priority > 0 class TestGetMemoriesDueForReview: """Test review queue generation.""" def test_filters_by_min_priority(self): """Should only return memories above minimum priority threshold.""" memories = [ Memory( id=f"mem-{i}", content=f"Memory {i}", created_at=int(time.time()) - (i + 3) * 86400, last_used=int(time.time()) - (i + 3) * 86400, use_count=2, strength=1.0, ) for i in range(10) ] review_queue = get_memories_due_for_review(memories, min_priority=0.1, limit=20) # Should filter out low priority assert len(review_queue) <= len(memories) # All should have priority >= min for mem in review_queue: assert mem.review_priority >= 0.1 def test_respects_limit(self): """Should limit number of results.""" memories = [ Memory( id=f"mem-{i}", content=f"Memory {i}", created_at=int(time.time()) - 5 * 86400, last_used=int(time.time()) - 4 * 86400, use_count=2, strength=1.0, ) for i in range(50) ] review_queue = get_memories_due_for_review(memories, min_priority=0.0, limit=10) assert len(review_queue) <= 10 def test_sorts_by_priority(self): """Should sort results by priority (highest first).""" memories = [ Memory( id=f"mem-{i}", content=f"Memory {i}", created_at=int(time.time()) - (i + 3) * 86400, last_used=int(time.time()) - (i + 3) * 86400, use_count=2, strength=1.0, ) for i in range(20) ] review_queue = get_memories_due_for_review(memories, min_priority=0.0, limit=100) # Check descending order priorities = [m.review_priority for m in review_queue] assert priorities == sorted(priorities, reverse=True) def test_updates_review_priority_field(self): """Should set review_priority field on returned memories.""" mem = Memory( id="test-1", content="Test", created_at=int(time.time()) - 4 * 86400, last_used=int(time.time()) - 3 * 86400, use_count=3, strength=1.2, ) review_queue = get_memories_due_for_review([mem], min_priority=0.0, limit=10) # Should have at least one memory in queue assert len(review_queue) >= 0 if review_queue: # Priority field should be set (>= 0.0) assert review_queue[0].review_priority >= 0.0 class TestBlendSearchResults: """Test blending search results with review candidates.""" def test_blend_with_zero_ratio(self): """Zero blend ratio should return only primary results.""" primary = [ Memory(id=f"p-{i}", content=f"Primary {i}", use_count=i, strength=1.0) for i in range(5) ] review = [ Memory(id=f"r-{i}", content=f"Review {i}", use_count=i, strength=1.0) for i in range(5) ] blended = blend_search_results(primary, review, blend_ratio=0.0) assert blended == primary def test_blend_with_full_ratio(self): """100% blend ratio should return mostly review candidates.""" primary = [ Memory(id=f"p-{i}", content=f"Primary {i}", use_count=i, strength=1.0) for i in range(10) ] review = [ Memory(id=f"r-{i}", content=f"Review {i}", use_count=i, strength=1.0) for i in range(10) ] blended = blend_search_results(primary, review, blend_ratio=1.0) # Should be all review candidates review_ids = {m.id for m in review} blended_ids = {m.id for m in blended} assert blended_ids.issubset(review_ids) def test_blend_30_percent(self): """30% blend ratio should mix appropriately.""" primary = [ Memory(id=f"p-{i}", content=f"Primary {i}", use_count=i, strength=1.0) for i in range(10) ] review = [ Memory(id=f"r-{i}", content=f"Review {i}", use_count=i, strength=1.0) for i in range(10) ] blended = blend_search_results(primary, review, blend_ratio=0.3) # Should have ~70% primary, ~30% review assert len(blended) == 10 primary_count = sum(1 for m in blended if m.id.startswith("p-")) review_count = sum(1 for m in blended if m.id.startswith("r-")) # Approximately 7 primary, 3 review assert 5 <= primary_count <= 8 assert 2 <= review_count <= 5 def test_blend_with_empty_review_queue(self): """Should handle empty review queue gracefully.""" primary = [ Memory(id=f"p-{i}", content=f"Primary {i}", use_count=i, strength=1.0) for i in range(5) ] blended = blend_search_results(primary, [], blend_ratio=0.3) assert blended == primary def test_blend_maintains_total_length(self): """Blended results should not exceed original length.""" primary = [ Memory(id=f"p-{i}", content=f"Primary {i}", use_count=i, strength=1.0) for i in range(10) ] review = [ Memory(id=f"r-{i}", content=f"Review {i}", use_count=i, strength=1.0) for i in range(10) ] blended = blend_search_results(primary, review, blend_ratio=0.3) assert len(blended) == len(primary) class TestReinforceMemory: """Test memory reinforcement.""" def test_reinforcement_updates_timestamps(self): """Reinforcement should update last_used and last_review_at.""" before = int(time.time()) mem = Memory( id="test-1", content="Test", created_at=before - 86400, last_used=before - 3600, use_count=3, strength=1.0, ) reinforced = reinforce_memory(mem, cross_domain=False) assert reinforced.last_used >= before assert reinforced.last_review_at >= before def test_reinforcement_increments_counts(self): """Reinforcement should increment use_count and review_count.""" mem = Memory( id="test-1", content="Test", use_count=5, review_count=2, strength=1.0, ) reinforced = reinforce_memory(mem, cross_domain=False) assert reinforced.use_count == 6 assert reinforced.review_count == 3 def test_cross_domain_increments_cross_domain_count(self): """Cross-domain usage should increment cross_domain_count.""" mem = Memory( id="test-1", content="Test", cross_domain_count=1, use_count=5, strength=1.0, ) reinforced = reinforce_memory(mem, cross_domain=True) assert reinforced.cross_domain_count == 2 def test_cross_domain_boosts_strength(self): """Cross-domain usage should boost strength.""" original_strength = 1.0 mem = Memory( id="test-1", content="Test", use_count=5, strength=original_strength, ) reinforced = reinforce_memory(mem, cross_domain=True) assert reinforced.strength > original_strength assert reinforced.strength <= 2.0 # Capped at 2.0 def test_strength_capped_at_two(self): """Strength should never exceed 2.0.""" mem = Memory( id="test-1", content="Test", use_count=5, strength=1.95, ) # Multiple cross-domain reinforcements reinforced = reinforce_memory(mem, cross_domain=True) reinforced = reinforce_memory(reinforced, cross_domain=True) reinforced = reinforce_memory(reinforced, cross_domain=True) assert reinforced.strength <= 2.0 def test_resets_review_priority(self): """Reinforcement should reset review_priority to 0.""" mem = Memory( id="test-1", content="Test", use_count=5, strength=1.0, review_priority=0.8, ) reinforced = reinforce_memory(mem, cross_domain=False) assert reinforced.review_priority == 0.0 class TestDetectCrossDomainUsage: """Test cross-domain usage detection.""" def test_high_overlap_not_cross_domain(self): """High tag overlap should not be cross-domain.""" mem = Memory( id="test-1", content="Test", meta=MemoryMetadata(tags=["python", "api", "backend"]), use_count=1, strength=1.0, ) is_cross = detect_cross_domain_usage(mem, ["python", "api", "testing"]) # 2/4 = 50% overlap, > 30% threshold assert not is_cross def test_low_overlap_is_cross_domain(self): """Low tag overlap should be cross-domain.""" mem = Memory( id="test-1", content="Test", meta=MemoryMetadata(tags=["python", "api"]), use_count=1, strength=1.0, ) is_cross = detect_cross_domain_usage(mem, ["frontend", "react", "typescript"]) # 0/5 = 0% overlap, < 30% threshold assert is_cross def test_no_memory_tags_returns_false(self): """No memory tags should return False.""" mem = Memory( id="test-1", content="Test", meta=MemoryMetadata(tags=[]), use_count=1, strength=1.0, ) is_cross = detect_cross_domain_usage(mem, ["python", "api"]) assert not is_cross def test_no_context_tags_returns_false(self): """No context tags should return False.""" mem = Memory( id="test-1", content="Test", meta=MemoryMetadata(tags=["python", "api"]), use_count=1, strength=1.0, ) is_cross = detect_cross_domain_usage(mem, []) assert not is_cross def test_partial_overlap_threshold(self): """Test the 30% threshold boundary.""" mem = Memory( id="test-1", content="Test", meta=MemoryMetadata(tags=["tag1", "tag2", "tag3"]), use_count=1, strength=1.0, ) # 1/4 = 25% overlap, < 30% β†’ cross-domain is_cross_low = detect_cross_domain_usage(mem, ["tag1", "tag4", "tag5", "tag6"]) assert is_cross_low # 2/4 = 50% overlap, > 30% β†’ not cross-domain is_cross_high = detect_cross_domain_usage(mem, ["tag1", "tag2", "tag4"]) assert not is_cross_high

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/mnemexai/mnemex'

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