项目文件夹

文件
wehub-resource-sync 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
chore: import upstream snapshot with attribution
2026-07-13 13:08:55 +08:00

507 行
17 KiB
Python

"""
Test that all strategies can execute analyze_topic() with mocked dependencies.
This is Level 3 testing - verifying the main method works without crashing.
"""
import pytest
from loguru import logger
from unittest.mock import Mock
from .conftest import FACTORY_STRATEGY_NAMES
# Strategies known to have complex requirements or be experimental
SLOW_STRATEGIES = [
"evidence",
"constrained",
"parallel-constrained",
"early-stop-constrained",
"smart-query",
"dual-confidence",
"dual-confidence-with-rejection",
"concurrent-dual-confidence",
"constraint-parallel",
"modular",
"modular-parallel",
"browsecomp",
"browsecomp-entity",
]
# Core strategies that should definitely work
CORE_STRATEGIES = [
"source-based",
"rapid",
"parallel",
"iterdrag",
"standard",
"news",
"recursive",
]
class TestCoreStrategiesAnalyzeTopic:
"""Test analyze_topic for core strategies that should definitely work."""
@pytest.mark.parametrize("strategy_name", CORE_STRATEGIES)
def test_analyze_topic_returns_dict(
self,
strategy_name: str,
strategy_mock_llm,
strategy_mock_search,
strategy_settings_snapshot,
):
"""Test that analyze_topic returns a dict with expected keys."""
from local_deep_research.search_system_factory import (
create_strategy,
)
strategy = create_strategy(
strategy_name=strategy_name,
model=strategy_mock_llm,
search=strategy_mock_search,
settings_snapshot=strategy_settings_snapshot,
)
try:
result = strategy.analyze_topic("What is artificial intelligence?")
# Should return a dict
assert isinstance(result, dict), (
f"{strategy_name} returned {type(result)} instead of dict"
)
# Should have key attributes (may vary by strategy)
# Most strategies should return at least these
logger.info(f"{strategy_name} returned keys: {list(result.keys())}")
# Verify commonly expected keys
if "error" in result:
logger.warning(
f"{strategy_name} returned with error: {result.get('error')}"
)
except Exception as e:
pytest.fail(
f"{strategy_name}.analyze_topic failed: {type(e).__name__}: {e}"
)
@pytest.mark.parametrize("strategy_name", CORE_STRATEGIES)
def test_analyze_topic_with_progress_callback(
self,
strategy_name: str,
strategy_mock_llm,
strategy_mock_search,
strategy_settings_snapshot,
):
"""Test that progress callbacks are called during analyze_topic."""
from local_deep_research.search_system_factory import (
create_strategy,
)
strategy = create_strategy(
strategy_name=strategy_name,
model=strategy_mock_llm,
search=strategy_mock_search,
settings_snapshot=strategy_settings_snapshot,
)
callback = Mock()
strategy.set_progress_callback(callback)
try:
strategy.analyze_topic("Test query")
# Callback should have been called at least once
if callback.call_count > 0:
logger.info(
f"{strategy_name} called progress callback {callback.call_count} times"
)
else:
logger.warning(
f"{strategy_name} never called progress callback"
)
except Exception as e:
pytest.fail(f"{strategy_name} failed with callback: {e}")
class TestAllStrategiesAnalyzeTopic:
"""Test analyze_topic for all factory strategies."""
@pytest.mark.parametrize("strategy_name", FACTORY_STRATEGY_NAMES)
def test_analyze_topic_does_not_crash(
self,
strategy_name: str,
strategy_mock_llm,
strategy_mock_search,
strategy_settings_snapshot,
):
"""
Test that analyze_topic doesn't crash for any strategy.
This test documents which strategies work and which have issues,
without failing the entire test suite.
"""
from local_deep_research.search_system_factory import (
create_strategy,
)
try:
strategy = create_strategy(
strategy_name=strategy_name,
model=strategy_mock_llm,
search=strategy_mock_search,
settings_snapshot=strategy_settings_snapshot,
)
result = strategy.analyze_topic(
"Test query for strategy validation"
)
# Success - log what we got
assert isinstance(result, dict), (
f"Expected dict, got {type(result)}"
)
keys = list(result.keys())
logger.info(
f"[OK] {strategy_name}: returned {len(keys)} keys: {keys[:5]}..."
)
# Check for error in result
if "error" in result and result["error"]:
logger.warning(
f"[WARN] {strategy_name} returned error: {result['error']}"
)
except AttributeError as e:
# Log attribute errors - often indicate missing mock methods
logger.exception(f"[ATTR ERROR] {strategy_name}")
pytest.skip(f"{strategy_name} has attribute issues: {e}")
except TypeError as e:
# Type errors often indicate constructor or method signature issues
logger.exception(f"[TYPE ERROR] {strategy_name}")
pytest.skip(f"{strategy_name} has type issues: {e}")
except Exception as e:
# Other exceptions - log for analysis
logger.exception(f"[ERROR] {strategy_name}: {type(e).__name__}")
pytest.skip(f"{strategy_name} failed: {type(e).__name__}: {e}")
class TestAnalyzeTopicReturnStructure:
"""Test the structure of analyze_topic return values."""
@pytest.mark.parametrize("strategy_name", CORE_STRATEGIES)
def test_result_has_findings_key(
self,
strategy_name: str,
strategy_mock_llm,
strategy_mock_search,
strategy_settings_snapshot,
):
"""Test that result contains 'findings' key."""
from local_deep_research.search_system_factory import (
create_strategy,
)
strategy = create_strategy(
strategy_name=strategy_name,
model=strategy_mock_llm,
search=strategy_mock_search,
settings_snapshot=strategy_settings_snapshot,
)
result = strategy.analyze_topic("Test query")
# findings key should exist (may be empty list)
assert "findings" in result or "error" in result, (
f"{strategy_name} missing 'findings' key. Keys: {list(result.keys())}"
)
@pytest.mark.parametrize("strategy_name", CORE_STRATEGIES)
def test_result_has_current_knowledge(
self,
strategy_name: str,
strategy_mock_llm,
strategy_mock_search,
strategy_settings_snapshot,
):
"""Test that result contains current_knowledge."""
from local_deep_research.search_system_factory import (
create_strategy,
)
strategy = create_strategy(
strategy_name=strategy_name,
model=strategy_mock_llm,
search=strategy_mock_search,
settings_snapshot=strategy_settings_snapshot,
)
result = strategy.analyze_topic("Test query")
# Should have current_knowledge (the synthesized answer)
if "error" not in result:
assert "current_knowledge" in result, (
f"{strategy_name} missing 'current_knowledge'. Keys: {list(result.keys())}"
)
class TestLinksAccumulation:
"""Test that strategies properly accumulate links."""
@pytest.mark.parametrize("strategy_name", CORE_STRATEGIES)
def test_links_populated_after_analyze(
self,
strategy_name: str,
strategy_mock_llm,
strategy_mock_search,
strategy_settings_snapshot,
):
"""Test that all_links_of_system is populated after analyze_topic."""
from local_deep_research.search_system_factory import (
create_strategy,
)
strategy = create_strategy(
strategy_name=strategy_name,
model=strategy_mock_llm,
search=strategy_mock_search,
settings_snapshot=strategy_settings_snapshot,
)
# Start empty
assert len(strategy.all_links_of_system) == 0
strategy.analyze_topic("Test query")
# After search, should have some links (from mock search results)
logger.info(
f"{strategy_name} accumulated {len(strategy.all_links_of_system)} links"
)
class TestQuestionsTracking:
"""Test that strategies track generated questions."""
@pytest.mark.parametrize("strategy_name", CORE_STRATEGIES)
def test_questions_by_iteration_populated(
self,
strategy_name: str,
strategy_mock_llm,
strategy_mock_search,
strategy_settings_snapshot,
):
"""Test that questions_by_iteration is populated."""
from local_deep_research.search_system_factory import (
create_strategy,
)
strategy = create_strategy(
strategy_name=strategy_name,
model=strategy_mock_llm,
search=strategy_mock_search,
settings_snapshot=strategy_settings_snapshot,
)
result = strategy.analyze_topic("Test query")
# Should have questions tracked
questions = strategy.questions_by_iteration
logger.info(
f"{strategy_name} tracked {len(questions)} iterations of questions"
)
# Result may also contain questions
if "questions_by_iteration" in result:
logger.info(
f"Result contains questions_by_iteration with {len(result['questions_by_iteration'])} iterations"
)
class TestErrorHandling:
"""Test strategy behavior when search returns errors or empty results."""
@pytest.mark.parametrize("strategy_name", CORE_STRATEGIES)
def test_handles_empty_search_results(
self,
strategy_name: str,
strategy_mock_llm,
strategy_settings_snapshot,
):
"""Test that strategy handles empty search results gracefully."""
from local_deep_research.search_system_factory import (
create_strategy,
)
# Mock search that returns empty results
empty_search = Mock()
empty_search.run = Mock(return_value=[])
empty_search.include_full_content = True
strategy = create_strategy(
strategy_name=strategy_name,
model=strategy_mock_llm,
search=empty_search,
settings_snapshot=strategy_settings_snapshot,
)
try:
result = strategy.analyze_topic("Test query with no results")
# Should not crash - may return error or empty findings
assert isinstance(result, dict)
logger.info(
f"{strategy_name} handled empty results: {list(result.keys())}"
)
except Exception as e:
pytest.fail(f"{strategy_name} crashed on empty results: {e}")
@pytest.mark.parametrize(
"strategy_name", CORE_STRATEGIES[:3]
) # Test subset
def test_handles_search_exception(
self,
strategy_name: str,
strategy_mock_llm,
strategy_settings_snapshot,
):
"""Test that strategy handles search exceptions gracefully."""
from local_deep_research.search_system_factory import (
create_strategy,
)
# Mock search that raises an exception
error_search = Mock()
error_search.run = Mock(side_effect=Exception("Search API error"))
error_search.include_full_content = True
strategy = create_strategy(
strategy_name=strategy_name,
model=strategy_mock_llm,
search=error_search,
settings_snapshot=strategy_settings_snapshot,
)
try:
result = strategy.analyze_topic("Test query")
# Should handle the error gracefully
assert isinstance(result, dict)
# Should indicate an error occurred
if "error" in result:
logger.info(
f"{strategy_name} returned error: {result['error']}"
)
except Exception as e:
# Some strategies may propagate the error
logger.warning(f"{strategy_name} propagated error: {e}")
def test_source_based_scrubs_exception_text_from_error_fields(
self,
strategy_mock_llm,
strategy_settings_snapshot,
):
"""CWE-209 / CodeQL #8019 regression guard: the except block in
SourceBasedSearchStrategy must run exception text through
sanitize_error_for_client before it lands in the client-visible
current_knowledge / formatted_findings / finding-content fields.
Unlike test_handles_search_exception above, a failure here fails
the test — no try/except softening — so a regression to raw
``f"Error: {e!s}"`` (or a crash inside the except block) is
caught.
A search-level exception never reaches that except block (the
per-question search path swallows it into a "No sources were
found" summary), so the failure is injected at
``findings_repository.format_findings_to_text`` — called inside
the guarded block right before the return.
"""
from local_deep_research.search_system_factory import (
create_strategy,
)
secret = "sk-STRATEGYLEAK123456789012"
mock_search = Mock()
mock_search.run = Mock(
return_value=[
{
"title": "Result",
"link": "https://example.com/1",
"snippet": "A result snippet",
}
]
)
mock_search.include_full_content = True
strategy = create_strategy(
strategy_name="source-based",
model=strategy_mock_llm,
search=mock_search,
settings_snapshot=strategy_settings_snapshot,
)
# The categorizable token sits past char 200 of the scrubbed
# message: it must survive the strategy-layer cap (500, aligned
# with _ERROR_BOUNDARY_MAX_LEN / _TOOL_ERROR_MAX_LEN). With
# sanitize_error_for_client's 200-char default it would be
# truncated away before the API boundary ever sees it.
padding = "x" * 230
strategy.findings_repository.format_findings_to_text = Mock(
side_effect=Exception(
f"401 Unauthorized: https://api.example.com/v1?api_key={secret}"
f" {padding} Connection refused [Errno 111]"
)
)
result = strategy.analyze_topic("Test query")
assert result["current_knowledge"].startswith("Error:")
assert result["formatted_findings"].startswith("Error:")
assert secret not in result["current_knowledge"]
assert secret not in result["formatted_findings"]
assert "Connection refused" in result["current_knowledge"]
error_contents = [
f["content"]
for f in result["findings"]
if isinstance(f, dict) and isinstance(f.get("content"), str)
]
assert error_contents, "expected at least one finding with content"
assert all(secret not in content for content in error_contents)
class TestSearchSystemIntegration:
"""Test analyze_topic through AdvancedSearchSystem."""
@pytest.mark.parametrize("strategy_name", CORE_STRATEGIES)
def test_search_system_analyze_topic(
self,
strategy_name: str,
strategy_mock_llm,
strategy_mock_search,
strategy_settings_snapshot,
):
"""Test analyze_topic through the full AdvancedSearchSystem."""
from local_deep_research.search_system import AdvancedSearchSystem
system = AdvancedSearchSystem(
llm=strategy_mock_llm,
search=strategy_mock_search,
strategy_name=strategy_name,
settings_snapshot=strategy_settings_snapshot,
)
result = system.analyze_topic("Test query")
assert isinstance(result, dict)
assert "search_system" in result # AdvancedSearchSystem adds this
assert result["search_system"] == system
logger.info(f"SearchSystem with {strategy_name}: {list(result.keys())}")