Files
2026-03-27 17:55:57 +08:00

227 lines
6.8 KiB
Go

package mockmcp
import (
"encoding/json"
"io"
"net/http"
"strings"
"testing"
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/market"
)
func TestDefaultFixtureValidate(t *testing.T) {
t.Parallel()
if err := DefaultFixture().Validate(); err != nil {
t.Fatalf("default fixture should validate: %v", err)
}
}
func TestMarketServersEndpoint(t *testing.T) {
t.Parallel()
srv := DefaultServer()
defer srv.Close()
resp := mustDoRequest(t, http.MethodGet, srv.MarketURL(), nil)
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
t.Fatalf("unexpected status: %d", resp.StatusCode)
}
var payload struct {
Metadata struct {
Count int `json:"count"`
NextCursor string `json:"nextCursor"`
} `json:"metadata"`
Servers []struct {
Server struct {
SchemaURI string `json:"$schema"`
Name string `json:"name"`
Description string `json:"description"`
Remotes []struct {
Type string `json:"type"`
URL string `json:"url"`
} `json:"remotes"`
} `json:"server"`
Meta struct {
Metadata market.RegistryMetadata `json:"metadata"`
CLI market.CLIOverlay `json:"cli"`
} `json:"_meta"`
} `json:"servers"`
}
decodeJSON(t, resp.Body, &payload)
if payload.Metadata.Count != 2 {
t.Fatalf("expected 2 servers, got %d", payload.Metadata.Count)
}
if len(payload.Servers) != 2 {
t.Fatalf("expected 2 servers, got %d", len(payload.Servers))
}
doc := payload.Servers[0]
if doc.Meta.Metadata.MCPID != 9629 {
t.Fatalf("expected doc mcp id 9629, got %d", doc.Meta.Metadata.MCPID)
}
if got, want := doc.Meta.Metadata.DetailURL, srv.DetailURL(9629); got != want {
t.Fatalf("unexpected detail url: got %q want %q", got, want)
}
if doc.Meta.CLI.ID != "doc" || doc.Meta.CLI.Hidden {
t.Fatalf("expected visible doc cli overlay, got %+v", doc.Meta.CLI)
}
if doc.Server.Remotes[0].URL != srv.RemoteURL("/server/doc") {
t.Fatalf("unexpected remote url: %q", doc.Server.Remotes[0].URL)
}
legacy := payload.Servers[1]
if legacy.Meta.Metadata.Lifecycle.DeprecatedBy != 9629 {
t.Fatalf("expected legacy lifecycle deprecatedBy 9629, got %d", legacy.Meta.Metadata.Lifecycle.DeprecatedBy)
}
if !legacy.Meta.CLI.Hidden {
t.Fatalf("expected legacy CLI overlay to be hidden")
}
}
func TestMarketDetailEndpoint(t *testing.T) {
t.Parallel()
srv := DefaultServer()
defer srv.Close()
resp := mustDoRequest(t, http.MethodGet, srv.DetailURL(9629), nil)
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
t.Fatalf("unexpected status: %d", resp.StatusCode)
}
var payload market.DetailResponse
decodeJSON(t, resp.Body, &payload)
if !payload.Success {
t.Fatalf("expected success detail response")
}
if payload.Result.MCPID != 9629 {
t.Fatalf("unexpected mcp id: %d", payload.Result.MCPID)
}
if len(payload.Result.Tools) != 2 {
t.Fatalf("expected 2 tools, got %d", len(payload.Result.Tools))
}
if payload.Result.Tools[0].ActionVersion != "G-ACT-VER-100" {
t.Fatalf("unexpected action version: %q", payload.Result.Tools[0].ActionVersion)
}
if !strings.Contains(payload.Result.Tools[0].ToolRequest, "\"keyword\"") {
t.Fatalf("expected tool request schema to include keyword")
}
}
func TestJSONRPCLifecycle(t *testing.T) {
t.Parallel()
srv := DefaultServer()
defer srv.Close()
initResp := mustDoRequest(t, http.MethodPost, srv.RemoteURL("/server/doc"), strings.NewReader(`{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"dws","version":"0.0.0-dev"}}}`))
defer initResp.Body.Close()
if initResp.StatusCode != http.StatusOK {
t.Fatalf("unexpected initialize status: %d", initResp.StatusCode)
}
var initPayload struct {
JSONRPC string `json:"jsonrpc"`
ID int `json:"id"`
Result struct {
ProtocolVersion string `json:"protocolVersion"`
Capabilities map[string]any `json:"capabilities"`
ServerInfo map[string]any `json:"serverInfo"`
} `json:"result"`
}
decodeJSON(t, initResp.Body, &initPayload)
if initPayload.Result.ProtocolVersion != DefaultProtocolVer {
t.Fatalf("unexpected protocol version: %q", initPayload.Result.ProtocolVersion)
}
if got := initPayload.Result.ServerInfo["name"]; got != "doc" {
t.Fatalf("unexpected server info name: %v", got)
}
notifyResp := mustDoRequest(t, http.MethodPost, srv.RemoteURL("/server/doc"), strings.NewReader(`{"jsonrpc":"2.0","method":"notifications/initialized","params":{}}`))
defer notifyResp.Body.Close()
if notifyResp.StatusCode != http.StatusNoContent {
t.Fatalf("unexpected notify status: %d", notifyResp.StatusCode)
}
listResp := mustDoRequest(t, http.MethodPost, srv.RemoteURL("/server/doc"), strings.NewReader(`{"jsonrpc":"2.0","id":2,"method":"tools/list"}`))
defer listResp.Body.Close()
var listPayload struct {
Result struct {
Tools []struct {
Name string `json:"name"`
} `json:"tools"`
} `json:"result"`
}
decodeJSON(t, listResp.Body, &listPayload)
if len(listPayload.Result.Tools) != 2 {
t.Fatalf("expected 2 tools, got %d", len(listPayload.Result.Tools))
}
if listPayload.Result.Tools[0].Name != "search_documents" {
t.Fatalf("unexpected first tool name: %q", listPayload.Result.Tools[0].Name)
}
callResp := mustDoRequest(t, http.MethodPost, srv.RemoteURL("/server/doc"), strings.NewReader(`{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"search_documents","arguments":{"keyword":"plan"}}}`))
defer callResp.Body.Close()
var callPayload struct {
Result struct {
Content map[string]any `json:"content"`
} `json:"result"`
}
decodeJSON(t, callResp.Body, &callPayload)
if items, ok := callPayload.Result.Content["items"].([]any); !ok || len(items) != 1 {
t.Fatalf("unexpected call result content: %#v", callPayload.Result.Content)
}
createResp := mustDoRequest(t, http.MethodPost, srv.RemoteURL("/server/doc"), strings.NewReader(`{"jsonrpc":"2.0","id":4,"method":"tools/call","params":{"name":"create_document","arguments":{"title":"hello"}}}`))
defer createResp.Body.Close()
var createPayload struct {
Result struct {
Content map[string]any `json:"content"`
} `json:"result"`
}
decodeJSON(t, createResp.Body, &createPayload)
if got := createPayload.Result.Content["documentId"]; got != "doc-123" {
t.Fatalf("unexpected create result: %#v", createPayload.Result.Content)
}
}
func mustDoRequest(t *testing.T, method, url string, body io.Reader) *http.Response {
t.Helper()
req, err := http.NewRequest(method, url, body)
if err != nil {
t.Fatalf("build request: %v", err)
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
t.Fatalf("do request: %v", err)
}
return resp
}
func decodeJSON(t *testing.T, r io.Reader, out any) {
t.Helper()
data, err := io.ReadAll(r)
if err != nil {
t.Fatalf("read json: %v", err)
}
if err := json.Unmarshal(data, out); err != nil {
t.Fatalf("unmarshal json: %v\npayload: %s", err, string(data))
}
}