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
416 lines
15 KiB
Python
416 lines
15 KiB
Python
"""Extended tests for search_system.py - covering settings processing, programmatic mode,
|
|
strategy selection, and result processing."""
|
|
|
|
from unittest.mock import Mock, patch
|
|
|
|
|
|
def _make_strategy_mock():
|
|
"""Create a mock strategy with required attributes."""
|
|
mock_strategy = Mock()
|
|
mock_strategy.questions_by_iteration = []
|
|
mock_strategy.all_links_of_system = []
|
|
return mock_strategy
|
|
|
|
|
|
def _patch_system():
|
|
"""Return nested context managers for search system patching.
|
|
|
|
Usage:
|
|
with _create_patched():
|
|
...
|
|
|
|
Returns a helper that patches both create_strategy and StandardCitationHandler.
|
|
"""
|
|
|
|
class PatchContext:
|
|
def __init__(self):
|
|
self.strategy = _make_strategy_mock()
|
|
self._p1 = patch(
|
|
"local_deep_research.search_system_factory.create_strategy",
|
|
return_value=self.strategy,
|
|
)
|
|
self._p2 = patch(
|
|
"local_deep_research.citation_handlers.standard_citation_handler.StandardCitationHandler",
|
|
return_value=Mock(
|
|
_create_documents=Mock(), _format_sources=Mock()
|
|
),
|
|
)
|
|
|
|
def __enter__(self):
|
|
self.mock_create = self._p1.__enter__()
|
|
self.mock_citation = self._p2.__enter__()
|
|
return self
|
|
|
|
def __exit__(self, *args):
|
|
self._p2.__exit__(*args)
|
|
self._p1.__exit__(*args)
|
|
|
|
return PatchContext()
|
|
|
|
|
|
class TestAdvancedSearchSystemSettingsProcessing:
|
|
"""Tests for settings snapshot processing in AdvancedSearchSystem.__init__."""
|
|
|
|
def test_max_iterations_from_dict_settings(self):
|
|
"""max_iterations should be extracted from dict-wrapped settings."""
|
|
settings = {
|
|
"search.iterations": {"value": 5, "ui_element": "number"},
|
|
"search.questions_per_iteration": {
|
|
"value": 4,
|
|
"ui_element": "number",
|
|
},
|
|
}
|
|
|
|
with _patch_system():
|
|
from local_deep_research.search_system import AdvancedSearchSystem
|
|
|
|
system = AdvancedSearchSystem(
|
|
llm=Mock(), search=Mock(), settings_snapshot=settings
|
|
)
|
|
|
|
assert system.max_iterations == 5
|
|
assert system.questions_per_iteration == 4
|
|
|
|
def test_max_iterations_from_direct_value_settings(self):
|
|
"""max_iterations should work with direct (non-dict) settings values."""
|
|
settings = {
|
|
"search.iterations": 3,
|
|
"search.questions_per_iteration": 2,
|
|
}
|
|
|
|
with _patch_system():
|
|
from local_deep_research.search_system import AdvancedSearchSystem
|
|
|
|
system = AdvancedSearchSystem(
|
|
llm=Mock(), search=Mock(), settings_snapshot=settings
|
|
)
|
|
|
|
assert system.max_iterations == 3
|
|
assert system.questions_per_iteration == 2
|
|
|
|
def test_max_iterations_defaults_when_missing(self):
|
|
"""max_iterations should default to 1 when not in settings."""
|
|
with _patch_system():
|
|
from local_deep_research.search_system import AdvancedSearchSystem
|
|
|
|
system = AdvancedSearchSystem(
|
|
llm=Mock(), search=Mock(), settings_snapshot={}
|
|
)
|
|
|
|
assert system.max_iterations == 1
|
|
assert system.questions_per_iteration == 3
|
|
|
|
def test_explicit_max_iterations_overrides_settings(self):
|
|
"""Explicit max_iterations parameter should override settings snapshot."""
|
|
settings = {"search.iterations": {"value": 10}}
|
|
|
|
with _patch_system():
|
|
from local_deep_research.search_system import AdvancedSearchSystem
|
|
|
|
system = AdvancedSearchSystem(
|
|
llm=Mock(),
|
|
search=Mock(),
|
|
max_iterations=7,
|
|
settings_snapshot=settings,
|
|
)
|
|
|
|
assert system.max_iterations == 7
|
|
|
|
|
|
class TestAdvancedSearchSystemProgrammaticMode:
|
|
"""Tests for programmatic mode behavior."""
|
|
|
|
def test_programmatic_mode_sets_flag(self):
|
|
"""programmatic_mode flag should be stored."""
|
|
with _patch_system():
|
|
from local_deep_research.search_system import AdvancedSearchSystem
|
|
|
|
system = AdvancedSearchSystem(
|
|
llm=Mock(), search=Mock(), programmatic_mode=True
|
|
)
|
|
|
|
assert system.programmatic_mode is True
|
|
|
|
def test_non_programmatic_mode_default(self):
|
|
"""programmatic_mode should default to False."""
|
|
with _patch_system():
|
|
from local_deep_research.search_system import AdvancedSearchSystem
|
|
|
|
system = AdvancedSearchSystem(llm=Mock(), search=Mock())
|
|
|
|
assert system.programmatic_mode is False
|
|
|
|
|
|
class TestAdvancedSearchSystemResearchContext:
|
|
"""Tests for research context handling."""
|
|
|
|
def test_stores_research_context(self):
|
|
"""research_context should be stored on the system."""
|
|
context = {"delegate_strategy": "rapid", "some_key": "some_value"}
|
|
|
|
with _patch_system():
|
|
from local_deep_research.search_system import AdvancedSearchSystem
|
|
|
|
system = AdvancedSearchSystem(
|
|
llm=Mock(), search=Mock(), research_context=context
|
|
)
|
|
|
|
assert system.research_context == context
|
|
|
|
def test_none_research_context(self):
|
|
"""None research_context should not cause errors."""
|
|
with _patch_system():
|
|
from local_deep_research.search_system import AdvancedSearchSystem
|
|
|
|
system = AdvancedSearchSystem(
|
|
llm=Mock(), search=Mock(), research_context=None
|
|
)
|
|
|
|
assert system.research_context is None
|
|
|
|
def test_research_id_stored(self):
|
|
"""research_id should be stored on the system."""
|
|
with _patch_system():
|
|
from local_deep_research.search_system import AdvancedSearchSystem
|
|
|
|
system = AdvancedSearchSystem(
|
|
llm=Mock(), search=Mock(), research_id="test-123"
|
|
)
|
|
|
|
assert system.research_id == "test-123"
|
|
|
|
|
|
class TestAdvancedSearchSystemContextualFollowUp:
|
|
"""Tests for contextual follow-up strategy initialization."""
|
|
|
|
def test_contextual_followup_uses_delegate(self):
|
|
"""Contextual follow-up strategy should create a delegate strategy."""
|
|
context = {"delegate_strategy": "rapid"}
|
|
|
|
with _patch_system() as ctx:
|
|
with patch(
|
|
"local_deep_research.search_system.EnhancedContextualFollowUpStrategy"
|
|
) as mock_followup_cls:
|
|
mock_followup_cls.return_value = Mock()
|
|
|
|
from local_deep_research.search_system import (
|
|
AdvancedSearchSystem,
|
|
)
|
|
|
|
AdvancedSearchSystem(
|
|
llm=Mock(),
|
|
search=Mock(),
|
|
strategy_name="enhanced-contextual-followup",
|
|
research_context=context,
|
|
)
|
|
|
|
# Verify delegate strategy was created with rapid
|
|
ctx.mock_create.assert_called()
|
|
call_kwargs = ctx.mock_create.call_args
|
|
assert call_kwargs.kwargs.get("strategy_name") == "rapid" or (
|
|
call_kwargs[1].get("strategy_name") == "rapid"
|
|
)
|
|
|
|
def test_contextual_followup_defaults_delegate_to_source_based(self):
|
|
"""Contextual follow-up without delegate_strategy should default to source-based."""
|
|
with _patch_system() as ctx:
|
|
with patch(
|
|
"local_deep_research.search_system.EnhancedContextualFollowUpStrategy"
|
|
) as mock_followup_cls:
|
|
mock_followup_cls.return_value = Mock()
|
|
|
|
from local_deep_research.search_system import (
|
|
AdvancedSearchSystem,
|
|
)
|
|
|
|
AdvancedSearchSystem(
|
|
llm=Mock(),
|
|
search=Mock(),
|
|
strategy_name="contextual-followup",
|
|
research_context={},
|
|
)
|
|
|
|
call_kwargs = ctx.mock_create.call_args
|
|
strategy_name = call_kwargs.kwargs.get(
|
|
"strategy_name"
|
|
) or call_kwargs[1].get("strategy_name")
|
|
assert strategy_name == "source-based"
|
|
|
|
def test_contextual_followup_name_variants(self):
|
|
"""All contextual follow-up name variants should be recognized."""
|
|
variants = [
|
|
"enhanced-contextual-followup",
|
|
"enhanced_contextual_followup",
|
|
"contextual-followup",
|
|
"contextual_followup",
|
|
]
|
|
|
|
for variant in variants:
|
|
with _patch_system():
|
|
with patch(
|
|
"local_deep_research.search_system.EnhancedContextualFollowUpStrategy"
|
|
) as mock_followup_cls:
|
|
mock_followup_cls.return_value = Mock()
|
|
|
|
from local_deep_research.search_system import (
|
|
AdvancedSearchSystem,
|
|
)
|
|
|
|
AdvancedSearchSystem(
|
|
llm=Mock(),
|
|
search=Mock(),
|
|
strategy_name=variant,
|
|
)
|
|
# Should use EnhancedContextualFollowUpStrategy
|
|
mock_followup_cls.assert_called_once()
|
|
|
|
|
|
class TestAdvancedSearchSystemSearchOriginalQuery:
|
|
"""Tests for search_original_query parameter."""
|
|
|
|
def test_search_original_query_default_true(self):
|
|
"""search_original_query should default to True."""
|
|
with _patch_system():
|
|
from local_deep_research.search_system import AdvancedSearchSystem
|
|
|
|
system = AdvancedSearchSystem(llm=Mock(), search=Mock())
|
|
|
|
assert system.search_original_query is True
|
|
|
|
def test_search_original_query_can_be_disabled(self):
|
|
"""search_original_query can be set to False for news searches."""
|
|
with _patch_system():
|
|
from local_deep_research.search_system import AdvancedSearchSystem
|
|
|
|
system = AdvancedSearchSystem(
|
|
llm=Mock(), search=Mock(), search_original_query=False
|
|
)
|
|
|
|
assert system.search_original_query is False
|
|
|
|
|
|
class TestAdvancedSearchSystemPerformSearch:
|
|
"""Tests for _perform_search result processing."""
|
|
|
|
def test_result_includes_query(self):
|
|
"""Result should include the query if not returned by strategy."""
|
|
with _patch_system() as ctx:
|
|
ctx.strategy.analyze_topic.return_value = {
|
|
"current_knowledge": "some results"
|
|
}
|
|
ctx.strategy.questions_by_iteration = {1: ["q1"]}
|
|
|
|
from local_deep_research.search_system import AdvancedSearchSystem
|
|
|
|
system = AdvancedSearchSystem(llm=Mock(), search=Mock())
|
|
|
|
with patch(
|
|
"local_deep_research.news.core.search_integration.NewsSearchCallback"
|
|
):
|
|
result = system.analyze_topic("test query")
|
|
|
|
assert result["query"] == "test query"
|
|
|
|
def test_result_includes_search_system_reference(self):
|
|
"""Result should include reference to the search system."""
|
|
with _patch_system() as ctx:
|
|
ctx.strategy.analyze_topic.return_value = {
|
|
"current_knowledge": "results"
|
|
}
|
|
ctx.strategy.questions_by_iteration = {}
|
|
|
|
from local_deep_research.search_system import AdvancedSearchSystem
|
|
|
|
system = AdvancedSearchSystem(llm=Mock(), search=Mock())
|
|
|
|
with patch(
|
|
"local_deep_research.news.core.search_integration.NewsSearchCallback"
|
|
):
|
|
result = system.analyze_topic("test query")
|
|
|
|
assert result["search_system"] is system
|
|
|
|
def test_settings_snapshot_extraction_in_perform_search(self):
|
|
"""_perform_search should extract LLM/search info from settings snapshot."""
|
|
settings = {
|
|
"llm.provider": {"value": "openai"},
|
|
"llm.model": {"value": "gpt-4"},
|
|
"search.tool": {"value": "searxng"},
|
|
}
|
|
|
|
progress_calls = []
|
|
|
|
def capture_progress(msg, progress, metadata):
|
|
progress_calls.append((msg, progress, metadata))
|
|
|
|
with _patch_system() as ctx:
|
|
ctx.strategy.analyze_topic.return_value = {"current_knowledge": "r"}
|
|
ctx.strategy.questions_by_iteration = {}
|
|
|
|
from local_deep_research.search_system import AdvancedSearchSystem
|
|
|
|
system = AdvancedSearchSystem(
|
|
llm=Mock(), search=Mock(), settings_snapshot=settings
|
|
)
|
|
system.set_progress_callback(capture_progress)
|
|
|
|
with patch(
|
|
"local_deep_research.news.core.search_integration.NewsSearchCallback"
|
|
):
|
|
system.analyze_topic("test")
|
|
|
|
# Should have progress callbacks with LLM and search info
|
|
assert len(progress_calls) >= 2
|
|
assert "openai" in progress_calls[0][0]
|
|
assert "gpt-4" in progress_calls[0][0]
|
|
|
|
def test_links_deduplication(self):
|
|
"""all_links_of_system should not be duplicated when same object."""
|
|
shared_links = [{"title": "link1", "url": "http://example.com"}]
|
|
|
|
with _patch_system() as ctx:
|
|
ctx.strategy.analyze_topic.return_value = {"current_knowledge": "r"}
|
|
ctx.strategy.questions_by_iteration = {}
|
|
ctx.strategy.all_links_of_system = shared_links
|
|
|
|
from local_deep_research.search_system import AdvancedSearchSystem
|
|
|
|
system = AdvancedSearchSystem(llm=Mock(), search=Mock())
|
|
system.all_links_of_system = shared_links
|
|
|
|
with patch(
|
|
"local_deep_research.news.core.search_integration.NewsSearchCallback"
|
|
):
|
|
system.analyze_topic("test")
|
|
|
|
# Should NOT have duplicated the links
|
|
assert len(system.all_links_of_system) == 1
|
|
|
|
|
|
class TestAdvancedSearchSystemProgressCallback:
|
|
"""Tests for progress callback mechanism."""
|
|
|
|
def test_set_progress_callback_propagates_to_strategy(self):
|
|
"""set_progress_callback should propagate to the strategy."""
|
|
with _patch_system() as ctx:
|
|
from local_deep_research.search_system import AdvancedSearchSystem
|
|
|
|
system = AdvancedSearchSystem(llm=Mock(), search=Mock())
|
|
|
|
def noop_callback(msg, pct, meta):
|
|
return None
|
|
|
|
system.set_progress_callback(noop_callback)
|
|
|
|
ctx.strategy.set_progress_callback.assert_called_with(noop_callback)
|
|
|
|
def test_progress_callback_before_strategy_exists(self):
|
|
"""Setting callback before strategy is created should not crash."""
|
|
with _patch_system():
|
|
from local_deep_research.search_system import AdvancedSearchSystem
|
|
|
|
system = AdvancedSearchSystem(llm=Mock(), search=Mock())
|
|
|
|
# Default callback should be a no-op lambda
|
|
system.progress_callback("test", 50, {})
|