Files
agent-radar 2ed3415fb7
Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
源码快照 @ 2026-09-01 (source-only, 剔除非源码素材)
2026-09-01 20:17:27 +08:00

155 lines
4.4 KiB
Go

package mcp
import (
"context"
"encoding/json"
"strings"
"sync"
"testing"
"time"
agentruntime "github.com/tutti-os/tutti/packages/agent/daemon/runtime"
)
type scriptedConnection struct {
frames chan agentruntime.ProcessFrame
mu sync.Mutex
sent [][]byte
}
func newScriptedConnection() *scriptedConnection {
return &scriptedConnection{frames: make(chan agentruntime.ProcessFrame, 16)}
}
func (connection *scriptedConnection) Send(data []byte) error {
connection.mu.Lock()
connection.sent = append(connection.sent, append([]byte(nil), data...))
connection.mu.Unlock()
return nil
}
func (connection *scriptedConnection) Recv() (agentruntime.ProcessFrame, error) {
frame, ok := <-connection.frames
if !ok {
return agentruntime.ProcessFrame{}, context.Canceled
}
return frame, nil
}
func (connection *scriptedConnection) Close() error { close(connection.frames); return nil }
func (connection *scriptedConnection) push(value any) {
data, _ := json.Marshal(value)
connection.frames <- agentruntime.ProcessFrame{Stdout: append(data, '\n')}
}
func TestStdioClientDeliversNotificationsAndDeclinesServerRequestsByDefault(t *testing.T) {
connection := newScriptedConnection()
notifications := make(chan Notification, 1)
client, err := NewStdioClient(StdioClientConfig{
Connection: connection,
NotificationHandler: func(notification Notification) {
notifications <- notification
},
})
if err != nil {
t.Fatal(err)
}
if client == nil {
t.Fatal("stdio client was not created")
}
connection.push(map[string]any{"jsonrpc": "2.0", "method": "notifications/tools/list_changed", "params": map[string]any{}})
connection.push(map[string]any{"jsonrpc": "2.0", "id": 42, "method": "elicitation/create", "params": map[string]any{}})
select {
case notification := <-notifications:
if notification.Method != "notifications/tools/list_changed" {
t.Fatalf("notification = %#v", notification)
}
case <-time.After(time.Second):
t.Fatal("notification was not delivered")
}
deadline := time.Now().Add(time.Second)
for time.Now().Before(deadline) {
connection.mu.Lock()
sent := append([][]byte(nil), connection.sent...)
connection.mu.Unlock()
if len(sent) != 0 {
var response struct {
Error *RPCError `json:"error"`
}
if err := json.Unmarshal(sent[0], &response); err != nil {
t.Fatal(err)
}
if response.Error == nil || response.Error.Code != -32601 {
t.Fatalf("response = %s", sent[0])
}
return
}
time.Sleep(time.Millisecond)
}
t.Fatal("server request response was not sent")
}
func TestStdioClientFailsClosedOnOversizedMessage(t *testing.T) {
connection := newScriptedConnection()
client, err := NewStdioClient(StdioClientConfig{Connection: connection, MaxMessageBytes: 16})
if err != nil {
t.Fatal(err)
}
connection.frames <- agentruntime.ProcessFrame{Stdout: []byte(strings.Repeat("x", 17))}
deadline := time.Now().Add(time.Second)
for !client.IsClosed() && time.Now().Before(deadline) {
time.Sleep(time.Millisecond)
}
if err := client.ClosedError(); err == nil || !strings.Contains(err.Error(), "exceeds limit") {
t.Fatalf("ClosedError() = %v", err)
}
}
func TestStdioClientNotificationHandlerCanIssueNestedCall(t *testing.T) {
connection := newScriptedConnection()
completed := make(chan error, 1)
var client *StdioClient
client, err := NewStdioClient(StdioClientConfig{
Connection: connection,
NotificationHandler: func(Notification) {
_, callErr := client.Call(context.Background(), "tools/list", map[string]any{})
completed <- callErr
},
})
if err != nil {
t.Fatal(err)
}
connection.push(map[string]any{"jsonrpc": "2.0", "method": "notifications/tools/list_changed"})
deadline := time.Now().Add(time.Second)
var request struct {
ID int64 `json:"id"`
}
for time.Now().Before(deadline) {
connection.mu.Lock()
sent := append([][]byte(nil), connection.sent...)
connection.mu.Unlock()
if len(sent) != 0 {
if err := json.Unmarshal(sent[0], &request); err != nil {
t.Fatal(err)
}
break
}
time.Sleep(time.Millisecond)
}
if request.ID == 0 {
t.Fatal("notification handler did not issue nested call")
}
connection.push(map[string]any{"jsonrpc": "2.0", "id": request.ID, "result": map[string]any{"tools": []any{}}})
select {
case err := <-completed:
if err != nil {
t.Fatalf("nested Call: %v", err)
}
case <-time.After(time.Second):
t.Fatal("nested Call deadlocked the protocol reader")
}
}