#!/usr/bin/env python3 """ Test Suite for Original Dictation Functionality Tests basic voice-to-text transcription features """ import os import sys import unittest import tempfile import threading import time import subprocess from unittest.mock import Mock, patch, MagicMock # Add src to path sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'src')) class TestOriginalDictation(unittest.TestCase): """Test the original dictation service functionality""" def setUp(self): """Setup test environment""" self.temp_dir = tempfile.mkdtemp() self.lock_file = os.path.join(self.temp_dir, "test_listening.lock") # Mock environment variables that might be expected os.environ['DISPLAY'] = ':0' os.environ['XAUTHORITY'] = '/tmp/.Xauthority' def tearDown(self): """Clean up test environment""" if os.path.exists(self.lock_file): os.remove(self.lock_file) os.rmdir(self.temp_dir) def test_enhanced_dictation_import(self): """Test that enhanced dictation can be imported""" try: from src.dictation_service.enhanced_dictation import ( send_notification, download_model_if_needed, process_partial_text, process_final_text ) self.assertTrue(callable(send_notification)) self.assertTrue(callable(download_model_if_needed)) except ImportError as e: self.fail(f"Cannot import enhanced dictation functions: {e}") def test_basic_dictation_import(self): """Test that basic dictation can be imported""" try: from src.dictation_service.vosk_dictation import main self.assertTrue(callable(main)) except ImportError as e: self.fail(f"Cannot import basic dictation: {e}") def test_notification_system(self): """Test notification functionality""" try: from src.dictation_service.enhanced_dictation import send_notification # Test with mock subprocess with patch('subprocess.run') as mock_run: mock_run.return_value = Mock(returncode=0) # Test basic notification send_notification("Test Title", "Test Message", 2000) mock_run.assert_called_once_with( ["notify-send", "-t", "2000", "-u", "low", "Test Title", "Test Message"], capture_output=True, check=True ) print("āœ… Notification system working correctly") except Exception as e: self.fail(f"Notification system test failed: {e}") def test_text_processing_functions(self): """Test text processing logic""" try: from src.dictation_service.enhanced_dictation import process_partial_text, process_final_text # Mock keyboard and logging for testing with patch('src.dictation_service.enhanced_dictation.keyboard') as mock_keyboard, \ patch('src.dictation_service.enhanced_dictation.logging') as mock_logging, \ patch('src.dictation_service.enhanced_dictation.send_notification') as mock_notify: # Test partial text processing process_partial_text("hello world") mock_logging.info.assert_called_with("šŸ’­ hello world") # Test final text processing process_final_text("hello world test") # Should type the text mock_keyboard.type.assert_called_once_with("Hello world test ") except Exception as e: self.fail(f"Text processing test failed: {e}") def test_text_filtering_logic(self): """Test text filtering for dictation""" test_cases = [ ("the", True), # Should be filtered ("a", True), # Should be filtered ("uh", True), # Should be filtered ("hello", False), # Should not be filtered ("test message", False), # Should not be filtered ("x", True), # Too short ("", True), # Empty (" ", True), # Only whitespace ] for text, should_filter in test_cases: with self.subTest(text=text): # Simulate filtering logic formatted = text.strip() # Check if text should be filtered will_filter = ( len(formatted.split()) == 1 and formatted.lower() in ['the', 'a', 'an', 'uh', 'huh', 'um', 'hmm'] or len(formatted) < 2 ) self.assertEqual(will_filter, should_filter, f"Text '{text}' filtering mismatch") def test_audio_callback_mock(self): """Test audio callback with mock data""" try: from src.dictation_service.enhanced_dictation import audio_callback import queue # Mock global state with patch('src.dictation_service.enhanced_dictation.is_listening', True), \ patch('src.dictation_service.enhanced_dictation.q', queue.Queue()) as mock_queue: # Mock audio data import numpy as np audio_data = np.random.randint(-32768, 32767, size=(8000, 1), dtype=np.int16) # Test callback audio_callback(audio_data, 8000, None, None) # Check that data was added to queue self.assertFalse(mock_queue.empty()) except ImportError: self.skipTest("numpy not available for audio testing") except Exception as e: self.fail(f"Audio callback test failed: {e}") def test_lock_file_operations(self): """Test lock file creation and monitoring""" # Test lock file creation self.assertFalse(os.path.exists(self.lock_file)) # Create lock file with open(self.lock_file, 'w') as f: f.write("test") self.assertTrue(os.path.exists(self.lock_file)) # Test lock file removal os.remove(self.lock_file) self.assertFalse(os.path.exists(self.lock_file)) def test_model_download_function(self): """Test model download function""" try: from src.dictation_service.enhanced_dictation import download_model_if_needed # Mock subprocess calls with patch('os.path.exists') as mock_exists, \ patch('subprocess.check_call') as mock_subprocess, \ patch('sys.exit') as mock_exit: # Test when model doesn't exist mock_exists.return_value = False download_model_if_needed("test-model") # Should attempt download mock_subprocess.assert_called() mock_exit.assert_not_called() # Test when model exists mock_exists.return_value = True mock_subprocess.reset_mock() download_model_if_needed("test-model") # Should not attempt download mock_subprocess.assert_not_called() except Exception as e: self.fail(f"Model download test failed: {e}") def test_state_transitions(self): """Test dictation state transitions""" # Simulate the state checking logic from main() def check_dictation_state(lock_file_path): if os.path.exists(lock_file_path): return "listening" else: return "idle" # Test idle state self.assertEqual(check_dictation_state(self.lock_file), "idle") # Test listening state with open(self.lock_file, 'w') as f: f.write("listening") self.assertEqual(check_dictation_state(self.lock_file), "listening") # Test back to idle os.remove(self.lock_file) self.assertEqual(check_dictation_state(self.lock_file), "idle") def test_keyboard_output_simulation(self): """Test keyboard output functionality""" try: from pynput.keyboard import Controller # Create keyboard controller keyboard = Controller() # Test that we can create controller (actual typing tests would interfere with user) self.assertIsNotNone(keyboard) self.assertTrue(hasattr(keyboard, 'type')) self.assertTrue(hasattr(keyboard, 'press')) self.assertTrue(hasattr(keyboard, 'release')) except ImportError: self.skipTest("pynput not available") except Exception as e: self.fail(f"Keyboard controller test failed: {e}") def test_error_handling(self): """Test error handling in dictation functions""" try: from src.dictation_service.enhanced_dictation import send_notification # Test with failing subprocess with patch('subprocess.run') as mock_run: mock_run.side_effect = FileNotFoundError("notify-send not found") # Should not raise exception try: send_notification("Test", "Message") except Exception: self.fail("send_notification should handle subprocess errors gracefully") except Exception as e: self.fail(f"Error handling test failed: {e}") def test_text_formatting(self): """Test text formatting for dictation output""" test_cases = [ ("hello world", "Hello world"), ("test", "Test"), ("CAPITALIZED", "CAPITALIZED"), ("", ""), ("a", "A"), ] for input_text, expected in test_cases: with self.subTest(input_text=input_text): # Simulate text formatting logic if input_text: formatted = input_text.strip() formatted = formatted[0].upper() + formatted[1:] if formatted else formatted else: formatted = "" self.assertEqual(formatted, expected) class TestDictationIntegration(unittest.TestCase): """Integration tests for dictation system""" def setUp(self): """Setup integration test environment""" self.temp_dir = tempfile.mkdtemp() self.lock_file = os.path.join(self.temp_dir, "integration_test.lock") def tearDown(self): """Clean up integration test environment""" if os.path.exists(self.lock_file): os.remove(self.lock_file) os.rmdir(self.temp_dir) def test_full_dictation_flow_simulation(self): """Test simulated full dictation flow""" try: from src.dictation_service.enhanced_dictation import ( process_partial_text, process_final_text, send_notification ) # Mock all external dependencies with patch('src.dictation_service.enhanced_dictation.keyboard') as mock_keyboard, \ patch('src.dictation_service.enhanced_dictation.logging') as mock_logging, \ patch('src.dictation_service.enhanced_dictation.send_notification') as mock_notify: # Simulate dictation session print("\nšŸŽ¤ Simulating Dictation Session...") # Start dictation (would be triggered by lock file) mock_logging.info.assert_any_call("=== Enhanced Dictation Ready ===") mock_logging.info.assert_any_call("Features: Real-time streaming + instant typing + visual feedback") # Simulate user speaking test_phrases = [ "hello world", "this is a test", "dictation is working" ] for phrase in test_phrases: # Simulate partial text processing process_partial_text(phrase[:3] + "...") # Simulate final text processing process_final_text(phrase) # Verify keyboard typing calls self.assertEqual(mock_keyboard.type.call_count, len(test_phrases)) # Verify logging calls mock_logging.info.assert_any_call("āœ… Hello world") mock_logging.info.assert_any_call("āœ… This is a test") mock_logging.info.assert_any_call("āœ… Dictation is working") print("āœ… Dictation flow simulation successful") except Exception as e: self.fail(f"Full dictation flow test failed: {e}") def test_service_startup_simulation(self): """Test service startup sequence""" try: from src.dictation_service.enhanced_dictation import main # Mock the infinite while loop to run briefly with patch('src.dictation_service.enhanced_dictation.time.sleep') as mock_sleep, \ patch('src.dictation_service.enhanced_dictation.os.path.exists') as mock_exists, \ patch('sounddevice.RawInputStream') as mock_stream, \ patch('src.dictation_service.enhanced_dictation.download_model_if_needed') as mock_download: # Setup mocks mock_exists.return_value = False # No lock file initially mock_stream.return_value.__enter__ = Mock() mock_stream.return_value.__exit__ = Mock() # Mock time.sleep to raise KeyboardInterrupt after a few calls sleep_count = 0 def mock_sleep_func(duration): nonlocal sleep_count sleep_count += 1 if sleep_count > 3: # After 3 sleep calls, simulate KeyboardInterrupt raise KeyboardInterrupt() mock_sleep.side_effect = mock_sleep_func # Run main (should exit after KeyboardInterrupt) try: main() except KeyboardInterrupt: pass # Expected # Verify initialization mock_download.assert_called_once() mock_stream.assert_called_once() print("āœ… Service startup simulation successful") except Exception as e: self.fail(f"Service startup test failed: {e}") def test_audio_system(): """Test actual audio system if available""" print("\nšŸ”Š Testing Audio System...") try: # Test arecord availability result = subprocess.run( ["arecord", "--version"], capture_output=True, timeout=5 ) if result.returncode == 0: print("āœ… Audio recording system available") else: print("āš ļø Audio recording system may have issues") except (FileNotFoundError, subprocess.TimeoutExpired): print("āš ļø arecord not available") try: # Test aplay availability result = subprocess.run( ["aplay", "--version"], capture_output=True, timeout=5 ) if result.returncode == 0: print("āœ… Audio playback system available") else: print("āš ļø Audio playback system may have issues") except (FileNotFoundError, subprocess.TimeoutExpired): print("āš ļø aplay not available") def test_vosk_models(): """Test available Vosk models""" print("\n🧠 Testing Vosk Models...") model_configs = [ ("vosk-model-small-en-us-0.15", "Small model (fast)"), ("vosk-model-en-us-0.22-lgraph", "Medium model"), ("vosk-model-en-us-0.22", "Large model (accurate)") ] for model_name, description in model_configs: if os.path.exists(model_name): print(f"āœ… {description}: Found") else: print(f"āš ļø {description}: Not found (will download if needed)") def main(): """Main test runner for original dictation""" print("šŸŽ¤ Original Dictation Service - Test Suite") print("=" * 50) # Run unit tests print("\nšŸ“‹ Running Original Dictation Unit Tests...") unittest.main(argv=[''], exit=False, verbosity=2) print("\n" + "=" * 50) print("šŸ” System Checks...") # Audio system test test_audio_system() # Vosk model test test_vosk_models() print("\n" + "=" * 50) print("āœ… Original Dictation Tests Complete!") print("\nšŸ“Š Summary:") print("- All core dictation functions tested") print("- Audio system availability verified") print("- Vosk model status checked") print("- Error handling and state management verified") if __name__ == "__main__": main()