@@ -1094,3 +1094,175 @@ async def test_image_to_base64_file_path_string(tmp_path):
10941094 mock_session = MagicMock ()
10951095 result = await _image_to_base64 (str (img ), mock_session )
10961096 assert result == base64 .b64encode (b"PNGDATA" ).decode ("utf-8" )
1097+
1098+
1099+ # Tests for passthrough mode (no initial prompt/image)
1100+
1101+
1102+ @pytest .mark .asyncio
1103+ async def test_connect_without_initial_state_sends_passthrough ():
1104+ """Connecting without prompt/image sends passthrough set_image (null image + null prompt)."""
1105+ client = DecartClient (api_key = "test-key" )
1106+
1107+ with (
1108+ patch ("decart.realtime.client.WebRTCManager" ) as mock_manager_class ,
1109+ patch ("decart.realtime.client.aiohttp.ClientSession" ) as mock_session_cls ,
1110+ ):
1111+ mock_manager = AsyncMock ()
1112+ mock_manager .connect = AsyncMock (return_value = True )
1113+ mock_manager .is_connected = MagicMock (return_value = True )
1114+ mock_manager_class .return_value = mock_manager
1115+
1116+ mock_session = MagicMock ()
1117+ mock_session .closed = False
1118+ mock_session .close = AsyncMock ()
1119+ mock_session_cls .return_value = mock_session
1120+
1121+ mock_track = MagicMock ()
1122+
1123+ from decart .realtime .types import RealtimeConnectOptions
1124+
1125+ realtime_client = await RealtimeClient .connect (
1126+ base_url = client .base_url ,
1127+ api_key = client .api_key ,
1128+ local_track = mock_track ,
1129+ options = RealtimeConnectOptions (
1130+ model = models .realtime ("mirage" ),
1131+ on_remote_stream = lambda t : None ,
1132+ # No initial_state — should trigger passthrough
1133+ ),
1134+ )
1135+
1136+ assert realtime_client is not None
1137+ mock_manager .connect .assert_called_once ()
1138+ call_kwargs = mock_manager .connect .call_args [1 ]
1139+ # initial_image and initial_prompt should both be None
1140+ assert call_kwargs .get ("initial_image" ) is None
1141+ assert call_kwargs .get ("initial_prompt" ) is None
1142+
1143+
1144+ @pytest .mark .asyncio
1145+ async def test_passthrough_sends_set_image_with_null_prompt ():
1146+ """_send_passthrough_and_wait sends set_image with null image_data and null prompt."""
1147+ from decart .realtime .webrtc_connection import WebRTCConnection
1148+
1149+ connection = WebRTCConnection ()
1150+
1151+ sent_messages : list = []
1152+
1153+ async def capture_send (message ):
1154+ sent_messages .append (message )
1155+ # Simulate set_image_ack arriving immediately (like FakeWebSocket in JS tests)
1156+ if connection ._pending_image_set :
1157+ event , result = connection ._pending_image_set
1158+ result ["success" ] = True
1159+ event .set ()
1160+
1161+ connection ._send_message = capture_send # type: ignore[assignment]
1162+
1163+ await connection ._send_passthrough_and_wait ()
1164+
1165+ assert len (sent_messages ) == 1
1166+ msg = sent_messages [0 ]
1167+ assert msg .type == "set_image"
1168+ assert msg .image_data is None
1169+ assert msg .prompt is None
1170+
1171+ # Verify JSON serialization includes null values
1172+ from decart .realtime .messages import message_to_json
1173+ import json
1174+
1175+ json_str = message_to_json (msg )
1176+ parsed = json .loads (json_str )
1177+ assert parsed == {"type" : "set_image" , "image_data" : None , "prompt" : None }
1178+
1179+
1180+ @pytest .mark .asyncio
1181+ async def test_subscribe_mode_skips_passthrough ():
1182+ """Subscribe mode (null local_track) must not send passthrough set_image."""
1183+ client = DecartClient (api_key = "test-key" )
1184+
1185+ with (patch ("decart.realtime.client.WebRTCManager" ) as mock_manager_class ,):
1186+ mock_manager = AsyncMock ()
1187+ mock_manager .connect = AsyncMock (return_value = True )
1188+ mock_manager_class .return_value = mock_manager
1189+
1190+ # subscribe() passes local_track=None internally
1191+ from decart .realtime .subscribe import SubscribeClient , encode_subscribe_token
1192+
1193+ token = encode_subscribe_token ("test-sid" , "1.2.3.4" , 8080 )
1194+
1195+ from decart .realtime .subscribe import SubscribeOptions
1196+
1197+ sub_client = await RealtimeClient .subscribe (
1198+ base_url = client .base_url ,
1199+ api_key = client .api_key ,
1200+ options = SubscribeOptions (
1201+ token = token ,
1202+ on_remote_stream = lambda t : None ,
1203+ ),
1204+ )
1205+
1206+ assert sub_client is not None
1207+ # Verify connect was called with local_track=None (subscribe mode)
1208+ mock_manager .connect .assert_called_once ()
1209+ call_args = mock_manager .connect .call_args
1210+ assert call_args [0 ][0 ] is None # first positional arg is local_track=None
1211+
1212+
1213+ @pytest .mark .asyncio
1214+ async def test_server_error_during_passthrough_fails_fast ():
1215+ """Server error during passthrough surfaces real error instead of 30s timeout."""
1216+ from decart .realtime .webrtc_connection import WebRTCConnection
1217+ from decart .realtime .messages import ErrorMessage
1218+ from decart .errors import WebRTCError
1219+
1220+ connection = WebRTCConnection ()
1221+
1222+ async def fake_send (message ):
1223+ # Simulate the server responding with an error instead of set_image_ack
1224+ await asyncio .sleep (0 ) # yield so wait_for is listening
1225+ connection ._handle_error (ErrorMessage (type = "error" , error = "insufficient_credits" ))
1226+
1227+ connection ._send_message = fake_send # type: ignore[assignment]
1228+
1229+ with pytest .raises (WebRTCError , match = "insufficient_credits" ):
1230+ await connection ._send_passthrough_and_wait ()
1231+
1232+
1233+ @pytest .mark .asyncio
1234+ async def test_server_error_during_initial_image_fails_fast ():
1235+ """Server error during initial image setup surfaces real error (pre-existing fix)."""
1236+ from decart .realtime .webrtc_connection import WebRTCConnection
1237+ from decart .realtime .messages import ErrorMessage
1238+ from decart .errors import WebRTCError
1239+
1240+ connection = WebRTCConnection ()
1241+
1242+ async def fake_send (message ):
1243+ await asyncio .sleep (0 )
1244+ connection ._handle_error (ErrorMessage (type = "error" , error = "invalid_image" ))
1245+
1246+ connection ._send_message = fake_send # type: ignore[assignment]
1247+
1248+ with pytest .raises (WebRTCError , match = "invalid_image" ):
1249+ await connection ._send_initial_image_and_wait ("base64data" )
1250+
1251+
1252+ @pytest .mark .asyncio
1253+ async def test_server_error_during_initial_prompt_fails_fast ():
1254+ """Server error during initial prompt setup surfaces real error (pre-existing fix)."""
1255+ from decart .realtime .webrtc_connection import WebRTCConnection
1256+ from decart .realtime .messages import ErrorMessage
1257+ from decart .errors import WebRTCError
1258+
1259+ connection = WebRTCConnection ()
1260+
1261+ async def fake_send (message ):
1262+ await asyncio .sleep (0 )
1263+ connection ._handle_error (ErrorMessage (type = "error" , error = "rate_limited" ))
1264+
1265+ connection ._send_message = fake_send # type: ignore[assignment]
1266+
1267+ with pytest .raises (WebRTCError , match = "rate_limited" ):
1268+ await connection ._send_initial_prompt_and_wait ({"text" : "test" , "enhance" : True })
0 commit comments