@@ -1094,3 +1094,177 @@ 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 (
1186+ patch ("decart.realtime.client.WebRTCManager" ) as mock_manager_class ,
1187+ ):
1188+ mock_manager = AsyncMock ()
1189+ mock_manager .connect = AsyncMock (return_value = True )
1190+ mock_manager_class .return_value = mock_manager
1191+
1192+ # subscribe() passes local_track=None internally
1193+ from decart .realtime .subscribe import SubscribeClient , encode_subscribe_token
1194+
1195+ token = encode_subscribe_token ("test-sid" , "1.2.3.4" , 8080 )
1196+
1197+ from decart .realtime .subscribe import SubscribeOptions
1198+
1199+ sub_client = await RealtimeClient .subscribe (
1200+ base_url = client .base_url ,
1201+ api_key = client .api_key ,
1202+ options = SubscribeOptions (
1203+ token = token ,
1204+ on_remote_stream = lambda t : None ,
1205+ ),
1206+ )
1207+
1208+ assert sub_client is not None
1209+ # Verify connect was called with local_track=None (subscribe mode)
1210+ mock_manager .connect .assert_called_once ()
1211+ call_args = mock_manager .connect .call_args
1212+ assert call_args [0 ][0 ] is None # first positional arg is local_track=None
1213+
1214+
1215+ @pytest .mark .asyncio
1216+ async def test_server_error_during_passthrough_fails_fast ():
1217+ """Server error during passthrough surfaces real error instead of 30s timeout."""
1218+ from decart .realtime .webrtc_connection import WebRTCConnection
1219+ from decart .realtime .messages import ErrorMessage
1220+ from decart .errors import WebRTCError
1221+
1222+ connection = WebRTCConnection ()
1223+
1224+ async def fake_send (message ):
1225+ # Simulate the server responding with an error instead of set_image_ack
1226+ await asyncio .sleep (0 ) # yield so wait_for is listening
1227+ connection ._handle_error (ErrorMessage (type = "error" , error = "insufficient_credits" ))
1228+
1229+ connection ._send_message = fake_send # type: ignore[assignment]
1230+
1231+ with pytest .raises (WebRTCError , match = "insufficient_credits" ):
1232+ await connection ._send_passthrough_and_wait ()
1233+
1234+
1235+ @pytest .mark .asyncio
1236+ async def test_server_error_during_initial_image_fails_fast ():
1237+ """Server error during initial image setup surfaces real error (pre-existing fix)."""
1238+ from decart .realtime .webrtc_connection import WebRTCConnection
1239+ from decart .realtime .messages import ErrorMessage
1240+ from decart .errors import WebRTCError
1241+
1242+ connection = WebRTCConnection ()
1243+
1244+ async def fake_send (message ):
1245+ await asyncio .sleep (0 )
1246+ connection ._handle_error (ErrorMessage (type = "error" , error = "invalid_image" ))
1247+
1248+ connection ._send_message = fake_send # type: ignore[assignment]
1249+
1250+ with pytest .raises (WebRTCError , match = "invalid_image" ):
1251+ await connection ._send_initial_image_and_wait ("base64data" )
1252+
1253+
1254+ @pytest .mark .asyncio
1255+ async def test_server_error_during_initial_prompt_fails_fast ():
1256+ """Server error during initial prompt setup surfaces real error (pre-existing fix)."""
1257+ from decart .realtime .webrtc_connection import WebRTCConnection
1258+ from decart .realtime .messages import ErrorMessage
1259+ from decart .errors import WebRTCError
1260+
1261+ connection = WebRTCConnection ()
1262+
1263+ async def fake_send (message ):
1264+ await asyncio .sleep (0 )
1265+ connection ._handle_error (ErrorMessage (type = "error" , error = "rate_limited" ))
1266+
1267+ connection ._send_message = fake_send # type: ignore[assignment]
1268+
1269+ with pytest .raises (WebRTCError , match = "rate_limited" ):
1270+ await connection ._send_initial_prompt_and_wait ({"text" : "test" , "enhance" : True })
0 commit comments