Add test files
This commit is contained in:
218
tests/test_search.py
Normal file
218
tests/test_search.py
Normal file
@@ -0,0 +1,218 @@
|
||||
"""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"]
|
||||
Reference in New Issue
Block a user