nifty-coder/stemsplit-backend
0
1"""2Unit tests for Azure Speech Services provider.3 4This module contains comprehensive tests for the AzureSpeechProvider class,5including transcription functionality, quota management, error handling,6and integration scenarios.7"""8 9import pytest10import asyncio11import json12from unittest.mock import Mock, AsyncMock, patch, MagicMock13from datetime import datetime, timezone, timedelta14from typing import Dict, Any15 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 ProviderType23)24from voice_control.exceptions import (25 ProviderError,26 ProviderUnavailableError,27 ProviderAuthenticationError,28 ProviderTimeoutError,29 UnsupportedFormatError,30 UnsupportedLanguageError,31 QuotaExceededError,32 RateLimitExceededError,33 TranscriptionError34)35 36 37class TestAzureSpeechProvider:38 """Test cases for AzureSpeechProvider."""39 40 @pytest.fixture41 def mock_azure_sdk(self):42 """Mock Azure Speech SDK components."""43 with patch('voice_control.providers.azure_speech_provider.AZURE_SPEECH_AVAILABLE', True):44 # Mock the individual imports45 with patch('voice_control.providers.azure_speech_provider.speechsdk') as mock_speechsdk, \46 patch('voice_control.providers.azure_speech_provider.SpeechConfig') as mock_speech_config_class, \47 patch('voice_control.providers.azure_speech_provider.AudioConfig') as mock_audio_config_class, \48 patch('voice_control.providers.azure_speech_provider.SpeechRecognizer') as mock_recognizer_class, \49 patch('voice_control.providers.azure_speech_provider.ResultReason') as mock_result_reason, \50 patch('voice_control.providers.azure_speech_provider.CancellationReason') as mock_cancel_reason:51 52 # Mock SpeechConfig instances53 mock_speech_config = Mock()54 mock_speech_config_class.return_value = mock_speech_config55 56 # Mock AudioConfig instances57 mock_audio_config = Mock()58 mock_audio_config_class.return_value = mock_audio_config59 60 # Mock SpeechRecognizer instances61 mock_recognizer = Mock()62 mock_recognizer_class.return_value = mock_recognizer63 64 # Mock audio components65 mock_push_stream = Mock()66 mock_speechsdk.audio.PushAudioInputStream.return_value = mock_push_stream67 mock_speechsdk.audio.AudioConfig.return_value = mock_audio_config68 69 # Mock ResultReason enum values70 mock_result_reason.RecognizedSpeech = "RecognizedSpeech"71 mock_result_reason.NoMatch = "NoMatch"72 mock_result_reason.RecognizingSpeech = "RecognizingSpeech"73 74 # Mock CancellationReason enum values75 mock_cancel_reason.Error = "Error"76 77 # Mock OutputFormat78 mock_speechsdk.OutputFormat.Detailed = "Detailed"79 80 # Mock ProfanityOption81 mock_speechsdk.ProfanityOption.Raw = "Raw"82 mock_speechsdk.ProfanityOption.Masked = "Masked"83 84 # Create a mock SDK object with all the mocked components85 mock_sdk = Mock()86 mock_sdk.SpeechConfig = mock_speech_config_class87 mock_sdk.AudioConfig = mock_audio_config_class88 mock_sdk.SpeechRecognizer = mock_recognizer_class89 mock_sdk.ResultReason = mock_result_reason90 mock_sdk.CancellationReason = mock_cancel_reason91 mock_sdk.OutputFormat = mock_speechsdk.OutputFormat92 mock_sdk.ProfanityOption = mock_speechsdk.ProfanityOption93 mock_sdk.audio = mock_speechsdk.audio94 95 yield mock_sdk96 97 @pytest.fixture98 def provider_config(self):99 """Create a test provider configuration."""100 return ProviderConfig(101 name="test_azure_speech",102 provider_type=ProviderType.AZURE_SPEECH,103 enabled=True,104 priority=3,105 free_tier_limits={106 "audio_hours_per_month": 5.0,107 "requests_per_minute": 20,108 "requests_per_day": 5000109 },110 rate_limits={111 "requests_per_minute": 20,112 "requests_per_day": 5000113 },114 supported_formats=["wav", "flac", "mp3", "ogg", "webm"],115 supported_languages=["en-US", "en-GB", "es-ES", "fr-FR"],116 cost_per_minute=2.5 / 60.0, # Neural pricing per minute117 api_credentials={118 "subscription_key": "test_key_12345",119 "region": "eastus"120 },121 timeout_seconds=60,122 max_retries=3123 )124 125 @pytest.fixture126 def provider(self, provider_config, mock_azure_sdk):127 """Create a test Azure Speech provider instance."""128 return AzureSpeechProvider(provider_config)129 130 def test_initialization_success(self, provider_config, mock_azure_sdk):131 """Test successful provider initialization."""132 provider = AzureSpeechProvider(provider_config)133 134 assert provider.name == "test_azure_speech"135 assert provider.provider_type == ProviderType.AZURE_SPEECH136 assert not provider._client_initialized137 assert provider._usage_stats["requests_today"] == 0138 assert provider._usage_stats["audio_hours_this_month"] == 0.0139 140 def test_initialization_invalid_provider_type(self, provider_config, mock_azure_sdk):141 """Test initialization with invalid provider type."""142 provider_config.provider_type = ProviderType.GOOGLE_SPEECH143 144 with pytest.raises(ValueError, match="Invalid provider type"):145 AzureSpeechProvider(provider_config)146 147 def test_initialization_missing_azure_sdk(self, provider_config):148 """Test initialization when Azure SDK is not available."""149 with patch('voice_control.providers.azure_speech_provider.AZURE_SPEECH_AVAILABLE', False):150 with pytest.raises(ImportError, match="Azure Speech SDK not available"):151 AzureSpeechProvider(provider_config)152 153 @pytest.mark.asyncio154 async def test_initialize_client_success(self, provider, mock_azure_sdk):155 """Test successful client initialization."""156 await provider._initialize_client()157 158 assert provider._client_initialized159 mock_azure_sdk.SpeechConfig.assert_called_once()160 161 @pytest.mark.asyncio162 async def test_initialize_client_missing_credentials(self, provider_config, mock_azure_sdk):163 """Test client initialization with missing credentials."""164 provider_config.api_credentials = {}165 provider = AzureSpeechProvider(provider_config)166 167 with pytest.raises(ProviderAuthenticationError):168 await provider._initialize_client()169 170 @pytest.mark.asyncio171 async def test_initialize_client_with_endpoint(self, provider_config, mock_azure_sdk):172 """Test client initialization with custom endpoint."""173 provider_config.api_credentials = {174 "subscription_key": "test_key",175 "endpoint": "https://custom.cognitiveservices.azure.com/"176 }177 provider = AzureSpeechProvider(provider_config)178 179 await provider._initialize_client()180 181 assert provider._client_initialized182 mock_azure_sdk.SpeechConfig.assert_called_once()183 184 def test_supports_format(self, provider):185 """Test audio format support checking."""186 assert provider.supports_format("wav")187 assert provider.supports_format("WAV") # Case insensitive188 assert provider.supports_format("mp3")189 assert provider.supports_format("flac")190 assert provider.supports_format("webm")191 assert not provider.supports_format("xyz")192 assert not provider.supports_format("")193 194 def test_supports_language(self, provider):195 """Test language support checking."""196 assert provider.supports_language("en-US")197 assert provider.supports_language("es-ES")198 assert provider.supports_language("fr-FR")199 assert provider.supports_language("de-DE")200 assert not provider.supports_language("xx-XX")201 assert not provider.supports_language("")202 203 @pytest.mark.asyncio204 async def test_estimate_cost_free_tier(self, provider):205 """Test cost estimation within free tier."""206 # 1 hour of audio (within 5-hour free tier)207 cost = await provider.estimate_cost(3600.0)208 assert cost == 0.0209 210 @pytest.mark.asyncio211 async def test_estimate_cost_exceeds_free_tier(self, provider):212 """Test cost estimation exceeding free tier."""213 # 6 hours of audio (exceeds 5-hour free tier)214 cost = await provider.estimate_cost(6 * 3600.0)215 expected_cost = 1 * 2.5 # 1 hour at $2.50/hour (neural pricing)216 assert cost == expected_cost217 218 @pytest.mark.asyncio219 async def test_transcribe_audio_success(self, provider, mock_azure_sdk):220 """Test successful audio transcription."""221 # Mock recognition result222 mock_result = Mock()223 mock_result.reason = "RecognizedSpeech"224 mock_result.text = "Hello world"225 mock_result.json = json.dumps({226 "NBest": [{227 "Confidence": 0.95,228 "Display": "Hello world",229 "Words": [230 {"Word": "Hello", "Offset": 0, "Duration": 5000000, "Confidence": 0.98},231 {"Word": "world", "Offset": 5000000, "Duration": 5000000, "Confidence": 0.92}232 ]233 }]234 })235 236 mock_recognizer = mock_azure_sdk.SpeechRecognizer.return_value237 mock_recognizer.recognize_once.return_value = mock_result238 239 # Test transcription240 audio_data = b"fake_audio_data"241 result = await provider.transcribe_audio(audio_data, "wav", "en-US")242 243 assert isinstance(result, TranscriptionResult)244 assert result.text == "Hello world"245 assert result.confidence == 0.95246 assert result.provider == provider.name247 assert result.language == "en-US"248 assert result.is_final249 assert len(result.word_timestamps) == 2250 assert result.word_timestamps[0].word == "Hello"251 assert result.word_timestamps[1].word == "world"252 253 @pytest.mark.asyncio254 async def test_transcribe_audio_no_match(self, provider, mock_azure_sdk):255 """Test transcription with no speech detected."""256 # Mock no match result257 mock_result = Mock()258 mock_result.reason = "NoMatch"259 mock_result.text = ""260 261 mock_recognizer = mock_azure_sdk.SpeechRecognizer.return_value262 mock_recognizer.recognize_once.return_value = mock_result263 264 # Test transcription265 audio_data = b"silent_audio_data"266 result = await provider.transcribe_audio(audio_data, "wav", "en-US")267 268 assert result.text == ""269 assert result.confidence == 0.0270 assert result.is_final271 272 @pytest.mark.asyncio273 async def test_transcribe_audio_unsupported_format(self, provider):274 """Test transcription with unsupported audio format."""275 audio_data = b"fake_audio_data"276 277 with pytest.raises(UnsupportedFormatError):278 await provider.transcribe_audio(audio_data, "xyz", "en-US")279 280 @pytest.mark.asyncio281 async def test_transcribe_audio_unsupported_language(self, provider):282 """Test transcription with unsupported language."""283 audio_data = b"fake_audio_data"284 285 with pytest.raises(UnsupportedLanguageError):286 await provider.transcribe_audio(audio_data, "wav", "xx-XX")287 288 @pytest.mark.asyncio289 async def test_transcribe_audio_quota_exceeded(self, provider, mock_azure_sdk):290 """Test transcription when quota is exceeded."""291 # Set usage to exceed monthly limit292 provider._usage_stats["audio_hours_this_month"] = 5.0 # At limit293 294 audio_data = b"fake_audio_data"295 296 with pytest.raises(QuotaExceededError):297 await provider.transcribe_audio(audio_data, "wav", "en-US", audio_duration=3600.0) # 1 hour298 299 @pytest.mark.asyncio300 async def test_transcribe_audio_rate_limit_exceeded(self, provider, mock_azure_sdk):301 """Test transcription when rate limit is exceeded."""302 # Set usage to exceed minute limit303 provider._usage_stats["requests_this_minute"] = 20 # At limit304 305 audio_data = b"fake_audio_data"306 307 with pytest.raises(RateLimitExceededError):308 await provider.transcribe_audio(audio_data, "wav", "en-US")309 310 @pytest.mark.asyncio311 async def test_transcribe_streaming_success(self, provider, mock_azure_sdk):312 """Test successful streaming transcription."""313 # Mock streaming components314 mock_push_stream = Mock()315 mock_azure_sdk.audio.PushAudioInputStream.return_value = mock_push_stream316 317 mock_recognizer = mock_azure_sdk.SpeechRecognizer.return_value318 mock_recognizer.recognized = Mock()319 mock_recognizer.recognizing = Mock()320 mock_recognizer.start_continuous_recognition = Mock()321 mock_recognizer.stop_continuous_recognition = Mock()322 323 # Create async generator for audio stream324 async def audio_generator():325 yield b"chunk1"326 yield b"chunk2"327 yield b"chunk3"328 329 # Test streaming330 results = []331 async for result in provider.transcribe_streaming(audio_generator(), "wav", "en-US"):332 results.append(result)333 334 # Verify streaming setup335 mock_push_stream.write.assert_called()336 mock_push_stream.close.assert_called_once()337 mock_recognizer.start_continuous_recognition.assert_called_once()338 mock_recognizer.stop_continuous_recognition.assert_called_once()339 340 @pytest.mark.asyncio341 async def test_check_health_success(self, provider, mock_azure_sdk):342 """Test successful health check."""343 # Mock successful recognition344 mock_result = Mock()345 mock_result.reason = "RecognizedSpeech"346 347 mock_recognizer = mock_azure_sdk.SpeechRecognizer.return_value348 mock_recognizer.recognize_once.return_value = mock_result349 350 health = await provider.check_health()351 assert health is True352 353 @pytest.mark.asyncio354 async def test_check_health_failure(self, provider, mock_azure_sdk):355 """Test health check failure."""356 # Mock recognition failure357 mock_recognizer = mock_azure_sdk.SpeechRecognizer.return_value358 mock_recognizer.recognize_once.side_effect = Exception("Service unavailable")359 360 health = await provider.check_health()361 assert health is False362 363 @pytest.mark.asyncio364 async def test_get_quota_status(self, provider):365 """Test quota status retrieval."""366 # Set some usage367 provider._usage_stats["requests_this_minute"] = 5368 provider._usage_stats["requests_today"] = 100369 provider._usage_stats["audio_hours_this_month"] = 2.5370 371 quota_status = await provider.get_quota_status()372 373 assert "requests_per_minute" in quota_status374 assert "requests_per_day" in quota_status375 assert "audio_hours_per_month" in quota_status376 377 minute_quota = quota_status["requests_per_minute"]378 assert minute_quota.current_usage == 5379 assert minute_quota.limit == 20380 assert minute_quota.remaining == 15381 assert minute_quota.percentage_used == 0.25382 383 monthly_quota = quota_status["audio_hours_per_month"]384 assert monthly_quota.current_usage == 2.5385 assert monthly_quota.limit == 5.0386 assert monthly_quota.remaining == 2.5387 assert monthly_quota.percentage_used == 0.5388 389 @pytest.mark.asyncio390 async def test_reset_counters_minute(self, provider):391 """Test minute counter reset."""392 # Set usage and old timestamp393 provider._usage_stats["requests_this_minute"] = 10394 provider._usage_stats["last_minute_reset"] = datetime.now(timezone.utc) - timedelta(minutes=2)395 396 await provider._reset_counters_if_needed()397 398 assert provider._usage_stats["requests_this_minute"] == 0399 400 @pytest.mark.asyncio401 async def test_reset_counters_daily(self, provider):402 """Test daily counter reset."""403 # Set usage and old timestamp404 provider._usage_stats["requests_today"] = 100405 provider._usage_stats["last_daily_reset"] = datetime.now(timezone.utc).date() - timedelta(days=1)406 407 await provider._reset_counters_if_needed()408 409 assert provider._usage_stats["requests_today"] == 0410 411 @pytest.mark.asyncio412 async def test_reset_counters_monthly(self, provider):413 """Test monthly counter reset."""414 # Set usage and old timestamp415 provider._usage_stats["audio_hours_this_month"] = 3.0416 provider._usage_stats["last_monthly_reset"] = datetime.now(timezone.utc) - timedelta(days=32)417 418 await provider._reset_counters_if_needed()419 420 assert provider._usage_stats["audio_hours_this_month"] == 0.0421 422 def test_estimate_audio_duration_wav(self, provider):423 """Test audio duration estimation for WAV format."""424 audio_data = b"x" * 32000 # 32KB of data425 duration = provider._estimate_audio_duration(audio_data, "wav")426 expected = 32000 / (16000 * 2) # 16kHz, 16-bit427 assert duration == expected428 429 def test_estimate_audio_duration_mp3(self, provider):430 """Test audio duration estimation for MP3 format."""431 audio_data = b"x" * 8000 # 8KB of data432 duration = provider._estimate_audio_duration(audio_data, "mp3")433 expected = 8000 / 8000 # Compressed format estimation434 assert duration == expected435 436 @pytest.mark.asyncio437 async def test_handle_azure_error_authentication(self, provider):438 """Test handling of authentication errors."""439 error = Exception("unauthorized access")440 441 with pytest.raises(ProviderAuthenticationError):442 await provider._handle_azure_error(error)443 444 @pytest.mark.asyncio445 async def test_handle_azure_error_rate_limit(self, provider):446 """Test handling of rate limit errors."""447 error = Exception("rate limit exceeded")448 449 with pytest.raises(RateLimitExceededError):450 await provider._handle_azure_error(error)451 452 @pytest.mark.asyncio453 async def test_handle_azure_error_timeout(self, provider):454 """Test handling of timeout errors."""455 error = Exception("request timed out")456 457 with pytest.raises(ProviderTimeoutError):458 await provider._handle_azure_error(error)459 460 @pytest.mark.asyncio461 async def test_handle_azure_error_unavailable(self, provider):462 """Test handling of service unavailable errors."""463 error = Exception("service unavailable")464 465 with pytest.raises(ProviderUnavailableError):466 await provider._handle_azure_error(error)467 468 @pytest.mark.asyncio469 async def test_handle_azure_error_generic(self, provider):470 """Test handling of generic errors."""471 error = Exception("unknown error")472 473 with pytest.raises(TranscriptionError):474 await provider._handle_azure_error(error)475 476 def test_extract_word_timestamps_success(self, provider):477 """Test successful word timestamp extraction."""478 detailed_result = {479 "NBest": [{480 "Words": [481 {"Word": "Hello", "Offset": 0, "Duration": 5000000, "Confidence": 0.98},482 {"Word": "world", "Offset": 5000000, "Duration": 5000000, "Confidence": 0.92}483 ]484 }]485 }486 487 timestamps = provider._extract_word_timestamps(detailed_result)488 489 assert len(timestamps) == 2490 assert timestamps[0].word == "Hello"491 assert timestamps[0].start_time == 0.0492 assert timestamps[0].end_time == 0.5493 assert timestamps[0].confidence == 0.98494 495 assert timestamps[1].word == "world"496 assert timestamps[1].start_time == 0.5497 assert timestamps[1].end_time == 1.0498 assert timestamps[1].confidence == 0.92499 500 def test_extract_word_timestamps_empty(self, provider):501 """Test word timestamp extraction with empty result."""502 detailed_result = {}503 timestamps = provider._extract_word_timestamps(detailed_result)504 assert timestamps == []505 506 detailed_result = {"NBest": []}507 timestamps = provider._extract_word_timestamps(detailed_result)508 assert timestamps == []509 510 detailed_result = {"NBest": [{"Words": []}]}511 timestamps = provider._extract_word_timestamps(detailed_result)512 assert timestamps == []513 514 def test_extract_word_timestamps_malformed(self, provider):515 """Test word timestamp extraction with malformed data."""516 detailed_result = {517 "NBest": [{518 "Words": [519 {"Word": "Hello"}, # Missing timing info520 {"Offset": 1000000, "Duration": 2000000} # Missing word521 ]522 }]523 }524 525 # Should handle gracefully and return empty list526 timestamps = provider._extract_word_timestamps(detailed_result)527 assert len(timestamps) <= 2 # May extract partial data or return empty528 529 def test_get_next_month_start_december(self, provider):530 """Test next month calculation for December."""531 with patch('voice_control.providers.azure_speech_provider.datetime') as mock_dt:532 mock_dt.now.return_value = datetime(2023, 12, 15, 10, 30, 0, tzinfo=timezone.utc)533 mock_dt.side_effect = lambda *args, **kw: datetime(*args, **kw)534 535 next_month = provider._get_next_month_start()536 expected = datetime(2024, 1, 1, 0, 0, 0, tzinfo=timezone.utc)537 assert next_month == expected538 539 def test_get_next_month_start_regular(self, provider):540 """Test next month calculation for regular months."""541 with patch('voice_control.providers.azure_speech_provider.datetime') as mock_dt:542 mock_dt.now.return_value = datetime(2023, 6, 15, 10, 30, 0, tzinfo=timezone.utc)543 mock_dt.side_effect = lambda *args, **kw: datetime(*args, **kw)544 545 next_month = provider._get_next_month_start()546 expected = datetime(2023, 7, 1, 0, 0, 0, tzinfo=timezone.utc)547 assert next_month == expected548 549 550class TestAzureSpeechProviderFactory:551 """Test cases for Azure Speech provider factory function."""552 553 @patch('voice_control.providers.azure_speech_provider.AZURE_SPEECH_AVAILABLE', True)554 def test_create_azure_speech_provider_defaults(self):555 """Test factory function with default parameters."""556 provider = create_azure_speech_provider()557 558 assert provider.name == "azure_speech"559 assert provider.config.priority == 3560 assert provider.config.provider_type == ProviderType.AZURE_SPEECH561 assert provider.config.enabled562 assert provider.config.cost_per_minute == AzureSpeechProvider.PRICING["neural"] / 60.0563 564 @patch('voice_control.providers.azure_speech_provider.AZURE_SPEECH_AVAILABLE', True)565 def test_create_azure_speech_provider_custom(self):566 """Test factory function with custom parameters."""567 provider = create_azure_speech_provider(568 name="custom_azure",569 priority=1,570 enabled=False,571 api_credentials={572 "subscription_key": "custom_key",573 "region": "westus"574 }575 )576 577 assert provider.name == "custom_azure"578 assert provider.config.priority == 1579 assert not provider.config.enabled580 assert provider.config.api_credentials["subscription_key"] == "custom_key"581 assert provider.config.api_credentials["region"] == "westus"582 583 584class TestAzureSpeechProviderIntegration:585 """Integration tests for Azure Speech provider."""586 587 @pytest.fixture588 def integration_config(self):589 """Create configuration for integration tests."""590 return ProviderConfig(591 name="azure_integration_test",592 provider_type=ProviderType.AZURE_SPEECH,593 enabled=True,594 priority=1,595 free_tier_limits=AzureSpeechProvider.FREE_TIER_LIMITS,596 rate_limits={597 "requests_per_minute": 20,598 "requests_per_day": 5000599 },600 supported_formats=AzureSpeechProvider.SUPPORTED_FORMATS,601 supported_languages=AzureSpeechProvider.SUPPORTED_LANGUAGES,602 cost_per_minute=AzureSpeechProvider.PRICING["neural"] / 60.0,603 api_credentials={604 "subscription_key": "integration_test_key",605 "region": "eastus"606 },607 timeout_seconds=30,608 max_retries=2609 )610 611 @pytest.mark.asyncio612 @patch('voice_control.providers.azure_speech_provider.AZURE_SPEECH_AVAILABLE', True)613 async def test_full_transcription_workflow(self, integration_config, mock_azure_sdk):614 """Test complete transcription workflow."""615 # Mock successful recognition616 mock_result = Mock()617 mock_result.reason = "RecognizedSpeech"618 mock_result.text = "Integration test successful"619 mock_result.json = json.dumps({620 "NBest": [{621 "Confidence": 0.92,622 "Display": "Integration test successful"623 }]624 })625 626 mock_recognizer = mock_azure_sdk.SpeechRecognizer.return_value627 mock_recognizer.recognize_once.return_value = mock_result628 629 # Create provider and test630 provider = AzureSpeechProvider(integration_config)631 632 # Test transcription633 audio_data = b"integration_test_audio_data"634 result = await provider.transcribe_audio(audio_data, "wav", "en-US")635 636 # Verify results637 assert result.text == "Integration test successful"638 assert result.confidence == 0.92639 assert result.provider == "azure_integration_test"640 assert result.language == "en-US"641 assert result.processing_time > 0642 643 # Verify usage was tracked644 assert provider._usage_stats["requests_today"] == 1645 assert provider._usage_stats["requests_this_minute"] == 1646 assert provider._usage_stats["audio_hours_this_month"] > 0647 648 @pytest.mark.asyncio649 @patch('voice_control.providers.azure_speech_provider.AZURE_SPEECH_AVAILABLE', True)650 async def test_quota_management_workflow(self, integration_config, mock_azure_sdk):651 """Test quota management across multiple requests."""652 provider = AzureSpeechProvider(integration_config)653 654 # Make multiple requests to test quota tracking655 for i in range(5):656 # Mock successful recognition657 mock_result = Mock()658 mock_result.reason = "RecognizedSpeech"659 mock_result.text = f"Request {i+1}"660 mock_result.json = json.dumps({661 "NBest": [{"Confidence": 0.9, "Display": f"Request {i+1}"}]662 })663 664 mock_recognizer = mock_azure_sdk.SpeechRecognizer.return_value665 mock_recognizer.recognize_once.return_value = mock_result666 667 # Transcribe668 audio_data = b"test_audio"669 result = await provider.transcribe_audio(audio_data, "wav", "en-US")670 assert result.text == f"Request {i+1}"671 672 # Check quota status673 quota_status = await provider.get_quota_status()674 675 minute_quota = quota_status["requests_per_minute"]676 assert minute_quota.current_usage == 5677 assert minute_quota.remaining == 15678 assert minute_quota.percentage_used == 0.25679 680 # Verify usage stats681 assert provider._usage_stats["requests_today"] == 5682 assert provider._usage_stats["requests_this_minute"] == 5683 684 685if __name__ == "__main__":686 pytest.main([__file__])