CoolFace
Apppublic

nifty-coder/stemsplit-backend

sourceHugging Faceupdated 8mo agoView on Hugging Face
0likes
test_azure_speech_integration.py548 linesDownload Raw Back to tests
1"""2Integration tests for Azure Speech Services provider.3 4This module contains integration tests that verify the AzureSpeechProvider5works correctly with the broader voice control system, including provider6manager integration, error handling, and real-world scenarios.7"""8 9import pytest10import asyncio11import json12from unittest.mock import Mock, AsyncMock, patch, MagicMock13from datetime import datetime, timezone, timedelta14from typing import Dict, Any, List15 16from voice_control.providers.azure_speech_provider import AzureSpeechProvider, create_azure_speech_provider17from voice_control.models import (18    ProviderConfig, 19    TranscriptionResult, 20    QuotaStatus, 21    QuotaType,22    ProviderType,23    ProviderStatus24)25from voice_control.exceptions import (26    ProviderError,27    ProviderUnavailableError,28    ProviderAuthenticationError,29    ProviderTimeoutError,30    UnsupportedFormatError,31    UnsupportedLanguageError,32    QuotaExceededError,33    RateLimitExceededError,34    TranscriptionError35)36 37 38class TestAzureSpeechIntegration:39    """Integration tests for Azure Speech provider with system components."""40    41    @pytest.fixture42    def mock_azure_sdk(self):43        """Mock Azure Speech SDK for integration tests."""44        with patch('voice_control.providers.azure_speech_provider.AZURE_SPEECH_AVAILABLE', True):45            with patch('voice_control.providers.azure_speech_provider.speechsdk') as mock_sdk:46                # Mock all necessary components47                mock_speech_config = Mock()48                mock_sdk.SpeechConfig.return_value = mock_speech_config49                50                mock_audio_config = Mock()51                mock_sdk.audio.AudioConfig.return_value = mock_audio_config52                53                mock_push_stream = Mock()54                mock_sdk.audio.PushAudioInputStream.return_value = mock_push_stream55                56                mock_recognizer = Mock()57                mock_sdk.SpeechRecognizer.return_value = mock_recognizer58                59                # Mock enums60                mock_sdk.ResultReason.RecognizedSpeech = "RecognizedSpeech"61                mock_sdk.ResultReason.NoMatch = "NoMatch"62                mock_sdk.ResultReason.RecognizingSpeech = "RecognizingSpeech"63                mock_sdk.CancellationReason.Error = "Error"64                mock_sdk.OutputFormat.Detailed = "Detailed"65                mock_sdk.ProfanityOption.Raw = "Raw"66                mock_sdk.ProfanityOption.Masked = "Masked"67                68                yield mock_sdk69    70    @pytest.fixture71    def provider_config(self):72        """Create provider configuration for integration tests."""73        return ProviderConfig(74            name="azure_integration",75            provider_type=ProviderType.AZURE_SPEECH,76            enabled=True,77            priority=3,78            free_tier_limits={79                "audio_hours_per_month": 5.0,80                "requests_per_minute": 20,81                "requests_per_day": 500082            },83            rate_limits={84                "requests_per_minute": 20,85                "requests_per_day": 500086            },87            supported_formats=["wav", "flac", "mp3", "ogg", "webm"],88            supported_languages=["en-US", "en-GB", "es-ES", "fr-FR", "de-DE"],89            cost_per_minute=2.5 / 60.0,90            api_credentials={91                "subscription_key": "integration_test_key_12345",92                "region": "eastus"93            },94            timeout_seconds=60,95            max_retries=396        )97    98    @pytest.fixture99    def provider(self, provider_config, mock_azure_sdk):100        """Create Azure Speech provider for integration tests."""101        return AzureSpeechProvider(provider_config)102    103    @pytest.mark.asyncio104    async def test_provider_manager_integration(self, provider, mock_azure_sdk):105        """Test integration with provider manager system."""106        # Mock successful transcription107        mock_result = Mock()108        mock_result.reason = "RecognizedSpeech"109        mock_result.text = "Provider manager integration test"110        mock_result.json = json.dumps({111            "NBest": [{112                "Confidence": 0.88,113                "Display": "Provider manager integration test",114                "Words": [115                    {"Word": "Provider", "Offset": 0, "Duration": 5000000, "Confidence": 0.9},116                    {"Word": "manager", "Offset": 5000000, "Duration": 5000000, "Confidence": 0.85},117                    {"Word": "integration", "Offset": 10000000, "Duration": 8000000, "Confidence": 0.9},118                    {"Word": "test", "Offset": 18000000, "Duration": 4000000, "Confidence": 0.87}119                ]120            }]121        })122        123        mock_recognizer = mock_azure_sdk.SpeechRecognizer.return_value124        mock_recognizer.recognize_once.return_value = mock_result125        126        # Test transcription through provider interface127        audio_data = b"provider_manager_test_audio"128        result = await provider.transcribe_audio(129            audio_data, 130            "wav", 131            "en-US",132            enable_word_timestamps=True133        )134        135        # Verify provider manager compatible result136        assert isinstance(result, TranscriptionResult)137        assert result.text == "Provider manager integration test"138        assert result.confidence == 0.88139        assert result.provider == "azure_integration"140        assert result.language == "en-US"141        assert result.is_final142        assert len(result.word_timestamps) == 4143        144        # Verify word timestamps are properly formatted145        assert result.word_timestamps[0].word == "Provider"146        assert result.word_timestamps[0].start_time == 0.0147        assert result.word_timestamps[0].end_time == 0.5148        assert result.word_timestamps[0].confidence == 0.9149    150    @pytest.mark.asyncio151    async def test_fallback_chain_integration(self, provider, mock_azure_sdk):152        """Test provider behavior in fallback chain scenarios."""153        # Test 1: Provider available and working154        mock_result = Mock()155        mock_result.reason = "RecognizedSpeech"156        mock_result.text = "Fallback test success"157        mock_result.json = json.dumps({158            "NBest": [{"Confidence": 0.92, "Display": "Fallback test success"}]159        })160        161        mock_recognizer = mock_azure_sdk.SpeechRecognizer.return_value162        mock_recognizer.recognize_once.return_value = mock_result163        164        # Should work normally165        health = await provider.check_health()166        assert health is True167        168        result = await provider.transcribe_audio(b"test_audio", "wav", "en-US")169        assert result.text == "Fallback test success"170        171        # Test 2: Provider failure (should raise appropriate exception for fallback)172        mock_recognizer.recognize_once.side_effect = Exception("Service temporarily unavailable")173        174        with pytest.raises(TranscriptionError):175            await provider.transcribe_audio(b"test_audio", "wav", "en-US")176        177        # Health check should also fail178        health = await provider.check_health()179        assert health is False180    181    @pytest.mark.asyncio182    async def test_rate_limiter_integration(self, provider, mock_azure_sdk):183        """Test integration with rate limiting system."""184        # Mock successful transcription185        mock_result = Mock()186        mock_result.reason = "RecognizedSpeech"187        mock_result.text = "Rate limit test"188        mock_result.json = json.dumps({189            "NBest": [{"Confidence": 0.9, "Display": "Rate limit test"}]190        })191        192        mock_recognizer = mock_azure_sdk.SpeechRecognizer.return_value193        mock_recognizer.recognize_once.return_value = mock_result194        195        # Test quota checking before requests196        quota_status = await provider.get_quota_status()197        initial_minute_usage = quota_status["requests_per_minute"].current_usage198        initial_daily_usage = quota_status["requests_per_day"].current_usage199        200        # Make a request201        result = await provider.transcribe_audio(b"test_audio", "wav", "en-US")202        assert result.text == "Rate limit test"203        204        # Verify quota was consumed205        quota_status = await provider.get_quota_status()206        assert quota_status["requests_per_minute"].current_usage == initial_minute_usage + 1207        assert quota_status["requests_per_day"].current_usage == initial_daily_usage + 1208        209        # Test rate limit enforcement210        provider._usage_stats["requests_this_minute"] = 20  # At limit211        212        with pytest.raises(RateLimitExceededError) as exc_info:213            await provider.transcribe_audio(b"test_audio", "wav", "en-US")214        215        assert exc_info.value.provider == "azure_integration"216        assert exc_info.value.retry_after == 60.0217    218    @pytest.mark.asyncio219    async def test_audio_processor_integration(self, provider, mock_azure_sdk):220        """Test integration with audio processing components."""221        # Mock successful transcription222        mock_result = Mock()223        mock_result.reason = "RecognizedSpeech"224        mock_result.text = "Audio processing integration test"225        mock_result.json = json.dumps({226            "NBest": [{"Confidence": 0.87, "Display": "Audio processing integration test"}]227        })228        229        mock_recognizer = mock_azure_sdk.SpeechRecognizer.return_value230        mock_recognizer.recognize_once.return_value = mock_result231        232        # Test different audio formats233        test_formats = ["wav", "mp3", "flac", "webm"]234        235        for format_type in test_formats:236            if provider.supports_format(format_type):237                audio_data = b"test_audio_" + format_type.encode()238                239                result = await provider.transcribe_audio(240                    audio_data, 241                    format_type, 242                    "en-US",243                    audio_duration=2.5  # Provide duration for testing244                )245                246                assert result.text == "Audio processing integration test"247                assert result.audio_duration == 2.5248                249                # Verify format-specific processing250                mock_azure_sdk.SpeechConfig.assert_called()251                mock_azure_sdk.SpeechRecognizer.assert_called()252    253    @pytest.mark.asyncio254    async def test_cache_manager_integration(self, provider, mock_azure_sdk):255        """Test integration with caching system."""256        # Mock successful transcription257        mock_result = Mock()258        mock_result.reason = "RecognizedSpeech"259        mock_result.text = "Cache integration test"260        mock_result.json = json.dumps({261            "NBest": [{"Confidence": 0.91, "Display": "Cache integration test"}]262        })263        264        mock_recognizer = mock_azure_sdk.SpeechRecognizer.return_value265        mock_recognizer.recognize_once.return_value = mock_result266        267        # Test transcription (should be cacheable)268        audio_data = b"cacheable_audio_data"269        270        result1 = await provider.transcribe_audio(audio_data, "wav", "en-US")271        assert result1.text == "Cache integration test"272        273        # Second request with same audio (cache system would handle this)274        result2 = await provider.transcribe_audio(audio_data, "wav", "en-US")275        assert result2.text == "Cache integration test"276        277        # Both results should have consistent metadata for caching278        assert result1.provider == result2.provider279        assert result1.language == result2.language280        assert result1.confidence == result2.confidence281    282    @pytest.mark.asyncio283    async def test_cost_monitor_integration(self, provider, mock_azure_sdk):284        """Test integration with cost monitoring system."""285        # Mock successful transcription286        mock_result = Mock()287        mock_result.reason = "RecognizedSpeech"288        mock_result.text = "Cost monitoring test"289        mock_result.json = json.dumps({290            "NBest": [{"Confidence": 0.89, "Display": "Cost monitoring test"}]291        })292        293        mock_recognizer = mock_azure_sdk.SpeechRecognizer.return_value294        mock_recognizer.recognize_once.return_value = mock_result295        296        # Test cost estimation297        audio_duration = 3600.0  # 1 hour298        estimated_cost = await provider.estimate_cost(audio_duration)299        300        # Should be free within 5-hour limit301        assert estimated_cost == 0.0302        303        # Test cost for exceeding free tier304        audio_duration = 6 * 3600.0  # 6 hours305        estimated_cost = await provider.estimate_cost(audio_duration)306        307        # Should charge for 1 hour at neural pricing308        expected_cost = 1 * 2.5  # 1 hour * $2.50/hour309        assert estimated_cost == expected_cost310        311        # Test actual usage tracking312        initial_usage = provider._usage_stats["audio_hours_this_month"]313        314        result = await provider.transcribe_audio(315            b"cost_test_audio", 316            "wav", 317            "en-US",318            audio_duration=1800.0  # 30 minutes319        )320        321        # Verify usage was tracked322        final_usage = provider._usage_stats["audio_hours_this_month"]323        assert final_usage > initial_usage324        assert final_usage == initial_usage + 0.5  # 30 minutes = 0.5 hours325    326    @pytest.mark.asyncio327    async def test_streaming_integration(self, provider, mock_azure_sdk):328        """Test streaming transcription integration."""329        # Mock streaming components330        mock_push_stream = Mock()331        mock_azure_sdk.audio.PushAudioInputStream.return_value = mock_push_stream332        333        mock_recognizer = mock_azure_sdk.SpeechRecognizer.return_value334        mock_recognizer.recognized = Mock()335        mock_recognizer.recognizing = Mock()336        mock_recognizer.start_continuous_recognition = Mock()337        mock_recognizer.stop_continuous_recognition = Mock()338        339        # Create test audio stream340        async def test_audio_stream():341            chunks = [342                b"chunk_1_audio_data",343                b"chunk_2_audio_data", 344                b"chunk_3_audio_data",345                b"final_chunk_audio"346            ]347            for chunk in chunks:348                yield chunk349                await asyncio.sleep(0.1)  # Simulate streaming delay350        351        # Test streaming transcription352        session_id = "test_streaming_session"353        results = []354        355        async for result in provider.transcribe_streaming(356            test_audio_stream(),357            "wav",358            "en-US",359            session_id=session_id,360            interim_results=True361        ):362            results.append(result)363        364        # Verify streaming setup was called365        mock_push_stream.write.assert_called()366        mock_push_stream.close.assert_called_once()367        mock_recognizer.start_continuous_recognition.assert_called_once()368        mock_recognizer.stop_continuous_recognition.assert_called_once()369        370        # Verify session was tracked371        assert session_id not in provider._active_streams  # Should be cleaned up372    373    @pytest.mark.asyncio374    async def test_error_handling_integration(self, provider, mock_azure_sdk):375        """Test comprehensive error handling integration."""376        # Test authentication error377        mock_recognizer = mock_azure_sdk.SpeechRecognizer.return_value378        mock_recognizer.recognize_once.side_effect = Exception("unauthorized access denied")379        380        with pytest.raises(ProviderAuthenticationError):381            await provider.transcribe_audio(b"test_audio", "wav", "en-US")382        383        # Test rate limit error384        mock_recognizer.recognize_once.side_effect = Exception("rate limit exceeded, please try again")385        386        with pytest.raises(RateLimitExceededError):387            await provider.transcribe_audio(b"test_audio", "wav", "en-US")388        389        # Test timeout error390        mock_recognizer.recognize_once.side_effect = Exception("request timed out after 60 seconds")391        392        with pytest.raises(ProviderTimeoutError):393            await provider.transcribe_audio(b"test_audio", "wav", "en-US")394        395        # Test service unavailable error396        mock_recognizer.recognize_once.side_effect = Exception("service temporarily unavailable")397        398        with pytest.raises(ProviderUnavailableError):399            await provider.transcribe_audio(b"test_audio", "wav", "en-US")400        401        # Test generic error402        mock_recognizer.recognize_once.side_effect = Exception("unknown internal error")403        404        with pytest.raises(TranscriptionError):405            await provider.transcribe_audio(b"test_audio", "wav", "en-US")406    407    @pytest.mark.asyncio408    async def test_multi_language_integration(self, provider, mock_azure_sdk):409        """Test multi-language support integration."""410        # Mock successful transcription for different languages411        mock_result = Mock()412        mock_result.reason = "RecognizedSpeech"413        mock_result.json = json.dumps({414            "NBest": [{"Confidence": 0.85, "Display": "Multi-language test"}]415        })416        417        mock_recognizer = mock_azure_sdk.SpeechRecognizer.return_value418        mock_recognizer.recognize_once.return_value = mock_result419        420        # Test supported languages421        test_languages = ["en-US", "en-GB", "es-ES", "fr-FR", "de-DE"]422        423        for language in test_languages:424            if provider.supports_language(language):425                mock_result.text = f"Test in {language}"426                427                result = await provider.transcribe_audio(428                    b"multilingual_test_audio",429                    "wav",430                    language431                )432                433                assert result.text == f"Test in {language}"434                assert result.language == language435                assert result.confidence == 0.85436        437        # Test unsupported language438        with pytest.raises(UnsupportedLanguageError):439            await provider.transcribe_audio(b"test_audio", "wav", "xx-XX")440    441    @pytest.mark.asyncio442    async def test_quota_reset_integration(self, provider, mock_azure_sdk):443        """Test quota reset timing integration."""444        # Set up initial usage445        provider._usage_stats["requests_this_minute"] = 10446        provider._usage_stats["requests_today"] = 100447        provider._usage_stats["audio_hours_this_month"] = 2.5448        449        # Set timestamps to trigger resets450        now = datetime.now(timezone.utc)451        provider._usage_stats["last_minute_reset"] = now - timedelta(minutes=2)452        provider._usage_stats["last_daily_reset"] = now.date() - timedelta(days=1)453        provider._usage_stats["last_monthly_reset"] = now - timedelta(days=32)454        455        # Check quota status (should trigger resets)456        quota_status = await provider.get_quota_status()457        458        # Verify resets occurred459        assert provider._usage_stats["requests_this_minute"] == 0460        assert provider._usage_stats["requests_today"] == 0461        assert provider._usage_stats["audio_hours_this_month"] == 0.0462        463        # Verify quota status reflects resets464        assert quota_status["requests_per_minute"].current_usage == 0465        assert quota_status["requests_per_day"].current_usage == 0466        assert quota_status["audio_hours_per_month"].current_usage == 0.0467    468    @pytest.mark.asyncio469    async def test_configuration_integration(self, mock_azure_sdk):470        """Test provider configuration integration."""471        # Test with custom endpoint configuration472        custom_config = ProviderConfig(473            name="azure_custom_endpoint",474            provider_type=ProviderType.AZURE_SPEECH,475            enabled=True,476            priority=1,477            free_tier_limits={"audio_hours_per_month": 10.0, "requests_per_minute": 50},478            rate_limits={"requests_per_minute": 50},479            supported_formats=["wav", "mp3"],480            supported_languages=["en-US", "es-ES"],481            cost_per_minute=1.0 / 60.0,  # Standard pricing482            api_credentials={483                "subscription_key": "custom_key",484                "endpoint": "https://custom.cognitiveservices.azure.com/"485            },486            timeout_seconds=30,487            max_retries=2488        )489        490        provider = AzureSpeechProvider(custom_config)491        492        # Verify configuration was applied493        assert provider.name == "azure_custom_endpoint"494        assert provider.config.priority == 1495        assert provider.config.free_tier_limits["audio_hours_per_month"] == 10.0496        assert provider.config.api_credentials["endpoint"] == "https://custom.cognitiveservices.azure.com/"497        498        # Test initialization with custom endpoint499        await provider._initialize_client()500        assert provider._client_initialized501        502        # Verify custom limits are used503        quota_status = await provider.get_quota_status()504        assert quota_status["requests_per_minute"].limit == 50505        assert quota_status["audio_hours_per_month"].limit == 10.0506 507 508class TestAzureSpeechProviderFactoryIntegration:509    """Integration tests for Azure Speech provider factory."""510    511    @patch('voice_control.providers.azure_speech_provider.AZURE_SPEECH_AVAILABLE', True)512    def test_factory_integration_with_provider_manager(self):513        """Test factory function integration with provider manager."""514        # Create provider using factory515        provider = create_azure_speech_provider(516            name="factory_test_azure",517            priority=2,518            api_credentials={519                "subscription_key": "factory_test_key",520                "region": "westus2"521            }522        )523        524        # Verify provider is properly configured for provider manager525        assert isinstance(provider, AzureSpeechProvider)526        assert provider.name == "factory_test_azure"527        assert provider.config.priority == 2528        assert provider.config.provider_type == ProviderType.AZURE_SPEECH529        assert provider.config.enabled530        531        # Verify it has all required interface methods532        assert hasattr(provider, 'transcribe_audio')533        assert hasattr(provider, 'transcribe_streaming')534        assert hasattr(provider, 'check_health')535        assert hasattr(provider, 'get_quota_status')536        assert hasattr(provider, 'supports_format')537        assert hasattr(provider, 'supports_language')538        assert hasattr(provider, 'estimate_cost')539        540        # Verify configuration is compatible with system541        assert len(provider.config.supported_formats) > 0542        assert len(provider.config.supported_languages) > 0543        assert provider.config.cost_per_minute >= 0544        assert provider.config.timeout_seconds > 0545 546 547if __name__ == "__main__":548    pytest.main([__file__])