Files
dingtalk-workspace-cli/internal/shortcut/adapter_test.go
dxy704330469 eb0bd69b82 feat(doc): add reviewed document shortcuts
- add 45 public document shortcuts and 2 reviewed expert-only paths
- preserve six historical command and Schema identities alongside canonical leaves
- add safe local download primitives and document access/share orchestration
- keep comment create/reply confirmation backward-compatible
- ensure grant-and-share upgrades insufficient roles before messaging
- return non-zero partial/failure message ledgers and structured partial-write recovery metadata
- enumerate every selection candidate and use rune-safe Unicode keyword contexts
- assert zero-call confirmation boundaries for destructive shortcuts

Validation:
- full Go test suite and repository policy
- real DingTalk E2E for 34 canonical shortcuts, all 8 compatibility-affected entries, READER-to-EDITOR grant-and-share upgrade, and same-block selection ambiguity with zero comment writes
- command compatibility across 1,221 historical nodes and complete Schema compatibility
- 1,152 Agent examples including 62 real Cobra dry-runs
- 100% changed-code coverage across 1,260 executable statements
2026-08-06 09:56:58 +08:00

308 lines
12 KiB
Go

// Copyright 2026 Alibaba Group
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package shortcut
import (
"strings"
"testing"
"github.com/spf13/pflag"
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/corecmd"
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/corecmd/contract"
)
// TestCrossPlatformCoverageFromShortcutMapsSharedBase verifies FromShortcut
// projects the complete live Shortcut surface into command.
func TestCrossPlatformCoverageFromShortcutMapsSharedBase(t *testing.T) {
s := Shortcut{
Service: "chat",
Command: "+demo",
Description: "演示",
Risk: RiskHighWrite,
Hidden: true,
Tips: []string{"dws chat +demo --name a"},
Flags: []Flag{
{Name: "name", Shorthand: "n", Type: FlagString, Desc: "名称", Required: true, Default: "d", Enum: []string{"a", "b"}, Hidden: true},
{Name: "count", Type: FlagInt, Desc: "数量"},
{Name: "flag", Type: FlagBool, Desc: "开关"},
{Name: "ids", Type: FlagStringSlice, Desc: "列表"},
{Name: "note", Desc: "空类型默认 string"},
},
Constraints: []Constraint{
{Kind: ConstraintExactlyOne, Flags: []string{"name", "count"}, Description: "二选一"},
{Kind: ConstraintAtLeastOne, Flags: []string{"flag", "ids"}},
{Kind: ConstraintMutuallyExclusive, Flags: []string{"name", "flag"}},
{Kind: ConstraintCustom, Flags: []string{"note"}, Description: "自定义由 Validate 保证"},
},
Validate: func(*RuntimeContext) error { return nil },
}
cs := FromShortcut(s)
if cs.Use != "+demo" || cs.Short != "演示" {
t.Fatalf("identity = %q/%q", cs.Use, cs.Short)
}
if !cs.Hidden || cs.Example != " dws chat +demo --name a" {
t.Fatalf("hidden/example = %v/%q", cs.Hidden, cs.Example)
}
// Long carries ONLY the intent prose: corecmd.New renders the
// 参数约束 section, so it must not already be present here.
if strings.Contains(cs.Long, "参数约束") {
t.Fatalf("Long must not pre-render 参数约束: %q", cs.Long)
}
if cs.Safety.Effect != "destructive" || cs.Safety.Risk != "high" ||
cs.Safety.Confirmation != "user_required" || cs.Safety.Idempotency != "unknown" {
t.Fatalf("adapter safety = %#v, want destructive/high/user_required/unknown", cs.Safety)
}
if got := EffectiveSafety(Shortcut{Risk: RiskWrite}); got.Effect != "write" || got.Confirmation != "user_required" {
t.Fatalf("legacy effective safety = %#v", got)
}
explicit := contract.SafetySpec{Effect: "read", Risk: "low", Confirmation: "not_required", Idempotency: "idempotent"}
if got := EffectiveSafety(Shortcut{Risk: RiskHighWrite, Safety: explicit}); got != explicit {
t.Fatalf("explicit effective safety = %#v, want %#v", got, explicit)
}
if cs.Orchestrate == nil {
t.Fatal("multi-step Execute must project into Orchestrate")
}
if cs.Invoke != nil || cs.RunE != nil {
t.Fatal("only Orchestrate may be set for a shortcut projection")
}
if cs.Validate == nil {
t.Fatal("Shortcut.Validate must project into CommandSpec.Validate")
}
if len(cs.Flags) != 5 {
t.Fatalf("flags len = %d, want 5", len(cs.Flags))
}
wantKinds := []corecmd.FlagKind{corecmd.KindString, corecmd.KindInt, corecmd.KindBool, corecmd.KindStringSlice, corecmd.KindString}
for i, want := range wantKinds {
if cs.Flags[i].Kind != want {
t.Fatalf("flag[%d].Kind = %v, want %v", i, cs.Flags[i].Kind, want)
}
}
name := cs.Flags[0]
if name.Name != "name" || name.Shorthand != "n" || !name.Required || name.Default != "d" ||
!name.Hidden || name.ValidationMode != corecmd.ValidationShortcut ||
name.RequiredError != "缺少必填参数 --name:名称" ||
strings.Join(name.Enum, ",") != "a,b" {
t.Fatalf("name flag base fields = %#v", name)
}
// Usage keeps mount()'s flagHelp decoration (必填 / 可选值).
if name.Usage != flagHelp(s.Flags[0]) {
t.Fatalf("name usage = %q, want flagHelp %q", name.Usage, flagHelp(s.Flags[0]))
}
if !strings.Contains(name.Usage, "可选值") {
t.Fatalf("enum decoration lost from usage: %q", name.Usage)
}
// All constraints, including custom declaration/help facts, are carried.
if len(cs.Constraints) != 4 {
t.Fatalf("constraints len = %d, want 4", len(cs.Constraints))
}
if cs.Constraints[0].Kind != corecmd.ExactlyOne || cs.Constraints[0].Description != "二选一" {
t.Fatalf("constraint[0] = %#v", cs.Constraints[0])
}
if cs.Constraints[1].Kind != corecmd.AtLeastOne || cs.Constraints[2].Kind != corecmd.MutuallyExclusive {
t.Fatalf("constraint kinds = %#v", cs.Constraints)
}
if cs.Constraints[3].Kind != corecmd.Custom ||
cs.Constraints[3].Description != "自定义由 Validate 保证" {
t.Fatalf("custom constraint = %#v", cs.Constraints[3])
}
// The projected Flags slice must be a copy, not an alias of the registry's.
cs.Constraints[0].Flags[0] = "mutated"
if s.Constraints[0].Flags[0] != "name" {
t.Fatal("constraint Flags slice is aliased, not copied")
}
}
func TestCrossPlatformCoverageFromShortcutAliasesAndPositionalAlias(t *testing.T) {
executed := ""
s := Shortcut{
Service: "chat",
Command: "+search",
Aliases: []string{"+search-group"},
SinglePositionalAliasFor: "query",
Description: "搜索群",
Intent: "按名称搜索群",
Contract: corecmd.ContractDecl{
Description: "按名称搜索群",
Interface: &contract.InterfaceSpec{
Mode: contract.InterfaceModeComposite, Availability: contract.InterfaceAvailable, Reason: "test composite",
},
Selection: contract.SelectionSpec{
AgentSummary: "搜索群", UseWhen: []string{"按名称搜索群"}, AvoidWhen: []string{"不要用于成员搜索"}, Examples: []string{"dws chat +search --query demo"},
},
Identity: contract.ToolIdentitySpec{
ProductID: "chat", Name: "shortcut_search", CanonicalPath: "chat.shortcut_search", CLIPath: "chat +search", PrimaryCLIPath: "chat +search",
},
},
Flags: []Flag{{Name: "query", Desc: "关键词", Required: true, Aliases: []string{"keyword"}, AliasesVisible: true}},
Execute: func(rt *RuntimeContext) error { executed = rt.Str("query"); return nil },
}
spec := FromShortcut(s)
if got := spec.Contract.Identity.Aliases; len(got) != 1 || got[0] != "chat +search-group" {
t.Fatalf("contract aliases = %#v", got)
}
cmd := mount(s)
if !cmd.HasAlias("+search-group") {
t.Fatalf("cobra aliases = %#v", cmd.Aliases)
}
if alias := cmd.Flags().Lookup("keyword"); alias == nil || alias.Hidden {
t.Fatalf("historically public flag alias = %#v, want visible", alias)
}
cmd.SetArgs([]string{"项目群"})
if err := cmd.Execute(); err != nil || executed != "项目群" {
t.Fatalf("positional execute err=%v value=%q", err, executed)
}
conflict := mount(s)
conflict.SetArgs([]string{"项目群", "--query", "另一个群"})
if err := conflict.Execute(); err == nil || !strings.Contains(err.Error(), "不能同时提供") {
t.Fatalf("positional/flag conflict err = %v", err)
}
tooMany := mount(s)
tooMany.SetArgs([]string{"one", "two"})
if err := tooMany.Execute(); err == nil {
t.Fatal("multiple positional aliases unexpectedly accepted")
}
missing := mount(Shortcut{
Service: "chat", Command: "+missing", Description: "missing",
SinglePositionalAliasFor: "query", Execute: func(*RuntimeContext) error { return nil },
})
missing.SetArgs([]string{"value"})
if err := missing.Execute(); err == nil || !strings.Contains(err.Error(), "is not registered") {
t.Fatalf("missing positional flag err = %v", err)
}
invalid := mount(Shortcut{
Service: "chat", Command: "+invalid", Description: "invalid",
SinglePositionalAliasFor: "query", Flags: []Flag{{Name: "query", Type: FlagInt}}, Execute: func(*RuntimeContext) error { return nil },
})
invalid.SetArgs([]string{"not-an-int"})
if err := invalid.Execute(); err == nil || !strings.Contains(err.Error(), "无法写入") {
t.Fatalf("invalid positional value err = %v", err)
}
}
// TestCrossPlatformCoverageFromShortcutMatchesMountSurface pins the live
// adapter surface: flag set (names/types/usage) and rendered Long must agree.
// This catches a double-rendered 参数约束 or lost flagHelp decoration.
func TestCrossPlatformCoverageFromShortcutMatchesMountSurface(t *testing.T) {
s := Shortcut{
Service: "chat",
Command: "+surface",
Description: "表层对比",
Intent: "用于校验投影与 mount 的一致性",
Risk: RiskWrite,
Flags: []Flag{
{Name: "group", Type: FlagString, Desc: "群 ID", Required: true},
{Name: "mode", Type: FlagString, Desc: "模式", Enum: []string{"a", "b"}},
{Name: "count", Type: FlagInt, Desc: "数量"},
},
Constraints: []Constraint{{Kind: ConstraintAtLeastOne, Flags: []string{"group", "mode"}}},
Execute: func(*RuntimeContext) error { return nil },
}
mounted := mount(s)
projected := corecmd.New(FromShortcut(s))
mounted.Flags().VisitAll(func(want *pflag.Flag) {
got := projected.Flags().Lookup(want.Name)
if got == nil {
t.Fatalf("projected command missing flag --%s", want.Name)
}
if got.Value.Type() != want.Value.Type() {
t.Fatalf("flag --%s type = %s, want %s", want.Name, got.Value.Type(), want.Value.Type())
}
if got.Usage != want.Usage {
t.Fatalf("flag --%s usage = %q, want %q", want.Name, got.Usage, want.Usage)
}
})
projected.Flags().VisitAll(func(extra *pflag.Flag) {
if mounted.Flags().Lookup(extra.Name) == nil {
t.Fatalf("projected command has extra flag --%s", extra.Name)
}
})
if projected.Long != mounted.Long {
t.Fatalf("Long mismatch:\nprojected=%q\nmounted =%q", projected.Long, mounted.Long)
}
if n := strings.Count(projected.Long, "参数约束"); n != 1 {
t.Fatalf("参数约束 rendered %d times: %q", n, projected.Long)
}
}
// TestCrossPlatformCoverageFromShortcutEmpty covers the empty flag/constraint
// short-circuits and the read-risk default.
func TestCrossPlatformCoverageFromShortcutEmpty(t *testing.T) {
cs := FromShortcut(Shortcut{Service: "x", Command: "+bare", Description: "bare"})
if cs.Flags != nil {
t.Fatalf("empty flags = %#v, want nil", cs.Flags)
}
if cs.Constraints != nil {
t.Fatalf("empty constraints = %#v, want nil", cs.Constraints)
}
if cs.Safety.Effect != "read" || cs.Safety.Risk != "low" ||
cs.Safety.Confirmation != "not_required" || cs.Safety.Idempotency != "idempotent" {
t.Fatalf("default safety = %#v, want read/low/not_required/idempotent", cs.Safety)
}
}
// TestCrossPlatformCoverageFromShortcutWithoutExecuteFailsClosed pins the
// adapter's fail-closed body: a shortcut that never authored Execute must
// surface a typed internal error instead of silently exiting 0.
func TestCrossPlatformCoverageFromShortcutWithoutExecuteFailsClosed(t *testing.T) {
cmd := corecmd.New(FromShortcut(Shortcut{
Service: "chat", Command: "+noexec", Description: "缺少 Execute 的声明",
}))
cmd.SilenceErrors = true
cmd.SilenceUsage = true
cmd.SetArgs(nil)
err := cmd.Execute()
if err == nil || !strings.Contains(err.Error(), "未实现 Execute") {
t.Fatalf("missing Execute err = %v, want 未实现 Execute internal error", err)
}
if !strings.Contains(err.Error(), "chat +noexec") {
t.Fatalf("missing Execute err = %v, want service/command identification", err)
}
}
// TestCrossPlatformCoverageFromShortcutUnknownConstraintKindPanics keeps the
// adapter's build-time contract: an unmapped constraint kind is a programming
// error caught at construction, never a silently dropped rule.
func TestCrossPlatformCoverageFromShortcutUnknownConstraintKindPanics(t *testing.T) {
defer func() {
recovered := recover()
if recovered == nil {
t.Fatal("unknown constraint kind must panic at construction")
}
if msg, _ := recovered.(string); !strings.Contains(msg, `unknown shortcut constraint kind "bogus"`) {
t.Fatalf("panic = %v, want unknown shortcut constraint kind", recovered)
}
}()
FromShortcut(Shortcut{
Service: "chat",
Command: "+bogus",
Description: "非法约束",
Flags: []Flag{
{Name: "a", Desc: "A"},
{Name: "b", Desc: "B"},
},
Constraints: []Constraint{{Kind: ConstraintKind("bogus"), Flags: []string{"a", "b"}}},
Execute: func(*RuntimeContext) error { return nil },
})
}