from unittest.mock import AsyncMock, MagicMock, patch import pytest from qdrant_client import models from vector_search_mcp.engine import get_engine from vector_search_mcp.models import Match, MatchAny, MatchExclude, SearchRow @pytest.fixture(autouse=True) def clear_engine_cache(): """Clear the engine cache before each test for proper isolation""" get_engine.cache_clear() yield get_engine.cache_clear() @pytest.fixture def mock_qdrant_client(): """Create a mock Qdrant client for testing""" client = AsyncMock() # Default search response client.search.return_value = [ models.ScoredPoint( id=1, score=0.95, payload={"text": "Test document 1", "category": "test"}, version=1, ), models.ScoredPoint( id=2, score=0.85, payload={"text": "Test document 2", "category": "test"}, version=1, ), ] return client @pytest.fixture def mock_settings(): """Create mock settings for testing""" settings = MagicMock() settings.url = "http://localhost:6333" settings.api_key = "test_api_key" return settings @pytest.fixture def sample_embedding(): """Provide a sample embedding vector for testing""" return [0.1, 0.2, 0.3, 0.4, 0.5] @pytest.fixture def sample_conditions(): """Provide sample conditions for testing""" return [ Match(key="category", value="document"), MatchAny(key="tags", any=["python", "rust"]), MatchExclude(key="status", exclude=["deleted"]), ] @pytest.fixture def sample_scored_points(): """Provide sample ScoredPoint objects for testing""" return [ models.ScoredPoint( id=1, score=0.95, payload={"text": "First document", "category": "tech"}, version=1, ), models.ScoredPoint( id=2, score=0.87, payload={"text": "Second document", "category": "science"}, version=1, ), models.ScoredPoint( id=3, score=0.75, payload=None, # This should be filtered out version=1, ), ] @pytest.fixture def sample_search_rows(): """Provide sample SearchRow objects for testing""" return [ SearchRow( chunk_id="1", score=0.95, payload={"text": "First document", "category": "tech"}, ), SearchRow( chunk_id="2", score=0.87, payload={"text": "Second document", "category": "science"}, ), ] @pytest.fixture def mock_qdrant_engine_dependencies(): """Mock all external dependencies for QdrantEngine""" with ( patch("vector_search_mcp.engine.qdrant_engine.Settings") as mock_settings_class, patch( "vector_search_mcp.engine.qdrant_engine.AsyncQdrantClient" ) as mock_client_class, ): # Setup mock settings mock_settings = MagicMock() mock_settings.url = "http://localhost:6333" mock_settings.api_key = "test_api_key" mock_settings_class.return_value = mock_settings # Setup mock client mock_client = AsyncMock() mock_client_class.return_value = mock_client yield { "settings_class": mock_settings_class, "client_class": mock_client_class, "settings": mock_settings, "client": mock_client, } @pytest.fixture def qdrant_filter_single(): """Create a single-condition Qdrant filter for testing""" return models.Filter( must=[ models.FieldCondition( key="category", match=models.MatchValue(value="document") ) ] ) @pytest.fixture def qdrant_filter_multiple(): """Create a multi-condition Qdrant filter for testing""" return models.Filter( must=[ models.FieldCondition( key="category", match=models.MatchValue(value="document") ), models.FieldCondition( key="tags", match=models.MatchAny(any=["python", "rust"]) ), models.FieldCondition( key="status", match=models.MatchExcept(**{"except": ["deleted"]}) ), ] )