From caeceb58a59730363d37692aace29c0693e14e56 Mon Sep 17 00:00:00 2001 From: "Paul Gauthier (aider)" Date: Sat, 30 Nov 2024 15:41:15 -0800 Subject: [PATCH] test: add voice input device tests with mocked hardware dependencies --- tests/basic/test_voice.py | 101 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) diff --git a/tests/basic/test_voice.py b/tests/basic/test_voice.py index e69de29bb..5b62c7148 100644 --- a/tests/basic/test_voice.py +++ b/tests/basic/test_voice.py @@ -0,0 +1,101 @@ +import pytest +from unittest.mock import Mock, patch +import numpy as np +import queue +import tempfile +import os + +from aider.voice import Voice, SoundDeviceError + +@pytest.fixture +def mock_sounddevice(): + with patch('sounddevice.query_devices') as mock_query: + mock_query.return_value = [ + {'name': 'test_device', 'max_input_channels': 2}, + {'name': 'another_device', 'max_input_channels': 1} + ] + yield mock_query + +@pytest.fixture +def mock_soundfile(): + with patch('soundfile.SoundFile') as mock_sf: + yield mock_sf + +def test_voice_init_default_device(mock_sounddevice): + voice = Voice() + assert voice.device_id is None + assert voice.audio_format == 'wav' + +def test_voice_init_specific_device(mock_sounddevice): + voice = Voice(device_name='test_device') + assert voice.device_id == 0 + +def test_voice_init_invalid_device(mock_sounddevice): + with pytest.raises(ValueError) as exc: + Voice(device_name='nonexistent_device') + assert 'Device' in str(exc.value) + assert 'not found' in str(exc.value) + +def test_voice_init_invalid_format(): + with pytest.raises(ValueError) as exc: + Voice(audio_format='invalid') + assert 'Unsupported audio format' in str(exc.value) + +def test_callback_processing(): + voice = Voice() + voice.q = queue.Queue() + + # Test with silence (low amplitude) + test_data = np.zeros((1000, 1)) + voice.callback(test_data, None, None, None) + assert voice.pct < 0.1 + + # Test with loud signal (high amplitude) + test_data = np.ones((1000, 1)) + voice.callback(test_data, None, None, None) + assert voice.pct > 0.9 + + # Verify data is queued + assert not voice.q.empty() + +@patch('aider.voice.litellm') +def test_record_and_transcribe(mock_litellm, mock_soundfile): + voice = Voice() + + # Mock the recording process + with patch('prompt_toolkit.shortcuts.prompt'): + with patch('sounddevice.InputStream'): + # Mock the transcription response + mock_litellm.transcription.return_value = Mock(text="Hello, world!") + + result = voice.record_and_transcribe() + + assert result == "Hello, world!" + mock_litellm.transcription.assert_called_once() + +def test_get_prompt(): + voice = Voice() + voice.start_time = voice.start_time = os.times().elapsed + voice.pct = 0.5 # 50% volume level + + prompt = voice.get_prompt() + assert "Recording" in prompt + assert "sec" in prompt + assert "█" in prompt # Should contain some filled blocks + assert "░" in prompt # Should contain some empty blocks + +@patch('sounddevice.InputStream') +def test_record_and_transcribe_keyboard_interrupt(mock_stream): + voice = Voice() + mock_stream.side_effect = KeyboardInterrupt() + + result = voice.record_and_transcribe() + assert result is None + +@patch('sounddevice.InputStream') +def test_record_and_transcribe_device_error(mock_stream): + voice = Voice() + mock_stream.side_effect = SoundDeviceError("Test error") + + result = voice.record_and_transcribe() + assert result is None