Files
2026-07-13 13:39:38 +08:00

181 lines
5.6 KiB
Python

from __future__ import annotations
import unittest
from unittest.mock import AsyncMock, patch
from livekit.plugins.hamming._plugin import (
RECORDING_MODE_NONE,
RECORDING_MODE_SESSION_AUDIO,
HammingRuntime,
RecordingResolutionContext,
_build_recording_config,
_extract_test_case_run_id,
_SessionMonitor,
build_config,
)
class _FakeSession:
def __init__(self) -> None:
self._events: dict[str, object] = {}
async def start(self, *args: object, **kwargs: object) -> object:
return {"args": args, "kwargs": kwargs}
def on(self, event_name: str, handler: object) -> None:
self._events[event_name] = handler
class _FakeReport:
room = "room-123"
started_at = 100.0
timestamp = 125.0
events: list[object] = []
audio_recording_path = None
def to_dict(self) -> dict[str, object]:
return {"room": self.room, "events": []}
class RecordingConfigTests(unittest.TestCase):
def test_default_configuration_disables_recording(self) -> None:
config = build_config(api_key="test-key", external_agent_id="agent-123")
self.assertEqual(config.recording.mode, RECORDING_MODE_NONE)
def test_auto_record_audio_maps_to_session_audio_mode(self) -> None:
config = _build_recording_config(
recording=None,
auto_record_audio=True,
recording_mode=None,
)
self.assertEqual(config.mode, RECORDING_MODE_SESSION_AUDIO)
def test_unsupported_modes_fail_fast(self) -> None:
with self.assertRaises(ValueError):
_build_recording_config(
recording={"mode": "unsupported_mode"},
auto_record_audio=False,
recording_mode=None,
)
def test_participant_egress_requires_recording_resolver(self) -> None:
with self.assertRaises(ValueError):
build_config(
api_key="test-key",
external_agent_id="agent-123",
recording={"mode": "participant_egress"},
)
def test_extract_test_case_run_id_falls_back_to_recording_context(self) -> None:
value = _extract_test_case_run_id(
participant_metadata_raw=None,
recording_context={"customer_conversation_id": "conv-123"},
)
self.assertEqual(value, "conv-123")
class AttachSessionRecordingTests(unittest.IsolatedAsyncioTestCase):
async def test_session_audio_wraps_session_start_with_default_recording_options(
self,
) -> None:
runtime = HammingRuntime(
build_config(
api_key="test-key",
external_agent_id="agent-123",
recording={"mode": "session_audio"},
)
)
session = _FakeSession()
runtime.attach_session(session)
result = await session.start(foo="bar")
self.assertEqual(
result,
{
"args": (),
"kwargs": {
"foo": "bar",
"record": {
"audio": True,
"traces": False,
"logs": False,
"transcript": False,
},
},
},
)
self.assertIn("close", session._events)
async def test_session_audio_does_not_override_explicit_record_argument(self) -> None:
runtime = HammingRuntime(
build_config(
api_key="test-key",
external_agent_id="agent-123",
recording={"mode": "session_audio"},
)
)
session = _FakeSession()
runtime.attach_session(session)
result = await session.start(record={"audio": False})
self.assertEqual(
result,
{
"args": (),
"kwargs": {
"record": {
"audio": False,
},
},
},
)
async def test_room_composite_lookup_retries_after_transient_fetch_failure(self) -> None:
runtime = HammingRuntime(build_config(api_key="test-key", external_agent_id="agent-123"))
runtime.transport.fetch_test_case_run_recording_url = AsyncMock(
side_effect=[RuntimeError("transient"), "https://recordings.example.com/room.ogg"]
)
monitor = _SessionMonitor(
runtime=runtime,
session=_FakeSession(),
participant_identity=None,
participant_metadata='{"conversation_id":"conv-123"}',
external_agent_id="agent-123",
job_ctx=None,
session_key=1,
recording_context=None,
)
resolution_context = RecordingResolutionContext(
session=monitor._session,
report=_FakeReport(),
job_ctx=None,
close_event=None,
call_id="call-123",
room_name="room-123",
participant_identity=None,
participant_metadata_raw='{"conversation_id":"conv-123"}',
external_agent_id="agent-123",
recording_context=None,
)
with patch("livekit.plugins.hamming._plugin.asyncio.sleep", new=AsyncMock()) as sleep_mock:
recording_url = await monitor._resolve_hamming_room_composite_recording_url(
resolution_context=resolution_context
)
self.assertEqual(recording_url, "https://recordings.example.com/room.ogg")
self.assertEqual(runtime.transport.fetch_test_case_run_recording_url.await_count, 2)
sleep_mock.assert_awaited_once()
if __name__ == "__main__":
unittest.main()