Files
dingtalk-workspace-cli/internal/cmdcore/cmdcore_test.go
T
玉澜 ceca98c573 refactor(cmdcore): layer dispatch into Invoke/Orchestrate behind a shared Ctx
The single Dispatch hook assumed every command is one MCP call, so the
Shortcut projection could only ever describe a command, never run it. Split
dispatch into Invoke (assembled toolArgs) and Orchestrate (multi-step), keep
RunE as the escape hatch, and reject specs that declare anything other than
exactly one at construction time. Ctx gives both hooks the same typed flag
accessors so orchestration no longer reaches for framework-specific plumbing.

Catalog output stays byte-identical.
2026-07-30 20:32:04 +08:00

1004 lines
34 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 cmdcore
import (
"errors"
"reflect"
"strings"
"testing"
"github.com/spf13/cobra"
)
// cmdcore is a shared base package: it must be fully covered by its own direct
// tests rather than relying on cross-package coverpkg from its consumers, so the
// per-package coverage CI job (which runs without -coverpkg) sees it covered.
func newTestCommand() *cobra.Command {
cmd := &cobra.Command{Use: "t"}
cmd.SilenceErrors = true
cmd.SilenceUsage = true
return cmd
}
// ── flag registration ──────────────────────────────────────────────
func TestCrossPlatformCoverageRegisterFlagsAllKinds(t *testing.T) {
cmd := newTestCommand()
RegisterFlags(cmd, []FlagSpec{
{Name: "s", Usage: "S", Default: "d"},
{Name: "i", Usage: "I", Kind: KindInt, Aliases: []string{"i-alias"}},
{Name: "b", Usage: "B", Kind: KindBool},
{Name: "sl", Usage: "SL", Kind: KindStringSlice, Aliases: []string{"sl-alias"}},
{Name: "req", Usage: "R", MarkRequired: true},
})
if f := cmd.Flags().Lookup("s"); f == nil || f.DefValue != "d" || f.Usage != "S" {
t.Fatalf("string flag = %#v", f)
}
for name, wantType := range map[string]string{"i": "int", "b": "bool", "sl": "stringSlice"} {
f := cmd.Flags().Lookup(name)
if f == nil || f.Value.Type() != wantType {
t.Fatalf("flag %q type = %v, want %s", name, f, wantType)
}
}
// Aliases are registered with the main kind and hidden.
for _, alias := range []string{"i-alias", "sl-alias"} {
f := cmd.Flags().Lookup(alias)
if f == nil || !f.Hidden {
t.Fatalf("alias %q = %#v, want registered+hidden", alias, f)
}
}
if cmd.Flags().Lookup("i-alias").Value.Type() != "int" {
t.Fatal("int alias must be registered as int")
}
if ann := cmd.Flags().Lookup("req").Annotations[cobra.BashCompOneRequiredFlag]; len(ann) == 0 {
t.Fatal("MarkRequired did not reach cobra")
}
}
// ── effective value fallback chain ─────────────────────────────────
func TestCrossPlatformCoverageEffectiveValueFallbackChain(t *testing.T) {
spec := FlagSpec{Name: "v", Usage: "V", Default: "reg-default", Aliases: []string{"v-alias"}, EnvVar: "DWS_CMDCORE_V"}
// registration default is the chain tail
cmd := newTestCommand()
RegisterFlags(cmd, []FlagSpec{spec})
if got := EffectiveValue(cmd, spec); got != "reg-default" {
t.Fatalf("default tail = %q", got)
}
// env beats the registration default
t.Setenv("DWS_CMDCORE_V", "from-env")
if got := EffectiveValue(cmd, spec); got != "from-env" {
t.Fatalf("env = %q", got)
}
// explicit alias beats env
cmd = newTestCommand()
RegisterFlags(cmd, []FlagSpec{spec})
_ = cmd.Flags().Set("v-alias", "from-alias")
if got := EffectiveValue(cmd, spec); got != "from-alias" {
t.Fatalf("alias = %q", got)
}
// explicit main flag wins
_ = cmd.Flags().Set("v", "explicit")
if got := EffectiveValue(cmd, spec); got != "explicit" {
t.Fatalf("explicit = %q", got)
}
}
func TestCrossPlatformCoverageEffectiveValueTrimAndEmptySkips(t *testing.T) {
// Trim makes whitespace-only fall through to the next fallback level.
trimmed := FlagSpec{Name: "v", Usage: "V", Trim: true, Aliases: []string{"v-alias"}, Default: "tail"}
cmd := newTestCommand()
RegisterFlags(cmd, []FlagSpec{trimmed})
_ = cmd.Flags().Set("v", " ")
_ = cmd.Flags().Set("v-alias", " kept ")
if got := EffectiveValue(cmd, trimmed); got != "kept" {
t.Fatalf("trim fallthrough = %q, want trimmed alias", got)
}
// Empty explicit value also falls through (usable() rejects "").
plain := FlagSpec{Name: "p", Usage: "P", Default: "tail"}
cmd = newTestCommand()
RegisterFlags(cmd, []FlagSpec{plain})
_ = cmd.Flags().Set("p", "")
if got := EffectiveValue(cmd, plain); got != "tail" {
t.Fatalf("empty explicit = %q, want tail", got)
}
// Empty alias is skipped too.
aliased := FlagSpec{Name: "a", Usage: "A", Aliases: []string{"a-alias"}, Default: "tail"}
cmd = newTestCommand()
RegisterFlags(cmd, []FlagSpec{aliased})
_ = cmd.Flags().Set("a-alias", "")
if got := EffectiveValue(cmd, aliased); got != "tail" {
t.Fatalf("empty alias = %q, want tail", got)
}
// Empty env is skipped.
env := FlagSpec{Name: "e", Usage: "E", EnvVar: "DWS_CMDCORE_EMPTY", Default: "tail"}
t.Setenv("DWS_CMDCORE_EMPTY", "")
cmd = newTestCommand()
RegisterFlags(cmd, []FlagSpec{env})
if got := EffectiveValue(cmd, env); got != "tail" {
t.Fatalf("empty env = %q, want tail", got)
}
}
func TestCrossPlatformCoverageIntegerValue(t *testing.T) {
spec := FlagSpec{Name: "n", Usage: "N", Kind: KindInt, EnvVar: "DWS_CMDCORE_N"}
cmd := newTestCommand()
RegisterFlags(cmd, []FlagSpec{spec})
// unset int formats to "0" → treated as empty → 0, no error
if v, err := integerValue(cmd, spec); v != 0 || err != nil {
t.Fatalf("unset int = %d, %v", v, err)
}
_ = cmd.Flags().Set("n", "7")
if v, err := integerValue(cmd, spec); v != 7 || err != nil {
t.Fatalf("explicit int = %d, %v", v, err)
}
// unparseable env value must error rather than silently drop
cmd = newTestCommand()
RegisterFlags(cmd, []FlagSpec{spec})
t.Setenv("DWS_CMDCORE_N", "abc")
if _, err := integerValue(cmd, spec); err == nil || !strings.Contains(err.Error(), "invalid integer value") {
t.Fatalf("unparseable env err = %v", err)
}
}
func TestCrossPlatformCoverageSliceValue(t *testing.T) {
spec := FlagSpec{Name: "ids", Usage: "IDS", Kind: KindStringSlice, Aliases: []string{"ids-alias"}}
cmd := newTestCommand()
RegisterFlags(cmd, []FlagSpec{spec})
if got := sliceValue(cmd, spec); got != nil {
t.Fatalf("unset slice = %#v, want nil", got)
}
_ = cmd.Flags().Set("ids", " a , , b ")
if got := sliceValue(cmd, spec); !reflect.DeepEqual(got, []string{"a", "b"}) {
t.Fatalf("trimmed slice = %#v", got)
}
// all-empty elements count as not provided
cmd = newTestCommand()
RegisterFlags(cmd, []FlagSpec{spec})
_ = cmd.Flags().Set("ids", " , ")
if got := sliceValue(cmd, spec); got != nil {
t.Fatalf("blank slice = %#v, want nil", got)
}
// alias supplies the value when the main flag is blank
_ = cmd.Flags().Set("ids-alias", "x")
if got := sliceValue(cmd, spec); !reflect.DeepEqual(got, []string{"x"}) {
t.Fatalf("alias slice = %#v", got)
}
}
func TestCrossPlatformCoverageHasEffectiveValueAllKinds(t *testing.T) {
cmd := newTestCommand()
flags := []FlagSpec{
{Name: "s", Usage: "S"},
{Name: "i", Usage: "I", Kind: KindInt},
{Name: "b", Usage: "B", Kind: KindBool},
{Name: "sl", Usage: "SL", Kind: KindStringSlice},
{Name: "bad", Usage: "BAD", Kind: KindInt, EnvVar: "DWS_CMDCORE_BAD"},
}
RegisterFlags(cmd, flags)
for _, f := range flags[:4] {
if hasEffectiveValue(cmd, f) {
t.Fatalf("unset %q reported as provided", f.Name)
}
}
_ = cmd.Flags().Set("s", "v")
_ = cmd.Flags().Set("i", "3")
_ = cmd.Flags().Set("b", "false") // bool: Changed counts even for false
_ = cmd.Flags().Set("sl", "a")
for _, f := range flags[:4] {
if !hasEffectiveValue(cmd, f) {
t.Fatalf("set %q reported as missing", f.Name)
}
}
// explicit int 0 is NOT provided (non-zero only)
zero := newTestCommand()
RegisterFlags(zero, flags)
_ = zero.Flags().Set("i", "0")
if hasEffectiveValue(zero, flags[1]) {
t.Fatal("explicit int 0 must count as missing")
}
// unparseable int counts as provided so BuildArgs reports the precise error
t.Setenv("DWS_CMDCORE_BAD", "nope")
if !hasEffectiveValue(cmd, flags[4]) {
t.Fatal("unparseable int must count as provided")
}
}
// ── required validation ────────────────────────────────────────────
func TestCrossPlatformCoverageValidateRequired(t *testing.T) {
plain := []FlagSpec{{Name: "content", Usage: "C", Required: true}}
cmd := newTestCommand()
RegisterFlags(cmd, plain)
if err := ValidateRequired(cmd, plain); err == nil || !strings.Contains(err.Error(), "content") {
t.Fatalf("missing plain required err = %v", err)
}
_ = cmd.Flags().Set("content", "x")
if err := ValidateRequired(cmd, plain); err != nil {
t.Fatalf("satisfied plain required err = %v", err)
}
// alias satisfies required
aliased := []FlagSpec{{Name: "node", Usage: "N", Required: true, Aliases: []string{"id"}}}
cmd = newTestCommand()
RegisterFlags(cmd, aliased)
_ = cmd.Flags().Set("id", "n1")
if err := ValidateRequired(cmd, aliased); err != nil {
t.Fatalf("alias-satisfied required err = %v", err)
}
// env-backed required with explicit hint
hinted := []FlagSpec{{Name: "token", Usage: "T", Required: true, EnvVar: "DWS_CMDCORE_TOKEN", RequiredHint: "flag --token is required (or set DWS_CMDCORE_TOKEN)"}}
cmd = newTestCommand()
RegisterFlags(cmd, hinted)
if err := ValidateRequired(cmd, hinted); err == nil || !strings.Contains(err.Error(), "DWS_CMDCORE_TOKEN") {
t.Fatalf("hint err = %v", err)
}
t.Setenv("DWS_CMDCORE_TOKEN", "t")
if err := ValidateRequired(cmd, hinted); err != nil {
t.Fatalf("env-satisfied required err = %v", err)
}
// env-backed required WITHOUT hint falls back to the default wording
noHint := []FlagSpec{{Name: "key", Usage: "K", Required: true, EnvVar: "DWS_CMDCORE_ABSENT"}}
cmd = newTestCommand()
RegisterFlags(cmd, noHint)
if err := ValidateRequired(cmd, noHint); err == nil || !strings.Contains(err.Error(), "flag --key is required") {
t.Fatalf("default hint err = %v", err)
}
// non-required flags are ignored
optional := []FlagSpec{{Name: "opt", Usage: "O"}}
cmd = newTestCommand()
RegisterFlags(cmd, optional)
if err := ValidateRequired(cmd, optional); err != nil {
t.Fatalf("optional err = %v", err)
}
}
// ── toolArgs assembly ──────────────────────────────────────────────
func TestCrossPlatformCoverageBuildArgsAllKinds(t *testing.T) {
flags := []FlagSpec{
{Name: "name", Usage: "N", Bind: "userName"},
{Name: "plain", Usage: "P"},
{Name: "n", Usage: "NUM", Kind: KindInt, Bind: "count"},
{Name: "b", Usage: "B", Kind: KindBool, Bind: "flagOn"},
{Name: "ids", Usage: "IDS", Kind: KindStringSlice, Bind: "idList"},
{Name: "scope", Usage: "S", ArgDefault: "ALL"},
{Name: "note", Usage: "NOTE", OmitEmpty: true},
}
cmd := newTestCommand()
RegisterFlags(cmd, flags)
_ = cmd.Flags().Set("name", "alice")
_ = cmd.Flags().Set("n", "5")
_ = cmd.Flags().Set("b", "false")
_ = cmd.Flags().Set("ids", " a , b ")
args, err := BuildArgs(cmd, flags)
if err != nil {
t.Fatal(err)
}
if args["userName"] != "alice" || args["count"] != 5 || args["flagOn"] != false {
t.Fatalf("args = %#v", args)
}
if !reflect.DeepEqual(args["idList"], []string{"a", "b"}) {
t.Fatalf("idList = %#v", args["idList"])
}
if args["scope"] != "ALL" {
t.Fatalf("ArgDefault not applied: %#v", args["scope"])
}
if _, ok := args["note"]; ok {
t.Fatalf("OmitEmpty flag leaked: %#v", args)
}
// bind defaults to the flag name; empty non-OmitEmpty string still enters
if v, ok := args["plain"]; !ok || v != "" {
t.Fatalf("plain = %#v (want present empty string)", args["plain"])
}
// unset int / unset bool / unset slice stay out
for _, key := range []string{"count2", "flagOff", "idNone"} {
if _, ok := args[key]; ok {
t.Fatalf("unexpected key %q", key)
}
}
}
func TestCrossPlatformCoverageBuildArgsIntZeroBoolUnsetSliceEmpty(t *testing.T) {
flags := []FlagSpec{
{Name: "n", Usage: "N", Kind: KindInt},
{Name: "b", Usage: "B", Kind: KindBool},
{Name: "sl", Usage: "SL", Kind: KindStringSlice},
}
cmd := newTestCommand()
RegisterFlags(cmd, flags)
_ = cmd.Flags().Set("n", "0") // non-zero only → skipped
_ = cmd.Flags().Set("sl", " ") // blank elements → skipped
args, err := BuildArgs(cmd, flags)
if err != nil {
t.Fatal(err)
}
if len(args) != 0 {
t.Fatalf("args = %#v, want empty", args)
}
}
func TestCrossPlatformCoverageBuildArgsTransformAndErrors(t *testing.T) {
// Transform result is bound
ok := []FlagSpec{{Name: "csv", Usage: "C", Bind: "list", Transform: func(raw string) (any, error) {
return strings.Split(raw, ","), nil
}}}
cmd := newTestCommand()
RegisterFlags(cmd, ok)
_ = cmd.Flags().Set("csv", "a,b")
args, err := BuildArgs(cmd, ok)
if err != nil || !reflect.DeepEqual(args["list"], []string{"a", "b"}) {
t.Fatalf("transform args = %#v err = %v", args, err)
}
// Transform returning (nil, nil) skips the key
skip := []FlagSpec{{Name: "x", Usage: "X", Transform: func(string) (any, error) { return nil, nil }}}
cmd = newTestCommand()
RegisterFlags(cmd, skip)
_ = cmd.Flags().Set("x", "v")
args, err = BuildArgs(cmd, skip)
if err != nil {
t.Fatal(err)
}
if _, present := args["x"]; present {
t.Fatalf("nil transform should skip key: %#v", args)
}
// Transform error propagates
boom := errors.New("transform boom")
bad := []FlagSpec{{Name: "y", Usage: "Y", Transform: func(string) (any, error) { return nil, boom }}}
cmd = newTestCommand()
RegisterFlags(cmd, bad)
_ = cmd.Flags().Set("y", "v")
if _, err := BuildArgs(cmd, bad); !errors.Is(err, boom) {
t.Fatalf("transform error = %v", err)
}
// integer parse error propagates from BuildArgs
badInt := []FlagSpec{{Name: "n", Usage: "N", Kind: KindInt, EnvVar: "DWS_CMDCORE_BADINT"}}
t.Setenv("DWS_CMDCORE_BADINT", "zzz")
cmd = newTestCommand()
RegisterFlags(cmd, badInt)
if _, err := BuildArgs(cmd, badInt); err == nil || !strings.Contains(err.Error(), "invalid integer value") {
t.Fatalf("bad int err = %v", err)
}
}
// ── constraints ────────────────────────────────────────────────────
func TestCrossPlatformCoverageValidateConstraintDeclsPanics(t *testing.T) {
flags := []FlagSpec{{Name: "a", Usage: "A"}, {Name: "b", Usage: "B"}}
// valid declaration does not panic
ValidateConstraintDecls("ok", flags, []Constraint{{Kind: ExactlyOne, Flags: []string{"a", "b"}}})
mustPanic := func(name string, constraints []Constraint, needle string) {
t.Helper()
defer func() {
r := recover()
if r == nil {
t.Fatalf("%s: expected panic", name)
}
if msg, _ := r.(string); !strings.Contains(msg, needle) {
t.Fatalf("%s: panic = %v, want %q", name, r, needle)
}
}()
ValidateConstraintDecls("leafX", flags, constraints)
}
mustPanic("unknown kind", []Constraint{{Kind: "bogus", Flags: []string{"a", "b"}}}, "unknown constraint kind")
mustPanic("too few flags", []Constraint{{Kind: AtLeastOne, Flags: []string{"a"}}}, "needs at least two flags")
mustPanic("undeclared", []Constraint{{Kind: AtLeastOne, Flags: []string{"a", "zzz"}}}, "references undeclared flag")
}
func TestCrossPlatformCoverageConstraintProvided(t *testing.T) {
flags := []FlagSpec{
{Name: "s", Usage: "S", Default: "reg", Aliases: []string{"s-alias"}, EnvVar: "DWS_CMDCORE_CP"},
{Name: "b", Usage: "B", Kind: KindBool},
{Name: "sl", Usage: "SL", Kind: KindStringSlice, Aliases: []string{"sl-alias"}},
}
cmd := newTestCommand()
RegisterFlags(cmd, flags)
// registration default must NOT count as provided
if constraintProvided(cmd, flags[0]) {
t.Fatal("registration default must not count as provided")
}
if constraintProvided(cmd, flags[1]) || constraintProvided(cmd, flags[2]) {
t.Fatal("unset bool/slice must not count as provided")
}
// explicit main flag / alias / env each count
_ = cmd.Flags().Set("s", "v")
if !constraintProvided(cmd, flags[0]) {
t.Fatal("explicit main flag must count")
}
cmd = newTestCommand()
RegisterFlags(cmd, flags)
_ = cmd.Flags().Set("s-alias", "v")
if !constraintProvided(cmd, flags[0]) {
t.Fatal("explicit alias must count")
}
cmd = newTestCommand()
RegisterFlags(cmd, flags)
t.Setenv("DWS_CMDCORE_CP", "v")
if !constraintProvided(cmd, flags[0]) {
t.Fatal("env must count")
}
// whitespace-only explicit values do not count
blank := FlagSpec{Name: "w", Usage: "W", Aliases: []string{"w-alias"}}
cmd = newTestCommand()
RegisterFlags(cmd, []FlagSpec{blank})
_ = cmd.Flags().Set("w", " ")
_ = cmd.Flags().Set("w-alias", " ")
if constraintProvided(cmd, blank) {
t.Fatal("whitespace-only must not count")
}
// bool counts on Changed, slice counts on non-empty element (main or alias)
cmd = newTestCommand()
RegisterFlags(cmd, flags)
_ = cmd.Flags().Set("b", "false")
if !constraintProvided(cmd, flags[1]) {
t.Fatal("explicit bool false must count")
}
_ = cmd.Flags().Set("sl", " ")
if constraintProvided(cmd, flags[2]) {
t.Fatal("blank slice must not count")
}
_ = cmd.Flags().Set("sl-alias", "x")
if !constraintProvided(cmd, flags[2]) {
t.Fatal("alias slice must count")
}
// a non-blank main slice flag counts on its own (no alias involved)
mainSlice := newTestCommand()
RegisterFlags(mainSlice, flags)
_ = mainSlice.Flags().Set("sl", "kept")
if !constraintProvided(mainSlice, flags[2]) {
t.Fatal("non-blank main slice must count")
}
}
func TestCrossPlatformCoverageValidateConstraints(t *testing.T) {
flags := []FlagSpec{{Name: "a", Usage: "A"}, {Name: "b", Usage: "B"}}
build := func(set ...string) *cobra.Command {
cmd := newTestCommand()
RegisterFlags(cmd, flags)
for _, name := range set {
_ = cmd.Flags().Set(name, "v")
}
return cmd
}
atLeast := []Constraint{{Kind: AtLeastOne, Flags: []string{"a", "b"}}}
if err := ValidateConstraints(build(), flags, atLeast); err == nil || !strings.Contains(err.Error(), "请至少指定 --a、--b 之一") {
t.Fatalf("at_least_one(0) err = %v", err)
}
if err := ValidateConstraints(build("a"), flags, atLeast); err != nil {
t.Fatalf("at_least_one(1) err = %v", err)
}
exactly := []Constraint{{Kind: ExactlyOne, Flags: []string{"a", "b"}}}
if err := ValidateConstraints(build(), flags, exactly); err == nil || !strings.Contains(err.Error(), "请指定 --a、--b 之一") {
t.Fatalf("exactly_one(0) err = %v", err)
}
if err := ValidateConstraints(build("a"), flags, exactly); err != nil {
t.Fatalf("exactly_one(1) err = %v", err)
}
if err := ValidateConstraints(build("a", "b"), flags, exactly); err == nil ||
!strings.Contains(err.Error(), "参数 --a、--b 只能指定其一(当前指定了 --a、--b)") {
t.Fatalf("exactly_one(2) err = %v", err)
}
exclusive := []Constraint{{Kind: MutuallyExclusive, Flags: []string{"a", "b"}}}
if err := ValidateConstraints(build(), flags, exclusive); err != nil {
t.Fatalf("mutually_exclusive(0) err = %v", err)
}
if err := ValidateConstraints(build("a"), flags, exclusive); err != nil {
t.Fatalf("mutually_exclusive(1) err = %v", err)
}
if err := ValidateConstraints(build("a", "b"), flags, exclusive); err == nil ||
!strings.Contains(err.Error(), "参数 --a、--b 互斥,只能指定其一(当前指定了 --a、--b)") {
t.Fatalf("mutually_exclusive(2) err = %v", err)
}
// no constraints → nil
if err := ValidateConstraints(build(), flags, nil); err != nil {
t.Fatalf("no constraints err = %v", err)
}
}
// ── risk confirmation ──────────────────────────────────────────────
func TestCrossPlatformCoverageRiskEffective(t *testing.T) {
if Risk("").Effective() != RiskRead {
t.Fatal("empty risk must default to read")
}
if RiskWrite.Effective() != RiskWrite || RiskHighWrite.Effective() != RiskHighWrite {
t.Fatal("explicit risk must be preserved")
}
}
func TestCrossPlatformCoverageConfirmRisk(t *testing.T) {
newRiskCmd := func(stdin string) *cobra.Command {
cmd := newTestCommand()
cmd.PersistentFlags().Bool("yes", false, "")
cmd.PersistentFlags().Bool("dry-run", false, "")
if stdin != "" {
cmd.SetIn(strings.NewReader(stdin))
}
cmd.SetErr(&strings.Builder{})
return cmd
}
// read-only never prompts
if !ConfirmRisk(newRiskCmd(""), RiskRead) {
t.Fatal("read risk must pass")
}
if !ConfirmRisk(newRiskCmd(""), "") {
t.Fatal("empty risk must pass as read")
}
// --yes and --dry-run bypass the prompt
yes := newRiskCmd("")
_ = yes.PersistentFlags().Set("yes", "true")
if !ConfirmRisk(yes, RiskHighWrite) {
t.Fatal("--yes must bypass")
}
dry := newRiskCmd("")
_ = dry.PersistentFlags().Set("dry-run", "true")
if !ConfirmRisk(dry, RiskWrite) {
t.Fatal("--dry-run must bypass")
}
// interactive accept / decline
for _, answer := range []string{"yes\n", "y\n", "YES\n"} {
if !ConfirmRisk(newRiskCmd(answer), RiskWrite) {
t.Fatalf("answer %q must confirm", answer)
}
}
for _, answer := range []string{"no\n", "\n", "maybe\n"} {
if ConfirmRisk(newRiskCmd(answer), RiskHighWrite) {
t.Fatalf("answer %q must decline", answer)
}
}
}
func TestCrossPlatformCoverageBoolFlag(t *testing.T) {
if BoolFlag(nil, "yes") {
t.Fatal("nil command must report false")
}
// missing flag → false
if BoolFlag(newTestCommand(), "yes") {
t.Fatal("missing flag must report false")
}
// local flag
local := newTestCommand()
local.Flags().Bool("yes", true, "")
if !BoolFlag(local, "yes") {
t.Fatal("local flag must be read")
}
// root persistent flag read from a child (inherited path)
root := &cobra.Command{Use: "root"}
root.PersistentFlags().Bool("yes", true, "")
child := &cobra.Command{Use: "child"}
root.AddCommand(child)
if !BoolFlag(child, "yes") {
t.Fatal("root persistent flag must be visible to the child")
}
}
// ── schema projection + help ───────────────────────────────────────
func TestCrossPlatformCoverageAnnotateConstraints(t *testing.T) {
cmd := newTestCommand()
AnnotateConstraints(cmd, []Constraint{
{Kind: AtLeastOne, Flags: []string{"a", "b"}},
{Kind: ExactlyOne, Flags: []string{"c", "d"}},
{Kind: MutuallyExclusive, Flags: []string{"e", "f"}},
})
encoded := cmd.Annotations["dws.schema.constraints"]
if !strings.Contains(encoded, `"require_one_of"`) || !strings.Contains(encoded, `"mutually_exclusive"`) {
t.Fatalf("annotation = %s", encoded)
}
// exactly_one projects into BOTH require_one_of and mutually_exclusive
for _, want := range []string{`["a","b"]`, `["c","d"]`, `["e","f"]`} {
if !strings.Contains(encoded, want) {
t.Fatalf("annotation missing %s: %s", want, encoded)
}
}
// no constraints → no annotation written
bare := newTestCommand()
AnnotateConstraints(bare, nil)
if bare.Annotations["dws.schema.constraints"] != "" {
t.Fatalf("unexpected annotation %q", bare.Annotations["dws.schema.constraints"])
}
}
func TestCrossPlatformCoverageConstraintHelp(t *testing.T) {
if got := ConstraintHelp(nil); got != "" {
t.Fatalf("empty constraints help = %q", got)
}
help := ConstraintHelp([]Constraint{
{Kind: AtLeastOne, Flags: []string{"a", "b"}},
{Kind: ExactlyOne, Flags: []string{"c", "d"}},
{Kind: MutuallyExclusive, Flags: []string{"e", "f"}},
{Kind: AtLeastOne, Flags: []string{"g", "h"}, Description: "自定义文案"},
})
for _, want := range []string{
"参数约束:",
"--a、--b 至少指定一个",
"--c、--d 必须且只能指定一个",
"--e、--f 互斥,最多指定一个",
" - 自定义文案",
} {
if !strings.Contains(help, want) {
t.Fatalf("help missing %q:\n%s", want, help)
}
}
}
// ── unified builder ────────────────────────────────────────────────
func TestCrossPlatformCoverageNewCommandOrchestration(t *testing.T) {
var order []string
var gotArgs map[string]any
postMounted := false
cmd := NewCommand(CommandSpec{
Use: "route",
Short: "S",
Long: "L",
Example: "E",
Flags: []FlagSpec{
{Name: "a", Usage: "A", Bind: "aKey"},
{Name: "b", Usage: "B"},
},
Constraints: []Constraint{{Kind: MutuallyExclusive, Flags: []string{"a", "b"}}},
Validate: func(*cobra.Command, []string) error {
order = append(order, "validate")
return nil
},
PostMount: func(c *cobra.Command) {
postMounted = true
if c.RunE != nil {
t.Error("PostMount must run before RunE is assigned")
}
},
Invoke: func(_ *Ctx, toolArgs map[string]any) error {
order = append(order, "dispatch")
gotArgs = toolArgs
return nil
},
})
if !postMounted {
t.Fatal("PostMount not invoked")
}
if cmd.Use != "route" || cmd.Short != "S" || cmd.Example != "E" {
t.Fatalf("identity = %#v", cmd)
}
// constraint help appended to Long, schema annotation projected
if !strings.Contains(cmd.Long, "参数约束:") || !strings.HasPrefix(cmd.Long, "L") {
t.Fatalf("Long = %q", cmd.Long)
}
if cmd.Annotations["dws.schema.constraints"] == "" {
t.Fatal("schema constraints not projected")
}
cmd.SetArgs([]string{"--a", "v"})
if err := cmd.Execute(); err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(order, []string{"validate", "dispatch"}) {
t.Fatalf("order = %v, want validate before dispatch", order)
}
if gotArgs["aKey"] != "v" {
t.Fatalf("dispatch args = %#v", gotArgs)
}
}
func TestCrossPlatformCoverageNewCommandRunEEscapeHatch(t *testing.T) {
// RunE bypasses the whole generated pipeline: the declared Required flag is
// left unset, so had the framework body run it would have failed validation.
ran := false
cmd := NewCommand(CommandSpec{
Use: "escape",
Flags: []FlagSpec{{Name: "x", Usage: "X", Required: true}},
RunE: func(*cobra.Command, []string) error {
ran = true
return nil
},
})
cmd.SilenceErrors = true
cmd.SilenceUsage = true
cmd.SetArgs(nil)
if err := cmd.Execute(); err != nil {
t.Fatalf("RunE escape hatch must bypass validation, got %v", err)
}
if !ran {
t.Fatal("RunE escape hatch did not run")
}
}
func TestCrossPlatformCoverageNewCommandStopsOnFailures(t *testing.T) {
// required failure stops before Validate/Dispatch
validated := false
dispatched := false
spec := CommandSpec{
Use: "gate",
Flags: []FlagSpec{{Name: "need", Usage: "N", Required: true}, {Name: "other", Usage: "O"}},
Constraints: []Constraint{
{Kind: MutuallyExclusive, Flags: []string{"need", "other"}},
},
Validate: func(*cobra.Command, []string) error {
validated = true
return nil
},
Invoke: func(*Ctx, map[string]any) error {
dispatched = true
return nil
},
}
cmd := NewCommand(spec)
cmd.SetArgs(nil)
if err := cmd.Execute(); err == nil || !strings.Contains(err.Error(), "need") {
t.Fatalf("required err = %v", err)
}
if validated || dispatched {
t.Fatal("required failure must stop the pipeline")
}
// constraint failure stops before Validate/Dispatch
validated, dispatched = false, false
cmd = NewCommand(spec)
cmd.SetArgs([]string{"--need", "a", "--other", "b"})
if err := cmd.Execute(); err == nil || !strings.Contains(err.Error(), "互斥") {
t.Fatalf("constraint err = %v", err)
}
if validated || dispatched {
t.Fatal("constraint failure must stop before Validate")
}
// Validate hook failure stops before Dispatch
boom := errors.New("validate boom")
dispatched = false
cmd = NewCommand(CommandSpec{
Use: "hook",
Flags: []FlagSpec{{Name: "x", Usage: "X"}},
Validate: func(*cobra.Command, []string) error { return boom },
Invoke: func(*Ctx, map[string]any) error {
dispatched = true
return nil
},
})
cmd.SetArgs([]string{"--x", "v"})
if err := cmd.Execute(); !errors.Is(err, boom) {
t.Fatalf("validate hook err = %v", err)
}
if dispatched {
t.Fatal("Validate failure must stop before Dispatch")
}
// BuildArgs failure stops before Dispatch
dispatched = false
cmd = NewCommand(CommandSpec{
Use: "args",
Flags: []FlagSpec{{Name: "y", Usage: "Y", Transform: func(string) (any, error) { return nil, boom }}},
Invoke: func(*Ctx, map[string]any) error {
dispatched = true
return nil
},
})
cmd.SetArgs([]string{"--y", "v"})
if err := cmd.Execute(); !errors.Is(err, boom) {
t.Fatalf("BuildArgs err = %v", err)
}
if dispatched {
t.Fatal("BuildArgs failure must stop before Dispatch")
}
}
func TestCrossPlatformCoverageNewCommandDeclineCancels(t *testing.T) {
dispatched := false
cmd := NewCommand(CommandSpec{
Use: "risky",
Risk: RiskHighWrite,
Flags: []FlagSpec{{Name: "x", Usage: "X"}},
Invoke: func(*Ctx, map[string]any) error {
dispatched = true
return nil
},
})
cmd.PersistentFlags().Bool("yes", false, "")
cmd.PersistentFlags().Bool("dry-run", false, "")
cmd.SilenceErrors = true
cmd.SilenceUsage = true
cmd.SetIn(strings.NewReader("no\n"))
cmd.SetErr(&strings.Builder{})
cmd.SetArgs([]string{"--x", "v"})
if err := cmd.Execute(); err == nil || !strings.Contains(err.Error(), "用户取消了操作") {
t.Fatalf("decline err = %v", err)
}
if dispatched {
t.Fatal("declined command must not dispatch")
}
}
func TestCrossPlatformCoverageNewCommandRequiresExactlyOneDispatcher(t *testing.T) {
mustPanic := func(name string, spec CommandSpec, needle string) {
t.Helper()
defer func() {
r := recover()
if r == nil {
t.Fatalf("%s: expected panic", name)
}
if msg, _ := r.(string); !strings.Contains(msg, needle) {
t.Fatalf("%s: panic = %v, want %q", name, r, needle)
}
}()
NewCommand(spec)
}
flags := []FlagSpec{{Name: "x", Usage: "X"}}
// Zero dispatchers: a spec with no runnable body must never reach run time,
// where it would have prompted for confirmation and then exited 0 doing nothing.
mustPanic("no dispatcher", CommandSpec{Use: "bare", Risk: RiskHighWrite, Flags: flags},
"must declare exactly one of RunE/Invoke/Orchestrate, got 0")
// Two competing dispatchers are equally a programming error.
mustPanic("two dispatchers", CommandSpec{
Use: "both",
Flags: flags,
Invoke: func(*Ctx, map[string]any) error { return nil },
Orchestrate: func(*Ctx) error { return nil },
}, "got 2")
mustPanic("runE plus invoke", CommandSpec{
Use: "both2",
Flags: flags,
RunE: func(*cobra.Command, []string) error { return nil },
Invoke: func(*Ctx, map[string]any) error { return nil },
}, "got 2")
}
func TestCrossPlatformCoverageNewCommandOrchestrateDispatch(t *testing.T) {
var seen struct {
str string
n int
flag bool
slice []string
args []string
dry bool
yes bool
chg bool
}
t.Setenv("DWS_CMDCORE_ORCH_ENV", "from-env")
t.Setenv("DWS_CMDCORE_ORCH_BADINT", "not-a-number")
cmd := NewCommand(CommandSpec{
Use: "orch",
Flags: []FlagSpec{
{Name: "name", Usage: "N", Aliases: []string{"n-alias"}},
{Name: "env", Usage: "E", EnvVar: "DWS_CMDCORE_ORCH_ENV"},
{Name: "count", Usage: "C", Kind: KindInt},
{Name: "bad", Usage: "B", Kind: KindInt, EnvVar: "DWS_CMDCORE_ORCH_BADINT"},
{Name: "on", Usage: "O", Kind: KindBool},
{Name: "ids", Usage: "I", Kind: KindStringSlice},
},
Orchestrate: func(c *Ctx) error {
// Ctx accessors must honor the declared alias/env fallback chain.
seen.str = c.Str("name")
seen.n = c.Int("count")
seen.flag = c.Bool("on")
seen.slice = c.StrSlice("ids")
seen.args = c.Args()
seen.dry = c.DryRun()
seen.yes = c.Yes()
seen.chg = c.Changed("count")
if got := c.Str("env"); got != "from-env" {
t.Errorf("Ctx.Str(env) = %q, want env fallback", got)
}
if c.Command() == nil || c.Command().Use != "orch" {
t.Errorf("Ctx.Command() = %v", c.Command())
}
// Undeclared names resolve to zero values rather than panicking.
if c.Str("nope") != "" || c.Int("nope") != 0 || c.StrSlice("nope") != nil {
t.Error("undeclared flag must yield zero values")
}
// An unparseable effective value degrades to 0 instead of erroring.
if got := c.Int("bad"); got != 0 {
t.Errorf("Ctx.Int(bad) = %d, want 0", got)
}
return nil
},
})
cmd.Args = cobra.ArbitraryArgs
cmd.PersistentFlags().Bool("dry-run", false, "")
cmd.PersistentFlags().Bool("yes", false, "")
cmd.SilenceErrors = true
cmd.SilenceUsage = true
cmd.SetArgs([]string{"--n-alias", "via-alias", "--count", "4", "--on", "--ids", " a , b ", "--yes", "pos1"})
if err := cmd.Execute(); err != nil {
t.Fatal(err)
}
if seen.str != "via-alias" {
t.Fatalf("Ctx.Str(name) = %q, want alias value", seen.str)
}
if seen.n != 4 || !seen.flag || !seen.chg {
t.Fatalf("typed accessors = %+v", seen)
}
if !reflect.DeepEqual(seen.slice, []string{"a", "b"}) {
t.Fatalf("Ctx.StrSlice = %#v", seen.slice)
}
if !reflect.DeepEqual(seen.args, []string{"pos1"}) {
t.Fatalf("Ctx.Args = %#v", seen.args)
}
if seen.dry || !seen.yes {
t.Fatalf("global flags: dry=%v yes=%v", seen.dry, seen.yes)
}
}
func TestCrossPlatformCoverageNewCommandOrchestrateHonorsConfirmation(t *testing.T) {
ran := false
build := func() *cobra.Command {
cmd := NewCommand(CommandSpec{
Use: "risky-orch",
Risk: RiskHighWrite,
Flags: []FlagSpec{{Name: "x", Usage: "X"}},
Orchestrate: func(*Ctx) error { ran = true; return nil },
})
cmd.PersistentFlags().Bool("dry-run", false, "")
cmd.PersistentFlags().Bool("yes", false, "")
cmd.SilenceErrors = true
cmd.SilenceUsage = true
cmd.SetErr(&strings.Builder{})
return cmd
}
// Declining must abort before the orchestration body runs.
declined := build()
declined.SetIn(strings.NewReader("no\n"))
declined.SetArgs([]string{"--x", "v"})
if err := declined.Execute(); err == nil || !strings.Contains(err.Error(), "用户取消了操作") {
t.Fatalf("declined orchestrate err = %v", err)
}
if ran {
t.Fatal("declined orchestrate must not run the body")
}
// --yes bypasses the prompt and runs it.
confirmed := build()
confirmed.SetArgs([]string{"--x", "v", "--yes"})
if err := confirmed.Execute(); err != nil {
t.Fatal(err)
}
if !ran {
t.Fatal("confirmed orchestrate did not run")
}
}