Files
dingtalk-workspace-cli/internal/cmdcore/cmdcore_test.go
T
玉澜andCursor 41fdf79aea refactor(leaf): declare params on LeafSpec; keep Call as execution
Lift business flags/const params out of Call/PostMount, add typed
flag defaults and policy gates, and realign the RFC acceptance bar to
"no Execute/Call body exists only to assemble params".

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-30 20:32:08 +08:00

1068 lines
35 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")
}
}
func TestRegisterFlagTypedDefaults(t *testing.T) {
cmd := newTestCommand()
RegisterFlag(cmd, KindInt, "page-size", "20", "page")
RegisterFlag(cmd, KindBool, "flag", "true", "bool")
if def, _ := cmd.Flags().GetInt("page-size"); def != 20 {
t.Fatalf("int default = %d, want 20", def)
}
if def, _ := cmd.Flags().GetBool("flag"); !def {
t.Fatal("bool default = false, want true")
}
if got := cmd.Flags().Lookup("page-size").DefValue; got != "20" {
t.Fatalf("page-size DefValue = %q, want 20", got)
}
}
func TestBuildArgsIntArgDefaultFloor(t *testing.T) {
flags := []FlagSpec{{
Name: "page-size", Usage: "P", Kind: KindInt,
Default: "20", ArgDefault: "20", Bind: "pageSize",
}}
cmd := newTestCommand()
RegisterFlags(cmd, flags)
args, err := BuildArgs(cmd, flags)
if err != nil {
t.Fatal(err)
}
if args["pageSize"] != 20 {
t.Fatalf("unset pageSize = %#v, want 20", args["pageSize"])
}
_ = cmd.Flags().Set("page-size", "0")
args, err = BuildArgs(cmd, flags)
if err != nil {
t.Fatal(err)
}
if args["pageSize"] != 20 {
t.Fatalf("zero pageSize = %#v, want floor 20", args["pageSize"])
}
}
func TestNewCommandMergesConstParams(t *testing.T) {
var got map[string]any
cmd := NewCommand(CommandSpec{
Use: "pub",
Flags: []FlagSpec{{Name: "id", Usage: "ID", Bind: "versionId", Trim: true}},
ConstParams: map[string]any{"precheckOnly": false},
Invoke: func(_ *Ctx, toolArgs map[string]any) error {
got = toolArgs
return nil
},
})
_ = cmd.Flags().Set("id", "V1")
if err := cmd.RunE(cmd, nil); err != nil {
t.Fatal(err)
}
if got["versionId"] != "V1" {
t.Fatalf("versionId = %#v", got["versionId"])
}
if got["precheckOnly"] != false {
t.Fatalf("precheckOnly = %#v, want false", got["precheckOnly"])
}
}