Skip to main content
Glama
test_background_analysis.py7.24 kB
import pytest from unittest.mock import patch, MagicMock, AsyncMock import asyncio from code_flow_graph.mcp_server.analyzer import MCPAnalyzer, AnalysisState @pytest.fixture def mock_core_components(): """Mock core components to avoid actual initialization.""" with patch('code_flow_graph.mcp_server.analyzer.PythonASTExtractor') as mock_extractor, \ patch('code_flow_graph.mcp_server.analyzer.CallGraphBuilder') as mock_builder, \ patch('code_flow_graph.mcp_server.analyzer.CodeVectorStore') as mock_store: yield mock_extractor, mock_builder, mock_store def test_analyzer_initial_state(mock_core_components): """Test that analyzer starts in NOT_STARTED state.""" config = { 'watch_directories': ['.'], 'chromadb_path': './test_chroma' } analyzer = MCPAnalyzer(config) assert analyzer.analysis_state == AnalysisState.NOT_STARTED assert analyzer.analysis_task is None assert analyzer.analysis_error is None def test_is_ready_before_analysis(mock_core_components): """Test is_ready returns False before analysis.""" config = { 'watch_directories': ['.'], 'chromadb_path': './test_chroma' } analyzer = MCPAnalyzer(config) assert analyzer.is_ready() is False @pytest.mark.asyncio async def test_start_analysis_creates_task(mock_core_components): """Test that start_analysis creates a background task.""" mock_extractor, mock_builder, mock_store = mock_core_components config = { 'watch_directories': ['.'], 'chromadb_path': './test_chroma' } analyzer = MCPAnalyzer(config) # Mock the analyze method to avoid actual analysis analyzer.analyze = AsyncMock() # Start analysis await analyzer.start_analysis() # Check that task was created assert analyzer.analysis_task is not None # Give the task a moment to start await asyncio.sleep(0.01) # State should be IN_PROGRESS or COMPLETED assert analyzer.analysis_state in [AnalysisState.IN_PROGRESS, AnalysisState.COMPLETED] @pytest.mark.asyncio async def test_analysis_state_transitions(mock_core_components): """Test that analysis state transitions correctly.""" mock_extractor, mock_builder, mock_store = mock_core_components config = { 'watch_directories': ['.'], 'chromadb_path': './test_chroma' } analyzer = MCPAnalyzer(config) # Mock the analyze method to complete quickly async def mock_analyze(): pass analyzer.analyze = mock_analyze # Initial state assert analyzer.analysis_state == AnalysisState.NOT_STARTED # Start analysis await analyzer.start_analysis() # Wait for task to complete if analyzer.analysis_task: await analyzer.analysis_task # Should be completed assert analyzer.analysis_state == AnalysisState.COMPLETED @pytest.mark.asyncio async def test_analysis_failure_handling(mock_core_components): """Test that analysis failures are handled correctly.""" mock_extractor, mock_builder, mock_store = mock_core_components config = { 'watch_directories': ['.'], 'chromadb_path': './test_chroma' } analyzer = MCPAnalyzer(config) # Mock the analyze method to raise an error async def mock_analyze(): raise ValueError("Test error") analyzer.analyze = mock_analyze # Start analysis await analyzer.start_analysis() # Wait for task to complete if analyzer.analysis_task: await analyzer.analysis_task # Should be in FAILED state assert analyzer.analysis_state == AnalysisState.FAILED assert analyzer.analysis_error is not None assert isinstance(analyzer.analysis_error, ValueError) @pytest.mark.asyncio async def test_wait_for_analysis(mock_core_components): """Test wait_for_analysis method.""" mock_extractor, mock_builder, mock_store = mock_core_components config = { 'watch_directories': ['.'], 'chromadb_path': './test_chroma' } analyzer = MCPAnalyzer(config) # Mock the analyze method async def mock_analyze(): await asyncio.sleep(0.1) # Simulate some work analyzer.analyze = mock_analyze # Start analysis await analyzer.start_analysis() # Wait for analysis to complete result = await analyzer.wait_for_analysis(timeout=1.0) assert result is True assert analyzer.is_ready() is True @pytest.mark.asyncio async def test_ping_tool_includes_status(): """Test that ping tool includes analysis status.""" from code_flow_graph.mcp_server.server import ping_tool, server # Mock analyzer with IN_PROGRESS state mock_analyzer = MagicMock() mock_analyzer.analysis_state = AnalysisState.IN_PROGRESS mock_analyzer.builder.functions = {'test.func': MagicMock()} server.analyzer = mock_analyzer try: response = await ping_tool(message="test") assert response.status == "ok" assert response.echoed == "test" assert response.analysis_status == "in_progress" assert response.indexed_functions == 1 finally: if hasattr(server, 'analyzer'): delattr(server, 'analyzer') @pytest.mark.asyncio async def test_semantic_search_includes_status(): """Test that semantic_search includes analysis status.""" from code_flow_graph.mcp_server.server import semantic_search, server # Mock analyzer with COMPLETED state mock_store = MagicMock() mock_store.query_functions.return_value = [{"metadata": {"name": "test_func"}}] mock_analyzer = MagicMock() mock_analyzer.vector_store = mock_store mock_analyzer.analysis_state = AnalysisState.COMPLETED server.analyzer = mock_analyzer try: response = await semantic_search(query="test", n_results=1, filters={}, format="json") assert response.analysis_status == "completed" assert len(response.results) == 1 finally: if hasattr(server, 'analyzer'): delattr(server, 'analyzer') @pytest.mark.asyncio async def test_start_analysis_only_once(mock_core_components): """Test that start_analysis warns if called multiple times.""" mock_extractor, mock_builder, mock_store = mock_core_components config = { 'watch_directories': ['.'], 'chromadb_path': './test_chroma' } analyzer = MCPAnalyzer(config) # Mock analyze to take some time async def slow_analyze(): await asyncio.sleep(0.1) analyzer.analyze = slow_analyze # Start analysis first time await analyzer.start_analysis() first_task = analyzer.analysis_task # Give it a moment to start await asyncio.sleep(0.01) # Try to start again - should warn and not create new task await analyzer.start_analysis() second_task = analyzer.analysis_task # Should be the same task (second call should be ignored) assert first_task is second_task # Clean up if analyzer.analysis_task: await analyzer.analysis_task

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/mrorigo/code-flow-mcp'

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