Files
local-deep-research/tests/database/test_encryption_threads.py
LearningCircuit d9bc003094 fix: resolve test failures and fix import paths in test suite
- Bulk fix import paths from src.local_deep_research to local_deep_research
  across 190+ test files
- Fix test_settings_routes.py: update validate_setting tests to use proper
  Setting objects instead of incorrect parameter signature
- Fix test_research_service.py: remove tests for non-existent functions,
  correct mimetype assertions for latex export
- Fix test_app_factory.py: use truly public IP (8.8.8.8) instead of
  TEST-NET reserved range for IP validation test
- Fix test_api_functions.py: correct patch paths for get_user_db_session,
  has_request_context, and get_settings_manager
- Fix test_news_api.py: correct patch paths for database session

Test results: 8592 passing, 175 failing (down from 286), 115 skipped
Remaining failures are test isolation issues that pass individually.
2026-01-21 20:50:48 +01:00

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()