internal/mcptools/tools_test.go
Rama main · réplica de lectura del repositorio original.
package mcptools
import (
"context"
"errors"
"strings"
"testing"
"time"
"xprem/internal/services"
"xprem/internal/types"
)
type fakeReadServices struct{}
func (fakeReadServices) GetBranches(_ context.Context, _ string) ([]types.BranchMapping, error) {
mainId, stagingId := "branch-id-1", "branch-id-2"
return []types.BranchMapping{
{BranchName: "main", BranchId: &mainId},
{BranchName: "staging", BranchId: &stagingId},
}, nil
}
func (fakeReadServices) GetRuntimeVersionsWithUpdateStats(_ context.Context, _ string, _ string) ([]types.RuntimeVersionWithStats, error) {
return []types.RuntimeVersionWithStats{{RuntimeVersion: "1.0.0", NumberOfUpdates: 3}}, nil
}
func (fakeReadServices) GetChannels(_ context.Context, _ string) ([]types.ChannelMapping, error) {
return []types.ChannelMapping{
{ReleaseChannelName: "production", ReleaseChannelId: "channel-id-1", Rollout: &types.ChannelRollout{ChannelName: "production", Percentage: 25}},
{ReleaseChannelName: "staging", ReleaseChannelId: "channel-id-2"},
}, nil
}
func (fakeReadServices) GetUpdateFeed(_ context.Context, _ string, query types.UpdateFeedQuery) ([]types.UpdateFeedItem, error) {
// Always a full page plus one, so pagination has a next page to signal.
items := make([]types.UpdateFeedItem, query.Limit)
for i := range items {
items[i].UpdateId = "1"
items[i].BranchID = 7
items[i].FeedCreatedAt = time.Date(2026, 7, 30, 0, 0, 0, 0, time.UTC).Add(-time.Duration(i) * time.Minute)
}
return items, nil
}
func (fakeReadServices) GetUpdateRollout(_ context.Context, _ string, _ string, _ string) ([]types.RolloutUpdate, error) {
return []types.RolloutUpdate{{UpdateId: "u1", Platform: "ios", Percentage: 50}}, nil
}
func (fakeReadServices) ListPatches(_ context.Context, _ string, _ string, _ string) ([]types.BundlePatch, error) {
return []types.BundlePatch{{TargetUpdateId: "2", SourceUpdateId: "1", Status: types.BundlePatchStored}}, nil
}
func (fakeReadServices) RetrieveAppCertificate(_ context.Context, _ string) (string, error) {
return "-----BEGIN CERTIFICATE-----", nil
}
func readDeps() Deps {
deps := testDeps()
fake := fakeReadServices{}
deps.Branches = fake
deps.Channels = fake
deps.UpdateFeed = fake
deps.UpdateRollouts = fake
deps.BundlePatches = fake
deps.Certificates = fake
deps.SSOEnabled = func(context.Context) bool { return false }
return deps
}
// Every app-scoped tool must refuse an invisible app with the 404 answer and
// an empty appId with the guidance message.
func TestAppScopedToolsRequireVisibleApp(t *testing.T) {
deps := readDeps()
deps.VisibleApps = func(_ context.Context, _ *services.DashboardPrincipal) (bool, map[string]bool, error) {
return true, map[string]bool{"app-visible": true}, nil
}
principal := &services.DashboardPrincipal{UserId: "user-1"}
req := callToolRequestFor(principal)
ctx := context.Background()
calls := map[string]func(appID string) error{
"get_branches": func(appID string) error {
_, _, err := getBranchesHandler(deps)(ctx, req, GetBranchesInput{AppId: appID})
return err
},
"get_runtime_versions": func(appID string) error {
_, _, err := getRuntimeVersionsHandler(deps)(ctx, req, GetRuntimeVersionsInput{AppId: appID, Branch: "main"})
return err
},
"get_channels": func(appID string) error {
_, _, err := getChannelsHandler(deps)(ctx, req, GetChannelsInput{AppId: appID})
return err
},
"get_channel_rollouts": func(appID string) error {
_, _, err := getChannelRolloutsHandler(deps)(ctx, req, GetChannelsInput{AppId: appID})
return err
},
"get_updates": func(appID string) error {
_, _, err := getUpdatesHandler(deps)(ctx, req, GetUpdatesInput{AppId: appID})
return err
},
"get_update_rollout": func(appID string) error {
_, _, err := getUpdateRolloutHandler(deps)(ctx, req, GetUpdateRolloutInput{AppId: appID, Branch: "main", RuntimeVersion: "1.0.0"})
return err
},
"get_update_patches": func(appID string) error {
_, _, err := getUpdatePatchesHandler(deps)(ctx, req, GetUpdatePatchesInput{AppId: appID, Branch: "main", UpdateId: "2"})
return err
},
}
for name, call := range calls {
t.Run(name, func(t *testing.T) {
if err := call("app-visible"); err != nil {
t.Fatalf("visible app must pass, got %v", err)
}
err := call("app-hidden")
if err == nil || err.Error() != ErrAppNotFound.Error() {
t.Fatalf("invisible app must read as 404, got %v", err)
}
if err := call(""); err == nil || !strings.Contains(err.Error(), "appId is required") {
t.Fatalf("empty appId must guide to get_apps, got %v", err)
}
})
}
}
// An app id that does not exist must read as not found for every account,
// including an admin and any deployment where roles are not enforced: those
// report restricted=false, so visibility alone cannot answer.
func TestAppScopedToolsRefuseUnknownApp(t *testing.T) {
deps := readDeps()
deps.VisibleApps = func(_ context.Context, _ *services.DashboardPrincipal) (bool, map[string]bool, error) {
return false, nil, nil
}
admin := &services.DashboardPrincipal{UserId: "admin-1", IsAdmin: true}
_, _, err := getBranchesHandler(deps)(context.Background(), callToolRequestFor(admin), GetBranchesInput{AppId: "does-not-exist"})
if err == nil || err.Error() != ErrAppNotFound.Error() {
t.Fatalf("an unknown app must be refused, got %v", err)
}
if _, _, err := getBranchesHandler(deps)(context.Background(), callToolRequestFor(admin), GetBranchesInput{AppId: "app-1"}); err != nil {
t.Fatalf("a known app must pass, got %v", err)
}
}
func TestGetBranchesNameFilter(t *testing.T) {
deps := readDeps()
principal := &services.DashboardPrincipal{UserId: "user-1"}
_, output, err := getBranchesHandler(deps)(context.Background(), callToolRequestFor(principal), GetBranchesInput{AppId: "app-1", Name: "MAIN"})
if err != nil {
t.Fatal(err)
}
if len(output.Branches) != 1 || output.Branches[0].BranchName != "main" {
t.Fatalf("expected the main branch only, got %+v", output.Branches)
}
}
func TestChannelLookupById(t *testing.T) {
deps := readDeps()
principal := &services.DashboardPrincipal{UserId: "user-1"}
_, output, err := getChannelsHandler(deps)(context.Background(), callToolRequestFor(principal), GetChannelsInput{AppId: "app-1", Id: "channel-id-1"})
if err != nil {
t.Fatal(err)
}
if len(output.Channels) != 1 || output.Channels[0].ReleaseChannelName != "production" {
t.Fatalf("expected production by id, got %+v", output.Channels)
}
}
func TestGetChannelRolloutsOnlyActive(t *testing.T) {
deps := readDeps()
principal := &services.DashboardPrincipal{UserId: "user-1"}
_, output, err := getChannelRolloutsHandler(deps)(context.Background(), callToolRequestFor(principal), GetChannelsInput{AppId: "app-1"})
if err != nil {
t.Fatal(err)
}
if len(output.Rollouts) != 1 || output.Rollouts[0].ChannelName != "production" || output.Rollouts[0].Rollout.Percentage != 25 {
t.Fatalf("expected the production rollout only, got %+v", output.Rollouts)
}
}
func TestGetUpdatesPagination(t *testing.T) {
deps := readDeps()
principal := &services.DashboardPrincipal{UserId: "user-1"}
req := callToolRequestFor(principal)
ctx := context.Background()
_, output, err := getUpdatesHandler(deps)(ctx, req, GetUpdatesInput{AppId: "app-1", Limit: 5})
if err != nil {
t.Fatal(err)
}
if len(output.Updates) != 5 {
t.Fatalf("expected the page capped at 5, got %d", len(output.Updates))
}
if output.NextCursor == "" {
t.Fatal("a full page plus one must signal a next page")
}
// The cursor round-trips into the next query.
if _, _, err := getUpdatesHandler(deps)(ctx, req, GetUpdatesInput{AppId: "app-1", Limit: 5, Cursor: output.NextCursor}); err != nil {
t.Fatalf("nextCursor must be accepted back, got %v", err)
}
if _, _, err := getUpdatesHandler(deps)(ctx, req, GetUpdatesInput{AppId: "app-1", Cursor: "not-a-cursor!!"}); err == nil {
t.Fatal("a malformed cursor must be refused")
}
}
func TestGetCertificateAuthorize(t *testing.T) {
deps := readDeps()
principal := &services.DashboardPrincipal{UserId: "user-1"}
// The deps' Authorize fake denies everything: the tool must refuse.
if _, _, err := getCertificateHandler(deps)(context.Background(), callToolRequestFor(principal), GetCertificateInput{AppId: "app-1"}); err == nil {
t.Fatal("expected the authorization denial to propagate")
}
deps.Authorize = func(_ context.Context, _ *services.DashboardPrincipal, appID string, access Access) error {
if appID != "app-1" || access.Perm != "certificate:read" {
return errors.New("wrong authorization request")
}
return nil
}
_, output, err := getCertificateHandler(deps)(context.Background(), callToolRequestFor(principal), GetCertificateInput{AppId: "app-1"})
if err != nil {
t.Fatal(err)
}
if !strings.HasPrefix(output.CertificatePEM, "-----BEGIN CERTIFICATE-----") {
t.Fatalf("expected a PEM, got %q", output.CertificatePEM)
}
}