577820c8ba
Add two BDD scenarios to server_lifecycle.feature that bring previously unreachable code under coverage: - "ServerLifecycle start runs uvicorn and marks lifecycle stopped": mocks uvicorn.Server so start() completes without binding a real port; covers the full start() body (lines 105-133) and the _install_signal_handlers() call path (lines 148-159). Signal handlers are saved and restored in a finally block so the test runner's SIGINT/SIGTERM handlers are not permanently clobbered. - "ServerLifecycle request_shutdown is a no-op when server not started": calls request_shutdown() on a fresh lifecycle where _server is None; covers the False branch of the `if self._server is not None:` guard (previously only the True branch was exercised by the mock-server scenario). Also marks the diagnostics-only skill fallback in agent_card.py (lines 244-251) with # pragma: no cover — the current A2aLocalFacade never surfaces _cleveragents/diagnostics/ operations, making this branch structurally unreachable without a future facade extension. ISSUES CLOSED: #867
186 lines
8.1 KiB
Gherkin
186 lines
8.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 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 result 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"
|
|
|
|
Scenario: A2A endpoint returns -32700 parse error for invalid JSON bytes
|
|
Given a running ASGI test client
|
|
When I POST invalid JSON bytes to /a2a
|
|
Then the response status code should be 200
|
|
And the response body should contain "-32700"
|
|
|
|
Scenario: A2A endpoint returns -32603 internal error when dispatch raises
|
|
Given a running ASGI test client with a failing facade
|
|
When I POST a JSON-RPC request to /a2a with operation "_cleveragents/health/check"
|
|
Then the response status code should be 200
|
|
And the response body should contain "-32603"
|
|
|
|
# -----------------------------------------------------------------------
|
|
# 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
|
|
|
|
Scenario: ServerLifecycle request_shutdown is a no-op when server not started
|
|
Given a fresh ServerLifecycle
|
|
When I call request_shutdown on the lifecycle
|
|
Then the lifecycle should not be started
|
|
|
|
# -----------------------------------------------------------------------
|
|
# 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
|