Files
wehub-resource-sync 4b6817381b
Benchmark image — build + push to ECR (any adapter) / build + push (push) Waiting to run
CI / quality (ubuntu-latest) (push) Waiting to run
CI / test (tools-runtime) (push) Waiting to run
CI / test (e2e-general) (push) Waiting to run
CI / test (cli-runtime) (push) Waiting to run
CI / test (e2e-provider-and-openclaw) (push) Waiting to run
CI / test (integrations-and-misc) (push) Waiting to run
CI / coverage-report (push) Blocked by required conditions
CI / test-kubernetes (push) Waiting to run
CI / should-run-thorough (push) Waiting to run
CI / test-thorough (cloudwatch-demo) (push) Blocked by required conditions
CI / test-thorough (flink-ecs) (push) Blocked by required conditions
CI / test-thorough (upstream-lambda) (push) Blocked by required conditions
CI / test-thorough (prefect-ecs-fargate) (push) Blocked by required conditions
CodeQL / Analyze (python) (push) Waiting to run
Release / build-binaries (zip, opensre.exe, onefile, windows-latest, windows-x64) (push) Blocked by required conditions
Release / publish-release (push) Blocked by required conditions
Release / publish-main-release (push) Blocked by required conditions
Release / prepare (push) Waiting to run
Release / verify (push) Blocked by required conditions
Release / build-python-dist (push) Blocked by required conditions
Release / build-binaries (tar.gz, opensre, onedir, macos-15-intel, darwin-x64) (push) Blocked by required conditions
Release / build-binaries (tar.gz, opensre, onedir, macos-latest, darwin-arm64) (push) Blocked by required conditions
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04, linux-x64) (push) Blocked by required conditions
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04-arm, linux-arm64) (push) Blocked by required conditions
Synthetic Deterministic Tests / Synthetic offline (deterministic) (push) Waiting to run
Interactive Shell Live (PR + post-merge) / turn-checks (no-LLM) (push) Waiting to run
Interactive Shell Live (PR + post-merge) / turn-live shard ${{ matrix.shard_index }} (push) Waiting to run
CI (OpenClaw E2E) / openclaw test (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:10:45 +08:00

162 lines
4.7 KiB
Python

"""Unit tests for the EventEmitterMixin, ToolFilterMixin, and SteeringMixin mixins."""
from __future__ import annotations
from collections import deque
from typing import Any
from core.agent.mixins import EventEmitterMixin, SteeringMixin, ToolFilterMixin
from core.events import RuntimeEvent, runtime_event_from_tuple
from core.messages import UserRuntimeMessage
class _Emitter(EventEmitterMixin):
pass
class _Filter(ToolFilterMixin):
pass
class _Steering(SteeringMixin):
def __init__(self) -> None:
self._steering_messages: deque[str] = deque()
self._follow_up_messages: deque[str] = deque()
def test_default_callbacks_are_none() -> None:
e = _Emitter()
assert e._on_tuple_event is None
assert e._on_runtime_event is None
def test_emit_tuple_forwards_to_listener() -> None:
seen: list[tuple[str, dict[str, Any]]] = []
e = _Emitter()
e._on_tuple_event = lambda kind, data: seen.append((kind, data))
e._emit_tuple("custom", {"x": 1})
assert seen == [("custom", {"x": 1})]
def test_emit_tuple_swallows_listener_errors() -> None:
e = _Emitter()
def boom(kind: str, data: dict[str, Any]) -> None:
raise RuntimeError("renderer broke")
e._on_tuple_event = boom
e._emit_tuple("custom", {}) # must not raise — rendering can't break the loop
def test_emit_tuple_is_noop_without_listener() -> None:
_Emitter()._emit_tuple("custom", {}) # no listener, no error
def test_emit_runtime_forwards_to_listener() -> None:
seen: list[RuntimeEvent] = []
e = _Emitter()
e._on_runtime_event = seen.append
event = runtime_event_from_tuple("agent_start", {"tool_count": 0})
assert event is not None
e._emit_runtime(event)
assert seen == [event]
def test_emit_runtime_swallows_listener_errors() -> None:
e = _Emitter()
def boom(event: RuntimeEvent) -> None:
raise RuntimeError("renderer broke")
e._on_runtime_event = boom
event = runtime_event_from_tuple("agent_start", {"tool_count": 0})
assert event is not None
e._emit_runtime(event) # must not raise
def test_emit_routes_unmapped_kind_to_tuple() -> None:
tuple_seen: list[tuple[str, dict[str, Any]]] = []
runtime_seen: list[RuntimeEvent] = []
e = _Emitter()
e._on_tuple_event = lambda kind, data: tuple_seen.append((kind, data))
e._on_runtime_event = runtime_seen.append
e._emit("zzz_unmapped_kind", {"k": "v"})
assert tuple_seen == [("zzz_unmapped_kind", {"k": "v"})]
assert runtime_seen == []
def test_emit_routes_mapped_kind_to_runtime() -> None:
runtime_seen: list[RuntimeEvent] = []
e = _Emitter()
e._on_runtime_event = runtime_seen.append
e._emit("agent_start", {"tool_count": 3})
assert len(runtime_seen) == 1
def test_filter_tools_returns_the_same_list() -> None:
tools: list[Any] = ["t1", "t2"]
assert _Filter()._filter_tools(tools) is tools
def test_mixins_compose_in_one_class() -> None:
# A single class can compose both mixins (as ConnectedInvestigationAgent does).
class _Composed(EventEmitterMixin, ToolFilterMixin):
pass
seen: list[tuple[str, dict[str, Any]]] = []
c = _Composed()
c._on_tuple_event = lambda kind, data: seen.append((kind, data))
c._emit("zzz_unmapped_kind", {"a": 1})
assert seen == [("zzz_unmapped_kind", {"a": 1})]
tools: list[Any] = [1, 2, 3]
assert c._filter_tools(tools) is tools
def test_steer_queues_message_drained_into_transcript() -> None:
s = _Steering()
s.steer("look at the newest deploy first")
messages: list[Any] = []
s._drain_steering_messages(messages)
assert len(messages) == 1
assert isinstance(messages[0], UserRuntimeMessage)
assert messages[0].content == "look at the newest deploy first"
def test_steer_ignores_blank_message() -> None:
s = _Steering()
s.steer(" ")
messages: list[Any] = []
s._drain_steering_messages(messages)
assert messages == []
def test_drain_steering_messages_empties_the_queue() -> None:
s = _Steering()
s.steer("first")
s.steer("second")
messages: list[Any] = []
s._drain_steering_messages(messages)
assert [m.content for m in messages] == ["first", "second"]
more: list[Any] = []
s._drain_steering_messages(more)
assert more == []
def test_follow_up_pop_returns_none_when_empty() -> None:
s = _Steering()
assert s._pop_follow_up_message() is None
def test_follow_up_pop_returns_queued_message_once() -> None:
s = _Steering()
s.follow_up("now summarize the remediation")
assert s._pop_follow_up_message() == "now summarize the remediation"
assert s._pop_follow_up_message() is None
def test_follow_up_ignores_blank_message() -> None:
s = _Steering()
s.follow_up(" ")
assert s._pop_follow_up_message() is None