fix: manage WebRTC pipeline shutdown
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import unittest
|
||||
from types import SimpleNamespace
|
||||
from unittest.mock import AsyncMock, patch
|
||||
@@ -8,6 +9,30 @@ from services.conversation_history import ConversationRecorder
|
||||
|
||||
|
||||
class ConversationRecorderTest(unittest.IsolatedAsyncioTestCase):
|
||||
async def test_finish_waits_for_database_cleanup_when_cancelled(self):
|
||||
recorder = ConversationRecorder("conv_test")
|
||||
started = asyncio.Event()
|
||||
release = asyncio.Event()
|
||||
completed = asyncio.Event()
|
||||
|
||||
async def finish_database_write(*, status: str):
|
||||
self.assertEqual(status, "failed")
|
||||
started.set()
|
||||
await release.wait()
|
||||
completed.set()
|
||||
|
||||
recorder._finish = finish_database_write
|
||||
task = asyncio.create_task(recorder.finish(status="failed"))
|
||||
await started.wait()
|
||||
task.cancel()
|
||||
await asyncio.sleep(0)
|
||||
|
||||
self.assertFalse(task.done())
|
||||
release.set()
|
||||
with self.assertRaises(asyncio.CancelledError):
|
||||
await task
|
||||
self.assertTrue(completed.is_set())
|
||||
|
||||
async def test_fixed_reply_transcript_keeps_workflow_metadata(self):
|
||||
recorder = ConversationRecorder("conv_test")
|
||||
recorder._append = AsyncMock()
|
||||
|
||||
65
backend/tests/test_voice_webrtc_lifecycle.py
Normal file
65
backend/tests/test_voice_webrtc_lifecycle.py
Normal file
@@ -0,0 +1,65 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import unittest
|
||||
from unittest.mock import patch
|
||||
|
||||
from routes import voice_webrtc
|
||||
|
||||
|
||||
class FakeConnection:
|
||||
pc_id = "pc_test"
|
||||
|
||||
|
||||
class WebRTCPipelineLifecycleTest(unittest.IsolatedAsyncioTestCase):
|
||||
async def asyncTearDown(self):
|
||||
tasks = list(voice_webrtc._pipeline_tasks)
|
||||
for task in tasks:
|
||||
task.cancel()
|
||||
if tasks:
|
||||
await asyncio.gather(*tasks, return_exceptions=True)
|
||||
voice_webrtc._pipeline_tasks.clear()
|
||||
voice_webrtc._connection_tasks.clear()
|
||||
voice_webrtc._active_connections.clear()
|
||||
|
||||
async def test_pipeline_task_is_retained_until_completion(self):
|
||||
connection = FakeConnection()
|
||||
release = asyncio.Event()
|
||||
|
||||
async def pipeline():
|
||||
await release.wait()
|
||||
|
||||
task = voice_webrtc._start_pipeline_task(connection, pipeline())
|
||||
self.assertIn(task, voice_webrtc._pipeline_tasks)
|
||||
self.assertIs(voice_webrtc._connection_tasks[connection], task)
|
||||
|
||||
release.set()
|
||||
await task
|
||||
await asyncio.sleep(0)
|
||||
|
||||
self.assertNotIn(task, voice_webrtc._pipeline_tasks)
|
||||
self.assertNotIn(connection, voice_webrtc._connection_tasks)
|
||||
|
||||
async def test_stuck_pipeline_is_cancelled_after_close_grace_period(self):
|
||||
connection = FakeConnection()
|
||||
cancelled = asyncio.Event()
|
||||
|
||||
async def pipeline():
|
||||
try:
|
||||
await asyncio.Event().wait()
|
||||
finally:
|
||||
cancelled.set()
|
||||
|
||||
task = voice_webrtc._start_pipeline_task(connection, pipeline())
|
||||
with patch.object(voice_webrtc, "PIPELINE_CLOSE_GRACE_SECONDS", 0.01):
|
||||
await voice_webrtc._wait_for_pipeline_close(
|
||||
task,
|
||||
connection_id=connection.pc_id,
|
||||
)
|
||||
|
||||
self.assertTrue(cancelled.is_set())
|
||||
self.assertTrue(task.cancelled())
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user