Platform coverage only selects TestCrossPlatformCoverage*; name the new declaration-only visibility regression accordingly and exercise the ForTest deps restore helper. Co-authored-by: Cursor <cursoragent@cursor.com>
164 lines
5.8 KiB
Go
164 lines
5.8 KiB
Go
// Copyright 2026 Alibaba Group
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
package app
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
|
|
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/cli"
|
|
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/helpers"
|
|
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/pkg/edition"
|
|
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/pkg/mcptypes"
|
|
"github.com/spf13/cobra"
|
|
)
|
|
|
|
// schemaSourceRootMarkerCaller is a recognizable ToolCaller used to detect
|
|
// InitDeps clobbering when NewSchemaSourceRootCommand builds the tree.
|
|
type schemaSourceRootMarkerCaller struct{}
|
|
|
|
func (schemaSourceRootMarkerCaller) CallTool(context.Context, string, string, map[string]any) (*edition.ToolResult, error) {
|
|
return &edition.ToolResult{}, nil
|
|
}
|
|
func (schemaSourceRootMarkerCaller) Format() string { return "json" }
|
|
func (schemaSourceRootMarkerCaller) DryRun() bool { return false }
|
|
func (schemaSourceRootMarkerCaller) Fields() string { return "" }
|
|
func (schemaSourceRootMarkerCaller) JQ() string { return "" }
|
|
|
|
// TestSchemaSourceRootPreservesRuntimeDepsAndPluginEndpoints ensures Schema
|
|
// assembly (NewSchemaSourceRootCommand / ResolveMeta delivery) does not call
|
|
// helpers.InitDeps or SetDynamicServers, which would wipe a live process's
|
|
// caller and plugin endpoints.
|
|
func TestSchemaSourceRootPreservesRuntimeDepsAndPluginEndpoints(t *testing.T) {
|
|
const (
|
|
pluginID = "schema-source-root-plugin-marker"
|
|
pluginEndpoint = "https://schema-source-root-plugin.example/mcp"
|
|
)
|
|
|
|
dynamicMu.RLock()
|
|
previousEndpoints := dynamicEndpoints
|
|
previousProducts := dynamicProducts
|
|
previousAliases := dynamicAliases
|
|
previousToolEndpoints := dynamicToolEndpoints
|
|
dynamicMu.RUnlock()
|
|
t.Cleanup(func() {
|
|
dynamicMu.Lock()
|
|
dynamicEndpoints = previousEndpoints
|
|
dynamicProducts = previousProducts
|
|
dynamicAliases = previousAliases
|
|
dynamicToolEndpoints = previousToolEndpoints
|
|
dynamicMu.Unlock()
|
|
})
|
|
|
|
marker := schemaSourceRootMarkerCaller{}
|
|
helpers.InitDepsForTest(t, marker)
|
|
SetDynamicServers(nil)
|
|
AppendDynamicServer(mcptypes.ServerDescriptor{
|
|
Key: pluginID,
|
|
Endpoint: pluginEndpoint,
|
|
CLI: mcptypes.CLIOverlay{
|
|
ID: pluginID,
|
|
Command: pluginID,
|
|
},
|
|
})
|
|
|
|
root := NewSchemaSourceRootCommand()
|
|
if root == nil {
|
|
t.Fatal("NewSchemaSourceRootCommand returned nil")
|
|
}
|
|
|
|
if got := helpers.GetCaller(); got != marker {
|
|
t.Fatalf("helpers.GetCaller() after NewSchemaSourceRootCommand = %T (%p), want marker caller preserved", got, got)
|
|
}
|
|
gotEndpoint, ok := directRuntimeEndpoint(pluginID, "")
|
|
if !ok || gotEndpoint != pluginEndpoint {
|
|
t.Fatalf("plugin endpoint after NewSchemaSourceRootCommand = %q ok=%v, want %q preserved", gotEndpoint, ok, pluginEndpoint)
|
|
}
|
|
|
|
resolved, err := cli.ResolveSchemaBuild(root)
|
|
if err != nil {
|
|
t.Fatalf("ResolveSchemaBuild after declaration-only root: %v", err)
|
|
}
|
|
if resolved.CommandCount() == 0 {
|
|
t.Fatal("ResolveSchemaBuild command count is 0")
|
|
}
|
|
|
|
// Delivery path also builds NewSchemaSourceRootCommand via the factory.
|
|
registerSchemaRuntimeDelivery()
|
|
meta, ok := cli.ResolveMeta("dev app delete")
|
|
if !ok || meta.Identity.Canonical == "" {
|
|
t.Fatalf("ResolveMeta after registerSchemaRuntimeDelivery = %#v ok=%v", meta, ok)
|
|
}
|
|
if got := helpers.GetCaller(); got != marker {
|
|
t.Fatalf("helpers.GetCaller() after ResolveMeta delivery = %T, want marker caller preserved", got)
|
|
}
|
|
gotEndpoint, ok = directRuntimeEndpoint(pluginID, "")
|
|
if !ok || gotEndpoint != pluginEndpoint {
|
|
t.Fatalf("plugin endpoint after ResolveMeta delivery = %q ok=%v, want %q preserved", gotEndpoint, ok, pluginEndpoint)
|
|
}
|
|
}
|
|
|
|
// TestCrossPlatformCoverageSchemaSourceRootStaticServerVisibilityWithoutInject
|
|
// proves the declaration-only Schema source root does not need
|
|
// injectStaticServers to keep StaticServers products visible. VisibleProducts
|
|
// may be unset; StaticServers alone must still prevent
|
|
// hideNonDirectRuntimeCommands from marking those products Hidden — without
|
|
// mutating dynamic endpoints.
|
|
func TestCrossPlatformCoverageSchemaSourceRootStaticServerVisibilityWithoutInject(t *testing.T) {
|
|
previous := edition.Get()
|
|
t.Cleanup(func() { edition.Override(previous) })
|
|
|
|
dynamicMu.RLock()
|
|
previousEndpoints := dynamicEndpoints
|
|
previousProducts := dynamicProducts
|
|
previousAliases := dynamicAliases
|
|
previousToolEndpoints := dynamicToolEndpoints
|
|
dynamicMu.RUnlock()
|
|
t.Cleanup(func() {
|
|
dynamicMu.Lock()
|
|
dynamicEndpoints = previousEndpoints
|
|
dynamicProducts = previousProducts
|
|
dynamicAliases = previousAliases
|
|
dynamicToolEndpoints = previousToolEndpoints
|
|
dynamicMu.Unlock()
|
|
})
|
|
|
|
edition.Override(&edition.Hooks{
|
|
Name: "schema-source-root-static-visibility",
|
|
StaticServers: func() []edition.ServerInfo {
|
|
return []edition.ServerInfo{
|
|
{ID: "", Name: "ignored-empty", Endpoint: "https://schema-source-root-static.example/empty"},
|
|
{ID: " ", Name: "ignored-blank", Endpoint: "https://schema-source-root-static.example/blank"},
|
|
{ID: "doc", Name: "Doc", Endpoint: "https://schema-source-root-static.example/doc"},
|
|
}
|
|
},
|
|
// VisibleProducts intentionally nil: declaration-only visibility must
|
|
// still derive from StaticServers without SetDynamicServers.
|
|
})
|
|
SetDynamicServers(nil)
|
|
|
|
root := NewSchemaSourceRootCommand()
|
|
var doc *cobra.Command
|
|
for _, cmd := range root.Commands() {
|
|
if cmd.Name() == "doc" {
|
|
doc = cmd
|
|
break
|
|
}
|
|
}
|
|
if doc == nil {
|
|
t.Fatal("doc command missing from Schema source root")
|
|
}
|
|
if doc.Hidden {
|
|
t.Fatal("declaration-only Schema source root hid doc despite StaticServers")
|
|
}
|
|
// Endpoint resolution may still read StaticServers directly; the invariant
|
|
// is that declaration-only construction must not mutate the dynamic registry.
|
|
dynamicMu.RLock()
|
|
_, injected := dynamicProducts["doc"]
|
|
dynamicMu.RUnlock()
|
|
if injected {
|
|
t.Fatal("declaration-only Schema source root must not inject StaticServers into dynamicProducts")
|
|
}
|
|
}
|