打通 T+ 快照、业务、发货、回款和核算准备链路,保留来源差异并提供待补状态。 新增业务自动接入任务、跨平台读取服务托管、权限复核、来源关联和前端核对页面。 补充迁移、隔离集成测试、浏览器模拟验收及角色授权边界修复。
105 lines
3.7 KiB
Python
105 lines
3.7 KiB
Python
"""在隔离 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]
|