7a0da7932b
Backwards Compatibility / Verify Encryption Constants (push) Waiting to run
Backwards Compatibility / PyPI Version Compatibility (push) Waiting to run
Backwards Compatibility / Database Migration Tests (push) Waiting to run
CodeQL Advanced / Analyze (javascript-typescript) (push) Waiting to run
CodeQL Advanced / Analyze (python) (push) Waiting to run
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-form] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-metrics] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-workflow] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-core] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-pages] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [history-news] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [library] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [link-analytics] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [mobile] (push) Blocked by required conditions
Docker Tests (Consolidated) / detect-changes (push) Waiting to run
Docker Tests (Consolidated) / Build Test Image (push) Waiting to run
Docker Tests (Consolidated) / All Pytest Tests + Coverage (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [accessibility] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [api-crud] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-login] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-pages] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-register] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-core] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-lifecycle] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [error-benchmark] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) (push) Blocked by required conditions
Docker Tests (Consolidated) / Accessibility Tests (push) Blocked by required conditions
Docker Tests (Consolidated) / LLM Unit Tests (push) Blocked by required conditions
Docker Tests (Consolidated) / LLM Example Tests (push) Blocked by required conditions
Docker Tests (Consolidated) / Production Image Smoke Test (push) Blocked by required conditions
Docker Tests (Consolidated) / Infrastructure Tests (push) Blocked by required conditions
OSSF Scorecard / OSSF Security Scorecard Analysis (push) Waiting to run
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
501 lines
18 KiB
Python
501 lines
18 KiB
Python
"""
|
|
Tests for Research API endpoints.
|
|
|
|
Phase 32: API Endpoint Tests - Tests for research-related API functionality.
|
|
Tests research_functions.py API methods including quick_summary and deep_research.
|
|
"""
|
|
|
|
from unittest.mock import MagicMock, patch
|
|
import pytest
|
|
|
|
|
|
class TestInitSearchSystem:
|
|
"""Tests for _init_search_system function."""
|
|
|
|
@patch("local_deep_research.api.research_functions.get_llm")
|
|
@patch("local_deep_research.api.research_functions.AdvancedSearchSystem")
|
|
def test_init_search_system_basic(self, mock_system_class, mock_get_llm):
|
|
"""Test basic initialization of search system."""
|
|
from local_deep_research.api.research_functions import (
|
|
_init_search_system,
|
|
)
|
|
|
|
mock_llm = MagicMock()
|
|
mock_get_llm.return_value = mock_llm
|
|
mock_system = MagicMock()
|
|
mock_system_class.return_value = mock_system
|
|
|
|
result = _init_search_system()
|
|
|
|
mock_get_llm.assert_called_once()
|
|
mock_system_class.assert_called_once()
|
|
assert result == mock_system
|
|
|
|
@patch("local_deep_research.api.research_functions.get_llm")
|
|
@patch("local_deep_research.api.research_functions.AdvancedSearchSystem")
|
|
def test_init_search_system_with_model_name(
|
|
self, mock_system_class, mock_get_llm
|
|
):
|
|
"""Test initialization with custom model name."""
|
|
from local_deep_research.api.research_functions import (
|
|
_init_search_system,
|
|
)
|
|
|
|
mock_llm = MagicMock()
|
|
mock_get_llm.return_value = mock_llm
|
|
mock_system = MagicMock()
|
|
mock_system_class.return_value = mock_system
|
|
|
|
_init_search_system(model_name="gpt-4")
|
|
|
|
call_kwargs = mock_get_llm.call_args[1]
|
|
assert call_kwargs.get("model_name") == "gpt-4"
|
|
|
|
@patch("local_deep_research.api.research_functions.get_llm")
|
|
@patch("local_deep_research.api.research_functions.AdvancedSearchSystem")
|
|
def test_init_search_system_with_temperature(
|
|
self, mock_system_class, mock_get_llm
|
|
):
|
|
"""Test initialization with custom temperature."""
|
|
from local_deep_research.api.research_functions import (
|
|
_init_search_system,
|
|
)
|
|
|
|
mock_llm = MagicMock()
|
|
mock_get_llm.return_value = mock_llm
|
|
mock_system = MagicMock()
|
|
mock_system_class.return_value = mock_system
|
|
|
|
_init_search_system(temperature=0.5)
|
|
|
|
call_kwargs = mock_get_llm.call_args[1]
|
|
assert call_kwargs.get("temperature") == 0.5
|
|
|
|
@patch("local_deep_research.api.research_functions.get_llm")
|
|
@patch("local_deep_research.api.research_functions.AdvancedSearchSystem")
|
|
def test_init_search_system_with_provider(
|
|
self, mock_system_class, mock_get_llm
|
|
):
|
|
"""Test initialization with custom provider."""
|
|
from local_deep_research.api.research_functions import (
|
|
_init_search_system,
|
|
)
|
|
|
|
mock_llm = MagicMock()
|
|
mock_get_llm.return_value = mock_llm
|
|
mock_system = MagicMock()
|
|
mock_system_class.return_value = mock_system
|
|
|
|
_init_search_system(provider="anthropic")
|
|
|
|
call_kwargs = mock_get_llm.call_args[1]
|
|
assert call_kwargs.get("provider") == "anthropic"
|
|
|
|
@patch("local_deep_research.api.research_functions.get_llm")
|
|
@patch("local_deep_research.api.research_functions.AdvancedSearchSystem")
|
|
def test_init_search_system_with_iterations(
|
|
self, mock_system_class, mock_get_llm
|
|
):
|
|
"""Test initialization with custom iterations."""
|
|
from local_deep_research.api.research_functions import (
|
|
_init_search_system,
|
|
)
|
|
|
|
mock_llm = MagicMock()
|
|
mock_get_llm.return_value = mock_llm
|
|
mock_system = MagicMock()
|
|
mock_system_class.return_value = mock_system
|
|
|
|
result = _init_search_system(iterations=5)
|
|
|
|
assert result.max_iterations == 5
|
|
|
|
@patch("local_deep_research.api.research_functions.get_llm")
|
|
@patch("local_deep_research.api.research_functions.AdvancedSearchSystem")
|
|
def test_init_search_system_with_questions_per_iteration(
|
|
self, mock_system_class, mock_get_llm
|
|
):
|
|
"""Test initialization with custom questions per iteration."""
|
|
from local_deep_research.api.research_functions import (
|
|
_init_search_system,
|
|
)
|
|
|
|
mock_llm = MagicMock()
|
|
mock_get_llm.return_value = mock_llm
|
|
mock_system = MagicMock()
|
|
mock_system_class.return_value = mock_system
|
|
|
|
result = _init_search_system(questions_per_iteration=3)
|
|
|
|
assert result.questions_per_iteration == 3
|
|
|
|
@patch("local_deep_research.api.research_functions.get_llm")
|
|
@patch("local_deep_research.api.research_functions.AdvancedSearchSystem")
|
|
def test_init_search_system_with_progress_callback(
|
|
self, mock_system_class, mock_get_llm
|
|
):
|
|
"""Test initialization with progress callback."""
|
|
from local_deep_research.api.research_functions import (
|
|
_init_search_system,
|
|
)
|
|
|
|
mock_llm = MagicMock()
|
|
mock_get_llm.return_value = mock_llm
|
|
mock_system = MagicMock()
|
|
mock_system_class.return_value = mock_system
|
|
|
|
callback = MagicMock()
|
|
_init_search_system(progress_callback=callback)
|
|
|
|
mock_system.set_progress_callback.assert_called_once_with(callback)
|
|
|
|
@patch("local_deep_research.api.research_functions.get_llm")
|
|
@patch("local_deep_research.api.research_functions.AdvancedSearchSystem")
|
|
@patch("local_deep_research.api.research_functions.get_search")
|
|
def test_init_search_system_with_search_tool(
|
|
self, mock_get_search, mock_system_class, mock_get_llm
|
|
):
|
|
"""Test initialization with custom search tool."""
|
|
from local_deep_research.api.research_functions import (
|
|
_init_search_system,
|
|
)
|
|
|
|
mock_llm = MagicMock()
|
|
mock_get_llm.return_value = mock_llm
|
|
mock_system = MagicMock()
|
|
mock_system_class.return_value = mock_system
|
|
mock_search = MagicMock()
|
|
mock_get_search.return_value = mock_search
|
|
|
|
_init_search_system(search_tool="arxiv")
|
|
|
|
mock_get_search.assert_called_once()
|
|
call_args = mock_get_search.call_args[0]
|
|
assert call_args[0] == "arxiv"
|
|
|
|
@patch("local_deep_research.api.research_functions.get_llm")
|
|
@patch("local_deep_research.api.research_functions.AdvancedSearchSystem")
|
|
def test_init_search_system_with_search_strategy(
|
|
self, mock_system_class, mock_get_llm
|
|
):
|
|
"""Test initialization with custom search strategy."""
|
|
from local_deep_research.api.research_functions import (
|
|
_init_search_system,
|
|
)
|
|
|
|
mock_llm = MagicMock()
|
|
mock_get_llm.return_value = mock_llm
|
|
mock_system = MagicMock()
|
|
mock_system_class.return_value = mock_system
|
|
|
|
_init_search_system(search_strategy="modular")
|
|
|
|
call_kwargs = mock_system_class.call_args[1]
|
|
assert call_kwargs.get("strategy_name") == "modular"
|
|
|
|
@patch("local_deep_research.api.research_functions.get_llm")
|
|
@patch("local_deep_research.api.research_functions.AdvancedSearchSystem")
|
|
@patch(
|
|
"local_deep_research.web_search_engines.retriever_registry.retriever_registry"
|
|
)
|
|
def test_init_search_system_with_retrievers(
|
|
self, mock_registry, mock_system_class, mock_get_llm
|
|
):
|
|
"""Test initialization with custom retrievers."""
|
|
from local_deep_research.api.research_functions import (
|
|
_init_search_system,
|
|
)
|
|
|
|
mock_llm = MagicMock()
|
|
mock_get_llm.return_value = mock_llm
|
|
mock_system = MagicMock()
|
|
mock_system_class.return_value = mock_system
|
|
|
|
retrievers = {"custom": MagicMock()}
|
|
_init_search_system(retrievers=retrievers)
|
|
|
|
mock_registry.register_multiple.assert_called_once_with(retrievers)
|
|
|
|
@patch("local_deep_research.api.research_functions.get_llm")
|
|
@patch("local_deep_research.api.research_functions.AdvancedSearchSystem")
|
|
@patch("local_deep_research.llm.register_llm")
|
|
def test_init_search_system_with_llms(
|
|
self, mock_register_llm, mock_system_class, mock_get_llm
|
|
):
|
|
"""Test initialization with custom LLMs."""
|
|
from local_deep_research.api.research_functions import (
|
|
_init_search_system,
|
|
)
|
|
|
|
mock_llm = MagicMock()
|
|
mock_get_llm.return_value = mock_llm
|
|
mock_system = MagicMock()
|
|
mock_system_class.return_value = mock_system
|
|
|
|
custom_llm = MagicMock()
|
|
llms = {"custom_llm": custom_llm}
|
|
_init_search_system(llms=llms)
|
|
|
|
mock_register_llm.assert_called_once_with("custom_llm", custom_llm)
|
|
|
|
|
|
class TestQuickSummary:
|
|
"""Tests for quick_summary function."""
|
|
|
|
@patch("local_deep_research.api.research_functions._init_search_system")
|
|
def test_quick_summary_basic(self, mock_init_system):
|
|
"""Test basic quick summary."""
|
|
from local_deep_research.api.research_functions import quick_summary
|
|
|
|
mock_system = MagicMock()
|
|
mock_system.analyze_topic.return_value = {
|
|
"current_knowledge": "Summary content",
|
|
"iterations": 1,
|
|
"questions_by_iteration": {},
|
|
"all_links_of_system": [],
|
|
}
|
|
mock_init_system.return_value = mock_system
|
|
|
|
result = quick_summary("What is AI?")
|
|
|
|
assert "summary" in result or "current_knowledge" in result
|
|
mock_system.analyze_topic.assert_called_once()
|
|
|
|
@patch("local_deep_research.api.research_functions._init_search_system")
|
|
def test_quick_summary_with_provider(self, mock_init_system):
|
|
"""Test quick summary with custom provider in settings_snapshot."""
|
|
from local_deep_research.api.research_functions import quick_summary
|
|
|
|
mock_system = MagicMock()
|
|
mock_system.analyze_topic.return_value = {
|
|
"current_knowledge": "Summary",
|
|
"iterations": 1,
|
|
"questions_by_iteration": {},
|
|
"all_links_of_system": [],
|
|
}
|
|
mock_init_system.return_value = mock_system
|
|
|
|
quick_summary("Query", provider="anthropic")
|
|
|
|
# Provider is passed via settings_snapshot, not as a direct kwarg
|
|
call_kwargs = mock_init_system.call_args[1]
|
|
# Check that settings_snapshot was created and passed
|
|
assert "settings_snapshot" in call_kwargs
|
|
|
|
@patch("local_deep_research.api.research_functions._init_search_system")
|
|
def test_quick_summary_with_temperature(self, mock_init_system):
|
|
"""Test quick summary with custom temperature in settings_snapshot."""
|
|
from local_deep_research.api.research_functions import quick_summary
|
|
|
|
mock_system = MagicMock()
|
|
mock_system.analyze_topic.return_value = {
|
|
"current_knowledge": "Summary",
|
|
"iterations": 1,
|
|
"questions_by_iteration": {},
|
|
"all_links_of_system": [],
|
|
}
|
|
mock_init_system.return_value = mock_system
|
|
|
|
quick_summary("Query", temperature=0.3)
|
|
|
|
# Temperature is passed via settings_snapshot, not as a direct kwarg
|
|
call_kwargs = mock_init_system.call_args[1]
|
|
# Check that settings_snapshot was created and passed
|
|
assert "settings_snapshot" in call_kwargs
|
|
|
|
@patch("local_deep_research.api.research_functions._init_search_system")
|
|
@patch(
|
|
"local_deep_research.web_search_engines.retriever_registry.retriever_registry"
|
|
)
|
|
def test_quick_summary_with_retrievers(
|
|
self, mock_registry, mock_init_system
|
|
):
|
|
"""Test quick summary registers retrievers with registry."""
|
|
from local_deep_research.api.research_functions import quick_summary
|
|
|
|
mock_system = MagicMock()
|
|
mock_system.analyze_topic.return_value = {
|
|
"current_knowledge": "Summary",
|
|
"iterations": 1,
|
|
"questions_by_iteration": {},
|
|
"all_links_of_system": [],
|
|
}
|
|
mock_init_system.return_value = mock_system
|
|
|
|
retrievers = {"custom": MagicMock()}
|
|
quick_summary("Query", retrievers=retrievers)
|
|
|
|
# Retrievers are registered with the registry, not passed to _init_search_system
|
|
mock_registry.register_multiple.assert_called_once_with(retrievers)
|
|
|
|
@patch("local_deep_research.api.research_functions._init_search_system")
|
|
def test_quick_summary_with_research_id(self, mock_init_system):
|
|
"""Test quick summary with research ID tracking."""
|
|
from local_deep_research.api.research_functions import quick_summary
|
|
|
|
mock_system = MagicMock()
|
|
mock_system.analyze_topic.return_value = {
|
|
"current_knowledge": "Summary",
|
|
"iterations": 1,
|
|
"questions_by_iteration": {},
|
|
"all_links_of_system": [],
|
|
}
|
|
mock_init_system.return_value = mock_system
|
|
|
|
quick_summary("Query", research_id="test-123")
|
|
|
|
call_kwargs = mock_init_system.call_args[1]
|
|
assert call_kwargs.get("research_id") == "test-123"
|
|
|
|
@patch("local_deep_research.api.research_functions._init_search_system")
|
|
def test_quick_summary_search_original_query_default(
|
|
self, mock_init_system
|
|
):
|
|
"""Test quick summary search_original_query default is True."""
|
|
from local_deep_research.api.research_functions import quick_summary
|
|
|
|
mock_system = MagicMock()
|
|
mock_system.analyze_topic.return_value = {
|
|
"current_knowledge": "Summary",
|
|
"iterations": 1,
|
|
"questions_by_iteration": {},
|
|
"all_links_of_system": [],
|
|
}
|
|
mock_init_system.return_value = mock_system
|
|
|
|
quick_summary("Query")
|
|
|
|
call_kwargs = mock_init_system.call_args[1]
|
|
assert call_kwargs.get("search_original_query") is True
|
|
|
|
@patch("local_deep_research.api.research_functions._init_search_system")
|
|
def test_quick_summary_search_original_query_false(self, mock_init_system):
|
|
"""Test quick summary with search_original_query disabled."""
|
|
from local_deep_research.api.research_functions import quick_summary
|
|
|
|
mock_system = MagicMock()
|
|
mock_system.analyze_topic.return_value = {
|
|
"current_knowledge": "Summary",
|
|
"iterations": 1,
|
|
"questions_by_iteration": {},
|
|
"all_links_of_system": [],
|
|
}
|
|
mock_init_system.return_value = mock_system
|
|
|
|
quick_summary("Query", search_original_query=False)
|
|
|
|
call_kwargs = mock_init_system.call_args[1]
|
|
assert call_kwargs.get("search_original_query") is False
|
|
|
|
|
|
class TestResearchAPIValidation:
|
|
"""Tests for API input validation."""
|
|
|
|
@patch("local_deep_research.api.research_functions._init_search_system")
|
|
def test_quick_summary_empty_query(self, mock_init_system):
|
|
"""Test quick summary with empty query."""
|
|
from local_deep_research.api.research_functions import quick_summary
|
|
|
|
mock_system = MagicMock()
|
|
mock_system.analyze_topic.return_value = {
|
|
"current_knowledge": "",
|
|
"iterations": 0,
|
|
"questions_by_iteration": {},
|
|
"all_links_of_system": [],
|
|
}
|
|
mock_init_system.return_value = mock_system
|
|
|
|
# Should not raise, but may return empty results
|
|
result = quick_summary("")
|
|
assert result is not None
|
|
|
|
@patch("local_deep_research.api.research_functions._init_search_system")
|
|
def test_quick_summary_special_characters(self, mock_init_system):
|
|
"""Test quick summary with special characters."""
|
|
from local_deep_research.api.research_functions import quick_summary
|
|
|
|
mock_system = MagicMock()
|
|
mock_system.analyze_topic.return_value = {
|
|
"current_knowledge": "Result",
|
|
"iterations": 1,
|
|
"questions_by_iteration": {},
|
|
"all_links_of_system": [],
|
|
}
|
|
mock_init_system.return_value = mock_system
|
|
|
|
result = quick_summary("What about <script>alert('test')</script>?")
|
|
assert result is not None
|
|
|
|
@patch("local_deep_research.api.research_functions._init_search_system")
|
|
def test_quick_summary_unicode_query(self, mock_init_system):
|
|
"""Test quick summary with unicode characters."""
|
|
from local_deep_research.api.research_functions import quick_summary
|
|
|
|
mock_system = MagicMock()
|
|
mock_system.analyze_topic.return_value = {
|
|
"current_knowledge": "Result",
|
|
"iterations": 1,
|
|
"questions_by_iteration": {},
|
|
"all_links_of_system": [],
|
|
}
|
|
mock_init_system.return_value = mock_system
|
|
|
|
result = quick_summary("什么是人工智能?")
|
|
assert result is not None
|
|
|
|
|
|
class TestResearchAPIErrorHandling:
|
|
"""Tests for API error handling."""
|
|
|
|
@patch("local_deep_research.api.research_functions._init_search_system")
|
|
def test_quick_summary_system_error(self, mock_init_system):
|
|
"""Test quick summary handles system errors."""
|
|
from local_deep_research.api.research_functions import quick_summary
|
|
|
|
mock_init_system.side_effect = Exception("System error")
|
|
|
|
with pytest.raises(Exception):
|
|
quick_summary("Query")
|
|
|
|
@patch("local_deep_research.api.research_functions._init_search_system")
|
|
def test_quick_summary_analyze_error(self, mock_init_system):
|
|
"""Test quick summary handles analyze_topic errors."""
|
|
from local_deep_research.api.research_functions import quick_summary
|
|
|
|
mock_system = MagicMock()
|
|
mock_system.analyze_topic.side_effect = Exception("Analysis error")
|
|
mock_init_system.return_value = mock_system
|
|
|
|
with pytest.raises(Exception):
|
|
quick_summary("Query")
|
|
|
|
|
|
class TestResearchAPIIntegration:
|
|
"""Integration tests for research API."""
|
|
|
|
@patch("local_deep_research.api.research_functions._init_search_system")
|
|
def test_full_research_workflow(self, mock_init_system):
|
|
"""Test complete research workflow."""
|
|
from local_deep_research.api.research_functions import quick_summary
|
|
|
|
mock_system = MagicMock()
|
|
mock_system.analyze_topic.return_value = {
|
|
"current_knowledge": "Comprehensive research results about AI",
|
|
"iterations": 3,
|
|
"questions_by_iteration": {1: ["Q1", "Q2"], 2: ["Q3"], 3: ["Q4"]},
|
|
"all_links_of_system": [
|
|
{"url": "http://source1.com", "title": "Source 1"},
|
|
{"url": "http://source2.com", "title": "Source 2"},
|
|
],
|
|
}
|
|
mock_init_system.return_value = mock_system
|
|
|
|
result = quick_summary(
|
|
"What is artificial intelligence?",
|
|
provider="openai",
|
|
temperature=0.7,
|
|
)
|
|
|
|
assert result is not None
|
|
mock_system.analyze_topic.assert_called_once()
|