"""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