604 lines
22 KiB
Go
604 lines
22 KiB
Go
// Copyright 2026 Alibaba Group
|
|
// Licensed under the Apache License, Version 2.0
|
|
|
|
package app
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
stderrors "errors"
|
|
"io"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/cli"
|
|
apperrors "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/errors"
|
|
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/pipeline"
|
|
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/shortcut"
|
|
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/pkg/cmdutil"
|
|
"github.com/spf13/cobra"
|
|
)
|
|
|
|
const (
|
|
runtimeShortcutFallbackChildEnv = "DWS_RUNTIME_SHORTCUT_FALLBACK_CHILD"
|
|
runtimeShortcutFallbackConfigEnv = "DWS_RUNTIME_SHORTCUT_FALLBACK_CONFIG_DIR"
|
|
)
|
|
|
|
func TestCrossPlatformCoverageRuntimeUserShortcutTakesPrecedenceOverReviewedFallback(t *testing.T) {
|
|
if os.Getenv(runtimeShortcutFallbackChildEnv) == "1" {
|
|
t.Setenv("DWS_CONFIG_DIR", os.Getenv(runtimeShortcutFallbackConfigEnv))
|
|
engine := newPipelineEngine()
|
|
root := NewRootCommandWithEngine(context.Background(), engine)
|
|
runtimeCommand := exactAppCommand(root, "chat +members")
|
|
if runtimeCommand == nil || !runtimeCommand.Runnable() || cmdutil.IsHintOnlyCommand(runtimeCommand) {
|
|
var userDefined []string
|
|
for _, candidate := range shortcut.All() {
|
|
if candidate.UserDefined {
|
|
userDefined = append(userDefined, candidate.Service+" "+candidate.Command)
|
|
}
|
|
}
|
|
t.Fatalf("runtime shortcut was not mounted as an executable command: command=%#v user_defined=%v config=%q", runtimeCommand, userDefined, os.Getenv("DWS_CONFIG_DIR"))
|
|
}
|
|
root.SetOut(io.Discard)
|
|
root.SetErr(io.Discard)
|
|
args := []string{"chat", "+members", "--sentinel", "fixture", "--mock", "--format", "json"}
|
|
root.SetArgs(args)
|
|
ctx, err := pipeline.RunPreParseArgs(root, engine, args)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if ctx == nil || ctx.Command != "dws chat +members" {
|
|
t.Fatalf("runtime shortcut context = %#v", ctx)
|
|
}
|
|
assertNoCommandPathFallbackCorrection(t, ctx)
|
|
if err := root.Execute(); err != nil {
|
|
t.Fatalf("runtime shortcut execute: %v", err)
|
|
}
|
|
return
|
|
}
|
|
|
|
configDir := t.TempDir()
|
|
shortcutDir := filepath.Join(configDir, "shortcuts")
|
|
if err := os.MkdirAll(shortcutDir, 0o700); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
definition := `version: 1
|
|
service: chat
|
|
command: "+members"
|
|
product: chat
|
|
description: runtime members fixture
|
|
execute:
|
|
tool: fixture_user_members
|
|
bind:
|
|
sentinel: "${sentinel}"
|
|
flags:
|
|
- name: sentinel
|
|
type: string
|
|
required: true
|
|
desc: fixture sentinel
|
|
`
|
|
if err := os.WriteFile(filepath.Join(shortcutDir, "chat.members.yaml"), []byte(definition), 0o600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
command := exec.Command(os.Args[0], "-test.run=^TestCrossPlatformCoverageRuntimeUserShortcutTakesPrecedenceOverReviewedFallback$", "-test.count=1")
|
|
command.Env = append(os.Environ(), runtimeShortcutFallbackConfigEnv+"="+configDir, runtimeShortcutFallbackChildEnv+"=1")
|
|
output, err := command.CombinedOutput()
|
|
if err != nil {
|
|
t.Fatalf("runtime shortcut child failed: %v\n%s", err, strings.TrimSpace(string(output)))
|
|
}
|
|
if !strings.Contains(string(output), `"_tool": "fixture_user_members"`) {
|
|
t.Fatalf("runtime shortcut child used the wrong command:\n%s", strings.TrimSpace(string(output)))
|
|
}
|
|
}
|
|
|
|
func TestCrossPlatformCoverageReviewedCommandFallbacksReachCanonicalDryRunPayload(t *testing.T) {
|
|
_, canonicalQuery, canonicalQueryAttempts, err := executeParamAliasDryRunE2E(t,
|
|
"chat", "+chat-search", "--query", "project", "--dry-run",
|
|
)
|
|
if err != nil || len(canonicalQueryAttempts) != 0 {
|
|
t.Fatalf("canonical query preview = %#v, attempts=%v, error=%v", canonicalQuery, canonicalQueryAttempts, err)
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
args []string
|
|
wantCommand string
|
|
wantPreview paramAliasDryRunPreview
|
|
}{
|
|
{
|
|
name: "group search query",
|
|
args: []string{"chat", "+group-search", "--query", "project", "--dry-run"},
|
|
wantCommand: "dws chat +chat-search",
|
|
wantPreview: canonicalQuery,
|
|
},
|
|
}
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
ctx, preview, attempts, executeErr := executeParamAliasDryRunE2E(t, test.args...)
|
|
if executeErr != nil {
|
|
t.Fatalf("fallback execute error = %v", executeErr)
|
|
}
|
|
if len(attempts) != 0 {
|
|
t.Fatalf("fallback crossed dry-run dispatch boundary: %#v", attempts)
|
|
}
|
|
if !reflect.DeepEqual(preview, test.wantPreview) {
|
|
t.Fatalf("fallback preview = %#v, want canonical %#v", preview, test.wantPreview)
|
|
}
|
|
if ctx == nil || ctx.Command != test.wantCommand || len(ctx.Corrections) == 0 {
|
|
t.Fatalf("fallback context = %#v, want command %q", ctx, test.wantCommand)
|
|
}
|
|
correction := ctx.Corrections[0]
|
|
if correction.Handler != "command-path-fallback" || correction.Kind != "reviewed-fallback" {
|
|
t.Fatalf("fallback correction = %#v", correction)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCrossPlatformCoverageOfficialCommandAliasesBypassFallbackAndReachEquivalentPayload(t *testing.T) {
|
|
_, canonicalQuery, canonicalQueryAttempts, err := executeParamAliasDryRunE2E(t,
|
|
"chat", "+chat-search", "--query", "project", "--dry-run",
|
|
)
|
|
if err != nil || len(canonicalQueryAttempts) != 0 {
|
|
t.Fatalf("canonical query preview = %#v, attempts=%v, error=%v", canonicalQuery, canonicalQueryAttempts, err)
|
|
}
|
|
_, canonicalKeyword, canonicalKeywordAttempts, err := executeParamAliasDryRunE2E(t,
|
|
"chat", "+chat-search", "--keyword", "project", "--dry-run",
|
|
)
|
|
if err != nil || len(canonicalKeywordAttempts) != 0 {
|
|
t.Fatalf("canonical keyword preview = %#v, attempts=%v, error=%v", canonicalKeyword, canonicalKeywordAttempts, err)
|
|
}
|
|
_, nativeCanonical, nativeCanonicalAttempts, err := executeParamAliasDryRunE2E(t,
|
|
"chat", "search", "--query", "project", "--dry-run",
|
|
)
|
|
if err != nil || len(nativeCanonicalAttempts) != 0 {
|
|
t.Fatalf("native canonical preview = %#v, attempts=%v, error=%v", nativeCanonical, nativeCanonicalAttempts, err)
|
|
}
|
|
|
|
tests := []struct {
|
|
name string
|
|
args []string
|
|
wantCommand string
|
|
wantPreview paramAliasDryRunPreview
|
|
}{
|
|
{
|
|
name: "search group shortcut alias",
|
|
args: []string{"chat", "+search-group", "--keyword", "project", "--dry-run"},
|
|
wantCommand: "dws chat +chat-search",
|
|
wantPreview: canonicalKeyword,
|
|
},
|
|
{
|
|
name: "chat group search shortcut alias",
|
|
args: []string{"chat", "+chat-group-search", "--query", "project", "--dry-run"},
|
|
wantCommand: "dws chat +chat-search",
|
|
wantPreview: canonicalQuery,
|
|
},
|
|
{
|
|
name: "hidden native compatibility leaf",
|
|
args: []string{"chat", "group", "search", "--query", "project", "--dry-run"},
|
|
wantCommand: "dws chat group search",
|
|
wantPreview: nativeCanonical,
|
|
},
|
|
}
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
ctx, preview, attempts, executeErr := executeParamAliasDryRunE2E(t, test.args...)
|
|
if executeErr != nil {
|
|
t.Fatalf("official alias execute error = %v", executeErr)
|
|
}
|
|
if len(attempts) != 0 {
|
|
t.Fatalf("official alias crossed dry-run dispatch boundary: %#v", attempts)
|
|
}
|
|
if !reflect.DeepEqual(preview, test.wantPreview) {
|
|
t.Fatalf("official alias preview = %#v, want canonical %#v", preview, test.wantPreview)
|
|
}
|
|
if ctx == nil || ctx.Command != test.wantCommand {
|
|
t.Fatalf("official alias context = %#v, want command %q", ctx, test.wantCommand)
|
|
}
|
|
assertNoCommandPathFallbackCorrection(t, ctx)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCrossPlatformCoverageReviewedReadFallbacksResolveCanonicalLeafBeforeParameterValidation(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
args []string
|
|
target string
|
|
}{
|
|
{
|
|
name: "members",
|
|
args: []string{"chat", "+members", "--group", "Fixture Group"},
|
|
target: "chat +group-members",
|
|
},
|
|
{
|
|
name: "group member list",
|
|
args: []string{"chat", "+group-member-list", "--group-name", "Fixture Group"},
|
|
target: "chat +group-members",
|
|
},
|
|
{
|
|
name: "list group bots",
|
|
args: []string{"chat", "+list-group-bots", "--group", "cid-fixture"},
|
|
target: "chat +chat-bots",
|
|
},
|
|
{
|
|
name: "list robot",
|
|
args: []string{"chat", "+list-robot", "--group", "cid-fixture"},
|
|
target: "chat +chat-bots",
|
|
},
|
|
{
|
|
name: "list robots",
|
|
args: []string{"chat", "+list-robots", "--group", "cid-fixture"},
|
|
target: "chat +chat-bots",
|
|
},
|
|
{
|
|
name: "bot list",
|
|
args: []string{"chat", "+bot-list", "--group", "cid-fixture"},
|
|
target: "chat +chat-bots",
|
|
},
|
|
{
|
|
name: "conversation detail",
|
|
args: []string{"chat", "+conversation-detail", "--group", "cid-fixture"},
|
|
target: "chat +conversation-info",
|
|
},
|
|
}
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
root := NewSchemaSourceRootCommand()
|
|
root.SetOut(io.Discard)
|
|
root.SetErr(io.Discard)
|
|
root.SetArgs(test.args)
|
|
ctx, err := pipeline.RunPreParseArgs(root, newPipelineEngine(), test.args)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if ctx == nil || ctx.Command != "dws "+test.target || len(ctx.Corrections) == 0 {
|
|
t.Fatalf("read fallback context = %#v", ctx)
|
|
}
|
|
wantPrefix := strings.Fields(test.target)
|
|
if len(ctx.Args) < len(wantPrefix) || !reflect.DeepEqual(ctx.Args[:len(wantPrefix)], wantPrefix) {
|
|
t.Fatalf("read fallback args = %v, want prefix %v", ctx.Args, wantPrefix)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCrossPlatformCoverageOfficialGroupMembersAliasBypassesCommandFallback(t *testing.T) {
|
|
args := []string{"chat", "+chat-group-members", "--conversation-id", "cid-fixture", "--member-types", "user"}
|
|
root := NewSchemaSourceRootCommand()
|
|
root.SetOut(io.Discard)
|
|
root.SetErr(io.Discard)
|
|
root.SetArgs(args)
|
|
ctx, err := pipeline.RunPreParseArgs(root, newPipelineEngine(), args)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if ctx == nil || ctx.Command != "dws chat +chat-members-list" {
|
|
t.Fatalf("official group-members alias context = %#v", ctx)
|
|
}
|
|
assertNoCommandPathFallbackCorrection(t, ctx)
|
|
}
|
|
|
|
func TestCrossPlatformCoverageReviewedRenameFallbackResolvesCanonicalLeafBeforeParameterValidation(t *testing.T) {
|
|
args := []string{"chat", "+rename-group", "--id", "cid-fixture", "--name", "Fixture Group"}
|
|
root := NewSchemaSourceRootCommand()
|
|
root.SetOut(io.Discard)
|
|
root.SetErr(io.Discard)
|
|
root.SetArgs(args)
|
|
ctx, err := pipeline.RunPreParseArgs(root, newPipelineEngine(), args)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if ctx == nil || ctx.Command != "dws chat +chat-update" || len(ctx.Corrections) == 0 {
|
|
t.Fatalf("rename fallback context = %#v", ctx)
|
|
}
|
|
wantPrefix := []string{"chat", "+chat-update"}
|
|
if len(ctx.Args) < len(wantPrefix) || !reflect.DeepEqual(ctx.Args[:len(wantPrefix)], wantPrefix) {
|
|
t.Fatalf("rename fallback args = %v, want prefix %v", ctx.Args, wantPrefix)
|
|
}
|
|
}
|
|
|
|
func TestCrossPlatformCoverageReviewedDocHistorySaveFallbacksPreserveArguments(t *testing.T) {
|
|
sources := []string{"+create-version", "+save-version", "+snapshot", "+version-create"}
|
|
for _, source := range sources {
|
|
t.Run(source, func(t *testing.T) {
|
|
args := []string{"doc", source, "--node", "node-fixture", "--mock", "--format", "json"}
|
|
root := NewSchemaSourceRootCommand()
|
|
root.SetOut(io.Discard)
|
|
root.SetErr(io.Discard)
|
|
root.SetArgs(args)
|
|
ctx, err := pipeline.RunPreParseArgs(root, newPipelineEngine(), args)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if ctx == nil || ctx.Command != "dws doc +history-save" || len(ctx.Corrections) == 0 {
|
|
t.Fatalf("history-save fallback context = %#v", ctx)
|
|
}
|
|
wantArgs := []string{"doc", "+history-save", "--node", "node-fixture", "--mock", "--format", "json"}
|
|
if !reflect.DeepEqual(ctx.Args, wantArgs) {
|
|
t.Fatalf("history-save fallback args = %v, want %v", ctx.Args, wantArgs)
|
|
}
|
|
correction := ctx.Corrections[0]
|
|
if correction.Handler != "command-path-fallback" || correction.Kind != "reviewed-fallback" {
|
|
t.Fatalf("history-save correction = %#v", correction)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCrossPlatformCoverageDocExportPDFRemainsUnknownShortcut(t *testing.T) {
|
|
if entry, ok := cli.LookupCommandPathFallback("doc +export-pdf"); ok {
|
|
t.Fatalf("+export-pdf must not receive a path-only fallback: %#v", entry)
|
|
}
|
|
root := NewSchemaSourceRootCommand()
|
|
root.SetOut(io.Discard)
|
|
root.SetErr(io.Discard)
|
|
args := []string{"doc", "+export-pdf", "--node", "node-fixture", "--mock", "--format", "json"}
|
|
root.SetArgs(args)
|
|
ctx, err := pipeline.RunPreParseArgs(root, newPipelineEngine(), args)
|
|
if ctx == nil {
|
|
t.Fatal("+export-pdf returned nil context")
|
|
}
|
|
var structured *apperrors.Error
|
|
if !stderrors.As(err, &structured) || structured.Reason != "unknown_shortcut" {
|
|
t.Fatalf("+export-pdf preparse error = %T %#v", err, err)
|
|
}
|
|
if len(ctx.Corrections) != 0 {
|
|
t.Fatalf("+export-pdf unexpectedly received corrections: %#v", ctx.Corrections)
|
|
}
|
|
}
|
|
|
|
func TestCrossPlatformCoverageReviewedAmbiguousCommandFallbackNeverDispatches(t *testing.T) {
|
|
tests := []struct {
|
|
path string
|
|
candidates []string
|
|
}{
|
|
{path: "chat +group-send-text", candidates: []string{"chat +send-to-group", "chat +messages-send"}},
|
|
{path: "chat +message-list", candidates: []string{"chat +chat-messages", "chat +messages-list-direct", "chat +search-msg", "chat +unread-chats"}},
|
|
{path: "chat +read-single", candidates: []string{"chat +messages-list-direct", "chat +chat-messages"}},
|
|
{path: "chat +send", candidates: []string{"chat +messages-send", "chat +dm", "chat +send-to-group"}},
|
|
{path: "chat +send-by-bot", candidates: []string{"chat +messages-send", "chat message send-by-bot"}},
|
|
{path: "chat +send-dm", candidates: []string{"chat +dm", "chat +messages-send"}},
|
|
{path: "chat +send-message", candidates: []string{"chat +messages-send", "chat +dm", "chat +send-to-group"}},
|
|
{path: "chat +send-single", candidates: []string{"chat +dm", "chat +messages-send"}},
|
|
{path: "chat +send-text", candidates: []string{"chat +messages-send", "chat +dm", "chat +send-to-group"}},
|
|
{path: "chat +send-to", candidates: []string{"chat +messages-send", "chat +dm", "chat +send-to-group"}},
|
|
{path: "chat +send-file", candidates: []string{"chat +messages-send", "chat message send"}},
|
|
{path: "chat +send-image", candidates: []string{"chat +messages-send", "chat message send"}},
|
|
{path: "chat +send-media", candidates: []string{"chat +messages-send", "chat message send"}},
|
|
{path: "chat +conversation-category-list", candidates: []string{"chat +category-list", "chat +category-list-conversations"}},
|
|
{path: "chat +conversation-group-list", candidates: []string{"chat +category-list-conversations", "chat +conversation-list"}},
|
|
{path: "chat +list-my-groups", candidates: []string{"chat +my-groups", "chat +chat-list-mine", "chat +chat-list"}},
|
|
{path: "oa +list-processes", candidates: []string{"oa +search-forms", "oa approval list-submitted", "oa approval list-initiated"}},
|
|
}
|
|
for _, test := range tests {
|
|
t.Run(test.path, func(t *testing.T) {
|
|
caller := ¶mAliasCaptureCaller{}
|
|
args := append(strings.Fields(test.path), "--format", "json")
|
|
ctx, err := executeParamAliasE2E(t, caller, args...)
|
|
if ctx == nil {
|
|
t.Fatal("ambiguous command fallback returned nil context")
|
|
}
|
|
var structured *apperrors.Error
|
|
if !stderrors.As(err, &structured) || structured.Reason != "ambiguous_command_fallback" || structured.ExitCode() != 3 {
|
|
t.Fatalf("ambiguous error = %T %#v", err, err)
|
|
}
|
|
if len(structured.Actions) != len(test.candidates) || len(caller.calls) != 0 {
|
|
t.Fatalf("ambiguous actions=%v calls=%#v", structured.Actions, caller.calls)
|
|
}
|
|
for _, candidate := range test.candidates {
|
|
if !strings.Contains(structured.Hint, "dws "+candidate) {
|
|
t.Errorf("ambiguous hint %q missing candidate %q", structured.Hint, candidate)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCrossPlatformCoverageCanonicalShortcutBadFlagDoesNotEnterCommandFallback(t *testing.T) {
|
|
root := NewSchemaSourceRootCommand()
|
|
root.SetOut(io.Discard)
|
|
root.SetErr(io.Discard)
|
|
args := []string{"chat", "+chat-search", "--definitely-not-a-real-flag", "project"}
|
|
root.SetArgs(args)
|
|
ctx, err := pipeline.RunPreParseArgs(root, newPipelineEngine(), args)
|
|
if err != nil {
|
|
t.Fatalf("preparse error = %v", err)
|
|
}
|
|
if ctx == nil || ctx.Command != "dws chat +chat-search" {
|
|
t.Fatalf("canonical context = %#v", ctx)
|
|
}
|
|
for _, correction := range ctx.Corrections {
|
|
if correction.Handler == "command-path-fallback" {
|
|
t.Fatalf("canonical command received fallback correction: %#v", ctx.Corrections)
|
|
}
|
|
}
|
|
if err := root.Execute(); err == nil || !strings.Contains(err.Error(), "unknown flag") {
|
|
t.Fatalf("canonical bad flag error = %v", err)
|
|
}
|
|
}
|
|
|
|
func TestCrossPlatformCoverageRewrittenShortcutStillUsesCanonicalParameterErrors(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
args []string
|
|
want string
|
|
}{
|
|
{
|
|
name: "missing required query",
|
|
args: []string{"chat", "+group-search", "--dry-run"},
|
|
want: "请至少指定 --query、--keyword 之一",
|
|
},
|
|
{
|
|
name: "unknown canonical flag",
|
|
args: []string{"chat", "+group-search", "--definitely-not-a-real-flag", "project"},
|
|
want: "unknown flag",
|
|
},
|
|
}
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
root := NewSchemaSourceRootCommand()
|
|
root.SetOut(io.Discard)
|
|
root.SetErr(io.Discard)
|
|
root.SetArgs(test.args)
|
|
ctx, err := pipeline.RunPreParseArgs(root, newPipelineEngine(), test.args)
|
|
if err != nil {
|
|
t.Fatalf("preparse error = %v", err)
|
|
}
|
|
if ctx == nil || ctx.Command != "dws chat +chat-search" || len(ctx.Corrections) == 0 ||
|
|
ctx.Corrections[0].Handler != "command-path-fallback" {
|
|
t.Fatalf("rewritten context = %#v", ctx)
|
|
}
|
|
if err := root.Execute(); err == nil || !strings.Contains(err.Error(), test.want) {
|
|
t.Fatalf("canonical parameter error = %v, want containing %q", err, test.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCrossPlatformCoverageCommandFallbackNamesStayOutOfHelpSchemaAndShortcutCatalog(t *testing.T) {
|
|
invalidShortcuts := map[string]bool{
|
|
"+bot-list": true,
|
|
"+conversation-detail": true,
|
|
"+conversation-category-list": true,
|
|
"+conversation-group-list": true,
|
|
"+list-my-groups": true,
|
|
"+group-search": true,
|
|
"+members": true,
|
|
"+group-member-list": true,
|
|
"+list-group-bots": true,
|
|
"+list-robot": true,
|
|
"+list-robots": true,
|
|
"+message-list": true,
|
|
"+read-single": true,
|
|
"+rename-group": true,
|
|
"+send": true,
|
|
"+send-by-bot": true,
|
|
"+send-dm": true,
|
|
"+send-message": true,
|
|
"+send-single": true,
|
|
"+send-text": true,
|
|
"+send-to": true,
|
|
"+send-file": true,
|
|
"+send-image": true,
|
|
"+send-media": true,
|
|
"+group-send-text": true,
|
|
}
|
|
root := NewSchemaSourceRootCommand()
|
|
chat := exactAppCommand(root, "chat")
|
|
if chat == nil {
|
|
t.Fatal("chat command missing")
|
|
}
|
|
for _, child := range chat.Commands() {
|
|
if invalidShortcuts[child.Name()] {
|
|
t.Fatalf("fallback name %q became a real command", child.Name())
|
|
}
|
|
for _, alias := range child.Aliases {
|
|
if invalidShortcuts[alias] {
|
|
t.Fatalf("fallback name %q became a Cobra alias", alias)
|
|
}
|
|
}
|
|
}
|
|
for path := range invalidShortcuts {
|
|
if _, ok := cli.ResolveMeta("chat " + path); ok {
|
|
t.Fatalf("fallback path %q leaked into embedded Schema", path)
|
|
}
|
|
}
|
|
for _, declared := range shortcut.All() {
|
|
if declared.Service == "chat" && invalidShortcuts[declared.Command] {
|
|
t.Fatalf("fallback name %q leaked into shortcut catalog", declared.Command)
|
|
}
|
|
}
|
|
if _, ok := cli.ResolveMeta("oa +list-processes"); ok {
|
|
t.Fatal("fallback path oa +list-processes leaked into embedded Schema")
|
|
}
|
|
for _, declared := range shortcut.All() {
|
|
if declared.Service == "oa" && declared.Command == "+list-processes" {
|
|
t.Fatal("fallback name +list-processes leaked into shortcut catalog")
|
|
}
|
|
}
|
|
|
|
group := exactAppCommand(root, "chat group")
|
|
searchCompatibility := exactAppCommand(root, "chat group search")
|
|
if group == nil || searchCompatibility == nil || !searchCompatibility.Hidden || cmdutil.IsHintOnlyCommand(searchCompatibility) {
|
|
t.Fatalf("chat group search must be a hidden executable compatibility leaf: group=%v search=%v", group, searchCompatibility)
|
|
}
|
|
var help bytes.Buffer
|
|
group.SetOut(&help)
|
|
if err := group.Help(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if strings.Contains(help.String(), "\n search ") {
|
|
t.Fatalf("hidden fallback source leaked into group help:\n%s", help.String())
|
|
}
|
|
}
|
|
|
|
func TestCrossPlatformCoverageDocCommandFallbackNamesStayOutOfHelpSchemaAndShortcutCatalog(t *testing.T) {
|
|
invalidShortcuts := map[string]bool{
|
|
"+create-version": true,
|
|
"+save-version": true,
|
|
"+snapshot": true,
|
|
"+version-create": true,
|
|
"+export-pdf": true,
|
|
}
|
|
root := NewSchemaSourceRootCommand()
|
|
doc := exactAppCommand(root, "doc")
|
|
if doc == nil {
|
|
t.Fatal("doc command missing")
|
|
}
|
|
for _, child := range doc.Commands() {
|
|
if invalidShortcuts[child.Name()] {
|
|
t.Fatalf("invalid shortcut %q became a real command", child.Name())
|
|
}
|
|
for _, alias := range child.Aliases {
|
|
if invalidShortcuts[alias] {
|
|
t.Fatalf("invalid shortcut %q became a Cobra alias", alias)
|
|
}
|
|
}
|
|
}
|
|
for path := range invalidShortcuts {
|
|
if _, ok := cli.ResolveMeta("doc " + path); ok {
|
|
t.Fatalf("invalid path %q leaked into embedded Schema", path)
|
|
}
|
|
}
|
|
for _, declared := range shortcut.All() {
|
|
if declared.Service == "doc" && invalidShortcuts[declared.Command] {
|
|
t.Fatalf("invalid shortcut %q leaked into shortcut catalog", declared.Command)
|
|
}
|
|
}
|
|
}
|
|
|
|
func assertNoCommandPathFallbackCorrection(t *testing.T, ctx *pipeline.Context) {
|
|
t.Helper()
|
|
for _, correction := range ctx.Corrections {
|
|
if correction.Handler == "command-path-fallback" {
|
|
t.Fatalf("official command unexpectedly received fallback correction: %#v", ctx.Corrections)
|
|
}
|
|
}
|
|
}
|
|
|
|
func exactAppCommand(root *cobra.Command, path string) *cobra.Command {
|
|
current := root
|
|
parts := strings.Fields(path)
|
|
if len(parts) > 0 && parts[0] == root.Name() {
|
|
parts = parts[1:]
|
|
}
|
|
for _, part := range parts {
|
|
var next *cobra.Command
|
|
for _, child := range current.Commands() {
|
|
if child.Name() == part {
|
|
next = child
|
|
break
|
|
}
|
|
}
|
|
if next == nil {
|
|
return nil
|
|
}
|
|
current = next
|
|
}
|
|
return current
|
|
}
|