Files
wehub-resource-sync 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
chore: import upstream snapshot with attribution
2026-07-13 13:08:55 +08:00

686 lines
21 KiB
Python

"""
Extended tests for environment settings type classes.
Tests cover:
- IntegerSetting min/max validation and edge cases
- PathSetting path expansion and validation
- EnumSetting case sensitivity and matching
- SecretSetting value hiding
- Base EnvSetting functionality
"""
import os
from pathlib import Path
import pytest
from local_deep_research.settings.env_settings import (
BooleanSetting,
StringSetting,
IntegerSetting,
PathSetting,
SecretSetting,
EnumSetting,
SettingsRegistry,
)
class TestIntegerSettingValidation:
"""Tests for IntegerSetting min/max validation."""
@pytest.fixture(autouse=True)
def clean_env(self):
"""Clean environment before each test."""
original_env = {
k: v for k, v in os.environ.items() if k.startswith("LDR_TEST_")
}
for key in list(os.environ.keys()):
if key.startswith("LDR_TEST_"):
os.environ.pop(key, None)
yield
for key in list(os.environ.keys()):
if key.startswith("LDR_TEST_"):
os.environ.pop(key, None)
for key, value in original_env.items():
os.environ[key] = value
def test_integer_setting_min_value_enforcement(self):
"""Test that values below min raise ValueError."""
setting = IntegerSetting(
key="test.min_value",
description="Test setting",
default=10,
min_value=5,
max_value=100,
)
os.environ["LDR_TEST_MIN_VALUE"] = "3" # Below min
with pytest.raises(ValueError) as exc_info:
setting.get_value()
assert "below minimum" in str(exc_info.value)
def test_integer_setting_max_value_enforcement(self):
"""Test that values above max raise ValueError."""
setting = IntegerSetting(
key="test.max_value",
description="Test setting",
default=10,
min_value=5,
max_value=100,
)
os.environ["LDR_TEST_MAX_VALUE"] = "200" # Above max
with pytest.raises(ValueError) as exc_info:
setting.get_value()
assert "above maximum" in str(exc_info.value)
def test_integer_setting_invalid_value_uses_default(self):
"""Test that non-numeric values fall back to default."""
setting = IntegerSetting(
key="test.invalid", description="Test setting", default=42
)
os.environ["LDR_TEST_INVALID"] = "not_a_number"
result = setting.get_value()
assert result == 42
def test_integer_setting_float_string_truncates(self):
"""Test that float strings are converted to int (truncated)."""
setting = IntegerSetting(
key="test.float", description="Test setting", default=0
)
os.environ["LDR_TEST_FLOAT"] = "3.7"
# This should fail since int("3.7") raises ValueError
# The implementation should handle this as invalid
result = setting.get_value()
# Expect default since float string is invalid for int()
assert result == 0
def test_integer_setting_empty_string_uses_default(self):
"""Test that empty string uses default value."""
setting = IntegerSetting(
key="test.empty", description="Test setting", default=99
)
os.environ["LDR_TEST_EMPTY"] = ""
result = setting.get_value()
# Empty string is invalid, should use default
assert result == 99
def test_integer_setting_valid_value_in_range(self):
"""Test that valid value within range is returned."""
setting = IntegerSetting(
key="test.valid",
description="Test setting",
default=10,
min_value=5,
max_value=100,
)
os.environ["LDR_TEST_VALID"] = "50"
result = setting.get_value()
assert result == 50
def test_integer_setting_boundary_values(self):
"""Test that boundary values are accepted."""
setting = IntegerSetting(
key="test.boundary",
description="Test setting",
default=10,
min_value=5,
max_value=100,
)
# Test min boundary
os.environ["LDR_TEST_BOUNDARY"] = "5"
assert setting.get_value() == 5
# Test max boundary
os.environ["LDR_TEST_BOUNDARY"] = "100"
assert setting.get_value() == 100
def test_integer_setting_negative_value(self):
"""Test that negative values work correctly."""
setting = IntegerSetting(
key="test.negative",
description="Test setting",
default=0,
min_value=-100,
max_value=100,
)
os.environ["LDR_TEST_NEGATIVE"] = "-50"
result = setting.get_value()
assert result == -50
class TestPathSettingValidation:
"""Tests for PathSetting path expansion and validation."""
@pytest.fixture(autouse=True)
def clean_env(self):
"""Clean environment before each test."""
original_env = {
k: v for k, v in os.environ.items() if k.startswith("LDR_TEST_")
}
for key in list(os.environ.keys()):
if key.startswith("LDR_TEST_"):
os.environ.pop(key, None)
yield
for key in list(os.environ.keys()):
if key.startswith("LDR_TEST_"):
os.environ.pop(key, None)
for key, value in original_env.items():
os.environ[key] = value
def test_path_setting_expands_tilde(self):
"""Test that ~/path is expanded to full home path."""
setting = PathSetting(
key="test.tilde_path", description="Test setting", default=None
)
os.environ["LDR_TEST_TILDE_PATH"] = "~/test_dir"
result = setting.get_value()
assert result.startswith(str(Path.home()))
assert "test_dir" in result
def test_path_setting_expands_env_vars(self):
"""Test that $HOME/path is expanded to the correct absolute path."""
setting = PathSetting(
key="test.env_path", description="Test setting", default=None
)
os.environ["LDR_TEST_ENV_PATH"] = "$HOME/test_env_dir"
result = setting.get_value()
expected = str(Path(os.environ["HOME"]) / "test_env_dir")
assert result == expected
def test_path_setting_create_if_missing(self, tmp_path):
"""Test that directory is created when create_if_missing=True."""
test_dir = tmp_path / "new_directory"
setting = PathSetting(
key="test.create_path",
description="Test setting",
default=None,
create_if_missing=True,
)
os.environ["LDR_TEST_CREATE_PATH"] = str(test_dir)
result = setting.get_value()
assert test_dir.exists()
assert result == str(test_dir)
def test_path_setting_must_exist_raises(self, tmp_path):
"""Test that ValueError is raised when path doesn't exist and must_exist=True."""
nonexistent_path = tmp_path / "nonexistent_dir"
setting = PathSetting(
key="test.must_exist",
description="Test setting",
default=None,
must_exist=True,
)
os.environ["LDR_TEST_MUST_EXIST"] = str(nonexistent_path)
with pytest.raises(ValueError) as exc_info:
setting.get_value()
assert "does not exist" in str(exc_info.value)
def test_path_setting_none_returns_none(self):
"""Test that unset path returns None."""
setting = PathSetting(
key="test.unset_path", description="Test setting", default=None
)
# Don't set the env var
result = setting.get_value()
assert result is None
def test_path_setting_absolute_path_unchanged(self, tmp_path):
"""Test that absolute path is returned as-is."""
setting = PathSetting(
key="test.absolute", description="Test setting", default=None
)
os.environ["LDR_TEST_ABSOLUTE"] = str(tmp_path)
result = setting.get_value()
assert result == str(tmp_path)
def test_path_setting_default_value(self):
"""Test that default value is used when env var not set."""
setting = PathSetting(
key="test.default",
description="Test setting",
default="/default/path",
)
result = setting.get_value()
assert "/default/path" in result
class TestEnumSettingValidation:
"""Tests for EnumSetting case sensitivity and matching."""
@pytest.fixture(autouse=True)
def clean_env(self):
"""Clean environment before each test."""
original_env = {
k: v for k, v in os.environ.items() if k.startswith("LDR_TEST_")
}
for key in list(os.environ.keys()):
if key.startswith("LDR_TEST_"):
os.environ.pop(key, None)
yield
for key in list(os.environ.keys()):
if key.startswith("LDR_TEST_"):
os.environ.pop(key, None)
for key, value in original_env.items():
os.environ[key] = value
def test_enum_setting_case_insensitive(self):
"""Test that matching is case-insensitive by default."""
setting = EnumSetting(
key="test.enum_ci",
description="Test setting",
allowed_values={"DEBUG", "INFO", "WARNING"},
default="INFO",
case_sensitive=False,
)
os.environ["LDR_TEST_ENUM_CI"] = "debug"
result = setting.get_value()
assert result == "DEBUG" # Returns canonical form
def test_enum_setting_case_sensitive(self):
"""Test that case-sensitive matching works."""
setting = EnumSetting(
key="test.enum_cs",
description="Test setting",
allowed_values={"DEBUG", "INFO", "WARNING"},
default="INFO",
case_sensitive=True,
)
os.environ["LDR_TEST_ENUM_CS"] = "debug" # lowercase
with pytest.raises(ValueError) as exc_info:
setting.get_value()
assert "not in allowed values" in str(exc_info.value)
def test_enum_setting_canonical_form(self):
"""Test that returned value is in canonical form."""
setting = EnumSetting(
key="test.enum_canon",
description="Test setting",
allowed_values={"WAL", "TRUNCATE", "DELETE"},
default="WAL",
case_sensitive=False,
)
os.environ["LDR_TEST_ENUM_CANON"] = "wal"
result = setting.get_value()
# Should return uppercase canonical form
assert result == "WAL"
def test_enum_setting_invalid_uses_default(self):
"""Test that invalid values return default via registry."""
setting = EnumSetting(
key="test.enum_invalid",
description="Test setting",
allowed_values={"A", "B", "C"},
default="A",
)
os.environ["LDR_TEST_ENUM_INVALID"] = "X" # Not in allowed
# Direct get_value raises, but through registry returns default
with pytest.raises(ValueError):
setting.get_value()
def test_enum_setting_valid_value(self):
"""Test that valid value is accepted."""
setting = EnumSetting(
key="test.enum_valid",
description="Test setting",
allowed_values={"OPTION1", "OPTION2", "OPTION3"},
default="OPTION1",
)
os.environ["LDR_TEST_ENUM_VALID"] = "OPTION2"
result = setting.get_value()
assert result == "OPTION2"
def test_enum_setting_default_when_not_set(self):
"""Test that default is used when env var not set."""
setting = EnumSetting(
key="test.enum_default",
description="Test setting",
allowed_values={"X", "Y", "Z"},
default="Y",
)
result = setting.get_value()
assert result == "Y"
class TestSecretSettingHiding:
"""Tests for SecretSetting value hiding."""
@pytest.fixture(autouse=True)
def clean_env(self):
"""Clean environment before each test."""
original_env = {
k: v for k, v in os.environ.items() if k.startswith("LDR_TEST_")
}
for key in list(os.environ.keys()):
if key.startswith("LDR_TEST_"):
os.environ.pop(key, None)
yield
for key in list(os.environ.keys()):
if key.startswith("LDR_TEST_"):
os.environ.pop(key, None)
for key, value in original_env.items():
os.environ[key] = value
def test_secret_setting_repr_hides_value(self):
"""Test that repr() hides the actual value."""
setting = SecretSetting(
key="test.secret_repr", description="Test setting", default=None
)
os.environ["LDR_TEST_SECRET_REPR"] = "super_secret_value"
repr_str = repr(setting)
assert "super_secret_value" not in repr_str
assert "***" in repr_str
def test_secret_setting_str_hides_value(self):
"""Test that str() hides the actual value."""
setting = SecretSetting(
key="test.secret_str", description="Test setting", default=None
)
os.environ["LDR_TEST_SECRET_STR"] = "super_secret_value"
str_result = str(setting)
assert "super_secret_value" not in str_result
assert "SET" in str_result
def test_secret_setting_get_value_returns_actual(self):
"""Test that get_value() returns the actual secret."""
setting = SecretSetting(
key="test.secret_get", description="Test setting", default=None
)
os.environ["LDR_TEST_SECRET_GET"] = "actual_secret_value"
result = setting.get_value()
assert result == "actual_secret_value"
def test_secret_setting_unset_shows_not_set(self):
"""Test that str() shows NOT SET when unset."""
setting = SecretSetting(
key="test.secret_unset", description="Test setting", default=None
)
str_result = str(setting)
assert "NOT SET" in str_result
class TestBaseEnvSetting:
"""Tests for base EnvSetting functionality."""
@pytest.fixture(autouse=True)
def clean_env(self):
"""Clean environment before each test."""
original_env = {
k: v for k, v in os.environ.items() if k.startswith("LDR_TEST_")
}
for key in list(os.environ.keys()):
if key.startswith("LDR_TEST_"):
os.environ.pop(key, None)
yield
for key in list(os.environ.keys()):
if key.startswith("LDR_TEST_"):
os.environ.pop(key, None)
for key, value in original_env.items():
os.environ[key] = value
def test_env_setting_env_var_auto_generation(self):
"""Test that env_var is auto-generated from key."""
setting = BooleanSetting(
key="test.nested.setting", description="Test setting", default=False
)
assert setting.env_var == "LDR_TEST_NESTED_SETTING"
def test_env_setting_is_set_property_true(self):
"""Test is_set returns True when env var is set."""
setting = BooleanSetting(
key="test.is_set", description="Test setting", default=False
)
os.environ["LDR_TEST_IS_SET"] = "true"
assert setting.is_set is True
def test_env_setting_is_set_property_false(self):
"""Test is_set returns False when env var is not set."""
setting = BooleanSetting(
key="test.not_set", description="Test setting", default=False
)
assert setting.is_set is False
def test_env_setting_required_raises_when_missing(self):
"""Test that required setting raises when not set."""
setting = StringSetting(
key="test.required",
description="Test setting",
default=None,
required=True,
)
with pytest.raises(ValueError) as exc_info:
setting.get_value()
assert "Required environment variable" in str(exc_info.value)
def test_env_setting_repr(self):
"""Test __repr__ method."""
setting = BooleanSetting(
key="test.repr", description="Test setting", default=False
)
repr_str = repr(setting)
assert "BooleanSetting" in repr_str
assert "test.repr" in repr_str
assert "LDR_TEST_REPR" in repr_str
class TestBooleanSettingConversion:
"""Tests for BooleanSetting value conversion."""
@pytest.fixture(autouse=True)
def clean_env(self):
"""Clean environment before each test."""
original_env = {
k: v for k, v in os.environ.items() if k.startswith("LDR_TEST_")
}
for key in list(os.environ.keys()):
if key.startswith("LDR_TEST_"):
os.environ.pop(key, None)
yield
for key in list(os.environ.keys()):
if key.startswith("LDR_TEST_"):
os.environ.pop(key, None)
for key, value in original_env.items():
os.environ[key] = value
@pytest.mark.parametrize(
"value", ["true", "True", "TRUE", "1", "yes", "on", "enabled"]
)
def test_boolean_setting_truthy_values(self, value):
"""Test that truthy values convert to True."""
setting = BooleanSetting(
key="test.bool", description="Test setting", default=False
)
os.environ["LDR_TEST_BOOL"] = value
assert setting.get_value() is True
@pytest.mark.parametrize(
"value", ["false", "False", "FALSE", "0", "no", "off", ""]
)
def test_boolean_setting_falsy_values(self, value):
"""Test that falsy values convert to False."""
setting = BooleanSetting(
key="test.bool_false", description="Test setting", default=True
)
os.environ["LDR_TEST_BOOL_FALSE"] = value
assert setting.get_value() is False
class TestSettingsRegistryExtended:
"""Extended tests for SettingsRegistry functionality."""
@pytest.fixture(autouse=True)
def clean_env(self):
"""Clean environment before each test."""
original_env = {
k: v for k, v in os.environ.items() if k.startswith("LDR_TEST_")
}
for key in list(os.environ.keys()):
if key.startswith("LDR_TEST_"):
os.environ.pop(key, None)
yield
for key in list(os.environ.keys()):
if key.startswith("LDR_TEST_"):
os.environ.pop(key, None)
for key, value in original_env.items():
os.environ[key] = value
def test_registry_register_category(self):
"""Test registering a category of settings."""
registry = SettingsRegistry()
settings = [
BooleanSetting("cat1.setting1", "Test 1", default=False),
BooleanSetting("cat1.setting2", "Test 2", default=True),
]
registry.register_category("cat1", settings)
assert "cat1.setting1" in registry.list_all_settings()
assert "cat1.setting2" in registry.list_all_settings()
def test_registry_get_returns_default_for_unknown(self):
"""Test that get() returns default for unknown keys."""
registry = SettingsRegistry()
result = registry.get("unknown.key", default="fallback")
assert result == "fallback"
def test_registry_get_setting_object(self):
"""Test get_setting_object returns the EnvSetting instance."""
registry = SettingsRegistry()
setting = BooleanSetting("test.obj", "Test", default=False)
registry.register_category("test", [setting])
result = registry.get_setting_object("test.obj")
assert result is setting
def test_registry_is_env_only(self):
"""Test is_env_only returns correct values."""
registry = SettingsRegistry()
setting = BooleanSetting("test.env_only", "Test", default=False)
registry.register_category("test", [setting])
assert registry.is_env_only("test.env_only") is True
assert registry.is_env_only("unknown.key") is False
def test_registry_get_env_var(self):
"""Test get_env_var returns correct env var name."""
registry = SettingsRegistry()
setting = BooleanSetting("test.env_var", "Test", default=False)
registry.register_category("test", [setting])
result = registry.get_env_var("test.env_var")
assert result == "LDR_TEST_ENV_VAR"
def test_registry_get_all_env_vars(self):
"""Test get_all_env_vars returns all registered env vars."""
registry = SettingsRegistry()
settings = [
BooleanSetting("test.s1", "Desc 1", default=False),
StringSetting("test.s2", "Desc 2", default="val"),
]
registry.register_category("test", settings)
result = registry.get_all_env_vars()
assert "LDR_TEST_S1" in result
assert "LDR_TEST_S2" in result
assert result["LDR_TEST_S1"] == "Desc 1"
def test_registry_list_all_settings(self):
"""Test list_all_settings returns all registered keys."""
registry = SettingsRegistry()
settings = [
BooleanSetting("test.a", "A", default=False),
BooleanSetting("test.b", "B", default=False),
]
registry.register_category("test", settings)
result = registry.list_all_settings()
assert "test.a" in result
assert "test.b" in result
assert len(result) == 2