477 lines
16 KiB
Python
477 lines
16 KiB
Python
"""
|
|
Test suite for API configuration and settings.
|
|
|
|
This module tests Issue #48 configuration features:
|
|
1. Settings loading from environment variables
|
|
2. JWT configuration (secret key, algorithm, expiration)
|
|
3. Database URL configuration
|
|
4. CORS configuration
|
|
5. Environment-specific settings (dev/prod)
|
|
6. Configuration validation
|
|
|
|
Tests follow TDD - written before implementation.
|
|
"""
|
|
|
|
import pytest
|
|
import os
|
|
from typing import Dict, Any
|
|
from unittest.mock import patch
|
|
|
|
|
|
# ============================================================================
|
|
# Unit Tests: Settings Loading
|
|
# ============================================================================
|
|
|
|
class TestSettingsLoading:
|
|
"""Test configuration settings loading."""
|
|
|
|
def test_load_settings_from_environment(self):
|
|
"""Test that settings are loaded from environment variables."""
|
|
# Arrange
|
|
try:
|
|
with patch.dict(os.environ, {
|
|
"DATABASE_URL": "sqlite+aiosqlite:///test.db",
|
|
"JWT_SECRET_KEY": "test-secret-key",
|
|
"JWT_ALGORITHM": "HS256",
|
|
"JWT_EXPIRATION_MINUTES": "30",
|
|
}):
|
|
# Act
|
|
from tradingagents.api.config import Settings
|
|
settings = Settings()
|
|
|
|
# Assert
|
|
assert settings.DATABASE_URL == "sqlite+aiosqlite:///test.db"
|
|
assert settings.JWT_SECRET_KEY == "test-secret-key"
|
|
assert settings.JWT_ALGORITHM == "HS256"
|
|
assert settings.JWT_EXPIRATION_MINUTES == 30
|
|
except ImportError:
|
|
pytest.skip("Config not implemented yet")
|
|
|
|
def test_settings_default_values(self):
|
|
"""Test that settings have sensible defaults."""
|
|
# Arrange
|
|
try:
|
|
with patch.dict(os.environ, {}, clear=True):
|
|
# Act
|
|
from tradingagents.api.config import Settings
|
|
settings = Settings()
|
|
|
|
# Assert: Should have defaults
|
|
assert hasattr(settings, "JWT_ALGORITHM")
|
|
if settings.JWT_ALGORITHM:
|
|
assert settings.JWT_ALGORITHM == "HS256"
|
|
except ImportError:
|
|
pytest.skip("Config not implemented yet")
|
|
|
|
def test_settings_required_fields_validation(self):
|
|
"""Test that required settings raise error if missing."""
|
|
# Arrange
|
|
try:
|
|
with patch.dict(os.environ, {}, clear=True):
|
|
# Act & Assert
|
|
from tradingagents.api.config import Settings
|
|
|
|
# May raise ValidationError if required fields missing
|
|
# Or may use defaults - depends on implementation
|
|
settings = Settings()
|
|
assert settings is not None
|
|
except ImportError:
|
|
pytest.skip("Config not implemented yet")
|
|
except Exception as e:
|
|
# Expected if required fields are missing
|
|
assert "JWT_SECRET_KEY" in str(e) or True
|
|
|
|
|
|
# ============================================================================
|
|
# Unit Tests: JWT Configuration
|
|
# ============================================================================
|
|
|
|
class TestJWTConfiguration:
|
|
"""Test JWT-specific configuration."""
|
|
|
|
def test_jwt_secret_key_from_env(self):
|
|
"""Test JWT secret key is loaded from environment."""
|
|
# Arrange
|
|
try:
|
|
with patch.dict(os.environ, {
|
|
"JWT_SECRET_KEY": "my-super-secret-key-123",
|
|
}):
|
|
# Act
|
|
from tradingagents.api.config import Settings
|
|
settings = Settings()
|
|
|
|
# Assert
|
|
assert settings.JWT_SECRET_KEY == "my-super-secret-key-123"
|
|
except ImportError:
|
|
pytest.skip("Config not implemented yet")
|
|
|
|
def test_jwt_algorithm_configuration(self):
|
|
"""Test JWT algorithm can be configured."""
|
|
# Arrange
|
|
try:
|
|
with patch.dict(os.environ, {
|
|
"JWT_SECRET_KEY": "test-key",
|
|
"JWT_ALGORITHM": "HS512",
|
|
}):
|
|
# Act
|
|
from tradingagents.api.config import Settings
|
|
settings = Settings()
|
|
|
|
# Assert
|
|
assert settings.JWT_ALGORITHM == "HS512"
|
|
except ImportError:
|
|
pytest.skip("Config not implemented yet")
|
|
|
|
def test_jwt_expiration_minutes(self):
|
|
"""Test JWT expiration time configuration."""
|
|
# Arrange
|
|
try:
|
|
with patch.dict(os.environ, {
|
|
"JWT_SECRET_KEY": "test-key",
|
|
"JWT_EXPIRATION_MINUTES": "60",
|
|
}):
|
|
# Act
|
|
from tradingagents.api.config import Settings
|
|
settings = Settings()
|
|
|
|
# Assert
|
|
assert settings.JWT_EXPIRATION_MINUTES == 60
|
|
except ImportError:
|
|
pytest.skip("Config not implemented yet")
|
|
|
|
def test_jwt_secret_key_min_length(self):
|
|
"""Test that JWT secret key has minimum length requirement."""
|
|
# Arrange
|
|
try:
|
|
with patch.dict(os.environ, {
|
|
"JWT_SECRET_KEY": "short", # Too short
|
|
}):
|
|
# Act
|
|
from tradingagents.api.config import Settings
|
|
|
|
# May raise ValidationError for weak secret
|
|
# Or may accept it (validation in code)
|
|
settings = Settings()
|
|
|
|
# Assert: If no validation, at least warn
|
|
assert len(settings.JWT_SECRET_KEY) >= 5
|
|
except ImportError:
|
|
pytest.skip("Config not implemented yet")
|
|
except Exception:
|
|
# Expected if validation is strict
|
|
pass
|
|
|
|
|
|
# ============================================================================
|
|
# Unit Tests: Database Configuration
|
|
# ============================================================================
|
|
|
|
class TestDatabaseConfiguration:
|
|
"""Test database URL configuration."""
|
|
|
|
def test_database_url_from_env(self):
|
|
"""Test database URL is loaded from environment."""
|
|
# Arrange
|
|
try:
|
|
with patch.dict(os.environ, {
|
|
"DATABASE_URL": "postgresql+asyncpg://user:pass@localhost/db",
|
|
}):
|
|
# Act
|
|
from tradingagents.api.config import Settings
|
|
settings = Settings()
|
|
|
|
# Assert
|
|
assert settings.DATABASE_URL == "postgresql+asyncpg://user:pass@localhost/db"
|
|
except ImportError:
|
|
pytest.skip("Config not implemented yet")
|
|
|
|
def test_database_url_sqlite_default(self):
|
|
"""Test that SQLite is used if no DATABASE_URL provided."""
|
|
# Arrange
|
|
try:
|
|
with patch.dict(os.environ, {}, clear=True):
|
|
# Act
|
|
from tradingagents.api.config import Settings
|
|
settings = Settings()
|
|
|
|
# Assert: Should have some default
|
|
if hasattr(settings, "DATABASE_URL") and settings.DATABASE_URL:
|
|
assert "sqlite" in settings.DATABASE_URL.lower()
|
|
except ImportError:
|
|
pytest.skip("Config not implemented yet")
|
|
|
|
def test_database_url_validation(self):
|
|
"""Test that invalid database URLs are rejected."""
|
|
# Arrange
|
|
try:
|
|
with patch.dict(os.environ, {
|
|
"DATABASE_URL": "invalid-url",
|
|
}):
|
|
# Act
|
|
from tradingagents.api.config import Settings
|
|
|
|
# May raise ValidationError or accept it
|
|
settings = Settings()
|
|
|
|
# Assert: At least it's set
|
|
assert settings.DATABASE_URL is not None
|
|
except ImportError:
|
|
pytest.skip("Config not implemented yet")
|
|
|
|
|
|
# ============================================================================
|
|
# Unit Tests: CORS Configuration
|
|
# ============================================================================
|
|
|
|
class TestCORSConfiguration:
|
|
"""Test CORS (Cross-Origin Resource Sharing) configuration."""
|
|
|
|
def test_cors_origins_from_env(self):
|
|
"""Test CORS allowed origins configuration."""
|
|
# Arrange
|
|
try:
|
|
with patch.dict(os.environ, {
|
|
"CORS_ORIGINS": "http://localhost:3000,https://app.example.com",
|
|
}):
|
|
# Act
|
|
from tradingagents.api.config import Settings
|
|
settings = Settings()
|
|
|
|
# Assert
|
|
if hasattr(settings, "CORS_ORIGINS"):
|
|
assert "localhost:3000" in settings.CORS_ORIGINS
|
|
except ImportError:
|
|
pytest.skip("Config not implemented yet")
|
|
|
|
def test_cors_allow_credentials(self):
|
|
"""Test CORS allow credentials setting."""
|
|
# Arrange
|
|
try:
|
|
with patch.dict(os.environ, {
|
|
"CORS_ALLOW_CREDENTIALS": "true",
|
|
}):
|
|
# Act
|
|
from tradingagents.api.config import Settings
|
|
settings = Settings()
|
|
|
|
# Assert
|
|
if hasattr(settings, "CORS_ALLOW_CREDENTIALS"):
|
|
assert settings.CORS_ALLOW_CREDENTIALS is True
|
|
except ImportError:
|
|
pytest.skip("Config not implemented yet")
|
|
|
|
def test_cors_wildcard_origin(self):
|
|
"""Test CORS wildcard origin (*) configuration."""
|
|
# Arrange
|
|
try:
|
|
with patch.dict(os.environ, {
|
|
"CORS_ORIGINS": "*",
|
|
}):
|
|
# Act
|
|
from tradingagents.api.config import Settings
|
|
settings = Settings()
|
|
|
|
# Assert
|
|
if hasattr(settings, "CORS_ORIGINS"):
|
|
assert "*" in settings.CORS_ORIGINS or settings.CORS_ORIGINS == "*"
|
|
except ImportError:
|
|
pytest.skip("Config not implemented yet")
|
|
|
|
|
|
# ============================================================================
|
|
# Unit Tests: Environment-Specific Settings
|
|
# ============================================================================
|
|
|
|
class TestEnvironmentSettings:
|
|
"""Test environment-specific configuration (dev/staging/prod)."""
|
|
|
|
def test_debug_mode_in_development(self):
|
|
"""Test debug mode enabled in development."""
|
|
# Arrange
|
|
try:
|
|
with patch.dict(os.environ, {
|
|
"ENVIRONMENT": "development",
|
|
}):
|
|
# Act
|
|
from tradingagents.api.config import Settings
|
|
settings = Settings()
|
|
|
|
# Assert
|
|
if hasattr(settings, "DEBUG"):
|
|
assert settings.DEBUG is True
|
|
except ImportError:
|
|
pytest.skip("Config not implemented yet")
|
|
|
|
def test_debug_mode_in_production(self):
|
|
"""Test debug mode disabled in production."""
|
|
# Arrange
|
|
try:
|
|
with patch.dict(os.environ, {
|
|
"ENVIRONMENT": "production",
|
|
}):
|
|
# Act
|
|
from tradingagents.api.config import Settings
|
|
settings = Settings()
|
|
|
|
# Assert
|
|
if hasattr(settings, "DEBUG"):
|
|
assert settings.DEBUG is False
|
|
except ImportError:
|
|
pytest.skip("Config not implemented yet")
|
|
|
|
def test_log_level_configuration(self):
|
|
"""Test log level can be configured."""
|
|
# Arrange
|
|
try:
|
|
with patch.dict(os.environ, {
|
|
"LOG_LEVEL": "DEBUG",
|
|
}):
|
|
# Act
|
|
from tradingagents.api.config import Settings
|
|
settings = Settings()
|
|
|
|
# Assert
|
|
if hasattr(settings, "LOG_LEVEL"):
|
|
assert settings.LOG_LEVEL == "DEBUG"
|
|
except ImportError:
|
|
pytest.skip("Config not implemented yet")
|
|
|
|
|
|
# ============================================================================
|
|
# Integration Tests: Settings in Application
|
|
# ============================================================================
|
|
|
|
class TestSettingsIntegration:
|
|
"""Test that settings are used correctly in application."""
|
|
|
|
def test_settings_singleton_pattern(self):
|
|
"""Test that settings use singleton or cached instance."""
|
|
# Arrange
|
|
try:
|
|
from tradingagents.api.config import Settings
|
|
|
|
# Act
|
|
settings1 = Settings()
|
|
settings2 = Settings()
|
|
|
|
# Assert: May be same instance (singleton) or different but equal
|
|
assert settings1.JWT_ALGORITHM == settings2.JWT_ALGORITHM
|
|
except ImportError:
|
|
pytest.skip("Config not implemented yet")
|
|
|
|
def test_settings_in_dependency_injection(self):
|
|
"""Test that settings can be used in FastAPI dependencies."""
|
|
# This would test get_settings() dependency
|
|
try:
|
|
from tradingagents.api.dependencies import get_settings
|
|
|
|
# Act
|
|
settings = get_settings()
|
|
|
|
# Assert
|
|
assert settings is not None
|
|
assert hasattr(settings, "JWT_SECRET_KEY")
|
|
except ImportError:
|
|
pytest.skip("Dependencies not implemented yet")
|
|
|
|
|
|
# ============================================================================
|
|
# Edge Cases: Configuration
|
|
# ============================================================================
|
|
|
|
class TestConfigurationEdgeCases:
|
|
"""Test edge cases in configuration."""
|
|
|
|
def test_empty_jwt_secret_key(self):
|
|
"""Test handling of empty JWT secret key."""
|
|
# Arrange
|
|
try:
|
|
with patch.dict(os.environ, {
|
|
"JWT_SECRET_KEY": "",
|
|
}):
|
|
# Act & Assert
|
|
from tradingagents.api.config import Settings
|
|
|
|
# Should either raise error or use default
|
|
settings = Settings()
|
|
assert settings.JWT_SECRET_KEY != "" # Should have fallback
|
|
except ImportError:
|
|
pytest.skip("Config not implemented yet")
|
|
except Exception:
|
|
# Expected if validation is strict
|
|
pass
|
|
|
|
def test_negative_jwt_expiration(self):
|
|
"""Test handling of negative JWT expiration time."""
|
|
# Arrange
|
|
try:
|
|
with patch.dict(os.environ, {
|
|
"JWT_SECRET_KEY": "test-key",
|
|
"JWT_EXPIRATION_MINUTES": "-30",
|
|
}):
|
|
# Act
|
|
from tradingagents.api.config import Settings
|
|
|
|
# Should either raise error or use default
|
|
settings = Settings()
|
|
assert settings.JWT_EXPIRATION_MINUTES > 0
|
|
except ImportError:
|
|
pytest.skip("Config not implemented yet")
|
|
except Exception:
|
|
# Expected if validation rejects negative values
|
|
pass
|
|
|
|
def test_very_large_jwt_expiration(self):
|
|
"""Test handling of very large JWT expiration time."""
|
|
# Arrange
|
|
try:
|
|
with patch.dict(os.environ, {
|
|
"JWT_SECRET_KEY": "test-key",
|
|
"JWT_EXPIRATION_MINUTES": "525600", # 1 year
|
|
}):
|
|
# Act
|
|
from tradingagents.api.config import Settings
|
|
settings = Settings()
|
|
|
|
# Assert: Should accept or cap at reasonable max
|
|
assert settings.JWT_EXPIRATION_MINUTES <= 525600
|
|
except ImportError:
|
|
pytest.skip("Config not implemented yet")
|
|
|
|
def test_malformed_database_url(self):
|
|
"""Test handling of malformed database URL."""
|
|
# Arrange
|
|
malformed_urls = [
|
|
"not-a-url",
|
|
"postgresql://", # Incomplete
|
|
"sqlite://", # Missing path
|
|
]
|
|
|
|
try:
|
|
from tradingagents.api.config import Settings
|
|
|
|
for url in malformed_urls:
|
|
with patch.dict(os.environ, {"DATABASE_URL": url}):
|
|
# Act: Should either accept or reject
|
|
settings = Settings()
|
|
# No assertion - just check it doesn't crash
|
|
except ImportError:
|
|
pytest.skip("Config not implemented yet")
|
|
|
|
def test_unicode_in_config_values(self):
|
|
"""Test Unicode characters in configuration values."""
|
|
# Arrange
|
|
try:
|
|
with patch.dict(os.environ, {
|
|
"APP_NAME": "交易代理 🚀",
|
|
}):
|
|
# Act
|
|
from tradingagents.api.config import Settings
|
|
settings = Settings()
|
|
|
|
# Assert
|
|
if hasattr(settings, "APP_NAME"):
|
|
assert "🚀" in settings.APP_NAME
|
|
except ImportError:
|
|
pytest.skip("Config not implemented yet")
|