package bnp import ( "path/filepath" "strings" "testing" abiv1 "git.dev.alexdunmow.com/block/pluginsdk/abi/v1" "google.golang.org/protobuf/proto" ) func wikiMCPManifest(hasHTTPHandler bool) *abiv1.PluginManifest { procedure := "/wiki.v1.WikiService/SaveArticle" return &abiv1.PluginManifest{ AbiVersion: 1, Name: "wiki", Version: "0.1.12", HasHttpHandler: hasHTTPHandler, RbacMethodRoles: map[string]string{procedure: "admin"}, McpTools: []*abiv1.MCPToolDescriptor{{ Procedure: procedure, ServiceFullName: "wiki.v1.WikiService", MethodName: "SaveArticle", InputSchemaJson: []byte(`{"type":"object","properties":{"article":{"type":"object"}}}`), }}, } } func TestValidateMCPManifestAcceptsCallableWikiDescriptor(t *testing.T) { manifest := wikiMCPManifest(true) if err := ValidateMCPManifest(manifest, "@ninja", "wiki"); err != nil { t.Fatalf("ValidateMCPManifest() error = %v", err) } name, err := mcpToolName("@ninja", "wiki", "wiki.v1.WikiService", "SaveArticle") if err != nil { t.Fatalf("mcpToolName() error = %v", err) } if name != "plugin_ninja_wiki_v1_save_article" { t.Fatalf("tool name = %q", name) } } func TestValidateMCPManifestRejectsMissingRuntimeHandler(t *testing.T) { err := ValidateMCPManifest(wikiMCPManifest(false), "@ninja", "wiki") if err == nil || !strings.Contains(err.Error(), "has_http_handler") { t.Fatalf("error = %v, want has_http_handler rejection", err) } } func TestValidateMCPManifestRejectsMalformedDescriptors(t *testing.T) { tests := []struct { name string mutate func(*abiv1.PluginManifest) want string }{ { name: "unmapped procedure", mutate: func(manifest *abiv1.PluginManifest) { manifest.RbacMethodRoles = nil }, want: "RBAC role", }, { name: "invalid schema", mutate: func(manifest *abiv1.PluginManifest) { manifest.McpTools[0].InputSchemaJson = []byte(`{"type":"string"}`) }, want: "input schema", }, { name: "missing API major", mutate: func(manifest *abiv1.PluginManifest) { descriptor := manifest.McpTools[0] descriptor.Procedure = "/wiki.WikiService/SaveArticle" descriptor.ServiceFullName = "wiki.WikiService" manifest.RbacMethodRoles = map[string]string{descriptor.Procedure: "admin"} }, want: "API major", }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { manifest := wikiMCPManifest(true) test.mutate(manifest) err := ValidateMCPManifest(manifest, "@ninja", "wiki") if err == nil || !strings.Contains(err.Error(), test.want) { t.Fatalf("error = %v, want %q", err, test.want) } }) } } func TestVerifyRejectsMCPDescriptorsWithoutRuntimeHandler(t *testing.T) { manifestBytes, err := proto.Marshal(wikiMCPManifest(false)) if err != nil { t.Fatalf("marshal manifest: %v", err) } out := filepath.Join(t.TempDir(), "wiki.bnp") _, err = packArtifact(out, []packEntry{ {ArtifactPath: fileWasm, Data: []byte("wasm")}, {ArtifactPath: fileMod, Data: []byte("[plugin]\nname = \"wiki\"\nscope = \"@ninja\"\nversion = \"0.1.12\"\n")}, {ArtifactPath: fileManifest, Data: manifestBytes}, }) if err != nil { t.Fatalf("pack artifact: %v", err) } if _, err := Verify(out); err == nil || !strings.Contains(err.Error(), "has_http_handler") { t.Fatalf("Verify() error = %v, want has_http_handler rejection", err) } }