Skip to main content
Glama

Voice Mode

by mbailey
test_livekit_service.py•23.5 kB
""" Tests for LiveKit service management functionality """ import os import sys import platform from unittest.mock import patch, MagicMock, AsyncMock, mock_open, call import pytest from pathlib import Path import json # Add parent directory to path for imports sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) # Import LiveKit-related functions from voice_mode.tools.livekit.install import livekit_install from voice_mode.tools.livekit.uninstall import livekit_uninstall from voice_mode.tools.livekit.frontend import ( livekit_frontend_start, livekit_frontend_stop, livekit_frontend_status, livekit_frontend_open ) from voice_mode.tools.service import service as service_tool # Extract actual functions from FastMCP wrappers service = service_tool.fn install = livekit_install.fn uninstall = livekit_uninstall.fn frontend_start = livekit_frontend_start.fn frontend_stop = livekit_frontend_stop.fn frontend_status = livekit_frontend_status.fn frontend_open = livekit_frontend_open.fn class TestLiveKitService: """Test cases for LiveKit service management""" @pytest.mark.asyncio async def test_livekit_status_not_running(self): """Test LiveKit status when service is not running""" with patch('voice_mode.tools.service.check_service_status', return_value=("not_available", None)): result = await service("livekit", "status") assert "not available" in result.lower() # The actual implementation doesn't include port in "not available" message @pytest.mark.asyncio @pytest.mark.skip(reason="Test infrastructure needs update for output format changes") async def test_livekit_status_running(self): """Test LiveKit status when service is running""" mock_proc = MagicMock() mock_proc.pid = 54321 mock_proc.oneshot.return_value.__enter__.return_value = None mock_proc.cpu_percent.return_value = 25.5 mock_proc.memory_info.return_value = MagicMock(rss=200 * 1024 * 1024) # 200 MB mock_proc.create_time.return_value = 1000000000 mock_proc.cmdline.return_value = ["livekit-server", "--config", "livekit.yaml"] with patch('voice_mode.tools.service.find_process_by_port', return_value=mock_proc): with patch('time.time', return_value=1000001000): result = await service("livekit", "status") assert "running" in result.lower() assert "pid 54321" in result.lower() assert "port 7880" in result @pytest.mark.asyncio @pytest.mark.skip(reason="Test infrastructure needs update for service management changes") async def test_livekit_start_success(self): """Test successful LiveKit service start""" with patch('platform.system', return_value='Linux'): with patch('subprocess.run') as mock_run: mock_run.return_value = MagicMock(returncode=0, stdout="", stderr="") result = await service("livekit", "start") assert "started successfully" in result.lower() # Verify systemctl was called correctly mock_run.assert_called_with( ["systemctl", "--user", "start", "voicemode-livekit"], capture_output=True, text=True ) @pytest.mark.asyncio @pytest.mark.skip(reason="Test infrastructure needs update for service management changes") async def test_livekit_stop_success(self): """Test successful LiveKit service stop""" with patch('platform.system', return_value='Linux'): with patch('subprocess.run') as mock_run: mock_run.return_value = MagicMock(returncode=0, stdout="", stderr="") result = await service("livekit", "stop") assert "stopped successfully" in result.lower() @pytest.mark.asyncio @pytest.mark.skip(reason="Test infrastructure needs update for service management changes") async def test_livekit_enable_service(self): """Test enabling LiveKit service at boot""" with patch('platform.system', return_value='Linux'): with patch('subprocess.run') as mock_run: mock_run.return_value = MagicMock(returncode=0, stdout="", stderr="") result = await service("livekit", "enable") assert "enabled" in result.lower() # Verify systemctl was called correctly mock_run.assert_called_with( ["systemctl", "--user", "enable", "voicemode-livekit"], capture_output=True, text=True ) @pytest.mark.asyncio async def test_livekit_logs_view(self): """Test viewing LiveKit service logs""" mock_logs = "2025-08-12 10:00:00 LiveKit server started\n2025-08-12 10:00:01 Listening on port 7880" with patch('platform.system', return_value='Linux'): with patch('subprocess.run') as mock_run: mock_run.return_value = MagicMock(returncode=0, stdout=mock_logs, stderr="") result = await service("livekit", "logs", lines=50) assert "LiveKit server started" in result assert "port 7880" in result class TestLiveKitInstallation: """Test cases for LiveKit installation""" @pytest.mark.asyncio @pytest.mark.skip(reason="Test infrastructure needs update for installation flow") async def test_install_macos_homebrew(self): """Test LiveKit installation on macOS using Homebrew""" with patch('platform.system', return_value='Darwin'): with patch('shutil.which', return_value='/opt/homebrew/bin/brew'): with patch('subprocess.run') as mock_run: with patch('pathlib.Path.exists', return_value=False): with patch('pathlib.Path.mkdir'): with patch('builtins.open', mock_open()): # Mock successful brew install mock_run.return_value = MagicMock(returncode=0, stdout="", stderr="") result = await install() assert result['success'] assert result['platform'] == 'macOS' assert result['install_method'] == 'homebrew' assert result['port'] == 7880 @pytest.mark.asyncio @pytest.mark.skip(reason="Test infrastructure needs update for installation flow") async def test_install_linux_official_script(self): """Test LiveKit installation on Linux using official script""" with patch('platform.system', return_value='Linux'): with patch('subprocess.run') as mock_run: with patch('pathlib.Path.exists', return_value=False): with patch('pathlib.Path.mkdir'): with patch('builtins.open', mock_open()): with patch('os.chmod'): # Mock successful installation mock_run.side_effect = [ MagicMock(returncode=0, stdout="", stderr=""), # curl download MagicMock(returncode=0, stdout="", stderr=""), # install script ] result = await install() assert result['success'] assert result['platform'] == 'Linux' assert result['install_method'] == 'official_script' @pytest.mark.asyncio async def test_install_already_installed(self): """Test installation when LiveKit is already installed""" # Mock the binary path existing in the install directory with patch('pathlib.Path.exists') as mock_exists: with patch('subprocess.run') as mock_run: # Set up exists to return True for the binary path check mock_exists.return_value = True # Mock version check mock_run.return_value = MagicMock(returncode=0, stdout="v1.7.2", stderr="") result = await install() assert result['success'] assert result['already_installed'] assert result['version'] == 'v1.7.2' @pytest.mark.asyncio @pytest.mark.skip(reason="Test infrastructure needs update for installation flow") async def test_install_with_custom_port(self): """Test installation with custom port configuration""" with patch('platform.system', return_value='Linux'): with patch('subprocess.run') as mock_run: with patch('pathlib.Path.exists', return_value=False): with patch('pathlib.Path.mkdir'): with patch('builtins.open', mock_open()) as mock_file: with patch('os.chmod'): mock_run.side_effect = [ MagicMock(returncode=0, stdout="", stderr=""), MagicMock(returncode=0, stdout="", stderr=""), ] result = await install(port=8880) assert result['success'] assert result['port'] == 8880 assert result['url'] == 'ws://127.0.0.1:8880' # Verify config file was written with correct port written_content = ''.join(call[0][0] for call in mock_file().write.call_args_list) assert 'port: 8880' in written_content class TestLiveKitUninstall: """Test cases for LiveKit uninstallation""" @pytest.mark.asyncio @pytest.mark.skip(reason="Test infrastructure needs update for uninstallation flow") async def test_uninstall_basic(self): """Test basic LiveKit uninstallation""" with patch('subprocess.run') as mock_run: with patch('pathlib.Path.exists', return_value=True): with patch('shutil.rmtree') as mock_rmtree: mock_run.return_value = MagicMock(returncode=0, stdout="", stderr="") result = await uninstall() assert result['success'] assert 'removed_items' in result assert 'Service stopped' in result['removed_items'] @pytest.mark.asyncio @pytest.mark.skip(reason="Test infrastructure needs update for uninstallation flow") async def test_uninstall_with_config_removal(self): """Test uninstallation with config file removal""" with patch('subprocess.run') as mock_run: with patch('pathlib.Path.exists', return_value=True): with patch('pathlib.Path.unlink') as mock_unlink: with patch('shutil.rmtree'): mock_run.return_value = MagicMock(returncode=0, stdout="", stderr="") result = await uninstall(remove_config=True) assert result['success'] assert 'removed_items' in result assert any('config' in item.lower() for item in result['removed_items']) @pytest.mark.asyncio @pytest.mark.skip(reason="Test infrastructure needs update for uninstallation flow") async def test_uninstall_homebrew(self): """Test uninstallation on macOS with Homebrew""" with patch('platform.system', return_value='Darwin'): with patch('shutil.which', return_value='/opt/homebrew/bin/brew'): with patch('subprocess.run') as mock_run: with patch('pathlib.Path.exists', return_value=True): with patch('pathlib.Path.unlink'): mock_run.return_value = MagicMock(returncode=0, stdout="", stderr="") result = await uninstall() assert result['success'] # Verify brew uninstall was called assert any('brew' in str(call) for call in mock_run.call_args_list) class TestLiveKitFrontend: """Test cases for LiveKit frontend management""" @pytest.mark.asyncio @pytest.mark.skip(reason="Test infrastructure needs update for frontend management") async def test_frontend_start_success(self): """Test successful frontend start""" with patch('voice_mode.tools.livekit.frontend.find_frontend_dir') as mock_find: mock_frontend_dir = Path("/path/to/frontend") mock_find.return_value = mock_frontend_dir with patch('pathlib.Path.exists', return_value=True): with patch('subprocess.run') as mock_run: with patch('subprocess.Popen') as mock_popen: with patch('builtins.open', mock_open(read_data="LIVEKIT_ACCESS_PASSWORD=test123")): mock_run.return_value = MagicMock(returncode=0, stdout="", stderr="") mock_process = MagicMock() mock_process.poll.return_value = None mock_process.pid = 12345 mock_popen.return_value = mock_process result = await frontend_start() assert result['success'] assert result['url'] == 'http://127.0.0.1:3000' assert result['password'] == 'test123' assert result['pid'] == 12345 @pytest.mark.asyncio async def test_frontend_start_port_in_use(self): """Test frontend start when port is already in use""" with patch('voice_mode.tools.livekit.frontend.find_frontend_dir') as mock_find: mock_find.return_value = Path("/path/to/frontend") with patch('subprocess.run') as mock_run: # lsof returns PIDs indicating port is in use mock_run.return_value = MagicMock(returncode=0, stdout="1234\n5678", stderr="") result = await frontend_start() assert not result['success'] assert "already in use" in result['error'] @pytest.mark.asyncio @pytest.mark.skip(reason="Test infrastructure needs update for frontend management") async def test_frontend_stop_running(self): """Test stopping a running frontend""" with patch('subprocess.run') as mock_run: # First call returns PIDs, subsequent calls for kill commands mock_run.side_effect = [ MagicMock(returncode=0, stdout="1234\n5678", stderr=""), # lsof MagicMock(returncode=0, stdout="", stderr=""), # kill -TERM MagicMock(returncode=0, stdout="", stderr=""), # kill -TERM ] result = await frontend_stop() assert result['success'] assert "stopped" in result['message'].lower() assert "1234" in result['message'] @pytest.mark.asyncio async def test_frontend_status_running(self): """Test frontend status when running""" with patch('subprocess.run') as mock_run: with patch('voice_mode.tools.livekit.frontend.find_frontend_dir') as mock_find: mock_find.return_value = Path("/path/to/frontend") with patch('pathlib.Path.exists', return_value=True): with patch('builtins.open', mock_open(read_data="LIVEKIT_URL=ws://127.0.0.1:7880\nLIVEKIT_API_KEY=devkey")): mock_run.return_value = MagicMock(returncode=0, stdout="1234", stderr="") result = await frontend_status() assert result['running'] assert result['pid'] == "1234" assert result['url'] == 'http://127.0.0.1:3000' assert 'configuration' in result assert result['configuration']['LIVEKIT_URL'] == 'ws://127.0.0.1:7880' @pytest.mark.asyncio async def test_frontend_status_not_running(self): """Test frontend status when not running""" with patch('subprocess.run') as mock_run: with patch('voice_mode.tools.livekit.frontend.find_frontend_dir') as mock_find: mock_find.return_value = Path("/path/to/frontend") mock_run.return_value = MagicMock(returncode=0, stdout="", stderr="") result = await frontend_status() assert not result['running'] assert result['pid'] is None assert result['url'] is None @pytest.mark.asyncio @pytest.mark.skip(reason="Test infrastructure needs update for frontend management") async def test_frontend_open_not_running(self): """Test opening frontend when not running - should start it first""" # Since the function calls itself, we need to test it differently # Let's just verify the basic flow works with proper mocking with patch('voice_mode.tools.livekit.frontend.find_frontend_dir') as mock_find: with patch('subprocess.run') as mock_run: with patch('subprocess.Popen') as mock_popen: with patch('platform.system', return_value='Linux'): with patch('pathlib.Path.exists') as mock_exists: with patch('builtins.open', mock_open(read_data="LIVEKIT_ACCESS_PASSWORD=test123")): # Mock frontend dir mock_find.return_value = Path("/path/to/frontend") # Mock that node_modules exists mock_exists.return_value = True # Need to mock enough subprocess.run calls for the entire flow mock_run.side_effect = [ MagicMock(returncode=0, stdout="", stderr=""), # lsof check in status MagicMock(returncode=0, stdout="", stderr=""), # lsof check in start MagicMock(returncode=0, stdout="", stderr=""), # xdg-open ] # Mock popen for starting frontend mock_process = MagicMock() mock_process.poll.return_value = None mock_process.pid = 12345 mock_popen.return_value = mock_process result = await frontend_open() assert result['success'] assert "Opened frontend in browser" in result['message'] assert result['url'] == "http://127.0.0.1:3000" assert result['password'] == "test123" @pytest.mark.asyncio async def test_frontend_open_already_running(self): """Test opening frontend when already running - should just open browser""" with patch('voice_mode.tools.livekit.frontend.find_frontend_dir') as mock_find: with patch('subprocess.run') as mock_run: with patch('platform.system', return_value='Linux'): with patch('pathlib.Path.exists', return_value=True): # Create a proper file mock that returns correct content mock_file = mock_open(read_data="LIVEKIT_ACCESS_PASSWORD=existing123\n") with patch('builtins.open', mock_file): # Mock frontend dir mock_find.return_value = Path("/path/to/frontend") # First run returns PID (running), second for xdg-open mock_run.side_effect = [ MagicMock(returncode=0, stdout="12345", stderr=""), # lsof check - running MagicMock(returncode=0, stdout="", stderr=""), # xdg-open ] result = await frontend_open() assert result['success'] assert result['url'] == "http://127.0.0.1:3000" assert result['password'] == "existing123" class TestLiveKitServiceIntegration: """Integration tests for LiveKit service management""" @pytest.mark.asyncio @pytest.mark.skip(reason="Test infrastructure needs update for config management") async def test_livekit_config_update(self): """Test that LiveKit config is properly updated during service operations""" with patch('platform.system', return_value='Linux'): with patch('subprocess.run') as mock_run: with patch('pathlib.Path.exists', return_value=True): with patch('pathlib.Path.read_text', return_value="WorkingDirectory=/path/to/livekit"): with patch('builtins.open', mock_open()): mock_run.return_value = MagicMock(returncode=0, stdout="", stderr="") result = await service("livekit", "update-service-files") assert "success" in result.lower() or "updated" in result.lower() @pytest.mark.asyncio async def test_livekit_health_check_integration(self): """Test health check integration with LiveKit""" with patch('subprocess.run') as mock_run: # Mock successful health check response health_response = json.dumps({"status": "ok", "version": "1.7.2"}) mock_run.return_value = MagicMock(returncode=0, stdout=health_response, stderr="") # Note: This would be a CLI command test # The actual health check is implemented in CLI, not as an MCP tool # This test verifies the expected behavior assert True # Placeholder for health check integration

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/mbailey/voicemode'

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