"""
Test Voice Control Tools - TASK_66 Phase 5 Integration & Testing
Comprehensive testing for voice control MCP tools with property-based testing,
security validation, and accessibility compliance.
Architecture: Property-Based Testing + Unit Testing + Integration Testing + Security Validation
Performance: <100ms test execution, comprehensive coverage, edge case validation
Security: Voice command security, audio processing validation, privacy protection testing
"""
import pytest
import asyncio
from unittest.mock import AsyncMock, Mock, patch
from typing import Dict, List, Optional, Any
from datetime import datetime, UTC
try:
from src.server.tools.voice_control_tools import (
_km_process_voice_commands_impl,
km_process_voice_commands,
km_configure_voice_control,
km_provide_voice_feedback,
km_train_voice_recognition,
VoiceControlManager,
get_voice_control_manager,
VoiceControlConfiguration
)
from src.core.voice_architecture import (
VoiceLanguage, SpeechRecognitionEngine, TrainingType,
VoiceControlError
)
from src.core.either import Either
VOICE_TOOLS_AVAILABLE = True
except ImportError as e:
VOICE_TOOLS_AVAILABLE = False
# Create mock placeholders
_km_process_voice_commands_impl = None
km_process_voice_commands = None
km_configure_voice_control = None
km_provide_voice_feedback = None
km_train_voice_recognition = None
VoiceControlManager = None
get_voice_control_manager = None
VoiceControlConfiguration = None
VoiceLanguage = None
SpeechRecognitionEngine = None
TrainingType = None
VoiceControlError = None
Either = None
@pytest.mark.skipif(not VOICE_TOOLS_AVAILABLE, reason="Voice control tools imports not available")
class TestVoiceControlTools:
"""Test voice control MCP tools functionality."""
@pytest.fixture
def voice_manager(self):
"""Create voice control manager for testing."""
return VoiceControlManager()
@pytest.mark.asyncio
async def test_km_process_voice_commands_success(self, voice_manager):
"""Test successful voice command processing."""
with patch('src.server.tools.voice_control_tools.get_voice_control_manager', return_value=voice_manager):
# Mock voice processing
mock_result = {
"recognition": {
"recognized_text": "open calculator",
"confidence": 0.95,
"language": "en-US",
"processing_time_ms": 150
},
"intent": {
"command_type": "application_control",
"automation_target": "calculator",
"parameters": {},
"confidence": 0.92
},
"execution": {
"status": "success",
"results": {"application_opened": True},
"automation_triggered": True,
"execution_time_ms": 200
}
}
voice_manager.process_voice_command = AsyncMock(return_value=Either.success(mock_result))
voice_manager.feedback_manager = Mock()
voice_manager.feedback_manager.provide_feedback = AsyncMock(return_value=Either.success({"synthesis_successful": True}))
result = await _km_process_voice_commands_impl(
audio_input="open calculator",
recognition_language="en-US",
confidence_threshold=0.8,
provide_feedback=True
)
assert result["success"] is True
assert "voice_processing" in result
assert result["voice_processing"]["recognition"]["recognized_text"] == "open calculator"
assert result["voice_processing"]["recognition"]["confidence"] == 0.95
assert result["voice_processing"]["intent"]["command_type"] == "application_control"
assert result["voice_processing"]["execution"]["status"] == "success"
assert "performance_metrics" in result
@pytest.mark.asyncio
async def test_km_process_voice_commands_recognition_failure(self, voice_manager):
"""Test voice command processing with recognition failure."""
with patch('src.server.tools.voice_control_tools.get_voice_control_manager', return_value=voice_manager):
voice_manager.process_voice_command = AsyncMock(
return_value=Either.error(VoiceControlError("voice_recognition", "Recognition failed"))
)
result = await _km_process_voice_commands_impl(
audio_input="unclear audio",
confidence_threshold=0.9
)
assert result["success"] is False
assert "error" in result
assert result["error_type"] == "voice_processing_error"
@pytest.mark.asyncio
async def test_km_configure_voice_control_recognition_settings(self, voice_manager):
"""Test voice control configuration for recognition settings."""
with patch('src.server.tools.voice_control_tools.get_voice_control_manager', return_value=voice_manager):
voice_manager.speech_recognizer = Mock()
voice_manager.speech_recognizer.configure_recognition = AsyncMock(
return_value=Either.success({"configuration_applied": True})
)
result = await km_configure_voice_control(
configuration_type="recognition",
language_settings={"primary_language": "en-US", "accent": "american"},
recognition_sensitivity=0.85,
accessibility_mode=True
)
assert result["success"] is True
assert result["configuration_type"] == "recognition"
assert result["applied_settings"]["recognition_sensitivity"] == 0.85
assert result["applied_settings"]["accessibility_mode"] is True
@pytest.mark.asyncio
async def test_km_configure_voice_control_command_mappings(self, voice_manager):
"""Test voice control configuration for command mappings."""
with patch('src.server.tools.voice_control_tools.get_voice_control_manager', return_value=voice_manager):
voice_manager.intent_processor = Mock()
voice_manager.intent_processor.configure_commands = AsyncMock(
return_value=Either.success({"commands_configured": 5})
)
command_mappings = {
"open calculator": "launch_application:calculator",
"close window": "window_control:close",
"save document": "file_operation:save"
}
result = await km_configure_voice_control(
configuration_type="commands",
command_mappings=command_mappings,
wake_word="hey assistant"
)
assert result["success"] is True
assert result["configuration_type"] == "commands"
assert result["applied_settings"]["wake_word"] == "hey assistant"
@pytest.mark.asyncio
async def test_km_configure_voice_control_invalid_type(self):
"""Test voice control configuration with invalid type."""
result = await km_configure_voice_control(
configuration_type="invalid_type"
)
assert result["success"] is False
assert "Invalid configuration type" in result["error"]
@pytest.mark.asyncio
async def test_km_provide_voice_feedback_success(self, voice_manager):
"""Test successful voice feedback generation."""
with patch('src.server.tools.voice_control_tools.get_voice_control_manager', return_value=voice_manager):
mock_feedback_result = {
"audio_duration_seconds": 3.5,
"voice_characteristics": {"pitch": "medium", "speed": "normal"},
"synthesis_engine": "system_native",
"audio_file_path": None,
"synthesis_time_ms": 120,
"audio_quality": "high"
}
voice_manager.feedback_manager = Mock()
voice_manager.feedback_manager.provide_feedback = AsyncMock(
return_value=Either.success(Mock(**mock_feedback_result))
)
result = await km_provide_voice_feedback(
message="Command executed successfully",
language="en-US",
speech_rate=1.2,
voice_volume=0.9,
emotion_tone="happy"
)
assert result["success"] is True
assert result["speech_synthesis"]["message"] == "Command executed successfully"
assert result["speech_synthesis"]["audio_duration_seconds"] == 3.5
assert result["settings_applied"]["speech_rate"] == 1.2
assert result["settings_applied"]["volume"] == 0.9
assert result["settings_applied"]["emotion_tone"] == "happy"
@pytest.mark.asyncio
async def test_km_provide_voice_feedback_empty_message(self):
"""Test voice feedback with empty message."""
result = await km_provide_voice_feedback(
message="",
language="en-US"
)
assert result["success"] is False
assert "Message cannot be empty" in result["error"]
@pytest.mark.asyncio
async def test_km_provide_voice_feedback_invalid_language(self, voice_manager):
"""Test voice feedback with invalid language fallback."""
with patch('src.server.tools.voice_control_tools.get_voice_control_manager', return_value=voice_manager):
mock_feedback_result = {
"audio_duration_seconds": 2.0,
"voice_characteristics": {"pitch": "medium", "speed": "normal"},
"synthesis_engine": "system_native",
"audio_file_path": None,
"synthesis_time_ms": 100,
"audio_quality": "high"
}
voice_manager.feedback_manager = Mock()
voice_manager.feedback_manager.provide_feedback = AsyncMock(
return_value=Either.success(Mock(**mock_feedback_result))
)
result = await km_provide_voice_feedback(
message="Test message",
language="invalid-lang" # Should fallback to en-US
)
assert result["success"] is True
assert result["settings_applied"]["language"] == "en-US"
@pytest.mark.asyncio
async def test_km_train_voice_recognition_user_voice(self, voice_manager):
"""Test voice recognition training for user voice."""
with patch('src.server.tools.voice_control_tools.get_voice_control_manager', return_value=voice_manager):
mock_training_result = {
"sessions_completed": 5,
"accuracy_improvement": 15.3,
"baseline_accuracy": 78.5,
"final_accuracy": 93.8,
"training_duration_minutes": 12.5,
"voice_profile": Mock(
profile_id="user_profile_123",
acoustic_characteristics={"fundamental_frequency": 150, "formants": [800, 1200, 2500]},
personalization_level=0.85,
supported_languages=[VoiceLanguage.EN_US, VoiceLanguage.EN_GB]
),
"validation_results": {"validation_accuracy": 92.1, "cross_validation_score": 0.89},
"processing_speed_improvement": 8.2,
"noise_robustness": 0.78,
"command_recognition_accuracy": 94.5
}
voice_manager.speech_recognizer = Mock()
voice_manager.speech_recognizer.train_recognition = AsyncMock(
return_value=Either.success(Mock(**mock_training_result))
)
voice_manager.save_voice_profile = AsyncMock(return_value=Either.success(True))
training_phrases = [
"open calculator",
"close window",
"save document",
"copy text",
"paste content"
]
result = await km_train_voice_recognition(
training_type="user_voice",
training_data=training_phrases,
user_profile_name="john_doe",
training_sessions=5,
adaptation_mode="comprehensive",
validate_training=True,
save_profile=True
)
assert result["success"] is True
assert result["training_type"] == "user_voice"
assert result["user_profile"] == "john_doe"
assert result["training_results"]["sessions_completed"] == 5
assert result["training_results"]["accuracy_improvement"] == 15.3
assert result["voice_profile"]["profile_id"] == "user_profile_123"
assert result["validation_results"]["validation_accuracy"] == 92.1
@pytest.mark.asyncio
async def test_km_train_voice_recognition_invalid_type(self):
"""Test voice recognition training with invalid type."""
result = await km_train_voice_recognition(
training_type="invalid_type",
user_profile_name="test_user"
)
assert result["success"] is False
assert "Invalid training type" in result["error"]
@pytest.mark.asyncio
async def test_km_train_voice_recognition_custom_commands(self, voice_manager):
"""Test voice recognition training for custom commands."""
with patch('src.server.tools.voice_control_tools.get_voice_control_manager', return_value=voice_manager):
mock_training_result = {
"sessions_completed": 3,
"accuracy_improvement": 22.1,
"baseline_accuracy": 65.2,
"final_accuracy": 87.3,
"training_duration_minutes": 8.0,
"voice_profile": Mock(
profile_id="custom_commands_456",
acoustic_characteristics={"custom_vocabulary": True},
personalization_level=0.92,
supported_languages=[VoiceLanguage.EN_US]
),
"validation_results": None,
"processing_speed_improvement": 12.5,
"noise_robustness": 0.82,
"command_recognition_accuracy": 89.7
}
voice_manager.speech_recognizer = Mock()
voice_manager.speech_recognizer.train_recognition = AsyncMock(
return_value=Either.success(Mock(**mock_training_result))
)
voice_manager.save_voice_profile = AsyncMock(return_value=Either.success(True))
custom_commands = [
"activate productivity mode",
"switch to development workspace",
"run automation sequence alpha",
"execute backup protocol"
]
result = await km_train_voice_recognition(
training_type="custom_commands",
training_data=custom_commands,
user_profile_name="custom_user",
training_sessions=3,
adaptation_mode="quick",
validate_training=False
)
assert result["success"] is True
assert result["training_type"] == "custom_commands"
assert result["training_results"]["accuracy_improvement"] == 22.1
assert result["validation_results"] is None
class TestVoiceControlManager:
"""Test VoiceControlManager functionality."""
@pytest.fixture
def voice_manager(self):
"""Create voice control manager for testing."""
return VoiceControlManager()
def test_voice_manager_initialization(self, voice_manager):
"""Test voice control manager initialization."""
assert voice_manager.speech_recognizer is not None
assert voice_manager.intent_processor is not None
assert voice_manager.feedback_manager is not None
assert voice_manager.command_dispatcher is not None
assert voice_manager.active_sessions == {}
assert voice_manager.voice_profiles == {}
assert voice_manager.performance_metrics["total_commands_processed"] == 0
def test_performance_metrics_update(self, voice_manager):
"""Test performance metrics updating."""
# Test successful command
voice_manager._update_performance_metrics(0.5, True)
assert voice_manager.performance_metrics["total_commands_processed"] == 1
assert voice_manager.performance_metrics["successful_recognitions"] == 1
assert voice_manager.performance_metrics["average_processing_time"] == 0.5
# Test failed command
voice_manager._update_performance_metrics(0.3, False)
assert voice_manager.performance_metrics["total_commands_processed"] == 2
assert voice_manager.performance_metrics["successful_recognitions"] == 1
assert voice_manager.performance_metrics["average_processing_time"] == 0.4
def test_get_voice_control_manager_singleton(self):
"""Test voice control manager singleton pattern."""
manager1 = get_voice_control_manager()
manager2 = get_voice_control_manager()
assert manager1 is manager2
class TestVoiceControlIntegration:
"""Test voice control integration scenarios."""
@pytest.mark.asyncio
async def test_full_voice_workflow(self):
"""Test complete voice control workflow."""
# Test voice command processing
result1 = await km_process_voice_commands(
audio_input="open calendar application",
recognition_language="en-US",
confidence_threshold=0.8,
execute_immediately=True,
provide_feedback=True
)
# Test configuration
result2 = await km_configure_voice_control(
configuration_type="recognition",
recognition_sensitivity=0.9,
accessibility_mode=True
)
# Test voice feedback
result3 = await km_provide_voice_feedback(
message="Calendar application has been opened",
speech_rate=1.0,
voice_volume=0.8
)
# Test training (would normally fail due to mock dependencies, but that's expected)
result4 = await km_train_voice_recognition(
training_type="user_voice",
user_profile_name="integration_test_user",
training_sessions=2
)
# Verify results structure (some may fail due to mocking, but should have consistent structure)
assert "success" in result1
assert "timestamp" in result1
assert "success" in result2
assert "timestamp" in result2
assert "success" in result3
assert "timestamp" in result3
assert "success" in result4
assert "timestamp" in result4
class TestVoiceControlSecurity:
"""Test voice control security features."""
@pytest.mark.asyncio
async def test_audio_input_validation(self):
"""Test audio input security validation."""
# Test with potentially malicious input
result = await km_process_voice_commands(
audio_input="<script>alert('xss')</script>",
recognition_language="en-US"
)
# Should handle malicious input gracefully
assert "success" in result
assert "error" in result or "voice_processing" in result
@pytest.mark.asyncio
async def test_message_length_validation(self):
"""Test voice feedback message length validation."""
# Test with very long message
long_message = "A" * 1500 # Exceeds max_length=1000
result = await km_provide_voice_feedback(message=long_message)
# Should handle gracefully (Pydantic validation or processing)
assert "success" in result
@pytest.mark.asyncio
async def test_configuration_validation(self):
"""Test voice control configuration validation."""
# Test with invalid sensitivity value
result = await km_configure_voice_control(
configuration_type="recognition",
recognition_sensitivity=1.5 # Outside valid range 0.1-1.0
)
# Should handle invalid values gracefully
assert "success" in result