* chore: update coverage badge [skip ci] * chore: update coverage badge [skip ci] * chore: update coverage badge [skip ci] * chore: update coverage badge [skip ci] * chore: update coverage badge [skip ci] * 1.0.19 changelog * stick opt * fix(cli): utf-8 safe sticky suffix guard + changelog (#272) SuffixLooksLikeValue used to read suffix[0] (a single byte) for both the uuid format branch and the fallback "is the first rune a letter?" check. For multi-byte UTF-8 leading runes — common in dws because value text is often Chinese — this picked up only the first byte (0xE0..0xF4 lead), which is not a letter and not a hex digit, so the function silently returned true and let glued tokens like --name<CJK> get split into --name <CJK>... This switches both branches to utf8.DecodeRuneInString and adds a utf8.RuneError guard so invalid UTF-8 input is rejected too. Also locks down the new behaviour in CHANGELOG ## [Unreleased]: - Changed: schema-aware sticky flag splitting - Added: available_flags field on unknown-flag errors Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: Cursor <cursoragent@cursor.com>
107 lines
3.5 KiB
Go
107 lines
3.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 cmdutil
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/spf13/cobra"
|
|
)
|
|
|
|
func TestSuggestFlagFix_falseGlue_starttime1(t *testing.T) {
|
|
cmd := &cobra.Command{Use: "list"}
|
|
cmd.Flags().String("start", "", "begin time")
|
|
_ = cmd.Flags().SetAnnotation("start", "x-cli-format", []string{"date-time"})
|
|
|
|
err := errUnknownFlag("starttime1")
|
|
fix := SuggestFlagFix(cmd, err)
|
|
if strings.Contains(fix.Suggestion, "Space required") {
|
|
t.Fatalf("should not treat as glued value, got %q", fix.Suggestion)
|
|
}
|
|
if fix.AutoFixFlag != "" {
|
|
t.Fatalf("AutoFixFlag = %q, want empty", fix.AutoFixFlag)
|
|
}
|
|
if !strings.Contains(fix.Suggestion, "--help") {
|
|
t.Fatalf("expected help fallback, got %q", fix.Suggestion)
|
|
}
|
|
}
|
|
|
|
func TestSuggestFlagFix_trueGlue_isoDateSuffix(t *testing.T) {
|
|
cmd := &cobra.Command{Use: "list"}
|
|
cmd.Flags().String("start", "", "begin time")
|
|
_ = cmd.Flags().SetAnnotation("start", "x-cli-format", []string{"date-time"})
|
|
|
|
err := errUnknownFlag("start2026-02-07")
|
|
fix := SuggestFlagFix(cmd, err)
|
|
wantSub := "Space required between flag and value: --start 2026-02-07"
|
|
if fix.Suggestion != wantSub {
|
|
t.Fatalf("Suggestion = %q, want %q", fix.Suggestion, wantSub)
|
|
}
|
|
if fix.AutoFixFlag != "start" || fix.AutoFixValue != "2026-02-07" {
|
|
t.Fatalf("AutoFix = %q/%q, want start/2026-02-07", fix.AutoFixFlag, fix.AutoFixValue)
|
|
}
|
|
}
|
|
|
|
func TestSuggestFlagFix_levenshteinAddsUsage(t *testing.T) {
|
|
cmd := &cobra.Command{Use: "send"}
|
|
cmd.Flags().String("conversation-id", "", "Conversation id")
|
|
|
|
err := errUnknownFlag("conversaton-id")
|
|
fix := SuggestFlagFix(cmd, err)
|
|
if !strings.HasPrefix(fix.Suggestion, "Did you mean --conversation-id?") {
|
|
t.Fatalf("unexpected: %q", fix.Suggestion)
|
|
}
|
|
if !strings.Contains(fix.Suggestion, "Conversation id") {
|
|
t.Fatalf("expected usage in hint, got %q", fix.Suggestion)
|
|
}
|
|
}
|
|
|
|
func TestSuggestFlagFix_skipsHiddenAlias(t *testing.T) {
|
|
cmd := &cobra.Command{Use: "list"}
|
|
cmd.Flags().String("start", "", "begin time")
|
|
_ = cmd.Flags().SetAnnotation("start", "x-cli-format", []string{"date-time"})
|
|
cmd.Flags().String("start-time", "", "")
|
|
_ = cmd.Flags().MarkHidden("start-time")
|
|
|
|
fix := SuggestFlagFix(cmd, errUnknownFlag("starttime1"))
|
|
if strings.Contains(fix.Suggestion, "start-time") {
|
|
t.Fatalf("must not recommend hidden alias, got %q", fix.Suggestion)
|
|
}
|
|
if !strings.Contains(fix.Suggestion, "--help") {
|
|
t.Fatalf("expected help fallback, got %q", fix.Suggestion)
|
|
}
|
|
}
|
|
|
|
func TestVisibleFlagNames_skipsHiddenAndInternal(t *testing.T) {
|
|
cmd := &cobra.Command{Use: "x"}
|
|
cmd.Flags().String("alpha", "", "")
|
|
cmd.Flags().String("json", "", "")
|
|
cmd.Flags().String("beta", "", "")
|
|
_ = cmd.Flags().MarkHidden("beta")
|
|
|
|
names := VisibleFlagNames(cmd)
|
|
if len(names) != 1 || names[0] != "alpha" {
|
|
t.Fatalf("got %v, want [alpha]", names)
|
|
}
|
|
}
|
|
|
|
func errUnknownFlag(body string) error {
|
|
return &stubFlagErr{msg: "unknown flag: --" + body}
|
|
}
|
|
|
|
type stubFlagErr struct{ msg string }
|
|
|
|
func (e *stubFlagErr) Error() string { return e.msg }
|