Files
dingtalk-workspace-cli/internal/pipeline/handlers/boolvalue_test.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())
}
}