"""Protocol-shaped deterministic mocks used by the mock profile. They record protocol facts instead of pretending that an HTTP callback is a SIP, ARI, RTP, AI-stream, or OSS integration. """ from __future__ import annotations import hashlib import json import threading from pathlib import Path from typing import Any class ProtocolJournal: def __init__(self, path: str | Path) -> None: self.path = Path(path) self.path.parent.mkdir(parents=True, exist_ok=True) self._lock = threading.Lock() def append(self, protocol: str, event: str, **facts: Any) -> None: entry = {"protocol": protocol, "event": event, **facts} try: encoded = json.dumps( entry, ensure_ascii=False, sort_keys=True, separators=(",", ":") ) with self._lock, self.path.open("a", encoding="utf-8") as stream: stream.write(encoded + "\n") except (OSError, TypeError, ValueError) as exc: raise RuntimeError("protocol journal write failed") from exc def read(self) -> list[dict[str, Any]]: try: lines = self.path.read_text(encoding="utf-8").splitlines() return [json.loads(line) for line in lines if line] except (OSError, UnicodeDecodeError, json.JSONDecodeError) as exc: raise RuntimeError("protocol journal read failed") from exc class MockSIPPeer: def __init__(self, journal: ProtocolJournal) -> None: self.journal = journal def invite(self, call_id: str, callee: str, target_uri: str, caller: str) -> None: self.journal.append( "sip", "invite", call_id=call_id, callee=callee, target_uri=target_uri, caller=caller, ) def ringing(self, call_id: str) -> None: self.journal.append("sip", "ringing", call_id=call_id) def answered(self, call_id: str) -> None: self.journal.append("sip", "answered", call_id=call_id) def bye(self, call_id: str, reason: str) -> None: self.journal.append("sip", "bye", call_id=call_id, reason=reason) class MockARI: def __init__(self, journal: ProtocolJournal) -> None: self.journal = journal def channel_created(self, call_id: str, cell_id: str) -> None: self.journal.append("ari", "channel_created", call_id=call_id, cell_id=cell_id) def bridge_ready(self, call_id: str) -> None: self.journal.append("ari", "bridge_ready", call_id=call_id) def media_closed(self, call_id: str) -> None: self.journal.append("ari", "media_closed", call_id=call_id) def cleanup(self, call_id: str) -> None: self.journal.append("ari", "cleanup", call_id=call_id) class MockRTP: def __init__(self, journal: ProtocolJournal) -> None: self.journal = journal def packet( self, call_id: str, direction: str, sequence: int, payload: bytes | None = None, ) -> None: payload = payload if payload is not None else b"\x01\x02" * 160 self.journal.append( "rtp", "packet", call_id=call_id, direction=direction, sequence=sequence, payload_bytes=len(payload), payload_sha256=hashlib.sha256(payload).hexdigest(), non_silent=any(payload), ) class MockAI: def __init__(self, journal: ProtocolJournal) -> None: self.journal = journal def turn( self, call_id: str, turn_index: int, speaker: str, playback_state: str, text: str | None = None, ) -> None: self.journal.append( "ai", "turn", call_id=call_id, turn_index=turn_index, speaker=speaker, playback_state=playback_state, text=text or f"mock-{speaker}-turn-{turn_index}", ) self.journal.append( "ai", "playback", call_id=call_id, turn_index=turn_index, state="sent", ) self.journal.append( "ai", "playback", call_id=call_id, turn_index=turn_index, state=playback_state if playback_state in {"confirmed", "playback_confirmed", "unknown"} else "unknown", )