318 lines
12 KiB
Python
318 lines
12 KiB
Python
"""Integration tests for Settings model (Issue #5: DB-4).
|
|
|
|
Integration tests covering:
|
|
- Settings creation with related User entity
|
|
- Querying settings by user
|
|
- Updating settings for a user
|
|
- Complex alert preferences scenarios
|
|
- Multi-user settings isolation
|
|
- Settings deletion and cascade behavior
|
|
|
|
Follows TDD principles with comprehensive coverage.
|
|
Tests written BEFORE implementation (RED phase).
|
|
"""
|
|
|
|
import pytest
|
|
from decimal import Decimal
|
|
from sqlalchemy import select
|
|
from sqlalchemy.exc import IntegrityError
|
|
|
|
# Mark all tests in this module as asyncio
|
|
pytestmark = pytest.mark.asyncio
|
|
|
|
|
|
class TestSettingsIntegration:
|
|
"""Integration tests for Settings model with User relationship."""
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_create_settings_for_user(self, db_session, test_user):
|
|
"""Should create settings for a user and retrieve them."""
|
|
try:
|
|
from tradingagents.api.models.settings import Settings, RiskProfile
|
|
|
|
# Create settings
|
|
settings = Settings(
|
|
user_id=test_user.id,
|
|
risk_profile=RiskProfile.MODERATE,
|
|
risk_score=Decimal("6.0"),
|
|
max_position_pct=Decimal("15.0"),
|
|
max_portfolio_risk_pct=Decimal("3.0"),
|
|
investment_horizon_years=7,
|
|
alert_preferences={
|
|
"email": {
|
|
"enabled": True,
|
|
"address": test_user.email,
|
|
"alert_types": ["price_alert", "portfolio_alert"]
|
|
}
|
|
},
|
|
)
|
|
|
|
db_session.add(settings)
|
|
await db_session.commit()
|
|
await db_session.refresh(settings)
|
|
|
|
# Retrieve settings by user_id
|
|
result = await db_session.execute(
|
|
select(Settings).where(Settings.user_id == test_user.id)
|
|
)
|
|
retrieved_settings = result.scalar_one()
|
|
|
|
# Verify
|
|
assert retrieved_settings.id == settings.id
|
|
assert retrieved_settings.user_id == test_user.id
|
|
assert retrieved_settings.risk_profile == RiskProfile.MODERATE
|
|
assert retrieved_settings.risk_score == Decimal("6.0")
|
|
assert retrieved_settings.alert_preferences["email"]["address"] == test_user.email
|
|
|
|
except ImportError:
|
|
pytest.skip("Settings model not yet implemented (TDD RED phase)")
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_update_user_settings(self, db_session, test_user):
|
|
"""Should update existing settings for a user."""
|
|
try:
|
|
from tradingagents.api.models.settings import Settings, RiskProfile
|
|
|
|
# Create initial settings
|
|
settings = Settings(
|
|
user_id=test_user.id,
|
|
risk_profile=RiskProfile.CONSERVATIVE,
|
|
risk_score=Decimal("3.0"),
|
|
)
|
|
|
|
db_session.add(settings)
|
|
await db_session.commit()
|
|
await db_session.refresh(settings)
|
|
|
|
initial_id = settings.id
|
|
|
|
# Update settings
|
|
settings.risk_profile = RiskProfile.AGGRESSIVE
|
|
settings.risk_score = Decimal("8.5")
|
|
settings.max_position_pct = Decimal("25.0")
|
|
settings.alert_preferences = {
|
|
"sms": {
|
|
"enabled": True,
|
|
"phone": "+1234567890",
|
|
}
|
|
}
|
|
|
|
await db_session.commit()
|
|
await db_session.refresh(settings)
|
|
|
|
# Verify updates
|
|
assert settings.id == initial_id # Same record
|
|
assert settings.risk_profile == RiskProfile.AGGRESSIVE
|
|
assert settings.risk_score == Decimal("8.5")
|
|
assert settings.max_position_pct == Decimal("25.0")
|
|
assert settings.alert_preferences["sms"]["phone"] == "+1234567890"
|
|
|
|
except ImportError:
|
|
pytest.skip("Settings model not yet implemented (TDD RED phase)")
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_settings_isolation_between_users(self, db_session, test_user, second_user):
|
|
"""Should maintain separate settings for different users."""
|
|
try:
|
|
from tradingagents.api.models.settings import Settings, RiskProfile
|
|
|
|
# Create settings for first user
|
|
settings1 = Settings(
|
|
user_id=test_user.id,
|
|
risk_profile=RiskProfile.CONSERVATIVE,
|
|
risk_score=Decimal("2.0"),
|
|
max_position_pct=Decimal("5.0"),
|
|
)
|
|
db_session.add(settings1)
|
|
|
|
# Create settings for second user
|
|
settings2 = Settings(
|
|
user_id=second_user.id,
|
|
risk_profile=RiskProfile.AGGRESSIVE,
|
|
risk_score=Decimal("9.0"),
|
|
max_position_pct=Decimal("30.0"),
|
|
)
|
|
db_session.add(settings2)
|
|
|
|
await db_session.commit()
|
|
|
|
# Retrieve settings for first user
|
|
result1 = await db_session.execute(
|
|
select(Settings).where(Settings.user_id == test_user.id)
|
|
)
|
|
user1_settings = result1.scalar_one()
|
|
|
|
# Retrieve settings for second user
|
|
result2 = await db_session.execute(
|
|
select(Settings).where(Settings.user_id == second_user.id)
|
|
)
|
|
user2_settings = result2.scalar_one()
|
|
|
|
# Verify isolation
|
|
assert user1_settings.id != user2_settings.id
|
|
assert user1_settings.risk_profile == RiskProfile.CONSERVATIVE
|
|
assert user2_settings.risk_profile == RiskProfile.AGGRESSIVE
|
|
assert user1_settings.max_position_pct == Decimal("5.0")
|
|
assert user2_settings.max_position_pct == Decimal("30.0")
|
|
|
|
except ImportError:
|
|
pytest.skip("Settings model not yet implemented (TDD RED phase)")
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_complex_alert_preferences_workflow(self, db_session, test_user):
|
|
"""Should handle complex alert preferences updates."""
|
|
try:
|
|
from tradingagents.api.models.settings import Settings
|
|
|
|
# Start with email alerts only
|
|
settings = Settings(
|
|
user_id=test_user.id,
|
|
alert_preferences={
|
|
"email": {
|
|
"enabled": True,
|
|
"address": "user@example.com",
|
|
"alert_types": ["price_alert"]
|
|
}
|
|
},
|
|
)
|
|
db_session.add(settings)
|
|
await db_session.commit()
|
|
await db_session.refresh(settings)
|
|
|
|
# Add SMS alerts
|
|
settings.alert_preferences = {
|
|
"email": {
|
|
"enabled": True,
|
|
"address": "user@example.com",
|
|
"alert_types": ["price_alert", "portfolio_alert"]
|
|
},
|
|
"sms": {
|
|
"enabled": True,
|
|
"phone": "+1234567890",
|
|
"alert_types": ["critical_alert"],
|
|
"rate_limit": {"max_per_hour": 5}
|
|
}
|
|
}
|
|
await db_session.commit()
|
|
await db_session.refresh(settings)
|
|
|
|
# Verify complex structure
|
|
assert "email" in settings.alert_preferences
|
|
assert "sms" in settings.alert_preferences
|
|
assert len(settings.alert_preferences["email"]["alert_types"]) == 2
|
|
assert settings.alert_preferences["sms"]["rate_limit"]["max_per_hour"] == 5
|
|
|
|
# Disable email, keep SMS - must reassign entire dict for SQLAlchemy to track change
|
|
updated_prefs = dict(settings.alert_preferences)
|
|
updated_prefs["email"] = dict(updated_prefs["email"])
|
|
updated_prefs["email"]["enabled"] = False
|
|
settings.alert_preferences = updated_prefs
|
|
await db_session.commit()
|
|
await db_session.refresh(settings)
|
|
|
|
assert settings.alert_preferences["email"]["enabled"] is False
|
|
assert settings.alert_preferences["sms"]["enabled"] is True
|
|
|
|
except ImportError:
|
|
pytest.skip("Settings model not yet implemented (TDD RED phase)")
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_query_settings_by_risk_profile(self, db_session, test_user, second_user):
|
|
"""Should query settings by risk profile."""
|
|
try:
|
|
from tradingagents.api.models.settings import Settings, RiskProfile
|
|
from tradingagents.api.models import User
|
|
from tradingagents.api.services.auth_service import hash_password
|
|
|
|
# Create third user
|
|
user3 = User(
|
|
username="user3",
|
|
email="user3@example.com",
|
|
hashed_password=hash_password("password123"),
|
|
)
|
|
db_session.add(user3)
|
|
await db_session.commit()
|
|
await db_session.refresh(user3)
|
|
|
|
# Create settings with different risk profiles
|
|
settings1 = Settings(user_id=test_user.id, risk_profile=RiskProfile.CONSERVATIVE)
|
|
settings2 = Settings(user_id=second_user.id, risk_profile=RiskProfile.AGGRESSIVE)
|
|
settings3 = Settings(user_id=user3.id, risk_profile=RiskProfile.CONSERVATIVE)
|
|
|
|
db_session.add_all([settings1, settings2, settings3])
|
|
await db_session.commit()
|
|
|
|
# Query conservative profiles
|
|
result = await db_session.execute(
|
|
select(Settings).where(Settings.risk_profile == RiskProfile.CONSERVATIVE)
|
|
)
|
|
conservative_settings = result.scalars().all()
|
|
|
|
# Verify
|
|
assert len(conservative_settings) == 2
|
|
assert all(s.risk_profile == RiskProfile.CONSERVATIVE for s in conservative_settings)
|
|
|
|
# Query aggressive profiles
|
|
result = await db_session.execute(
|
|
select(Settings).where(Settings.risk_profile == RiskProfile.AGGRESSIVE)
|
|
)
|
|
aggressive_settings = result.scalars().all()
|
|
|
|
assert len(aggressive_settings) == 1
|
|
assert aggressive_settings[0].user_id == second_user.id
|
|
|
|
except ImportError:
|
|
pytest.skip("Settings model not yet implemented (TDD RED phase)")
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_settings_with_user_deletion(self, db_session):
|
|
"""Should handle settings cleanup when user is deleted."""
|
|
try:
|
|
from tradingagents.api.models.settings import Settings, RiskProfile
|
|
from tradingagents.api.models import User
|
|
from tradingagents.api.services.auth_service import hash_password
|
|
|
|
# Create user
|
|
user = User(
|
|
username="tempuser",
|
|
email="temp@example.com",
|
|
hashed_password=hash_password("password123"),
|
|
)
|
|
db_session.add(user)
|
|
await db_session.commit()
|
|
await db_session.refresh(user)
|
|
|
|
# Create settings
|
|
settings = Settings(
|
|
user_id=user.id,
|
|
risk_profile=RiskProfile.MODERATE,
|
|
)
|
|
db_session.add(settings)
|
|
await db_session.commit()
|
|
|
|
settings_id = settings.id
|
|
user_id = user.id
|
|
|
|
# Delete user
|
|
await db_session.delete(user)
|
|
await db_session.commit()
|
|
|
|
# Verify settings were cascade deleted
|
|
result = await db_session.execute(
|
|
select(Settings).where(Settings.id == settings_id)
|
|
)
|
|
deleted_settings = result.scalar_one_or_none()
|
|
|
|
assert deleted_settings is None
|
|
|
|
# Verify user is also deleted
|
|
result = await db_session.execute(
|
|
select(User).where(User.id == user_id)
|
|
)
|
|
deleted_user = result.scalar_one_or_none()
|
|
|
|
assert deleted_user is None
|
|
|
|
except ImportError:
|
|
pytest.skip("Settings model not yet implemented (TDD RED phase)")
|