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

444 lines
16 KiB
Python

"""
Comprehensive tests for settings logger module.
Tests cover:
- log_settings function with all log levels
- redact_sensitive_keys function
- create_settings_summary function
- get_settings_log_level function
"""
import os
import pytest
from unittest.mock import patch
# Import the functions we want to test
# We need to patch the module-level SETTINGS_LOG_LEVEL before importing
# So we'll import the module itself and access functions through it
@pytest.fixture(autouse=True)
def _restore_settings_logger_module():
"""Restore the pre-test module state after each test.
The tests reload settings.logger under a patched LDR_LOG_SETTINGS to
rebuild SETTINGS_LOG_LEVEL; without a restore, the last test's level
(e.g. "debug") would stay in the module for the rest of the process,
silently defeating the quiet-by-default contract for later tests.
"""
from local_deep_research.settings import logger as settings_logger
snapshot = dict(settings_logger.__dict__)
yield
settings_logger.__dict__.clear()
settings_logger.__dict__.update(snapshot)
class TestLogSettingsNoneLevel:
"""Tests for log_settings with none level."""
def test_log_settings_none_level_skips_logging(self):
"""Test that log_settings does nothing when level is 'none'."""
with patch.dict(os.environ, {"LDR_LOG_SETTINGS": "none"}):
# Re-import to get new SETTINGS_LOG_LEVEL
import importlib
from local_deep_research.settings import logger as settings_logger
importlib.reload(settings_logger)
with patch.object(settings_logger.logger, "info") as mock_info:
with patch.object(
settings_logger.logger, "debug"
) as mock_debug:
settings_logger.log_settings({"key": "value"})
mock_info.assert_not_called()
mock_debug.assert_not_called()
class TestLogSettingsSummaryLevel:
"""Tests for log_settings with summary level."""
def test_log_settings_summary_level(self):
"""Test that log_settings outputs summary at INFO level."""
with patch.dict(os.environ, {"LDR_LOG_SETTINGS": "summary"}):
import importlib
from local_deep_research.settings import logger as settings_logger
importlib.reload(settings_logger)
with patch.object(settings_logger.logger, "info") as mock_info:
settings_logger.log_settings(
{"key": "value"}, message="Test message"
)
mock_info.assert_called_once()
call_args = mock_info.call_args[0][0]
assert "Test message" in call_args
class TestLogSettingsDebugLevel:
"""Tests for log_settings with debug level."""
def test_log_settings_debug_level(self):
"""Test that log_settings outputs full settings with redaction at DEBUG level."""
with patch.dict(os.environ, {"LDR_LOG_SETTINGS": "debug"}):
import importlib
from local_deep_research.settings import logger as settings_logger
importlib.reload(settings_logger)
with patch.object(settings_logger.logger, "debug") as mock_debug:
settings_logger.log_settings(
{"api_key": "secret123", "normal": "value"},
message="Test message",
)
mock_debug.assert_called_once()
call_args = mock_debug.call_args[0][0]
assert "redacted" in call_args.lower()
class TestLogSettingsDebugUnsafeLevel:
"""Tests for debug_unsafe deprecation — maps to 'none'."""
def test_debug_unsafe_maps_to_none(self):
"""Test that debug_unsafe is deprecated and maps to 'none'."""
import warnings
with patch.dict(os.environ, {"LDR_LOG_SETTINGS": "debug_unsafe"}):
import importlib
from local_deep_research.settings import logger as settings_logger
with warnings.catch_warnings(record=True) as w:
warnings.simplefilter("always")
importlib.reload(settings_logger)
# Should have emitted a DeprecationWarning
deprecation_warnings = [
x for x in w if issubclass(x.category, DeprecationWarning)
]
assert len(deprecation_warnings) >= 1
assert (
"deprecated" in str(deprecation_warnings[0].message).lower()
)
assert settings_logger.get_settings_log_level() == "none"
@pytest.mark.parametrize("env_value", ["unsafe", "raw"])
def test_unsafe_aliases_map_to_none(self, env_value):
"""Test that 'unsafe' and 'raw' also map to 'none'."""
import warnings
with patch.dict(os.environ, {"LDR_LOG_SETTINGS": env_value}):
import importlib
from local_deep_research.settings import logger as settings_logger
with warnings.catch_warnings(record=True):
warnings.simplefilter("always")
importlib.reload(settings_logger)
assert settings_logger.get_settings_log_level() == "none"
def test_debug_unsafe_does_not_log_secrets(self):
"""Test that debug_unsafe no longer logs unredacted settings."""
import warnings
with patch.dict(os.environ, {"LDR_LOG_SETTINGS": "debug_unsafe"}):
import importlib
from local_deep_research.settings import logger as settings_logger
with warnings.catch_warnings(record=True):
warnings.simplefilter("always")
importlib.reload(settings_logger)
with patch.object(settings_logger.logger, "debug") as mock_debug:
settings_logger.log_settings(
{"api_key": "secret123"}, message="Test message"
)
# Should NOT have logged anything (level is "none")
mock_debug.assert_not_called()
class TestLogSettingsForcedLevel:
"""Tests for log_settings with forced level override."""
def test_log_settings_force_level_overrides_env(self):
"""Test that force_level parameter overrides environment setting."""
with patch.dict(os.environ, {"LDR_LOG_SETTINGS": "none"}):
import importlib
from local_deep_research.settings import logger as settings_logger
importlib.reload(settings_logger)
with patch.object(settings_logger.logger, "info") as mock_info:
settings_logger.log_settings(
{"key": "value"}, force_level="summary"
)
# Should have logged despite env being 'none'
mock_info.assert_called_once()
class TestLogSettingsEdgeCases:
"""Tests for edge cases in log_settings."""
def test_log_settings_with_empty_settings(self):
"""Test log_settings handles empty dict gracefully."""
with patch.dict(os.environ, {"LDR_LOG_SETTINGS": "summary"}):
import importlib
from local_deep_research.settings import logger as settings_logger
importlib.reload(settings_logger)
with patch.object(settings_logger.logger, "info") as mock_info:
# Should not raise
settings_logger.log_settings({})
mock_info.assert_called_once()
call_args = mock_info.call_args[0][0]
assert "0 total settings" in call_args
def test_log_settings_with_non_dict_settings(self):
"""Test log_settings handles non-dict input."""
with patch.dict(os.environ, {"LDR_LOG_SETTINGS": "summary"}):
import importlib
from local_deep_research.settings import logger as settings_logger
importlib.reload(settings_logger)
with patch.object(settings_logger.logger, "info") as mock_info:
# Should not raise
settings_logger.log_settings("not a dict")
mock_info.assert_called_once()
class TestRedactSensitiveKeys:
"""Tests for redact_sensitive_keys function."""
@pytest.fixture(autouse=True)
def setup_module(self):
"""Import the module for tests."""
with patch.dict(os.environ, {"LDR_LOG_SETTINGS": "none"}):
import importlib
from local_deep_research.settings import logger as settings_logger
importlib.reload(settings_logger)
self.settings_logger = settings_logger
def test_redact_api_key_pattern(self):
"""Test that 'api_key' pattern is redacted."""
result = self.settings_logger.redact_sensitive_keys(
{"api_key": "secret123"}
)
assert result["api_key"] == "***REDACTED***"
def test_redact_password_pattern(self):
"""Test that 'password' pattern is redacted."""
result = self.settings_logger.redact_sensitive_keys(
{"password": "mypassword", "db_password": "dbpass"}
)
assert result["password"] == "***REDACTED***"
assert result["db_password"] == "***REDACTED***"
def test_redact_token_pattern(self):
"""Test that 'token' pattern is redacted."""
result = self.settings_logger.redact_sensitive_keys(
{"token": "abc123", "access_token": "xyz789"}
)
assert result["token"] == "***REDACTED***"
assert result["access_token"] == "***REDACTED***"
def test_redact_secret_pattern(self):
"""Test that 'secret' pattern is redacted."""
result = self.settings_logger.redact_sensitive_keys(
{"secret": "shh", "secret_key": "shhh"}
)
assert result["secret"] == "***REDACTED***"
assert result["secret_key"] == "***REDACTED***"
def test_redact_nested_sensitive_keys(self):
"""Test that nested sensitive keys are redacted."""
result = self.settings_logger.redact_sensitive_keys(
{"outer": {"api_key": "nested_secret", "normal": "value"}}
)
assert result["outer"]["api_key"] == "***REDACTED***"
assert result["outer"]["normal"] == "value"
def test_redact_preserves_non_sensitive(self):
"""Test that non-sensitive keys are preserved."""
result = self.settings_logger.redact_sensitive_keys(
{"app_name": "MyApp", "debug": True, "count": 42}
)
assert result["app_name"] == "MyApp"
assert result["debug"] is True
assert result["count"] == 42
def test_redact_setting_dict_with_value_key(self):
"""Test redaction of settings format with 'value' key."""
result = self.settings_logger.redact_sensitive_keys(
{"api_key": {"value": "secret", "type": "string"}}
)
assert result["api_key"]["value"] == "***REDACTED***"
assert result["api_key"]["type"] == "string"
class TestCreateSettingsSummary:
"""Tests for create_settings_summary function."""
@pytest.fixture(autouse=True)
def setup_module(self):
"""Import the module for tests."""
with patch.dict(os.environ, {"LDR_LOG_SETTINGS": "none"}):
import importlib
from local_deep_research.settings import logger as settings_logger
importlib.reload(settings_logger)
self.settings_logger = settings_logger
def test_create_settings_summary_counts(self):
"""Test that summary correctly counts different setting types."""
result = self.settings_logger.create_settings_summary(
{
"search.engine.google": True,
"search.engine.bing": True,
"llm.provider": "openai",
"llm.temperature": 0.7,
"app.debug": False,
}
)
assert "5 total settings" in result
assert "search engines: 2" in result
assert "LLM: 2" in result
def test_create_settings_summary_empty_dict(self):
"""Test summary of empty settings."""
result = self.settings_logger.create_settings_summary({})
assert "0 total settings" in result
def test_create_settings_summary_non_dict(self):
"""Test summary of non-dict input."""
result = self.settings_logger.create_settings_summary("string_settings")
assert "str" in result
def test_create_settings_summary_with_object(self):
"""Test summary with object input."""
class CustomSettings:
pass
result = self.settings_logger.create_settings_summary(CustomSettings())
assert "CustomSettings" in result
class TestGetSettingsLogLevel:
"""Tests for get_settings_log_level function."""
def test_get_settings_log_level_returns_current(self):
"""Test that get_settings_log_level returns current level."""
with patch.dict(os.environ, {"LDR_LOG_SETTINGS": "debug"}):
import importlib
from local_deep_research.settings import logger as settings_logger
importlib.reload(settings_logger)
result = settings_logger.get_settings_log_level()
assert result == "debug"
def test_get_settings_log_level_default(self):
"""Test that get_settings_log_level returns 'none' by default."""
# Remove the env var if set
env = os.environ.copy()
env.pop("LDR_LOG_SETTINGS", None)
with patch.dict(os.environ, env, clear=True):
import importlib
from local_deep_research.settings import logger as settings_logger
importlib.reload(settings_logger)
result = settings_logger.get_settings_log_level()
assert result == "none"
class TestLogLevelMapping:
"""Tests for log level value mapping."""
@pytest.mark.parametrize(
"env_value,expected",
[
("false", "none"),
("0", "none"),
("no", "none"),
("off", "none"),
("none", "none"),
("true", "summary"),
("1", "summary"),
("yes", "summary"),
("info", "summary"),
("summary", "summary"),
("debug", "debug"),
("full", "debug"),
("all", "debug"),
("debug_unsafe", "none"),
("unsafe", "none"),
("raw", "none"),
("invalid_value", "none"), # Invalid defaults to none
],
)
def test_log_level_mapping(self, env_value, expected):
"""Test that various env values map to correct log levels."""
import warnings
with patch.dict(os.environ, {"LDR_LOG_SETTINGS": env_value}):
import importlib
from local_deep_research.settings import logger as settings_logger
with warnings.catch_warnings():
warnings.simplefilter("ignore", DeprecationWarning)
importlib.reload(settings_logger)
result = settings_logger.get_settings_log_level()
assert result == expected, (
f"Expected {env_value} to map to {expected}, got {result}"
)
class TestRedactCaseSensitivity:
"""Tests for case sensitivity in redaction."""
@pytest.fixture(autouse=True)
def setup_module(self):
"""Import the module for tests."""
with patch.dict(os.environ, {"LDR_LOG_SETTINGS": "none"}):
import importlib
from local_deep_research.settings import logger as settings_logger
importlib.reload(settings_logger)
self.settings_logger = settings_logger
def test_redact_case_insensitive_api_key(self):
"""Test that API_KEY (uppercase) is also redacted."""
result = self.settings_logger.redact_sensitive_keys(
{"API_KEY": "secret", "Api_Key": "secret2"}
)
assert result["API_KEY"] == "***REDACTED***"
assert result["Api_Key"] == "***REDACTED***"
def test_redact_case_insensitive_password(self):
"""Test that PASSWORD (uppercase) is also redacted."""
result = self.settings_logger.redact_sensitive_keys(
{"PASSWORD": "secret", "Password": "secret2"}
)
assert result["PASSWORD"] == "***REDACTED***"
assert result["Password"] == "***REDACTED***"