Files
temp/features/steps/a2a_events_coverage_r3_steps.py
freemo 31472b5413 test(coverage): add Behave scenarios for 39 under-covered modules
Add Behave feature/step pairs that exercise uncovered branches across handlers, LSP, CLI, and service layers to reach the coverage gate.

ISSUES CLOSED: #1232
2026-03-31 21:47:12 +00:00

276 lines
9.4 KiB
Python

"""Step definitions for a2a_events_coverage_r3.feature.
Exercises previously-uncovered lines in src/cleveragents/a2a/events.py:
63, 73-76, 82, 91, 100, 126, 234-238, 244, 250, 258
"""
from __future__ import annotations
from typing import Any
from unittest.mock import MagicMock
from behave import given, then, when
try:
from cleveragents.a2a.events import (
TASK_STATUS_UPDATE,
A2aEventQueue,
EventBusBridge,
)
from cleveragents.a2a.models import A2aEvent
except ImportError:
pass # module may be unavailable in isolated environments
# -----------------------------------------------------------------------
# Givens
# -----------------------------------------------------------------------
@given("aecov3 a fresh A2aEventQueue")
def step_aecov3_fresh_queue(context: Any) -> None:
context.aecov3_queue = A2aEventQueue()
context.aecov3_error = None
@given("aecov3 a subscriber callback that raises RuntimeError")
def step_aecov3_failing_subscriber(context: Any) -> None:
def _boom(event: Any) -> None:
raise RuntimeError("subscriber exploded")
context.aecov3_sub_id = context.aecov3_queue.subscribe_local(_boom)
@given("aecov3 a mock EventBus that returns a disposable subscription")
def step_aecov3_mock_bus_disposable(context: Any) -> None:
context.aecov3_bus = MagicMock()
context.aecov3_subscription = MagicMock()
# The subscription has a .dispose() method
context.aecov3_subscription.dispose = MagicMock()
context.aecov3_bus_callback = None
def _fake_subscribe(callback: Any) -> MagicMock:
context.aecov3_bus_callback = callback
return context.aecov3_subscription
context.aecov3_bus.subscribe.side_effect = _fake_subscribe
@given("aecov3 a mock EventBus that returns a non-disposable subscription")
def step_aecov3_mock_bus_non_disposable(context: Any) -> None:
context.aecov3_bus = MagicMock(spec=[])
context.aecov3_bus.subscribe = MagicMock()
context.aecov3_subscription = "simple-token" # no .dispose attribute
context.aecov3_bus_callback = None
def _fake_subscribe(callback: Any) -> str:
context.aecov3_bus_callback = callback
return context.aecov3_subscription
context.aecov3_bus.subscribe.side_effect = _fake_subscribe
@given("aecov3 a mock EventBus that captures the callback")
def step_aecov3_mock_bus_capture(context: Any) -> None:
context.aecov3_bus = MagicMock()
context.aecov3_bus_callback = None
def _fake_subscribe(callback: Any) -> MagicMock:
context.aecov3_bus_callback = callback
return MagicMock()
context.aecov3_bus.subscribe.side_effect = _fake_subscribe
@given("aecov3 an EventBusBridge connecting the bus and queue")
def step_aecov3_bridge(context: Any) -> None:
context.aecov3_bridge = EventBusBridge(context.aecov3_bus, context.aecov3_queue)
# -----------------------------------------------------------------------
# Whens
# -----------------------------------------------------------------------
@when("aecov3 I publish a plain dict instead of an A2aEvent")
def step_aecov3_publish_dict(context: Any) -> None:
try:
context.aecov3_queue.publish({"not": "an event"}) # type: ignore[arg-type]
except Exception as exc:
context.aecov3_error = exc
@when("aecov3 I publish a valid A2aEvent")
def step_aecov3_publish_valid(context: Any) -> None:
context.aecov3_event = A2aEvent(
event_type="TestEvent",
plan_id="plan-test",
data={"key": "value"},
)
try:
context.aecov3_queue.publish(context.aecov3_event)
except Exception as exc:
context.aecov3_error = exc
@when("aecov3 I subscribe with a non-callable value")
def step_aecov3_subscribe_non_callable(context: Any) -> None:
try:
context.aecov3_queue.subscribe_local("not-a-callable") # type: ignore[arg-type]
except Exception as exc:
context.aecov3_error = exc
@when("aecov3 I unsubscribe with an empty string")
def step_aecov3_unsubscribe_empty(context: Any) -> None:
try:
context.aecov3_queue.unsubscribe("")
except Exception as exc:
context.aecov3_error = exc
@when("aecov3 I unsubscribe with a non-string value")
def step_aecov3_unsubscribe_non_string(context: Any) -> None:
try:
context.aecov3_queue.unsubscribe(12345) # type: ignore[arg-type]
except Exception as exc:
context.aecov3_error = exc
@when("aecov3 I call get_events with limit 0")
def step_aecov3_get_events_zero(context: Any) -> None:
try:
context.aecov3_queue.get_events(limit=0)
except Exception as exc:
context.aecov3_error = exc
@when("aecov3 I call get_events with a string limit")
def step_aecov3_get_events_string(context: Any) -> None:
try:
context.aecov3_queue.get_events(limit="ten") # type: ignore[arg-type]
except Exception as exc:
context.aecov3_error = exc
@when("aecov3 I call subscribe_remote with an empty endpoint")
def step_aecov3_remote_empty(context: Any) -> None:
try:
context.aecov3_queue.subscribe_remote("")
except Exception as exc:
context.aecov3_error = exc
@when("aecov3 I call subscribe_remote with a non-string endpoint")
def step_aecov3_remote_non_string(context: Any) -> None:
try:
context.aecov3_queue.subscribe_remote(42) # type: ignore[arg-type]
except Exception as exc:
context.aecov3_error = exc
@when("aecov3 the bridge is started")
def step_aecov3_bridge_start(context: Any) -> None:
context.aecov3_bridge.start()
@when("aecov3 the bridge is stopped")
def step_aecov3_bridge_stop(context: Any) -> None:
context.aecov3_bridge.stop()
@when("aecov3 the bridge receives a domain event without event_type")
def step_aecov3_domain_no_type(context: Any) -> None:
domain_event = MagicMock(spec=[])
# No event_type attribute at all → getattr returns None → early return
context.aecov3_bus_callback(domain_event)
@when('aecov3 the bridge receives a domain event with string event_type "{type_str}"')
def step_aecov3_domain_string_type(context: Any, type_str: str) -> None:
domain_event = MagicMock()
# Use a plain string (no .value) so the `else str(event_type_name)` branch runs
domain_event.event_type = type_str
# Remove .value so hasattr(event_type_name, "value") is False on the str
# Python str does not have a `.value` attribute, so this exercises line 250
domain_event.plan_id = "plan-bridge-test"
domain_event.details = {"info": "testing"}
context.aecov3_bus_callback(domain_event)
# -----------------------------------------------------------------------
# Thens
# -----------------------------------------------------------------------
@then('aecov3 a TypeError should be stored with message "{msg}"')
def step_aecov3_check_type_error(context: Any, msg: str) -> None:
assert context.aecov3_error is not None, "Expected an error but none was stored"
assert isinstance(context.aecov3_error, TypeError), (
f"Expected TypeError, got {type(context.aecov3_error).__name__}"
)
assert msg in str(context.aecov3_error), (
f"Expected message containing '{msg}', got: {context.aecov3_error}"
)
@then('aecov3 a ValueError should be stored with message "{msg}"')
def step_aecov3_check_value_error(context: Any, msg: str) -> None:
assert context.aecov3_error is not None, "Expected an error but none was stored"
assert isinstance(context.aecov3_error, ValueError), (
f"Expected ValueError, got {type(context.aecov3_error).__name__}"
)
assert msg in str(context.aecov3_error), (
f"Expected message containing '{msg}', got: {context.aecov3_error}"
)
@then("aecov3 no error should be stored")
def step_aecov3_no_error(context: Any) -> None:
assert context.aecov3_error is None, (
f"Expected no error, got: {context.aecov3_error}"
)
@then("aecov3 the event should be in the queue")
def step_aecov3_event_in_queue(context: Any) -> None:
events = context.aecov3_queue.get_events()
ids = [e.event_id for e in events]
assert context.aecov3_event.event_id in ids, (
f"Event {context.aecov3_event.event_id} not found in queue: {ids}"
)
@then("aecov3 the subscription dispose should have been called")
def step_aecov3_dispose_called(context: Any) -> None:
context.aecov3_subscription.dispose.assert_called_once()
@then("aecov3 the bridge subscription should be None")
def step_aecov3_subscription_none(context: Any) -> None:
assert context.aecov3_bridge._subscription is None, (
f"Expected None subscription, got: {context.aecov3_bridge._subscription}"
)
@then("aecov3 the queue should be empty")
def step_aecov3_queue_empty(context: Any) -> None:
events = context.aecov3_queue.get_events()
assert len(events) == 0, f"Expected empty queue, got {len(events)} events"
@then("aecov3 the queue should contain a TaskStatusUpdateEvent")
def step_aecov3_queue_has_status(context: Any) -> None:
events = context.aecov3_queue.get_events()
types = [e.event_type for e in events]
assert TASK_STATUS_UPDATE in types, (
f"Expected {TASK_STATUS_UPDATE} in queue, got: {types}"
)
@then('aecov3 the queue should contain an event with type "{expected_type}"')
def step_aecov3_queue_has_type(context: Any, expected_type: str) -> None:
events = context.aecov3_queue.get_events()
types = [e.event_type for e in events]
assert expected_type in types, f"Expected '{expected_type}' in queue, got: {types}"