"""Tests for infrastructure config.""" import pytest from app.infrastructure.config import ( AppConfig, DBConfig, Environment, KCConfig, SecurityConfig, Settings, ) class TestSettings: """Test Settings with composition pattern.""" def test_default_values(self) -> None: """Test default settings values by creating settings without env file.""" # Create settings with required secrets and no env file s = Settings( _env_file=None, security=SecurityConfig(secret_key="test-secret-key"), kc=KCConfig(client_secret="test-client-secret"), ) assert s.app.name == "Blog API" assert s.app.debug is False assert s.app.host == "0.0.0.0" assert s.app.port == 8000 assert s.database_url == "sqlite+aiosqlite:///./blog.db" assert s.db.echo is False assert s.security.secret_key == "test-secret-key" assert s.kc.client_secret == "test-client-secret" assert s.environment == Environment.DEV def test_custom_values(self) -> None: """Test custom settings values.""" s = Settings( _env_file=None, environment=Environment.PROD, app=AppConfig( name="Test API", debug=True, host="localhost", port=9000, ), db=DBConfig(url="postgresql+asyncpg://user:pass@host/db"), security=SecurityConfig(secret_key="test-secret"), kc=KCConfig(client_secret="test-client-secret"), ) assert s.app.name == "Test API" assert s.app.debug is True assert s.app.host == "localhost" assert s.app.port == 9000 assert s.database_url == "postgresql+asyncpg://user:pass@host/db" assert s.security.secret_key == "test-secret" assert s.kc.client_secret == "test-client-secret" assert s.environment == Environment.PROD def test_model_config(self) -> None: """Test settings model config.""" assert "env_file" in Settings.model_config def test_is_dev_property(self) -> None: """Test is_dev property.""" s = Settings( _env_file=None, environment=Environment.DEV, security=SecurityConfig(secret_key="test"), kc=KCConfig(client_secret="test"), ) assert s.is_dev is True assert s.is_prod is False def test_is_prod_property(self) -> None: """Test is_prod property.""" s = Settings( _env_file=None, environment=Environment.PROD, security=SecurityConfig(secret_key="test"), kc=KCConfig(client_secret="test"), ) assert s.is_prod is True assert s.is_dev is False def test_prod_requires_security_secret(self) -> None: """Test that prod mode requires security secret_key.""" with pytest.raises(ValueError, match="SECURITY_SECRET_KEY"): Settings( _env_file=None, environment=Environment.PROD, security=SecurityConfig(secret_key=""), kc=KCConfig(client_secret="test"), ) def test_prod_requires_kc_secret(self) -> None: """Test that prod mode requires KC client_secret.""" with pytest.raises(ValueError, match="KC_CLIENT_SECRET"): Settings( _env_file=None, environment=Environment.PROD, security=SecurityConfig(secret_key="test"), kc=KCConfig(client_secret=""), ) def test_database_url_dev_default(self) -> None: """Test default database URL in dev mode.""" s = Settings( _env_file=None, environment=Environment.DEV, security=SecurityConfig(secret_key="test"), kc=KCConfig(client_secret="test"), ) assert s.database_url == "sqlite+aiosqlite:///./blog.db" def test_database_url_prod_builds_postgres(self) -> None: """Test that database URL builds from components in prod.""" s = Settings( _env_file=None, environment=Environment.PROD, db=DBConfig( url=None, # Force building from components host="db.example.com", port=5433, user="admin", password="secret", name="mydb", ), security=SecurityConfig(secret_key="test"), kc=KCConfig(client_secret="test"), ) assert ( s.database_url == "postgresql+asyncpg://admin:secret@db.example.com:5433/mydb" ) def test_database_url_override(self) -> None: """Test that explicit database URL overrides auto-building.""" s = Settings( _env_file=None, environment=Environment.PROD, db=DBConfig( url="postgresql+asyncpg://custom/url", host="ignored", user="ignored", ), security=SecurityConfig(secret_key="test"), kc=KCConfig(client_secret="test"), ) assert s.database_url == "postgresql+asyncpg://custom/url" class TestAppConfig: """Test AppConfig.""" def test_default_values(self) -> None: """Test AppConfig default values.""" cfg = AppConfig() assert cfg.name == "Blog API" assert cfg.debug is False assert cfg.host == "0.0.0.0" assert cfg.port == 8000 class TestDBConfig: """Test DBConfig.""" def test_default_values(self) -> None: """Test DBConfig default values.""" cfg = DBConfig() assert cfg.url is None assert cfg.echo is False assert cfg.host == "localhost" assert cfg.port == 5432 assert cfg.user == "postgres" assert cfg.password == "postgres" assert cfg.name == "blog" def test_postgres_url_validation(self) -> None: """Test URL validation for postgres.""" cfg = DBConfig(url="postgresql+asyncpg://user:pass@host/db") assert cfg.url == "postgresql+asyncpg://user:pass@host/db" def test_sqlite_url_validation(self) -> None: """Test URL validation for sqlite.""" cfg = DBConfig(url="sqlite+aiosqlite:///./test.db") assert cfg.url == "sqlite+aiosqlite:///./test.db" def test_invalid_url_validation(self) -> None: """Test URL validation rejects invalid URLs.""" with pytest.raises(ValueError, match="sqlite+.*postgresql+"): DBConfig(url="mysql://invalid") class TestKCConfig: """Test KCConfig.""" def test_default_values(self) -> None: """Test KCConfig default values.""" cfg = KCConfig(client_secret="test-secret") assert cfg.server_url == "http://localhost:8080" assert cfg.realm == "blog" assert cfg.client_id == "blog-api" assert cfg.client_secret == "test-secret" assert cfg.token_cache_ttl == 60 def test_is_configured_with_secret(self) -> None: """Test is_configured returns True when secret is set.""" cfg = KCConfig(client_secret="test-secret") assert cfg.is_configured is True def test_is_configured_without_secret(self) -> None: """Test is_configured returns False when secret is empty.""" cfg = KCConfig(client_secret="") assert cfg.is_configured is False class TestSecurityConfig: """Test SecurityConfig.""" def test_default_values(self) -> None: """Test SecurityConfig default values.""" cfg = SecurityConfig(secret_key="test-key") assert cfg.secret_key == "test-key" assert cfg.access_token_expire_minutes == 30 def test_is_configured_with_secret(self) -> None: """Test is_configured returns True when secret is set.""" cfg = SecurityConfig(secret_key="test-secret") assert cfg.is_configured is True def test_is_configured_without_secret(self) -> None: """Test is_configured returns False when secret is empty.""" cfg = SecurityConfig(secret_key="") assert cfg.is_configured is False class TestEnvironment: """Test Environment enum.""" def test_dev_value(self) -> None: """Test DEV environment value.""" assert Environment.DEV.value == "dev" def test_prod_value(self) -> None: """Test PROD environment value.""" assert Environment.PROD.value == "prod"