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

324 lines
11 KiB
Python

"""
Protocol-based interface tests for SettingsManager.
These tests define and verify the contract that SettingsManager must fulfill
for consumers like rag_routes.py. Using Python's Protocol (PEP 544) enables:
- Static type checking with mypy to catch wrong imports at development time
- Runtime verification with @runtime_checkable
- Clear documentation of required interface
This prevents regressions like issue #1877 where the wrong SettingsManager
was imported, causing 'object has no attribute' errors at runtime.
"""
from typing import Any, Protocol, runtime_checkable
from unittest.mock import Mock
@runtime_checkable
class RagRoutesSettingsProtocol(Protocol):
"""
Protocol defining the SettingsManager interface required by rag_routes.py.
This protocol specifies the exact methods that rag_routes.py calls on
SettingsManager. Any class used as a SettingsManager in rag_routes.py
MUST implement all these methods.
Usage in rag_routes.py:
- get_setting(): Called throughout for retrieving settings
- get_bool_setting(): Called at lines 133, 2185, 2309 for boolean settings
- get_settings_snapshot(): Called at line 2193 for background thread operations
"""
def get_setting(
self, key: str, default: Any = None, check_env: bool = True
) -> Any:
"""Retrieve a setting value by key."""
...
def get_bool_setting(self, key: str, default: bool = False) -> bool:
"""Retrieve a setting value as a boolean."""
...
def get_settings_snapshot(self) -> dict[str, Any]:
"""Get a snapshot of all settings as a flat dictionary."""
...
class TestSettingsManagerProtocolCompliance:
"""
Tests verifying SettingsManager implements the required protocol.
These tests use Python's @runtime_checkable Protocol to verify that
the SettingsManager class implements all required methods with the
correct signatures.
"""
def test_settings_manager_implements_protocol(self):
"""
Verify settings.manager.SettingsManager implements protocol.
This is the single unified SettingsManager used throughout the app.
"""
from local_deep_research.settings.manager import (
SettingsManager,
)
manager = SettingsManager()
# Runtime protocol check - this verifies the class has all required methods
assert isinstance(manager, RagRoutesSettingsProtocol), (
"settings.manager.SettingsManager must implement "
"RagRoutesSettingsProtocol. Missing methods will cause runtime errors "
"in rag_routes.py. See issue #1877."
)
class TestSettingsManagerImportIdentity:
"""
Tests verifying the correct SettingsManager is imported in rag_routes.py.
These tests check that rag_routes.py imports SettingsManager from
settings.manager (the unified implementation).
"""
def test_rag_routes_imports_correct_settings_manager(self):
"""
Verify rag_routes.py imports SettingsManager from settings.manager.
This test checks the actual imported class identity, ensuring that
rag_routes.py uses the correct SettingsManager that has all required
methods (get_bool_setting, get_settings_snapshot).
"""
from local_deep_research.research_library.routes import rag_routes
from local_deep_research.settings.manager import (
SettingsManager as ExpectedManager,
)
# Get the SettingsManager class that rag_routes is using
# It should be imported at module level or used in functions
assert hasattr(rag_routes, "SettingsManager"), (
"rag_routes.py should have SettingsManager available. "
"Check the import statements."
)
# Verify it's the correct class
assert rag_routes.SettingsManager is ExpectedManager, (
f"rag_routes.py imports SettingsManager from wrong module. "
f"Expected: {ExpectedManager.__module__}.{ExpectedManager.__name__}, "
f"Got: {rag_routes.SettingsManager.__module__}.{rag_routes.SettingsManager.__name__}. "
f"This will cause 'has no attribute' errors. See issue #1877."
)
def test_rag_routes_settings_manager_has_required_methods(self):
"""
Verify the SettingsManager in rag_routes has all methods it calls.
This is a defense-in-depth test that checks the imported class
has all the methods that rag_routes.py actually calls.
"""
from local_deep_research.research_library.routes import rag_routes
manager_class = rag_routes.SettingsManager
manager = manager_class()
# Methods called in rag_routes.py
required_methods = [
("get_setting", True), # (method_name, is_callable)
("get_bool_setting", True),
("get_settings_snapshot", True),
]
for method_name, should_be_callable in required_methods:
assert hasattr(manager, method_name), (
f"rag_routes.py's SettingsManager missing '{method_name}'. "
f"This method is called in rag_routes.py and will cause "
f"AttributeError at runtime. See issue #1877."
)
if should_be_callable:
attr = getattr(manager, method_name)
assert callable(attr), (
f"rag_routes.py's SettingsManager.{method_name} is not callable. "
f"Expected a method, got {type(attr)}."
)
class TestSettingsManagerMethodSignatures:
"""
Tests verifying method signatures match expected usage patterns.
These tests ensure that the methods not only exist but accept the
arguments that rag_routes.py passes to them.
"""
def test_get_bool_setting_accepts_key_and_default(self):
"""
Verify get_bool_setting accepts (key, default) arguments.
rag_routes.py calls: settings.get_bool_setting("key", True)
"""
from local_deep_research.settings.manager import (
SettingsManager,
)
mock_session = Mock()
mock_query = Mock()
mock_query.filter.return_value.all.return_value = []
mock_session.query.return_value = mock_query
manager = SettingsManager(db_session=mock_session)
# This should not raise TypeError
result = manager.get_bool_setting(
"research_library.auto_index_enabled", True
)
assert isinstance(result, bool)
def test_get_settings_snapshot_returns_mutable_dict(self):
"""
Verify get_settings_snapshot returns a dict that can be modified.
rag_routes.py modifies the snapshot: snapshot["_username"] = username
"""
from local_deep_research.settings.manager import (
SettingsManager,
)
mock_session = Mock()
mock_query = Mock()
mock_query.all.return_value = []
mock_session.query.return_value = mock_query
manager = SettingsManager(db_session=mock_session)
snapshot = manager.get_settings_snapshot()
# Should be a dict
assert isinstance(snapshot, dict)
# Should be mutable (rag_routes.py adds keys to it)
snapshot["_username"] = "testuser"
snapshot["_db_password"] = "testpass"
assert snapshot["_username"] == "testuser"
assert snapshot["_db_password"] == "testpass"
def test_get_setting_accepts_check_env_parameter(self):
"""
Verify get_setting accepts check_env parameter.
rag_routes.py calls: settings.get_setting("key", default, check_env=True)
"""
from local_deep_research.settings.manager import (
SettingsManager,
)
mock_session = Mock()
mock_query = Mock()
mock_query.filter.return_value.all.return_value = []
mock_session.query.return_value = mock_query
manager = SettingsManager(db_session=mock_session)
# These should not raise TypeError
result1 = manager.get_setting("some.key", "default", check_env=True)
result2 = manager.get_setting("some.key", "default", check_env=False)
# Should return the default when key not found
assert result1 == "default"
assert result2 == "default"
class TestSettingsManagerHasAllMethods:
"""
Tests verifying that the unified SettingsManager has all required methods.
"""
def test_settings_manager_has_all_methods(self):
"""Verify settings.manager.SettingsManager has all expected methods."""
from local_deep_research.settings.manager import (
SettingsManager,
)
manager = SettingsManager()
expected_methods = [
"get_setting",
"get_bool_setting",
"get_settings_snapshot",
"get_all_settings",
"set_setting",
"delete_setting",
]
for method in expected_methods:
assert hasattr(manager, method), (
f"settings.manager.SettingsManager should have {method}"
)
class TestProtocolUsageInTypeChecking:
"""
Tests demonstrating how to use the protocol for type checking.
These tests show patterns that can be used elsewhere in the codebase
to ensure type safety when working with SettingsManager.
"""
def test_function_accepting_protocol(self):
"""
Demonstrate a function that accepts any SettingsManager implementing protocol.
This pattern can be used in rag_routes.py to ensure type safety:
def get_rag_service(settings: RagRoutesSettingsProtocol) -> RAGService:
enabled = settings.get_bool_setting("auto_index", True)
...
"""
from local_deep_research.settings.manager import (
SettingsManager,
)
def requires_protocol(settings: RagRoutesSettingsProtocol) -> bool:
"""Function that requires the protocol."""
return settings.get_bool_setting("test.key", False)
mock_session = Mock()
mock_query = Mock()
mock_query.filter.return_value.all.return_value = []
mock_session.query.return_value = mock_query
manager = SettingsManager(db_session=mock_session)
# This should work because SettingsManager implements the protocol
result = requires_protocol(manager)
assert isinstance(result, bool)
def test_protocol_enables_mock_verification(self):
"""
Demonstrate using protocol to create properly-shaped mocks.
When mocking SettingsManager, the protocol ensures mocks have
all required methods.
"""
def create_mock_settings() -> RagRoutesSettingsProtocol:
"""Create a mock that satisfies the protocol."""
mock = Mock(spec=RagRoutesSettingsProtocol)
mock.get_setting.return_value = "test_value"
mock.get_bool_setting.return_value = True
mock.get_settings_snapshot.return_value = {"key": "value"}
return mock
mock_settings = create_mock_settings()
# Verify the mock satisfies the protocol
assert hasattr(mock_settings, "get_setting")
assert hasattr(mock_settings, "get_bool_setting")
assert hasattr(mock_settings, "get_settings_snapshot")
# Use the mock
assert mock_settings.get_bool_setting("test", False) is True
assert mock_settings.get_settings_snapshot() == {"key": "value"}