feat(T+): 完成数据贯通与自动接入闭环
打通 T+ 快照、业务、发货、回款和核算准备链路,保留来源差异并提供待补状态。 新增业务自动接入任务、跨平台读取服务托管、权限复核、来源关联和前端核对页面。 补充迁移、隔离集成测试、浏览器模拟验收及角色授权边界修复。
This commit is contained in:
1 parent
3efe72c344
commit
1e53792ad6
151 files changed
+8842
-401
No files matched your search
@@ -0,0 +1,104 @@
|
||||
"""在隔离 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]
|
||||
Reference in new issue
Block a user