- Add gRPC server configuration to the application, allowing for gRPC communication. - Introduce new Makefile targets for gRPC unit testing and proto code generation. - Update CI workflows to include gRPC tests with SQLite as the transport layer. - Refactor the sandbox design to support multi-node capabilities and improve isolation. - Enhance the service layer to facilitate internal request forwarding for gRPC APIs. This commit lays the groundwork for integrating gRPC into the Yao SDK, improving performance and scalability.
264 lines
7 KiB
Go
264 lines
7 KiB
Go
package mcp_test
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"google.golang.org/grpc/codes"
|
|
"google.golang.org/grpc/status"
|
|
|
|
"github.com/yaoapp/yao/grpc/pb"
|
|
"github.com/yaoapp/yao/grpc/tests/testutils"
|
|
)
|
|
|
|
const echoSession = "echo"
|
|
|
|
// --- MCPListTools ---
|
|
|
|
func TestMCPListTools_Success(t *testing.T) {
|
|
conn := testutils.Prepare(t)
|
|
defer testutils.Clean()
|
|
|
|
client := testutils.NewClient(conn)
|
|
token := testutils.ObtainAccessToken(t, "grpc:mcp")
|
|
ctx := testutils.WithToken(context.Background(), token)
|
|
|
|
resp, err := client.MCPListTools(ctx, &pb.MCPListRequest{
|
|
SessionId: echoSession,
|
|
})
|
|
assert.NoError(t, err)
|
|
if assert.NotNil(t, resp) {
|
|
assert.NotEmpty(t, resp.Tools)
|
|
|
|
var tools []map[string]interface{}
|
|
err := json.Unmarshal(resp.Tools, &tools)
|
|
assert.NoError(t, err)
|
|
assert.GreaterOrEqual(t, len(tools), 3, "echo MCP defines ping, status, echo")
|
|
|
|
names := make(map[string]bool)
|
|
for _, tool := range tools {
|
|
if n, ok := tool["name"].(string); ok {
|
|
names[n] = true
|
|
}
|
|
}
|
|
assert.True(t, names["ping"], "should contain ping tool")
|
|
assert.True(t, names["status"], "should contain status tool")
|
|
assert.True(t, names["echo"], "should contain echo tool")
|
|
}
|
|
}
|
|
|
|
func TestMCPListTools_InvalidSession(t *testing.T) {
|
|
conn := testutils.Prepare(t)
|
|
defer testutils.Clean()
|
|
|
|
client := testutils.NewClient(conn)
|
|
token := testutils.ObtainAccessToken(t, "grpc:mcp")
|
|
ctx := testutils.WithToken(context.Background(), token)
|
|
|
|
_, err := client.MCPListTools(ctx, &pb.MCPListRequest{
|
|
SessionId: "nonexistent-session",
|
|
})
|
|
assert.Error(t, err)
|
|
st, _ := status.FromError(err)
|
|
assert.Equal(t, codes.NotFound, st.Code())
|
|
}
|
|
|
|
// --- MCPCallTool ---
|
|
|
|
func TestMCPCallTool_Ping(t *testing.T) {
|
|
conn := testutils.Prepare(t)
|
|
defer testutils.Clean()
|
|
|
|
client := testutils.NewClient(conn)
|
|
token := testutils.ObtainAccessToken(t, "grpc:mcp")
|
|
ctx := testutils.WithToken(context.Background(), token)
|
|
|
|
args, _ := json.Marshal(map[string]interface{}{"count": 2, "message": "ping"})
|
|
resp, err := client.MCPCallTool(ctx, &pb.MCPCallRequest{
|
|
SessionId: echoSession,
|
|
Tool: "ping",
|
|
Arguments: args,
|
|
})
|
|
assert.NoError(t, err)
|
|
if assert.NotNil(t, resp) {
|
|
assert.NotEmpty(t, resp.Result)
|
|
var result map[string]interface{}
|
|
err := json.Unmarshal(resp.Result, &result)
|
|
assert.NoError(t, err)
|
|
}
|
|
}
|
|
|
|
func TestMCPCallTool_Echo(t *testing.T) {
|
|
conn := testutils.Prepare(t)
|
|
defer testutils.Clean()
|
|
|
|
client := testutils.NewClient(conn)
|
|
token := testutils.ObtainAccessToken(t, "grpc:mcp")
|
|
ctx := testutils.WithToken(context.Background(), token)
|
|
|
|
args, _ := json.Marshal(map[string]interface{}{"message": "hello", "uppercase": true})
|
|
resp, err := client.MCPCallTool(ctx, &pb.MCPCallRequest{
|
|
SessionId: echoSession,
|
|
Tool: "echo",
|
|
Arguments: args,
|
|
})
|
|
assert.NoError(t, err)
|
|
if assert.NotNil(t, resp) {
|
|
assert.NotEmpty(t, resp.Result)
|
|
}
|
|
}
|
|
|
|
func TestMCPCallTool_NilArgs(t *testing.T) {
|
|
conn := testutils.Prepare(t)
|
|
defer testutils.Clean()
|
|
|
|
client := testutils.NewClient(conn)
|
|
token := testutils.ObtainAccessToken(t, "grpc:mcp")
|
|
ctx := testutils.WithToken(context.Background(), token)
|
|
|
|
resp, err := client.MCPCallTool(ctx, &pb.MCPCallRequest{
|
|
SessionId: echoSession,
|
|
Tool: "ping",
|
|
Arguments: nil,
|
|
})
|
|
assert.NoError(t, err)
|
|
if assert.NotNil(t, resp) {
|
|
assert.NotEmpty(t, resp.Result)
|
|
}
|
|
}
|
|
|
|
func TestMCPCallTool_InvalidSession(t *testing.T) {
|
|
conn := testutils.Prepare(t)
|
|
defer testutils.Clean()
|
|
|
|
client := testutils.NewClient(conn)
|
|
token := testutils.ObtainAccessToken(t, "grpc:mcp")
|
|
ctx := testutils.WithToken(context.Background(), token)
|
|
|
|
_, err := client.MCPCallTool(ctx, &pb.MCPCallRequest{
|
|
SessionId: "nonexistent-session",
|
|
Tool: "some-tool",
|
|
})
|
|
assert.Error(t, err)
|
|
st, _ := status.FromError(err)
|
|
assert.Equal(t, codes.NotFound, st.Code())
|
|
}
|
|
|
|
func TestMCPCallTool_BadArgs(t *testing.T) {
|
|
conn := testutils.Prepare(t)
|
|
defer testutils.Clean()
|
|
|
|
client := testutils.NewClient(conn)
|
|
token := testutils.ObtainAccessToken(t, "grpc:mcp")
|
|
ctx := testutils.WithToken(context.Background(), token)
|
|
|
|
_, err := client.MCPCallTool(ctx, &pb.MCPCallRequest{
|
|
SessionId: echoSession,
|
|
Tool: "ping",
|
|
Arguments: []byte("{not-json"),
|
|
})
|
|
assert.Error(t, err)
|
|
st, _ := status.FromError(err)
|
|
assert.Equal(t, codes.InvalidArgument, st.Code())
|
|
}
|
|
|
|
// --- MCPListResources ---
|
|
|
|
func TestMCPListResources_Success(t *testing.T) {
|
|
conn := testutils.Prepare(t)
|
|
defer testutils.Clean()
|
|
|
|
client := testutils.NewClient(conn)
|
|
token := testutils.ObtainAccessToken(t, "grpc:mcp")
|
|
ctx := testutils.WithToken(context.Background(), token)
|
|
|
|
resp, err := client.MCPListResources(ctx, &pb.MCPListRequest{
|
|
SessionId: echoSession,
|
|
})
|
|
assert.NoError(t, err)
|
|
if assert.NotNil(t, resp) {
|
|
assert.NotEmpty(t, resp.Resources)
|
|
|
|
var resources []map[string]interface{}
|
|
err := json.Unmarshal(resp.Resources, &resources)
|
|
assert.NoError(t, err)
|
|
assert.GreaterOrEqual(t, len(resources), 2, "echo MCP defines info and health resources")
|
|
}
|
|
}
|
|
|
|
func TestMCPListResources_InvalidSession(t *testing.T) {
|
|
conn := testutils.Prepare(t)
|
|
defer testutils.Clean()
|
|
|
|
client := testutils.NewClient(conn)
|
|
token := testutils.ObtainAccessToken(t, "grpc:mcp")
|
|
ctx := testutils.WithToken(context.Background(), token)
|
|
|
|
_, err := client.MCPListResources(ctx, &pb.MCPListRequest{
|
|
SessionId: "nonexistent-session",
|
|
})
|
|
assert.Error(t, err)
|
|
st, _ := status.FromError(err)
|
|
assert.Equal(t, codes.NotFound, st.Code())
|
|
}
|
|
|
|
// --- MCPReadResource ---
|
|
|
|
func TestMCPReadResource_Success(t *testing.T) {
|
|
conn := testutils.Prepare(t)
|
|
defer testutils.Clean()
|
|
|
|
client := testutils.NewClient(conn)
|
|
token := testutils.ObtainAccessToken(t, "grpc:mcp")
|
|
ctx := testutils.WithToken(context.Background(), token)
|
|
|
|
resp, err := client.MCPReadResource(ctx, &pb.MCPResourceRequest{
|
|
SessionId: echoSession,
|
|
Uri: "echo://info",
|
|
})
|
|
assert.NoError(t, err)
|
|
if assert.NotNil(t, resp) {
|
|
assert.NotEmpty(t, resp.Contents)
|
|
|
|
var contents []map[string]interface{}
|
|
err := json.Unmarshal(resp.Contents, &contents)
|
|
assert.NoError(t, err)
|
|
assert.Greater(t, len(contents), 0)
|
|
}
|
|
}
|
|
|
|
func TestMCPReadResource_InvalidSession(t *testing.T) {
|
|
conn := testutils.Prepare(t)
|
|
defer testutils.Clean()
|
|
|
|
client := testutils.NewClient(conn)
|
|
token := testutils.ObtainAccessToken(t, "grpc:mcp")
|
|
ctx := testutils.WithToken(context.Background(), token)
|
|
|
|
_, err := client.MCPReadResource(ctx, &pb.MCPResourceRequest{
|
|
SessionId: "nonexistent-session",
|
|
Uri: "echo://info",
|
|
})
|
|
assert.Error(t, err)
|
|
st, _ := status.FromError(err)
|
|
assert.Equal(t, codes.NotFound, st.Code())
|
|
}
|
|
|
|
func TestMCPReadResource_NotFoundURI(t *testing.T) {
|
|
conn := testutils.Prepare(t)
|
|
defer testutils.Clean()
|
|
|
|
client := testutils.NewClient(conn)
|
|
token := testutils.ObtainAccessToken(t, "grpc:mcp")
|
|
ctx := testutils.WithToken(context.Background(), token)
|
|
|
|
_, err := client.MCPReadResource(ctx, &pb.MCPResourceRequest{
|
|
SessionId: echoSession,
|
|
Uri: "echo://nonexistent",
|
|
})
|
|
assert.Error(t, err)
|
|
st, _ := status.FromError(err)
|
|
assert.Equal(t, codes.Internal, st.Code())
|
|
}
|