91 lines
3.2 KiB
Python
91 lines
3.2 KiB
Python
import asyncio
|
|
from types import SimpleNamespace
|
|
|
|
import pytest
|
|
|
|
from livekit.agents import stt
|
|
from livekit.plugins.azure import stt as azure_stt
|
|
|
|
pytestmark = pytest.mark.plugin("azure")
|
|
|
|
|
|
def test_azure_recognized_emits_final_transcript(monkeypatch):
|
|
events = []
|
|
monkeypatch.setattr(
|
|
azure_stt.speechsdk,
|
|
"AutoDetectSourceLanguageResult",
|
|
lambda result: SimpleNamespace(language="en-US"),
|
|
)
|
|
|
|
stream = azure_stt.SpeechStream.__new__(azure_stt.SpeechStream)
|
|
stream._opts = SimpleNamespace(language=["en-US"])
|
|
stream._loop = SimpleNamespace(call_soon_threadsafe=lambda callback, *args: callback(*args))
|
|
stream._event_ch = SimpleNamespace(send_nowait=events.append)
|
|
stream._start_time_offset = 0.0
|
|
|
|
stream._on_recognized(
|
|
SimpleNamespace(
|
|
result=SimpleNamespace(
|
|
text="hello",
|
|
offset=10_000_000,
|
|
duration=20_000_000,
|
|
result_id="azure-result-id",
|
|
)
|
|
)
|
|
)
|
|
|
|
assert [event.type for event in events] == [stt.SpeechEventType.FINAL_TRANSCRIPT]
|
|
|
|
|
|
def test_azure_stream_emits_usage_for_processed_audio():
|
|
events = []
|
|
stream = azure_stt.SpeechStream.__new__(azure_stt.SpeechStream)
|
|
stream._event_ch = SimpleNamespace(send_nowait=events.append)
|
|
stream._audio_duration = 3.5
|
|
stream._last_audio_duration_report_time = 0.0
|
|
|
|
stream._emit_recognition_usage()
|
|
|
|
assert [event.type for event in events] == [stt.SpeechEventType.RECOGNITION_USAGE]
|
|
assert events[0].recognition_usage is not None
|
|
assert events[0].recognition_usage.audio_duration == 3.5
|
|
assert stream._audio_duration == 0.0
|
|
|
|
|
|
def _canceled_event(reason, code=None, error_details=""):
|
|
return SimpleNamespace(
|
|
cancellation_details=SimpleNamespace(reason=reason, code=code, error_details=error_details)
|
|
)
|
|
|
|
|
|
def test_azure_canceled_error_unblocks_run():
|
|
# An Error cancellation (e.g. a service timeout) must wake _run via the
|
|
# stopped event and stash the details, so the base retry/fallback path runs
|
|
# instead of the stream hanging on a dead recognizer.
|
|
stream = azure_stt.SpeechStream.__new__(azure_stt.SpeechStream)
|
|
stream._loop = SimpleNamespace(call_soon_threadsafe=lambda callback, *args: callback(*args))
|
|
stream._session_stopped_event = asyncio.Event()
|
|
stream._cancellation_error = None
|
|
|
|
details = SimpleNamespace(
|
|
reason=azure_stt.speechsdk.CancellationReason.Error,
|
|
code=azure_stt.speechsdk.CancellationErrorCode.ServiceTimeout,
|
|
error_details="timeout",
|
|
)
|
|
stream._on_canceled(SimpleNamespace(cancellation_details=details))
|
|
|
|
assert stream._session_stopped_event.is_set()
|
|
assert stream._cancellation_error is details
|
|
|
|
|
|
def test_azure_canceled_without_error_is_ignored():
|
|
stream = azure_stt.SpeechStream.__new__(azure_stt.SpeechStream)
|
|
stream._loop = SimpleNamespace(call_soon_threadsafe=lambda callback, *args: callback(*args))
|
|
stream._session_stopped_event = asyncio.Event()
|
|
stream._cancellation_error = None
|
|
|
|
stream._on_canceled(_canceled_event(azure_stt.speechsdk.CancellationReason.EndOfStream))
|
|
|
|
assert not stream._session_stopped_event.is_set()
|
|
assert stream._cancellation_error is None
|