5ede1c018a
The new entity-sync feature introduced A2aRequest with a `method` field (JSON-RPC 2.0 wire format), but several test/helper files used the wrong field name `operation` when constructing requests or logging, and checked non-existent `.status`/`.data` attributes on A2aResponse instead of `.result`/`.error`. Fixes: - asgi_app.py: log `a2a_request.method`, not `.operation` (typecheck error) - server_lifecycle_steps.py: send `method` key in JSON-RPC payload; look up status in `result` dict, not top-level response body - server_lifecycle.feature: expect "healthy" (what the handler returns), not "ok" - entity_sync_steps.py: construct A2aRequest(method=...) not (operation=...); assert on .result/.error instead of .status/.data - robot/helper_entity_sync.py: same method= and result/error fixes across sync_pull, sync_push, sync_status, sync_facade_no_service helpers - robot/helper_server_lifecycle.py: same method= and result path fixes ISSUES CLOSED: #1125
167 lines
7.2 KiB
Gherkin
167 lines
7.2 KiB
Gherkin
@phase2 @a2a @server
|
|
Feature: ASGI Server Lifecycle
|
|
As a platform operator
|
|
I want to start, health-check, and shut down the CleverAgents ASGI server
|
|
So that the A2A JSON-RPC 2.0 endpoint is available for client communication
|
|
|
|
# -----------------------------------------------------------------------
|
|
# ASGI application creation
|
|
# -----------------------------------------------------------------------
|
|
|
|
Scenario: create_asgi_app returns a FastAPI application
|
|
When I create an ASGI application with default settings
|
|
Then the ASGI app should be a FastAPI instance
|
|
|
|
Scenario: create_asgi_app accepts a custom facade
|
|
Given an A2aLocalFacade with no services
|
|
When I create an ASGI application with that facade
|
|
Then the ASGI app should be a FastAPI instance
|
|
|
|
Scenario: create_asgi_app rejects invalid facade type
|
|
When I try to create an ASGI application with facade "not-a-facade"
|
|
Then a TypeError should be raised for invalid facade
|
|
|
|
Scenario: create_asgi_app rejects invalid port
|
|
When I try to create an ASGI application with port 0
|
|
Then a ValueError should be raised for invalid port
|
|
|
|
Scenario: create_asgi_app rejects invalid host
|
|
When I try to create an ASGI application with an empty host
|
|
Then a ValueError should be raised for invalid host
|
|
|
|
# -----------------------------------------------------------------------
|
|
# Health check endpoint
|
|
# -----------------------------------------------------------------------
|
|
|
|
Scenario: Health endpoint returns healthy status
|
|
Given a running ASGI test client
|
|
When I request GET /health
|
|
Then the response status code should be 200
|
|
And the response body should contain "healthy"
|
|
|
|
# -----------------------------------------------------------------------
|
|
# Agent Card discovery endpoint
|
|
# -----------------------------------------------------------------------
|
|
|
|
Scenario: Agent Card endpoint returns valid agent card
|
|
Given a running ASGI test client
|
|
When I request GET /.well-known/agent.json
|
|
Then the response status code should be 200
|
|
And the response body should contain "CleverAgents"
|
|
And the response body should contain "url"
|
|
|
|
# -----------------------------------------------------------------------
|
|
# A2A JSON-RPC endpoint
|
|
# -----------------------------------------------------------------------
|
|
|
|
Scenario: A2A endpoint dispatches health check operation
|
|
Given a running ASGI test client
|
|
When I POST a JSON-RPC request to /a2a with operation "_cleveragents/health/check"
|
|
Then the response status code should be 200
|
|
And the A2A response status should be "healthy"
|
|
|
|
Scenario: A2A endpoint returns error for unknown operation
|
|
Given a running ASGI test client
|
|
When I POST a JSON-RPC request to /a2a with operation "nonexistent.operation"
|
|
Then the response status code should be 404
|
|
|
|
Scenario: A2A endpoint returns 400 for malformed request
|
|
Given a running ASGI test client
|
|
When I POST a malformed JSON body to /a2a
|
|
Then the response status code should be 400
|
|
|
|
# -----------------------------------------------------------------------
|
|
# ServerLifecycle construction
|
|
# -----------------------------------------------------------------------
|
|
|
|
Scenario: ServerLifecycle accepts valid configuration
|
|
When I create a ServerLifecycle with host "127.0.0.1" and port 9000
|
|
Then the lifecycle host should be "127.0.0.1"
|
|
And the lifecycle port should be 9000
|
|
And the lifecycle should not be started
|
|
And the lifecycle should not be stopped
|
|
|
|
Scenario: ServerLifecycle rejects empty host
|
|
When I try to create a ServerLifecycle with an empty host
|
|
Then a ValueError should be raised for invalid host
|
|
|
|
Scenario: ServerLifecycle rejects invalid port 0
|
|
When I try to create a ServerLifecycle with port 0
|
|
Then a ValueError should be raised for invalid port
|
|
|
|
Scenario: ServerLifecycle rejects port above 65535
|
|
When I try to create a ServerLifecycle with port 70000
|
|
Then a ValueError should be raised for invalid port
|
|
|
|
Scenario: ServerLifecycle rejects empty log_level
|
|
When I try to create a ServerLifecycle with empty log_level
|
|
Then a ValueError should be raised for invalid log_level
|
|
|
|
# -----------------------------------------------------------------------
|
|
# Server configuration from Settings
|
|
# -----------------------------------------------------------------------
|
|
|
|
Scenario: Settings provides default server host
|
|
Then the default server host from Settings should be "0.0.0.0"
|
|
|
|
Scenario: Settings provides default server port
|
|
Then the default server port from Settings should be 8080
|
|
|
|
# -----------------------------------------------------------------------
|
|
# Graceful shutdown
|
|
# -----------------------------------------------------------------------
|
|
|
|
Scenario: ServerLifecycle request_shutdown sets flag
|
|
Given a ServerLifecycle with a mock uvicorn server
|
|
When I call request_shutdown on the lifecycle
|
|
Then the mock server should_exit flag should be true
|
|
|
|
Scenario: ServerLifecycle cannot be started twice
|
|
Given a ServerLifecycle that has already been started
|
|
When I try to start the lifecycle again
|
|
Then a RuntimeError should be raised for double start
|
|
|
|
# -----------------------------------------------------------------------
|
|
# Full start lifecycle (mocked uvicorn)
|
|
# -----------------------------------------------------------------------
|
|
|
|
Scenario: ServerLifecycle start runs uvicorn and marks stopped on exit
|
|
Given a ServerLifecycle with mocked uvicorn server run
|
|
When I call start on the lifecycle
|
|
Then the lifecycle should be started
|
|
And the lifecycle should be stopped
|
|
And the mocked uvicorn server run should have been called
|
|
|
|
Scenario: ServerLifecycle start installs signal handlers on main thread
|
|
Given a ServerLifecycle with mocked uvicorn and signal handlers
|
|
When I call start on the lifecycle
|
|
Then SIGTERM and SIGINT handlers should have been installed
|
|
|
|
Scenario: Signal handler calls request_shutdown with signal name
|
|
Given a ServerLifecycle with mocked uvicorn server run
|
|
When I call start on the lifecycle
|
|
And I invoke the installed SIGTERM handler
|
|
Then the mocked server should_exit should be true
|
|
|
|
# -----------------------------------------------------------------------
|
|
# run_server convenience function
|
|
# -----------------------------------------------------------------------
|
|
|
|
Scenario: run_server resolves defaults from Settings
|
|
When I call run_server with mocked ServerLifecycle
|
|
Then the lifecycle should have been created with Settings defaults
|
|
And start should have been called on the lifecycle
|
|
|
|
Scenario: run_server passes explicit host and port overrides
|
|
When I call run_server with host "10.0.0.1" and port 3000 using mocked lifecycle
|
|
Then the lifecycle should have been created with host "10.0.0.1" and port 3000
|
|
|
|
# -----------------------------------------------------------------------
|
|
# Signal handler edge case — non-main thread
|
|
# -----------------------------------------------------------------------
|
|
|
|
Scenario: Signal handlers are skipped when not on main thread
|
|
Given a ServerLifecycle with mocked uvicorn and signal tracking
|
|
When I call _install_signal_handlers from a non-main thread
|
|
Then no signal handlers should have been installed
|