133 lines
6.5 KiB
Go
133 lines
6.5 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 handlers
|
|
|
|
import (
|
|
"errors"
|
|
"reflect"
|
|
"testing"
|
|
|
|
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/pipeline"
|
|
)
|
|
|
|
func TestBoolValueHandler(t *testing.T) {
|
|
boolFlags := []pipeline.FlagInfo{{Name: "yes", Shorthand: "y", Type: "bool"}}
|
|
tests := []struct {
|
|
name string
|
|
args []string
|
|
flags []pipeline.FlagInfo
|
|
protected []string
|
|
want []string
|
|
corrections int
|
|
conflict bool
|
|
}{
|
|
{name: "long false", args: []string{"--yes", "false"}, flags: boolFlags, want: []string{"--yes=false"}, corrections: 1},
|
|
{name: "long mixed-case false", args: []string{"--yes", "FaLsE"}, flags: boolFlags, want: []string{"--yes=false"}, corrections: 1},
|
|
{name: "long true synonym", args: []string{"--yes", "yes"}, flags: boolFlags, want: []string{"--yes=true"}, corrections: 1},
|
|
{name: "shorthand zero", args: []string{"-y", "0"}, flags: boolFlags, want: []string{"--yes=false"}, corrections: 1},
|
|
{name: "shorthand equals synonym", args: []string{"-y=off"}, flags: boolFlags, want: []string{"--yes=false"}, corrections: 1},
|
|
{name: "preserves following flags", args: []string{"--yes", "off", "--format", "json"}, flags: boolFlags, want: []string{"--yes=false", "--format", "json"}, corrections: 1},
|
|
{name: "explicit equals stays native", args: []string{"--yes=false"}, flags: boolFlags, want: []string{"--yes=false"}},
|
|
{name: "explicit equals synonym normalizes", args: []string{"--yes=No"}, flags: boolFlags, want: []string{"--yes=false"}, corrections: 1},
|
|
{name: "bare bool stays native", args: []string{"--yes"}, flags: boolFlags, want: []string{"--yes"}},
|
|
{name: "invalid literal is positional", args: []string{"--yes", "maybe"}, flags: boolFlags, want: []string{"--yes", "maybe"}},
|
|
{name: "invalid inline literal stays native", args: []string{"--yes=maybe"}, flags: boolFlags, want: []string{"--yes=maybe"}},
|
|
{name: "non bool flag is unchanged", args: []string{"--name", "false"}, flags: []pipeline.FlagInfo{{Name: "name", Type: "string"}}, want: []string{"--name", "false"}},
|
|
{name: "unknown flag is unchanged", args: []string{"--confirm", "false"}, flags: boolFlags, want: []string{"--confirm", "false"}},
|
|
{name: "shorthand cluster is unchanged", args: []string{"-vy", "false"}, flags: boolFlags, want: []string{"-vy", "false"}},
|
|
{name: "protected bool is unchanged", args: []string{"--yes", "false"}, flags: boolFlags, protected: []string{"yes"}, want: []string{"--yes", "false"}},
|
|
{name: "protected noncanonical bool uses morphed key", args: []string{"--dry_run", "false"}, flags: []pipeline.FlagInfo{{Name: "dry_run", Type: "bool"}}, protected: []string{"dry-run"}, want: []string{"--dry_run", "false"}},
|
|
{name: "stops at double dash", args: []string{"--", "--yes", "false"}, flags: boolFlags, want: []string{"--", "--yes", "false"}},
|
|
{name: "no specs", args: []string{"--yes", "false"}, want: []string{"--yes", "false"}},
|
|
{name: "identical repeated values remain valid", args: []string{"--yes", "--yes=true", "--yes", "yes"}, flags: boolFlags, want: []string{"--yes", "--yes=true", "--yes=true"}, corrections: 1},
|
|
{name: "contradictory detached values fail", args: []string{"--yes", "true", "--yes", "false"}, flags: boolFlags, want: []string{"--yes=true", "--yes=false"}, corrections: 2, conflict: true},
|
|
{name: "bare and explicit false fail", args: []string{"--yes", "--yes=false"}, flags: boolFlags, want: []string{"--yes", "--yes=false"}, conflict: true},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
ctx := &pipeline.Context{
|
|
Args: append([]string(nil), test.args...),
|
|
FlagSpecs: test.flags,
|
|
}
|
|
for _, protected := range test.protected {
|
|
ctx.ProtectFlag(protected, pipeline.FlagProtectionBlocked)
|
|
}
|
|
handler := BoolValueHandler{}
|
|
err := handler.Handle(ctx)
|
|
var conflict *pipeline.BoolValueConflictError
|
|
if test.conflict {
|
|
if !errors.As(err, &conflict) {
|
|
t.Fatalf("Handle() error = %v, want BoolValueConflictError", err)
|
|
}
|
|
if conflict.Flag != "yes" || !reflect.DeepEqual(conflict.Values, []string{"false", "true"}) {
|
|
t.Fatalf("conflict = %#v", conflict)
|
|
}
|
|
} else if err != nil {
|
|
t.Fatalf("Handle() error = %v", err)
|
|
}
|
|
if !reflect.DeepEqual(ctx.Args, test.want) {
|
|
t.Fatalf("Args = %#v, want %#v", ctx.Args, test.want)
|
|
}
|
|
if len(ctx.Corrections) != test.corrections {
|
|
t.Fatalf("Corrections = %#v, want %d", ctx.Corrections, test.corrections)
|
|
}
|
|
if test.corrections > 0 {
|
|
correction := ctx.Corrections[0]
|
|
if correction.Handler != "boolvalue" || correction.Kind != "explicit-bool" || correction.Field != "--yes" {
|
|
t.Fatalf("correction metadata = %#v", correction)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestMatchBooleanFlagToken(t *testing.T) {
|
|
longNames := map[string]pipeline.FlagInfo{"yes": {Name: "yes", Shorthand: "y", Type: "bool"}}
|
|
shortNames := map[string]pipeline.FlagInfo{"y": longNames["yes"]}
|
|
tests := []struct {
|
|
argument string
|
|
value string
|
|
hasValue bool
|
|
matched bool
|
|
}{
|
|
{argument: "--yes", matched: true},
|
|
{argument: "--yes=false", value: "false", hasValue: true, matched: true},
|
|
{argument: "-y", matched: true},
|
|
{argument: "-y=true", value: "true", hasValue: true, matched: true},
|
|
{argument: "-vy"},
|
|
{argument: "--unknown=false"},
|
|
{argument: "yes"},
|
|
{argument: "--"},
|
|
}
|
|
for _, test := range tests {
|
|
t.Run(test.argument, func(t *testing.T) {
|
|
spec, value, hasValue, matched := matchBooleanFlagToken(test.argument, longNames, shortNames)
|
|
if value != test.value || hasValue != test.hasValue || matched != test.matched {
|
|
t.Fatalf("matchBooleanFlagToken(%q) = %#v, %q, %v, %v", test.argument, spec, value, hasValue, matched)
|
|
}
|
|
if matched && spec.Name != "yes" {
|
|
t.Fatalf("matched spec = %#v", spec)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestBoolValueHandlerMeta(t *testing.T) {
|
|
handler := BoolValueHandler{}
|
|
if handler.Name() != "boolvalue" || handler.Phase() != pipeline.PreParse {
|
|
t.Fatalf("handler metadata = %q/%v", handler.Name(), handler.Phase())
|
|
}
|
|
}
|