144 lines
4.7 KiB
Python
144 lines
4.7 KiB
Python
"""统一 run 生命周期服务的无 DB 回归测试。"""
|
|
from __future__ import annotations
|
|
|
|
import asyncio
|
|
import unittest
|
|
from contextlib import contextmanager
|
|
from types import SimpleNamespace
|
|
from unittest.mock import MagicMock, patch
|
|
from uuid import uuid4
|
|
|
|
from web import run_lifecycle
|
|
|
|
|
|
class ClaimRunTests(unittest.TestCase):
|
|
def test_claim_atomically_adds_message_and_marks_running(self) -> None:
|
|
tid, uid = uuid4(), uuid4()
|
|
task = SimpleNamespace(
|
|
task_id=tid,
|
|
user_id=uid,
|
|
run_status="idle",
|
|
model_profile="old",
|
|
)
|
|
session = MagicMock()
|
|
session.execute.side_effect = [
|
|
SimpleNamespace(scalar_one_or_none=lambda: task),
|
|
SimpleNamespace(scalar_one=lambda: 6),
|
|
MagicMock(),
|
|
]
|
|
|
|
@contextmanager
|
|
def fake_scope():
|
|
yield session
|
|
|
|
def prepare(_session, locked_task):
|
|
self.assertIs(locked_task, task)
|
|
return {"model_profile": "new"}, {"marker": "ok"}
|
|
|
|
with patch.object(run_lifecycle, "session_scope", fake_scope):
|
|
claim = run_lifecycle.claim_run_with_message(
|
|
tid, uid, "持久化消息", prepare=prepare,
|
|
attachment_refs=[{"path": "a.png", "kind": "image"}],
|
|
)
|
|
|
|
self.assertEqual(claim.metadata, {"marker": "ok"})
|
|
added = session.add.call_args.args[0]
|
|
self.assertEqual(added.task_id, tid)
|
|
self.assertEqual(added.idx, 7)
|
|
self.assertEqual(
|
|
added.payload,
|
|
{"role": "user", "content": "持久化消息"},
|
|
)
|
|
self.assertEqual(added.attachment_refs, [{"path": "a.png", "kind": "image"}])
|
|
update_stmt = session.execute.call_args_list[-1].args[0]
|
|
params = update_stmt.compile().params
|
|
self.assertEqual(params["run_status"], "running")
|
|
self.assertIsNone(params["run_error"])
|
|
self.assertEqual(params["model_profile"], "new")
|
|
|
|
def test_busy_task_is_rejected_before_message_insert(self) -> None:
|
|
session = MagicMock()
|
|
session.execute.return_value = SimpleNamespace(
|
|
scalar_one_or_none=lambda: SimpleNamespace(run_status="running")
|
|
)
|
|
|
|
@contextmanager
|
|
def fake_scope():
|
|
yield session
|
|
|
|
with (
|
|
patch.object(run_lifecycle, "session_scope", fake_scope),
|
|
self.assertRaises(run_lifecycle.RunTaskBusy),
|
|
):
|
|
run_lifecycle.claim_run_with_message(uuid4(), uuid4(), "消息")
|
|
session.add.assert_not_called()
|
|
|
|
|
|
class ScheduleRunTests(unittest.IsolatedAsyncioTestCase):
|
|
async def test_schedule_uses_persisted_entry_and_tracks_inflight(self) -> None:
|
|
tid, uid = uuid4(), uuid4()
|
|
app = SimpleNamespace(state=SimpleNamespace(inflight={}))
|
|
worker = MagicMock()
|
|
broker = MagicMock()
|
|
|
|
with (
|
|
patch.object(run_lifecycle, "run_agent_bg", worker),
|
|
patch.object(run_lifecycle, "broker", broker),
|
|
):
|
|
task = run_lifecycle.schedule_claimed_run(
|
|
app,
|
|
tid,
|
|
uid,
|
|
"已落库",
|
|
image_variant="image-x",
|
|
video_variant="video-y",
|
|
scheduled=True,
|
|
)
|
|
self.assertEqual(app.state.inflight[task], tid)
|
|
await task
|
|
await asyncio.sleep(0)
|
|
|
|
worker.assert_called_once_with(
|
|
tid,
|
|
uid,
|
|
"已落库",
|
|
"image-x",
|
|
"video-y",
|
|
True,
|
|
user_message_persisted=True,
|
|
)
|
|
self.assertEqual(app.state.inflight, {})
|
|
broker.start.assert_called_once_with(tid)
|
|
|
|
async def test_schedule_failure_marks_error_and_closes_broker(self) -> None:
|
|
tid, uid = uuid4(), uuid4()
|
|
app = SimpleNamespace(state=SimpleNamespace(inflight={}))
|
|
session = MagicMock()
|
|
broker = MagicMock()
|
|
|
|
@contextmanager
|
|
def fake_scope():
|
|
yield session
|
|
|
|
with (
|
|
patch.object(run_lifecycle, "session_scope", fake_scope),
|
|
patch.object(run_lifecycle, "broker", broker),
|
|
patch.object(
|
|
run_lifecycle.asyncio,
|
|
"create_task",
|
|
side_effect=RuntimeError("loop closed"),
|
|
),
|
|
self.assertRaises(run_lifecycle.RunScheduleError),
|
|
):
|
|
run_lifecycle.schedule_claimed_run(app, tid, uid, "已落库")
|
|
|
|
update_stmt = session.execute.call_args.args[0]
|
|
params = update_stmt.compile().params
|
|
self.assertEqual(params["run_status"], "error")
|
|
self.assertIn("loop closed", params["run_error"])
|
|
broker.close.assert_called_once_with(tid)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|