@@ -211,14 +211,25 @@ def test_restart_and_login_relaunches_client_and_reconnects(monkeypatch):
211211 executable = Path (r"C:\apps\qmt\bin.x64\XtMiniQmt.exe" )
212212 calls = []
213213
214+ issued_tokens = []
215+ monkeypatch .setattr (service , "issue_restart_password_token" , lambda password : issued_tokens .append (password ) or "test-token" )
214216 monkeypatch .setattr (service , "_get_current_session_id" , lambda : 1 )
215217 monkeypatch .setattr (service , "_resolve_qmt_client_path" , lambda qmt_path : executable )
216218 monkeypatch .setattr (service , "disconnect" , lambda : calls .append ("disconnect" ))
217219 monkeypatch .setattr (service , "discard_restart_password_token" , lambda token : calls .append (("discard" , token )))
218220 monkeypatch .setattr (service , "_set_connection_state" , lambda connected , message : calls .append (("state" , connected , message )) or message )
219221 monkeypatch .setattr (service , "_kill_qmt_process" , lambda name : calls .append (("kill" , name )) or "terminated" )
220222 monkeypatch .setattr (service , "_launch_qmt_process" , lambda path , password_token = None : calls .append (("launch" , path , password_token )) or SimpleNamespace (pid = 4321 ))
221- monkeypatch .setattr (service , "_fill_qmt_login_password" , lambda pid , password : calls .append (("fill" , pid , password )))
223+ # 关键:填密码被推到 helper 子进程后,gateway 主进程不应该再调
224+ # _fill_qmt_login_password。把它 monkeypatch 成抛 AssertionError 来
225+ # 防止回归到旧路径。
226+ monkeypatch .setattr (
227+ service ,
228+ "_fill_qmt_login_password" ,
229+ lambda * a , ** k : (_ for _ in ()).throw (
230+ AssertionError ("_fill_qmt_login_password 必须在 helper 子进程中执行" )
231+ ),
232+ )
222233 monkeypatch .setattr (
223234 service ,
224235 "connect" ,
@@ -233,15 +244,17 @@ def test_restart_and_login_relaunches_client_and_reconnects(monkeypatch):
233244 )
234245
235246 assert result == {"success" : True , "message" : "QMT 已重启并重新连接交易接口" }
247+ # 关键:launch 阶段必须传 token(让 helper 走子进程),不再有
248+ # _fill_qmt_login_password 调用。
236249 assert calls == [
237250 "disconnect" ,
238251 ("state" , False , "交易接口连接断开,正在重启 QMT" ),
239252 ("kill" , "XtMiniQmt.exe" ),
240- ("launch" , executable , None ),
241- ("fill" , 4321 , "trade-secret" ),
253+ ("launch" , executable , "test-token" ),
242254 ("connect" , "8881457417" , r"C:\apps\qmt\userdata_mini" ),
243- ("discard" , None ),
255+ ("discard" , "test-token" ),
244256 ]
257+ assert issued_tokens == ["trade-secret" ]
245258
246259
247260def test_restart_and_login_requires_password ():
@@ -256,26 +269,39 @@ def test_restart_and_login_requires_password():
256269 assert result == {"success" : False , "error" : "请输入交易密码" }
257270
258271
259- def test_restart_and_login_waits_for_login_window_before_connecting (monkeypatch ):
260- """填入密码后应等待登录窗口消失,再尝试 connect()。"""
272+ def test_restart_and_login_passes_token_to_launch_in_interactive_session (monkeypatch ):
273+ """#120 回归:填密码必须经 helper 子进程,gateway 主进程不应直接调
274+ ``_fill_qmt_login_password``(uiautomationcore.dll 残留 COM 回调会
275+ 在 QMT 退出后引发 access violation)。
276+ """
261277 service = TradeService ()
262278 service ._account_id = "8881457417"
263279 service ._qmt_path = r"C:\apps\qmt\userdata_mini"
264280 executable = Path (r"C:\apps\qmt\bin.x64\XtMiniQmt.exe" )
265281 calls = []
266282
283+ monkeypatch .setattr (service , "issue_restart_password_token" , lambda password : "tok-XYZ" )
267284 monkeypatch .setattr (service , "_get_current_session_id" , lambda : 1 )
268285 monkeypatch .setattr (service , "_resolve_qmt_client_path" , lambda qmt_path : executable )
269286 monkeypatch .setattr (service , "disconnect" , lambda : calls .append ("disconnect" ))
270287 monkeypatch .setattr (service , "discard_restart_password_token" , lambda token : calls .append (("discard" , token )))
271288 monkeypatch .setattr (service , "_set_connection_state" , lambda connected , message : calls .append (("state" , connected , message )) or message )
272289 monkeypatch .setattr (service , "_kill_qmt_process" , lambda name : "terminated" )
273- monkeypatch .setattr (service , "_launch_qmt_process" , lambda path , password_token = None : SimpleNamespace (pid = 4321 ))
274- monkeypatch .setattr (service , "_fill_qmt_login_password" , lambda pid , password : calls .append (("fill" , pid , password )))
290+ monkeypatch .setattr (service , "_launch_qmt_process" , lambda path , password_token = None : calls .append (("launch" , path , password_token )) or SimpleNamespace (pid = 4321 ))
291+ # gateway 主进程不应再调 _fill_qmt_login_password / _wait_for_login_window
292+ monkeypatch .setattr (
293+ service ,
294+ "_fill_qmt_login_password" ,
295+ lambda * a , ** k : (_ for _ in ()).throw (
296+ AssertionError ("不应在主进程中调 _fill_qmt_login_password" )
297+ ),
298+ )
275299 monkeypatch .setattr (
276300 service ,
277301 "_wait_for_login_window_to_close" ,
278- lambda pid , timeout_sec : calls .append (("wait" , pid , timeout_sec )) or True ,
302+ lambda * a , ** k : (_ for _ in ()).throw (
303+ AssertionError ("不应在主进程中调 _wait_for_login_window_to_close" )
304+ ),
279305 )
280306 monkeypatch .setattr (
281307 service ,
@@ -291,12 +317,15 @@ def test_restart_and_login_waits_for_login_window_before_connecting(monkeypatch)
291317 )
292318
293319 assert result ["success" ] is True
294- # 关键:fill 之后必须 wait,然后才 connect
295- assert ("fill" , 4321 , "trade-secret" ) in calls
296- fill_index = calls .index (("fill" , 4321 , "trade-secret" ))
297- wait_index = calls .index (("wait" , 4321 , 20.0 ))
298- connect_index = calls .index (("connect" , "8881457417" , r"C:\apps\qmt\userdata_mini" ))
299- assert fill_index < wait_index < connect_index
320+ # 关键:launch 阶段带 token(让 helper 子进程填密码),connect 紧随其后,
321+ # 中间不再有 _fill_qmt_login_password / _wait_for_login_window_to_close。
322+ assert calls == [
323+ "disconnect" ,
324+ ("state" , False , "交易接口连接断开,正在重启 QMT" ),
325+ ("launch" , executable , "tok-XYZ" ),
326+ ("connect" , "8881457417" , r"C:\apps\qmt\userdata_mini" ),
327+ ("discard" , "tok-XYZ" ),
328+ ]
300329
301330
302331def test_kill_qmt_process_ignores_missing_process_message (monkeypatch ):
@@ -364,6 +393,10 @@ def set_edit_text(self, password):
364393
365394
366395def test_launch_qmt_process_uses_shell_open_and_detects_new_pid (monkeypatch ):
396+ """#120 回归:Session 2 路径现在也走 helper 子进程,pywinauto 不在主进程内
397+ 调用。本测试只验证 _launch_qmt_process_locally 的旧行为(在没有 token
398+ 时仍可独立调用),由 _launch_qmt_process 路由。
399+ """
367400 service = TradeService ()
368401 executable = Path (r"C:\apps\qmt\bin.x64\XtMiniQmt.exe" )
369402 launch_calls = []
@@ -374,7 +407,7 @@ def test_launch_qmt_process_uses_shell_open_and_detects_new_pid(monkeypatch):
374407 monkeypatch .setattr (trade_service_module .os , "startfile" , lambda path , cwd = None : launch_calls .append ((path , cwd )))
375408 monkeypatch .setattr (trade_service_module .time , "sleep" , lambda seconds : None )
376409
377- process = service ._launch_qmt_process (executable )
410+ process = service ._launch_qmt_process_locally (executable , set () )
378411
379412 assert process .pid == 8765
380413 assert launch_calls == [(str (executable ), str (executable .parent ))]
0 commit comments