ec0b7631d0
CI / lint (push) Successful in 12s
CI / build (push) Successful in 15s
CI / quality (push) Successful in 23s
CI / typecheck (push) Successful in 36s
CI / benchmark-regression (push) Has been skipped
CI / security (push) Successful in 46s
CI / unit_tests (push) Successful in 3m3s
CI / integration_tests (push) Successful in 3m31s
CI / docker (push) Successful in 40s
CI / coverage (push) Successful in 5m34s
CI / benchmark-publish (push) Successful in 19m15s
CI / benchmark-publish (pull_request) Has been skipped
CI / lint (pull_request) Successful in 13s
CI / build (pull_request) Successful in 14s
CI / quality (pull_request) Successful in 17s
CI / security (pull_request) Successful in 34s
CI / typecheck (pull_request) Has been cancelled
CI / unit_tests (pull_request) Has been cancelled
CI / coverage (pull_request) Has been cancelled
CI / benchmark-regression (pull_request) Has been cancelled
CI / integration_tests (pull_request) Has been cancelled
CI / docker (pull_request) Has been cancelled
Renamed src/cleveragents/acp/ to src/cleveragents/a2a/ and all 13 Acp* classes to A2a* per ADR-047 (A2A Standard Adoption). Updated all imports, structlog event names (acp.* → a2a.*), field names (acp_version → a2a_version), and test references across the entire codebase. This is a cosmetic rename only — no behavioral changes. ISSUES CLOSED: #688
467 lines
20 KiB
Gherkin
467 lines
20 KiB
Gherkin
@phase2 @domain @lsp @lsp_server_stub
|
|
Feature: LSP server stub JSON-RPC protocol
|
|
As a developer integrating LSP into CleverAgents
|
|
I want a minimal LSP server stub that handles core lifecycle methods
|
|
So that I can test the JSON-RPC transport and protocol handshake
|
|
|
|
Background:
|
|
Given a mock LSP transport
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Initialize handshake
|
|
# ---------------------------------------------------------------------------
|
|
|
|
@lsp_initialize
|
|
Scenario: LSP initialize returns server info and capabilities
|
|
Given an initialize request with id 1
|
|
When the LSP server processes all messages
|
|
Then the response for id 1 should have result
|
|
And the result for id 1 should contain serverInfo with name "cleveragents-lsp-stub"
|
|
And the result for id 1 should contain serverInfo with version "0.1.0"
|
|
And the result for id 1 should contain capabilities key "textDocumentSync"
|
|
|
|
@lsp_initialize
|
|
Scenario: LSP initialize with client info is accepted
|
|
Given an initialize request with id 10 and clientInfo name "test-client"
|
|
When the LSP server processes all messages
|
|
Then the response for id 10 should have result
|
|
And the result for id 10 should contain serverInfo with name "cleveragents-lsp-stub"
|
|
|
|
@lsp_initialize
|
|
Scenario: Initialized notification is silently accepted
|
|
Given an initialize request with id 1
|
|
And a notification for method "initialized"
|
|
And an exit notification
|
|
When the LSP server processes all messages
|
|
Then the response for id 1 should have result
|
|
And there should be no error responses
|
|
|
|
@lsp_initialize
|
|
Scenario: Initialized notification before initialize is silently ignored
|
|
Given a notification for method "initialized"
|
|
And an exit notification
|
|
When the LSP server processes all messages
|
|
Then there should be no error responses
|
|
And the server exit code should be 1
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Shutdown handshake
|
|
# ---------------------------------------------------------------------------
|
|
|
|
@lsp_shutdown
|
|
Scenario: LSP shutdown returns null result
|
|
Given an initialize request with id 1
|
|
And a shutdown request with id 2
|
|
And an exit notification
|
|
When the LSP server processes all messages
|
|
Then the response for id 2 should have null result
|
|
And the server exit code should be 0
|
|
|
|
@lsp_shutdown
|
|
Scenario: LSP exit without prior shutdown returns exit code 1
|
|
Given an initialize request with id 1
|
|
And an exit notification
|
|
When the LSP server processes all messages
|
|
Then the server exit code should be 1
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Unsupported methods
|
|
# ---------------------------------------------------------------------------
|
|
|
|
@lsp_unsupported
|
|
Scenario: Unsupported method after initialize returns MethodNotFound error
|
|
Given an initialize request with id 1
|
|
And a request for method "textDocument/completion" with id 5
|
|
And an exit notification
|
|
When the LSP server processes all messages
|
|
Then the response for id 5 should have error code -32601
|
|
And the error message for id 5 should contain "Not implemented: textDocument/completion"
|
|
|
|
@lsp_unsupported
|
|
Scenario: Unsupported method textDocument/hover after initialize returns error
|
|
Given an initialize request with id 1
|
|
And a request for method "textDocument/hover" with id 6
|
|
And an exit notification
|
|
When the LSP server processes all messages
|
|
Then the response for id 6 should have error code -32601
|
|
And the error message for id 6 should contain "Not implemented: textDocument/hover"
|
|
|
|
@lsp_unsupported
|
|
Scenario: Unsupported method before initialize returns ServerNotInitialized
|
|
Given a request for method "textDocument/completion" with id 5
|
|
And an exit notification
|
|
When the LSP server processes all messages
|
|
Then the response for id 5 should have error code -32002
|
|
And the error message for id 5 should contain "Server not initialized"
|
|
|
|
@lsp_unsupported
|
|
Scenario: Unsupported notification is silently ignored
|
|
Given a notification for method "textDocument/didOpen"
|
|
And an exit notification
|
|
When the LSP server processes all messages
|
|
Then there should be no error responses
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Invalid JSON-RPC messages
|
|
# ---------------------------------------------------------------------------
|
|
|
|
@lsp_invalid
|
|
Scenario: Invalid JSON returns parse error response
|
|
Given raw bytes that are not valid JSON-RPC
|
|
And an exit notification
|
|
When the LSP server processes all messages
|
|
Then a parse error response should have been sent
|
|
And the server should have stopped
|
|
|
|
@lsp_invalid
|
|
Scenario: Missing jsonrpc version field returns error
|
|
Given a message with missing jsonrpc version and id 7
|
|
When the LSP server processes all messages
|
|
Then the response for id 7 should have error code -32600
|
|
And the error message for id 7 should contain "jsonrpc version"
|
|
|
|
@lsp_invalid
|
|
Scenario: Missing method field returns error
|
|
Given a message with missing method and id 8
|
|
When the LSP server processes all messages
|
|
Then the response for id 8 should have error code -32600
|
|
And the error message for id 8 should contain "method"
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Server lifecycle
|
|
# ---------------------------------------------------------------------------
|
|
|
|
@lsp_lifecycle
|
|
Scenario: Server startup reports PID
|
|
Given an initialize request with id 1
|
|
And an exit notification
|
|
When the LSP server processes all messages
|
|
Then the server should have logged its PID
|
|
|
|
@lsp_lifecycle
|
|
Scenario: Server handles EOF gracefully
|
|
When the LSP server processes all messages with empty input
|
|
Then the server should have stopped
|
|
And the server exit code should be 1
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# A2A facade wiring
|
|
# ---------------------------------------------------------------------------
|
|
|
|
@lsp_a2a
|
|
Scenario: LSP server stores provided A2A facade
|
|
Given an LSP server with A2A facade
|
|
And an initialize request with id 1
|
|
When the LSP server processes all messages
|
|
Then the response for id 1 should have result
|
|
And the result for id 1 should contain serverInfo with name "cleveragents-lsp-stub"
|
|
And the server should hold the provided A2A facade
|
|
|
|
@lsp_a2a
|
|
Scenario: LSP server lazily creates A2A facade on property access
|
|
Given an initialize request with id 1
|
|
And an exit notification
|
|
When the LSP server processes all messages
|
|
Then the response for id 1 should have result
|
|
And the server facade should be lazily created
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Full handshake sequence
|
|
# ---------------------------------------------------------------------------
|
|
|
|
@lsp_full_handshake
|
|
Scenario: Complete LSP lifecycle (initialize, shutdown, exit)
|
|
Given an initialize request with id 1
|
|
And a shutdown request with id 2
|
|
And an exit notification
|
|
When the LSP server processes all messages
|
|
Then the response for id 1 should have result
|
|
And the response for id 2 should have null result
|
|
And the server exit code should be 0
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Server construction validation
|
|
# ---------------------------------------------------------------------------
|
|
|
|
@lsp_construction
|
|
Scenario: LspServer rejects invalid input_stream
|
|
When I try to create an LspServer with invalid input stream
|
|
Then a TypeError should be raised for input stream
|
|
|
|
@lsp_construction
|
|
Scenario: LspServer rejects invalid output_stream
|
|
When I try to create an LspServer with invalid output stream
|
|
Then a TypeError should be raised for output stream
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Transport edge cases
|
|
# ---------------------------------------------------------------------------
|
|
|
|
@lsp_transport_edge
|
|
Scenario: Server recovers from invalid message and handles subsequent request
|
|
Given a message with missing jsonrpc version and id 7
|
|
And an initialize request with id 1
|
|
And an exit notification
|
|
When the LSP server processes all messages
|
|
Then the response for id 7 should have error code -32600
|
|
And the response for id 1 should have result
|
|
And the result for id 1 should contain serverInfo with name "cleveragents-lsp-stub"
|
|
|
|
@lsp_transport_edge
|
|
Scenario: Request with non-dict params uses empty params
|
|
Given an initialize request with id 1
|
|
And a request with non-dict params and id 20
|
|
And an exit notification
|
|
When the LSP server processes all messages
|
|
Then the response for id 20 should have error code -32601
|
|
|
|
@lsp_transport_edge
|
|
Scenario: Negative content length is handled gracefully
|
|
Given a message with negative content length
|
|
When the LSP server processes all messages
|
|
Then the server should have stopped
|
|
And the server exit code should be 1
|
|
And a transport warning should have been logged for "lsp.transport.negative_content_length"
|
|
|
|
@lsp_transport_edge
|
|
Scenario: Incomplete message body is handled gracefully
|
|
Given a message with incomplete body
|
|
When the LSP server processes all messages
|
|
Then the server should have stopped
|
|
And the server exit code should be 1
|
|
And a transport warning should have been logged for "lsp.transport.incomplete_read"
|
|
|
|
@lsp_transport_edge
|
|
Scenario: Unknown method returns MethodNotFound without data field
|
|
Given an initialize request with id 1
|
|
And a request for method "custom/withData" with id 30
|
|
And an exit notification
|
|
When the LSP server processes all messages
|
|
Then the response for id 30 should have error code -32601
|
|
And the error response for id 30 should not contain a data field
|
|
|
|
@lsp_transport_edge
|
|
Scenario: Missing content length header is handled
|
|
Given a message with missing content length header
|
|
When the LSP server processes all messages
|
|
Then the server should have stopped
|
|
And the server exit code should be 1
|
|
And a transport warning should have been logged for "lsp.transport.missing_content_length"
|
|
|
|
@lsp_transport_edge
|
|
Scenario: Oversized content length is rejected
|
|
Given a message with content length exceeding the maximum
|
|
And an exit notification
|
|
When the LSP server processes all messages
|
|
Then the server should have stopped
|
|
And the server exit code should be 1
|
|
And a transport warning should have been logged for "lsp.transport.content_length_exceeded"
|
|
|
|
@lsp_transport_edge
|
|
Scenario: Too many header lines are rejected
|
|
Given a message with too many header lines
|
|
When the LSP server processes all messages
|
|
Then the server should have stopped
|
|
And the server exit code should be 1
|
|
And a transport warning should have been logged for "lsp.transport.too_many_header_lines"
|
|
|
|
@lsp_transport_edge
|
|
Scenario: JSON array body returns InvalidRequest error
|
|
Given a message with a JSON array body
|
|
And an exit notification
|
|
When the LSP server processes all messages
|
|
Then an InvalidRequest error response should have been sent
|
|
|
|
@lsp_transport_edge
|
|
Scenario: Non-integer content length is handled gracefully
|
|
Given a message with non-integer content length
|
|
When the LSP server processes all messages
|
|
Then the server should have stopped
|
|
And the server exit code should be 1
|
|
And a transport warning should have been logged for "lsp.transport.invalid_content_length"
|
|
|
|
@lsp_transport_edge
|
|
Scenario: Zero content length returns parse error
|
|
Given a message with content length zero
|
|
And an exit notification
|
|
When the LSP server processes all messages
|
|
Then a parse error response should have been sent
|
|
And the server should have stopped
|
|
|
|
@lsp_transport_edge
|
|
Scenario: Duplicate content-length headers uses last value
|
|
Given a message with duplicate content-length headers and id 60
|
|
And an exit notification
|
|
When the LSP server processes all messages
|
|
Then the response for id 60 should have result
|
|
And the result for id 60 should contain serverInfo with name "cleveragents-lsp-stub"
|
|
|
|
@lsp_transport_edge
|
|
Scenario: Shutdown without prior initialize returns ServerNotInitialized
|
|
Given a shutdown request with id 1
|
|
And an exit notification
|
|
When the LSP server processes all messages
|
|
Then the response for id 1 should have error code -32002
|
|
And the error message for id 1 should contain "Server not initialized"
|
|
|
|
@lsp_transport_edge
|
|
Scenario: Initialize without params key uses empty params
|
|
Given a message with initialize request without params key and id 50
|
|
And an exit notification
|
|
When the LSP server processes all messages
|
|
Then the response for id 50 should have result
|
|
And the result for id 50 should contain serverInfo with name "cleveragents-lsp-stub"
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Post-shutdown request rejection (LSP spec §3.16)
|
|
# ---------------------------------------------------------------------------
|
|
|
|
@lsp_shutdown
|
|
Scenario: Request after shutdown returns InvalidRequest error
|
|
Given an initialize request with id 1
|
|
And a shutdown request with id 2
|
|
And a request for method "textDocument/completion" with id 3
|
|
And an exit notification
|
|
When the LSP server processes all messages
|
|
Then the response for id 3 should have error code -32600
|
|
And the error message for id 3 should contain "shutting down"
|
|
|
|
@lsp_shutdown
|
|
Scenario: Notification after shutdown is silently ignored
|
|
Given an initialize request with id 1
|
|
And a shutdown request with id 2
|
|
And a notification for method "textDocument/didOpen"
|
|
And an exit notification
|
|
When the LSP server processes all messages
|
|
Then the response for id 1 should have result
|
|
And the response for id 2 should have null result
|
|
And there should be no error responses
|
|
And the server exit code should be 0
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Double initialize rejection
|
|
# ---------------------------------------------------------------------------
|
|
|
|
@lsp_initialize
|
|
Scenario: Second initialize request returns InvalidRequest error
|
|
Given an initialize request with id 1
|
|
And an initialize request with id 2
|
|
And an exit notification
|
|
When the LSP server processes all messages
|
|
Then the response for id 1 should have result
|
|
And the response for id 2 should have error code -32600
|
|
And the error message for id 2 should contain "already initialized"
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Stream desync recovery (transport error followed by valid message)
|
|
# ---------------------------------------------------------------------------
|
|
|
|
@lsp_transport_edge
|
|
Scenario: Server recovers after oversized message with body and processes next request
|
|
Given an oversized message with real body followed by an initialize request with id 1
|
|
And an exit notification
|
|
When the LSP server processes all messages
|
|
Then the response for id 1 should have result
|
|
And the result for id 1 should contain serverInfo with name "cleveragents-lsp-stub"
|
|
|
|
@lsp_transport_edge
|
|
Scenario: Server recovers after invalid content-length header and processes next request
|
|
Given a message with non-integer content length followed by an initialize request with id 1
|
|
And an exit notification
|
|
When the LSP server processes all messages
|
|
Then the response for id 1 should have result
|
|
And the result for id 1 should contain serverInfo with name "cleveragents-lsp-stub"
|
|
|
|
@lsp_transport_edge
|
|
Scenario: Server recovers after too many header lines and processes next request
|
|
Given a message with too many header lines followed by an initialize request with id 1
|
|
And an exit notification
|
|
When the LSP server processes all messages
|
|
Then the response for id 1 should have result
|
|
And the result for id 1 should contain serverInfo with name "cleveragents-lsp-stub"
|
|
|
|
@lsp_transport_edge
|
|
Scenario: EOF during body discard of oversized message is handled gracefully
|
|
Given an oversized message with a short body that triggers EOF during discard
|
|
When the LSP server processes all messages
|
|
Then the server should have stopped
|
|
And the server exit code should be 1
|
|
And a transport warning should have been logged for "lsp.transport.content_length_exceeded"
|
|
|
|
@lsp_transport_edge
|
|
Scenario: Oversized single header line is handled gracefully
|
|
Given a message with a header line exceeding the maximum line length
|
|
And an exit notification
|
|
When the LSP server processes all messages
|
|
Then the server should have stopped
|
|
And the server exit code should be 1
|
|
And a transport warning should have been logged for "lsp.transport.too_many_header_lines"
|
|
|
|
@lsp_transport_edge
|
|
Scenario: Deeply nested JSON triggers parse error response
|
|
Given a message with deeply nested JSON
|
|
And an exit notification
|
|
When the LSP server processes all messages
|
|
Then a parse error response should have been sent
|
|
And a transport warning should have been logged for "lsp.transport.json_recursion_error"
|
|
And the server should have stopped
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# CLI serve command
|
|
# ---------------------------------------------------------------------------
|
|
|
|
@lsp_cli_serve
|
|
Scenario: CLI serve command rejects invalid log level
|
|
When I run lsp CLI serve with log level "banana"
|
|
Then the lsp serve CLI should have failed
|
|
And the lsp serve CLI output should contain "Invalid log level"
|
|
|
|
@lsp_cli_serve
|
|
Scenario: CLI serve command accepts valid log level and runs
|
|
When I run lsp CLI serve with piped empty stdin
|
|
Then the lsp serve CLI should have exited with code 1
|
|
And the lsp serve CLI output should contain "CleverAgents LSP Server"
|
|
|
|
@lsp_cli_serve
|
|
Scenario: CLI serve command with debug log level
|
|
When I run lsp CLI serve with log level "debug" and piped empty stdin
|
|
Then the lsp serve CLI should have exited with code 1
|
|
And the lsp serve CLI output should contain "CleverAgents LSP Server"
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Broken output stream (pipe failure)
|
|
# ---------------------------------------------------------------------------
|
|
|
|
@lsp_transport_edge
|
|
Scenario: Broken output stream stops the server gracefully
|
|
Given an initialize request with id 1
|
|
And an exit notification
|
|
When the LSP server processes all messages with a broken output stream
|
|
Then the server should have stopped
|
|
And the server exit code should be 1
|
|
And a transport warning should have been logged for "lsp.transport.write_error"
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# String-valued JSON-RPC request id
|
|
# ---------------------------------------------------------------------------
|
|
|
|
@lsp_initialize
|
|
Scenario: String request id is echoed correctly
|
|
Given an initialize request with string id "alpha-1"
|
|
And an exit notification
|
|
When the LSP server processes all messages
|
|
Then the response for string id "alpha-1" should have result
|
|
And the result for string id "alpha-1" should contain serverInfo with name "cleveragents-lsp-stub"
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# EOF after shutdown without exit notification
|
|
# ---------------------------------------------------------------------------
|
|
|
|
@lsp_shutdown
|
|
Scenario: EOF after shutdown without exit returns exit code 0
|
|
Given an initialize request with id 1
|
|
And a shutdown request with id 2
|
|
When the LSP server processes all messages
|
|
Then the response for id 2 should have null result
|
|
And the server exit code should be 0
|