Files
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

637 lines
24 KiB
Python

"""Tests for API research functions."""
from unittest.mock import MagicMock, patch
from local_deep_research.api.research_functions import (
_init_search_system,
quick_summary,
generate_report,
detailed_research,
analyze_documents,
)
class TestInitSearchSystem:
"""Tests for _init_search_system function."""
def test_returns_search_system(self, mock_get_llm, mock_get_search):
"""Test that function returns an AdvancedSearchSystem."""
with patch(
"local_deep_research.api.research_functions.AdvancedSearchSystem"
) as mock_class:
mock_system = MagicMock()
mock_class.return_value = mock_system
result = _init_search_system()
assert result == mock_system
mock_class.assert_called_once()
def test_passes_custom_temperature(self, mock_get_llm, mock_get_search):
"""Test that custom temperature is passed to get_llm."""
with (
patch(
"local_deep_research.api.research_functions.AdvancedSearchSystem"
),
patch(
"local_deep_research.api.research_functions.get_llm"
) as mock_llm,
):
_init_search_system(temperature=0.5)
mock_llm.assert_called_once()
assert mock_llm.call_args[1]["temperature"] == 0.5
def test_passes_model_name(self, mock_get_llm, mock_get_search):
"""Test that model_name is passed to get_llm."""
with (
patch(
"local_deep_research.api.research_functions.AdvancedSearchSystem"
),
patch(
"local_deep_research.api.research_functions.get_llm"
) as mock_llm,
):
_init_search_system(model_name="gpt-4")
assert mock_llm.call_args[1]["model_name"] == "gpt-4"
def test_passes_provider(self, mock_get_llm, mock_get_search):
"""Test that provider is passed to get_llm."""
with (
patch(
"local_deep_research.api.research_functions.AdvancedSearchSystem"
),
patch(
"local_deep_research.api.research_functions.get_llm"
) as mock_llm,
):
_init_search_system(provider="openai")
assert mock_llm.call_args[1]["provider"] == "openai"
def test_creates_search_engine(self, mock_get_llm, mock_get_search):
"""Test that search engine is created when search_tool specified."""
with (
patch(
"local_deep_research.api.research_functions.AdvancedSearchSystem"
),
patch(
"local_deep_research.api.research_functions.get_search"
) as mock_search,
):
_init_search_system(search_tool="wikipedia")
mock_search.assert_called_once()
assert mock_search.call_args[0][0] == "wikipedia"
def test_sets_iterations(self, mock_get_llm, mock_get_search):
"""Test that max_iterations is set on system."""
with patch(
"local_deep_research.api.research_functions.AdvancedSearchSystem"
) as mock_class:
mock_system = MagicMock()
mock_class.return_value = mock_system
_init_search_system(iterations=5)
assert mock_system.max_iterations == 5
def test_sets_questions_per_iteration(self, mock_get_llm, mock_get_search):
"""Test that questions_per_iteration is set on system."""
with patch(
"local_deep_research.api.research_functions.AdvancedSearchSystem"
) as mock_class:
mock_system = MagicMock()
mock_class.return_value = mock_system
_init_search_system(questions_per_iteration=10)
assert mock_system.questions_per_iteration == 10
def test_sets_progress_callback(self, mock_get_llm, mock_get_search):
"""Test that progress callback is set."""
callback = MagicMock()
with patch(
"local_deep_research.api.research_functions.AdvancedSearchSystem"
) as mock_class:
mock_system = MagicMock()
mock_class.return_value = mock_system
_init_search_system(progress_callback=callback)
mock_system.set_progress_callback.assert_called_once_with(callback)
def test_registers_retrievers(self, mock_get_llm, mock_get_search):
"""Test that custom retrievers are registered."""
retriever = MagicMock()
with (
patch(
"local_deep_research.api.research_functions.AdvancedSearchSystem"
),
patch(
"local_deep_research.web_search_engines.retriever_registry.retriever_registry"
) as mock_registry,
):
_init_search_system(retrievers={"custom": retriever})
mock_registry.register_multiple.assert_called_once_with(
{"custom": retriever}
)
def test_registers_llms(self, mock_get_llm, mock_get_search):
"""Test that custom LLMs are registered."""
custom_llm = MagicMock()
with (
patch(
"local_deep_research.api.research_functions.AdvancedSearchSystem"
),
patch("local_deep_research.llm.register_llm") as mock_register,
):
_init_search_system(llms={"custom_llm": custom_llm})
mock_register.assert_called_once_with("custom_llm", custom_llm)
def test_uses_settings_snapshot(
self, mock_get_llm, mock_get_search, sample_settings_snapshot
):
"""Test that settings_snapshot is passed through."""
with patch(
"local_deep_research.api.research_functions.AdvancedSearchSystem"
) as mock_class:
_init_search_system(settings_snapshot=sample_settings_snapshot)
call_kwargs = mock_class.call_args[1]
assert call_kwargs["settings_snapshot"] == sample_settings_snapshot
class TestQuickSummary:
"""Tests for quick_summary function."""
def test_returns_dict(self, mock_get_llm, mock_advanced_search_system):
"""Test that function returns a dictionary."""
with patch(
"local_deep_research.api.research_functions.create_settings_snapshot",
return_value={},
):
result = quick_summary("test query")
assert isinstance(result, dict)
def test_returns_summary_key(
self, mock_get_llm, mock_advanced_search_system
):
"""Test that result contains 'summary' key."""
with patch(
"local_deep_research.api.research_functions.create_settings_snapshot",
return_value={},
):
result = quick_summary("test query")
assert "summary" in result
def test_returns_findings_key(
self, mock_get_llm, mock_advanced_search_system
):
"""Test that result contains 'findings' key."""
with patch(
"local_deep_research.api.research_functions.create_settings_snapshot",
return_value={},
):
result = quick_summary("test query")
assert "findings" in result
def test_returns_iterations_key(
self, mock_get_llm, mock_advanced_search_system
):
"""Test that result contains 'iterations' key."""
with patch(
"local_deep_research.api.research_functions.create_settings_snapshot",
return_value={},
):
result = quick_summary("test query")
assert "iterations" in result
def test_returns_sources_key(
self, mock_get_llm, mock_advanced_search_system
):
"""Test that result contains 'sources' key."""
with patch(
"local_deep_research.api.research_functions.create_settings_snapshot",
return_value={},
):
result = quick_summary("test query")
assert "sources" in result
def test_generates_research_id_if_none(
self, mock_get_llm, mock_advanced_search_system
):
"""Test that research_id is generated if not provided."""
with (
patch(
"local_deep_research.api.research_functions.create_settings_snapshot",
return_value={},
),
patch(
"local_deep_research.api.research_functions.set_search_context"
) as mock_set_context,
):
quick_summary("test query")
call_args = mock_set_context.call_args[0][0]
assert "research_id" in call_args
# Should be a valid UUID
assert len(call_args["research_id"]) == 36
def test_uses_provided_research_id(
self, mock_get_llm, mock_advanced_search_system
):
"""Test that provided research_id is used."""
with (
patch(
"local_deep_research.api.research_functions.create_settings_snapshot",
return_value={},
),
patch(
"local_deep_research.api.research_functions.set_search_context"
) as mock_set_context,
):
quick_summary("test query", research_id="custom-id")
call_args = mock_set_context.call_args[0][0]
assert call_args["research_id"] == "custom-id"
def test_registers_custom_retrievers(
self, mock_get_llm, mock_advanced_search_system
):
"""Test that custom retrievers are registered."""
retriever = MagicMock()
with (
patch(
"local_deep_research.api.research_functions.create_settings_snapshot",
return_value={},
),
patch(
"local_deep_research.web_search_engines.retriever_registry.retriever_registry"
) as mock_registry,
):
quick_summary("test query", retrievers={"custom": retriever})
mock_registry.register_multiple.assert_called()
def test_calls_analyze_topic(
self, mock_get_llm, mock_advanced_search_system
):
"""Test that analyze_topic is called with query."""
with patch(
"local_deep_research.api.research_functions.create_settings_snapshot",
return_value={},
):
quick_summary("test query")
mock_advanced_search_system.analyze_topic.assert_called_once_with(
"test query"
)
def test_handles_empty_results(self, mock_get_llm):
"""Test handling when analyze_topic returns empty results."""
mock_system = MagicMock()
mock_system.analyze_topic.return_value = {}
with (
patch(
"local_deep_research.api.research_functions.AdvancedSearchSystem",
return_value=mock_system,
),
patch(
"local_deep_research.api.research_functions.create_settings_snapshot",
return_value={},
),
):
result = quick_summary("test query")
assert (
result["summary"] == "Unable to generate summary for the query."
)
def test_creates_settings_snapshot_when_not_provided(
self, mock_get_llm, mock_advanced_search_system
):
"""Test that settings snapshot is created if not provided."""
with patch(
"local_deep_research.api.research_functions.create_settings_snapshot",
return_value={},
) as mock_create:
quick_summary("test query", provider="openai", temperature=0.5)
mock_create.assert_called_once()
def test_uses_provided_settings_snapshot(
self, mock_get_llm, mock_advanced_search_system
):
"""Test that provided settings_snapshot is used."""
# Create a valid settings snapshot with required keys
custom_snapshot = {
# "library" is always available without extra engine config
# (searxng needs an instance URL before the factory accepts it).
"search.tool": {"value": "library"},
"custom": "settings",
}
with patch(
"local_deep_research.api.research_functions.create_settings_snapshot",
return_value=custom_snapshot,
) as mock_create:
quick_summary("test query", settings_snapshot=custom_snapshot)
# Should not call create_settings_snapshot when snapshot is provided
mock_create.assert_not_called()
class TestGenerateReport:
"""Tests for generate_report function."""
def test_returns_dict(
self, mock_get_llm, mock_advanced_search_system, mock_report_generator
):
"""Test that function returns a dictionary."""
with (
patch(
"local_deep_research.api.research_functions.IntegratedReportGenerator",
return_value=mock_report_generator,
),
patch(
"local_deep_research.api.research_functions.create_settings_snapshot",
return_value={},
),
):
result = generate_report("test query")
assert isinstance(result, dict)
def test_returns_content_key(
self, mock_get_llm, mock_advanced_search_system, mock_report_generator
):
"""Test that result contains 'content' key."""
with (
patch(
"local_deep_research.api.research_functions.IntegratedReportGenerator",
return_value=mock_report_generator,
),
patch(
"local_deep_research.api.research_functions.create_settings_snapshot",
return_value={},
),
):
result = generate_report("test query")
assert "content" in result
def test_calls_analyze_topic(
self, mock_get_llm, mock_advanced_search_system, mock_report_generator
):
"""Test that analyze_topic is called."""
with (
patch(
"local_deep_research.api.research_functions.IntegratedReportGenerator",
return_value=mock_report_generator,
),
patch(
"local_deep_research.api.research_functions.create_settings_snapshot",
return_value={},
),
):
generate_report("test query")
mock_advanced_search_system.analyze_topic.assert_called_once_with(
"test query"
)
def test_calls_report_generator(
self, mock_get_llm, mock_advanced_search_system, mock_report_generator
):
"""Test that report generator is called."""
with (
patch(
"local_deep_research.api.research_functions.IntegratedReportGenerator",
return_value=mock_report_generator,
),
patch(
"local_deep_research.api.research_functions.create_settings_snapshot",
return_value={},
),
):
generate_report("test query")
mock_report_generator.generate_report.assert_called_once()
def test_saves_to_file(
self,
mock_get_llm,
mock_advanced_search_system,
mock_report_generator,
tmp_path,
):
"""Test saving report to file."""
output_file = str(tmp_path / "report.md")
with (
patch(
"local_deep_research.api.research_functions.IntegratedReportGenerator",
return_value=mock_report_generator,
),
patch(
"local_deep_research.api.research_functions.create_settings_snapshot",
return_value={},
),
patch(
"local_deep_research.security.file_write_verifier.write_file_verified"
) as mock_write,
):
result = generate_report("test query", output_file=output_file)
mock_write.assert_called_once()
assert result["file_path"] == output_file
def test_sets_progress_callback(
self, mock_get_llm, mock_advanced_search_system, mock_report_generator
):
"""Test that progress callback is set."""
callback = MagicMock()
with (
patch(
"local_deep_research.api.research_functions.IntegratedReportGenerator",
return_value=mock_report_generator,
),
patch(
"local_deep_research.api.research_functions.create_settings_snapshot",
return_value={},
),
):
generate_report("test query", progress_callback=callback)
mock_advanced_search_system.set_progress_callback.assert_called_once_with(
callback
)
def test_searches_per_section(
self, mock_get_llm, mock_advanced_search_system, mock_report_generator
):
"""Test that searches_per_section is passed to generator."""
with (
patch(
"local_deep_research.api.research_functions.IntegratedReportGenerator"
) as mock_gen_class,
patch(
"local_deep_research.api.research_functions.create_settings_snapshot",
return_value={},
),
):
mock_gen_class.return_value = mock_report_generator
generate_report("test query", searches_per_section=5)
call_kwargs = mock_gen_class.call_args[1]
assert call_kwargs["searches_per_section"] == 5
class TestDetailedResearch:
"""Tests for detailed_research function."""
def test_returns_dict(self, mock_get_llm, mock_advanced_search_system):
"""Test that function returns a dictionary."""
result = detailed_research("test query")
assert isinstance(result, dict)
def test_returns_query(self, mock_get_llm, mock_advanced_search_system):
"""Test that result contains 'query' key."""
result = detailed_research("test query")
assert result["query"] == "test query"
def test_returns_research_id(
self, mock_get_llm, mock_advanced_search_system
):
"""Test that result contains 'research_id' key."""
result = detailed_research("test query")
assert "research_id" in result
def test_uses_provided_research_id(
self, mock_get_llm, mock_advanced_search_system
):
"""Test that provided research_id is used."""
result = detailed_research("test query", research_id="custom-id")
assert result["research_id"] == "custom-id"
def test_generates_research_id(
self, mock_get_llm, mock_advanced_search_system
):
"""Test that research_id is generated if not provided."""
result = detailed_research("test query")
# Should be a valid UUID
assert len(result["research_id"]) == 36
def test_returns_summary(self, mock_get_llm, mock_advanced_search_system):
"""Test that result contains 'summary' key."""
result = detailed_research("test query")
assert "summary" in result
def test_returns_findings(self, mock_get_llm, mock_advanced_search_system):
"""Test that result contains 'findings' key."""
result = detailed_research("test query")
assert "findings" in result
def test_returns_metadata(self, mock_get_llm, mock_advanced_search_system):
"""Test that result contains 'metadata' key."""
result = detailed_research("test query")
assert "metadata" in result
assert "timestamp" in result["metadata"]
def test_calls_analyze_topic(
self, mock_get_llm, mock_advanced_search_system
):
"""Test that analyze_topic is called with query."""
detailed_research("test query")
mock_advanced_search_system.analyze_topic.assert_called_once_with(
"test query"
)
class TestAnalyzeDocuments:
"""Tests for analyze_documents function."""
def test_returns_dict(self, mock_get_llm, mock_get_search):
"""Test that function returns a dictionary."""
result = analyze_documents("test query", "test_collection")
assert isinstance(result, dict)
def test_returns_summary(self, mock_get_llm, mock_get_search):
"""Test that result contains 'summary' key."""
result = analyze_documents("test query", "test_collection")
assert "summary" in result
def test_returns_documents(self, mock_get_llm, mock_get_search):
"""Test that result contains 'documents' key."""
result = analyze_documents("test query", "test_collection")
assert "documents" in result
def test_collection_not_found(self, mock_get_llm):
"""Test handling when collection is not found."""
with patch(
"local_deep_research.api.research_functions.get_search",
return_value=None,
):
result = analyze_documents("test query", "nonexistent_collection")
assert "Error" in result["summary"]
assert result["documents"] == []
def test_no_results_found(self, mock_get_llm):
"""Test handling when no documents are found."""
mock_search = MagicMock()
mock_search.run.return_value = []
with patch(
"local_deep_research.api.research_functions.get_search",
return_value=mock_search,
):
result = analyze_documents("test query", "test_collection")
assert "No documents found" in result["summary"]
assert result["documents"] == []
def test_sets_max_results(self, mock_get_llm):
"""Test that max_results is set on search engine."""
mock_search = MagicMock()
mock_search.run.return_value = []
with patch(
"local_deep_research.api.research_functions.get_search",
return_value=mock_search,
):
analyze_documents("test query", "test_collection", max_results=50)
assert mock_search.max_results == 50
def test_uses_custom_temperature(self, mock_get_search):
"""Test that custom temperature is passed to get_llm."""
with patch(
"local_deep_research.api.research_functions.get_llm"
) as mock_llm:
# Create a mock LLM that returns a proper response
mock_llm_instance = MagicMock()
mock_llm_instance.invoke.return_value = MagicMock(
content="Summary text"
)
mock_llm.return_value = mock_llm_instance
analyze_documents("test query", "test_collection", temperature=0.3)
mock_llm.assert_called_once()
assert mock_llm.call_args[1]["temperature"] == 0.3
def test_saves_to_file(self, mock_get_llm, tmp_path):
"""Test saving analysis to file."""
output_file = str(tmp_path / "analysis.md")
mock_search = MagicMock()
mock_search.run.return_value = [
{
"title": "Doc 1",
"link": "https://example.com",
"content": "Content",
}
]
with (
patch(
"local_deep_research.api.research_functions.get_search",
return_value=mock_search,
),
patch(
"local_deep_research.security.file_write_verifier.write_file_verified"
) as mock_write,
):
result = analyze_documents(
"test query", "test_collection", output_file=output_file
)
mock_write.assert_called_once()
assert result["file_path"] == output_file
def test_returns_collection_name(self, mock_get_llm, mock_get_search):
"""Test that result includes collection name."""
result = analyze_documents("test query", "my_collection")
assert result["collection"] == "my_collection"
def test_returns_document_count(self, mock_get_llm, mock_get_search):
"""Test that result includes document count."""
result = analyze_documents("test query", "test_collection")
assert "document_count" in result