219 lines
7.2 KiB
Python
219 lines
7.2 KiB
Python
"""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"]
|