6390ce1171
- 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.
155 lines
4.9 KiB
Python
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()
|