1256 lines
40 KiB
Python
1256 lines
40 KiB
Python
"""
|
|
Test suite for SQLAlchemy database models.
|
|
|
|
This module tests Issue #48 and Issue #3 database models:
|
|
1. User model with hashed passwords
|
|
2. User model with tax_jurisdiction, timezone, api_key_hash, is_verified (Issue #3)
|
|
3. Strategy model with JSON parameters
|
|
4. Relationships (User -> Strategies)
|
|
5. Model validation and constraints
|
|
6. Timestamps (created_at, updated_at)
|
|
7. Cascade delete behavior
|
|
|
|
Tests follow TDD - written before implementation.
|
|
"""
|
|
|
|
import pytest
|
|
from datetime import datetime
|
|
from typing import Dict, Any
|
|
|
|
pytestmark = pytest.mark.asyncio
|
|
|
|
|
|
# ============================================================================
|
|
# Unit Tests: User Model
|
|
# ============================================================================
|
|
|
|
class TestUserModel:
|
|
"""Test User database model."""
|
|
|
|
async def test_create_user(self, db_session):
|
|
"""Test creating a user with required fields."""
|
|
# Arrange
|
|
try:
|
|
from tradingagents.api.models import User
|
|
|
|
user = User(
|
|
username="testuser",
|
|
email="test@example.com",
|
|
hashed_password="$argon2id$v=19$m=65536,t=3,p=4$fakehash",
|
|
)
|
|
|
|
# Act
|
|
db_session.add(user)
|
|
await db_session.commit()
|
|
await db_session.refresh(user)
|
|
|
|
# Assert
|
|
assert user.id is not None
|
|
assert user.username == "testuser"
|
|
assert user.email == "test@example.com"
|
|
assert user.hashed_password.startswith("$argon2")
|
|
except ImportError:
|
|
pytest.skip("Models not implemented yet")
|
|
|
|
async def test_user_unique_username(self, db_session):
|
|
"""Test that username must be unique."""
|
|
# Arrange
|
|
try:
|
|
from tradingagents.api.models import User
|
|
from sqlalchemy.exc import IntegrityError
|
|
|
|
user1 = User(
|
|
username="testuser",
|
|
email="test1@example.com",
|
|
hashed_password="hash1",
|
|
)
|
|
user2 = User(
|
|
username="testuser", # Same username
|
|
email="test2@example.com",
|
|
hashed_password="hash2",
|
|
)
|
|
|
|
# Act
|
|
db_session.add(user1)
|
|
await db_session.commit()
|
|
|
|
db_session.add(user2)
|
|
|
|
# Assert: Should raise IntegrityError
|
|
with pytest.raises(IntegrityError):
|
|
await db_session.commit()
|
|
except ImportError:
|
|
pytest.skip("Models not implemented yet")
|
|
|
|
async def test_user_unique_email(self, db_session):
|
|
"""Test that email must be unique."""
|
|
# Arrange
|
|
try:
|
|
from tradingagents.api.models import User
|
|
from sqlalchemy.exc import IntegrityError
|
|
|
|
user1 = User(
|
|
username="user1",
|
|
email="test@example.com",
|
|
hashed_password="hash1",
|
|
)
|
|
user2 = User(
|
|
username="user2",
|
|
email="test@example.com", # Same email
|
|
hashed_password="hash2",
|
|
)
|
|
|
|
# Act
|
|
db_session.add(user1)
|
|
await db_session.commit()
|
|
|
|
db_session.add(user2)
|
|
|
|
# Assert
|
|
with pytest.raises(IntegrityError):
|
|
await db_session.commit()
|
|
except ImportError:
|
|
pytest.skip("Models not implemented yet")
|
|
|
|
async def test_user_timestamps(self, db_session):
|
|
"""Test that user has created_at and updated_at timestamps."""
|
|
# Arrange
|
|
try:
|
|
from tradingagents.api.models import User
|
|
|
|
user = User(
|
|
username="timestampuser",
|
|
email="timestamp@example.com",
|
|
hashed_password="hash",
|
|
)
|
|
|
|
# Act
|
|
db_session.add(user)
|
|
await db_session.commit()
|
|
await db_session.refresh(user)
|
|
|
|
# Assert
|
|
assert hasattr(user, "created_at")
|
|
assert hasattr(user, "updated_at")
|
|
assert isinstance(user.created_at, datetime)
|
|
assert isinstance(user.updated_at, datetime)
|
|
except ImportError:
|
|
pytest.skip("Models not implemented yet")
|
|
|
|
async def test_user_full_name_optional(self, db_session):
|
|
"""Test that full_name is optional."""
|
|
# Arrange
|
|
try:
|
|
from tradingagents.api.models import User
|
|
|
|
user = User(
|
|
username="user_no_name",
|
|
email="noname@example.com",
|
|
hashed_password="hash",
|
|
# No full_name provided
|
|
)
|
|
|
|
# Act
|
|
db_session.add(user)
|
|
await db_session.commit()
|
|
await db_session.refresh(user)
|
|
|
|
# Assert: Should succeed without full_name
|
|
assert user.id is not None
|
|
assert user.full_name is None or user.full_name == ""
|
|
except ImportError:
|
|
pytest.skip("Models not implemented yet")
|
|
|
|
async def test_user_is_active_default(self, db_session):
|
|
"""Test that is_active defaults to True."""
|
|
# Arrange
|
|
try:
|
|
from tradingagents.api.models import User
|
|
|
|
user = User(
|
|
username="activeuser",
|
|
email="active@example.com",
|
|
hashed_password="hash",
|
|
)
|
|
|
|
# Act
|
|
db_session.add(user)
|
|
await db_session.commit()
|
|
await db_session.refresh(user)
|
|
|
|
# Assert
|
|
if hasattr(user, "is_active"):
|
|
assert user.is_active is True
|
|
except ImportError:
|
|
pytest.skip("Models not implemented yet")
|
|
|
|
async def test_user_strategies_relationship(self, db_session):
|
|
"""Test User has strategies relationship."""
|
|
# Arrange
|
|
try:
|
|
from tradingagents.api.models import User, Strategy
|
|
|
|
user = User(
|
|
username="reluser",
|
|
email="rel@example.com",
|
|
hashed_password="hash",
|
|
)
|
|
db_session.add(user)
|
|
await db_session.commit()
|
|
await db_session.refresh(user)
|
|
|
|
strategy = Strategy(
|
|
name="Test Strategy",
|
|
description="Test",
|
|
user_id=user.id,
|
|
)
|
|
db_session.add(strategy)
|
|
await db_session.commit()
|
|
|
|
# Act: Access relationship
|
|
await db_session.refresh(user)
|
|
|
|
# Assert: Can access strategies through relationship
|
|
# This depends on how relationship is configured
|
|
assert hasattr(user, "strategies") or user.id is not None
|
|
except ImportError:
|
|
pytest.skip("Models not implemented yet")
|
|
|
|
|
|
# ============================================================================
|
|
# Unit Tests: Strategy Model
|
|
# ============================================================================
|
|
|
|
class TestStrategyModel:
|
|
"""Test Strategy database model."""
|
|
|
|
async def test_create_strategy(self, db_session, test_user):
|
|
"""Test creating a strategy with required fields."""
|
|
# Arrange
|
|
if test_user is None:
|
|
pytest.skip("User model not implemented yet")
|
|
|
|
try:
|
|
from tradingagents.api.models import Strategy
|
|
|
|
strategy = Strategy(
|
|
name="Test Strategy",
|
|
description="A test strategy",
|
|
user_id=test_user.id,
|
|
)
|
|
|
|
# Act
|
|
db_session.add(strategy)
|
|
await db_session.commit()
|
|
await db_session.refresh(strategy)
|
|
|
|
# Assert
|
|
assert strategy.id is not None
|
|
assert strategy.name == "Test Strategy"
|
|
assert strategy.description == "A test strategy"
|
|
assert strategy.user_id == test_user.id
|
|
except ImportError:
|
|
pytest.skip("Models not implemented yet")
|
|
|
|
async def test_strategy_with_parameters(self, db_session, test_user):
|
|
"""Test creating strategy with JSON parameters."""
|
|
# Arrange
|
|
if test_user is None:
|
|
pytest.skip("User model not implemented yet")
|
|
|
|
try:
|
|
from tradingagents.api.models import Strategy
|
|
|
|
parameters = {
|
|
"symbol": "AAPL",
|
|
"period": 20,
|
|
"threshold": 0.05,
|
|
"indicators": ["SMA", "RSI"],
|
|
}
|
|
|
|
strategy = Strategy(
|
|
name="Parameterized Strategy",
|
|
description="Test",
|
|
parameters=parameters,
|
|
user_id=test_user.id,
|
|
)
|
|
|
|
# Act
|
|
db_session.add(strategy)
|
|
await db_session.commit()
|
|
await db_session.refresh(strategy)
|
|
|
|
# Assert
|
|
assert strategy.parameters == parameters
|
|
assert strategy.parameters["symbol"] == "AAPL"
|
|
assert strategy.parameters["period"] == 20
|
|
except ImportError:
|
|
pytest.skip("Models not implemented yet")
|
|
|
|
async def test_strategy_empty_parameters(self, db_session, test_user):
|
|
"""Test strategy with empty parameters dict."""
|
|
# Arrange
|
|
if test_user is None:
|
|
pytest.skip("User model not implemented yet")
|
|
|
|
try:
|
|
from tradingagents.api.models import Strategy
|
|
|
|
strategy = Strategy(
|
|
name="Empty Params",
|
|
description="Test",
|
|
parameters={},
|
|
user_id=test_user.id,
|
|
)
|
|
|
|
# Act
|
|
db_session.add(strategy)
|
|
await db_session.commit()
|
|
await db_session.refresh(strategy)
|
|
|
|
# Assert
|
|
assert strategy.parameters == {}
|
|
except ImportError:
|
|
pytest.skip("Models not implemented yet")
|
|
|
|
async def test_strategy_null_parameters(self, db_session, test_user):
|
|
"""Test strategy with null parameters."""
|
|
# Arrange
|
|
if test_user is None:
|
|
pytest.skip("User model not implemented yet")
|
|
|
|
try:
|
|
from tradingagents.api.models import Strategy
|
|
|
|
strategy = Strategy(
|
|
name="Null Params",
|
|
description="Test",
|
|
parameters=None,
|
|
user_id=test_user.id,
|
|
)
|
|
|
|
# Act
|
|
db_session.add(strategy)
|
|
await db_session.commit()
|
|
await db_session.refresh(strategy)
|
|
|
|
# Assert: Should handle null (may default to {} or stay None)
|
|
assert strategy.parameters is None or strategy.parameters == {}
|
|
except ImportError:
|
|
pytest.skip("Models not implemented yet")
|
|
|
|
async def test_strategy_is_active_default(self, db_session, test_user):
|
|
"""Test that is_active defaults to True."""
|
|
# Arrange
|
|
if test_user is None:
|
|
pytest.skip("User model not implemented yet")
|
|
|
|
try:
|
|
from tradingagents.api.models import Strategy
|
|
|
|
strategy = Strategy(
|
|
name="Active Strategy",
|
|
description="Test",
|
|
user_id=test_user.id,
|
|
)
|
|
|
|
# Act
|
|
db_session.add(strategy)
|
|
await db_session.commit()
|
|
await db_session.refresh(strategy)
|
|
|
|
# Assert
|
|
if hasattr(strategy, "is_active"):
|
|
assert strategy.is_active is True
|
|
except ImportError:
|
|
pytest.skip("Models not implemented yet")
|
|
|
|
async def test_strategy_timestamps(self, db_session, test_user):
|
|
"""Test that strategy has timestamps."""
|
|
# Arrange
|
|
if test_user is None:
|
|
pytest.skip("User model not implemented yet")
|
|
|
|
try:
|
|
from tradingagents.api.models import Strategy
|
|
|
|
strategy = Strategy(
|
|
name="Timestamp Strategy",
|
|
description="Test",
|
|
user_id=test_user.id,
|
|
)
|
|
|
|
# Act
|
|
db_session.add(strategy)
|
|
await db_session.commit()
|
|
await db_session.refresh(strategy)
|
|
|
|
# Assert
|
|
assert hasattr(strategy, "created_at")
|
|
assert hasattr(strategy, "updated_at")
|
|
assert isinstance(strategy.created_at, datetime)
|
|
assert isinstance(strategy.updated_at, datetime)
|
|
except ImportError:
|
|
pytest.skip("Models not implemented yet")
|
|
|
|
async def test_strategy_updated_at_changes(self, db_session, test_user):
|
|
"""Test that updated_at changes on update."""
|
|
# Arrange
|
|
if test_user is None:
|
|
pytest.skip("User model not implemented yet")
|
|
|
|
try:
|
|
from tradingagents.api.models import Strategy
|
|
import asyncio
|
|
|
|
strategy = Strategy(
|
|
name="Update Test",
|
|
description="Original",
|
|
user_id=test_user.id,
|
|
)
|
|
db_session.add(strategy)
|
|
await db_session.commit()
|
|
await db_session.refresh(strategy)
|
|
|
|
original_updated_at = strategy.updated_at
|
|
|
|
# Wait a moment to ensure timestamp difference
|
|
await asyncio.sleep(0.1)
|
|
|
|
# Act: Update strategy
|
|
strategy.description = "Modified"
|
|
await db_session.commit()
|
|
await db_session.refresh(strategy)
|
|
|
|
# Assert: updated_at should change
|
|
assert strategy.updated_at > original_updated_at
|
|
except ImportError:
|
|
pytest.skip("Models not implemented yet")
|
|
|
|
async def test_strategy_foreign_key_constraint(self, db_session):
|
|
"""Test that strategy requires valid user_id."""
|
|
# Arrange
|
|
try:
|
|
from tradingagents.api.models import Strategy
|
|
from sqlalchemy.exc import IntegrityError
|
|
|
|
strategy = Strategy(
|
|
name="Invalid User",
|
|
description="Test",
|
|
user_id=99999, # Non-existent user
|
|
)
|
|
|
|
# Act & Assert
|
|
db_session.add(strategy)
|
|
with pytest.raises(IntegrityError):
|
|
await db_session.commit()
|
|
except ImportError:
|
|
pytest.skip("Models not implemented yet")
|
|
|
|
async def test_strategy_cascade_delete(self, db_session, test_user):
|
|
"""Test that deleting user cascades to strategies."""
|
|
# Arrange
|
|
if test_user is None:
|
|
pytest.skip("User model not implemented yet")
|
|
|
|
try:
|
|
from tradingagents.api.models import Strategy, User
|
|
from sqlalchemy import select
|
|
|
|
strategy = Strategy(
|
|
name="Cascade Test",
|
|
description="Test",
|
|
user_id=test_user.id,
|
|
)
|
|
db_session.add(strategy)
|
|
await db_session.commit()
|
|
strategy_id = strategy.id
|
|
|
|
# Act: Delete user
|
|
await db_session.delete(test_user)
|
|
await db_session.commit()
|
|
|
|
# Assert: Strategy should be deleted too (cascade)
|
|
result = await db_session.execute(
|
|
select(Strategy).where(Strategy.id == strategy_id)
|
|
)
|
|
deleted_strategy = result.scalar_one_or_none()
|
|
assert deleted_strategy is None
|
|
except ImportError:
|
|
pytest.skip("Models not implemented yet")
|
|
|
|
|
|
# ============================================================================
|
|
# Unit Tests: Model Validation
|
|
# ============================================================================
|
|
|
|
class TestModelValidation:
|
|
"""Test model field validation and constraints."""
|
|
|
|
async def test_user_required_fields(self, db_session):
|
|
"""Test that user requires username, email, hashed_password."""
|
|
# Arrange
|
|
try:
|
|
from tradingagents.api.models import User
|
|
from sqlalchemy.exc import IntegrityError
|
|
|
|
# Missing username
|
|
user = User(
|
|
email="test@example.com",
|
|
hashed_password="hash",
|
|
)
|
|
|
|
# Act & Assert
|
|
db_session.add(user)
|
|
with pytest.raises(IntegrityError):
|
|
await db_session.commit()
|
|
except ImportError:
|
|
pytest.skip("Models not implemented yet")
|
|
|
|
async def test_strategy_required_fields(self, db_session, test_user):
|
|
"""Test that strategy requires name, description, user_id."""
|
|
# Arrange
|
|
if test_user is None:
|
|
pytest.skip("User model not implemented yet")
|
|
|
|
try:
|
|
from tradingagents.api.models import Strategy
|
|
from sqlalchemy.exc import IntegrityError
|
|
|
|
# Missing name
|
|
strategy = Strategy(
|
|
description="Test",
|
|
user_id=test_user.id,
|
|
)
|
|
|
|
# Act & Assert
|
|
db_session.add(strategy)
|
|
with pytest.raises(IntegrityError):
|
|
await db_session.commit()
|
|
except ImportError:
|
|
pytest.skip("Models not implemented yet")
|
|
|
|
async def test_email_format_not_validated_at_db_level(self, db_session):
|
|
"""Test that email format validation is done at API level, not DB."""
|
|
# Arrange
|
|
try:
|
|
from tradingagents.api.models import User
|
|
|
|
# Invalid email format
|
|
user = User(
|
|
username="testuser",
|
|
email="not-an-email",
|
|
hashed_password="hash",
|
|
)
|
|
|
|
# Act
|
|
db_session.add(user)
|
|
await db_session.commit()
|
|
|
|
# Assert: DB should accept it (validation is at API level)
|
|
assert user.id is not None
|
|
except ImportError:
|
|
pytest.skip("Models not implemented yet")
|
|
|
|
|
|
# ============================================================================
|
|
# Integration Tests: Complex Queries
|
|
# ============================================================================
|
|
|
|
class TestModelQueries:
|
|
"""Test querying models."""
|
|
|
|
async def test_query_user_by_username(self, db_session, test_user):
|
|
"""Test querying user by username."""
|
|
# Arrange
|
|
if test_user is None:
|
|
pytest.skip("User model not implemented yet")
|
|
|
|
try:
|
|
from tradingagents.api.models import User
|
|
from sqlalchemy import select
|
|
|
|
# Act
|
|
result = await db_session.execute(
|
|
select(User).where(User.username == test_user.username)
|
|
)
|
|
user = result.scalar_one_or_none()
|
|
|
|
# Assert
|
|
assert user is not None
|
|
assert user.id == test_user.id
|
|
assert user.username == test_user.username
|
|
except ImportError:
|
|
pytest.skip("Models not implemented yet")
|
|
|
|
async def test_query_user_by_email(self, db_session, test_user):
|
|
"""Test querying user by email."""
|
|
# Arrange
|
|
if test_user is None:
|
|
pytest.skip("User model not implemented yet")
|
|
|
|
try:
|
|
from tradingagents.api.models import User
|
|
from sqlalchemy import select
|
|
|
|
# Act
|
|
result = await db_session.execute(
|
|
select(User).where(User.email == test_user.email)
|
|
)
|
|
user = result.scalar_one_or_none()
|
|
|
|
# Assert
|
|
assert user is not None
|
|
assert user.email == test_user.email
|
|
except ImportError:
|
|
pytest.skip("Models not implemented yet")
|
|
|
|
async def test_query_strategies_by_user(self, db_session, test_user, test_strategy):
|
|
"""Test querying all strategies for a user."""
|
|
# Arrange
|
|
if test_user is None or test_strategy is None:
|
|
pytest.skip("Models not implemented yet")
|
|
|
|
try:
|
|
from tradingagents.api.models import Strategy
|
|
from sqlalchemy import select
|
|
|
|
# Act
|
|
result = await db_session.execute(
|
|
select(Strategy).where(Strategy.user_id == test_user.id)
|
|
)
|
|
strategies = result.scalars().all()
|
|
|
|
# Assert
|
|
assert len(strategies) >= 1
|
|
assert test_strategy.id in [s.id for s in strategies]
|
|
except ImportError:
|
|
pytest.skip("Models not implemented yet")
|
|
|
|
async def test_query_active_strategies(self, db_session, test_user, multiple_strategies):
|
|
"""Test querying only active strategies."""
|
|
# Arrange
|
|
if test_user is None or not multiple_strategies:
|
|
pytest.skip("Models not implemented yet")
|
|
|
|
try:
|
|
from tradingagents.api.models import Strategy
|
|
from sqlalchemy import select
|
|
|
|
# Act
|
|
result = await db_session.execute(
|
|
select(Strategy).where(
|
|
Strategy.user_id == test_user.id,
|
|
Strategy.is_active == True,
|
|
)
|
|
)
|
|
active_strategies = result.scalars().all()
|
|
|
|
# Assert
|
|
assert len(active_strategies) >= 1
|
|
assert all(s.is_active for s in active_strategies)
|
|
except ImportError:
|
|
pytest.skip("Models not implemented yet")
|
|
|
|
async def test_order_strategies_by_created_at(self, db_session, test_user, multiple_strategies):
|
|
"""Test ordering strategies by creation time."""
|
|
# Arrange
|
|
if test_user is None or not multiple_strategies:
|
|
pytest.skip("Models not implemented yet")
|
|
|
|
try:
|
|
from tradingagents.api.models import Strategy
|
|
from sqlalchemy import select
|
|
|
|
# Act
|
|
result = await db_session.execute(
|
|
select(Strategy)
|
|
.where(Strategy.user_id == test_user.id)
|
|
.order_by(Strategy.created_at.desc())
|
|
)
|
|
strategies = result.scalars().all()
|
|
|
|
# Assert: Sorted by created_at descending
|
|
assert len(strategies) >= 2
|
|
for i in range(len(strategies) - 1):
|
|
assert strategies[i].created_at >= strategies[i + 1].created_at
|
|
except ImportError:
|
|
pytest.skip("Models not implemented yet")
|
|
|
|
async def test_pagination_query(self, db_session, test_user, multiple_strategies):
|
|
"""Test paginated query with limit and offset."""
|
|
# Arrange
|
|
if test_user is None or not multiple_strategies:
|
|
pytest.skip("Models not implemented yet")
|
|
|
|
try:
|
|
from tradingagents.api.models import Strategy
|
|
from sqlalchemy import select
|
|
|
|
# Act: Get first page
|
|
result = await db_session.execute(
|
|
select(Strategy)
|
|
.where(Strategy.user_id == test_user.id)
|
|
.limit(2)
|
|
.offset(0)
|
|
)
|
|
page1 = result.scalars().all()
|
|
|
|
# Act: Get second page
|
|
result = await db_session.execute(
|
|
select(Strategy)
|
|
.where(Strategy.user_id == test_user.id)
|
|
.limit(2)
|
|
.offset(2)
|
|
)
|
|
page2 = result.scalars().all()
|
|
|
|
# Assert: Pages have different strategies
|
|
assert len(page1) <= 2
|
|
if page1 and page2:
|
|
assert page1[0].id != page2[0].id
|
|
except ImportError:
|
|
pytest.skip("Models not implemented yet")
|
|
|
|
|
|
# ============================================================================
|
|
# Edge Cases: Models
|
|
# ============================================================================
|
|
|
|
class TestModelEdgeCases:
|
|
"""Test edge cases in model behavior."""
|
|
|
|
async def test_user_very_long_username(self, db_session):
|
|
"""Test user with very long username."""
|
|
# Arrange
|
|
try:
|
|
from tradingagents.api.models import User
|
|
|
|
user = User(
|
|
username="a" * 500,
|
|
email="long@example.com",
|
|
hashed_password="hash",
|
|
)
|
|
|
|
# Act
|
|
db_session.add(user)
|
|
await db_session.commit()
|
|
|
|
# Assert: Should either succeed or fail with constraint violation
|
|
assert user.id is not None or True # Either way is acceptable
|
|
except Exception:
|
|
# May raise exception if username has length constraint
|
|
pass
|
|
|
|
async def test_strategy_with_unicode_name(self, db_session, test_user):
|
|
"""Test strategy with Unicode characters in name."""
|
|
# Arrange
|
|
if test_user is None:
|
|
pytest.skip("User model not implemented yet")
|
|
|
|
try:
|
|
from tradingagents.api.models import Strategy
|
|
|
|
strategy = Strategy(
|
|
name="策略 测试 🚀",
|
|
description="测试描述",
|
|
user_id=test_user.id,
|
|
)
|
|
|
|
# Act
|
|
db_session.add(strategy)
|
|
await db_session.commit()
|
|
await db_session.refresh(strategy)
|
|
|
|
# Assert
|
|
assert strategy.name == "策略 测试 🚀"
|
|
assert strategy.description == "测试描述"
|
|
except ImportError:
|
|
pytest.skip("Models not implemented yet")
|
|
|
|
async def test_strategy_with_very_deep_json(self, db_session, test_user):
|
|
"""Test strategy with deeply nested JSON parameters."""
|
|
# Arrange
|
|
if test_user is None:
|
|
pytest.skip("User model not implemented yet")
|
|
|
|
try:
|
|
from tradingagents.api.models import Strategy
|
|
|
|
deep_params = {
|
|
"l1": {
|
|
"l2": {
|
|
"l3": {
|
|
"l4": {
|
|
"l5": {"value": "deep"}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
strategy = Strategy(
|
|
name="Deep JSON",
|
|
description="Test",
|
|
parameters=deep_params,
|
|
user_id=test_user.id,
|
|
)
|
|
|
|
# Act
|
|
db_session.add(strategy)
|
|
await db_session.commit()
|
|
await db_session.refresh(strategy)
|
|
|
|
# Assert
|
|
assert strategy.parameters["l1"]["l2"]["l3"]["l4"]["l5"]["value"] == "deep"
|
|
except ImportError:
|
|
pytest.skip("Models not implemented yet")
|
|
|
|
|
|
# ============================================================================
|
|
# Unit Tests: Issue #3 - User Model Enhancements
|
|
# ============================================================================
|
|
|
|
class TestUserModelIssue3:
|
|
"""Test User model enhancements from Issue #3.
|
|
|
|
New fields tested:
|
|
- tax_jurisdiction: Nullable string for user's tax jurisdiction
|
|
- timezone: Nullable string for user's timezone (must be valid IANA timezone)
|
|
- api_key_hash: Nullable string for hashed API key
|
|
- is_verified: Boolean flag for email verification (defaults to False)
|
|
"""
|
|
|
|
async def test_user_tax_jurisdiction_default_none(self, db_session):
|
|
"""Test that tax_jurisdiction defaults to None."""
|
|
# Arrange
|
|
try:
|
|
from tradingagents.api.models import User
|
|
|
|
user = User(
|
|
username="taxuser",
|
|
email="tax@example.com",
|
|
hashed_password="hash",
|
|
)
|
|
|
|
# Act
|
|
db_session.add(user)
|
|
await db_session.commit()
|
|
await db_session.refresh(user)
|
|
|
|
# Assert
|
|
assert hasattr(user, "tax_jurisdiction")
|
|
assert user.tax_jurisdiction is None
|
|
except ImportError:
|
|
pytest.skip("Models not implemented yet")
|
|
|
|
async def test_user_tax_jurisdiction_custom_value(self, db_session):
|
|
"""Test setting custom tax_jurisdiction value."""
|
|
# Arrange
|
|
try:
|
|
from tradingagents.api.models import User
|
|
|
|
user = User(
|
|
username="taxuser2",
|
|
email="tax2@example.com",
|
|
hashed_password="hash",
|
|
tax_jurisdiction="US-CA",
|
|
)
|
|
|
|
# Act
|
|
db_session.add(user)
|
|
await db_session.commit()
|
|
await db_session.refresh(user)
|
|
|
|
# Assert
|
|
assert user.tax_jurisdiction == "US-CA"
|
|
except ImportError:
|
|
pytest.skip("Models not implemented yet")
|
|
|
|
async def test_user_timezone_default_none(self, db_session):
|
|
"""Test that timezone defaults to None."""
|
|
# Arrange
|
|
try:
|
|
from tradingagents.api.models import User
|
|
|
|
user = User(
|
|
username="tzuser",
|
|
email="tz@example.com",
|
|
hashed_password="hash",
|
|
)
|
|
|
|
# Act
|
|
db_session.add(user)
|
|
await db_session.commit()
|
|
await db_session.refresh(user)
|
|
|
|
# Assert
|
|
assert hasattr(user, "timezone")
|
|
assert user.timezone is None
|
|
except ImportError:
|
|
pytest.skip("Models not implemented yet")
|
|
|
|
async def test_user_timezone_valid_value(self, db_session):
|
|
"""Test setting valid timezone value."""
|
|
# Arrange
|
|
try:
|
|
from tradingagents.api.models import User
|
|
|
|
user = User(
|
|
username="tzuser2",
|
|
email="tz2@example.com",
|
|
hashed_password="hash",
|
|
timezone="America/New_York",
|
|
)
|
|
|
|
# Act
|
|
db_session.add(user)
|
|
await db_session.commit()
|
|
await db_session.refresh(user)
|
|
|
|
# Assert
|
|
assert user.timezone == "America/New_York"
|
|
except ImportError:
|
|
pytest.skip("Models not implemented yet")
|
|
|
|
async def test_user_timezone_various_timezones(self, db_session):
|
|
"""Test various valid IANA timezone values."""
|
|
# Arrange
|
|
try:
|
|
from tradingagents.api.models import User
|
|
|
|
timezones = [
|
|
"UTC",
|
|
"America/Los_Angeles",
|
|
"Europe/London",
|
|
"Asia/Tokyo",
|
|
"Australia/Sydney",
|
|
]
|
|
|
|
for i, tz in enumerate(timezones):
|
|
user = User(
|
|
username=f"tzuser_{i}",
|
|
email=f"tz{i}@example.com",
|
|
hashed_password="hash",
|
|
timezone=tz,
|
|
)
|
|
|
|
# Act
|
|
db_session.add(user)
|
|
await db_session.commit()
|
|
await db_session.refresh(user)
|
|
|
|
# Assert
|
|
assert user.timezone == tz
|
|
except ImportError:
|
|
pytest.skip("Models not implemented yet")
|
|
|
|
async def test_user_api_key_hash_default_none(self, db_session):
|
|
"""Test that api_key_hash defaults to None."""
|
|
# Arrange
|
|
try:
|
|
from tradingagents.api.models import User
|
|
|
|
user = User(
|
|
username="apiuser",
|
|
email="api@example.com",
|
|
hashed_password="hash",
|
|
)
|
|
|
|
# Act
|
|
db_session.add(user)
|
|
await db_session.commit()
|
|
await db_session.refresh(user)
|
|
|
|
# Assert
|
|
assert hasattr(user, "api_key_hash")
|
|
assert user.api_key_hash is None
|
|
except ImportError:
|
|
pytest.skip("Models not implemented yet")
|
|
|
|
async def test_user_api_key_hash_custom_value(self, db_session):
|
|
"""Test setting api_key_hash value."""
|
|
# Arrange
|
|
try:
|
|
from tradingagents.api.models import User
|
|
|
|
# Simulate hashed API key (bcrypt hash format)
|
|
api_key_hash = "$2b$12$LQv3c1yqBWVHxkd0LHAkCOYz6TtxMQJqhN8/LewY5ygOy3f3K0E6O"
|
|
|
|
user = User(
|
|
username="apiuser2",
|
|
email="api2@example.com",
|
|
hashed_password="hash",
|
|
api_key_hash=api_key_hash,
|
|
)
|
|
|
|
# Act
|
|
db_session.add(user)
|
|
await db_session.commit()
|
|
await db_session.refresh(user)
|
|
|
|
# Assert
|
|
assert user.api_key_hash == api_key_hash
|
|
assert user.api_key_hash.startswith("$2b$")
|
|
except ImportError:
|
|
pytest.skip("Models not implemented yet")
|
|
|
|
async def test_user_is_verified_default_false(self, db_session):
|
|
"""Test that is_verified defaults to False."""
|
|
# Arrange
|
|
try:
|
|
from tradingagents.api.models import User
|
|
|
|
user = User(
|
|
username="verifyuser",
|
|
email="verify@example.com",
|
|
hashed_password="hash",
|
|
)
|
|
|
|
# Act
|
|
db_session.add(user)
|
|
await db_session.commit()
|
|
await db_session.refresh(user)
|
|
|
|
# Assert
|
|
assert hasattr(user, "is_verified")
|
|
assert user.is_verified is False
|
|
except ImportError:
|
|
pytest.skip("Models not implemented yet")
|
|
|
|
async def test_user_is_verified_can_be_true(self, db_session):
|
|
"""Test setting is_verified to True."""
|
|
# Arrange
|
|
try:
|
|
from tradingagents.api.models import User
|
|
|
|
user = User(
|
|
username="verifyuser2",
|
|
email="verify2@example.com",
|
|
hashed_password="hash",
|
|
is_verified=True,
|
|
)
|
|
|
|
# Act
|
|
db_session.add(user)
|
|
await db_session.commit()
|
|
await db_session.refresh(user)
|
|
|
|
# Assert
|
|
assert user.is_verified is True
|
|
except ImportError:
|
|
pytest.skip("Models not implemented yet")
|
|
|
|
async def test_user_all_new_fields_together(self, db_session):
|
|
"""Test creating user with all Issue #3 fields."""
|
|
# Arrange
|
|
try:
|
|
from tradingagents.api.models import User
|
|
|
|
user = User(
|
|
username="fulluser",
|
|
email="full@example.com",
|
|
hashed_password="hash",
|
|
tax_jurisdiction="US-NY",
|
|
timezone="America/New_York",
|
|
api_key_hash="$2b$12$hashedapikey123",
|
|
is_verified=True,
|
|
)
|
|
|
|
# Act
|
|
db_session.add(user)
|
|
await db_session.commit()
|
|
await db_session.refresh(user)
|
|
|
|
# Assert
|
|
assert user.tax_jurisdiction == "US-NY"
|
|
assert user.timezone == "America/New_York"
|
|
assert user.api_key_hash == "$2b$12$hashedapikey123"
|
|
assert user.is_verified is True
|
|
except ImportError:
|
|
pytest.skip("Models not implemented yet")
|
|
|
|
async def test_user_update_timezone(self, db_session):
|
|
"""Test updating user's timezone."""
|
|
# Arrange
|
|
try:
|
|
from tradingagents.api.models import User
|
|
|
|
user = User(
|
|
username="updatetz",
|
|
email="updatetz@example.com",
|
|
hashed_password="hash",
|
|
timezone="UTC",
|
|
)
|
|
db_session.add(user)
|
|
await db_session.commit()
|
|
await db_session.refresh(user)
|
|
|
|
# Act: Update timezone
|
|
user.timezone = "America/Los_Angeles"
|
|
await db_session.commit()
|
|
await db_session.refresh(user)
|
|
|
|
# Assert
|
|
assert user.timezone == "America/Los_Angeles"
|
|
except ImportError:
|
|
pytest.skip("Models not implemented yet")
|
|
|
|
async def test_user_update_tax_jurisdiction(self, db_session):
|
|
"""Test updating user's tax_jurisdiction."""
|
|
# Arrange
|
|
try:
|
|
from tradingagents.api.models import User
|
|
|
|
user = User(
|
|
username="updatetax",
|
|
email="updatetax@example.com",
|
|
hashed_password="hash",
|
|
tax_jurisdiction="US-CA",
|
|
)
|
|
db_session.add(user)
|
|
await db_session.commit()
|
|
await db_session.refresh(user)
|
|
|
|
# Act: Update tax jurisdiction
|
|
user.tax_jurisdiction = "US-TX"
|
|
await db_session.commit()
|
|
await db_session.refresh(user)
|
|
|
|
# Assert
|
|
assert user.tax_jurisdiction == "US-TX"
|
|
except ImportError:
|
|
pytest.skip("Models not implemented yet")
|
|
|
|
async def test_user_verify_email(self, db_session):
|
|
"""Test verifying user's email."""
|
|
# Arrange
|
|
try:
|
|
from tradingagents.api.models import User
|
|
|
|
user = User(
|
|
username="toverify",
|
|
email="toverify@example.com",
|
|
hashed_password="hash",
|
|
is_verified=False,
|
|
)
|
|
db_session.add(user)
|
|
await db_session.commit()
|
|
await db_session.refresh(user)
|
|
|
|
assert user.is_verified is False
|
|
|
|
# Act: Verify email
|
|
user.is_verified = True
|
|
await db_session.commit()
|
|
await db_session.refresh(user)
|
|
|
|
# Assert
|
|
assert user.is_verified is True
|
|
except ImportError:
|
|
pytest.skip("Models not implemented yet")
|
|
|
|
async def test_user_query_by_timezone(self, db_session):
|
|
"""Test querying users by timezone."""
|
|
# Arrange
|
|
try:
|
|
from tradingagents.api.models import User
|
|
from sqlalchemy import select
|
|
|
|
# Create users with different timezones
|
|
user1 = User(
|
|
username="user_utc",
|
|
email="utc@example.com",
|
|
hashed_password="hash",
|
|
timezone="UTC",
|
|
)
|
|
user2 = User(
|
|
username="user_ny",
|
|
email="ny@example.com",
|
|
hashed_password="hash",
|
|
timezone="America/New_York",
|
|
)
|
|
user3 = User(
|
|
username="user_utc2",
|
|
email="utc2@example.com",
|
|
hashed_password="hash",
|
|
timezone="UTC",
|
|
)
|
|
|
|
db_session.add_all([user1, user2, user3])
|
|
await db_session.commit()
|
|
|
|
# Act: Query users in UTC timezone
|
|
result = await db_session.execute(
|
|
select(User).where(User.timezone == "UTC")
|
|
)
|
|
utc_users = result.scalars().all()
|
|
|
|
# Assert
|
|
assert len(utc_users) == 2
|
|
assert all(u.timezone == "UTC" for u in utc_users)
|
|
except ImportError:
|
|
pytest.skip("Models not implemented yet")
|
|
|
|
async def test_user_query_verified_users(self, db_session):
|
|
"""Test querying only verified users."""
|
|
# Arrange
|
|
try:
|
|
from tradingagents.api.models import User
|
|
from sqlalchemy import select
|
|
|
|
# Create verified and unverified users
|
|
verified_user = User(
|
|
username="verified",
|
|
email="verified@example.com",
|
|
hashed_password="hash",
|
|
is_verified=True,
|
|
)
|
|
unverified_user = User(
|
|
username="unverified",
|
|
email="unverified@example.com",
|
|
hashed_password="hash",
|
|
is_verified=False,
|
|
)
|
|
|
|
db_session.add_all([verified_user, unverified_user])
|
|
await db_session.commit()
|
|
|
|
# Act: Query verified users
|
|
result = await db_session.execute(
|
|
select(User).where(User.is_verified == True)
|
|
)
|
|
verified_users = result.scalars().all()
|
|
|
|
# Assert
|
|
assert len(verified_users) >= 1
|
|
assert all(u.is_verified for u in verified_users)
|
|
except ImportError:
|
|
pytest.skip("Models not implemented yet")
|
|
|
|
async def test_user_api_key_hash_nullable(self, db_session):
|
|
"""Test that api_key_hash can be set to None."""
|
|
# Arrange
|
|
try:
|
|
from tradingagents.api.models import User
|
|
|
|
user = User(
|
|
username="apinull",
|
|
email="apinull@example.com",
|
|
hashed_password="hash",
|
|
api_key_hash="$2b$12$somehash",
|
|
)
|
|
db_session.add(user)
|
|
await db_session.commit()
|
|
await db_session.refresh(user)
|
|
|
|
# Act: Remove API key
|
|
user.api_key_hash = None
|
|
await db_session.commit()
|
|
await db_session.refresh(user)
|
|
|
|
# Assert
|
|
assert user.api_key_hash is None
|
|
except ImportError:
|
|
pytest.skip("Models not implemented yet")
|