@@ -172,23 +172,74 @@ def _relay_with_fake_proc(self, lines):
172172 relay ._reader .join (timeout = 2 )
173173 return relay , proc
174174
175- def test_broadcasts_every_sidecar_line_to_subscribed_sockets (self ):
176- relay , proc = self ._relay_with_fake_proc (
177- ['{"type":"notify","method":"repl_data"}\n ' , '{"type":"result","id":1}\n ' ]
178- )
175+ def test_broadcasts_notify_events_to_every_subscribed_socket (self ):
176+ relay , proc = self ._relay_with_fake_proc ([])
179177 ws = mock .Mock ()
180178 relay .subscribe (ws )
181- # The reader thread already drained proc.stdout by the time it joined above,
182- # so re-broadcast is exercised directly for a deterministic assertion.
183- relay ._broadcast ('{"type":"notify","method":"repl_data"}' )
179+ relay ._route ('{"type":"notify","method":"repl_data"}' )
184180 ws .send_text .assert_called_with ('{"type":"notify","method":"repl_data"}' )
185181
186- def test_send_writes_a_newline_terminated_line_to_stdin (self ):
182+ def test_send_writes_a_newline_terminated_line_with_a_rewritten_id (self ):
187183 relay , proc = self ._relay_with_fake_proc ([])
188- relay .send ('{"method":"ping"}' )
189- proc .stdin .write .assert_called_once_with ('{"method":"ping"}\n ' )
184+ ws = mock .Mock ()
185+ relay .send ('{"id": 1, "method": "ping"}' , ws )
186+ written = proc .stdin .write .call_args [0 ][0 ]
187+ self .assertTrue (written .endswith ("\n " ))
188+ sent = self .mod .json .loads (written )
189+ self .assertEqual (sent ["method" ], "ping" )
190+ # The id sent to the sidecar is the relay's own counter value, tracked
191+ # in _pending against the tab's original id (1) -- not asserting it
192+ # merely differs from 1, which the first request could coincide with.
193+ self .assertEqual (relay ._pending , {sent ["id" ]: (ws , 1 )})
190194 proc .stdin .flush .assert_called_once ()
191195
196+ def test_a_result_routes_back_only_to_the_requesting_socket_with_its_own_id (self ):
197+ relay , proc = self ._relay_with_fake_proc ([])
198+ ws_a , ws_b = mock .Mock (), mock .Mock ()
199+ relay .subscribe (ws_a )
200+ relay .subscribe (ws_b )
201+ relay .send ('{"id": 1, "method": "eval"}' , ws_a )
202+ server_id = self .mod .json .loads (proc .stdin .write .call_args [0 ][0 ])["id" ]
203+
204+ relay ._route (self .mod .json .dumps ({"type" : "result" , "id" : server_id , "result" : {"value" : "2" }}))
205+
206+ ws_a .send_text .assert_called_once ()
207+ reply = self .mod .json .loads (ws_a .send_text .call_args [0 ][0 ])
208+ self .assertEqual (reply ["id" ], 1 ) # rewritten back to ws_a's own id
209+ ws_b .send_text .assert_not_called ()
210+
211+ def test_two_tabs_reusing_the_same_client_id_do_not_cross_wire (self ):
212+ """The bug mpftp#20 reports: independent per-tab id counters can collide."""
213+ relay , proc = self ._relay_with_fake_proc ([])
214+ ws_a , ws_b = mock .Mock (), mock .Mock ()
215+ relay .subscribe (ws_a )
216+ relay .subscribe (ws_b )
217+
218+ relay .send ('{"id": 1, "method": "connect"}' , ws_a )
219+ server_id_a = self .mod .json .loads (proc .stdin .write .call_args [0 ][0 ])["id" ]
220+ relay .send ('{"id": 1, "method": "list_ports"}' , ws_b )
221+ server_id_b = self .mod .json .loads (proc .stdin .write .call_args [0 ][0 ])["id" ]
222+ self .assertNotEqual (server_id_a , server_id_b )
223+
224+ # B's response arrives first; A must not receive it even though both
225+ # tabs used client id 1.
226+ relay ._route (self .mod .json .dumps ({"type" : "result" , "id" : server_id_b , "result" : []}))
227+ ws_a .send_text .assert_not_called ()
228+ reply_b = self .mod .json .loads (ws_b .send_text .call_args [0 ][0 ])
229+ self .assertEqual (reply_b ["id" ], 1 )
230+
231+ relay ._route (self .mod .json .dumps ({"type" : "result" , "id" : server_id_a , "result" : {"ok" : True }}))
232+ reply_a = self .mod .json .loads (ws_a .send_text .call_args [0 ][0 ])
233+ self .assertEqual (reply_a ["id" ], 1 )
234+ self .assertEqual (ws_b .send_text .call_count , 1 ) # unchanged since its own reply
235+
236+ def test_a_result_with_no_matching_pending_id_falls_back_to_broadcast (self ):
237+ relay , proc = self ._relay_with_fake_proc ([])
238+ ws = mock .Mock ()
239+ relay .subscribe (ws )
240+ relay ._route ('{"type":"result","id":999,"result":{}}' )
241+ ws .send_text .assert_called_once_with ('{"type":"result","id":999,"result":{}}' )
242+
192243 def test_a_dead_subscriber_is_dropped_after_a_failed_send (self ):
193244 relay , proc = self ._relay_with_fake_proc ([])
194245 ws = mock .Mock ()
@@ -205,6 +256,14 @@ def test_unsubscribe_stops_further_broadcasts(self):
205256 relay ._broadcast ("line" )
206257 ws .send_text .assert_not_called ()
207258
259+ def test_unsubscribe_drops_that_sockets_pending_requests (self ):
260+ relay , proc = self ._relay_with_fake_proc ([])
261+ ws = mock .Mock ()
262+ relay .subscribe (ws )
263+ relay .send ('{"id": 1, "method": "eval"}' , ws )
264+ relay .unsubscribe (ws )
265+ self .assertEqual (relay ._pending , {})
266+
208267
209268if __name__ == "__main__" :
210269 unittest .main ()
0 commit comments