"""Tests for task handlers.""" import uuid from unittest.mock import AsyncMock, MagicMock, patch import pytest from sqlmodel.ext.asyncio.session import AsyncSession from app.models.scheduled_task import ScheduledTask, TaskType from app.services.task_handlers import TaskExecutionError, TaskHandlerRegistry class TestTaskHandlerRegistry: """Test cases for task handler registry.""" @pytest.fixture def mock_credit_service(self): """Create mock credit service.""" return AsyncMock() @pytest.fixture def mock_player_service(self): """Create mock player service.""" return MagicMock() @pytest.fixture def task_registry( self, db_session: AsyncSession, mock_credit_service, mock_player_service, ) -> TaskHandlerRegistry: """Create task handler registry fixture.""" return TaskHandlerRegistry( db_session, mock_credit_service, mock_player_service, ) async def test_execute_task_unknown_type( self, task_registry: TaskHandlerRegistry, ): """Test executing task with unknown type.""" # Create task with invalid type task = ScheduledTask( name="Unknown Task", task_type="UNKNOWN_TYPE", # Invalid type scheduled_at=datetime.utcnow(), ) with pytest.raises(TaskExecutionError, match="No handler registered"): await task_registry.execute_task(task) async def test_handle_credit_recharge_all_users( self, task_registry: TaskHandlerRegistry, mock_credit_service, ): """Test handling credit recharge for all users.""" task = ScheduledTask( name="Daily Credit Recharge", task_type=TaskType.CREDIT_RECHARGE, scheduled_at=datetime.utcnow(), parameters={}, ) mock_credit_service.recharge_all_users_credits.return_value = { "users_recharged": 10, "total_credits": 1000, } await task_registry.execute_task(task) mock_credit_service.recharge_all_users_credits.assert_called_once() async def test_handle_credit_recharge_specific_user( self, task_registry: TaskHandlerRegistry, mock_credit_service, test_user_id: uuid.UUID, ): """Test handling credit recharge for specific user.""" task = ScheduledTask( name="User Credit Recharge", task_type=TaskType.CREDIT_RECHARGE, scheduled_at=datetime.utcnow(), parameters={"user_id": str(test_user_id)}, ) mock_credit_service.recharge_user_credits.return_value = { "user_id": str(test_user_id), "credits_added": 100, } await task_registry.execute_task(task) mock_credit_service.recharge_user_credits.assert_called_once_with(test_user_id) async def test_handle_credit_recharge_uuid_user_id( self, task_registry: TaskHandlerRegistry, mock_credit_service, test_user_id: uuid.UUID, ): """Test handling credit recharge with UUID user_id parameter.""" task = ScheduledTask( name="User Credit Recharge", task_type=TaskType.CREDIT_RECHARGE, scheduled_at=datetime.utcnow(), parameters={"user_id": test_user_id}, # UUID object instead of string ) await task_registry.execute_task(task) mock_credit_service.recharge_user_credits.assert_called_once_with(test_user_id) async def test_handle_play_sound_success( self, task_registry: TaskHandlerRegistry, test_sound_id: uuid.UUID, ): """Test successful play sound task handling.""" task = ScheduledTask( name="Play Sound", task_type=TaskType.PLAY_SOUND, scheduled_at=datetime.utcnow(), parameters={"sound_id": str(test_sound_id)}, ) # Mock sound repository mock_sound = MagicMock() mock_sound.id = test_sound_id mock_sound.filename = "test_sound.mp3" with patch.object(task_registry.sound_repository, "get_by_id", return_value=mock_sound): with patch("app.services.vlc_player.VLCPlayerService") as mock_vlc_class: mock_vlc_service = AsyncMock() mock_vlc_class.return_value = mock_vlc_service await task_registry.execute_task(task) task_registry.sound_repository.get_by_id.assert_called_once_with(test_sound_id) mock_vlc_service.play_sound.assert_called_once_with(mock_sound) async def test_handle_play_sound_missing_sound_id( self, task_registry: TaskHandlerRegistry, ): """Test play sound task with missing sound_id parameter.""" task = ScheduledTask( name="Play Sound", task_type=TaskType.PLAY_SOUND, scheduled_at=datetime.utcnow(), parameters={}, # Missing sound_id ) with pytest.raises(TaskExecutionError, match="sound_id parameter is required"): await task_registry.execute_task(task) async def test_handle_play_sound_invalid_sound_id( self, task_registry: TaskHandlerRegistry, ): """Test play sound task with invalid sound_id parameter.""" task = ScheduledTask( name="Play Sound", task_type=TaskType.PLAY_SOUND, scheduled_at=datetime.utcnow(), parameters={"sound_id": "invalid-uuid"}, ) with pytest.raises(TaskExecutionError, match="Invalid sound_id format"): await task_registry.execute_task(task) async def test_handle_play_sound_not_found( self, task_registry: TaskHandlerRegistry, test_sound_id: uuid.UUID, ): """Test play sound task with non-existent sound.""" task = ScheduledTask( name="Play Sound", task_type=TaskType.PLAY_SOUND, scheduled_at=datetime.utcnow(), parameters={"sound_id": str(test_sound_id)}, ) with patch.object(task_registry.sound_repository, "get_by_id", return_value=None): with pytest.raises(TaskExecutionError, match="Sound not found"): await task_registry.execute_task(task) async def test_handle_play_sound_uuid_parameter( self, task_registry: TaskHandlerRegistry, test_sound_id: uuid.UUID, ): """Test play sound task with UUID parameter (not string).""" task = ScheduledTask( name="Play Sound", task_type=TaskType.PLAY_SOUND, scheduled_at=datetime.utcnow(), parameters={"sound_id": test_sound_id}, # UUID object ) mock_sound = MagicMock() mock_sound.filename = "test_sound.mp3" with patch.object(task_registry.sound_repository, "get_by_id", return_value=mock_sound): with patch("app.services.vlc_player.VLCPlayerService") as mock_vlc_class: mock_vlc_service = AsyncMock() mock_vlc_class.return_value = mock_vlc_service await task_registry.execute_task(task) task_registry.sound_repository.get_by_id.assert_called_once_with(test_sound_id) async def test_handle_play_playlist_success( self, task_registry: TaskHandlerRegistry, mock_player_service, test_playlist_id: uuid.UUID, ): """Test successful play playlist task handling.""" task = ScheduledTask( name="Play Playlist", task_type=TaskType.PLAY_PLAYLIST, scheduled_at=datetime.utcnow(), parameters={ "playlist_id": str(test_playlist_id), "play_mode": "loop", "shuffle": True, }, ) # Mock playlist repository mock_playlist = MagicMock() mock_playlist.id = test_playlist_id mock_playlist.name = "Test Playlist" with patch.object(task_registry.playlist_repository, "get_by_id", return_value=mock_playlist): await task_registry.execute_task(task) task_registry.playlist_repository.get_by_id.assert_called_once_with(test_playlist_id) mock_player_service.load_playlist.assert_called_once_with(test_playlist_id) mock_player_service.set_mode.assert_called_once_with("loop") mock_player_service.set_shuffle.assert_called_once_with(True) mock_player_service.play.assert_called_once() async def test_handle_play_playlist_minimal_parameters( self, task_registry: TaskHandlerRegistry, mock_player_service, test_playlist_id: uuid.UUID, ): """Test play playlist task with minimal parameters.""" task = ScheduledTask( name="Play Playlist", task_type=TaskType.PLAY_PLAYLIST, scheduled_at=datetime.utcnow(), parameters={"playlist_id": str(test_playlist_id)}, ) mock_playlist = MagicMock() mock_playlist.name = "Test Playlist" with patch.object(task_registry.playlist_repository, "get_by_id", return_value=mock_playlist): await task_registry.execute_task(task) # Should use default values mock_player_service.set_mode.assert_called_once_with("continuous") mock_player_service.set_shuffle.assert_called_once_with(False) async def test_handle_play_playlist_missing_playlist_id( self, task_registry: TaskHandlerRegistry, ): """Test play playlist task with missing playlist_id parameter.""" task = ScheduledTask( name="Play Playlist", task_type=TaskType.PLAY_PLAYLIST, scheduled_at=datetime.utcnow(), parameters={}, # Missing playlist_id ) with pytest.raises(TaskExecutionError, match="playlist_id parameter is required"): await task_registry.execute_task(task) async def test_handle_play_playlist_invalid_playlist_id( self, task_registry: TaskHandlerRegistry, ): """Test play playlist task with invalid playlist_id parameter.""" task = ScheduledTask( name="Play Playlist", task_type=TaskType.PLAY_PLAYLIST, scheduled_at=datetime.utcnow(), parameters={"playlist_id": "invalid-uuid"}, ) with pytest.raises(TaskExecutionError, match="Invalid playlist_id format"): await task_registry.execute_task(task) async def test_handle_play_playlist_not_found( self, task_registry: TaskHandlerRegistry, test_playlist_id: uuid.UUID, ): """Test play playlist task with non-existent playlist.""" task = ScheduledTask( name="Play Playlist", task_type=TaskType.PLAY_PLAYLIST, scheduled_at=datetime.utcnow(), parameters={"playlist_id": str(test_playlist_id)}, ) with patch.object(task_registry.playlist_repository, "get_by_id", return_value=None): with pytest.raises(TaskExecutionError, match="Playlist not found"): await task_registry.execute_task(task) async def test_handle_play_playlist_valid_play_modes( self, task_registry: TaskHandlerRegistry, mock_player_service, test_playlist_id: uuid.UUID, ): """Test play playlist task with various valid play modes.""" mock_playlist = MagicMock() mock_playlist.name = "Test Playlist" valid_modes = ["continuous", "loop", "loop_one", "random", "single"] for mode in valid_modes: task = ScheduledTask( name="Play Playlist", task_type=TaskType.PLAY_PLAYLIST, scheduled_at=datetime.utcnow(), parameters={ "playlist_id": str(test_playlist_id), "play_mode": mode, }, ) with patch.object(task_registry.playlist_repository, "get_by_id", return_value=mock_playlist): await task_registry.execute_task(task) mock_player_service.set_mode.assert_called_with(mode) # Reset mock for next iteration mock_player_service.reset_mock() async def test_handle_play_playlist_invalid_play_mode( self, task_registry: TaskHandlerRegistry, mock_player_service, test_playlist_id: uuid.UUID, ): """Test play playlist task with invalid play mode.""" task = ScheduledTask( name="Play Playlist", task_type=TaskType.PLAY_PLAYLIST, scheduled_at=datetime.utcnow(), parameters={ "playlist_id": str(test_playlist_id), "play_mode": "invalid_mode", }, ) mock_playlist = MagicMock() mock_playlist.name = "Test Playlist" with patch.object(task_registry.playlist_repository, "get_by_id", return_value=mock_playlist): await task_registry.execute_task(task) # Should not call set_mode for invalid mode mock_player_service.set_mode.assert_not_called() # But should still load playlist and play mock_player_service.load_playlist.assert_called_once() mock_player_service.play.assert_called_once() async def test_task_execution_exception_handling( self, task_registry: TaskHandlerRegistry, mock_credit_service, ): """Test exception handling during task execution.""" task = ScheduledTask( name="Failing Task", task_type=TaskType.CREDIT_RECHARGE, scheduled_at=datetime.utcnow(), parameters={}, ) # Make credit service raise an exception mock_credit_service.recharge_all_users_credits.side_effect = Exception("Service error") with pytest.raises(TaskExecutionError, match="Task execution failed: Service error"): await task_registry.execute_task(task) async def test_task_registry_initialization( self, db_session: AsyncSession, mock_credit_service, mock_player_service, ): """Test task registry initialization.""" registry = TaskHandlerRegistry( db_session, mock_credit_service, mock_player_service, ) assert registry.db_session == db_session assert registry.credit_service == mock_credit_service assert registry.player_service == mock_player_service assert registry.sound_repository is not None assert registry.playlist_repository is not None # Check all handlers are registered expected_handlers = { TaskType.CREDIT_RECHARGE, TaskType.PLAY_SOUND, TaskType.PLAY_PLAYLIST, } assert set(registry._handlers.keys()) == expected_handlers