bank-fraud / tests /unit /test_config.py
root
init
942b115
Raw
History Blame Contribute Delete
5.07 kB
"""Unit tests for app.config (SETUP-03).
These tests exercise `app.config.load_settings()` directly, using only
monkeypatched (fake) environment values -- never the project's real `.env`
or real secrets.
"""
from __future__ import annotations
import pytest
from app.config import ConfigError, load_settings
VALID_ENV = {
"DB_HOST": "test-db-host.invalid",
"DB_PORT": "3306",
"DB_NAME": "test_db",
"DB_USER": "test_user",
"DB_PASSWORD": "test-password-not-real",
"DATABASE_URL": (
"mysql+pymysql://test_user:test-password-not-real"
"@test-db-host.invalid:3306/test_db"
),
"APP_PORT": "8000",
"ALLOWED_ORIGINS": "https://example.test,https://other.example.test",
"FRAUD_ALERT_THRESHOLD": "0.5",
"MODEL_PATH": "training/artifacts/model_v1.pkl",
"SEED_OFFICER_EMAIL": "officer@example.test",
"SEED_OFFICER_PASSWORD": "fake-seed-password-not-real",
"SESSION_EXPIRE_MINUTES": "1440",
"KAGGLE_USERNAME": "fake-kaggle-user",
"KAGGLE_KEY": "fake-kaggle-key-not-real",
}
@pytest.fixture
def valid_env(monkeypatch: pytest.MonkeyPatch) -> None:
"""Set a complete, fully-fake, valid environment for each test."""
for key, value in VALID_ENV.items():
monkeypatch.setenv(key, value)
def test_complete_env_yields_typed_settings(valid_env: None) -> None:
settings = load_settings()
assert settings.DB_HOST == VALID_ENV["DB_HOST"]
assert settings.DB_PORT == 3306
assert isinstance(settings.DB_PORT, int)
assert settings.DB_NAME == VALID_ENV["DB_NAME"]
assert settings.DB_USER == VALID_ENV["DB_USER"]
assert settings.DB_PASSWORD == VALID_ENV["DB_PASSWORD"]
assert settings.DATABASE_URL == VALID_ENV["DATABASE_URL"]
assert settings.APP_PORT == 8000
assert isinstance(settings.APP_PORT, int)
assert settings.ALLOWED_ORIGINS == [
"https://example.test",
"https://other.example.test",
]
assert settings.FRAUD_ALERT_THRESHOLD == pytest.approx(0.5)
assert isinstance(settings.FRAUD_ALERT_THRESHOLD, float)
assert settings.MODEL_PATH == VALID_ENV["MODEL_PATH"]
assert settings.SEED_OFFICER_EMAIL == VALID_ENV["SEED_OFFICER_EMAIL"]
assert settings.SEED_OFFICER_PASSWORD == VALID_ENV["SEED_OFFICER_PASSWORD"]
assert settings.SESSION_EXPIRE_MINUTES == 1440
assert isinstance(settings.SESSION_EXPIRE_MINUTES, int)
assert settings.KAGGLE_USERNAME == VALID_ENV["KAGGLE_USERNAME"]
assert settings.KAGGLE_KEY == VALID_ENV["KAGGLE_KEY"]
@pytest.mark.parametrize(
"missing_var",
[
"DB_HOST",
"DB_NAME",
"DB_USER",
"DB_PASSWORD",
"DATABASE_URL",
"ALLOWED_ORIGINS",
"FRAUD_ALERT_THRESHOLD",
"MODEL_PATH",
"DB_PORT",
"SEED_OFFICER_EMAIL",
"SEED_OFFICER_PASSWORD",
],
)
def test_missing_required_var_raises_config_error_naming_the_variable(
valid_env: None, monkeypatch: pytest.MonkeyPatch, missing_var: str
) -> None:
monkeypatch.delenv(missing_var, raising=False)
with pytest.raises(ConfigError) as exc_info:
load_settings()
message = str(exc_info.value)
assert missing_var in message
# Never echo secret-ish values in the error message.
assert VALID_ENV["DB_PASSWORD"] not in message
assert VALID_ENV["KAGGLE_KEY"] not in message
def test_out_of_range_fraud_alert_threshold_raises_config_error(
valid_env: None, monkeypatch: pytest.MonkeyPatch
) -> None:
monkeypatch.setenv("FRAUD_ALERT_THRESHOLD", "1.5")
with pytest.raises(ConfigError) as exc_info:
load_settings()
assert "FRAUD_ALERT_THRESHOLD" in str(exc_info.value)
def test_non_numeric_fraud_alert_threshold_raises_config_error(
valid_env: None, monkeypatch: pytest.MonkeyPatch
) -> None:
monkeypatch.setenv("FRAUD_ALERT_THRESHOLD", "not-a-number")
with pytest.raises(ConfigError) as exc_info:
load_settings()
assert "FRAUD_ALERT_THRESHOLD" in str(exc_info.value)
def test_unparseable_db_port_raises_config_error(
valid_env: None, monkeypatch: pytest.MonkeyPatch
) -> None:
monkeypatch.setenv("DB_PORT", "not-an-int")
with pytest.raises(ConfigError) as exc_info:
load_settings()
assert "DB_PORT" in str(exc_info.value)
def test_missing_app_port_falls_back_to_default(
valid_env: None, monkeypatch: pytest.MonkeyPatch
) -> None:
monkeypatch.delenv("APP_PORT", raising=False)
settings = load_settings()
assert settings.APP_PORT == 8000
def test_unparseable_app_port_raises_config_error(
valid_env: None, monkeypatch: pytest.MonkeyPatch
) -> None:
monkeypatch.setenv("APP_PORT", "not-an-int")
with pytest.raises(ConfigError) as exc_info:
load_settings()
assert "APP_PORT" in str(exc_info.value)
def test_missing_session_expire_minutes_falls_back_to_default(
valid_env: None, monkeypatch: pytest.MonkeyPatch
) -> None:
monkeypatch.delenv("SESSION_EXPIRE_MINUTES", raising=False)
settings = load_settings()
assert settings.SESSION_EXPIRE_MINUTES == 1440