internal/mcptools/whoami_test.go
Rama main · réplica de lectura del repositorio original.
package mcptools
import (
"context"
"errors"
"testing"
"xprem/config"
"xprem/internal/services"
"github.com/modelcontextprotocol/go-sdk/auth"
mcpprot "github.com/modelcontextprotocol/go-sdk/mcp"
)
func callToolRequestFor(principal *services.DashboardPrincipal) *mcpprot.CallToolRequest {
extra := map[string]any{}
if principal != nil {
extra[services.PrincipalExtraKey] = principal
}
return &mcpprot.CallToolRequest{
Extra: &mcpprot.RequestExtra{
TokenInfo: &auth.TokenInfo{UserID: "user-1", Extra: extra},
},
}
}
type fakeAppLister struct{}
func (fakeAppLister) GetApps(context.Context) ([]config.AppDescriptor, error) {
// app-hidden exists but is not always visible: that is what lets the
// visibility tests refuse it for the right reason.
return []config.AppDescriptor{{Id: "app-1"}, {Id: "app-visible"}, {Id: "app-hidden"}}, nil
}
func testDeps() Deps {
return Deps{
Apps: fakeAppLister{},
VisibleApps: func(_ context.Context, _ *services.DashboardPrincipal) (bool, map[string]bool, error) {
return false, nil, nil
},
CanUseSomewhere: func(_ context.Context, principal *services.DashboardPrincipal, _ Access) bool {
return principal != nil && principal.IsAdmin
},
Authorize: func(_ context.Context, _ *services.DashboardPrincipal, _ string, _ Access) error {
return errors.New("denied")
},
DescribePermissions: func(_ context.Context, principal *services.DashboardPrincipal, appIDs []string) (AccountPermissions, error) {
apps := make([]AppPermissions, len(appIDs))
for i, appID := range appIDs {
apps[i] = AppPermissions{AppID: appID, Granted: []string{"observe:read"}, Denied: []string{"app:delete"}}
}
return AccountPermissions{Role: "member", Apps: apps}, nil
},
}
}
// registeredTools builds a session server through Configurator and lists its
// tools through a real in-memory client, the same path tools/list takes.
func registeredTools(t *testing.T, deps Deps, principal *services.DashboardPrincipal) map[string]bool {
t.Helper()
ctx := context.Background()
server := mcpprot.NewServer(&mcpprot.Implementation{Name: "test", Version: "0"}, nil)
Configurator(deps)(ctx, principal, server)
clientTransport, serverTransport := mcpprot.NewInMemoryTransports()
if _, err := server.Connect(ctx, serverTransport, nil); err != nil {
t.Fatal(err)
}
session, err := mcpprot.NewClient(&mcpprot.Implementation{Name: "test-client", Version: "0"}, nil).Connect(ctx, clientTransport, nil)
if err != nil {
t.Fatal(err)
}
defer session.Close()
list, err := session.ListTools(ctx, nil)
if err != nil {
t.Fatal(err)
}
names := map[string]bool{}
for _, tool := range list.Tools {
names[tool.Name] = true
}
return names
}
func TestWhoami(t *testing.T) {
principal := &services.DashboardPrincipal{UserId: "user-1", Email: "a@b.c"}
// Through the real handler and the real Extra-key contract with the OAuth
// verifier: a drift on either side of the key fails here.
_, output, err := whoamiHandler(testDeps())(context.Background(), callToolRequestFor(principal), struct{}{})
if err != nil {
t.Fatal(err)
}
if output.UserId != "user-1" || output.Email != "a@b.c" {
t.Errorf("unexpected identity: %+v", output)
}
// One permission entry per app the fake lister reports, each carrying the
// full granted/denied picture.
permissions := output.Permissions
if permissions.Role != "member" || len(permissions.Apps) != 3 {
t.Fatalf("unexpected description: %+v", permissions)
}
if permissions.Apps[0].AppID != "app-1" || len(permissions.Apps[0].Granted) != 1 || len(permissions.Apps[0].Denied) != 1 {
t.Errorf("unexpected app entry: %+v", permissions.Apps[0])
}
}
func TestWhoamiWithoutPrincipal(t *testing.T) {
for name, req := range map[string]*mcpprot.CallToolRequest{
"no token info": {Extra: &mcpprot.RequestExtra{}},
"no extra": {},
"no principal": callToolRequestFor(nil),
} {
t.Run(name, func(t *testing.T) {
if _, _, err := whoamiHandler(testDeps())(context.Background(), req, struct{}{}); err == nil {
t.Fatal("expected an error without a principal")
}
})
}
}
func TestConfiguratorFiltersGatedTools(t *testing.T) {
deps := testDeps()
gated := Access{Perm: "update:publish", Fallback: FallbackAdminOnly}
registrations = append(registrations, struct {
register func(*mcpprot.Server, Deps)
access *Access
}{
register: func(server *mcpprot.Server, deps Deps) {
mcpprot.AddTool(server, &mcpprot.Tool{Name: "gated_tool", Description: "test"},
func(_ context.Context, _ *mcpprot.CallToolRequest, _ struct{}) (*mcpprot.CallToolResult, struct{}, error) {
return nil, struct{}{}, nil
})
},
access: &gated,
})
defer func() { registrations = registrations[:len(registrations)-1] }()
admin := &services.DashboardPrincipal{UserId: "admin-1", IsAdmin: true}
member := &services.DashboardPrincipal{UserId: "user-1"}
adminTools := registeredTools(t, deps, admin)
if !adminTools["whoami"] || !adminTools["gated_tool"] {
t.Errorf("admin must see every tool, got %v", adminTools)
}
memberTools := registeredTools(t, deps, member)
if !memberTools["whoami"] || memberTools["gated_tool"] {
t.Errorf("member must not see the gated tool, got %v", memberTools)
}
}