Files
learningcircuit--local-deep…/tests/api/test_research_api.py
T
wehub-resource-sync 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
chore: import upstream snapshot with attribution
2026-07-13 13:08:55 +08:00

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()