"""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