forked from cleveragents/cleveragents-core
5f0a545113
Implement FastAPI-based ASGI application served by uvicorn for the CleverAgents server mode. Add health check endpoint, A2A JSON-RPC routing, configurable host:port binding, and graceful shutdown handling. Server launches via `agents server start`. - Add FastAPI ASGI app factory at infrastructure/server/asgi_app.py with /.well-known/agent.json (Agent Card), /health (liveness), and /a2a (A2A JSON-RPC 2.0 dispatch via A2aLocalFacade) - Add ServerLifecycle class at infrastructure/server/server_lifecycle.py wrapping uvicorn.Server with SIGTERM/SIGINT graceful shutdown - Add `agents server start` CLI command with --host, --port, --log-level options, resolving defaults from Settings - Add fastapi>=0.115.0 dependency to pyproject.toml (uvicorn already present) - Add Behave BDD tests (features/server_lifecycle.feature, 20 scenarios) - Add Robot Framework integration tests (robot/server_lifecycle.robot) - Update CHANGELOG.md and vulture_whitelist.py ISSUES CLOSED: #862
123 lines
5.1 KiB
Gherkin
123 lines
5.1 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 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 "ok"
|
|
|
|
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 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
|