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
"""
Unit tests for RelationshipInferenceEngine
Tests the intelligent relationship type classification system for knowledge graph associations.
"""
import pytest
import time
from mcp_memory_service.consolidation.relationship_inference import RelationshipInferenceEngine
@pytest.fixture
def engine():
"""Create RelationshipInferenceEngine instance with default settings."""
return RelationshipInferenceEngine(min_confidence=0.5)
@pytest.fixture
def strict_engine():
"""Create RelationshipInferenceEngine with strict confidence threshold."""
return RelationshipInferenceEngine(min_confidence=0.8)
class TestRelationshipInferenceEngineInit:
"""Tests for RelationshipInferenceEngine initialization."""
def test_default_initialization(self):
"""Engine should initialize with default confidence threshold."""
engine = RelationshipInferenceEngine()
assert engine.min_confidence == 0.6
def test_custom_confidence_threshold(self):
"""Engine should accept custom confidence threshold."""
engine = RelationshipInferenceEngine(min_confidence=0.75)
assert engine.min_confidence == 0.75
def test_zero_confidence_threshold(self):
"""Engine should accept zero confidence threshold."""
engine = RelationshipInferenceEngine(min_confidence=0.0)
assert engine.min_confidence == 0.0
class TestTypeCombinaticAnalysis:
"""Tests for type combination analysis."""
@pytest.mark.asyncio
async def test_learning_fixes_error(self, engine):
"""Learning/insight fixing an error/bug should be detected."""
rel_type, confidence = await engine.infer_relationship_type(
source_type="learning/insight",
target_type="error/bug",
source_content="Fixed authentication timeout by adjusting configuration",
target_content="Authentication error: Request timeout after 30 seconds",
source_timestamp=time.time(),
target_timestamp=time.time() - 100
)
assert rel_type == "fixes"
assert confidence >= 0.5
@pytest.mark.asyncio
async def test_error_fixes_error(self, engine):
"""One error/bug memory can fix another."""
rel_type, confidence = await engine.infer_relationship_type(
source_type="error/bug",
target_type="error/bug",
source_content="Fixed database deadlock issue",
target_content="Database deadlock on transaction commit",
source_timestamp=time.time(),
target_timestamp=time.time() - 100
)
assert rel_type == "fixes"
assert confidence >= 0.4
@pytest.mark.asyncio
async def test_valid_relationship_types_returned(self, engine):
"""Engine should only return valid relationship types."""
valid_types = ["related", "follows", "supports", "causes", "fixes", "contradicts"]
rel_type, confidence = await engine.infer_relationship_type(
source_type="observation",
target_type="observation",
source_content="Some content",
target_content="Other content",
source_timestamp=time.time(),
target_timestamp=time.time()
)
assert rel_type in valid_types
assert 0.0 <= confidence <= 1.0
class TestContentSemanticAnalysis:
"""Tests for content semantic analysis."""
@pytest.mark.asyncio
async def test_fix_keyword_detection(self, engine):
"""Content with 'fixed' can suggest fixes relationship."""
rel_type, confidence = await engine.infer_relationship_type(
source_type="observation",
target_type="observation",
source_content="Fixed the configuration bug by updating the settings file",
target_content="Configuration bug causing application crashes",
source_timestamp=time.time(),
target_timestamp=time.time() - 100
)
# Should detect fix signal or default to related
assert rel_type in ["fixes", "related"]
assert confidence >= 0.0
@pytest.mark.asyncio
async def test_contradiction_keyword_detection(self, engine):
"""Content with contradiction keywords can be detected."""
rel_type, confidence = await engine.infer_relationship_type(
source_type="observation",
target_type="observation",
source_content="However, testing shows that caching actually improves performance",
target_content="Caching degrades performance",
source_timestamp=time.time(),
target_timestamp=time.time() - 50
)
# Should detect contradiction or default to related
assert rel_type in ["contradicts", "related"]
assert confidence >= 0.0
@pytest.mark.asyncio
async def test_no_clear_pattern_defaults_to_related(self, engine):
"""Content without clear patterns should default to 'related'."""
rel_type, confidence = await engine.infer_relationship_type(
source_type="observation",
target_type="observation",
source_content="Meeting notes about Q1 planning",
target_content="Team lunch at restaurant",
source_timestamp=time.time(),
target_timestamp=time.time()
)
assert rel_type == "related"
assert confidence >= 0.0
class TestTemporalAnalysis:
"""Tests for temporal pattern analysis."""
@pytest.mark.asyncio
async def test_temporal_proximity_affects_confidence(self, engine):
"""Temporal proximity should affect confidence scores."""
now = time.time()
# Very close in time (1 minute)
rel_type1, conf1 = await engine.infer_relationship_type(
source_type="observation",
target_type="observation",
source_content="Deployed version 1.0",
target_content="Checked deployment status",
source_timestamp=now,
target_timestamp=now + 60
)
# Far apart (1 week)
rel_type, confidence = await engine.infer_relationship_type(
source_type="observation",
target_type="observation",
source_content="Deployed version 1.0",
target_content="Checked deployment status",
source_timestamp=now,
target_timestamp=now + 604800
)
# Closer events should have higher or equal confidence
assert conf1 >= confidence or abs(conf1 - confidence) < 0.1
@pytest.mark.asyncio
async def test_sequential_events_detected(self, engine):
"""Sequential events should be recognized."""
now = time.time()
rel_type, confidence = await engine.infer_relationship_type(
source_type="observation",
target_type="observation",
source_content="Started deployment at 10:00 AM",
target_content="Deployment completed at 10:15 AM",
source_timestamp=now,
target_timestamp=now + 900 # 15 minutes later
)
# Should detect temporal relationship
assert rel_type in ["follows", "related"]
assert confidence >= 0.0
class TestContradictionDetection:
"""Tests for contradiction detection."""
@pytest.mark.asyncio
async def test_negation_detection(self, engine):
"""Negation words can contribute to contradiction detection."""
rel_type, confidence = await engine.infer_relationship_type(
source_type="observation",
target_type="observation",
source_content="The cache doesn't improve performance at all",
target_content="Caching significantly improves performance",
source_timestamp=time.time(),
target_timestamp=time.time() - 100
)
# May detect contradiction or default to related
assert rel_type in ["contradicts", "related"]
assert confidence >= 0.0
@pytest.mark.asyncio
async def test_explicit_contradiction(self, engine):
"""Explicit contradiction keywords can be detected."""
rel_type, confidence = await engine.infer_relationship_type(
source_type="observation",
target_type="observation",
source_content="This contradicts our earlier findings about performance",
target_content="Performance improved by 50%",
source_timestamp=time.time(),
target_timestamp=time.time() - 50
)
# May detect contradiction or default to related
assert rel_type in ["contradicts", "related"]
assert confidence >= 0.0
class TestConfidenceThreshold:
"""Tests for confidence threshold behavior."""
@pytest.mark.asyncio
async def test_strict_threshold_more_related_results(self, strict_engine):
"""Strict threshold should result in more 'related' classifications."""
rel_type, confidence = await strict_engine.infer_relationship_type(
source_type="observation",
target_type="observation",
source_content="Some observation",
target_content="Another observation",
source_timestamp=time.time(),
target_timestamp=time.time()
)
# With strict threshold, should default to "related"
assert rel_type == "related"
@pytest.mark.asyncio
async def test_high_confidence_inference(self, engine):
"""Strong signals should result in high-confidence inference."""
rel_type, confidence = await engine.infer_relationship_type(
source_type="learning/insight",
target_type="error/bug",
source_content="Fixed the authentication bug by updating timeout configuration",
target_content="Authentication bug causing timeout errors",
source_timestamp=time.time(),
target_timestamp=time.time() - 100
)
# Multiple strong signals should result in high confidence
assert rel_type == "fixes"
assert confidence >= 0.6
class TestEdgeCases:
"""Tests for edge cases and error handling."""
@pytest.mark.asyncio
async def test_none_content_causes_error(self, engine):
"""None content currently raises AttributeError - documented behavior."""
# This documents current behavior - engine expects non-None content
with pytest.raises(AttributeError):
await engine.infer_relationship_type(
source_type="observation",
target_type="observation",
source_content=None,
target_content=None,
source_timestamp=time.time(),
target_timestamp=time.time()
)
@pytest.mark.asyncio
async def test_empty_content_handled(self, engine):
"""Empty content should be handled gracefully."""
rel_type, confidence = await engine.infer_relationship_type(
source_type="observation",
target_type="observation",
source_content="",
target_content="",
source_timestamp=time.time(),
target_timestamp=time.time()
)
assert rel_type == "related"
assert confidence >= 0.0
@pytest.mark.asyncio
async def test_very_long_content(self, engine):
"""Very long content should be processed without errors."""
long_content = "word " * 5000
rel_type, confidence = await engine.infer_relationship_type(
source_type="observation",
target_type="observation",
source_content=long_content,
target_content="short",
source_timestamp=time.time(),
target_timestamp=time.time()
)
# Should complete without error
assert rel_type in ["related", "follows", "supports", "causes", "fixes", "contradicts"]
assert 0.0 <= confidence <= 1.0
@pytest.mark.asyncio
async def test_unicode_content(self, engine):
"""Unicode content should be handled correctly."""
rel_type, confidence = await engine.infer_relationship_type(
source_type="observation",
target_type="observation",
source_content="修正了错误 🐛 Fixed bug",
target_content="发现了错误 Found bug 🔍",
source_timestamp=time.time(),
target_timestamp=time.time() - 100
)
# May detect "fixed" keyword or default to related
assert rel_type in ["fixes", "related"]
assert confidence >= 0.0
@pytest.mark.asyncio
async def test_special_regex_characters(self, engine):
"""Special regex characters in content should not cause errors."""
rel_type, confidence = await engine.infer_relationship_type(
source_type="observation",
target_type="observation",
source_content="Fixed bug with pattern: [a-z]+(?:\\d{2,4})?",
target_content="Bug in regex pattern causing failures",
source_timestamp=time.time(),
target_timestamp=time.time() - 50
)
# May detect fix signal or default to related
assert rel_type in ["fixes", "related"]
assert confidence >= 0.0
class TestTagAnalysis:
"""Tests for tag-based analysis."""
@pytest.mark.asyncio
async def test_shared_tags_influence(self, engine):
"""Shared tags should influence relationship detection."""
# Many shared tags
rel_type1, conf1 = await engine.infer_relationship_type(
source_type="observation",
target_type="observation",
source_content="First observation",
target_content="Second observation",
source_timestamp=time.time(),
target_timestamp=time.time(),
source_tags=["bug", "auth", "urgent"],
target_tags=["bug", "auth", "critical"]
)
# No shared tags
rel_type2, conf2 = await engine.infer_relationship_type(
source_type="observation",
target_type="observation",
source_content="First observation",
target_content="Second observation",
source_timestamp=time.time(),
target_timestamp=time.time(),
source_tags=["feature"],
target_tags=["docs"]
)
# More shared context should result in higher or equal confidence
assert conf1 >= conf2 or abs(conf1 - conf2) < 0.1
@pytest.mark.asyncio
async def test_no_tags_handled(self, engine):
"""Missing tags should not cause errors."""
rel_type, confidence = await engine.infer_relationship_type(
source_type="observation",
target_type="observation",
source_content="Content",
target_content="Other content",
source_timestamp=time.time(),
target_timestamp=time.time()
)
assert rel_type in ["related", "follows", "supports", "causes", "fixes", "contradicts"]
assert 0.0 <= confidence <= 1.0
class TestIntegrationScenarios:
"""Integration tests for realistic scenarios."""
@pytest.mark.asyncio
async def test_bug_fix_complete_workflow(self, engine):
"""Test complete bug discovery → fix workflow."""
now = time.time()
rel_type, confidence = await engine.infer_relationship_type(
source_type="learning/insight",
target_type="error/bug",
source_content="Fixed by increasing database timeout from 5s to 30s",
target_content="Database connection timeout error after 5 seconds",
source_timestamp=now + 100,
target_timestamp=now,
source_tags=["bug", "database", "fix"],
target_tags=["bug", "database", "timeout"]
)
# Multiple strong signals should result in "fixes" with high confidence
assert rel_type == "fixes"
assert confidence >= 0.6
@pytest.mark.asyncio
async def test_decision_support_chain(self, engine):
"""Test decision support relationships."""
now = time.time()
rel_type, confidence = await engine.infer_relationship_type(
source_type="decision/architecture",
target_type="decision/tool_choice",
source_content="Decided to use microservices which supports distributed deployment",
target_content="Selected Docker for service deployment",
source_timestamp=now,
target_timestamp=now + 3600,
source_tags=["architecture", "planning"],
target_tags=["architecture", "deployment", "docker"]
)
# Should detect relationship (supports or related)
assert rel_type in ["supports", "related"]
assert confidence >= 0.0
@pytest.mark.asyncio
async def test_conflicting_observations(self, engine):
"""Test detection of conflicting observations."""
now = time.time()
rel_type, confidence = await engine.infer_relationship_type(
source_type="observation",
target_type="observation",
source_content="However, performance tests show caching degrades performance",
target_content="Caching significantly improves performance",
source_timestamp=now + 3600,
target_timestamp=now,
source_tags=["performance"],
target_tags=["performance", "caching"]
)
# May detect contradiction or default to related
assert rel_type in ["contradicts", "related"]
assert confidence >= 0.0
@pytest.mark.asyncio
async def test_unrelated_memories(self, engine):
"""Test that clearly unrelated memories default to 'related'."""
rel_type, confidence = await engine.infer_relationship_type(
source_type="observation",
target_type="observation",
source_content="Meeting notes about quarterly planning",
target_content="Recipe for chocolate cake",
source_timestamp=time.time(),
target_timestamp=time.time() - 10000
)
assert rel_type == "related"
assert confidence >= 0.0
class TestReturnTypes:
"""Tests for return type validation."""
@pytest.mark.asyncio
async def test_returns_tuple(self, engine):
"""Engine should always return a tuple of (str, float)."""
result = await engine.infer_relationship_type(
source_type="observation",
target_type="observation",
source_content="Content",
target_content="Other content",
source_timestamp=time.time(),
target_timestamp=time.time()
)
assert isinstance(result, tuple)
assert len(result) == 2
assert isinstance(result[0], str)
assert isinstance(result[1], float)
@pytest.mark.asyncio
async def test_confidence_in_valid_range(self, engine):
"""Confidence should always be between 0.0 and 1.0."""
for _ in range(10): # Test multiple scenarios
rel_type, confidence = await engine.infer_relationship_type(
source_type="observation",
target_type="observation",
source_content=f"Content {_}",
target_content=f"Other {_}",
source_timestamp=time.time(),
target_timestamp=time.time() - _
)
assert 0.0 <= confidence <= 1.0