fix(webui): clear stale run status on reconnect

This commit is contained in:
axelray-dev
2026-06-28 19:46:47 +08:00
committed by Xubin Ren
parent 5005bca353
commit 194e9d5f5f
5 changed files with 63 additions and 35 deletions
+17 -19
View File
@@ -1,6 +1,6 @@
"""Test websocket reconnect pushes idle status when no turn is active."""
"""Test websocket subscribe hydration only replays known active turns."""
from unittest.mock import AsyncMock, MagicMock, patch
from unittest.mock import MagicMock, patch
import pytest
@@ -8,29 +8,28 @@ from nanobot.channels.websocket import WebSocketChannel
@pytest.mark.asyncio
async def test_hydrate_after_subscribe_pushes_idle_when_no_turn_active():
"""Reconnecting client should receive idle status when no turn is running."""
async def test_hydrate_after_subscribe_is_quiet_when_no_turn_active():
"""Subscribe hydration must not inject an idle event into normal message order."""
channel = WebSocketChannel.__new__(WebSocketChannel)
channel.gateway = MagicMock()
channel.gateway.session_manager = MagicMock()
channel.gateway.session_manager.read_session_file = MagicMock(return_value={})
sent_events = []
async def mock_send_goal_state(chat_id, blob):
sent_events.append(("goal_state", chat_id, blob))
async def mock_send_goal_status(chat_id, status, **kwargs):
sent_events.append(("goal_status", chat_id, status, kwargs))
channel.send_goal_state = mock_send_goal_state
channel.send_goal_status = mock_send_goal_status
with patch("nanobot.channels.websocket.websocket_turn_wall_started_at", return_value=None):
await channel._hydrate_after_subscribe("test-chat")
# Should have pushed idle status
assert any(e[0] == "goal_status" and e[2] == "idle" for e in sent_events)
assert sent_events == []
@pytest.mark.asyncio
@@ -40,22 +39,21 @@ async def test_hydrate_after_subscribe_pushes_running_when_turn_active():
channel.gateway = MagicMock()
channel.gateway.session_manager = MagicMock()
channel.gateway.session_manager.read_session_file = MagicMock(return_value={})
sent_events = []
async def mock_send_goal_state(chat_id, blob):
sent_events.append(("goal_state", chat_id, blob))
async def mock_send_goal_status(chat_id, status, **kwargs):
sent_events.append(("goal_status", chat_id, status, kwargs))
channel.send_goal_state = mock_send_goal_state
channel.send_goal_status = mock_send_goal_status
with patch("nanobot.channels.websocket.websocket_turn_wall_started_at", return_value=1234567890.0):
await channel._hydrate_after_subscribe("test-chat")
# Should have pushed running status with started_at
running_events = [e for e in sent_events if e[0] == "goal_status" and e[2] == "running"]
assert len(running_events) == 1
assert running_events[0][3]["started_at"] == 1234567890.0
+11 -11
View File
@@ -16,12 +16,12 @@ async def test_cmd_stop_drains_pending_queue():
mock_loop = MagicMock()
mock_loop._cancel_active_tasks = AsyncMock(return_value=1)
mock_loop._pending_queues = {}
pending = asyncio.Queue()
await pending.put("msg1")
await pending.put("msg2")
mock_loop._pending_queues["test-session"] = pending
ctx = CommandContext(
msg=MagicMock(channel="websocket", chat_id="test-chat", metadata={}),
session=None,
@@ -29,9 +29,9 @@ async def test_cmd_stop_drains_pending_queue():
raw="/stop",
loop=mock_loop,
)
result = await cmd_stop(ctx)
assert isinstance(result, OutboundMessage)
assert "Stopped 3 task(s)" in result.content # 1 cancelled + 2 drained
assert "test-session" not in mock_loop._pending_queues
@@ -43,10 +43,10 @@ async def test_cmd_stop_with_empty_pending_queue():
mock_loop = MagicMock()
mock_loop._cancel_active_tasks = AsyncMock(return_value=2)
mock_loop._pending_queues = {}
pending = asyncio.Queue()
mock_loop._pending_queues["test-session"] = pending
ctx = CommandContext(
msg=MagicMock(channel="websocket", chat_id="test-chat", metadata={}),
session=None,
@@ -54,9 +54,9 @@ async def test_cmd_stop_with_empty_pending_queue():
raw="/stop",
loop=mock_loop,
)
result = await cmd_stop(ctx)
assert "Stopped 2 task(s)" in result.content
assert "test-session" not in mock_loop._pending_queues
@@ -67,7 +67,7 @@ async def test_cmd_stop_no_pending_queue():
mock_loop = MagicMock()
mock_loop._cancel_active_tasks = AsyncMock(return_value=0)
mock_loop._pending_queues = {}
ctx = CommandContext(
msg=MagicMock(channel="websocket", chat_id="test-chat", metadata={}),
session=None,
@@ -75,7 +75,7 @@ async def test_cmd_stop_no_pending_queue():
raw="/stop",
loop=mock_loop,
)
result = await cmd_stop(ctx)
assert "No active task to stop" in result.content