返回 DeepSeek-Reasonix
jetbrains_session_test.go
根目录 / internal / plugin / jetbrains_session_test.go
1 package plugin
2
3 import (
4 "context"
5 "encoding/json"
6 "net/http"
7 "net/http/httptest"
8 "sync"
9 "testing"
10 "time"
11 )
12
13 func TestJetBrainsPendingSessionPromotedByStandaloneGET(t *testing.T) {
14 const (
15 sessionID = "jetbrains-session-secret"
16 projectPath = "/private/project-path"
17 )
18 var (
19 mu sync.Mutex
20 active bool
21 virtualSecond int
22 getCount int
23 notFoundCount int
24 deleteCount int
25 )
26 getReady := make(chan struct{})
27 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
28 if got := r.Header.Get("IJ_MCP_SERVER_PROJECT_PATH"); got != projectPath {
29 http.Error(w, "missing project header", http.StatusBadRequest)
30 return
31 }
32 if r.Method == http.MethodGet {
33 if got := r.Header.Get("Mcp-Session-Id"); got != sessionID {
34 http.Error(w, "missing session", http.StatusNotFound)
35 return
36 }
37 mu.Lock()
38 active = true
39 getCount++
40 if getCount == 1 {
41 close(getReady)
42 }
43 mu.Unlock()
44 w.Header().Set("Content-Type", "text/event-stream")
45 w.WriteHeader(http.StatusOK)
46 w.(http.Flusher).Flush()
47 <-r.Context().Done()
48 return
49 }
50 if r.Method == http.MethodDelete {
51 if got := r.Header.Get("Mcp-Session-Id"); got != sessionID {
52 http.Error(w, "missing session", http.StatusBadRequest)
53 return
54 }
55 mu.Lock()
56 deleteCount++
57 mu.Unlock()
58 w.WriteHeader(http.StatusOK)
59 return
60 }
61
62 var request struct {
63 ID json.RawMessage `json:"id"`
64 Method string `json:"method"`
65 }
66 if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
67 http.Error(w, "bad request", http.StatusBadRequest)
68 return
69 }
70 if request.Method == "server/discover" {
71 writeRawHTTPRPCError(w, request.ID, -32601, "Method not found")
72 return
73 }
74 if request.Method == "initialize" {
75 w.Header().Set("Mcp-Session-Id", sessionID)
76 writeRawHTTPRPCResult(w, request.ID, map[string]any{
77 "protocolVersion": testLegacyProtocolVersion,
78 "serverInfo": map[string]any{"name": "jetbrains", "version": "1"},
79 "capabilities": map[string]any{"tools": map[string]any{}},
80 })
81 return
82 }
83 if len(request.ID) == 0 {
84 w.WriteHeader(http.StatusAccepted)
85 return
86 }
87 mu.Lock()
88 expiredPending := !active && virtualSecond >= 15
89 if expiredPending {
90 notFoundCount++
91 }
92 mu.Unlock()
93 if expiredPending {
94 http.Error(w, "Streamable HTTP session not found", http.StatusNotFound)
95 return
96 }
97 switch request.Method {
98 case "tools/list":
99 writeRawHTTPRPCResult(w, request.ID, map[string]any{"tools": []map[string]any{{
100 "name": "build_project", "description": "Build the project",
101 "inputSchema": map[string]any{"type": "object"},
102 }}})
103 case "tools/call":
104 writeRawHTTPRPCResult(w, request.ID, map[string]any{"content": []map[string]any{{"type": "text", "text": "built"}}})
105 default:
106 writeRawHTTPRPCError(w, request.ID, -32601, "Method not found")
107 }
108 }))
109
110 ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
111 defer cancel()
112 host, tools, err := StartAll(ctx, []Spec{{
113 Name: "rvb_monitor", Type: "streamable-http", URL: server.URL,
114 Headers: map[string]string{"IJ_MCP_SERVER_PROJECT_PATH": projectPath},
115 }})
116 if err != nil {
117 server.Close()
118 t.Fatalf("StartAll: %v", err)
119 }
120 select {
121 case <-getReady:
122 default:
123 host.Close()
124 server.Close()
125 t.Fatal("client became ready before establishing standalone GET/SSE")
126 }
127 mu.Lock()
128 virtualSecond = 20
129 mu.Unlock()
130 result, err := tools[0].Execute(ctx, json.RawMessage(`{}`))
131 if err != nil || result != "built" {
132 host.Close()
133 server.Close()
134 t.Fatalf("build_project after virtual 20s = %q, %v", result, err)
135 }
136 host.Close()
137 server.Close()
138 mu.Lock()
139 defer mu.Unlock()
140 if getCount != 1 || notFoundCount != 0 || deleteCount != 1 {
141 t.Fatalf("GET=%d 404=%d DELETE=%d, want 1/0/1", getCount, notFoundCount, deleteCount)
142 }
143 }
144
144 lines GO