e4dcfc49aa
Tests / Import Check (Python 3.13) (push) Has been cancelled
Tests / Import Check (Python 3.14) (push) Has been cancelled
Tests / Python Tests (Python 3.11) (push) Has been cancelled
Tests / Python Tests (Python 3.12) (push) Has been cancelled
Tests / Python Tests (Python 3.14) (push) Has been cancelled
Tests / Test Summary (push) Has been cancelled
Tests / Lint and Format (push) Has been cancelled
Tests / Web Node Tests (push) Has been cancelled
Tests / Import Check (Python 3.11) (push) Has been cancelled
Tests / Import Check (Python 3.12) (push) Has been cancelled
Tests / Python Tests (Python 3.13) (push) Has been cancelled
104 lines
3.3 KiB
Python
104 lines
3.3 KiB
Python
from __future__ import annotations
|
|
|
|
import asyncio
|
|
|
|
import pytest
|
|
|
|
from deeptutor.services.session.sqlite_store import SQLiteSessionStore
|
|
from deeptutor.services.session.turn_runtime import TurnRuntimeManager, _TurnExecution
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_subscribe_turn_does_not_synthesize_done_for_running_turn(tmp_path) -> None:
|
|
"""A paused/replaced subscription must not make the UI think the turn ended."""
|
|
|
|
store = SQLiteSessionStore(tmp_path / "chat_history.db")
|
|
runtime = TurnRuntimeManager(store)
|
|
session = await store.ensure_session(None)
|
|
turn = await store.create_turn(session["id"], capability="chat")
|
|
execution = _TurnExecution(
|
|
turn_id=turn["id"],
|
|
session_id=session["id"],
|
|
capability="chat",
|
|
payload={},
|
|
)
|
|
runtime._executions[turn["id"]] = execution
|
|
|
|
events: list[dict] = []
|
|
|
|
async def _collect() -> None:
|
|
async for event in runtime.subscribe_turn(turn["id"], after_seq=0):
|
|
events.append(event)
|
|
|
|
task = asyncio.create_task(_collect())
|
|
for _ in range(200):
|
|
if execution.subscribers:
|
|
break
|
|
await asyncio.sleep(0.01)
|
|
|
|
assert execution.subscribers
|
|
await execution.subscribers[0].queue.put(None)
|
|
await asyncio.wait_for(task, timeout=1)
|
|
|
|
assert events == []
|
|
persisted = await store.get_turn(turn["id"])
|
|
assert persisted is not None
|
|
assert persisted["status"] == "running"
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_subscribe_turn_marks_orphan_running_turn_failed(tmp_path) -> None:
|
|
"""A DB-running turn with no in-process execution is stale after restart."""
|
|
|
|
store = SQLiteSessionStore(tmp_path / "chat_history.db")
|
|
runtime = TurnRuntimeManager(store)
|
|
session = await store.ensure_session(None)
|
|
turn = await store.create_turn(session["id"], capability="chat")
|
|
|
|
events: list[dict] = []
|
|
async for event in runtime.subscribe_turn(turn["id"], after_seq=0):
|
|
events.append(event)
|
|
|
|
persisted = await store.get_turn(turn["id"])
|
|
assert persisted is not None
|
|
assert persisted["status"] == "failed"
|
|
assert "restart" in persisted["error"].lower()
|
|
assert [event["type"] for event in events] == ["error", "done"]
|
|
assert events[-1]["metadata"]["status"] == "failed"
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_start_turn_clears_orphan_running_turn_before_create(
|
|
monkeypatch: pytest.MonkeyPatch, tmp_path
|
|
) -> None:
|
|
"""A stale active turn should not block the next user message after restart."""
|
|
|
|
store = SQLiteSessionStore(tmp_path / "chat_history.db")
|
|
runtime = TurnRuntimeManager(store)
|
|
session = await store.ensure_session(None)
|
|
stale = await store.create_turn(session["id"], capability="chat")
|
|
|
|
async def _noop_run_turn(_execution):
|
|
return None
|
|
|
|
monkeypatch.setattr(runtime, "_run_turn", _noop_run_turn)
|
|
|
|
_, new_turn = await runtime.start_turn(
|
|
{
|
|
"type": "start_turn",
|
|
"session_id": session["id"],
|
|
"capability": "chat",
|
|
"content": "hello",
|
|
"tools": [],
|
|
"knowledge_bases": [],
|
|
"attachments": [],
|
|
"language": "en",
|
|
"config": {},
|
|
}
|
|
)
|
|
|
|
assert new_turn["id"] != stale["id"]
|
|
persisted = await store.get_turn(stale["id"])
|
|
assert persisted is not None
|
|
assert persisted["status"] == "failed"
|