7a0da7932b
OSV-Scanner (Scheduled) / scan-scheduled (push) Failing after 0s
Create Release / test-gate (push) Has been cancelled
Create Release / release-gate (push) Has been cancelled
Create Release / ci-gate (push) Has been cancelled
Create Release / version-check (push) Has been cancelled
Create Release / e2e-test-gate (push) Has been cancelled
Create Release / responsive-test-gate (push) Has been cancelled
Create Release / compat-test-gate (push) Has been cancelled
Create Release / compose-integration-gate (push) Has been cancelled
Create Release / vulture-gate (push) Has been cancelled
Create Release / build (push) Has been cancelled
Create Release / provenance (push) Has been cancelled
Create Release / prerelease-docker (push) Has been cancelled
Create Release / publish-docker (push) Has been cancelled
Create Release / create-release (push) Has been cancelled
Create Release / cleanup-changelog (push) Has been cancelled
Create Release / trigger-pypi (push) Has been cancelled
Create Release / monitor-pypi (push) Has been cancelled
Create Release / Clean up orphan prerelease tags and signatures (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-form] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-metrics] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-workflow] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-core] (push) Has been cancelled
CodeQL Advanced / Analyze (javascript-typescript) (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [history-news] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [library] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [link-analytics] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-core] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-lifecycle] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [error-benchmark] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-pages] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) (push) Has been cancelled
Docker Tests (Consolidated) / Accessibility Tests (push) Has been cancelled
Docker Tests (Consolidated) / LLM Unit Tests (push) Has been cancelled
Docker Tests (Consolidated) / LLM Example Tests (push) Has been cancelled
Docker Tests (Consolidated) / Production Image Smoke Test (push) Has been cancelled
Docker Tests (Consolidated) / Infrastructure Tests (push) Has been cancelled
OSSF Scorecard / OSSF Security Scorecard Analysis (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [mobile] (push) Has been cancelled
Backwards Compatibility / Verify Encryption Constants (push) Has been cancelled
Backwards Compatibility / PyPI Version Compatibility (push) Has been cancelled
Backwards Compatibility / Database Migration Tests (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled
Docker Tests (Consolidated) / detect-changes (push) Has been cancelled
Docker Tests (Consolidated) / Build Test Image (push) Has been cancelled
Docker Tests (Consolidated) / All Pytest Tests + Coverage (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [accessibility] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [api-crud] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-login] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-pages] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-register] (push) Has been cancelled
719 lines
24 KiB
Python
719 lines
24 KiB
Python
"""
|
|
Comprehensive tests for the Paperless-ngx search engine.
|
|
Tests initialization, API requests, search functionality, and document conversion.
|
|
|
|
Note: These tests mock HTTP requests to avoid requiring an actual Paperless instance.
|
|
"""
|
|
|
|
import pytest
|
|
from unittest.mock import Mock, patch
|
|
|
|
|
|
@pytest.fixture
|
|
def mock_requests_get():
|
|
"""Mock requests.get to avoid actual HTTP calls."""
|
|
with patch(
|
|
"local_deep_research.web_search_engines.engines.search_engine_paperless.requests.get"
|
|
) as mock_get:
|
|
yield mock_get
|
|
|
|
|
|
class TestPaperlessSearchEngineInit:
|
|
"""Tests for Paperless search engine initialization."""
|
|
|
|
def test_init_with_api_url_and_token(self, mock_requests_get):
|
|
"""Test initialization with API URL and token."""
|
|
from local_deep_research.web_search_engines.engines.search_engine_paperless import (
|
|
PaperlessSearchEngine,
|
|
)
|
|
|
|
engine = PaperlessSearchEngine(
|
|
api_url="http://localhost:8000", api_key="test_token"
|
|
)
|
|
|
|
assert engine.api_url == "http://localhost:8000"
|
|
assert engine.api_token == "test_token"
|
|
assert engine.headers["Authorization"] == "Token test_token"
|
|
|
|
def test_init_with_api_token_compatibility(self, mock_requests_get):
|
|
"""Test initialization with api_token parameter (backwards compatibility)."""
|
|
from local_deep_research.web_search_engines.engines.search_engine_paperless import (
|
|
PaperlessSearchEngine,
|
|
)
|
|
|
|
engine = PaperlessSearchEngine(
|
|
api_url="http://localhost:8000", api_token="legacy_token"
|
|
)
|
|
|
|
assert engine.api_token == "legacy_token"
|
|
|
|
def test_init_strips_trailing_slash(self, mock_requests_get):
|
|
"""Test that trailing slash is stripped from API URL."""
|
|
from local_deep_research.web_search_engines.engines.search_engine_paperless import (
|
|
PaperlessSearchEngine,
|
|
)
|
|
|
|
engine = PaperlessSearchEngine(api_url="http://localhost:8000/")
|
|
|
|
assert engine.api_url == "http://localhost:8000"
|
|
|
|
def test_init_default_values(self, mock_requests_get):
|
|
"""Test initialization with default values."""
|
|
from local_deep_research.web_search_engines.engines.search_engine_paperless import (
|
|
PaperlessSearchEngine,
|
|
)
|
|
|
|
engine = PaperlessSearchEngine()
|
|
|
|
assert engine.max_results == 10
|
|
assert engine.timeout == 30
|
|
assert engine.verify_ssl is True
|
|
assert engine.include_content is True
|
|
|
|
def test_init_with_custom_max_results(self, mock_requests_get):
|
|
"""Test initialization with custom max results."""
|
|
from local_deep_research.web_search_engines.engines.search_engine_paperless import (
|
|
PaperlessSearchEngine,
|
|
)
|
|
|
|
engine = PaperlessSearchEngine(max_results=25)
|
|
|
|
assert engine.max_results == 25
|
|
|
|
def test_init_with_custom_timeout(self, mock_requests_get):
|
|
"""Test initialization with custom timeout."""
|
|
from local_deep_research.web_search_engines.engines.search_engine_paperless import (
|
|
PaperlessSearchEngine,
|
|
)
|
|
|
|
engine = PaperlessSearchEngine(timeout=60)
|
|
|
|
assert engine.timeout == 60
|
|
|
|
def test_init_with_ssl_disabled(self, mock_requests_get):
|
|
"""Test initialization with SSL verification disabled."""
|
|
from local_deep_research.web_search_engines.engines.search_engine_paperless import (
|
|
PaperlessSearchEngine,
|
|
)
|
|
|
|
engine = PaperlessSearchEngine(verify_ssl=False)
|
|
|
|
assert engine.verify_ssl is False
|
|
|
|
def test_init_with_content_disabled(self, mock_requests_get):
|
|
"""Test initialization with content inclusion disabled."""
|
|
from local_deep_research.web_search_engines.engines.search_engine_paperless import (
|
|
PaperlessSearchEngine,
|
|
)
|
|
|
|
engine = PaperlessSearchEngine(include_content=False)
|
|
|
|
assert engine.include_content is False
|
|
|
|
def test_init_from_settings_snapshot(self, mock_requests_get):
|
|
"""Test initialization from settings snapshot."""
|
|
from local_deep_research.web_search_engines.engines.search_engine_paperless import (
|
|
PaperlessSearchEngine,
|
|
)
|
|
|
|
settings = {
|
|
"search.engine.web.paperless.default_params.api_url": "http://paperless.local:9000",
|
|
"search.engine.web.paperless.api_key": "snapshot_token",
|
|
}
|
|
|
|
engine = PaperlessSearchEngine(settings_snapshot=settings)
|
|
|
|
assert engine.api_url == "http://paperless.local:9000"
|
|
assert engine.api_token == "snapshot_token"
|
|
|
|
def test_init_with_no_token_empty_headers(self, mock_requests_get):
|
|
"""Test that headers are empty when no token provided."""
|
|
from local_deep_research.web_search_engines.engines.search_engine_paperless import (
|
|
PaperlessSearchEngine,
|
|
)
|
|
|
|
engine = PaperlessSearchEngine(api_url="http://localhost:8000")
|
|
|
|
assert "Authorization" not in engine.headers
|
|
|
|
|
|
class TestPaperlessAPIRequest:
|
|
"""Tests for Paperless API request handling."""
|
|
|
|
@pytest.fixture
|
|
def engine(self, mock_requests_get):
|
|
"""Create a Paperless engine."""
|
|
from local_deep_research.web_search_engines.engines.search_engine_paperless import (
|
|
PaperlessSearchEngine,
|
|
)
|
|
|
|
return PaperlessSearchEngine(
|
|
api_url="http://localhost:8000", api_key="test_token"
|
|
)
|
|
|
|
def test_make_request_success(self, engine, mock_requests_get):
|
|
"""Test successful API request."""
|
|
mock_response = Mock()
|
|
mock_response.json.return_value = {"results": [], "count": 0}
|
|
mock_response.raise_for_status = Mock()
|
|
mock_requests_get.return_value = mock_response
|
|
|
|
result = engine._make_request("/api/documents/", {"query": "test"})
|
|
|
|
assert result == {"results": [], "count": 0}
|
|
mock_requests_get.assert_called_once()
|
|
|
|
def test_make_request_includes_auth_header(self, engine, mock_requests_get):
|
|
"""Test that request includes authorization header."""
|
|
mock_response = Mock()
|
|
mock_response.json.return_value = {}
|
|
mock_response.raise_for_status = Mock()
|
|
mock_requests_get.return_value = mock_response
|
|
|
|
engine._make_request("/api/documents/")
|
|
|
|
call_kwargs = mock_requests_get.call_args[1]
|
|
assert call_kwargs["headers"]["Authorization"] == "Token test_token"
|
|
|
|
def test_make_request_handles_exception(self, engine, mock_requests_get):
|
|
"""Test that request exceptions are handled gracefully."""
|
|
import requests
|
|
|
|
mock_requests_get.side_effect = requests.exceptions.RequestException(
|
|
"Connection error"
|
|
)
|
|
|
|
result = engine._make_request("/api/documents/")
|
|
|
|
assert result == {}
|
|
|
|
def test_make_request_uses_correct_timeout(self, engine, mock_requests_get):
|
|
"""Test that request uses configured timeout."""
|
|
mock_response = Mock()
|
|
mock_response.json.return_value = {}
|
|
mock_response.raise_for_status = Mock()
|
|
mock_requests_get.return_value = mock_response
|
|
|
|
engine._make_request("/api/documents/")
|
|
|
|
call_kwargs = mock_requests_get.call_args[1]
|
|
assert call_kwargs["timeout"] == 30
|
|
|
|
|
|
class TestPaperlessSearchExecution:
|
|
"""Tests for Paperless search execution."""
|
|
|
|
@pytest.fixture
|
|
def engine(self, mock_requests_get):
|
|
"""Create a Paperless engine."""
|
|
from local_deep_research.web_search_engines.engines.search_engine_paperless import (
|
|
PaperlessSearchEngine,
|
|
)
|
|
|
|
return PaperlessSearchEngine(
|
|
api_url="http://localhost:8000",
|
|
api_key="test_token",
|
|
max_results=10,
|
|
)
|
|
|
|
def test_get_previews_success(self, engine, mock_requests_get):
|
|
"""Test successful preview retrieval."""
|
|
mock_response = Mock()
|
|
mock_response.json.return_value = {
|
|
"results": [
|
|
{
|
|
"id": 1,
|
|
"title": "Test Document",
|
|
"content": "This is test content about the query.",
|
|
"__search_hit__": {
|
|
"score": 0.95,
|
|
"rank": 1,
|
|
"highlights": "Test <span>query</span> content",
|
|
},
|
|
}
|
|
],
|
|
"count": 1,
|
|
}
|
|
mock_response.raise_for_status = Mock()
|
|
mock_requests_get.return_value = mock_response
|
|
|
|
previews = engine._get_previews("test query")
|
|
|
|
assert len(previews) == 1
|
|
assert previews[0]["title"] == "Test Document"
|
|
assert "query" in previews[0]["snippet"]
|
|
|
|
def test_get_previews_empty_results(self, engine, mock_requests_get):
|
|
"""Test preview retrieval with no results."""
|
|
mock_response = Mock()
|
|
mock_response.json.return_value = {"results": [], "count": 0}
|
|
mock_response.raise_for_status = Mock()
|
|
mock_requests_get.return_value = mock_response
|
|
|
|
previews = engine._get_previews("nonexistent query")
|
|
|
|
assert previews == []
|
|
|
|
def test_get_previews_handles_exception(self, engine, mock_requests_get):
|
|
"""Test that preview exceptions are handled."""
|
|
import requests
|
|
|
|
mock_requests_get.side_effect = requests.exceptions.RequestException(
|
|
"Error"
|
|
)
|
|
|
|
previews = engine._get_previews("test")
|
|
|
|
assert previews == []
|
|
|
|
def test_multi_pass_search_deduplicates(self, engine, mock_requests_get):
|
|
"""Test that multi-pass search deduplicates results."""
|
|
# Return same document in multiple passes
|
|
mock_response = Mock()
|
|
mock_response.json.return_value = {
|
|
"results": [
|
|
{"id": 1, "title": "Doc 1", "__search_hit__": {"score": 0.9}},
|
|
{"id": 2, "title": "Doc 2", "__search_hit__": {"score": 0.8}},
|
|
],
|
|
"count": 2,
|
|
}
|
|
mock_response.raise_for_status = Mock()
|
|
mock_requests_get.return_value = mock_response
|
|
|
|
results = engine._multi_pass_search("test")
|
|
|
|
# Should deduplicate by doc_id
|
|
assert len(results) == 2
|
|
|
|
|
|
class TestPaperlessDocumentConversion:
|
|
"""Tests for document conversion to LDR format."""
|
|
|
|
@pytest.fixture
|
|
def engine(self, mock_requests_get):
|
|
"""Create a Paperless engine."""
|
|
from local_deep_research.web_search_engines.engines.search_engine_paperless import (
|
|
PaperlessSearchEngine,
|
|
)
|
|
|
|
return PaperlessSearchEngine(api_url="http://localhost:8000")
|
|
|
|
def test_convert_document_with_highlights(self, engine, mock_requests_get):
|
|
"""Test document conversion with search highlights."""
|
|
doc_data = {
|
|
"id": 123,
|
|
"title": "Invoice 2024",
|
|
"content": "Full content here",
|
|
"correspondent_name": "Acme Corp",
|
|
"created": "2024-01-15",
|
|
"__search_hit__": {
|
|
"score": 0.95,
|
|
"rank": 1,
|
|
"highlights": "Found <span>matching</span> text here",
|
|
},
|
|
}
|
|
|
|
preview = engine._convert_document_to_preview(doc_data, "matching")
|
|
|
|
assert "Invoice 2024" in preview["title"]
|
|
assert preview["url"] == "http://localhost:8000/documents/123/details"
|
|
assert "matching" in preview["snippet"]
|
|
assert preview["author"] == "Acme Corp"
|
|
assert preview["source"] == "Paperless"
|
|
|
|
def test_convert_document_cleans_html_tags(self, engine, mock_requests_get):
|
|
"""Test that HTML tags are cleaned from highlights."""
|
|
doc_data = {
|
|
"id": 1,
|
|
"title": "Test",
|
|
"__search_hit__": {
|
|
"highlights": "<span class='match'>keyword</span> in text"
|
|
},
|
|
}
|
|
|
|
preview = engine._convert_document_to_preview(doc_data)
|
|
|
|
assert "<span" not in preview["snippet"]
|
|
assert "</span>" not in preview["snippet"]
|
|
# Highlights are converted to markdown bold
|
|
assert "**keyword**" in preview["snippet"]
|
|
|
|
def test_convert_document_without_highlights(
|
|
self, engine, mock_requests_get
|
|
):
|
|
"""Test document conversion when no highlights available."""
|
|
doc_data = {
|
|
"id": 1,
|
|
"title": "Test Document",
|
|
"content": "This is the document content with the query term inside.",
|
|
}
|
|
|
|
preview = engine._convert_document_to_preview(doc_data, "query")
|
|
|
|
# Should use content as fallback
|
|
assert "content" in preview["snippet"]
|
|
|
|
def test_convert_document_builds_enhanced_title(
|
|
self, engine, mock_requests_get
|
|
):
|
|
"""Test that enhanced title includes metadata."""
|
|
doc_data = {
|
|
"id": 1,
|
|
"title": "Report Q4",
|
|
"correspondent_name": "Finance Dept",
|
|
"document_type_name": "Financial Report",
|
|
"created": "2024-03-15",
|
|
"__search_hit__": {"highlights": "text"},
|
|
}
|
|
|
|
preview = engine._convert_document_to_preview(doc_data)
|
|
|
|
# Enhanced title should include correspondent, title, type, year
|
|
assert "Finance Dept" in preview["title"]
|
|
assert "Report Q4" in preview["title"]
|
|
assert "Financial Report" in preview["title"]
|
|
assert "2024" in preview["title"]
|
|
|
|
def test_convert_document_includes_metadata(
|
|
self, engine, mock_requests_get
|
|
):
|
|
"""Test that metadata is included in preview."""
|
|
doc_data = {
|
|
"id": 42,
|
|
"title": "Test",
|
|
"correspondent_name": "Test Sender",
|
|
"document_type_name": "Invoice",
|
|
"created": "2024-01-01",
|
|
"modified": "2024-01-02",
|
|
"archive_serial_number": "ASN-001",
|
|
"__search_hit__": {"score": 0.85, "rank": 3, "highlights": "text"},
|
|
}
|
|
|
|
preview = engine._convert_document_to_preview(doc_data)
|
|
|
|
assert preview["metadata"]["doc_id"] == "42"
|
|
assert preview["metadata"]["correspondent"] == "Test Sender"
|
|
assert preview["metadata"]["document_type"] == "Invoice"
|
|
assert preview["metadata"]["search_score"] == 0.85
|
|
assert preview["metadata"]["search_rank"] == 3
|
|
|
|
def test_convert_document_multiple_highlights(
|
|
self, engine, mock_requests_get
|
|
):
|
|
"""Test document conversion with multiple highlights returns multiple previews."""
|
|
doc_data = {
|
|
"id": 1,
|
|
"title": "Multi-match Document",
|
|
"__search_hit__": {
|
|
"highlights": [
|
|
"First <span>match</span> here",
|
|
"Second <span>match</span> there",
|
|
"Third <span>match</span> elsewhere",
|
|
]
|
|
},
|
|
}
|
|
|
|
previews = engine._convert_document_to_preview(doc_data)
|
|
|
|
# Should return list of previews for multiple highlights
|
|
assert isinstance(previews, list)
|
|
assert len(previews) == 3
|
|
assert "(excerpt 1)" in previews[0]["title"]
|
|
assert "(excerpt 2)" in previews[1]["title"]
|
|
|
|
|
|
class TestPaperlessFullContent:
|
|
"""Tests for Paperless full content retrieval."""
|
|
|
|
@pytest.fixture
|
|
def engine(self, mock_requests_get):
|
|
"""Create a Paperless engine."""
|
|
from local_deep_research.web_search_engines.engines.search_engine_paperless import (
|
|
PaperlessSearchEngine,
|
|
)
|
|
|
|
return PaperlessSearchEngine(api_url="http://localhost:8000")
|
|
|
|
def test_get_full_content_from_raw_data(self, engine, mock_requests_get):
|
|
"""Test full content extraction from raw data."""
|
|
items = [
|
|
{
|
|
"title": "Test Doc",
|
|
"snippet": "Short snippet",
|
|
"metadata": {"doc_id": "1"},
|
|
"_raw_data": {"content": "This is the full document content."},
|
|
}
|
|
]
|
|
|
|
results = engine._get_full_content(items)
|
|
|
|
assert (
|
|
results[0]["full_content"] == "This is the full document content."
|
|
)
|
|
assert "_raw_data" not in results[0]
|
|
|
|
def test_get_full_content_fetches_if_missing(
|
|
self, engine, mock_requests_get
|
|
):
|
|
"""Test that content is fetched if not in raw data."""
|
|
mock_response = Mock()
|
|
mock_response.json.return_value = {"content": "Fetched content"}
|
|
mock_response.raise_for_status = Mock()
|
|
mock_requests_get.return_value = mock_response
|
|
|
|
items = [
|
|
{
|
|
"title": "Test",
|
|
"snippet": "Snippet",
|
|
"metadata": {"doc_id": "123"},
|
|
"_raw_data": {}, # No content in raw data
|
|
}
|
|
]
|
|
|
|
results = engine._get_full_content(items)
|
|
|
|
assert results[0]["full_content"] == "Fetched content"
|
|
|
|
def test_get_full_content_disabled(self, mock_requests_get):
|
|
"""Test that content is not fetched when disabled."""
|
|
from local_deep_research.web_search_engines.engines.search_engine_paperless import (
|
|
PaperlessSearchEngine,
|
|
)
|
|
|
|
engine = PaperlessSearchEngine(
|
|
api_url="http://localhost:8000", include_content=False
|
|
)
|
|
|
|
items = [{"title": "Test", "snippet": "Snippet"}]
|
|
|
|
results = engine._get_full_content(items)
|
|
|
|
# Should return items unchanged
|
|
assert results == items
|
|
|
|
def test_get_full_content_handles_exception(
|
|
self, engine, mock_requests_get
|
|
):
|
|
"""Test that exceptions during full content retrieval are handled."""
|
|
items = [
|
|
{
|
|
"title": "Test",
|
|
"snippet": "Snippet fallback",
|
|
"metadata": {"doc_id": "1"},
|
|
# Missing _raw_data will cause issues
|
|
}
|
|
]
|
|
|
|
results = engine._get_full_content(items)
|
|
|
|
# Should fallback to snippet
|
|
assert results[0]["full_content"] == "Snippet fallback"
|
|
|
|
|
|
class TestPaperlessQueryExpansion:
|
|
"""Tests for LLM-based query expansion."""
|
|
|
|
@pytest.fixture
|
|
def engine(self, mock_requests_get):
|
|
"""Create a Paperless engine without LLM."""
|
|
from local_deep_research.web_search_engines.engines.search_engine_paperless import (
|
|
PaperlessSearchEngine,
|
|
)
|
|
|
|
return PaperlessSearchEngine(api_url="http://localhost:8000")
|
|
|
|
def test_expand_query_without_llm(self, engine, mock_requests_get):
|
|
"""Test that query is returned unchanged without LLM."""
|
|
result = engine._expand_query_with_llm("original query")
|
|
|
|
assert result == "original query"
|
|
|
|
def test_expand_query_with_llm(self, mock_requests_get):
|
|
"""Test query expansion with LLM."""
|
|
from local_deep_research.web_search_engines.engines.search_engine_paperless import (
|
|
PaperlessSearchEngine,
|
|
)
|
|
|
|
mock_llm = Mock()
|
|
mock_llm.invoke.return_value = Mock(
|
|
content='invoice OR "billing statement" OR receipt'
|
|
)
|
|
|
|
engine = PaperlessSearchEngine(
|
|
api_url="http://localhost:8000", llm=mock_llm
|
|
)
|
|
|
|
result = engine._expand_query_with_llm("find my invoices")
|
|
|
|
assert "invoice" in result.lower() or "billing" in result.lower()
|
|
|
|
def test_expand_query_handles_llm_exception(self, mock_requests_get):
|
|
"""Test that LLM exceptions fall back to original query."""
|
|
from local_deep_research.web_search_engines.engines.search_engine_paperless import (
|
|
PaperlessSearchEngine,
|
|
)
|
|
|
|
mock_llm = Mock()
|
|
mock_llm.invoke.side_effect = Exception("LLM error")
|
|
|
|
engine = PaperlessSearchEngine(
|
|
api_url="http://localhost:8000", llm=mock_llm
|
|
)
|
|
|
|
result = engine._expand_query_with_llm("test query")
|
|
|
|
assert result == "test query"
|
|
|
|
|
|
class TestPaperlessRun:
|
|
"""Tests for Paperless run method."""
|
|
|
|
@pytest.fixture
|
|
def engine(self, mock_requests_get):
|
|
"""Create a Paperless engine."""
|
|
from local_deep_research.web_search_engines.engines.search_engine_paperless import (
|
|
PaperlessSearchEngine,
|
|
)
|
|
|
|
return PaperlessSearchEngine(api_url="http://localhost:8000")
|
|
|
|
def test_run_success(self, engine, mock_requests_get):
|
|
"""Test successful search run."""
|
|
mock_response = Mock()
|
|
mock_response.json.return_value = {
|
|
"results": [
|
|
{
|
|
"id": 1,
|
|
"title": "Test Doc",
|
|
"content": "Full content",
|
|
"__search_hit__": {"score": 0.9, "highlights": "text"},
|
|
}
|
|
],
|
|
"count": 1,
|
|
}
|
|
mock_response.raise_for_status = Mock()
|
|
mock_requests_get.return_value = mock_response
|
|
|
|
results = engine.run("test query")
|
|
|
|
assert len(results) == 1
|
|
assert "full_content" in results[0]
|
|
|
|
def test_run_empty_results(self, engine, mock_requests_get):
|
|
"""Test run with no results."""
|
|
mock_response = Mock()
|
|
mock_response.json.return_value = {"results": [], "count": 0}
|
|
mock_response.raise_for_status = Mock()
|
|
mock_requests_get.return_value = mock_response
|
|
|
|
results = engine.run("nonexistent")
|
|
|
|
assert results == []
|
|
|
|
def test_run_handles_exception(self, engine, mock_requests_get):
|
|
"""Test that run handles exceptions."""
|
|
import requests
|
|
|
|
mock_requests_get.side_effect = requests.exceptions.RequestException(
|
|
"Error"
|
|
)
|
|
|
|
results = engine.run("test")
|
|
|
|
assert results == []
|
|
|
|
|
|
class TestPaperlessUtilities:
|
|
"""Tests for Paperless utility methods."""
|
|
|
|
@pytest.fixture
|
|
def engine(self, mock_requests_get):
|
|
"""Create a Paperless engine."""
|
|
from local_deep_research.web_search_engines.engines.search_engine_paperless import (
|
|
PaperlessSearchEngine,
|
|
)
|
|
|
|
return PaperlessSearchEngine(
|
|
api_url="http://localhost:8000", api_key="token"
|
|
)
|
|
|
|
def test_test_connection_success(self, engine, mock_requests_get):
|
|
"""Test connection test success."""
|
|
mock_response = Mock()
|
|
mock_response.json.return_value = {"status": "ok"}
|
|
mock_response.raise_for_status = Mock()
|
|
mock_requests_get.return_value = mock_response
|
|
|
|
result = engine.test_connection()
|
|
|
|
assert result is True
|
|
|
|
def test_test_connection_failure(self, engine, mock_requests_get):
|
|
"""Test connection test failure."""
|
|
import requests
|
|
|
|
mock_requests_get.side_effect = requests.exceptions.RequestException(
|
|
"Connection failed"
|
|
)
|
|
|
|
result = engine.test_connection()
|
|
|
|
assert result is False
|
|
|
|
def test_get_document_count_success(self, engine, mock_requests_get):
|
|
"""Test document count retrieval."""
|
|
mock_response = Mock()
|
|
mock_response.json.return_value = {"count": 150, "results": []}
|
|
mock_response.raise_for_status = Mock()
|
|
mock_requests_get.return_value = mock_response
|
|
|
|
count = engine.get_document_count()
|
|
|
|
assert count == 150
|
|
|
|
def test_get_document_count_error(self, engine, mock_requests_get):
|
|
"""Test document count on error."""
|
|
import requests
|
|
|
|
mock_requests_get.side_effect = requests.exceptions.RequestException(
|
|
"Error"
|
|
)
|
|
|
|
count = engine.get_document_count()
|
|
|
|
assert count == -1
|
|
|
|
|
|
class TestPaperlessAsync:
|
|
"""Tests for Paperless async methods."""
|
|
|
|
@pytest.fixture
|
|
def engine(self, mock_requests_get):
|
|
"""Create a Paperless engine."""
|
|
from local_deep_research.web_search_engines.engines.search_engine_paperless import (
|
|
PaperlessSearchEngine,
|
|
)
|
|
|
|
return PaperlessSearchEngine(api_url="http://localhost:8000")
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_arun_falls_back_to_sync(self, engine, mock_requests_get):
|
|
"""Test that arun falls back to sync run."""
|
|
mock_response = Mock()
|
|
mock_response.json.return_value = {
|
|
"results": [
|
|
{
|
|
"id": 1,
|
|
"title": "Test",
|
|
"__search_hit__": {"highlights": "text"},
|
|
}
|
|
],
|
|
"count": 1,
|
|
}
|
|
mock_response.raise_for_status = Mock()
|
|
mock_requests_get.return_value = mock_response
|
|
|
|
results = await engine.arun("test")
|
|
|
|
assert len(results) == 1
|