"""Tests for the search functionality.""" import pytest from unittest.mock import Mock, patch from src.search.searcher import Searcher, SearchOptions from src.models.document import Document, SourceType, SearchResult from src.search.embeddings import EmbeddingManager from src.search.vectorstore import VectorStore class TestSearcher: """Tests for the Searcher class.""" @pytest.fixture def mock_embedding_manager(self): """Create a mock embedding manager.""" manager = Mock(spec=EmbeddingManager) manager.embed_query.return_value = [0.1, 0.2, 0.3] manager.embed.return_value = [[0.1, 0.2, 0.3]] return manager @pytest.fixture def mock_vector_store(self): """Create a mock vector store.""" store = Mock(spec=VectorStore) store.search.return_value = [ { "id": "doc1", "content": "Test content", "metadata": { "source_type": "openapi", "title": "Test Doc", "file_path": "/test/doc.yaml", }, "distance": 0.1, "score": 0.9, } ] store.get_all_documents.return_value = [] store.get_stats.return_value = Mock( total_documents=1, openapi_count=1, readme_count=0, code_count=0, ) return store @pytest.fixture def searcher(self, mock_embedding_manager, mock_vector_store): """Create a searcher with mocked dependencies.""" with patch.object(Searcher, "__init__", lambda self: None): searcher = Searcher() searcher._embedding_manager = mock_embedding_manager searcher._vector_store = mock_vector_store searcher._config = Mock() searcher._config.default_limit = 10 return searcher def test_search_returns_results(self, searcher, mock_vector_store): """Test that search returns results.""" results = searcher.search("test query") assert len(results) > 0 result = results[0] assert isinstance(result, SearchResult) assert result.document.id == "doc1" assert result.score > 0 def test_search_with_limit(self, searcher, mock_vector_store): """Test search with limit option.""" options = SearchOptions(limit=5) searcher.search("test query", options) mock_vector_store.search.assert_called_once() call_args = mock_vector_store.search.call_args assert call_args.kwargs.get("n_results") == 10 def test_search_with_source_filter(self, searcher, mock_vector_store): """Test search with source type filter.""" options = SearchOptions(source_type=SourceType.OPENAPI) searcher.search("test query", options) mock_vector_store.search.assert_called_once() call_args = mock_vector_store.search.call_args assert call_args.kwargs.get("source_type") == SourceType.OPENAPI def test_empty_query_returns_empty(self, searcher): """Test that empty query returns empty list.""" results = searcher.search("") assert results == [] results = searcher.search(" ") assert results == [] def test_search_creates_document_from_result(self, searcher, mock_vector_store): """Test that search properly creates Document objects.""" results = searcher.search("test query") assert len(results) > 0 result = results[0] assert isinstance(result.document, Document) assert result.document.source_type == SourceType.OPENAPI assert result.document.title == "Test Doc" def test_keyword_extraction(self, searcher): """Test keyword extraction from query.""" keywords = searcher._extract_keywords("How do I authenticate users") assert "authenticate" in keywords assert "users" in keywords assert "how" not in keywords assert "do" not in keywords assert "i" not in keywords def test_keyword_score(self, searcher): """Test keyword matching score calculation.""" keywords = ["authenticate", "users"] high_score = searcher._calculate_keyword_score( keywords, "How to authenticate users with JWT" ) assert high_score > 0 low_score = searcher._calculate_keyword_score( keywords, "This is unrelated content" ) assert low_score == 0 def test_highlight_generation(self, searcher): """Test highlight snippet generation.""" highlights = searcher._generate_highlights( "authenticate users", "To authenticate users, you need to provide valid credentials." ) assert len(highlights) > 0 assert any("authenticate" in h.lower() or "users" in h.lower() for h in highlights) def test_hybrid_search(self, searcher, mock_vector_store): """Test hybrid search combines semantic and keyword results.""" mock_vector_store.search.return_value = [ { "id": "doc1", "content": "Test content about authentication", "metadata": { "source_type": "openapi", "title": "Auth Doc", "file_path": "/test/auth.yaml", }, "distance": 0.1, "score": 0.9, } ] results = searcher.hybrid_search("authentication") assert len(results) > 0 mock_vector_store.search.assert_called() class TestSearchOptions: """Tests for SearchOptions.""" def test_default_options(self): """Test default search options.""" options = SearchOptions() assert options.limit == 10 assert options.source_type is None assert options.min_score == 0.0 assert options.include_scores is True def test_custom_options(self): """Test custom search options.""" options = SearchOptions( limit=20, source_type=SourceType.README, min_score=0.5, ) assert options.limit == 20 assert options.source_type == SourceType.README assert options.min_score == 0.5 class TestSearchResult: """Tests for SearchResult.""" def test_search_result_creation(self): """Test creating a search result.""" doc = Document( id="test", content="test content", source_type=SourceType.OPENAPI, title="Test", ) result = SearchResult(document=doc, score=0.95) assert result.document == doc assert result.score == 0.95 assert result.highlights == [] def test_search_result_to_dict(self): """Test converting search result to dictionary.""" doc = Document( id="test", content="test content", source_type=SourceType.OPENAPI, title="Test", ) result = SearchResult(document=doc, score=0.95, highlights=["test"]) result_dict = result.to_dict() assert result_dict["id"] == "test" assert result_dict["score"] == 0.95 assert "test" in result_dict["highlights"]