e4224eb8ec
- Fix malformed JSON parse error: wrap request.json() in try/except, return -32700 Parse error instead of unhandled HTTP 500 - Fix JSON-RPC error responses: add required 'id' field to all error responses per JSON-RPC 2.0 Section 5 - Fix HTTP status codes: return HTTP 200 for all JSON-RPC responses (error codes expressed in JSON body per JSON-RPC 2.0 over HTTP) - Fix error message leakage: return generic messages instead of raw Pydantic ValidationError internals (CWE-209) - Add catch-all exception handler returning -32603 Internal error - Fix Agent Card url field: use base server URL without /a2a suffix; interfaces[0].url correctly uses endpoint URL with /a2a suffix - Fix 0.0.0.0 host: substitute 127.0.0.1 for Agent Card URL - Fix context typing: replace context: Any with context: Context in all step files per project convention - Fix inline imports: move all imports to top of step files - Fix _COMMANDS typing: use dict[str, Callable[[], None]] to avoid type: ignore suppression in helper files - Add BDD scenarios for entity-sync and namespace-mgmt skill enumeration - Update server_lifecycle.feature: correct HTTP status assertions to 200 ISSUES CLOSED: #867
169 lines
7.4 KiB
Gherkin
169 lines
7.4 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 HTTP 200 with JSON-RPC 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 200
|
|
And the response body should contain "-32601"
|
|
|
|
Scenario: A2A endpoint returns HTTP 200 with JSON-RPC error 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 200
|
|
And the response body should contain "-32600"
|
|
|
|
# -----------------------------------------------------------------------
|
|
# 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
|