refactor(bus): type outbound runtime events
This commit is contained in:
@@ -5,6 +5,7 @@ import pytest
|
||||
|
||||
from nanobot.agent.loop import AgentLoop
|
||||
from nanobot.bus.events import OutboundMessage
|
||||
from nanobot.bus.outbound_events import GoalStatusEvent
|
||||
from nanobot.bus.queue import MessageBus
|
||||
from nanobot.providers.base import GenerationSettings, LLMResponse
|
||||
from nanobot.session.webui_turns import WebuiTurnCoordinator
|
||||
@@ -54,13 +55,13 @@ async def test_process_direct_websocket_clears_run_status(tmp_path) -> None:
|
||||
events.append(await loop.bus.consume_outbound())
|
||||
|
||||
statuses = [
|
||||
event.metadata
|
||||
event.event
|
||||
for event in events
|
||||
if event.metadata.get("_goal_status") is True
|
||||
if isinstance(event.event, GoalStatusEvent)
|
||||
]
|
||||
assert [status["goal_status"] for status in statuses] == ["running", "idle"]
|
||||
assert isinstance(statuses[0].get("started_at"), float)
|
||||
assert "started_at" not in statuses[1]
|
||||
assert [status.status for status in statuses] == ["running", "idle"]
|
||||
assert isinstance(statuses[0].started_at, float)
|
||||
assert statuses[1].started_at is None
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
||||
@@ -9,6 +9,15 @@ import pytest
|
||||
import nanobot.agent.runner as runner_module
|
||||
from nanobot.agent.loop import AgentLoop
|
||||
from nanobot.bus.events import InboundMessage
|
||||
from nanobot.bus.outbound_events import (
|
||||
GoalStatusEvent,
|
||||
ProgressEvent,
|
||||
SessionUpdatedEvent,
|
||||
StreamDeltaEvent,
|
||||
StreamedResponseEvent,
|
||||
StreamEndEvent,
|
||||
TurnEndEvent,
|
||||
)
|
||||
from nanobot.bus.queue import MessageBus
|
||||
from nanobot.providers.base import LLMResponse, ToolCallRequest
|
||||
from nanobot.session.webui_turns import WebuiTurnCoordinator
|
||||
@@ -260,25 +269,45 @@ class TestToolEventProgress:
|
||||
)
|
||||
await loop._dispatch(msg)
|
||||
|
||||
# Drain all outbound messages and find the one carrying _tool_events
|
||||
# Drain all outbound messages and find the one carrying tool events.
|
||||
outbound = []
|
||||
while bus.outbound_size > 0:
|
||||
outbound.append(await bus.consume_outbound())
|
||||
|
||||
tool_event_msgs = [m for m in outbound if m.metadata and m.metadata.get("_tool_events")]
|
||||
assert tool_event_msgs, "expected at least one outbound message with _tool_events"
|
||||
tool_event_msgs = [
|
||||
m
|
||||
for m in outbound
|
||||
if isinstance(m.event, ProgressEvent) and m.event.tool_events
|
||||
]
|
||||
assert tool_event_msgs, "expected at least one outbound message with tool events"
|
||||
|
||||
start_msgs = [m for m in tool_event_msgs if m.metadata["_tool_events"][0]["phase"] == "start"]
|
||||
finish_msgs = [m for m in tool_event_msgs if m.metadata["_tool_events"][0]["phase"] in ("end", "error")]
|
||||
start_msgs = [
|
||||
m
|
||||
for m in tool_event_msgs
|
||||
if isinstance(m.event, ProgressEvent)
|
||||
and m.event.tool_events
|
||||
and m.event.tool_events[0]["phase"] == "start"
|
||||
]
|
||||
finish_msgs = [
|
||||
m
|
||||
for m in tool_event_msgs
|
||||
if isinstance(m.event, ProgressEvent)
|
||||
and m.event.tool_events
|
||||
and m.event.tool_events[0]["phase"] in ("end", "error")
|
||||
]
|
||||
assert start_msgs, "expected a start-phase tool event"
|
||||
assert finish_msgs, "expected a finish-phase tool event"
|
||||
|
||||
start = start_msgs[0].metadata["_tool_events"][0]
|
||||
assert isinstance(start_msgs[0].event, ProgressEvent)
|
||||
assert start_msgs[0].event.tool_events is not None
|
||||
start = start_msgs[0].event.tool_events[0]
|
||||
assert start["name"] == "exec"
|
||||
assert start["call_id"] == "tc1"
|
||||
assert start["result"] is None
|
||||
|
||||
finish = finish_msgs[0].metadata["_tool_events"][0]
|
||||
assert isinstance(finish_msgs[0].event, ProgressEvent)
|
||||
assert finish_msgs[0].event.tool_events is not None
|
||||
finish = finish_msgs[0].event.tool_events[0]
|
||||
assert finish["phase"] == "end"
|
||||
assert finish["result"] == "file.txt"
|
||||
|
||||
@@ -309,7 +338,8 @@ class TestToolEventProgress:
|
||||
await invoke_file_edit_progress(progress, edit_events)
|
||||
outbound = await bus.consume_outbound()
|
||||
assert outbound.channel == "telegram"
|
||||
assert outbound.metadata["_file_edit_events"] == edit_events
|
||||
assert isinstance(outbound.event, ProgressEvent)
|
||||
assert outbound.event.file_edit_events == edit_events
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_goal_turn_keeps_live_file_edit_progress_for_webui(self, tmp_path: Path) -> None:
|
||||
@@ -389,7 +419,8 @@ class TestToolEventProgress:
|
||||
edit_events = [
|
||||
event
|
||||
for msg in outbound
|
||||
for event in msg.metadata.get("_file_edit_events", [])
|
||||
if isinstance(msg.event, ProgressEvent)
|
||||
for event in msg.event.file_edit_events or []
|
||||
]
|
||||
assert any(
|
||||
event["status"] == "editing"
|
||||
@@ -433,8 +464,8 @@ class TestToolEventProgress:
|
||||
outbound.append(await bus.consume_outbound())
|
||||
|
||||
assert [m.content for m in outbound] == ["Hello"]
|
||||
assert not any(m.metadata.get("_progress") for m in outbound)
|
||||
assert not any(m.metadata.get("_streamed") for m in outbound)
|
||||
assert not any(isinstance(m.event, ProgressEvent) for m in outbound)
|
||||
assert not any(isinstance(m.event, StreamedResponseEvent) for m in outbound)
|
||||
provider.chat_stream_with_retry.assert_not_awaited()
|
||||
provider.chat_with_retry.assert_awaited_once()
|
||||
|
||||
@@ -443,7 +474,7 @@ class TestToolEventProgress:
|
||||
self,
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
"""Streaming channels still receive provider deltas through _stream_delta messages."""
|
||||
"""Streaming channels still receive provider deltas through stream events."""
|
||||
bus = MessageBus()
|
||||
provider = MagicMock()
|
||||
provider.supports_progress_deltas = True
|
||||
@@ -473,21 +504,19 @@ class TestToolEventProgress:
|
||||
while bus.outbound_size > 0:
|
||||
outbound.append(await bus.consume_outbound())
|
||||
|
||||
deltas = [m for m in outbound if m.metadata.get("_stream_delta")]
|
||||
stream_end = [m for m in outbound if m.metadata.get("_stream_end")]
|
||||
deltas = [m for m in outbound if isinstance(m.event, StreamDeltaEvent)]
|
||||
stream_end = [m for m in outbound if isinstance(m.event, StreamEndEvent)]
|
||||
final = [
|
||||
m for m in outbound
|
||||
if not m.metadata.get("_stream_delta")
|
||||
and not m.metadata.get("_stream_end")
|
||||
and not m.metadata.get("_turn_end")
|
||||
and not m.metadata.get("_goal_status")
|
||||
if not isinstance(m.event, StreamDeltaEvent | StreamEndEvent)
|
||||
and not isinstance(m.event, TurnEndEvent | GoalStatusEvent)
|
||||
]
|
||||
|
||||
assert [m.content for m in deltas] == ["Hel", "lo"]
|
||||
assert len(stream_end) == 1
|
||||
assert final[-1].content == "Hello"
|
||||
assert final[-1].metadata.get("_streamed") is True
|
||||
turn_end_msgs = [m for m in outbound if m.metadata.get("_turn_end")]
|
||||
assert isinstance(final[-1].event, StreamedResponseEvent)
|
||||
turn_end_msgs = [m for m in outbound if isinstance(m.event, TurnEndEvent)]
|
||||
assert len(turn_end_msgs) == 1
|
||||
assert turn_end_msgs[0].content == ""
|
||||
provider.chat_with_retry.assert_not_awaited()
|
||||
@@ -528,23 +557,28 @@ class TestToolEventProgress:
|
||||
while bus.outbound_size > 0:
|
||||
outbound.append(await bus.consume_outbound())
|
||||
|
||||
deltas = [m for m in outbound if m.metadata.get("_stream_delta")]
|
||||
stream_end = [m for m in outbound if m.metadata.get("_stream_end")]
|
||||
deltas = [m for m in outbound if isinstance(m.event, StreamDeltaEvent)]
|
||||
stream_end = [m for m in outbound if isinstance(m.event, StreamEndEvent)]
|
||||
final = [
|
||||
m for m in outbound
|
||||
if not m.metadata.get("_stream_delta")
|
||||
and not m.metadata.get("_stream_end")
|
||||
and not m.metadata.get("_turn_end")
|
||||
and not m.metadata.get("_goal_status")
|
||||
if not isinstance(m.event, StreamDeltaEvent | StreamEndEvent)
|
||||
and not isinstance(m.event, TurnEndEvent | GoalStatusEvent)
|
||||
]
|
||||
|
||||
assert [m.content for m in deltas] == ["partial", "full retry response"]
|
||||
assert [m.metadata.get("_resuming") for m in stream_end] == [True, False]
|
||||
assert deltas[0].metadata.get("_stream_id") == stream_end[0].metadata.get("_stream_id")
|
||||
assert deltas[1].metadata.get("_stream_id") == stream_end[1].metadata.get("_stream_id")
|
||||
assert deltas[0].metadata.get("_stream_id") != deltas[1].metadata.get("_stream_id")
|
||||
assert [m.event.resuming for m in stream_end if isinstance(m.event, StreamEndEvent)] == [
|
||||
True,
|
||||
False,
|
||||
]
|
||||
assert isinstance(deltas[0].event, StreamDeltaEvent)
|
||||
assert isinstance(deltas[1].event, StreamDeltaEvent)
|
||||
assert isinstance(stream_end[0].event, StreamEndEvent)
|
||||
assert isinstance(stream_end[1].event, StreamEndEvent)
|
||||
assert deltas[0].event.stream_id == stream_end[0].event.stream_id
|
||||
assert deltas[1].event.stream_id == stream_end[1].event.stream_id
|
||||
assert deltas[0].event.stream_id != deltas[1].event.stream_id
|
||||
assert final[-1].content == "full retry response"
|
||||
assert final[-1].metadata.get("_streamed") is True
|
||||
assert isinstance(final[-1].event, StreamedResponseEvent)
|
||||
provider.chat_with_retry.assert_not_awaited()
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@@ -623,9 +657,9 @@ class TestToolEventProgress:
|
||||
|
||||
done_msgs = [m for m in outbound if m.content == "Done"]
|
||||
assert len(done_msgs) == 1
|
||||
assert not done_msgs[0].metadata.get("_turn_end")
|
||||
assert not isinstance(done_msgs[0].event, TurnEndEvent)
|
||||
|
||||
turn_end_msgs = [m for m in outbound if m.metadata.get("_turn_end")]
|
||||
turn_end_msgs = [m for m in outbound if isinstance(m.event, TurnEndEvent)]
|
||||
assert len(turn_end_msgs) == 1
|
||||
assert turn_end_msgs[0].content == ""
|
||||
assert turn_end_msgs[0].chat_id == "chat1"
|
||||
@@ -659,14 +693,14 @@ class TestToolEventProgress:
|
||||
outbound.append(await bus.consume_outbound())
|
||||
|
||||
error_msgs = [m for m in outbound if m.content == "Sorry, I encountered an error."]
|
||||
turn_end_msgs = [m for m in outbound if m.metadata.get("_turn_end")]
|
||||
statuses = [m for m in outbound if m.metadata.get("_goal_status")]
|
||||
turn_end_msgs = [m for m in outbound if isinstance(m.event, TurnEndEvent)]
|
||||
statuses = [m for m in outbound if isinstance(m.event, GoalStatusEvent)]
|
||||
|
||||
assert len(error_msgs) == 1
|
||||
assert len(turn_end_msgs) == 1
|
||||
assert turn_end_msgs[0].content == ""
|
||||
assert turn_end_msgs[0].chat_id == "chat1"
|
||||
assert [m.metadata["goal_status"] for m in statuses] == ["idle"]
|
||||
assert [m.event.status for m in statuses if isinstance(m.event, GoalStatusEvent)] == ["idle"]
|
||||
assert outbound.index(error_msgs[0]) < outbound.index(turn_end_msgs[0])
|
||||
assert outbound.index(turn_end_msgs[0]) < outbound.index(statuses[-1])
|
||||
|
||||
@@ -705,27 +739,27 @@ class TestToolEventProgress:
|
||||
outbound: list = []
|
||||
for _ in range(12):
|
||||
outbound.append(await asyncio.wait_for(bus.consume_outbound(), timeout=0.5))
|
||||
if outbound[-1].metadata.get("_turn_end"):
|
||||
if isinstance(outbound[-1].event, TurnEndEvent):
|
||||
break
|
||||
else:
|
||||
raise AssertionError("_turn_end message not found")
|
||||
raise AssertionError("turn-end event not found")
|
||||
|
||||
done_with_body = [m for m in outbound if m.content == "Done"]
|
||||
assert len(done_with_body) == 1
|
||||
assert outbound[-1].metadata.get("_turn_end") is True
|
||||
assert isinstance(outbound[-1].event, TurnEndEvent)
|
||||
|
||||
await asyncio.wait_for(title_started.wait(), timeout=0.5)
|
||||
release_title.set()
|
||||
session_updated = None
|
||||
for _ in range(10):
|
||||
candidate = await asyncio.wait_for(bus.consume_outbound(), timeout=0.5)
|
||||
if (candidate.metadata or {}).get("_session_updated"):
|
||||
if isinstance(candidate.event, SessionUpdatedEvent):
|
||||
session_updated = candidate
|
||||
break
|
||||
assert session_updated is not None
|
||||
|
||||
assert (session_updated.metadata or {}).get("_session_updated") is True
|
||||
assert (session_updated.metadata or {}).get("_session_update_scope") == "metadata"
|
||||
assert isinstance(session_updated.event, SessionUpdatedEvent)
|
||||
assert session_updated.event.scope == "metadata"
|
||||
assert provider.chat_with_retry.await_count == 2
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@@ -837,4 +871,4 @@ class TestToolEventProgress:
|
||||
|
||||
assert len(outbound) == 1
|
||||
assert outbound[0].content == "Done"
|
||||
assert (outbound[0].metadata or {}).get("_turn_end") is not True
|
||||
assert not isinstance(outbound[0].event, TurnEndEvent)
|
||||
|
||||
@@ -7,6 +7,7 @@ from unittest.mock import AsyncMock, MagicMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from nanobot.bus.outbound_events import StreamedResponseEvent
|
||||
from nanobot.config.schema import AgentDefaults
|
||||
from nanobot.providers.base import LLMResponse, ToolCallRequest
|
||||
|
||||
@@ -23,8 +24,8 @@ def _make_loop(tmp_path):
|
||||
|
||||
with patch("nanobot.agent.loop.ContextBuilder"), \
|
||||
patch("nanobot.agent.loop.SessionManager"), \
|
||||
patch("nanobot.agent.loop.SubagentManager") as MockSubMgr:
|
||||
MockSubMgr.return_value.cancel_by_session = AsyncMock(return_value=0)
|
||||
patch("nanobot.agent.loop.SubagentManager") as mock_sub_mgr:
|
||||
mock_sub_mgr.return_value.cancel_by_session = AsyncMock(return_value=0)
|
||||
loop = AgentLoop(bus=bus, provider=provider, workspace=tmp_path)
|
||||
return loop
|
||||
|
||||
@@ -193,8 +194,9 @@ async def test_streamed_flag_not_set_on_llm_error(tmp_path):
|
||||
|
||||
assert result is not None
|
||||
assert "503" in result.content
|
||||
assert not result.metadata.get("_streamed"), \
|
||||
"_streamed must not be set when stop_reason is error"
|
||||
assert not isinstance(result.event, StreamedResponseEvent), (
|
||||
"streamed response event must not be set when stop_reason is error"
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@@ -239,7 +241,7 @@ async def test_ssrf_soft_block_can_finalize_after_streamed_tool_call(tmp_path):
|
||||
|
||||
assert result is not None
|
||||
assert result.content == "I cannot access private URLs. Please share the local file."
|
||||
assert result.metadata.get("_streamed") is True
|
||||
assert isinstance(result.event, StreamedResponseEvent)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
||||
@@ -8,6 +8,13 @@ import pytest
|
||||
from nanobot.agent.context import ContextBuilder
|
||||
from nanobot.agent.loop import AgentLoop
|
||||
from nanobot.bus.events import InboundMessage
|
||||
from nanobot.bus.outbound_events import (
|
||||
GoalStatusEvent,
|
||||
StreamDeltaEvent,
|
||||
StreamedResponseEvent,
|
||||
StreamEndEvent,
|
||||
TurnEndEvent,
|
||||
)
|
||||
from nanobot.bus.queue import MessageBus
|
||||
from nanobot.cron.session_turns import CRON_HISTORY_META, CRON_TRIGGER_META
|
||||
from nanobot.providers.base import LLMResponse
|
||||
@@ -765,7 +772,6 @@ async def test_internal_continuation_preserves_streaming_route_metadata(
|
||||
"_wants_stream": True,
|
||||
"message_id": "om_001",
|
||||
"origin_message_id": "root_001",
|
||||
"_stream_id": "old-stream",
|
||||
},
|
||||
))
|
||||
|
||||
@@ -775,23 +781,23 @@ async def test_internal_continuation_preserves_streaming_route_metadata(
|
||||
assert queued.metadata["_wants_stream"] is True
|
||||
assert queued.metadata["message_id"] == "om_001"
|
||||
assert queued.metadata["origin_message_id"] == "root_001"
|
||||
assert "_stream_id" not in queued.metadata
|
||||
|
||||
await loop._dispatch(queued)
|
||||
|
||||
outbound = []
|
||||
while loop.bus.outbound_size:
|
||||
outbound.append(await loop.bus.consume_outbound())
|
||||
deltas = [m for m in outbound if m.metadata.get("_stream_delta")]
|
||||
ends = [m for m in outbound if m.metadata.get("_stream_end")]
|
||||
streamed_markers = [m for m in outbound if m.metadata.get("_streamed")]
|
||||
deltas = [m for m in outbound if isinstance(m.event, StreamDeltaEvent)]
|
||||
ends = [m for m in outbound if isinstance(m.event, StreamEndEvent)]
|
||||
streamed_markers = [m for m in outbound if isinstance(m.event, StreamedResponseEvent)]
|
||||
|
||||
assert [m.content for m in deltas] == ["done"]
|
||||
assert len(ends) == 1
|
||||
assert ends[0].metadata["_resuming"] is False
|
||||
assert isinstance(ends[0].event, StreamEndEvent)
|
||||
assert ends[0].event.resuming is False
|
||||
assert ends[0].metadata["message_id"] == "om_001"
|
||||
assert ends[0].metadata["origin_message_id"] == "root_001"
|
||||
assert isinstance(ends[0].metadata.get("_stream_id"), str)
|
||||
assert isinstance(ends[0].event.stream_id, str)
|
||||
assert streamed_markers and streamed_markers[-1].content == "done"
|
||||
|
||||
|
||||
@@ -842,10 +848,10 @@ async def test_websocket_internal_continuation_keeps_single_visible_run(
|
||||
first_outbound = []
|
||||
while loop.bus.outbound_size:
|
||||
first_outbound.append(await loop.bus.consume_outbound())
|
||||
first_statuses = [m.metadata for m in first_outbound if m.metadata.get("_goal_status")]
|
||||
assert [m["goal_status"] for m in first_statuses] == ["running"]
|
||||
assert not [m for m in first_outbound if m.metadata.get("_turn_end")]
|
||||
started_at = first_statuses[0]["started_at"]
|
||||
first_statuses = [m.event for m in first_outbound if isinstance(m.event, GoalStatusEvent)]
|
||||
assert [m.status for m in first_statuses] == ["running"]
|
||||
assert not [m for m in first_outbound if isinstance(m.event, TurnEndEvent)]
|
||||
started_at = first_statuses[0].started_at
|
||||
|
||||
queued = await asyncio.wait_for(loop.bus.consume_inbound(), timeout=0.5)
|
||||
assert queued.metadata[INTERNAL_CONTINUATION_META] is True
|
||||
@@ -856,12 +862,13 @@ async def test_websocket_internal_continuation_keeps_single_visible_run(
|
||||
second_outbound = []
|
||||
while loop.bus.outbound_size:
|
||||
second_outbound.append(await loop.bus.consume_outbound())
|
||||
second_statuses = [m.metadata for m in second_outbound if m.metadata.get("_goal_status")]
|
||||
assert [m["goal_status"] for m in second_statuses] == ["running", "idle"]
|
||||
assert second_statuses[0]["started_at"] == started_at
|
||||
turn_end = [m for m in second_outbound if m.metadata.get("_turn_end")]
|
||||
second_statuses = [m.event for m in second_outbound if isinstance(m.event, GoalStatusEvent)]
|
||||
assert [m.status for m in second_statuses] == ["running", "idle"]
|
||||
assert second_statuses[0].started_at == started_at
|
||||
turn_end = [m for m in second_outbound if isinstance(m.event, TurnEndEvent)]
|
||||
assert len(turn_end) == 1
|
||||
assert isinstance(turn_end[0].metadata.get("latency_ms"), int)
|
||||
assert isinstance(turn_end[0].event, TurnEndEvent)
|
||||
assert isinstance(turn_end[0].event.latency_ms, int)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
||||
@@ -127,6 +127,7 @@ class TestDispatch:
|
||||
@pytest.mark.asyncio
|
||||
async def test_dispatch_streaming_preserves_message_metadata(self):
|
||||
from nanobot.bus.events import InboundMessage
|
||||
from nanobot.bus.outbound_events import StreamDeltaEvent, StreamEndEvent
|
||||
|
||||
loop, bus = _make_loop()
|
||||
msg = InboundMessage(
|
||||
@@ -156,10 +157,10 @@ class TestDispatch:
|
||||
|
||||
assert first.metadata["thread_root_event_id"] == "$root1"
|
||||
assert first.metadata["thread_reply_to_event_id"] == "$reply1"
|
||||
assert first.metadata["_stream_delta"] is True
|
||||
assert isinstance(first.event, StreamDeltaEvent)
|
||||
assert second.metadata["thread_root_event_id"] == "$root1"
|
||||
assert second.metadata["thread_reply_to_event_id"] == "$reply1"
|
||||
assert second.metadata["_stream_end"] is True
|
||||
assert isinstance(second.event, StreamEndEvent)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_processing_lock_serializes(self):
|
||||
|
||||
@@ -13,6 +13,7 @@ from nanobot.agent.tools.long_task import (
|
||||
CompleteGoalTool,
|
||||
LongTaskTool,
|
||||
)
|
||||
from nanobot.bus.outbound_events import GoalStateSyncEvent
|
||||
from nanobot.bus.queue import MessageBus
|
||||
from nanobot.bus.runtime_events import RuntimeEventBus
|
||||
from nanobot.session.goal_state import GOAL_STATE_KEY
|
||||
@@ -144,8 +145,8 @@ async def test_long_task_publishes_goal_state_ws_after_save(tmp_path):
|
||||
call = bus.publish_outbound.await_args.args[0]
|
||||
assert call.channel == "websocket"
|
||||
assert call.chat_id == "chat-99"
|
||||
assert call.metadata.get("_goal_state_sync") is True
|
||||
assert call.metadata["goal_state"] == {
|
||||
assert isinstance(call.event, GoalStateSyncEvent)
|
||||
assert call.event.goal_state == {
|
||||
"active": True,
|
||||
"ui_summary": "alpha",
|
||||
"objective": "Objective alpha",
|
||||
@@ -180,7 +181,8 @@ async def test_complete_goal_publishes_inactive_goal_state_ws(tmp_path):
|
||||
|
||||
bus.publish_outbound.assert_awaited_once()
|
||||
call = bus.publish_outbound.await_args.args[0]
|
||||
assert call.metadata["goal_state"] == {"active": False}
|
||||
assert isinstance(call.event, GoalStateSyncEvent)
|
||||
assert call.event.goal_state == {"active": False}
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
||||
Reference in New Issue
Block a user