407 lines
12 KiB
Python
407 lines
12 KiB
Python
import pytest
|
|
from unittest.mock import Mock, patch, mock_open, MagicMock
|
|
import json
|
|
import subprocess
|
|
from pathlib import Path
|
|
|
|
from src.commands.audio import (
|
|
detect_wsl,
|
|
get_windows_audio_devices,
|
|
get_linux_audio_devices,
|
|
_categorize_device_type,
|
|
display_windows_devices,
|
|
display_linux_devices,
|
|
WindowsAudioDevice,
|
|
LinuxAudioDevice
|
|
)
|
|
from src.config import AppConfig, AudioConfig, ServerConfig
|
|
|
|
|
|
# WSL Detection Tests
|
|
|
|
@patch('pathlib.Path.exists')
|
|
@patch('builtins.open', mock_open(read_data='Linux version 5.4.0-microsoft-standard'))
|
|
def test_detect_wsl_via_proc_version_microsoft(mock_exists):
|
|
"""Test WSL detection via /proc/version containing 'microsoft'."""
|
|
mock_exists.return_value = True
|
|
|
|
result = detect_wsl()
|
|
|
|
assert result is True
|
|
|
|
|
|
@patch('pathlib.Path.exists')
|
|
@patch('builtins.open', mock_open(read_data='Linux version 5.4.0-wsl2-standard'))
|
|
def test_detect_wsl_via_proc_version_wsl(mock_exists):
|
|
"""Test WSL detection via /proc/version containing 'wsl'."""
|
|
mock_exists.return_value = True
|
|
|
|
result = detect_wsl()
|
|
|
|
assert result is True
|
|
|
|
|
|
@patch('pathlib.Path.exists')
|
|
@patch('builtins.open', mock_open(read_data='Linux version 5.4.0-generic'))
|
|
@patch('os.getenv')
|
|
def test_detect_wsl_via_env_distro_name(mock_getenv, mock_exists):
|
|
"""Test WSL detection via WSL_DISTRO_NAME environment variable."""
|
|
mock_exists.return_value = True
|
|
mock_getenv.side_effect = lambda key: 'Ubuntu' if key == 'WSL_DISTRO_NAME' else None
|
|
|
|
result = detect_wsl()
|
|
|
|
assert result is True
|
|
|
|
|
|
@patch('pathlib.Path.exists')
|
|
@patch('builtins.open', mock_open(read_data='Linux version 5.4.0-generic'))
|
|
@patch('os.getenv')
|
|
def test_detect_wsl_via_env_wslenv(mock_getenv, mock_exists):
|
|
"""Test WSL detection via WSLENV environment variable."""
|
|
mock_exists.return_value = True
|
|
mock_getenv.side_effect = lambda key: 'PATH/l' if key == 'WSLENV' else None
|
|
|
|
result = detect_wsl()
|
|
|
|
assert result is True
|
|
|
|
|
|
@patch('pathlib.Path.exists')
|
|
@patch('builtins.open', mock_open(read_data='Linux version 5.4.0-generic'))
|
|
@patch('os.getenv', return_value=None)
|
|
def test_detect_wsl_false_native_linux(mock_getenv, mock_exists):
|
|
"""Test WSL detection returns False on native Linux."""
|
|
mock_exists.return_value = True
|
|
|
|
result = detect_wsl()
|
|
|
|
assert result is False
|
|
|
|
|
|
@patch('pathlib.Path.exists', return_value=False)
|
|
@patch('os.getenv', return_value=None)
|
|
def test_detect_wsl_false_no_proc_version(mock_getenv, mock_exists):
|
|
"""Test WSL detection returns False when /proc/version doesn't exist."""
|
|
result = detect_wsl()
|
|
|
|
assert result is False
|
|
|
|
|
|
@patch('pathlib.Path.exists')
|
|
@patch('builtins.open', side_effect=Exception("Permission denied"))
|
|
@patch('os.getenv', return_value=None)
|
|
def test_detect_wsl_handles_proc_version_read_error(mock_getenv, mock_exists):
|
|
"""Test WSL detection handles /proc/version read errors gracefully."""
|
|
mock_exists.return_value = True
|
|
|
|
result = detect_wsl()
|
|
|
|
assert result is False
|
|
|
|
|
|
# Device Type Categorization Tests
|
|
|
|
def test_categorize_input_device():
|
|
"""Test categorization of input device."""
|
|
instance_id = "SWD\\MMDEVAPI\\{0.0.1.00000000}.{a1b2c3d4-e5f6-7890-abcd-ef1234567890}\\capture"
|
|
|
|
result = _categorize_device_type(instance_id)
|
|
|
|
assert result == "Input"
|
|
|
|
|
|
def test_categorize_output_device():
|
|
"""Test categorization of output device."""
|
|
instance_id = "SWD\\MMDEVAPI\\{0.0.0.00000000}.{a1b2c3d4-e5f6-7890-abcd-ef1234567890}\\render"
|
|
|
|
result = _categorize_device_type(instance_id)
|
|
|
|
assert result == "Output"
|
|
|
|
|
|
def test_categorize_unknown_device():
|
|
"""Test categorization of unknown device type."""
|
|
instance_id = "SWD\\MMDEVAPI\\{0.0.2.00000000}.{a1b2c3d4-e5f6-7890-abcd-ef1234567890}\\unknown"
|
|
|
|
result = _categorize_device_type(instance_id)
|
|
|
|
assert result == "Unknown"
|
|
|
|
|
|
def test_categorize_empty_instance_id():
|
|
"""Test categorization with empty instance ID."""
|
|
result = _categorize_device_type("")
|
|
|
|
assert result == "Unknown"
|
|
|
|
|
|
# Windows Audio Devices Tests
|
|
|
|
@patch('subprocess.run')
|
|
@patch('typer.echo')
|
|
def test_get_windows_devices_success_single_device(mock_echo, mock_run):
|
|
"""Test successful retrieval of single Windows device."""
|
|
mock_result = Mock()
|
|
mock_result.returncode = 0
|
|
mock_result.stdout = json.dumps({
|
|
"FriendlyName": "Blue Yeti Microphone",
|
|
"Status": "OK",
|
|
"InstanceId": "USB\\VID_0B05&PID_1234\\capture"
|
|
})
|
|
mock_result.stderr = ""
|
|
mock_run.return_value = mock_result
|
|
|
|
result = get_windows_audio_devices()
|
|
|
|
assert len(result) == 1
|
|
assert result[0].name == "Blue Yeti Microphone"
|
|
assert result[0].status == "OK"
|
|
assert result[0].device_type == "Input"
|
|
assert "USB\\VID_0B05&PID_1234\\capture" in result[0].instance_id
|
|
|
|
|
|
@patch('subprocess.run')
|
|
@patch('typer.echo')
|
|
def test_get_windows_devices_success_multiple_devices(mock_echo, mock_run):
|
|
"""Test successful retrieval of multiple Windows devices."""
|
|
mock_result = Mock()
|
|
mock_result.returncode = 0
|
|
mock_result.stdout = json.dumps([
|
|
{
|
|
"FriendlyName": "Microphone",
|
|
"Status": "OK",
|
|
"InstanceId": "capture_device"
|
|
},
|
|
{
|
|
"FriendlyName": "Speakers",
|
|
"Status": "OK",
|
|
"InstanceId": "render_device"
|
|
}
|
|
])
|
|
mock_result.stderr = ""
|
|
mock_run.return_value = mock_result
|
|
|
|
result = get_windows_audio_devices()
|
|
|
|
assert len(result) == 2
|
|
assert result[0].device_type == "Input"
|
|
assert result[1].device_type == "Output"
|
|
|
|
|
|
@patch('subprocess.run')
|
|
@patch('typer.echo')
|
|
def test_get_windows_devices_powershell_error(mock_echo, mock_run):
|
|
"""Test PowerShell command failure."""
|
|
mock_result = Mock()
|
|
mock_result.returncode = 1
|
|
mock_result.stdout = ""
|
|
mock_result.stderr = "Get-PnpDevice : Access denied"
|
|
mock_run.return_value = mock_result
|
|
|
|
result = get_windows_audio_devices()
|
|
|
|
assert result == []
|
|
mock_echo.assert_called()
|
|
|
|
|
|
@patch('subprocess.run')
|
|
@patch('typer.echo')
|
|
def test_get_windows_devices_empty_output(mock_echo, mock_run):
|
|
"""Test PowerShell returning empty output."""
|
|
mock_result = Mock()
|
|
mock_result.returncode = 0
|
|
mock_result.stdout = ""
|
|
mock_result.stderr = ""
|
|
mock_run.return_value = mock_result
|
|
|
|
result = get_windows_audio_devices()
|
|
|
|
assert result == []
|
|
mock_echo.assert_called()
|
|
|
|
|
|
@patch('subprocess.run')
|
|
@patch('typer.echo')
|
|
def test_get_windows_devices_invalid_json(mock_echo, mock_run):
|
|
"""Test invalid JSON response from PowerShell."""
|
|
mock_result = Mock()
|
|
mock_result.returncode = 0
|
|
mock_result.stdout = "Invalid JSON {"
|
|
mock_result.stderr = ""
|
|
mock_run.return_value = mock_result
|
|
|
|
result = get_windows_audio_devices()
|
|
|
|
assert result == []
|
|
mock_echo.assert_called()
|
|
|
|
|
|
@patch('subprocess.run', side_effect=subprocess.TimeoutExpired("powershell.exe", 15))
|
|
@patch('typer.echo')
|
|
def test_get_windows_devices_timeout(mock_echo, mock_run):
|
|
"""Test PowerShell command timeout."""
|
|
result = get_windows_audio_devices()
|
|
|
|
assert result == []
|
|
mock_echo.assert_called()
|
|
|
|
|
|
@patch('subprocess.run')
|
|
@patch('typer.echo')
|
|
def test_get_windows_devices_filters_empty_names(mock_echo, mock_run):
|
|
"""Test filtering of devices with empty names."""
|
|
mock_result = Mock()
|
|
mock_result.returncode = 0
|
|
mock_result.stdout = json.dumps([
|
|
{
|
|
"FriendlyName": "Valid Device",
|
|
"Status": "OK",
|
|
"InstanceId": "capture_device"
|
|
},
|
|
{
|
|
"FriendlyName": "",
|
|
"Status": "OK",
|
|
"InstanceId": "empty_name_device"
|
|
},
|
|
{
|
|
"FriendlyName": None,
|
|
"Status": "OK",
|
|
"InstanceId": "null_name_device"
|
|
}
|
|
])
|
|
mock_result.stderr = ""
|
|
mock_run.return_value = mock_result
|
|
|
|
result = get_windows_audio_devices()
|
|
|
|
assert len(result) == 1
|
|
assert result[0].name == "Valid Device"
|
|
|
|
|
|
# Linux Audio Devices Tests
|
|
|
|
@patch('sounddevice.query_devices')
|
|
@patch('sounddevice.query_hostapis')
|
|
@patch('typer.echo')
|
|
def test_get_linux_devices_success(mock_echo, mock_hostapis, mock_devices):
|
|
"""Test successful retrieval of Linux devices."""
|
|
mock_devices.return_value = [
|
|
{
|
|
'name': 'pulse',
|
|
'max_input_channels': 32,
|
|
'max_output_channels': 32,
|
|
'default_samplerate': 44100.0,
|
|
'hostapi': 0
|
|
},
|
|
{
|
|
'name': 'default',
|
|
'max_input_channels': 32,
|
|
'max_output_channels': 32,
|
|
'default_samplerate': 44100.0,
|
|
'hostapi': 0
|
|
}
|
|
]
|
|
mock_hostapis.return_value = {'name': 'ALSA'}
|
|
|
|
result = get_linux_audio_devices()
|
|
|
|
assert len(result) == 2
|
|
assert result[0].name == 'pulse'
|
|
assert result[0].device_id == 0
|
|
assert result[0].hostapi_name == 'ALSA'
|
|
assert result[1].name == 'default'
|
|
assert result[1].device_id == 1
|
|
|
|
|
|
@patch('sounddevice.query_devices', side_effect=Exception("ALSA error"))
|
|
@patch('typer.echo')
|
|
def test_get_linux_devices_sounddevice_error(mock_echo, mock_devices):
|
|
"""Test sounddevice query failure."""
|
|
with pytest.raises(SystemExit):
|
|
get_linux_audio_devices()
|
|
|
|
|
|
# Display Functions Tests
|
|
|
|
@patch('typer.echo')
|
|
def test_display_windows_devices_empty_list(mock_echo):
|
|
"""Test display with empty device list."""
|
|
display_windows_devices([], True, True)
|
|
|
|
mock_echo.assert_called()
|
|
|
|
|
|
@patch('typer.echo')
|
|
@patch('src.commands.audio._display_single_windows_device')
|
|
def test_display_windows_devices_input_only(mock_display_single, mock_echo):
|
|
"""Test display showing only input devices."""
|
|
devices = [
|
|
WindowsAudioDevice('Microphone', 'OK', 'Input', 'mic1'),
|
|
WindowsAudioDevice('Speakers', 'OK', 'Output', 'spk1')
|
|
]
|
|
|
|
display_windows_devices(devices, show_inputs=True, show_outputs=False)
|
|
|
|
mock_display_single.assert_called_once_with(devices[0])
|
|
|
|
|
|
@patch('typer.echo')
|
|
@patch('src.commands.audio._display_single_windows_device')
|
|
def test_display_windows_devices_with_unknown(mock_display_single, mock_echo):
|
|
"""Test display including unknown device types."""
|
|
devices = [
|
|
WindowsAudioDevice('Microphone', 'OK', 'Input', 'mic1'),
|
|
WindowsAudioDevice('Unknown Device', 'OK', 'Unknown', 'unk1')
|
|
]
|
|
|
|
display_windows_devices(devices, show_inputs=True, show_outputs=True)
|
|
|
|
assert mock_display_single.call_count == 2
|
|
|
|
|
|
@patch('typer.echo')
|
|
def test_display_linux_devices_no_matching_devices(mock_echo):
|
|
"""Test display with no matching devices."""
|
|
devices = [
|
|
LinuxAudioDevice(0, 'pulse', 0, 32, 44100.0, 'ALSA')
|
|
]
|
|
config = AppConfig(ServerConfig(), AudioConfig())
|
|
|
|
with pytest.raises(SystemExit):
|
|
display_linux_devices(devices, show_inputs=True, show_outputs=False, config=config)
|
|
|
|
|
|
@patch('typer.echo')
|
|
@patch('sounddevice.default')
|
|
def test_display_linux_devices_success(mock_default, mock_echo):
|
|
"""Test successful display of Linux devices."""
|
|
mock_default.device = [0, 1]
|
|
|
|
devices = [
|
|
LinuxAudioDevice(0, 'pulse', 32, 32, 44100.0, 'ALSA')
|
|
]
|
|
config = AppConfig(ServerConfig(), AudioConfig())
|
|
|
|
display_linux_devices(devices, show_inputs=True, show_outputs=True, config=config)
|
|
|
|
# Verify that echo was called with device information
|
|
assert mock_echo.call_count > 0
|
|
|
|
|
|
@patch('typer.echo')
|
|
@patch('sounddevice.default')
|
|
def test_display_linux_devices_with_configured_device(mock_default, mock_echo):
|
|
"""Test display with configured device marked."""
|
|
mock_default.device = [0, 1]
|
|
|
|
devices = [
|
|
LinuxAudioDevice(0, 'pulse', 32, 32, 44100.0, 'ALSA'),
|
|
LinuxAudioDevice(1, 'default', 32, 32, 44100.0, 'ALSA')
|
|
]
|
|
config = AppConfig(ServerConfig(), AudioConfig(device=1))
|
|
|
|
display_linux_devices(devices, show_inputs=True, show_outputs=True, config=config)
|
|
|
|
# Verify that echo was called and device is marked as configured
|
|
assert mock_echo.call_count > 0 |