Files
cleveragents-core/features/server_lifecycle.feature
T
HAL9000 577820c8ba test(a2a): cover start() body and request_shutdown None branch; pragma diagnostics fallback
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
2026-06-11 20:00:04 -04:00

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