Files
dingtalk-workspace-cli/internal/shortcut/builtin/command_surface_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

218 lines
7.1 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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 builtin_test
import (
"strconv"
"strings"
"testing"
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/shortcut"
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/shortcut/builtin"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
)
// TestCmdcoreMountPreservesEveryBuiltInShortcutSurface is the differential
// guard for the live mount migration. It derives the historical Cobra surface
// directly from each Shortcut declaration and checks the command-built tree.
func TestCrossPlatformCoverageCmdcoreMountPreservesEveryBuiltInShortcutSurface(t *testing.T) {
mounted := map[string]*cobra.Command{}
for _, service := range builtin.BaseCommands() {
for _, command := range service.Commands() {
mounted[service.Name()+"\x00"+command.Name()] = command
}
}
var declarations int
for _, spec := range shortcut.All() {
if spec.UserDefined {
continue
}
declarations++
key := spec.Service + "\x00" + spec.Command
command := mounted[key]
if command == nil {
t.Errorf("%s %s: command is not mounted", spec.Service, spec.Command)
continue
}
if command.Short != spec.Description {
t.Errorf("%s %s: Short = %q, want %q", spec.Service, spec.Command, command.Short, spec.Description)
}
if command.Long != expectedShortcutLong(spec) {
t.Errorf("%s %s: Long mismatch\n got: %q\nwant: %q",
spec.Service, spec.Command, command.Long, expectedShortcutLong(spec))
}
if command.Example != expectedShortcutExamples(spec.Tips) {
t.Errorf("%s %s: Example = %q, want %q",
spec.Service, spec.Command, command.Example, expectedShortcutExamples(spec.Tips))
}
if command.Hidden != spec.Hidden {
t.Errorf("%s %s: Hidden = %v, want %v", spec.Service, spec.Command, command.Hidden, spec.Hidden)
}
declaredFlags := make(map[string]shortcut.Flag, len(spec.Flags))
for _, flag := range spec.Flags {
declaredFlags[flag.Name] = flag
got := command.Flags().Lookup(flag.Name)
if got == nil {
t.Errorf("%s %s: flag --%s is not mounted", spec.Service, spec.Command, flag.Name)
continue
}
if got.Value.Type() != expectedFlagType(flag.Type) {
t.Errorf("%s %s --%s: type = %q, want %q",
spec.Service, spec.Command, flag.Name, got.Value.Type(), expectedFlagType(flag.Type))
}
if got.Usage != expectedFlagHelp(flag) {
t.Errorf("%s %s --%s: usage = %q, want %q",
spec.Service, spec.Command, flag.Name, got.Usage, expectedFlagHelp(flag))
}
if got.Hidden != flag.Hidden {
t.Errorf("%s %s --%s: hidden = %v, want %v",
spec.Service, spec.Command, flag.Name, got.Hidden, flag.Hidden)
}
assertShortcutDefault(t, command, spec, flag)
for _, alias := range flag.Aliases {
declaredFlags[alias] = flag
gotAlias := command.Flags().Lookup(alias)
if gotAlias == nil {
t.Errorf("%s %s: flag alias --%s is not mounted", spec.Service, spec.Command, alias)
continue
}
wantHidden := !flag.AliasesVisible
if gotAlias.Hidden != wantHidden {
t.Errorf("%s %s: flag alias --%s hidden = %v, want %v", spec.Service, spec.Command, alias, gotAlias.Hidden, wantHidden)
}
}
}
command.Flags().VisitAll(func(flag *pflag.Flag) {
if flag.Name == "help" {
return
}
if _, ok := declaredFlags[flag.Name]; !ok {
t.Errorf("%s %s: undeclared flag --%s was mounted", spec.Service, spec.Command, flag.Name)
}
})
}
if len(mounted) != declarations {
t.Fatalf("mounted built-in shortcuts = %d, declarations = %d", len(mounted), declarations)
}
}
func expectedShortcutLong(spec shortcut.Shortcut) string {
long := strings.TrimSpace(spec.Intent)
if long == "" {
long = strings.TrimSpace(spec.Description)
}
if len(spec.Constraints) == 0 {
return long
}
lines := make([]string, 0, len(spec.Constraints))
for _, constraint := range spec.Constraints {
text := strings.TrimSpace(constraint.Description)
if text == "" {
switch constraint.Kind {
case shortcut.ConstraintAtLeastOne:
text = expectedDashed(constraint.Flags) + " 至少指定一个"
case shortcut.ConstraintExactlyOne:
text = expectedDashed(constraint.Flags) + " 必须且只能指定一个"
case shortcut.ConstraintMutuallyExclusive:
text = expectedDashed(constraint.Flags) + " 互斥,最多指定一个"
}
}
lines = append(lines, " - "+text)
}
return long + "\n\n参数约束:\n" + strings.Join(lines, "\n")
}
func expectedShortcutExamples(tips []string) string {
if len(tips) == 0 {
return ""
}
return " " + strings.Join(tips, "\n ")
}
func expectedFlagType(kind shortcut.FlagType) string {
switch kind {
case shortcut.FlagBool:
return "bool"
case shortcut.FlagInt:
return "int"
case shortcut.FlagStringSlice:
return "stringSlice"
default:
return "string"
}
}
func expectedFlagHelp(flag shortcut.Flag) string {
parts := make([]string, 0, 2)
if flag.Required && !strings.Contains(flag.Desc, "必填") {
parts = append(parts, "必填")
}
if len(flag.Enum) > 0 {
parts = append(parts, "可选值: "+strings.Join(flag.Enum, ", "))
}
if len(parts) == 0 {
return flag.Desc
}
if strings.TrimSpace(flag.Desc) == "" {
return strings.Join(parts, ";")
}
return flag.Desc + "(" + strings.Join(parts, ";") + ")"
}
func assertShortcutDefault(t *testing.T, command *cobra.Command, spec shortcut.Shortcut, flag shortcut.Flag) {
t.Helper()
switch flag.Type {
case shortcut.FlagBool:
got, err := command.Flags().GetBool(flag.Name)
if err != nil || got != (flag.Default == "true") {
t.Errorf("%s %s --%s: bool default = %v/%v, want %v",
spec.Service, spec.Command, flag.Name, got, err, flag.Default == "true")
}
case shortcut.FlagInt:
want, _ := strconv.Atoi(flag.Default)
got, err := command.Flags().GetInt(flag.Name)
if err != nil || got != want {
t.Errorf("%s %s --%s: int default = %d/%v, want %d",
spec.Service, spec.Command, flag.Name, got, err, want)
}
case shortcut.FlagStringSlice:
var want []string
if value := strings.TrimSpace(flag.Default); value != "" {
want = strings.Split(value, ",")
}
got, err := command.Flags().GetStringSlice(flag.Name)
if err != nil || strings.Join(got, "\x00") != strings.Join(want, "\x00") {
t.Errorf("%s %s --%s: string-slice default = %#v/%v, want %#v",
spec.Service, spec.Command, flag.Name, got, err, want)
}
default:
got, err := command.Flags().GetString(flag.Name)
if err != nil || got != flag.Default {
t.Errorf("%s %s --%s: string default = %q/%v, want %q",
spec.Service, spec.Command, flag.Name, got, err, flag.Default)
}
}
}
func expectedDashed(flags []string) string {
out := make([]string, len(flags))
for i, flag := range flags {
out[i] = "--" + flag
}
return strings.Join(out, "、")
}