Skip to main content
Glama
test_audio_player.py9.68 kB
""" Tests for audio player functionality. """ import asyncio import pytest from unittest.mock import Mock, patch, AsyncMock from pathlib import Path from mcp_server_play_sound.audio_player import ( AudioPlayer, AFPlayBackend, SimpleAudioBackend, PlaybackStatus, PlaybackResult ) from mcp_server_play_sound.config import ServerConfig class TestAFPlayBackend: """Test AFPlay backend functionality.""" def test_is_available_macos(self): """Test AFPlay availability detection on macOS.""" backend = AFPlayBackend() with patch('sys.platform', 'darwin'), \ patch('subprocess.run') as mock_run: mock_run.return_value.returncode = 0 assert backend.is_available() is True mock_run.return_value.returncode = 1 assert backend.is_available() is False def test_is_available_non_macos(self): """Test AFPlay availability on non-macOS platforms.""" backend = AFPlayBackend() with patch('sys.platform', 'linux'): assert backend.is_available() is False @pytest.mark.asyncio async def test_play_file_not_found(self): """Test playing non-existent file.""" backend = AFPlayBackend() non_existent_path = Path("/non/existent/file.wav") result = await backend.play(non_existent_path) assert result.status == PlaybackStatus.FILE_NOT_FOUND assert "not found" in result.message.lower() assert result.backend_used == "afplay" @pytest.mark.asyncio async def test_play_success(self): """Test successful audio playback.""" backend = AFPlayBackend() test_file = Path("test.wav") with patch('pathlib.Path.exists', return_value=True), \ patch('asyncio.create_subprocess_exec') as mock_subprocess: # Mock successful process mock_process = AsyncMock() mock_process.communicate.return_value = (b"", b"") mock_process.returncode = 0 mock_subprocess.return_value = mock_process result = await backend.play(test_file) assert result.status == PlaybackStatus.SUCCESS assert result.backend_used == "afplay" assert "successfully" in result.message.lower() @pytest.mark.asyncio async def test_play_timeout(self): """Test playback timeout.""" backend = AFPlayBackend() test_file = Path("test.wav") with patch('pathlib.Path.exists', return_value=True), \ patch('asyncio.create_subprocess_exec') as mock_subprocess: # Mock process that times out mock_process = AsyncMock() mock_process.communicate.side_effect = asyncio.TimeoutError() mock_process.kill = AsyncMock() mock_process.wait = AsyncMock() mock_subprocess.return_value = mock_process result = await backend.play(test_file, timeout=1) assert result.status == PlaybackStatus.TIMEOUT assert result.backend_used == "afplay" mock_process.kill.assert_called_once() class TestSimpleAudioBackend: """Test SimpleAudio backend functionality.""" def test_is_available_with_simpleaudio(self): """Test availability when simpleaudio is installed.""" backend = SimpleAudioBackend() with patch('builtins.__import__') as mock_import: mock_import.return_value = Mock() assert backend.is_available() is True def test_is_available_without_simpleaudio(self): """Test availability when simpleaudio is not installed.""" backend = SimpleAudioBackend() with patch('builtins.__import__', side_effect=ImportError()): assert backend.is_available() is False @pytest.mark.asyncio async def test_play_file_not_found(self): """Test playing non-existent file.""" backend = SimpleAudioBackend() non_existent_path = Path("/non/existent/file.wav") result = await backend.play(non_existent_path) assert result.status == PlaybackStatus.FILE_NOT_FOUND assert "not found" in result.message.lower() assert result.backend_used == "simpleaudio" class TestAudioPlayer: """Test AudioPlayer functionality.""" def test_init_with_backends(self): """Test AudioPlayer initialization with available backends.""" config = ServerConfig() with patch.object(AFPlayBackend, 'is_available', return_value=True), \ patch.object(SimpleAudioBackend, 'is_available', return_value=True): player = AudioPlayer(config) assert len(player.backends) == 2 assert any(b.name == "afplay" for b in player.backends) assert any(b.name == "simpleaudio" for b in player.backends) def test_init_no_backends(self): """Test AudioPlayer initialization with no available backends.""" config = ServerConfig() with patch.object(AFPlayBackend, 'is_available', return_value=False), \ patch.object(SimpleAudioBackend, 'is_available', return_value=False): player = AudioPlayer(config) assert len(player.backends) == 0 def test_get_default_sound_path(self): """Test default sound path detection.""" config = ServerConfig() player = AudioPlayer(config) # The path should end with the expected structure assert str(player._default_sound_path).endswith("assets/notification.wav") @pytest.mark.asyncio async def test_play_notification_default(self): """Test playing default notification sound.""" config = ServerConfig() with patch.object(AFPlayBackend, 'is_available', return_value=True): player = AudioPlayer(config) # Mock the backend play method mock_result = PlaybackResult( status=PlaybackStatus.SUCCESS, message="Test success", backend_used="afplay" ) with patch.object(player.backends[0], 'play', return_value=mock_result): result = await player.play_notification() assert result.status == PlaybackStatus.SUCCESS assert result.backend_used == "afplay" @pytest.mark.asyncio async def test_play_notification_with_fallback(self): """Test playing notification with fallback to default sound.""" config = ServerConfig(enable_fallback=True) with patch.object(AFPlayBackend, 'is_available', return_value=True): player = AudioPlayer(config) # Mock custom file failure and default file success failed_result = PlaybackResult( status=PlaybackStatus.FAILED, message="Custom file failed", backend_used="afplay" ) success_result = PlaybackResult( status=PlaybackStatus.SUCCESS, message="Default file played", backend_used="afplay" ) with patch.object(player.backends[0], 'play', side_effect=[failed_result, success_result]): result = await player.play_notification("/custom/path.wav") assert result.status == PlaybackStatus.FALLBACK_USED assert result.fallback_used is True assert "Custom audio failed" in result.message @pytest.mark.asyncio async def test_play_notification_no_backends(self): """Test playing notification with no available backends.""" config = ServerConfig() with patch.object(AFPlayBackend, 'is_available', return_value=False), \ patch.object(SimpleAudioBackend, 'is_available', return_value=False): player = AudioPlayer(config) result = await player.play_notification() assert result.status == PlaybackStatus.FAILED assert "No audio backends available" in result.message @pytest.mark.asyncio async def test_play_with_multiple_backends_fallback(self): """Test trying multiple backends when first fails.""" config = ServerConfig() with patch.object(AFPlayBackend, 'is_available', return_value=True), \ patch.object(SimpleAudioBackend, 'is_available', return_value=True): player = AudioPlayer(config) # First backend fails, second succeeds failed_result = PlaybackResult( status=PlaybackStatus.FAILED, message="First backend failed", backend_used="afplay" ) success_result = PlaybackResult( status=PlaybackStatus.SUCCESS, message="Second backend succeeded", backend_used="simpleaudio" ) with patch.object(player.backends[0], 'play', return_value=failed_result), \ patch.object(player.backends[1], 'play', return_value=success_result): result = await player._play_with_backends(Path("test.wav")) assert result.status == PlaybackStatus.SUCCESS assert result.backend_used == "simpleaudio"

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/davidteren/play-sound-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server