test(08-07): add failing repeatable-flag RED anchor for bonfire
- TestPhase8RedBonfireFlags asserts Input.Flags preserves ordered repeated --redirect-uri/--scope values while existing scalar --mode flags via Input.Flag stay unaffected - Adds the compiling seam (Flag.Repeatable, Input.Flags, cobraInput.Flags) without wiring Cobra StringSlice registration yet, so the test fails with PHASE8_RED:bonfire-flags (D-19)
This commit is contained in:
@@ -28,6 +28,13 @@ type Flag struct {
|
|||||||
Default string
|
Default string
|
||||||
// Bare allows --flag without a value; cobra stores "true".
|
// Bare allows --flag without a value; cobra stores "true".
|
||||||
Bare bool
|
Bare bool
|
||||||
|
// Repeatable registers this flag as an ordered, multi-occurrence
|
||||||
|
// string flag (Cobra StringSlice) instead of a scalar string flag
|
||||||
|
// (08-CONTEXT.md D-19; 08-PATTERNS.md "bonfire repeatable-flag gap").
|
||||||
|
// Repeated `--name=a --name=b` preserves insertion order and is read
|
||||||
|
// back through Input.Flags, never Input.Flag. Existing scalar/bare
|
||||||
|
// flags are unaffected: Repeatable defaults to false.
|
||||||
|
Repeatable bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// Arg describes a positional argument.
|
// Arg describes a positional argument.
|
||||||
@@ -42,6 +49,10 @@ type Input interface {
|
|||||||
Args() []string
|
Args() []string
|
||||||
Argument(name string) (string, bool)
|
Argument(name string) (string, bool)
|
||||||
Flag(name string) (string, bool)
|
Flag(name string) (string, bool)
|
||||||
|
// Flags returns the ordered, repeated values of a Repeatable flag
|
||||||
|
// (D-19). It returns nil for a scalar/bare flag or an unset repeatable
|
||||||
|
// flag; callers must use Flag for scalar flags.
|
||||||
|
Flags(name string) []string
|
||||||
}
|
}
|
||||||
|
|
||||||
type cobraInput struct {
|
type cobraInput struct {
|
||||||
@@ -64,6 +75,20 @@ func (in cobraInput) Argument(name string) (string, bool) {
|
|||||||
return "", false
|
return "", false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Flags returns the ordered values of a Repeatable (Cobra StringSlice)
|
||||||
|
// flag. It returns nil if the flag was never registered as repeatable
|
||||||
|
// (wrong pflag type) or the command has no flag set (08-CONTEXT.md D-19).
|
||||||
|
func (in cobraInput) Flags(name string) []string {
|
||||||
|
if in.cmd == nil || in.cmd.Flags() == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
vals, err := in.cmd.Flags().GetStringSlice(name)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return vals
|
||||||
|
}
|
||||||
|
|
||||||
func (in cobraInput) Flag(name string) (string, bool) {
|
func (in cobraInput) Flag(name string) (string, bool) {
|
||||||
if in.cmd == nil || in.cmd.Flags() == nil {
|
if in.cmd == nil || in.cmd.Flags() == nil {
|
||||||
return "", false
|
return "", false
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
@@ -244,6 +245,61 @@ func TestNonTTYProgressSkipsIntraDecile(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestPhase8RedBonfireFlags is the Phase 8 Wave 7 RED anchor (08-07-PLAN.md
|
||||||
|
// Task 1, D-19). bonfire.Flag/Input are scalar-only today
|
||||||
|
// (08-PATTERNS.md "bonfire repeatable-flag gap"): a Repeatable flag
|
||||||
|
// registered through Command.Flags must preserve every repeated
|
||||||
|
// `--name=value` occurrence in order via Input.Flags, while an ordinary
|
||||||
|
// scalar flag declared alongside it keeps working through Input.Flag
|
||||||
|
// unmodified. It fails with the PHASE8_RED:bonfire-flags sentinel while
|
||||||
|
// Repeatable is not yet wired into Cobra registration;
|
||||||
|
// scripts/check-phase8-red.sh verifies this failure is fail-closed.
|
||||||
|
func TestPhase8RedBonfireFlags(t *testing.T) {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
var gotRedirect, gotScope []string
|
||||||
|
var gotMode string
|
||||||
|
var gotModeOK bool
|
||||||
|
root, err := NewRootIO("app", []Command{{
|
||||||
|
Name: "demo:repeat",
|
||||||
|
Description: "Demo repeatable flags",
|
||||||
|
Flags: []Flag{
|
||||||
|
{Name: "redirect-uri", Repeatable: true, Description: "repeatable redirect URI"},
|
||||||
|
{Name: "scope", Repeatable: true, Description: "repeatable scope"},
|
||||||
|
{Name: "mode", Default: "slow", Description: "scalar, unaffected by D-19"},
|
||||||
|
},
|
||||||
|
Run: func(ctx context.Context, in Input, out Output) error {
|
||||||
|
gotRedirect = in.Flags("redirect-uri")
|
||||||
|
gotScope = in.Flags("scope")
|
||||||
|
gotMode, gotModeOK = in.Flag("mode")
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}}, strings.NewReader(""), &buf, &buf)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
root.SetArgs([]string{
|
||||||
|
"demo:repeat",
|
||||||
|
"--redirect-uri=https://a.example/callback",
|
||||||
|
"--redirect-uri=https://b.example/callback",
|
||||||
|
"--scope=read",
|
||||||
|
"--mode=fast",
|
||||||
|
})
|
||||||
|
if err := root.Execute(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
wantRedirect := []string{"https://a.example/callback", "https://b.example/callback"}
|
||||||
|
if !slices.Equal(gotRedirect, wantRedirect) {
|
||||||
|
t.Fatalf("PHASE8_RED:bonfire-flags: repeated --redirect-uri via Input.Flags = %v, want %v in insertion order", gotRedirect, wantRedirect)
|
||||||
|
}
|
||||||
|
if !slices.Equal(gotScope, []string{"read"}) {
|
||||||
|
t.Fatalf("PHASE8_RED:bonfire-flags: repeated --scope via Input.Flags = %v, want [read]", gotScope)
|
||||||
|
}
|
||||||
|
if !gotModeOK || gotMode != "fast" {
|
||||||
|
t.Fatalf("PHASE8_RED:bonfire-flags: existing scalar --mode via Input.Flag = (%q, %v), want (\"fast\", true) unaffected by Repeatable", gotMode, gotModeOK)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestHelpUsesSharedAdapter(t *testing.T) {
|
func TestHelpUsesSharedAdapter(t *testing.T) {
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
root, err := NewRoot("hello", []Command{{
|
root, err := NewRoot("hello", []Command{{
|
||||||
|
|||||||
Reference in New Issue
Block a user