Files
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

255 lines
9.0 KiB
Python

"""Tests for thread_settings module."""
import threading
from unittest.mock import MagicMock
import pytest
from local_deep_research.config.thread_settings import (
NoSettingsContextError,
clear_settings_context,
get_setting_from_snapshot,
get_settings_context,
set_settings_context,
settings_context,
_thread_local,
)
class TestNoSettingsContextError:
"""Tests for NoSettingsContextError exception."""
def test_is_exception(self):
"""Should be an Exception subclass."""
assert issubclass(NoSettingsContextError, Exception)
def test_can_be_raised_with_message(self):
"""Should accept error message."""
with pytest.raises(NoSettingsContextError) as exc_info:
raise NoSettingsContextError("test message")
assert "test message" in str(exc_info.value)
class TestSetSettingsContext:
"""Tests for set_settings_context function."""
def test_sets_context_on_thread_local(self, clean_thread_local):
"""Should set settings_context on thread local."""
context = MagicMock()
set_settings_context(context)
assert _thread_local.settings_context is context
def test_overwrites_previous_context(self, clean_thread_local):
"""Should overwrite previous context."""
context1 = MagicMock()
context2 = MagicMock()
set_settings_context(context1)
set_settings_context(context2)
assert _thread_local.settings_context is context2
class TestGetSettingsContext:
"""Tests for get_settings_context function."""
def test_returns_context_when_set(self, clean_thread_local):
"""Should return context when set."""
context = MagicMock()
set_settings_context(context)
assert get_settings_context() is context
def test_returns_none_when_not_set(self, clean_thread_local):
"""Should return None when no context set."""
assert get_settings_context() is None
class TestGetSettingFromSnapshot:
"""Tests for get_setting_from_snapshot function."""
def test_returns_value_from_snapshot(self, clean_thread_local):
"""Should return value from settings_snapshot."""
snapshot = {"test.key": "test_value"}
result = get_setting_from_snapshot(
"test.key", settings_snapshot=snapshot
)
assert result == "test_value"
def test_handles_full_format_value(self, clean_thread_local):
"""Should extract value from full format dict."""
snapshot = {
"test.key": {"value": "extracted_value", "ui_element": "text"}
}
result = get_setting_from_snapshot(
"test.key", settings_snapshot=snapshot
)
assert result == "extracted_value"
def test_returns_default_when_key_not_found(self, clean_thread_local):
"""Should return default when key not in snapshot."""
snapshot = {"other.key": "value"}
result = get_setting_from_snapshot(
"test.key", default="default_val", settings_snapshot=snapshot
)
assert result == "default_val"
def test_builds_dict_from_child_keys(self, clean_thread_local):
"""Should build dict from child keys."""
snapshot = {
"parent.child1": "value1",
"parent.child2": "value2",
}
result = get_setting_from_snapshot("parent", settings_snapshot=snapshot)
assert result == {"child1": "value1", "child2": "value2"}
def test_extracts_value_from_child_key_full_format(
self, clean_thread_local
):
"""Should extract value from child keys with full format."""
snapshot = {
"parent.child": {"value": "extracted", "ui_element": "text"},
}
result = get_setting_from_snapshot("parent", settings_snapshot=snapshot)
assert result == {"child": "extracted"}
def test_uses_thread_context_when_no_snapshot(
self, clean_thread_local, mock_settings_context
):
"""Should use thread-local context when no snapshot."""
set_settings_context(mock_settings_context)
mock_settings_context.get_setting.return_value = "context_value"
result = get_setting_from_snapshot("test.key")
assert result == "context_value"
mock_settings_context.get_setting.assert_called_with("test.key", None)
def test_extracts_value_from_context_dict(
self, clean_thread_local, mock_settings_context
):
"""Should extract value from context dict format."""
set_settings_context(mock_settings_context)
mock_settings_context.get_setting.return_value = {
"value": "extracted_value"
}
result = get_setting_from_snapshot("test.key")
assert result == "extracted_value"
def test_raises_when_no_context_and_no_default(self, clean_thread_local):
"""Should raise NoSettingsContextError when no context and no default."""
with pytest.raises(NoSettingsContextError) as exc_info:
get_setting_from_snapshot("test.key")
assert "test.key" in str(exc_info.value)
def test_returns_default_instead_of_raising(self, clean_thread_local):
"""Should return default instead of raising when provided."""
result = get_setting_from_snapshot("test.key", default="my_default")
assert result == "my_default"
def test_returns_none_when_snapshot_value_is_none(self, clean_thread_local):
"""Key present in snapshot with value None should return None,
not raise NoSettingsContextError. Regression for #4208: the
OpenAI embeddings test endpoint blew up here because
embeddings.openai.dimensions defaults to JSON null."""
snapshot = {
"embeddings.openai.dimensions": {
"value": None,
"ui_element": "number",
}
}
result = get_setting_from_snapshot(
"embeddings.openai.dimensions",
default=None,
settings_snapshot=snapshot,
)
assert result is None
def test_returns_none_when_simplified_snapshot_value_is_none(
self, clean_thread_local
):
"""Simplified-format snapshot (raw value, not {value: ...} dict)
with explicit None should also return None, not raise."""
snapshot = {"some.key": None}
result = get_setting_from_snapshot(
"some.key", default=None, settings_snapshot=snapshot
)
assert result is None
class TestClearSettingsContext:
"""Tests for clear_settings_context function."""
def test_clears_existing_context(self, clean_thread_local):
"""Should clear context when set."""
set_settings_context(MagicMock())
clear_settings_context()
assert get_settings_context() is None
def test_noop_when_no_context(self, clean_thread_local):
"""Should not raise when no context is set."""
clear_settings_context()
class TestSettingsContextManager:
"""Tests for settings_context context manager."""
def test_sets_and_clears_context(self, clean_thread_local):
"""Context should be set inside and cleared after the block."""
ctx = MagicMock()
with settings_context(ctx):
assert get_settings_context() is ctx
assert get_settings_context() is None
def test_clears_on_exception(self, clean_thread_local):
"""Context should be cleared even when an exception occurs."""
ctx = MagicMock()
with pytest.raises(ValueError):
with settings_context(ctx):
raise ValueError("boom")
assert get_settings_context() is None
def test_works_in_thread(self, clean_thread_local):
"""Context manager should work correctly in a separate thread."""
results = []
def worker():
ctx = MagicMock()
ctx.name = "thread-ctx"
with settings_context(ctx):
results.append(get_settings_context().name)
results.append(get_settings_context())
t = threading.Thread(target=worker)
t.start()
t.join()
assert results[0] == "thread-ctx"
assert results[1] is None
class TestThreadIsolation:
"""Tests for thread isolation of settings context."""
def test_contexts_isolated_between_threads(self, clean_thread_local):
"""Should isolate contexts between threads."""
results = {}
def thread_func(name, context_value):
context = MagicMock()
context.get_setting.return_value = context_value
set_settings_context(context)
# Small delay to ensure overlap
import time
time.sleep(0.01)
results[name] = get_settings_context().get_setting("key", None)
thread1 = threading.Thread(target=thread_func, args=("t1", "value1"))
thread2 = threading.Thread(target=thread_func, args=("t2", "value2"))
thread1.start()
thread2.start()
thread1.join()
thread2.join()
assert results["t1"] == "value1"
assert results["t2"] == "value2"