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
85 lines
3.3 KiB
Python
85 lines
3.3 KiB
Python
"""The LangGraph ``research_subtopic`` subagent pool must propagate the lead
|
|
thread's search context (which carries the user's DB password) into its stdlib
|
|
``ThreadPoolExecutor`` workers.
|
|
|
|
Without it, a subagent that re-creates an engine / registers the user's
|
|
document collections runs ``get_user_db_session`` with no password and fails to
|
|
open the per-user ENCRYPTED database — surfacing as
|
|
"Unknown search engine 'collection_…'". Sibling strategies close this gap with
|
|
``@preserve_research_context``; this strategy captures the context on the lead
|
|
thread and re-sets it per pool task.
|
|
|
|
The test drives the REAL ``_make_research_subtopic_tool`` pool wiring; only the
|
|
leaf subagent internals (``_make_web_search_tool``, ``build_fetch_tool``,
|
|
``create_agent``) are stubbed, so the assertion is purely about what context a
|
|
worker thread observes.
|
|
"""
|
|
|
|
from types import SimpleNamespace
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
import pytest
|
|
|
|
import local_deep_research.advanced_search_system.strategies.langgraph_agent_strategy as strat # noqa: E501
|
|
from local_deep_research.utilities.thread_context import (
|
|
clear_search_context,
|
|
get_search_context,
|
|
set_search_context,
|
|
)
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def _clean_search_context():
|
|
# Never let a context leak into or out of these tests.
|
|
clear_search_context()
|
|
yield
|
|
clear_search_context()
|
|
|
|
|
|
def _invoke_one_subtopic(seen):
|
|
"""Build the real research_subtopic tool with stubbed subagent internals,
|
|
then invoke it once. ``seen`` collects the search context observed inside
|
|
the pool worker (via the stubbed web-search tool factory)."""
|
|
|
|
def fake_web_search_tool(*_a, **_k):
|
|
# Runs INSIDE the pool worker — record the context it can see.
|
|
seen.append(get_search_context())
|
|
return SimpleNamespace(name="web_search")
|
|
|
|
agent = MagicMock()
|
|
agent.invoke.return_value = {"messages": [SimpleNamespace(content="ok")]}
|
|
|
|
with (
|
|
patch.object(strat, "_make_web_search_tool", fake_web_search_tool),
|
|
patch.object(strat, "build_fetch_tool", lambda *_a, **_k: None),
|
|
patch("langchain.agents.create_agent", return_value=agent),
|
|
):
|
|
tool = strat._make_research_subtopic_tool(
|
|
search_engine_name="collection_abc",
|
|
model=MagicMock(),
|
|
settings_snapshot={"search.tool": "collection_abc"},
|
|
collector=MagicMock(),
|
|
max_sub_iterations=1,
|
|
)
|
|
tool.invoke({"subtopics": ["what is X?"]})
|
|
|
|
|
|
class TestSubagentSearchContextPropagation:
|
|
def test_password_context_reaches_pool_worker(self):
|
|
# Lead thread has the user's DB password in the search context...
|
|
set_search_context({"user_password": "secret", "research_id": "r1"})
|
|
seen = []
|
|
_invoke_one_subtopic(seen)
|
|
# ...and the pool worker sees it (propagated), not None. This fails
|
|
# against the pre-fix code where the ContextVar was never carried in.
|
|
assert seen, "subagent web_search tool was never built"
|
|
assert seen[0] is not None
|
|
assert seen[0].get("user_password") == "secret"
|
|
|
|
def test_no_context_does_not_crash(self):
|
|
# No lead-thread context (programmatic/no-encryption): the pool worker
|
|
# simply sees None and the run proceeds without error.
|
|
seen = []
|
|
_invoke_one_subtopic(seen)
|
|
assert seen and seen[0] is None
|