9c6d69153e
CI / benchmark-publish (pull_request) Has been skipped
CI / build (pull_request) Successful in 18s
CI / lint (pull_request) Failing after 18s
CI / helm (pull_request) Successful in 23s
CI / security (pull_request) Failing after 52s
CI / typecheck (pull_request) Failing after 54s
CI / coverage (pull_request) Has been skipped
CI / benchmark-regression (pull_request) Has been skipped
CI / unit_tests (pull_request) Failing after 1m47s
CI / docker (pull_request) Has been skipped
CI / quality (pull_request) Successful in 3m42s
CI / e2e_tests (pull_request) Failing after 14m21s
CI / integration_tests (pull_request) Failing after 20m58s
CI / status-check (pull_request) Failing after 1s
Rewrites the A2aRequest and A2aResponse Pydantic models to use the field names mandated by the JSON-RPC 2.0 specification, fixing a fundamental protocol compliance issue that prevented external A2A-compliant clients from communicating with the server. Changes: - A2aRequest: a2a_version→jsonrpc (fixed '2.0'), request_id→id, operation→method; auth field removed (not in JSON-RPC 2.0) - A2aResponse: a2a_version→jsonrpc, request_id→id, status+data→result (success path), timing_ms removed; added _result_xor_error validator enforcing mutual exclusion of result and error fields - A2aLocalFacade.dispatch(): updated to use request.method, request.id, result=data, error=A2aErrorDetail(...) - A2aHttpTransport.send(): updated to use request.method - CLI call sites (session.py, plan.py): updated A2aRequest(method=...) and response.result / response.error field access - All existing A2A Behave step files updated to new field names - New 35-scenario Behave feature (a2a_jsonrpc_wire_format.feature) covering serialisation, deserialisation, validation, and facade dispatch - New 7-test Robot Framework suite (a2a_jsonrpc_wire_format.robot) for end-to-end wire format verification ISSUES CLOSED: #1501
129 lines
3.8 KiB
Python
129 lines
3.8 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 ( # noqa: E402
|
|
A2aNotAvailableError,
|
|
A2aVersionMismatchError,
|
|
)
|
|
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 transport stub raises A2aNotAvailableError."""
|
|
transport = A2aHttpTransport()
|
|
try:
|
|
transport.connect("http://localhost:8080")
|
|
print("FAIL: should have raised", file=sys.stderr)
|
|
sys.exit(1)
|
|
except A2aNotAvailableError:
|
|
pass
|
|
|
|
if transport.is_connected() is False:
|
|
print("a2a-transport-stub-ok")
|
|
else:
|
|
print("FAIL: is_connected should be False", file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
|
|
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."""
|
|
negotiator = A2aVersionNegotiator()
|
|
result = negotiator.negotiate("1.0")
|
|
if result != "1.0":
|
|
print("FAIL: expected 1.0", file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
try:
|
|
negotiator.negotiate("2.0")
|
|
print("FAIL: should have raised", 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) == 42:
|
|
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()
|