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
184 lines
5.8 KiB
Python
184 lines
5.8 KiB
Python
"""
|
|
Tests for encryption key passing to background threads.
|
|
|
|
Verifies that research threads can access encrypted databases with the correct password.
|
|
"""
|
|
|
|
import threading
|
|
import pytest
|
|
from unittest.mock import patch
|
|
|
|
from tests.test_utils import add_src_to_path
|
|
|
|
add_src_to_path()
|
|
|
|
|
|
class TestThreadContextPasswordStorage:
|
|
"""Test that thread context correctly stores and retrieves passwords."""
|
|
|
|
def test_set_and_get_password_in_same_thread(self):
|
|
"""Password set via set_search_context should be retrievable."""
|
|
from local_deep_research.utilities.thread_context import (
|
|
set_search_context,
|
|
get_search_context,
|
|
)
|
|
|
|
set_search_context(
|
|
{
|
|
"username": "test_user",
|
|
"user_password": "secret123",
|
|
}
|
|
)
|
|
|
|
ctx = get_search_context()
|
|
assert ctx is not None
|
|
assert ctx.get("user_password") == "secret123"
|
|
|
|
def test_context_includes_all_fields(self):
|
|
"""All fields in context should be preserved."""
|
|
from local_deep_research.utilities.thread_context import (
|
|
set_search_context,
|
|
get_search_context,
|
|
)
|
|
|
|
context = {
|
|
"research_id": "res_123",
|
|
"username": "alice",
|
|
"user_password": "pass456",
|
|
"custom_field": "custom_value",
|
|
}
|
|
set_search_context(context)
|
|
|
|
retrieved = get_search_context()
|
|
assert retrieved["research_id"] == "res_123"
|
|
assert retrieved["username"] == "alice"
|
|
assert retrieved["user_password"] == "pass456"
|
|
assert retrieved["custom_field"] == "custom_value"
|
|
|
|
|
|
class TestThreadContextIsolation:
|
|
"""Test that thread context is properly isolated between threads."""
|
|
|
|
def test_child_thread_does_not_inherit_context(self):
|
|
"""A new thread should NOT see the parent thread's context."""
|
|
from local_deep_research.utilities.thread_context import (
|
|
set_search_context,
|
|
get_search_context,
|
|
)
|
|
|
|
child_result = []
|
|
|
|
def child_thread():
|
|
ctx = get_search_context()
|
|
child_result.append(ctx)
|
|
|
|
# Set context in main thread
|
|
set_search_context({"user_password": "main_thread_pass"})
|
|
|
|
# Child thread should not see it
|
|
thread = threading.Thread(target=child_thread)
|
|
thread.start()
|
|
thread.join(timeout=2)
|
|
|
|
assert child_result[0] is None, (
|
|
"Child thread should not inherit parent's context"
|
|
)
|
|
|
|
def test_child_thread_can_set_own_context(self):
|
|
"""A child thread can set and retrieve its own context."""
|
|
from local_deep_research.utilities.thread_context import (
|
|
set_search_context,
|
|
get_search_context,
|
|
)
|
|
|
|
child_result = []
|
|
|
|
def child_thread():
|
|
set_search_context({"user_password": "child_pass"})
|
|
ctx = get_search_context()
|
|
child_result.append(ctx)
|
|
|
|
thread = threading.Thread(target=child_thread)
|
|
thread.start()
|
|
thread.join(timeout=2)
|
|
|
|
assert child_result[0] is not None
|
|
assert child_result[0]["user_password"] == "child_pass"
|
|
|
|
|
|
class TestGetUserDbSessionPasswordRetrieval:
|
|
"""Test that get_user_db_session retrieves password from thread context."""
|
|
|
|
def test_password_retrieved_from_thread_context(self):
|
|
"""get_user_db_session should use password from thread context."""
|
|
from local_deep_research.utilities.thread_context import (
|
|
set_search_context,
|
|
)
|
|
from local_deep_research.database.session_context import (
|
|
get_user_db_session,
|
|
)
|
|
from local_deep_research.database.encrypted_db import db_manager
|
|
from local_deep_research.database import thread_local_session
|
|
|
|
set_search_context(
|
|
{
|
|
"username": "test_user",
|
|
"user_password": "thread_context_password",
|
|
}
|
|
)
|
|
|
|
captured_passwords = []
|
|
|
|
def capture(username, password):
|
|
captured_passwords.append(password)
|
|
raise Exception("Captured")
|
|
|
|
with patch(
|
|
"local_deep_research.database.session_context.has_app_context",
|
|
return_value=False,
|
|
):
|
|
with patch.object(db_manager, "has_encryption", True):
|
|
with patch.object(
|
|
thread_local_session,
|
|
"get_metrics_session",
|
|
side_effect=capture,
|
|
):
|
|
try:
|
|
with get_user_db_session("test_user"):
|
|
pass
|
|
except Exception:
|
|
pass
|
|
|
|
assert len(captured_passwords) == 1
|
|
assert captured_passwords[0] == "thread_context_password"
|
|
|
|
def test_none_password_causes_error_with_encryption(self):
|
|
"""If password is None and encryption is enabled, should raise error."""
|
|
from local_deep_research.utilities.thread_context import (
|
|
set_search_context,
|
|
)
|
|
from local_deep_research.database.session_context import (
|
|
get_user_db_session,
|
|
DatabaseSessionError,
|
|
)
|
|
from local_deep_research.database.encrypted_db import db_manager
|
|
|
|
# Set context with None password
|
|
set_search_context(
|
|
{
|
|
"username": "test_user",
|
|
"user_password": None,
|
|
}
|
|
)
|
|
|
|
with patch(
|
|
"local_deep_research.database.session_context.has_app_context",
|
|
return_value=False,
|
|
):
|
|
with patch.object(db_manager, "has_encryption", True):
|
|
with pytest.raises(DatabaseSessionError) as exc_info:
|
|
with get_user_db_session("test_user"):
|
|
pass
|
|
|
|
assert "requires password" in str(exc_info.value).lower()
|