Files
cleveragents-core/robot/helper_a2a_facade.py
T
HAL9000 6390ce1171 fix(a2a): address reviewer feedback on HTTP transport
- Remove misplaced pytest test files: tests/unit/a2a_test_http_transport.py,
  tests/unit/__init__.py, and features/steps/test_a2a_http_transport_pytest.py.
  Project layout uses Behave in features/ exclusively per CONTRIBUTING.md.
- Resolve AmbiguousStep crash in features/steps/a2a_facade_steps.py by
  deduplicating step_transport_connect / step_transport_disconnect /
  "the transport should not be connected" definitions left over from the
  pre-implementation stub.
- Remove all `# type: ignore[arg-type]` comments (zero-tolerance policy).
- Fix ruff lint failures in src/cleveragents/a2a/transport.py: drop unused
  imports (Any, map_domain_error, BaseHandler, OpenerDirector), wrap long
  log lines (E501), and switch ssl.VerifyMode literal 0 to CERT_NONE for
  pyright compliance.
- Update Robot helpers (robot/helper_a2a_facade.py,
  robot/helper_m6_autonomy_acceptance.py) and the m6 / consolidated Behave
  scenarios to verify the new server-mode lifecycle (connect succeeds with
  valid URL, send-before-connect raises RuntimeError, invalid scheme raises
  ValueError) instead of the obsolete "stub raises A2aNotAvailableError"
  contract.
- Broaden the "I try to connect via the transport to ..." regex so the
  invalid-URL scenario outline matches the empty-string / quoted / None
  example cells; alias "I disconnect the transport" with @then so it is
  reachable from `And` after a `Then` keyword.
2026-06-14 16:11:26 -04:00

155 lines
4.9 KiB
Python

"""Helper script for a2a_facade.robot smoke tests.
Each subcommand is a self-contained check that prints a sentinel on success.
"""
from __future__ import annotations
import sys
from pathlib import Path
# Ensure local source tree is importable
_SRC = str(Path(__file__).resolve().parents[1] / "src")
if _SRC not in sys.path:
sys.path.insert(0, _SRC)
from cleveragents.a2a.errors import A2aVersionMismatchError # noqa: E402
from cleveragents.a2a.events import A2aEventQueue # noqa: E402
from cleveragents.a2a.facade import A2aLocalFacade # noqa: E402
from cleveragents.a2a.models import A2aEvent, A2aRequest # noqa: E402
from cleveragents.a2a.transport import A2aHttpTransport # noqa: E402
from cleveragents.a2a.versioning import A2aVersionNegotiator # noqa: E402
# ---------------------------------------------------------------------------
# Subcommands
# ---------------------------------------------------------------------------
def facade_dispatch() -> None:
"""Dispatch session.create via local facade."""
facade = A2aLocalFacade()
request = A2aRequest(method="session.create")
response = facade.dispatch(request)
if (
response.error is None
and response.result is not None
and "session_id" in response.result
):
print("a2a-facade-dispatch-ok")
else:
print(f"FAIL: unexpected response {response}", file=sys.stderr)
sys.exit(1)
def transport_stub() -> None:
"""Verify server-mode HTTP transport connect/disconnect lifecycle."""
transport = A2aHttpTransport()
if transport.is_connected() is not False:
print("FAIL: is_connected should be False before connect", file=sys.stderr)
sys.exit(1)
# Send before connect should raise RuntimeError
try:
transport.send(A2aRequest(method="plan.create", params={}))
print("FAIL: expected RuntimeError on send-before-connect", file=sys.stderr)
sys.exit(1)
except RuntimeError:
pass
# Connect with valid URL should succeed
transport.connect("http://localhost:8080")
if transport.is_connected() is not True:
print("FAIL: is_connected should be True after connect", file=sys.stderr)
sys.exit(1)
# Invalid URL should raise ValueError
transport_bad = A2aHttpTransport()
try:
transport_bad.connect("ftp://localhost")
print("FAIL: expected ValueError on invalid scheme", file=sys.stderr)
sys.exit(1)
except ValueError:
pass
# Disconnect should clear state
transport.disconnect()
if transport.is_connected() is not False:
print("FAIL: is_connected should be False after disconnect", file=sys.stderr)
sys.exit(1)
print("a2a-transport-stub-ok")
def event_queue() -> None:
"""Verify local event queue publish/subscribe."""
queue = A2aEventQueue()
received: list[A2aEvent] = []
queue.subscribe_local(lambda e: received.append(e))
queue.publish(A2aEvent(event_type="test.event"))
if len(received) == 1 and received[0].event_type == "test.event":
print("a2a-event-queue-ok")
else:
print(f"FAIL: received={received}", file=sys.stderr)
sys.exit(1)
def version_negotiate() -> None:
"""Verify version negotiation.
The current supported A2A version is 2.0. Negotiating with the
current version must succeed; negotiating with an unsupported version
(e.g. "99.0") must raise A2aVersionMismatchError.
"""
negotiator = A2aVersionNegotiator()
current = negotiator.get_current()
result = negotiator.negotiate(current)
if result != current:
print(f"FAIL: expected {current!r}, got {result!r}", file=sys.stderr)
sys.exit(1)
try:
negotiator.negotiate("99.0")
print("FAIL: should have raised for unsupported version", file=sys.stderr)
sys.exit(1)
except A2aVersionMismatchError:
pass
print("a2a-version-negotiate-ok")
def list_operations() -> None:
"""Verify list_operations returns expected operations."""
facade = A2aLocalFacade()
ops = facade.list_operations()
expected = {"session.create", "plan.create", "plan.execute", "context.get"}
if expected.issubset(set(ops)) and len(ops) == 44:
print("a2a-list-operations-ok")
else:
print(f"FAIL: ops={ops}", file=sys.stderr)
sys.exit(1)
# ---------------------------------------------------------------------------
# Dispatch
# ---------------------------------------------------------------------------
_COMMANDS = {
"facade-dispatch": facade_dispatch,
"transport-stub": transport_stub,
"event-queue": event_queue,
"version-negotiate": version_negotiate,
"list-operations": list_operations,
}
def main() -> None:
if len(sys.argv) < 2 or sys.argv[1] not in _COMMANDS:
print(f"Usage: {sys.argv[0]} <{'|'.join(_COMMANDS)}>", file=sys.stderr)
sys.exit(2)
_COMMANDS[sys.argv[1]]()
if __name__ == "__main__":
main()