mirror of
https://github.com/AstrBotDevs/AstrBot
synced 2026-07-18 10:00:40 +08:00
docs: add openspec specs for integration tests changes
This commit is contained in:
@@ -0,0 +1,62 @@
|
||||
# Internal Integration Tests Specification
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: MCP client integration with echo server
|
||||
|
||||
The MCP client SHALL be able to connect to a real MCP echo server fixture and perform tool operations.
|
||||
|
||||
#### Scenario: MCP client initialization
|
||||
- **WHEN** McpClient is instantiated
|
||||
- **THEN** client exists with `connected = False`
|
||||
|
||||
#### Scenario: MCP client connect without config is noop
|
||||
- **WHEN** `connect()` is called without server configuration
|
||||
- **THEN** client remains in disconnected state
|
||||
|
||||
#### Scenario: MCP client connects to echo server (deferred)
|
||||
- **WHEN** MCP client attempts to connect to echo MCP server
|
||||
- **THEN** connection is established and verified
|
||||
- **NOTE**: Test skipped - MCP ClientSession.initialize() protocol handshake is complex
|
||||
|
||||
#### Scenario: MCP client lists tools from server (deferred)
|
||||
- **WHEN** `list_tools()` is called on connected client
|
||||
- **THEN** returns list of available tools from server
|
||||
- **NOTE**: Test skipped due to protocol complexity
|
||||
|
||||
#### Scenario: MCP client calls echo tool (deferred)
|
||||
- **WHEN** `call_tool("echo", {"message": "test"})` is called
|
||||
- **THEN** returns echoed result
|
||||
- **NOTE**: Test skipped due to protocol complexity
|
||||
|
||||
### Requirement: ACP client integration with echo server
|
||||
|
||||
The ACP client SHALL be able to connect to a real ACP echo server fixture over TCP and Unix sockets.
|
||||
|
||||
#### Scenario: ACP client initial state
|
||||
- **WHEN** AstrbotAcpClient is instantiated
|
||||
- **THEN** `connected = False`, `_reader = None`, `_writer = None`
|
||||
|
||||
#### Scenario: ACP client connects to TCP server
|
||||
- **WHEN** `connect_to_server(host, port)` is called with TCP echo server
|
||||
- **THEN** client connects successfully with `connected = True`
|
||||
- **AND** `_reader` and `_writer` are set
|
||||
|
||||
#### Scenario: ACP client connects to Unix socket
|
||||
- **WHEN** `connect_to_unix_socket(socket_path)` is called with Unix socket echo server
|
||||
- **THEN** client connects successfully with `connected = True`
|
||||
- **AND** `_reader` and `_writer` are set
|
||||
|
||||
### Requirement: Integration test fixtures
|
||||
|
||||
The integration test fixtures SHALL provide real protocol servers for testing.
|
||||
|
||||
#### Scenario: Echo MCP server handles stdio protocol
|
||||
- **WHEN** MCP server receives JSON-RPC request on stdin
|
||||
- **THEN** responds with proper Content-Length headers on stdout
|
||||
- **AND** handles `initialize`, `tools/list`, `tools/call` methods
|
||||
|
||||
#### Scenario: Echo ACP server handles stream protocol
|
||||
- **WHEN** ACP server receives request on TCP/Unix socket
|
||||
- **THEN** responds with JSON-RPC responses
|
||||
- **AND** handles `initialize`, `echo`, and `{server}/{tool}` methods
|
||||
2
openspec/changes/lsp-integration-tests/.openspec.yaml
Normal file
2
openspec/changes/lsp-integration-tests/.openspec.yaml
Normal file
@@ -0,0 +1,2 @@
|
||||
schema: spec-driven
|
||||
created: 2026-03-24
|
||||
184
openspec/changes/lsp-integration-tests/design.md
Normal file
184
openspec/changes/lsp-integration-tests/design.md
Normal file
@@ -0,0 +1,184 @@
|
||||
# Design: LSP Integration Tests
|
||||
|
||||
## LSP Echo Server Fixture
|
||||
|
||||
Create a simple stdio-based LSP server for testing:
|
||||
|
||||
```python
|
||||
# tests/integration/fixtures/echo_lsp_server.py
|
||||
"""
|
||||
Simple LSP server that echoes back requests.
|
||||
Used for testing the LSP client.
|
||||
"""
|
||||
|
||||
import json
|
||||
import sys
|
||||
|
||||
def main():
|
||||
while True:
|
||||
line = sys.stdin.readline()
|
||||
if not line:
|
||||
break
|
||||
request = json.loads(line)
|
||||
|
||||
msg_id = request.get("id")
|
||||
method = request.get("method")
|
||||
|
||||
# Handle initialize
|
||||
if method == "initialize":
|
||||
print(json.dumps({
|
||||
"jsonrpc": "2.0",
|
||||
"id": msg_id,
|
||||
"result": {
|
||||
"capabilities": {"textDocumentSync": 1}
|
||||
}
|
||||
}))
|
||||
sys.stdout.flush()
|
||||
|
||||
# Handle initialized notification (no response)
|
||||
elif method == "initialized":
|
||||
continue
|
||||
|
||||
# Handle shutdown
|
||||
elif method == "shutdown":
|
||||
print(json.dumps({
|
||||
"jsonrpc": "2.0",
|
||||
"id": msg_id,
|
||||
"result": None
|
||||
}))
|
||||
sys.stdout.flush()
|
||||
|
||||
# Echo any textDocument/didOpen
|
||||
elif method == "textDocument/didOpen":
|
||||
print(json.dumps({
|
||||
"jsonrpc": "2.0",
|
||||
"id": msg_id,
|
||||
"result": None
|
||||
}))
|
||||
sys.stdout.flush()
|
||||
|
||||
# Echo any request
|
||||
elif msg_id is not None:
|
||||
print(json.dumps({
|
||||
"jsonrpc": "2.0",
|
||||
"id": msg_id,
|
||||
"result": {"echo": True}
|
||||
}))
|
||||
sys.stdout.flush()
|
||||
```
|
||||
|
||||
## LSP Client Integration Tests
|
||||
|
||||
```python
|
||||
# tests/integration/test_lsp_integration.py
|
||||
"""
|
||||
LSP Integration Tests.
|
||||
|
||||
Tests the LSP client against a real LSP server fixture.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import pytest
|
||||
|
||||
from astrbot._internal.protocols.lsp.client import AstrbotLspClient
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_lsp_client_initialization():
|
||||
"""Test LSP client can be initialized."""
|
||||
client = AstrbotLspClient()
|
||||
assert client is not None
|
||||
assert not client.connected
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_lsp_client_connect_to_echo_server():
|
||||
"""Test LSP client can connect to echo LSP server."""
|
||||
client = AstrbotLspClient()
|
||||
|
||||
test_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
server_path = os.path.join(test_dir, "fixtures", "echo_lsp_server.py")
|
||||
|
||||
await client.connect_to_server(
|
||||
command=["python", server_path],
|
||||
workspace_uri="file:///tmp"
|
||||
)
|
||||
|
||||
try:
|
||||
assert client.connected
|
||||
finally:
|
||||
await client.shutdown()
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_lsp_client_send_request():
|
||||
"""Test LSP client can send a request and receive response."""
|
||||
client = AstrbotLspClient()
|
||||
|
||||
test_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
server_path = os.path.join(test_dir, "fixtures", "echo_lsp_server.py")
|
||||
|
||||
await client.connect_to_server(
|
||||
command=["python", server_path],
|
||||
workspace_uri="file:///tmp"
|
||||
)
|
||||
|
||||
try:
|
||||
assert client.connected
|
||||
|
||||
# Send a custom request
|
||||
result = await client.send_request("custom/echo", {"message": "test"})
|
||||
assert result is not None
|
||||
|
||||
finally:
|
||||
await client.shutdown()
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_lsp_client_send_notification():
|
||||
"""Test LSP client can send a notification (no response)."""
|
||||
client = AstrbotLspClient()
|
||||
|
||||
test_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
server_path = os.path.join(test_dir, "fixtures", "echo_lsp_server.py")
|
||||
|
||||
await client.connect_to_server(
|
||||
command=["python", server_path],
|
||||
workspace_uri="file:///tmp"
|
||||
)
|
||||
|
||||
try:
|
||||
assert client.connected
|
||||
|
||||
# Send a notification (should not raise)
|
||||
await client.send_notification("custom/notify", {"data": "test"})
|
||||
|
||||
finally:
|
||||
await client.shutdown()
|
||||
```
|
||||
|
||||
## Test Fixtures Location
|
||||
|
||||
```
|
||||
tests/
|
||||
├── integration/
|
||||
│ ├── fixtures/
|
||||
│ │ ├── echo_lsp_server.py # New
|
||||
│ │ ├── echo_mcp_server.py # Existing
|
||||
│ │ └── echo_acp_server.py # Existing
|
||||
│ ├── test_lsp_integration.py # New
|
||||
│ ├── test_mcp_integration.py # Existing
|
||||
│ └── test_acp_integration.py # Existing
|
||||
```
|
||||
|
||||
## Running Tests
|
||||
|
||||
```bash
|
||||
# Run all integration tests
|
||||
uv run pytest tests/integration/ -v
|
||||
|
||||
# Run LSP integration only
|
||||
uv run pytest tests/integration/test_lsp_integration.py -v
|
||||
```
|
||||
25
openspec/changes/lsp-integration-tests/proposal.md
Normal file
25
openspec/changes/lsp-integration-tests/proposal.md
Normal file
@@ -0,0 +1,25 @@
|
||||
# LSP Integration Tests
|
||||
|
||||
## Problem
|
||||
|
||||
The `_internal` architecture includes an LSP (Language Server Protocol) client implementation at `astrbot/_internal/protocols/lsp/client.py`, but there are no integration tests validating that it can connect to and communicate with a real LSP server. The orchestrator manages an `AstrbotLspClient` instance (`self.lsp`), but this functionality remains untested.
|
||||
|
||||
## Solution
|
||||
|
||||
Create integration tests that:
|
||||
1. Build an LSP echo server fixture (stdio-based)
|
||||
2. Test the LSP client can connect, send requests, and receive responses
|
||||
3. Verify the JSON-RPC 2.0 protocol communication works end-to-end
|
||||
|
||||
## Why This Matters
|
||||
|
||||
- Architecture claims must be validated through tests
|
||||
- LSP functionality is used by the orchestrator for language intelligence
|
||||
- Integration tests catch regressions that unit tests miss
|
||||
- Validates the JSON-RPC 2.0 message framing and async communication
|
||||
|
||||
## Scope
|
||||
|
||||
- Create `tests/integration/fixtures/echo_lsp_server.py`
|
||||
- Create `tests/integration/test_lsp_integration.py`
|
||||
- Ensure tests can run in CI
|
||||
Reference in New Issue
Block a user