101 lines
4.3 KiB
Go
101 lines
4.3 KiB
Go
package wasmguest
|
|
|
|
import (
|
|
"encoding/json"
|
|
"testing"
|
|
|
|
"git.dev.alexdunmow.com/block/pluginsdk/rbac"
|
|
"google.golang.org/protobuf/proto"
|
|
"google.golang.org/protobuf/reflect/protodesc"
|
|
"google.golang.org/protobuf/reflect/protoregistry"
|
|
"google.golang.org/protobuf/types/descriptorpb"
|
|
_ "google.golang.org/protobuf/types/known/timestamppb"
|
|
)
|
|
|
|
func TestCaptureMCPToolsUsesCompiledProtobufDescriptor(t *testing.T) {
|
|
registerMCPFixture(t)
|
|
procedure := "/mcpfixture.v1.WikiService/ListArticles"
|
|
tools := captureMCPTools(map[string]rbac.Role{procedure: rbac.RoleAdmin})
|
|
if len(tools) != 1 {
|
|
t.Fatalf("captureMCPTools() returned %d tools, want 1", len(tools))
|
|
}
|
|
tool := tools[0]
|
|
if tool.GetProcedure() != procedure || tool.GetServiceFullName() != "mcpfixture.v1.WikiService" || tool.GetMethodName() != "ListArticles" {
|
|
t.Fatalf("unexpected procedure metadata: %#v", tool)
|
|
}
|
|
if tool.GetDescription() != "Lists Wiki articles." || tool.GetDocumentation() != "ListArticles lists Wiki articles.\n\nSupports pagination." {
|
|
t.Fatalf("unexpected descriptor documentation: description=%q doc=%q", tool.GetDescription(), tool.GetDocumentation())
|
|
}
|
|
var schema map[string]any
|
|
if err := json.Unmarshal(tool.GetInputSchemaJson(), &schema); err != nil {
|
|
t.Fatalf("schema is not JSON: %v", err)
|
|
}
|
|
properties, ok := schema["properties"].(map[string]any)
|
|
if !ok {
|
|
t.Fatalf("schema properties = %#v", schema["properties"])
|
|
}
|
|
assertSchemaType(t, properties, "search", "string")
|
|
assertSchemaType(t, properties, "page", "integer")
|
|
assertSchemaType(t, properties, "published_after", "string")
|
|
}
|
|
|
|
func TestCaptureMCPToolsSkipsUnresolvableAndStreamingProcedures(t *testing.T) {
|
|
registerMCPFixture(t)
|
|
tools := captureMCPTools(map[string]rbac.Role{
|
|
"/mcpfixture.v1.WikiService/StreamArticles": rbac.RoleAdmin,
|
|
"/missing.v1.WikiService/ListArticles": rbac.RoleAdmin,
|
|
"not-a-procedure": rbac.RoleAdmin,
|
|
})
|
|
if len(tools) != 0 {
|
|
t.Fatalf("captureMCPTools() returned %d tools, want fail-closed omission", len(tools))
|
|
}
|
|
}
|
|
|
|
func assertSchemaType(t *testing.T, properties map[string]any, field, expected string) {
|
|
t.Helper()
|
|
property, ok := properties[field].(map[string]any)
|
|
if !ok || property["type"] != expected {
|
|
t.Fatalf("schema field %q = %#v, want type %q", field, properties[field], expected)
|
|
}
|
|
}
|
|
|
|
func registerMCPFixture(t *testing.T) {
|
|
t.Helper()
|
|
if _, err := protoregistry.GlobalFiles.FindFileByPath("test/mcpfixture.proto"); err == nil {
|
|
return
|
|
}
|
|
file, err := protodesc.NewFile(&descriptorpb.FileDescriptorProto{
|
|
Name: new("test/mcpfixture.proto"),
|
|
Package: new("mcpfixture.v1"),
|
|
Syntax: new("proto3"),
|
|
Dependency: []string{"google/protobuf/timestamp.proto"},
|
|
MessageType: []*descriptorpb.DescriptorProto{{
|
|
Name: new("ListArticlesRequest"),
|
|
Field: []*descriptorpb.FieldDescriptorProto{
|
|
{Name: new("search"), Number: proto.Int32(1), Label: descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(), Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum()},
|
|
{Name: new("page"), Number: proto.Int32(2), Label: descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(), Type: descriptorpb.FieldDescriptorProto_TYPE_INT32.Enum()},
|
|
{Name: new("published_after"), Number: proto.Int32(3), Label: descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(), Type: descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(), TypeName: new(".google.protobuf.Timestamp")},
|
|
},
|
|
}, {
|
|
Name: new("ListArticlesResponse"),
|
|
}},
|
|
Service: []*descriptorpb.ServiceDescriptorProto{{
|
|
Name: new("WikiService"),
|
|
Method: []*descriptorpb.MethodDescriptorProto{{
|
|
Name: new("ListArticles"), InputType: new(".mcpfixture.v1.ListArticlesRequest"), OutputType: new(".mcpfixture.v1.ListArticlesResponse"),
|
|
}, {
|
|
Name: new("StreamArticles"), InputType: new(".mcpfixture.v1.ListArticlesRequest"), OutputType: new(".mcpfixture.v1.ListArticlesResponse"), ServerStreaming: new(true),
|
|
}},
|
|
}},
|
|
SourceCodeInfo: &descriptorpb.SourceCodeInfo{Location: []*descriptorpb.SourceCodeInfo_Location{{
|
|
Path: []int32{6, 0, 2, 0}, Span: []int32{1, 0, 1, 1}, LeadingComments: new("ListArticles lists Wiki articles.\n\nSupports pagination."),
|
|
}}},
|
|
}, protoregistry.GlobalFiles)
|
|
if err != nil {
|
|
t.Fatalf("create fixture descriptor: %v", err)
|
|
}
|
|
if err := protoregistry.GlobalFiles.RegisterFile(file); err != nil {
|
|
t.Fatalf("register fixture descriptor: %v", err)
|
|
}
|
|
}
|