179 lines
5.1 KiB
Go
179 lines
5.1 KiB
Go
package main
|
|
|
|
import (
|
|
"bufio"
|
|
"encoding/json"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestReadJSONRPCResponse_DirectResponse(t *testing.T) {
|
|
t.Parallel()
|
|
input := `{"jsonrpc":"2.0","id":1,"result":{"tools":[]}}` + "\n"
|
|
scanner := bufio.NewScanner(strings.NewReader(input))
|
|
|
|
got, err := readJSONRPCResponse(scanner)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if got != `{"jsonrpc":"2.0","id":1,"result":{"tools":[]}}` {
|
|
t.Fatalf("unexpected response: %s", got)
|
|
}
|
|
}
|
|
|
|
func TestReadJSONRPCResponse_SkipsNotifications(t *testing.T) {
|
|
t.Parallel()
|
|
input := strings.Join([]string{
|
|
`{"jsonrpc":"2.0","method":"notifications/resources/list_changed","params":{}}`,
|
|
`{"jsonrpc":"2.0","method":"notifications/tools/list_changed"}`,
|
|
`{"jsonrpc":"2.0","id":42,"result":{"content":[{"type":"text","text":"hello"}]}}`,
|
|
}, "\n") + "\n"
|
|
scanner := bufio.NewScanner(strings.NewReader(input))
|
|
|
|
got, err := readJSONRPCResponse(scanner)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
|
|
var msg map[string]json.RawMessage
|
|
if err := json.Unmarshal([]byte(got), &msg); err != nil {
|
|
t.Fatalf("response is not valid JSON: %v", err)
|
|
}
|
|
// Verify we got the response with id:42, not a notification
|
|
var id int
|
|
if err := json.Unmarshal(msg["id"], &id); err != nil {
|
|
t.Fatalf("failed to parse id: %v", err)
|
|
}
|
|
if id != 42 {
|
|
t.Fatalf("expected id 42, got %d", id)
|
|
}
|
|
}
|
|
|
|
func TestReadJSONRPCResponse_NoResponse(t *testing.T) {
|
|
t.Parallel()
|
|
// Only notifications, no response
|
|
input := `{"jsonrpc":"2.0","method":"notifications/resources/list_changed","params":{}}` + "\n"
|
|
scanner := bufio.NewScanner(strings.NewReader(input))
|
|
|
|
_, err := readJSONRPCResponse(scanner)
|
|
if err == nil {
|
|
t.Fatal("expected error for missing response, got nil")
|
|
}
|
|
if !strings.Contains(err.Error(), "unexpected end of output") {
|
|
t.Fatalf("expected 'unexpected end of output' error, got: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestReadJSONRPCResponse_EmptyInput(t *testing.T) {
|
|
t.Parallel()
|
|
scanner := bufio.NewScanner(strings.NewReader(""))
|
|
|
|
_, err := readJSONRPCResponse(scanner)
|
|
if err == nil {
|
|
t.Fatal("expected error for empty input, got nil")
|
|
}
|
|
}
|
|
|
|
func TestReadJSONRPCResponse_InvalidJSON(t *testing.T) {
|
|
t.Parallel()
|
|
input := "not valid json\n"
|
|
scanner := bufio.NewScanner(strings.NewReader(input))
|
|
|
|
_, err := readJSONRPCResponse(scanner)
|
|
if err == nil {
|
|
t.Fatal("expected error for invalid JSON, got nil")
|
|
}
|
|
if !strings.Contains(err.Error(), "failed to parse JSON-RPC message") {
|
|
t.Fatalf("expected parse error, got: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestReadJSONRPCResponse_ServerError(t *testing.T) {
|
|
t.Parallel()
|
|
input := `{"jsonrpc":"2.0","id":1,"error":{"code":-32601,"message":"method not found"}}` + "\n"
|
|
scanner := bufio.NewScanner(strings.NewReader(input))
|
|
|
|
_, err := readJSONRPCResponse(scanner)
|
|
if err == nil {
|
|
t.Fatal("expected error for server error response, got nil")
|
|
}
|
|
if !strings.Contains(err.Error(), "server returned error") {
|
|
t.Fatalf("expected 'server returned error', got: %v", err)
|
|
}
|
|
if !strings.Contains(err.Error(), "method not found") {
|
|
t.Fatalf("expected error to contain server message, got: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestBuildInitializeRequest(t *testing.T) {
|
|
t.Parallel()
|
|
got, err := buildInitializeRequest()
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
|
|
var msg map[string]json.RawMessage
|
|
if err := json.Unmarshal([]byte(got), &msg); err != nil {
|
|
t.Fatalf("result is not valid JSON: %v", err)
|
|
}
|
|
|
|
// Verify required fields
|
|
for _, field := range []string{"jsonrpc", "id", "method", "params"} {
|
|
if _, ok := msg[field]; !ok {
|
|
t.Errorf("missing required field %q", field)
|
|
}
|
|
}
|
|
|
|
// Verify method
|
|
var method string
|
|
if err := json.Unmarshal(msg["method"], &method); err != nil {
|
|
t.Fatalf("failed to parse method: %v", err)
|
|
}
|
|
if method != "initialize" {
|
|
t.Errorf("expected method 'initialize', got %q", method)
|
|
}
|
|
|
|
// Verify params contain protocolVersion and clientInfo
|
|
var params map[string]json.RawMessage
|
|
if err := json.Unmarshal(msg["params"], ¶ms); err != nil {
|
|
t.Fatalf("failed to parse params: %v", err)
|
|
}
|
|
for _, field := range []string{"protocolVersion", "capabilities", "clientInfo"} {
|
|
if _, ok := params[field]; !ok {
|
|
t.Errorf("missing params field %q", field)
|
|
}
|
|
}
|
|
|
|
var version string
|
|
if err := json.Unmarshal(params["protocolVersion"], &version); err != nil {
|
|
t.Fatalf("failed to parse protocolVersion: %v", err)
|
|
}
|
|
if version != "2024-11-05" {
|
|
t.Errorf("expected protocolVersion '2024-11-05', got %q", version)
|
|
}
|
|
}
|
|
|
|
func TestBuildInitializedNotification(t *testing.T) {
|
|
t.Parallel()
|
|
got := buildInitializedNotification()
|
|
|
|
var msg map[string]json.RawMessage
|
|
if err := json.Unmarshal([]byte(got), &msg); err != nil {
|
|
t.Fatalf("result is not valid JSON: %v", err)
|
|
}
|
|
|
|
// Must have jsonrpc and method
|
|
var method string
|
|
if err := json.Unmarshal(msg["method"], &method); err != nil {
|
|
t.Fatalf("failed to parse method: %v", err)
|
|
}
|
|
if method != "notifications/initialized" {
|
|
t.Errorf("expected method 'notifications/initialized', got %q", method)
|
|
}
|
|
|
|
// Must NOT have an id (it's a notification)
|
|
if _, hasID := msg["id"]; hasID {
|
|
t.Error("notification should not have an 'id' field")
|
|
}
|
|
}
|