Add collect stop, cutover nav prefetch/cache, and lane labels.

Let operators abort queued/running biz_state batches, speed cutover page switches, and show light/heavy collect lanes on monitor items.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
oliver 2026-09-22 19:09:18 +08:00
parent 305822aa4f
commit 924f9bd2f7
16 changed files with 779 additions and 112 deletions

View file

@ -0,0 +1,111 @@
"""biz_state collect stop: cancel queued / signal running."""
from __future__ import annotations
import unittest
from unittest.mock import patch
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from sqlalchemy.pool import StaticPool
from netx_api.biz_state import collect_stop as stop_mod
from netx_api.db import Base
from netx_api.models import BizStateBatch, BizStateTask
class BizStateCollectStopTests(unittest.TestCase):
def setUp(self) -> None:
engine = create_engine(
"sqlite+pysqlite:///:memory:",
future=True,
connect_args={"check_same_thread": False},
poolclass=StaticPool,
)
TestingSession = sessionmaker(
bind=engine, autoflush=False, autocommit=False, expire_on_commit=False
)
Base.metadata.create_all(bind=engine)
self.Session = TestingSession
self.db = TestingSession()
self._session_patch = patch.object(stop_mod, "SessionLocal", TestingSession)
self._session_patch.start()
stop_mod._stop_batches.clear()
stop_mod._holders.clear()
stop_mod._db_cache.clear()
self.task = BizStateTask(
id="t-stop",
source="managed",
ne_id="ne-1",
ne_name="NE1",
status="running",
collect_running=True,
)
self.db.add(self.task)
self.db.commit()
def tearDown(self) -> None:
self._session_patch.stop()
self.db.close()
def test_stop_cancels_queued_batch(self) -> None:
batch = BizStateBatch(
id="b-q",
task_id=self.task.id,
status="queued",
message="queued_manual",
)
self.db.add(batch)
self.db.commit()
out = stop_mod.request_stop_collect(self.task.id)
self.assertTrue(out.get("ok"))
self.assertTrue(out.get("stopped"))
self.assertEqual(out.get("cancelled_batches"), ["b-q"])
self.assertEqual(out.get("signaled_batches"), [])
self.db.expire_all()
b = self.db.get(BizStateBatch, "b-q")
t = self.db.get(BizStateTask, self.task.id)
assert b is not None and t is not None
self.assertEqual(b.status, "cancelled")
self.assertEqual(b.message, stop_mod.STOP_USER_MESSAGE)
self.assertFalse(t.collect_running)
def test_stop_signals_running_batch(self) -> None:
batch = BizStateBatch(
id="b-r",
task_id=self.task.id,
status="running",
message="",
)
self.db.add(batch)
self.db.commit()
out = stop_mod.request_stop_collect(self.task.id)
self.assertTrue(out.get("ok"))
self.assertTrue(out.get("stopped"))
self.assertEqual(out.get("signaled_batches"), ["b-r"])
self.assertTrue(stop_mod.is_stop_requested("b-r"))
self.db.expire_all()
b = self.db.get(BizStateBatch, "b-r")
t = self.db.get(BizStateTask, self.task.id)
assert b is not None and t is not None
self.assertEqual(b.status, "running")
self.assertEqual(b.message, stop_mod.STOP_REQUEST_TOKEN)
# Still running until worker finishes.
self.assertTrue(t.collect_running)
def test_stop_idle(self) -> None:
self.task.collect_running = False
self.db.commit()
out = stop_mod.request_stop_collect(self.task.id)
self.assertTrue(out.get("ok"))
self.assertFalse(out.get("stopped"))
self.assertEqual(out.get("reason"), "not_collecting")
if __name__ == "__main__":
unittest.main()