Skip to main content
Glama
test_context_integration.py22 kB
""" Integration tests for Context parameter acceptance in Phase 1. This test suite verifies that all tool and operation functions accept the Context parameter as required by FastMCP's Context API. Phase 1 only validates parameter acceptance - actual context usage will be tested in Phase 2+. """ import pytest import sys import os from unittest.mock import Mock, MagicMock, AsyncMock from fastmcp import Context # Add project root to Python path sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..')) from src.tools.discover import discover_subreddits, validate_subreddit, SearchConfig, calculate_confidence_from_distance from src.tools.search import search_in_subreddit from src.tools.posts import fetch_subreddit_posts, fetch_multiple_subreddits from src.tools.comments import fetch_submission_with_comments @pytest.fixture def mock_context(): """Create a mock Context object for testing.""" return Mock(spec=Context) @pytest.fixture def mock_reddit(): """Create a mock Reddit client.""" return Mock() @pytest.fixture def mock_chroma(): """Mock ChromaDB client and collection.""" with Mock() as mock_client: mock_collection = Mock() mock_collection.query.return_value = { 'metadatas': [[ {'name': 'test', 'subscribers': 1000, 'url': 'https://reddit.com/r/test', 'nsfw': False} ]], 'distances': [[0.5]] } yield mock_client, mock_collection class TestDiscoverOperations: """Test discover_subreddits accepts context.""" async def test_discover_accepts_context(self, mock_context, monkeypatch): """Verify discover_subreddits accepts context parameter.""" # Mock the chroma client mock_client = Mock() mock_collection = Mock() mock_collection.query.return_value = { 'metadatas': [[ {'name': 'test', 'subscribers': 1000, 'url': 'https://reddit.com/r/test', 'nsfw': False} ]], 'distances': [[0.5]] } def mock_get_client(): return mock_client def mock_get_collection(name, client): return mock_collection monkeypatch.setattr('src.tools.discover.get_chroma_client', mock_get_client) monkeypatch.setattr('src.tools.discover.get_collection', mock_get_collection) # Call with context result = await discover_subreddits(query="test", limit=5, ctx=mock_context) # Verify result structure (not context usage - that's Phase 2) assert "subreddits" in result or "error" in result class TestSearchOperations: """Test search_in_subreddit accepts context.""" def test_search_accepts_context(self, mock_context, mock_reddit): """Verify search_in_subreddit accepts context parameter.""" mock_subreddit = Mock() mock_subreddit.display_name = "test" mock_subreddit.search.return_value = [] mock_reddit.subreddit.return_value = mock_subreddit result = search_in_subreddit( subreddit_name="test", query="test query", reddit=mock_reddit, limit=5, ctx=mock_context ) assert "results" in result or "error" in result class TestPostOperations: """Test post-fetching functions accept context.""" def test_fetch_posts_accepts_context(self, mock_context, mock_reddit): """Verify fetch_subreddit_posts accepts context parameter.""" mock_subreddit = Mock() mock_subreddit.display_name = "test" mock_subreddit.subscribers = 1000 mock_subreddit.public_description = "Test" mock_subreddit.hot.return_value = [] mock_reddit.subreddit.return_value = mock_subreddit result = fetch_subreddit_posts( subreddit_name="test", reddit=mock_reddit, limit=5, ctx=mock_context ) assert "posts" in result or "error" in result async def test_fetch_multiple_accepts_context(self, mock_context, mock_reddit): """Verify fetch_multiple_subreddits accepts context parameter.""" mock_multi = Mock() mock_multi.hot.return_value = [] mock_reddit.subreddit.return_value = mock_multi result = await fetch_multiple_subreddits( subreddit_names=["test1", "test2"], reddit=mock_reddit, limit_per_subreddit=5, ctx=mock_context ) assert "subreddits_requested" in result or "error" in result class TestCommentOperations: """Test comment-fetching functions accept context.""" async def test_fetch_comments_accepts_context(self, mock_context, mock_reddit): """Verify fetch_submission_with_comments accepts context parameter.""" mock_submission = Mock() mock_submission.id = "test123" mock_submission.title = "Test" mock_submission.author = Mock() mock_submission.author.__str__ = Mock(return_value="testuser") mock_submission.score = 100 mock_submission.upvote_ratio = 0.95 mock_submission.num_comments = 0 mock_submission.created_utc = 1234567890.0 mock_submission.url = "https://reddit.com/test" mock_submission.selftext = "" mock_submission.subreddit = Mock() mock_submission.subreddit.display_name = "test" # Mock comments mock_comments = Mock() mock_comments.__iter__ = Mock(return_value=iter([])) mock_comments.replace_more = Mock() mock_submission.comments = mock_comments mock_reddit.submission.return_value = mock_submission result = await fetch_submission_with_comments( reddit=mock_reddit, submission_id="test123", comment_limit=10, ctx=mock_context ) assert "submission" in result or "error" in result class TestHelperFunctions: """Test helper functions accept context.""" def test_validate_subreddit_accepts_context(self, mock_context, monkeypatch): """Verify validate_subreddit accepts context parameter.""" # Mock the chroma client mock_client = Mock() mock_collection = Mock() mock_collection.query.return_value = { 'metadatas': [[ {'name': 'test', 'subscribers': 1000, 'nsfw': False} ]], 'distances': [[0.5]] } def mock_get_client(): return mock_client def mock_get_collection(name, client): return mock_collection monkeypatch.setattr('src.tools.discover.get_chroma_client', mock_get_client) monkeypatch.setattr('src.tools.discover.get_collection', mock_get_collection) result = validate_subreddit("test", ctx=mock_context) assert "valid" in result or "error" in result class TestContextParameterPosition: """Test that context parameter works in various positions.""" def test_context_as_last_param(self, mock_context, mock_reddit): """Verify context works as the last parameter.""" mock_subreddit = Mock() mock_subreddit.display_name = "test" mock_subreddit.search.return_value = [] mock_reddit.subreddit.return_value = mock_subreddit # Context is last parameter result = search_in_subreddit( subreddit_name="test", query="test", reddit=mock_reddit, sort="relevance", time_filter="all", limit=10, ctx=mock_context ) assert result is not None def test_context_with_defaults(self, mock_context, mock_reddit): """Verify context works with default parameters.""" mock_subreddit = Mock() mock_subreddit.display_name = "test" mock_subreddit.search.return_value = [] mock_reddit.subreddit.return_value = mock_subreddit # Only required params + context result = search_in_subreddit( subreddit_name="test", query="test", reddit=mock_reddit, ctx=mock_context ) assert result is not None class TestDiscoverSubredditsProgress: """Test progress reporting in discover_subreddits.""" async def test_reports_progress_during_search(self, mock_context, monkeypatch): """Verify progress is reported during vector search.""" # Mock ChromaDB response with 3 results mock_client = Mock() mock_collection = Mock() mock_collection.query.return_value = { 'metadatas': [[ {'name': 'Python', 'subscribers': 1000000, 'nsfw': False}, {'name': 'learnpython', 'subscribers': 500000, 'nsfw': False}, {'name': 'pythontips', 'subscribers': 100000, 'nsfw': False} ]], 'distances': [[0.5, 0.7, 0.9]] } # Setup async mock for progress mock_context.report_progress = AsyncMock() def mock_get_client(): return mock_client def mock_get_collection(name, client): return mock_collection monkeypatch.setattr('src.tools.discover.get_chroma_client', mock_get_client) monkeypatch.setattr('src.tools.discover.get_collection', mock_get_collection) result = await discover_subreddits(query="python", ctx=mock_context) # Verify progress was reported at least 3 times (once per result) assert mock_context.report_progress.call_count >= 3 # Verify progress parameters first_call = mock_context.report_progress.call_args_list[0] # Check if arguments were passed as kwargs or positional args if first_call[1]: # kwargs assert 'progress' in first_call[1] assert 'total' in first_call[1] else: # positional assert len(first_call[0]) >= 2 class TestFetchMultipleProgress: """Test progress reporting in fetch_multiple_subreddits.""" async def test_reports_progress_per_subreddit(self, mock_context, mock_reddit): """Verify progress is reported once per subreddit.""" # Setup async mock for progress mock_context.report_progress = AsyncMock() # Mock submissions from 3 different subreddits mock_sub1 = Mock() mock_sub1.subreddit.display_name = "sub1" mock_sub1.id = "id1" mock_sub1.title = "Title 1" mock_sub1.author = Mock() mock_sub1.author.__str__ = Mock(return_value="user1") mock_sub1.score = 100 mock_sub1.num_comments = 10 mock_sub1.created_utc = 1234567890.0 mock_sub1.url = "https://reddit.com/test1" mock_sub1.permalink = "/r/sub1/comments/id1/" mock_sub2 = Mock() mock_sub2.subreddit.display_name = "sub2" mock_sub2.id = "id2" mock_sub2.title = "Title 2" mock_sub2.author = Mock() mock_sub2.author.__str__ = Mock(return_value="user2") mock_sub2.score = 200 mock_sub2.num_comments = 20 mock_sub2.created_utc = 1234567891.0 mock_sub2.url = "https://reddit.com/test2" mock_sub2.permalink = "/r/sub2/comments/id2/" mock_sub3 = Mock() mock_sub3.subreddit.display_name = "sub3" mock_sub3.id = "id3" mock_sub3.title = "Title 3" mock_sub3.author = Mock() mock_sub3.author.__str__ = Mock(return_value="user3") mock_sub3.score = 300 mock_sub3.num_comments = 30 mock_sub3.created_utc = 1234567892.0 mock_sub3.url = "https://reddit.com/test3" mock_sub3.permalink = "/r/sub3/comments/id3/" mock_multi = Mock() mock_multi.hot.return_value = [mock_sub1, mock_sub2, mock_sub3] mock_reddit.subreddit.return_value = mock_multi result = await fetch_multiple_subreddits( subreddit_names=["sub1", "sub2", "sub3"], reddit=mock_reddit, ctx=mock_context ) # Verify progress was reported at least 3 times (once per subreddit) assert mock_context.report_progress.call_count >= 3 class TestFetchCommentsProgress: """Test progress reporting in fetch_submission_with_comments.""" async def test_reports_progress_during_loading(self, mock_context, mock_reddit): """Verify progress is reported during comment loading.""" # Setup async mock for progress mock_context.report_progress = AsyncMock() # Mock submission mock_submission = Mock() mock_submission.id = "test123" mock_submission.title = "Test" mock_submission.author = Mock() mock_submission.author.__str__ = Mock(return_value="testuser") mock_submission.score = 100 mock_submission.upvote_ratio = 0.95 mock_submission.num_comments = 5 mock_submission.created_utc = 1234567890.0 mock_submission.url = "https://reddit.com/test" mock_submission.selftext = "" mock_submission.subreddit = Mock() mock_submission.subreddit.display_name = "test" # Mock 5 comments mock_comments_list = [] for i in range(5): mock_comment = Mock() mock_comment.id = f"comment{i}" mock_comment.body = f"Comment {i}" mock_comment.author = Mock() mock_comment.author.__str__ = Mock(return_value=f"user{i}") mock_comment.score = 10 * i mock_comment.created_utc = 1234567890.0 + i mock_comment.replies = [] mock_comments_list.append(mock_comment) mock_comments = Mock() mock_comments.__iter__ = Mock(return_value=iter(mock_comments_list)) mock_comments.replace_more = Mock() mock_submission.comments = mock_comments mock_reddit.submission.return_value = mock_submission result = await fetch_submission_with_comments( reddit=mock_reddit, submission_id="test123", comment_limit=10, ctx=mock_context ) # Verify progress was reported at least 6 times (5 comments + 1 completion) assert mock_context.report_progress.call_count >= 6 class TestSearchConfig: """Test SearchConfig customization in discover_subreddits.""" async def test_custom_min_confidence_filtering(self, mock_context, monkeypatch): """Verify min_confidence parameter filters results correctly.""" # Mock ChromaDB response with results at different confidence levels mock_client = Mock() mock_collection = Mock() mock_collection.query.return_value = { 'metadatas': [[ {'name': 'Python', 'subscribers': 1000000, 'nsfw': False}, # distance 0.3 -> high confidence {'name': 'learnpython', 'subscribers': 500000, 'nsfw': False}, # distance 0.9 -> medium confidence {'name': 'pythontips', 'subscribers': 100000, 'nsfw': False} # distance 1.5 -> low confidence ]], 'distances': [[0.3, 0.9, 1.5]] } def mock_get_client(): return mock_client def mock_get_collection(name, client): return mock_collection monkeypatch.setattr('src.tools.discover.get_chroma_client', mock_get_client) monkeypatch.setattr('src.tools.discover.get_collection', mock_get_collection) # Test with high min_confidence filter result = await discover_subreddits(query="python", min_confidence=0.7, ctx=mock_context) # Should only return results with confidence >= 0.7 assert "subreddits" in result # All returned subreddits should meet or exceed min_confidence for sub in result.get("subreddits", []): assert sub["confidence"] >= 0.7 async def test_custom_generic_subreddit_penalty(self, mock_context, monkeypatch): """Verify custom generic subreddit penalty is applied.""" # Mock ChromaDB response mock_client = Mock() mock_collection = Mock() mock_collection.query.return_value = { 'metadatas': [[ {'name': 'funny', 'subscribers': 1000000, 'nsfw': False}, # Generic sub {'name': 'specific_topic', 'subscribers': 10000, 'nsfw': False} ]], 'distances': [[0.5, 0.5]] # Same distance } def mock_get_client(): return mock_client def mock_get_collection(name, client): return mock_collection monkeypatch.setattr('src.tools.discover.get_chroma_client', mock_get_client) monkeypatch.setattr('src.tools.discover.get_collection', mock_get_collection) # Test with custom penalty multiplier custom_config = SearchConfig(GENERIC_PENALTY_MULTIPLIER=0.1) # Harsher penalty result = await discover_subreddits( query="jokes", limit=10, config=custom_config, ctx=mock_context ) # Both start with same base confidence at distance 0.5 # funny should have 0.1x penalty (generic) unless "jokes" is in the name # specific_topic should not be penalized assert "subreddits" in result if len(result["subreddits"]) >= 2: # The generic sub should have lower confidence than specific one confidences = {sub["name"]: sub["confidence"] for sub in result["subreddits"]} # This test validates the config is actually used async def test_custom_distance_thresholds(self, mock_context, monkeypatch): """Verify custom match tier distance thresholds are applied.""" # Mock ChromaDB response mock_client = Mock() mock_collection = Mock() mock_collection.query.return_value = { 'metadatas': [[ {'name': 'test1', 'subscribers': 1000, 'nsfw': False}, {'name': 'test2', 'subscribers': 1000, 'nsfw': False}, {'name': 'test3', 'subscribers': 1000, 'nsfw': False} ]], 'distances': [[0.15, 0.4, 0.8]] } def mock_get_client(): return mock_client def mock_get_collection(name, client): return mock_collection monkeypatch.setattr('src.tools.discover.get_chroma_client', mock_get_client) monkeypatch.setattr('src.tools.discover.get_collection', mock_get_collection) # Test with custom thresholds (stricter) custom_config = SearchConfig( EXACT_DISTANCE_THRESHOLD=0.1, # Stricter exact threshold SEMANTIC_DISTANCE_THRESHOLD=0.3, # Stricter semantic threshold ADJACENT_DISTANCE_THRESHOLD=0.6 # Stricter adjacent threshold ) result = await discover_subreddits(query="test", config=custom_config, ctx=mock_context) # Verify tier classification matches custom thresholds assert "subreddits" in result for sub in result["subreddits"]: if sub["distance"] < 0.1: assert sub["match_tier"] == "exact" elif sub["distance"] < 0.3: assert sub["match_tier"] == "semantic" elif sub["distance"] < 0.6: assert sub["match_tier"] == "adjacent" def test_confidence_calculation_function(self): """Verify calculate_confidence_from_distance function works correctly.""" # Test with default config conf_0_5 = calculate_confidence_from_distance(0.5) conf_1_0 = calculate_confidence_from_distance(1.0) conf_1_5 = calculate_confidence_from_distance(1.5) # Lower distances should give higher confidence assert conf_0_5 > conf_1_0 assert conf_1_0 > conf_1_5 # All should be between 0 and 1 assert 0.0 <= conf_0_5 <= 1.0 assert 0.0 <= conf_1_0 <= 1.0 assert 0.0 <= conf_1_5 <= 1.0 def test_custom_confidence_mapping(self): """Verify custom confidence distance mapping is applied.""" # Create custom config with different mapping custom_config = SearchConfig( CONFIDENCE_DISTANCE_BREAKPOINTS={ 0.5: 0.9, # At distance 0.5, confidence is 0.9 1.0: 0.5, # At distance 1.0, confidence is 0.5 2.0: 0.1 # At distance 2.0, confidence is 0.1 } ) # Test confidence at custom breakpoints conf_0_5 = calculate_confidence_from_distance(0.5, custom_config) conf_1_0 = calculate_confidence_from_distance(1.0, custom_config) conf_2_0 = calculate_confidence_from_distance(2.0, custom_config) # Should respect custom mapping assert conf_0_5 == 0.9 assert conf_1_0 == 0.5 assert conf_2_0 == 0.1 async def test_search_config_with_context(self, mock_context, monkeypatch): """Verify SearchConfig works with context parameter.""" mock_client = Mock() mock_collection = Mock() mock_collection.query.return_value = { 'metadatas': [[ {'name': 'test', 'subscribers': 1000, 'nsfw': False} ]], 'distances': [[0.5]] } def mock_get_client(): return mock_client def mock_get_collection(name, client): return mock_collection monkeypatch.setattr('src.tools.discover.get_chroma_client', mock_get_client) monkeypatch.setattr('src.tools.discover.get_collection', mock_get_collection) # Setup progress tracking mock_context.report_progress = AsyncMock() # Call with both custom config and context custom_config = SearchConfig(GENERIC_PENALTY_MULTIPLIER=0.2) result = await discover_subreddits( query="test", config=custom_config, ctx=mock_context ) # Both should work together assert "subreddits" in result or "error" in result assert mock_context.report_progress.called # Context was used

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/king-of-the-grackles/dialog-reddit-tools'

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