179 lines
5.0 KiB
Python
179 lines
5.0 KiB
Python
"""Test helper utilities for reducing code duplication."""
|
|
|
|
from contextlib import asynccontextmanager
|
|
from typing import Any, Dict, Optional, Type, TypeVar
|
|
from unittest.mock import AsyncMock
|
|
|
|
from fastapi import FastAPI
|
|
from sqlmodel.ext.asyncio.session import AsyncSession
|
|
from sqlmodel import SQLModel
|
|
|
|
from app.models.user import User
|
|
from app.utils.auth import JWTUtils
|
|
|
|
T = TypeVar("T", bound=SQLModel)
|
|
|
|
|
|
def create_jwt_token_data(user: User) -> Dict[str, str]:
|
|
"""Create standardized JWT token data dictionary for a user.
|
|
|
|
Args:
|
|
user: User object to create token data for
|
|
|
|
Returns:
|
|
Dictionary with sub, email, and role fields
|
|
"""
|
|
return {
|
|
"sub": str(user.id),
|
|
"email": user.email,
|
|
"role": user.role,
|
|
}
|
|
|
|
|
|
def create_access_token_for_user(user: User) -> str:
|
|
"""Create an access token for a user using standardized token data.
|
|
|
|
Args:
|
|
user: User object to create token for
|
|
|
|
Returns:
|
|
JWT access token string
|
|
"""
|
|
token_data = create_jwt_token_data(user)
|
|
return JWTUtils.create_access_token(token_data)
|
|
|
|
|
|
async def create_and_save_model(
|
|
session: AsyncSession,
|
|
model_class: Type[T],
|
|
**kwargs: Any
|
|
) -> T:
|
|
"""Create, save, and refresh a model instance.
|
|
|
|
This consolidates the common pattern of:
|
|
- model = ModelClass(**kwargs)
|
|
- session.add(model)
|
|
- await session.commit()
|
|
- await session.refresh(model)
|
|
|
|
Args:
|
|
session: Database session
|
|
model_class: SQLModel class to instantiate
|
|
**kwargs: Arguments to pass to model constructor
|
|
|
|
Returns:
|
|
Created and refreshed model instance
|
|
"""
|
|
instance = model_class(**kwargs)
|
|
session.add(instance)
|
|
await session.commit()
|
|
await session.refresh(instance)
|
|
return instance
|
|
|
|
|
|
@asynccontextmanager
|
|
async def override_dependencies(
|
|
app: FastAPI,
|
|
overrides: Dict[Any, Any]
|
|
):
|
|
"""Context manager for FastAPI dependency overrides with automatic cleanup.
|
|
|
|
Args:
|
|
app: FastAPI application instance
|
|
overrides: Dictionary mapping dependency functions to mock implementations
|
|
|
|
Usage:
|
|
async with override_dependencies(test_app, {
|
|
get_service: lambda: mock_service,
|
|
get_repo: lambda: mock_repo
|
|
}):
|
|
# Test code here
|
|
pass
|
|
# Dependencies automatically cleaned up
|
|
"""
|
|
# Apply overrides
|
|
for dependency, override in overrides.items():
|
|
app.dependency_overrides[dependency] = override
|
|
|
|
try:
|
|
yield
|
|
finally:
|
|
# Clean up overrides
|
|
for dependency in overrides:
|
|
app.dependency_overrides.pop(dependency, None)
|
|
|
|
|
|
def create_mock_vlc_services() -> Dict[str, AsyncMock]:
|
|
"""Create standard set of mocked VLC-related services.
|
|
|
|
Returns:
|
|
Dictionary with mocked vlc_service, sound_repository, and credit_service
|
|
"""
|
|
return {
|
|
"vlc_service": AsyncMock(),
|
|
"sound_repository": AsyncMock(),
|
|
"credit_service": AsyncMock(),
|
|
}
|
|
|
|
|
|
def configure_mock_sound_play_success(
|
|
mocks: Dict[str, AsyncMock],
|
|
sound_data: Dict[str, Any]
|
|
) -> None:
|
|
"""Configure mocks for successful sound playback scenario.
|
|
|
|
Args:
|
|
mocks: Dictionary of mock services from create_mock_vlc_services()
|
|
sound_data: Dictionary with sound properties (id, name, etc.)
|
|
"""
|
|
from app.models.sound import Sound
|
|
|
|
mock_sound = Sound(**sound_data)
|
|
|
|
# Configure repository mock
|
|
mocks["sound_repository"].get_by_id.return_value = mock_sound
|
|
|
|
# Configure credit service mocks
|
|
mocks["credit_service"].validate_and_reserve_credits.return_value = None
|
|
mocks["credit_service"].deduct_credits.return_value = None
|
|
|
|
# Configure VLC service mock
|
|
mocks["vlc_service"].play_sound.return_value = True
|
|
|
|
|
|
def create_mock_vlc_stop_result(
|
|
success: bool = True,
|
|
processes_found: int = 3,
|
|
processes_killed: int = 3,
|
|
processes_remaining: int = 0,
|
|
message: Optional[str] = None,
|
|
error: Optional[str] = None
|
|
) -> Dict[str, Any]:
|
|
"""Create standardized VLC stop operation result.
|
|
|
|
Args:
|
|
success: Whether operation succeeded
|
|
processes_found: Number of VLC processes found
|
|
processes_killed: Number of processes successfully killed
|
|
processes_remaining: Number of processes still running
|
|
message: Success/status message
|
|
error: Error message (for failed operations)
|
|
|
|
Returns:
|
|
Dictionary with VLC stop operation result
|
|
"""
|
|
result = {
|
|
"success": success,
|
|
"processes_found": processes_found,
|
|
"processes_killed": processes_killed,
|
|
}
|
|
|
|
if not success:
|
|
result["error"] = error or "Command failed"
|
|
result["message"] = message or "Failed to stop VLC processes"
|
|
else:
|
|
# Always include processes_remaining for successful operations
|
|
result["processes_remaining"] = processes_remaining
|
|
result["message"] = message or f"Killed {processes_killed} VLC processes"
|
|
|
|
return result |