Files
ahtlon 9810a4f810 Just had a bunch of problems with tests being polluted by local .env
This should fix that.
test_settings.py written by qwen3.8
Assisted-by: local-qwen3.8 <localllm@ahtlon.de>
2026-11-05 22:45:23 +01:00

94 lines
3.3 KiB
Python

"""Checks that the suite never resolves settings from .env or the ambient OS env."""
import json
import os
from app.services.settings import Settings, settings
from test.conftest import TEST_ENV
# Values a developer's .env / shell would plausibly contribute.
HOSTILE_ENV = {
"MOCK": "False",
"DISABLE_CALDAV": "False",
"DISABLE_CARDS": "False",
"CALDAV_URL": "https://caldav.example.com/dav.php",
"CALDAV_USER": "real-user",
"CALDAV_PASSWORD": "real-password",
"CALDAV_CALENDARS": '["Family"]',
"CALDAV_REFRESH_INTERVAL": "1",
"MIFARE_APP_MASTER_KEY": "a" * 16,
"MIFARE_ACL_READ_BASE_KEY": "b" * 16,
"MIFARE_ACL_WRITE_BASE_KEY": "c" * 16,
"SQLALCHEMY_DATABASE_URL": "postgresql://db.example.com:5432/gatekeeper",
"MQTT_HOST": "mqtt.example.com",
"SECRET_KEY": "leaked-secret",
}
def test_every_settings_field_is_pinned_by_the_test_env():
ambient = {name.upper() for name in Settings.model_fields}
missing = sorted(ambient - set(TEST_ENV))
assert not missing, (
f"Settings fields not pinned by conftest.TEST_ENV: {missing} — their "
"value could come from .env or the OS environment"
)
stale = [name for name in TEST_ENV if name not in ambient]
assert not stale, f"TEST_ENV names that are no longer Settings fields: {stale}"
def test_ambient_env_and_dotenv_could_not_supply_any_value():
"""No field is left to .env: each one has a pinned value in os.environ."""
ambient = {name.upper() for name in Settings.model_fields}
unpinned = sorted(ambient - set(TEST_ENV))
assert not unpinned, f"unpinned Settings fields: {unpinned}"
for name, expected in TEST_ENV.items():
assert os.environ.get(name) == expected, (
f"conftest did not pin {name} (os.environ={os.environ.get(name)!r})"
)
for name, hostile in HOSTILE_ENV.items():
leaked = os.environ.get(name)
assert leaked != hostile, f"{name} leaked from the ambient env ({leaked!r})"
def test_shared_settings_come_from_the_test_env():
for name, expected in TEST_ENV.items():
actual = getattr(settings, name.lower())
assert _matches(actual, expected), (
f"{name}: settings={actual!r} != test value {expected!r}"
)
def _matches(actual, expected: str) -> bool:
"""Compare a pydantic-parsed value against the raw string pinned in TEST_ENV."""
if str(actual) == expected:
return True
try:
return actual == json.loads(expected)
except json.JSONDecodeError:
return False
def test_env_vars_outrank_env_file(monkeypatch):
"""Why the scrub matters: an injected env value wins over TEST_ENV/.env.
Scoped to this test (monkeypatch restores os.environ on teardown), which is how
a test that needs other settings must do it.
"""
for name, value in HOSTILE_ENV.items():
monkeypatch.setenv(name, value)
leaked = Settings()
assert leaked.mock is False
assert leaked.disable_caldav is False
assert leaked.sqlalchemy_database_url.startswith("postgresql://")
assert leaked.mqtt_host == "mqtt.example.com"
# The shared settings object the rest of the suite uses is untouched.
assert settings.mock is True
assert settings.disable_caldav is True
assert settings.disable_cards is True