Files
local-api-docs-search/tests/test_search.py
7000pctAUTO 10fa9462fe
Some checks failed
CI / test (push) Has been cancelled
CI / build (push) Has been cancelled
Add test files
2026-02-03 01:25:56 +00:00

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"]