"""在隔离 MySQL 中验证托管状态及命名锁,不连接 T+ 源库。""" import threading import time from uuid import uuid4 from app.core.config import get_settings from app.core.db import get_session_factory from app.models import TPlusRuntimeControl from app.services.tplus import supervisor def test_托管服务在真实命名锁下启动并安全停止(monkeypatch): factory = get_session_factory() monkeypatch.setattr(supervisor, "LOCK_NAME", f"ys:test:supervisor:{uuid4().hex}") monkeypatch.setattr(supervisor, "health_error", lambda _settings: None) monkeypatch.setattr(supervisor, "tick", lambda *_args, **_kwargs: None) entered, release, stop = threading.Event(), threading.Event(), threading.Event() calls = [] def worker(_session, **_kwargs): calls.append(1) entered.set() assert release.wait(15) monkeypatch.setattr(supervisor, "run_once", worker) with factory() as session: row = session.get(TPlusRuntimeControl, 1) row.desired_state = "RUNNING" session.commit() result = [] thread = threading.Thread(target=lambda: result.append( supervisor.run(factory, get_settings(), interval=0.05, stop_event=stop) )) thread.start() try: assert entered.wait(10), "托管服务未启动工作线程" assert supervisor.run(factory, get_settings(), stop_event=threading.Event()) == 1 with factory() as session: row = session.get(TPlusRuntimeControl, 1) row.desired_state = "STOPPED" session.commit() deadline = time.monotonic() + 10 while time.monotonic() < deadline: with factory() as session: state = session.get(TPlusRuntimeControl, 1).actual_state if state == "STOPPING": break time.sleep(0.05) assert state == "STOPPING" release.set() deadline = time.monotonic() + 10 while time.monotonic() < deadline: with factory() as session: state = session.get(TPlusRuntimeControl, 1).actual_state if state == "STOPPED": break time.sleep(0.05) assert state == "STOPPED" assert len(calls) == 1 finally: release.set() stop.set() thread.join(15) assert not thread.is_alive() assert result == [0] def test_来源异常时真实数据库记录降级状态且不领取任务(monkeypatch): factory = get_session_factory() monkeypatch.setattr(supervisor, "LOCK_NAME", f"ys:test:supervisor:{uuid4().hex}") monkeypatch.setattr(supervisor, "health_error", lambda _settings: "T+ 数据库连接不可用") claimed = threading.Event() monkeypatch.setattr(supervisor, "run_once", lambda *_args, **_kwargs: claimed.set()) with factory() as session: session.get(TPlusRuntimeControl, 1).desired_state = "RUNNING" session.commit() stop = threading.Event() result = [] thread = threading.Thread(target=lambda: result.append( supervisor.run(factory, get_settings(), interval=0.05, stop_event=stop) )) thread.start() try: deadline = time.monotonic() + 10 state = None while time.monotonic() < deadline: with factory() as session: row = session.get(TPlusRuntimeControl, 1) state = row.actual_state error = row.last_error if state == "DEGRADED": break time.sleep(0.05) assert state == "DEGRADED" assert error == "T+ 数据库连接不可用" assert not claimed.is_set() finally: stop.set() thread.join(15) assert not thread.is_alive() assert result == [0]