- Expanded package inclusion in `pyproject.toml` to support new modules. - Introduced new `adapters` and `protocol` packages for better organization. - Added backend adapter implementations for control plane integration. - Updated main application imports to reflect new package structure. - Removed deprecated core components and adjusted documentation accordingly. - Enhanced architecture documentation to clarify the new runtime and integration layers.
148 lines
4.5 KiB
Python
148 lines
4.5 KiB
Python
import asyncio
|
|
import time
|
|
|
|
import pytest
|
|
|
|
from runtime.history.bridge import SessionHistoryBridge
|
|
|
|
|
|
class _FakeHistoryWriter:
|
|
def __init__(self, *, add_delay_s: float = 0.0, add_result: bool = True):
|
|
self.add_delay_s = add_delay_s
|
|
self.add_result = add_result
|
|
self.created_call_ids = []
|
|
self.transcripts = []
|
|
self.finalize_calls = 0
|
|
self.finalize_statuses = []
|
|
self.finalize_at = None
|
|
self.last_transcript_at = None
|
|
|
|
async def create_call_record(self, *, user_id: int, assistant_id: str | None, source: str = "debug"):
|
|
_ = (user_id, assistant_id, source)
|
|
call_id = "call_test_1"
|
|
self.created_call_ids.append(call_id)
|
|
return call_id
|
|
|
|
async def add_transcript(
|
|
self,
|
|
*,
|
|
call_id: str,
|
|
turn_index: int,
|
|
speaker: str,
|
|
content: str,
|
|
start_ms: int,
|
|
end_ms: int,
|
|
confidence: float | None = None,
|
|
duration_ms: int | None = None,
|
|
) -> bool:
|
|
_ = confidence
|
|
if self.add_delay_s > 0:
|
|
await asyncio.sleep(self.add_delay_s)
|
|
self.transcripts.append(
|
|
{
|
|
"call_id": call_id,
|
|
"turn_index": turn_index,
|
|
"speaker": speaker,
|
|
"content": content,
|
|
"start_ms": start_ms,
|
|
"end_ms": end_ms,
|
|
"duration_ms": duration_ms,
|
|
}
|
|
)
|
|
self.last_transcript_at = time.monotonic()
|
|
return self.add_result
|
|
|
|
async def finalize_call_record(self, *, call_id: str, status: str, duration_seconds: int) -> bool:
|
|
_ = (call_id, duration_seconds)
|
|
self.finalize_calls += 1
|
|
self.finalize_statuses.append(status)
|
|
self.finalize_at = time.monotonic()
|
|
return True
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_slow_backend_does_not_block_enqueue():
|
|
writer = _FakeHistoryWriter(add_delay_s=0.15, add_result=True)
|
|
bridge = SessionHistoryBridge(
|
|
history_writer=writer,
|
|
enabled=True,
|
|
queue_max_size=32,
|
|
retry_max_attempts=0,
|
|
retry_backoff_sec=0.01,
|
|
finalize_drain_timeout_sec=1.0,
|
|
)
|
|
|
|
try:
|
|
call_id = await bridge.start_call(user_id=1, assistant_id="assistant_1", source="debug")
|
|
assert call_id == "call_test_1"
|
|
|
|
t0 = time.perf_counter()
|
|
queued = bridge.enqueue_turn(role="user", text="hello world")
|
|
elapsed_s = time.perf_counter() - t0
|
|
|
|
assert queued is True
|
|
assert elapsed_s < 0.02
|
|
|
|
await bridge.finalize(status="connected")
|
|
assert len(writer.transcripts) == 1
|
|
assert writer.finalize_calls == 1
|
|
finally:
|
|
await bridge.shutdown()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_failing_backend_retries_but_enqueue_remains_non_blocking():
|
|
writer = _FakeHistoryWriter(add_delay_s=0.01, add_result=False)
|
|
bridge = SessionHistoryBridge(
|
|
history_writer=writer,
|
|
enabled=True,
|
|
queue_max_size=32,
|
|
retry_max_attempts=2,
|
|
retry_backoff_sec=0.01,
|
|
finalize_drain_timeout_sec=0.5,
|
|
)
|
|
|
|
try:
|
|
await bridge.start_call(user_id=1, assistant_id="assistant_1", source="debug")
|
|
t0 = time.perf_counter()
|
|
assert bridge.enqueue_turn(role="assistant", text="retry me")
|
|
elapsed_s = time.perf_counter() - t0
|
|
assert elapsed_s < 0.02
|
|
|
|
await bridge.finalize(status="connected")
|
|
|
|
# Initial try + 2 retries
|
|
assert len(writer.transcripts) == 3
|
|
assert writer.finalize_calls == 1
|
|
finally:
|
|
await bridge.shutdown()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_finalize_is_idempotent_and_waits_for_queue_drain():
|
|
writer = _FakeHistoryWriter(add_delay_s=0.05, add_result=True)
|
|
bridge = SessionHistoryBridge(
|
|
history_writer=writer,
|
|
enabled=True,
|
|
queue_max_size=32,
|
|
retry_max_attempts=0,
|
|
retry_backoff_sec=0.01,
|
|
finalize_drain_timeout_sec=1.0,
|
|
)
|
|
|
|
try:
|
|
await bridge.start_call(user_id=1, assistant_id="assistant_1", source="debug")
|
|
assert bridge.enqueue_turn(role="user", text="first")
|
|
|
|
ok_1 = await bridge.finalize(status="connected")
|
|
ok_2 = await bridge.finalize(status="connected")
|
|
|
|
assert ok_1 is True
|
|
assert ok_2 is True
|
|
assert len(writer.transcripts) == 1
|
|
assert writer.finalize_calls == 1
|
|
assert writer.last_transcript_at is not None
|
|
assert writer.finalize_at is not None
|
|
assert writer.finalize_at >= writer.last_transcript_at
|
|
finally:
|
|
await bridge.shutdown()
|