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

372 lines
14 KiB
Python

"""End-to-end tests for API settings in research workflows."""
from unittest.mock import patch, MagicMock
from local_deep_research.api import quick_summary, detailed_research
from local_deep_research.api.settings_utils import create_settings_snapshot
class TestE2EResearchWithSettings:
"""Test end-to-end research workflows with various settings."""
@patch("local_deep_research.api.research_functions.get_llm")
@patch("local_deep_research.api.research_functions.get_search")
def test_quick_summary_full_flow(self, mock_get_search, mock_get_llm):
"""Test quick_summary with full settings propagation.
Patches happen on `api.research_functions` because the names are
imported into that module at import time — patching the original
module (`config.llm_config.get_llm`) would not affect already-bound
references.
"""
# Mock LLM
mock_llm = MagicMock()
mock_llm.invoke.return_value = MagicMock(content="Test summary")
mock_get_llm.return_value = mock_llm
# Mock search engine
mock_search = MagicMock()
mock_search.search.return_value = {
"results": [
{
"title": "Result 1",
"url": "http://example.com/1",
"snippet": "Snippet 1",
},
{
"title": "Result 2",
"url": "http://example.com/2",
"snippet": "Snippet 2",
},
]
}
mock_get_search.return_value = mock_search
# Run quick summary with custom settings
result = quick_summary(
"What is quantum computing?",
provider="anthropic",
api_key="test-key",
temperature=0.5,
max_search_results=10,
settings_override={
"llm.anthropic.model": "claude-3-opus-20240229",
"search.tool": "duckduckgo",
"search.region": "us-en",
},
)
# Verify LLM was configured correctly
mock_get_llm.assert_called()
# Verify search was configured correctly. get_search() takes the
# tool name as the first positional arg; everything else is in
# settings_snapshot rather than dedicated kwargs.
mock_get_search.assert_called()
search_args, search_kwargs = mock_get_search.call_args
assert search_args[0] == "duckduckgo"
snapshot = search_kwargs["settings_snapshot"]
assert snapshot["search.region"]["value"] == "us-en"
assert snapshot["search.max_results"]["value"] == 10
# Verify result structure
assert "summary" in result
assert "findings" in result
assert "iterations" in result
@patch("local_deep_research.api.research_functions.get_llm")
@patch("local_deep_research.api.research_functions.get_search")
def test_detailed_research_full_flow(self, mock_get_search, mock_get_llm):
"""Test detailed_research with comprehensive settings.
detailed_research accepts only `settings_snapshot` for configuration
(unlike quick_summary which has provider/api_key shortcuts), so the
test builds a snapshot via create_settings_snapshot and threads it
in directly. Patch targets follow `api.research_functions` because
that is where get_llm / get_search are imported and bound.
"""
# Mock LLM with different responses
mock_llm = MagicMock()
mock_llm.invoke.side_effect = [
MagicMock(content="Initial analysis"),
MagicMock(content="Deeper analysis"),
MagicMock(content="Final synthesis"),
]
mock_get_llm.return_value = mock_llm
mock_search = MagicMock()
mock_get_search.return_value = mock_search
snapshot = create_settings_snapshot(
provider="openai",
overrides={
"search.iterations": 3,
"search.max_results": 20,
"search.engines.arxiv.enabled": True,
"llm.max_tokens": 4000,
},
)
# Run detailed research with custom settings
result = detailed_research(
"Explain the applications of quantum computing in cryptography",
settings_snapshot=snapshot,
)
# The settings_snapshot values should be threaded through to
# get_search.
mock_get_search.assert_called()
passed_snapshot = mock_get_search.call_args[1]["settings_snapshot"]
assert passed_snapshot["search.max_results"]["value"] == 20
assert passed_snapshot["search.iterations"]["value"] == 3
# Verify the structured-report shape is returned (detailed_research
# returns a report dict, not a summary dict).
assert isinstance(result, dict)
def test_settings_isolation_between_calls(self):
"""Test that settings don't leak between API calls."""
with patch(
"local_deep_research.api.research_functions._init_search_system"
) as mock_init:
mock_system = MagicMock()
mock_system.analyze_topic.return_value = {
"current_knowledge": "Summary",
"findings": [],
"iterations": 1,
"questions": {},
"formatted_findings": "Findings",
"all_links_of_system": [],
}
mock_init.return_value = mock_system
# First call with one set of settings
_ = quick_summary("Query 1", provider="openai", temperature=0.9)
# Second call with different settings
_ = quick_summary("Query 2", provider="anthropic", temperature=0.1)
# Verify each call got its own settings
call1_settings = mock_init.call_args_list[0][1]["settings_snapshot"]
call2_settings = mock_init.call_args_list[1][1]["settings_snapshot"]
assert call1_settings["llm.provider"]["value"] == "openai"
assert call1_settings["llm.temperature"]["value"] == 0.9
assert call2_settings["llm.provider"]["value"] == "anthropic"
assert call2_settings["llm.temperature"]["value"] == 0.1
class TestMultiProviderScenarios:
"""Test scenarios with multiple LLM providers."""
def test_multi_model_research(self):
"""Test research using multiple models for comparison."""
models = [
("openai", "gpt-4", 0.3),
("anthropic", "claude-3-opus-20240229", 0.3),
("openai", "gpt-3.5-turbo", 0.5),
]
results = []
with patch(
"local_deep_research.api.research_functions._init_search_system"
) as mock_init:
mock_system = MagicMock()
mock_system.analyze_topic.return_value = {
"current_knowledge": "Model-specific summary",
"findings": [],
"iterations": 1,
"questions": {},
"formatted_findings": "Findings",
"all_links_of_system": [],
}
mock_init.return_value = mock_system
for provider, model, temp in models:
result = quick_summary(
"Compare quantum and classical computing",
provider=provider,
temperature=temp,
settings_override={
f"llm.{provider}.model": model,
},
)
results.append((provider, model, result))
# Verify each call used different settings
assert mock_init.call_count == len(models)
for i, (provider, model, _) in enumerate(models):
call_settings = mock_init.call_args_list[i][1][
"settings_snapshot"
]
assert call_settings["llm.provider"]["value"] == provider
assert call_settings[f"llm.{provider}.model"]["value"] == model
class TestSearchEngineIntegration:
"""Test integration with various search engines through settings."""
@patch("local_deep_research.api.research_functions.get_llm")
@patch("local_deep_research.api.research_functions.get_search")
def test_search_engine_specific_settings(
self, mock_get_search, mock_get_llm
):
"""Test that search engine specific settings are applied.
Each call's positional first argument identifies the chosen tool;
engine-specific settings should appear in `settings_snapshot`.
Patches target `api.research_functions` since that is where the
names are bound.
"""
search_configs = [
{
"engine": "searxng",
"settings": {
"search.tool": "searxng",
"search.engines.searxng.base_url": "https://searx.example.com",
"search.engines.searxng.timeout": 15,
},
},
{
"engine": "duckduckgo",
"settings": {
"search.tool": "duckduckgo",
"search.engines.duckduckgo.region": "uk-en",
"search.engines.duckduckgo.safe_search": False,
},
},
{
"engine": "wikipedia",
"settings": {
"search.tool": "wikipedia",
"search.engines.wikipedia.language": "es",
"search.engines.wikipedia.max_chars": 2000,
},
},
]
mock_get_llm.return_value = MagicMock()
mock_get_search.return_value = MagicMock()
for config in search_configs:
_ = quick_summary(
f"Test with {config['engine']}",
provider="openai",
api_key="test-key",
settings_override=config["settings"],
)
# Verify the chosen search engine appears as the first positional
# argument (the get_search public signature).
last_args, last_kwargs = mock_get_search.call_args
assert last_args[0] == config["engine"]
# Engine-specific overrides must propagate through settings_snapshot.
snapshot = last_kwargs["settings_snapshot"]
for key, value in config["settings"].items():
assert key in snapshot, (
f"missing snapshot key {key} for {config['engine']}"
)
assert snapshot[key]["value"] == value
class TestPerformanceSettings:
"""Test performance-related settings."""
def test_concurrent_research_settings(self):
"""Test settings for concurrent research operations."""
# Settings optimized for concurrent operations
concurrent_settings = create_settings_snapshot(
overrides={
"research.concurrent.enabled": True,
"research.concurrent.max_workers": 5,
"llm.request_timeout": 30,
"llm.max_retries": 2,
"search.request_timeout": 10,
"search.concurrent_searches": 3,
}
)
# Verify concurrent settings
assert (
concurrent_settings["research.concurrent.enabled"]["value"] is True
)
assert (
concurrent_settings["research.concurrent.max_workers"]["value"] == 5
)
assert concurrent_settings["llm.request_timeout"]["value"] == 30
def test_rate_limiting_settings(self):
"""Test rate limiting settings for API calls."""
rate_limit_settings = create_settings_snapshot(
overrides={
"llm.rate_limit.enabled": True,
"llm.rate_limit.requests_per_minute": 60,
"llm.rate_limit.tokens_per_minute": 90000,
"llm.rate_limit.concurrent_requests": 5,
"search.rate_limit.requests_per_second": 10,
}
)
# Verify rate limiting configuration
assert rate_limit_settings["llm.rate_limit.enabled"]["value"] is True
assert (
rate_limit_settings["llm.rate_limit.requests_per_minute"]["value"]
== 60
)
assert (
rate_limit_settings["search.rate_limit.requests_per_second"][
"value"
]
== 10
)
class TestDebugAndMonitoringSettings:
"""Test debug and monitoring related settings."""
def test_debug_mode_settings(self):
"""Test settings for debug mode."""
debug_settings = create_settings_snapshot(
overrides={
"debug.enabled": True,
"debug.log_level": "DEBUG",
"debug.log_api_calls": True,
"debug.log_search_queries": True,
"debug.save_intermediate_results": True,
"debug.pretty_print_responses": True,
}
)
# Verify debug settings
assert debug_settings["debug.enabled"]["value"] is True
assert debug_settings["debug.log_level"]["value"] == "DEBUG"
assert debug_settings["debug.log_api_calls"]["value"] is True
def test_monitoring_settings(self):
"""Test settings for monitoring and metrics."""
monitoring_settings = create_settings_snapshot(
overrides={
"monitoring.enabled": True,
"monitoring.metrics.api_latency": True,
"monitoring.metrics.token_usage": True,
"monitoring.metrics.search_performance": True,
"monitoring.export.format": "prometheus",
"monitoring.export.endpoint": "http://metrics.example.com",
}
)
# Verify monitoring configuration
assert monitoring_settings["monitoring.enabled"]["value"] is True
assert (
monitoring_settings["monitoring.metrics.token_usage"]["value"]
is True
)
assert (
monitoring_settings["monitoring.export.format"]["value"]
== "prometheus"
)