""" Unit tests for configuration module. Tests environment variable loading, validation, and error handling. """ import os import pytest from gondulf.config import Config, ConfigurationError class TestConfigLoad: """Tests for Config.load() method.""" def test_load_with_valid_secret_key(self, monkeypatch): """Test configuration loads successfully with valid SECRET_KEY.""" monkeypatch.setenv("GONDULF_SECRET_KEY", "a" * 32) monkeypatch.setenv("GONDULF_BASE_URL", "https://auth.example.com") Config.load() assert Config.SECRET_KEY == "a" * 32 def test_load_missing_secret_key_raises_error(self, monkeypatch): """Test that missing SECRET_KEY raises ConfigurationError.""" monkeypatch.delenv("GONDULF_SECRET_KEY", raising=False) with pytest.raises(ConfigurationError, match="GONDULF_SECRET_KEY is required"): Config.load() def test_load_short_secret_key_raises_error(self, monkeypatch): """Test that SECRET_KEY shorter than 32 chars raises error.""" monkeypatch.setenv("GONDULF_SECRET_KEY", "short") monkeypatch.setenv("GONDULF_BASE_URL", "https://auth.example.com") with pytest.raises(ConfigurationError, match="at least 32 characters"): Config.load() def test_load_database_url_default(self, monkeypatch): """Test DATABASE_URL defaults to sqlite:///./data/gondulf.db.""" monkeypatch.setenv("GONDULF_SECRET_KEY", "a" * 32) monkeypatch.setenv("GONDULF_BASE_URL", "https://auth.example.com") monkeypatch.delenv("GONDULF_DATABASE_URL", raising=False) Config.load() assert Config.DATABASE_URL == "sqlite:///./data/gondulf.db" def test_load_database_url_custom(self, monkeypatch): """Test DATABASE_URL can be customized.""" monkeypatch.setenv("GONDULF_SECRET_KEY", "a" * 32) monkeypatch.setenv("GONDULF_BASE_URL", "https://auth.example.com") monkeypatch.setenv("GONDULF_DATABASE_URL", "sqlite:////tmp/test.db") Config.load() assert Config.DATABASE_URL == "sqlite:////tmp/test.db" def test_load_smtp_configuration_defaults(self, monkeypatch): """Test SMTP configuration uses sensible defaults.""" monkeypatch.setenv("GONDULF_SECRET_KEY", "a" * 32) monkeypatch.setenv("GONDULF_BASE_URL", "https://auth.example.com") for key in [ "GONDULF_SMTP_HOST", "GONDULF_SMTP_PORT", "GONDULF_SMTP_USERNAME", "GONDULF_SMTP_PASSWORD", "GONDULF_SMTP_FROM", "GONDULF_SMTP_USE_TLS", ]: monkeypatch.delenv(key, raising=False) Config.load() assert Config.SMTP_HOST == "localhost" assert Config.SMTP_PORT == 587 assert Config.SMTP_USERNAME is None assert Config.SMTP_PASSWORD is None assert Config.SMTP_FROM == "noreply@example.com" assert Config.SMTP_USE_TLS is True def test_load_smtp_configuration_custom(self, monkeypatch): """Test SMTP configuration can be customized.""" monkeypatch.setenv("GONDULF_SECRET_KEY", "a" * 32) monkeypatch.setenv("GONDULF_BASE_URL", "https://auth.example.com") monkeypatch.setenv("GONDULF_SMTP_HOST", "smtp.gmail.com") monkeypatch.setenv("GONDULF_SMTP_PORT", "465") monkeypatch.setenv("GONDULF_SMTP_USERNAME", "user@gmail.com") monkeypatch.setenv("GONDULF_SMTP_PASSWORD", "password123") monkeypatch.setenv("GONDULF_SMTP_FROM", "sender@example.com") monkeypatch.setenv("GONDULF_SMTP_USE_TLS", "false") Config.load() assert Config.SMTP_HOST == "smtp.gmail.com" assert Config.SMTP_PORT == 465 assert Config.SMTP_USERNAME == "user@gmail.com" assert Config.SMTP_PASSWORD == "password123" assert Config.SMTP_FROM == "sender@example.com" assert Config.SMTP_USE_TLS is False def test_load_token_expiry_default(self, monkeypatch): """Test TOKEN_EXPIRY defaults to 3600 seconds.""" monkeypatch.setenv("GONDULF_SECRET_KEY", "a" * 32) monkeypatch.setenv("GONDULF_BASE_URL", "https://auth.example.com") monkeypatch.delenv("GONDULF_TOKEN_EXPIRY", raising=False) Config.load() assert Config.TOKEN_EXPIRY == 3600 def test_load_code_expiry_default(self, monkeypatch): """Test CODE_EXPIRY defaults to 600 seconds.""" monkeypatch.setenv("GONDULF_SECRET_KEY", "a" * 32) monkeypatch.setenv("GONDULF_BASE_URL", "https://auth.example.com") monkeypatch.delenv("GONDULF_CODE_EXPIRY", raising=False) Config.load() assert Config.CODE_EXPIRY == 600 def test_load_token_expiry_custom(self, monkeypatch): """Test TOKEN_EXPIRY can be customized.""" monkeypatch.setenv("GONDULF_SECRET_KEY", "a" * 32) monkeypatch.setenv("GONDULF_BASE_URL", "https://auth.example.com") monkeypatch.setenv("GONDULF_TOKEN_EXPIRY", "7200") Config.load() assert Config.TOKEN_EXPIRY == 7200 def test_load_log_level_default_production(self, monkeypatch): """Test LOG_LEVEL defaults to INFO in production mode.""" monkeypatch.setenv("GONDULF_SECRET_KEY", "a" * 32) monkeypatch.setenv("GONDULF_BASE_URL", "https://auth.example.com") monkeypatch.delenv("GONDULF_LOG_LEVEL", raising=False) monkeypatch.delenv("GONDULF_DEBUG", raising=False) Config.load() assert Config.LOG_LEVEL == "INFO" assert Config.DEBUG is False def test_load_log_level_default_debug(self, monkeypatch): """Test LOG_LEVEL defaults to DEBUG when DEBUG=true.""" monkeypatch.setenv("GONDULF_SECRET_KEY", "a" * 32) monkeypatch.setenv("GONDULF_BASE_URL", "https://auth.example.com") monkeypatch.delenv("GONDULF_LOG_LEVEL", raising=False) monkeypatch.setenv("GONDULF_DEBUG", "true") Config.load() assert Config.LOG_LEVEL == "DEBUG" assert Config.DEBUG is True def test_load_log_level_custom(self, monkeypatch): """Test LOG_LEVEL can be customized.""" monkeypatch.setenv("GONDULF_SECRET_KEY", "a" * 32) monkeypatch.setenv("GONDULF_BASE_URL", "https://auth.example.com") monkeypatch.setenv("GONDULF_LOG_LEVEL", "WARNING") Config.load() assert Config.LOG_LEVEL == "WARNING" def test_load_invalid_log_level_raises_error(self, monkeypatch): """Test invalid LOG_LEVEL raises ConfigurationError.""" monkeypatch.setenv("GONDULF_SECRET_KEY", "a" * 32) monkeypatch.setenv("GONDULF_BASE_URL", "https://auth.example.com") monkeypatch.setenv("GONDULF_LOG_LEVEL", "INVALID") with pytest.raises(ConfigurationError, match="must be one of"): Config.load() class TestConfigValidate: """Tests for Config.validate() method.""" def test_validate_valid_configuration(self, monkeypatch): """Test validation passes with valid configuration.""" monkeypatch.setenv("GONDULF_SECRET_KEY", "a" * 32) monkeypatch.setenv("GONDULF_BASE_URL", "https://auth.example.com") Config.load() Config.validate() # Should not raise def test_validate_smtp_port_too_low(self, monkeypatch): """Test validation fails when SMTP_PORT < 1.""" monkeypatch.setenv("GONDULF_SECRET_KEY", "a" * 32) monkeypatch.setenv("GONDULF_BASE_URL", "https://auth.example.com") Config.load() Config.SMTP_PORT = 0 with pytest.raises(ConfigurationError, match="must be between 1 and 65535"): Config.validate() def test_validate_smtp_port_too_high(self, monkeypatch): """Test validation fails when SMTP_PORT > 65535.""" monkeypatch.setenv("GONDULF_SECRET_KEY", "a" * 32) monkeypatch.setenv("GONDULF_BASE_URL", "https://auth.example.com") Config.load() Config.SMTP_PORT = 70000 with pytest.raises(ConfigurationError, match="must be between 1 and 65535"): Config.validate() def test_validate_token_expiry_negative(self, monkeypatch): """Test validation fails when TOKEN_EXPIRY < 300.""" monkeypatch.setenv("GONDULF_SECRET_KEY", "a" * 32) monkeypatch.setenv("GONDULF_BASE_URL", "https://auth.example.com") Config.load() Config.TOKEN_EXPIRY = -1 with pytest.raises(ConfigurationError, match="must be at least 300 seconds"): Config.validate() def test_validate_code_expiry_zero(self, monkeypatch): """Test validation fails when CODE_EXPIRY <= 0.""" monkeypatch.setenv("GONDULF_SECRET_KEY", "a" * 32) monkeypatch.setenv("GONDULF_BASE_URL", "https://auth.example.com") Config.load() Config.CODE_EXPIRY = 0 with pytest.raises(ConfigurationError, match="must be positive"): Config.validate()