返回 DeepSeek-Reasonix
sdk_session_error_test.go
根目录 / internal / plugin / sdk_session_error_test.go
1 package plugin
2
3 import (
4 "encoding/json"
5 "fmt"
6 "net/http"
7 "net/http/httptest"
8 "strings"
9 "sync/atomic"
10 "testing"
11
12 mcpjsonrpc "github.com/modelcontextprotocol/go-sdk/jsonrpc"
13 )
14
15 func TestApplicationJSONRPCSessionErrorsAreNotTransportLoss(t *testing.T) {
16 for _, message := range []string{
17 "session not found",
18 "session missing",
19 "session expired",
20 "invalid session",
21 "unknown session",
22 } {
23 err := fmt.Errorf("calling tool: %w", &mcpjsonrpc.Error{Code: -32042, Message: message})
24 if isExplicitMCPSessionMissing(err) {
25 t.Errorf("isExplicitMCPSessionMissing(%q) = true, want false without transport rejection", message)
26 }
27 }
28
29 err := fmt.Errorf("calling tool: %w", &mcpjsonrpc.Error{Code: -32042, Message: "not found"})
30 if isMCPHTTPNotFound(err) {
31 t.Fatal("application JSON-RPC not-found error was classified as HTTP 404")
32 }
33 }
34
35 func TestStructuredHTTP404SessionErrorIsTransportLoss(t *testing.T) {
36 sessionErr := &mcpjsonrpc.Error{Code: -32042, Message: "Session not found"}
37 rejectedErr := &mcpjsonrpc.Error{Code: -32005, Message: "rejected by transport"}
38 err := fmt.Errorf("sending tools/call: %w: %w: Not Found", sessionErr, rejectedErr)
39 if !isExplicitMCPSessionMissing(err) {
40 t.Fatal("structured HTTP 404 session error was not classified as transport session loss")
41 }
42 }
43
44 func TestHTTPTransportApplicationSessionErrorDoesNotReplayToolCall(t *testing.T) {
45 var initializeCount atomic.Int32
46 var toolCallCount atomic.Int32
47 srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
48 if r.Method == http.MethodGet {
49 w.WriteHeader(http.StatusMethodNotAllowed)
50 return
51 }
52 var req struct {
53 ID json.RawMessage `json:"id"`
54 Method string `json:"method"`
55 }
56 if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
57 http.Error(w, "bad body", http.StatusBadRequest)
58 return
59 }
60
61 switch req.Method {
62 case "server/discover":
63 writeRawHTTPRPCError(w, req.ID, -32601, "method not found")
64 case "initialize":
65 initializeCount.Add(1)
66 w.Header().Set("Mcp-Session-Id", "application-session")
67 writeRawHTTPRPCResult(w, req.ID, map[string]any{
68 "protocolVersion": testLegacyProtocolVersion,
69 "serverInfo": map[string]any{"name": "application-error", "version": "1"},
70 "capabilities": map[string]any{"tools": map[string]any{}},
71 })
72 case "notifications/initialized":
73 w.WriteHeader(http.StatusAccepted)
74 case "tools/list":
75 writeRawHTTPRPCResult(w, req.ID, map[string]any{"tools": []any{}})
76 case "tools/call":
77 toolCallCount.Add(1)
78 writeRawHTTPRPCError(w, req.ID, -32042, "invalid session")
79 default:
80 http.Error(w, "unknown method", http.StatusBadRequest)
81 }
82 }))
83 defer srv.Close()
84
85 transport, err := newHTTPTransport(Spec{Name: "application-error", Type: "http", URL: srv.URL})
86 if err != nil {
87 t.Fatal(err)
88 }
89 defer transport.close()
90 if _, err := transport.call(t.Context(), "tools/list", map[string]any{}); err != nil {
91 t.Fatalf("tools/list: %v", err)
92 }
93 if _, err := transport.call(t.Context(), "tools/call", map[string]any{
94 "name": "write", "arguments": map[string]any{},
95 }); err == nil || !strings.Contains(err.Error(), "invalid session") {
96 t.Fatalf("tools/call error = %v, want application error", err)
97 }
98 if got := initializeCount.Load(); got != 1 {
99 t.Fatalf("initialize count = %d, want no session rebuild", got)
100 }
101 if got := toolCallCount.Load(); got != 1 {
102 t.Fatalf("tools/call count = %d, want no replay", got)
103 }
104 }
105
105 lines GO