2026-10-07 13:44:46 +00:00
|
|
|
package commandkit
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"bytes"
|
|
|
|
|
"context"
|
|
|
|
|
"reflect"
|
|
|
|
|
"strconv"
|
|
|
|
|
"strings"
|
|
|
|
|
"testing"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
func TestConfigScopeDiscoveryOrdersAndDeduplicatesCapabilities(t *testing.T) {
|
|
|
|
|
app, err := New(Identity{Name: "example"})
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
newContextCalls := 0
|
|
|
|
|
acquireCalls := 0
|
|
|
|
|
suite := ConfigSuite[struct{}]{
|
|
|
|
|
NewContext: func() struct{} { newContextCalls++; return struct{}{} },
|
|
|
|
|
Scopes: []ConfigScope[struct{}]{
|
|
|
|
|
{Name: "zeta", Operations: []ConfigOperation{ConfigExplain, ConfigShow, ConfigExplain}, Acquire: func(context.Context, ConfigRequest, struct{}) (Acquired[ConfigView], error) {
|
|
|
|
|
acquireCalls++
|
|
|
|
|
return Acquired[ConfigView]{}, nil
|
|
|
|
|
}},
|
|
|
|
|
{Name: "promptkit", Operations: []ConfigOperation{ConfigSources}, Acquire: noopConfigAcquire[struct{}]},
|
|
|
|
|
{Name: "app", Operations: []ConfigOperation{ConfigShow, ConfigValidate, ConfigShow}, Acquire: func(context.Context, ConfigRequest, struct{}) (Acquired[ConfigView], error) {
|
|
|
|
|
acquireCalls++
|
|
|
|
|
return Acquired[ConfigView]{}, nil
|
|
|
|
|
}},
|
|
|
|
|
{Name: "metadata", Summary: "No provider is required."},
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
normalized, err := normalizeConfigSuite(suite)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
if !reflect.DeepEqual(normalized.Scopes[0].Operations, []ConfigOperation{ConfigShow, ConfigExplain}) || !reflect.DeepEqual(normalized.Scopes[2].Operations, []ConfigOperation{ConfigValidate, ConfigShow}) {
|
|
|
|
|
t.Fatalf("normalized capabilities = %#v", normalized.Scopes)
|
|
|
|
|
}
|
|
|
|
|
if err := RegisterConfig(app, suite); err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var textOutput bytes.Buffer
|
|
|
|
|
outcome := app.Execute(context.Background(), []string{"config", "scopes"}, Streams{Out: &textOutput})
|
|
|
|
|
if outcome.Kind != OutcomeSuccess {
|
|
|
|
|
t.Fatalf("text inventory outcome = %#v", outcome)
|
|
|
|
|
}
|
2026-10-07 14:17:46 +00:00
|
|
|
wantText := "app: validate, show\npromptkit: sources\nmetadata:\nzeta: show, explain\n"
|
2026-10-07 13:44:46 +00:00
|
|
|
if textOutput.String() != wantText {
|
|
|
|
|
t.Fatalf("text inventory = %q, want %q", textOutput.String(), wantText)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var yamlOutput bytes.Buffer
|
|
|
|
|
outcome = app.Execute(context.Background(), []string{"config", "scopes", "--format", "yaml"}, Streams{Out: &yamlOutput})
|
|
|
|
|
if outcome.Kind != OutcomeSuccess {
|
|
|
|
|
t.Fatalf("YAML inventory outcome = %#v", outcome)
|
|
|
|
|
}
|
|
|
|
|
if got := yamlOutput.String(); !strings.Contains(got, "scopes:") || strings.Index(got, "name: app") > strings.Index(got, "name: promptkit") || !strings.Contains(got, "operations: []") {
|
|
|
|
|
t.Fatalf("YAML inventory = %q", got)
|
|
|
|
|
}
|
|
|
|
|
outcome = app.Execute(context.Background(), []string{"config", "scopes", "--help"}, Streams{})
|
|
|
|
|
if outcome.Kind != OutcomeSuccess {
|
|
|
|
|
t.Fatalf("scope help outcome = %#v", outcome)
|
|
|
|
|
}
|
|
|
|
|
outcome = app.Execute(context.Background(), []string{"config", "scopes", "--format", "json"}, Streams{})
|
|
|
|
|
if outcome.Kind != OutcomeUsage {
|
|
|
|
|
t.Fatalf("invalid format outcome = %#v", outcome)
|
|
|
|
|
}
|
|
|
|
|
if newContextCalls != 0 || acquireCalls != 0 {
|
|
|
|
|
t.Fatalf("discovery called runtime callbacks: NewContext=%d Acquire=%d", newContextCalls, acquireCalls)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestConfigInputDefaultsToAppAndRetainsAbsentSelections(t *testing.T) {
|
|
|
|
|
suite := ConfigSuite[struct{}]{Scopes: []ConfigScope[struct{}]{{Name: "app"}}}
|
|
|
|
|
prepared, err := prepareInput(CommandSpec[configInput[struct{}]]{Flags: configInputFlags(suite, "yaml")}, make([][]string, 4), nil, true)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
input := prepared.build()
|
|
|
|
|
if input.Scope != "app" || input.Format != "yaml" || input.Selection.Config.Set || input.Selection.Overlay.Set {
|
|
|
|
|
t.Fatalf("configuration defaults = %#v", input)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-09 01:46:19 +00:00
|
|
|
func TestRegisterConfigOwnsPreparedScopeAndSelectorMetadata(t *testing.T) {
|
|
|
|
|
type selectorInput struct{ Region Supplied[string] }
|
|
|
|
|
var acquired selectorInput
|
|
|
|
|
suite := ConfigSuite[selectorInput]{
|
|
|
|
|
Selectors: []Selector[selectorInput]{{
|
|
|
|
|
Binding: ValueFlag[selectorInput](FlagMeta{Name: "region"}, nil, parseString, func(input *selectorInput, value Supplied[string]) { input.Region = value }),
|
|
|
|
|
Scopes: []string{"app"},
|
|
|
|
|
}},
|
|
|
|
|
Scopes: []ConfigScope[selectorInput]{{
|
|
|
|
|
Name: "app",
|
|
|
|
|
Operations: []ConfigOperation{ConfigValidate},
|
|
|
|
|
Acquire: func(_ context.Context, _ ConfigRequest, input selectorInput) (Acquired[ConfigView], error) {
|
|
|
|
|
acquired = input
|
|
|
|
|
return Acquired[ConfigView]{}, nil
|
|
|
|
|
},
|
|
|
|
|
}},
|
|
|
|
|
}
|
|
|
|
|
app, err := New(Identity{Name: "example"})
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
if err := RegisterConfig(app, suite); err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
suite.Scopes[0].Name = "renamed"
|
|
|
|
|
suite.Scopes[0].Operations[0] = ConfigShow
|
|
|
|
|
suite.Selectors[0].Scopes[0] = "renamed"
|
|
|
|
|
|
|
|
|
|
var inventory bytes.Buffer
|
|
|
|
|
if result := app.Execute(context.Background(), []string{"config", "scopes"}, Streams{Out: &inventory}); result.Kind != OutcomeSuccess {
|
|
|
|
|
t.Fatalf("scope inventory outcome = %#v", result)
|
|
|
|
|
}
|
|
|
|
|
if got := inventory.String(); got != "app: validate\n" {
|
|
|
|
|
t.Fatalf("scope inventory after caller mutation = %q", got)
|
|
|
|
|
}
|
|
|
|
|
if result := app.Execute(context.Background(), []string{"config", "validate", "--scope=app", "--region=west"}, Streams{}); result.Kind != OutcomeSuccess {
|
|
|
|
|
t.Fatalf("registered scope execution after caller mutation = %#v", result)
|
|
|
|
|
}
|
|
|
|
|
if acquired.Region != (Supplied[string]{Value: "west", Set: true}) {
|
|
|
|
|
t.Fatalf("acquired selector after caller mutation = %#v", acquired)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestRegisterConfigRejectsDuplicateSelectorNamesAndShorts(t *testing.T) {
|
|
|
|
|
type selectorInput struct{ First, Second string }
|
|
|
|
|
tests := []struct {
|
|
|
|
|
name string
|
|
|
|
|
firstMeta FlagMeta
|
|
|
|
|
secondMeta FlagMeta
|
|
|
|
|
}{
|
|
|
|
|
{name: "name", firstMeta: FlagMeta{Name: "region", Short: "r"}, secondMeta: FlagMeta{Name: "region", Short: "g"}},
|
|
|
|
|
{name: "short", firstMeta: FlagMeta{Name: "region", Short: "r"}, secondMeta: FlagMeta{Name: "group", Short: "r"}},
|
|
|
|
|
}
|
|
|
|
|
for _, test := range tests {
|
|
|
|
|
t.Run(test.name, func(t *testing.T) {
|
|
|
|
|
app, err := New(Identity{Name: "example"})
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
suite := ConfigSuite[selectorInput]{Selectors: []Selector[selectorInput]{
|
|
|
|
|
{Binding: ValueFlag[selectorInput](test.firstMeta, nil, parseString, func(input *selectorInput, value Supplied[string]) { input.First = value.Value })},
|
|
|
|
|
{Binding: ValueFlag[selectorInput](test.secondMeta, nil, parseString, func(input *selectorInput, value Supplied[string]) { input.Second = value.Value })},
|
|
|
|
|
}}
|
|
|
|
|
if err := RegisterConfig(app, suite); err == nil || !strings.Contains(err.Error(), "duplicate configuration selector flag") {
|
|
|
|
|
t.Fatalf("RegisterConfig error = %v", err)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-07 13:44:46 +00:00
|
|
|
func TestRegisterConfigRejectsInvalidSuitesAndCollisions(t *testing.T) {
|
|
|
|
|
tests := []struct {
|
|
|
|
|
name string
|
|
|
|
|
scope ConfigScope[struct{}]
|
|
|
|
|
want string
|
|
|
|
|
}{
|
|
|
|
|
{name: "empty name", scope: ConfigScope[struct{}]{}, want: "name must not be empty"},
|
|
|
|
|
{name: "all name", scope: ConfigScope[struct{}]{Name: "all"}, want: "reserved"},
|
|
|
|
|
{name: "unknown operation", scope: ConfigScope[struct{}]{Name: "app", Operations: []ConfigOperation{"diff"}}, want: "unknown operation"},
|
|
|
|
|
{name: "missing acquisition", scope: ConfigScope[struct{}]{Name: "app", Operations: []ConfigOperation{ConfigValidate}}, want: "without an acquisition"},
|
|
|
|
|
}
|
|
|
|
|
for _, test := range tests {
|
|
|
|
|
t.Run(test.name, func(t *testing.T) {
|
|
|
|
|
app, _ := New(Identity{Name: "example"})
|
|
|
|
|
err := RegisterConfig(app, ConfigSuite[struct{}]{Scopes: []ConfigScope[struct{}]{test.scope}})
|
|
|
|
|
if err == nil || !strings.Contains(err.Error(), test.want) {
|
|
|
|
|
t.Fatalf("RegisterConfig error = %v, want %q", err, test.want)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
app, _ := New(Identity{Name: "example"})
|
|
|
|
|
custom, err := NewCommand(CommandSpec[struct{}]{Name: "config", Run: func(Invocation, struct{}) Outcome { return Success() }})
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
if err := app.Register(custom); err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
if err := RegisterConfig(app, ConfigSuite[struct{}]{}); err == nil {
|
|
|
|
|
t.Fatal("replaced a pre-existing config command")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestConfigSelectorPreflightAndScopedContextProjection(t *testing.T) {
|
|
|
|
|
type selectorInput struct {
|
|
|
|
|
Count Supplied[int]
|
|
|
|
|
Enabled Supplied[bool]
|
|
|
|
|
Other Supplied[int]
|
|
|
|
|
Runtime string
|
|
|
|
|
}
|
|
|
|
|
baseContextCalls := 0
|
|
|
|
|
checkCalls := 0
|
|
|
|
|
var checked selectorInput
|
|
|
|
|
countDefault := 9
|
|
|
|
|
suite := ConfigSuite[selectorInput]{
|
|
|
|
|
NewContext: func() selectorInput { baseContextCalls++; return selectorInput{Runtime: "invocation cache"} },
|
|
|
|
|
Selectors: []Selector[selectorInput]{
|
|
|
|
|
{Binding: ValueFlag[selectorInput](FlagMeta{Name: "count"}, &countDefault, strconv.Atoi, func(input *selectorInput, value Supplied[int]) { input.Count = value }), Scopes: []string{"app"}},
|
|
|
|
|
{Binding: BoolFlag[selectorInput](FlagMeta{Name: "enabled"}, true, func(input *selectorInput, value Supplied[bool]) { input.Enabled = value }), Scopes: []string{"promptkit"}},
|
|
|
|
|
{Binding: ValueFlag[selectorInput](FlagMeta{Name: "other"}, nil, strconv.Atoi, func(input *selectorInput, value Supplied[int]) { input.Other = value })},
|
|
|
|
|
},
|
|
|
|
|
CheckInput: func(_ Selection, input selectorInput) error { checkCalls++; checked = input; return nil },
|
|
|
|
|
Scopes: []ConfigScope[selectorInput]{
|
|
|
|
|
{Name: "app", Operations: []ConfigOperation{ConfigValidate}, Acquire: noopConfigAcquire[selectorInput]},
|
|
|
|
|
{Name: "promptkit", Operations: []ConfigOperation{ConfigValidate}, Acquire: noopConfigAcquire[selectorInput]},
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
normalized, err := normalizeConfigSuite(suite)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
2026-10-09 01:46:19 +00:00
|
|
|
preparedSuite := prepareConfigSuite(normalized)
|
2026-10-07 13:44:46 +00:00
|
|
|
spec := CommandSpec[configInput[selectorInput]]{Flags: configInputFlags(normalized, "text")}
|
|
|
|
|
prepared, err := prepareInput(spec, [][]string{{"all"}, {"text"}, {""}, nil, {"0"}, {"false"}, nil}, nil, true)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
input := prepared.build()
|
2026-10-09 01:46:19 +00:00
|
|
|
scopeNames, err := preflightConfig(preparedSuite, input, ConfigValidate)
|
2026-10-07 13:44:46 +00:00
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
if !reflect.DeepEqual(scopeNames, []string{"app", "promptkit"}) || checkCalls != 1 || checked.Count != (Supplied[int]{Value: 0, Set: true}) || checked.Enabled != (Supplied[bool]{Value: false, Set: true}) {
|
|
|
|
|
t.Fatalf("preflight result=%v checked=%#v calls=%d", scopeNames, checked, checkCalls)
|
|
|
|
|
}
|
|
|
|
|
if !input.Selection.Config.Set || input.Selection.Config.Value != "" || input.Selection.Overlay.Set {
|
|
|
|
|
t.Fatalf("configuration selection presence = %#v", input.Selection)
|
|
|
|
|
}
|
|
|
|
|
if baseContextCalls != 0 {
|
|
|
|
|
t.Fatalf("NewContext called during preflight %d times", baseContextCalls)
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-09 01:46:19 +00:00
|
|
|
contexts := contextsForScopes(preparedSuite, input, scopeNames)
|
2026-10-07 13:44:46 +00:00
|
|
|
if baseContextCalls != 1 {
|
|
|
|
|
t.Fatalf("NewContext calls = %d, want one", baseContextCalls)
|
|
|
|
|
}
|
|
|
|
|
if contexts["app"].Count != (Supplied[int]{Value: 0, Set: true}) || contexts["app"].Enabled != (Supplied[bool]{}) || contexts["app"].Other != (Supplied[int]{}) {
|
|
|
|
|
t.Fatalf("app selectors = %#v", contexts["app"])
|
|
|
|
|
}
|
|
|
|
|
if contexts["promptkit"].Count != (Supplied[int]{}) || contexts["promptkit"].Enabled != (Supplied[bool]{Value: false, Set: true}) || contexts["promptkit"].Runtime != "invocation cache" {
|
|
|
|
|
t.Fatalf("promptkit selectors = %#v", contexts["promptkit"])
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestConfigPreflightRejectsUnsupportedAggregateAndInapplicableSelector(t *testing.T) {
|
|
|
|
|
type selectorInput struct{ Region Supplied[string] }
|
|
|
|
|
newContextCalls := 0
|
|
|
|
|
suite := ConfigSuite[selectorInput]{
|
|
|
|
|
NewContext: func() selectorInput { newContextCalls++; return selectorInput{} },
|
|
|
|
|
Selectors: []Selector[selectorInput]{{Binding: ValueFlag[selectorInput](FlagMeta{Name: "region"}, nil, parseString, func(input *selectorInput, value Supplied[string]) { input.Region = value }), Scopes: []string{"promptkit"}}},
|
|
|
|
|
Scopes: []ConfigScope[selectorInput]{
|
|
|
|
|
{Name: "app", Operations: []ConfigOperation{ConfigValidate}, Acquire: noopConfigAcquire[selectorInput]},
|
|
|
|
|
{Name: "promptkit", Operations: []ConfigOperation{ConfigShow}, Acquire: noopConfigAcquire[selectorInput]},
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
normalized, err := normalizeConfigSuite(suite)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
2026-10-09 01:46:19 +00:00
|
|
|
preparedSuite := prepareConfigSuite(normalized)
|
2026-10-07 13:44:46 +00:00
|
|
|
prepared, err := prepareInput(CommandSpec[configInput[selectorInput]]{Flags: configInputFlags(normalized, "text")}, [][]string{{"all"}, {"text"}, nil, nil, nil}, nil, true)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
2026-10-09 01:46:19 +00:00
|
|
|
if _, err := preflightConfig(preparedSuite, prepared.build(), ConfigValidate); err == nil || !strings.Contains(err.Error(), "does not support validate") {
|
2026-10-07 13:44:46 +00:00
|
|
|
t.Fatalf("unsupported aggregate error = %v", err)
|
|
|
|
|
}
|
|
|
|
|
if newContextCalls != 0 {
|
|
|
|
|
t.Fatalf("unsupported aggregate created context %d times", newContextCalls)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
prepared, err = prepareInput(CommandSpec[configInput[selectorInput]]{Flags: configInputFlags(normalized, "text")}, [][]string{{"app"}, {"text"}, nil, nil, {"west"}}, nil, true)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
2026-10-09 01:46:19 +00:00
|
|
|
if _, err := preflightConfig(preparedSuite, prepared.build(), ConfigValidate); err == nil || !strings.Contains(err.Error(), "not applicable") {
|
2026-10-07 13:44:46 +00:00
|
|
|
t.Fatalf("inapplicable selector error = %v", err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func noopConfigAcquire[C any](context.Context, ConfigRequest, C) (Acquired[ConfigView], error) {
|
|
|
|
|
return Acquired[ConfigView]{}, nil
|
|
|
|
|
}
|
2026-10-07 16:44:10 +00:00
|
|
|
|
|
|
|
|
func TestRequiredConfigSelectorOnlyAppliesToSelectedScopes(t *testing.T) {
|
|
|
|
|
app, _ := New(Identity{Name: "example"})
|
|
|
|
|
calls := 0
|
|
|
|
|
acquire := func(context.Context, ConfigRequest, string) (Acquired[ConfigView], error) {
|
|
|
|
|
calls++
|
|
|
|
|
return Acquired[ConfigView]{}, nil
|
|
|
|
|
}
|
|
|
|
|
err := RegisterConfig(app, ConfigSuite[string]{
|
|
|
|
|
Selectors: []Selector[string]{{Scopes: []string{"app"}, Binding: ValueFlag[string](FlagMeta{Name: "campaign", Required: true}, nil, parseString, func(c *string, v Supplied[string]) { *c = v.Value })}},
|
|
|
|
|
Scopes: []ConfigScope[string]{{Name: "app", Operations: []ConfigOperation{ConfigValidate}, Acquire: acquire}, {Name: "promptkit", Operations: []ConfigOperation{ConfigValidate}, Acquire: acquire}},
|
|
|
|
|
})
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
for _, tc := range []struct {
|
|
|
|
|
args []string
|
|
|
|
|
kind OutcomeKind
|
|
|
|
|
calls int
|
|
|
|
|
}{
|
|
|
|
|
{[]string{"--scope", "promptkit"}, OutcomeSuccess, 1},
|
|
|
|
|
{nil, OutcomeUsage, 0},
|
|
|
|
|
{[]string{"--scope", "all"}, OutcomeUsage, 0},
|
|
|
|
|
{[]string{"--scope", "all", "--campaign", "spring"}, OutcomeSuccess, 2},
|
|
|
|
|
{[]string{"--scope", "promptkit", "--campaign", "spring"}, OutcomeUsage, 0},
|
|
|
|
|
{[]string{"--help"}, OutcomeSuccess, 0},
|
|
|
|
|
} {
|
|
|
|
|
calls = 0
|
|
|
|
|
var out bytes.Buffer
|
|
|
|
|
result := app.Execute(context.Background(), append([]string{"config", "validate"}, tc.args...), Streams{Out: &out})
|
|
|
|
|
if result.Kind != tc.kind || calls != tc.calls {
|
|
|
|
|
t.Fatalf("%v: result=%#v calls=%d", tc.args, result, calls)
|
|
|
|
|
}
|
|
|
|
|
if len(tc.args) > 0 && tc.args[0] == "--help" && !strings.Contains(out.String(), "required where applicable") {
|
|
|
|
|
t.Fatalf("help=%q", out.String())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-10-07 16:50:23 +00:00
|
|
|
|
|
|
|
|
func TestUnknownConfigScopeReportsChoicesWithoutRuntimeWork(t *testing.T) {
|
|
|
|
|
for _, tc := range []struct {
|
|
|
|
|
names []string
|
|
|
|
|
scope []string
|
|
|
|
|
choices string
|
|
|
|
|
}{
|
|
|
|
|
{[]string{"promptkit"}, nil, "available: promptkit"},
|
|
|
|
|
{[]string{"zeta", "alpha", "promptkit", "app"}, []string{"--scope", "unknown"}, "available: app, promptkit, alpha, zeta"},
|
|
|
|
|
} {
|
|
|
|
|
runtimeCalls := 0
|
|
|
|
|
suite := ConfigSuite[struct{}]{NewContext: func() struct{} { runtimeCalls++; return struct{}{} }}
|
|
|
|
|
for _, name := range tc.names {
|
|
|
|
|
suite.Scopes = append(suite.Scopes, ConfigScope[struct{}]{Name: name, Operations: []ConfigOperation{ConfigValidate}, Acquire: func(context.Context, ConfigRequest, struct{}) (Acquired[ConfigView], error) {
|
|
|
|
|
runtimeCalls++
|
|
|
|
|
return Acquired[ConfigView]{}, nil
|
|
|
|
|
}})
|
|
|
|
|
}
|
|
|
|
|
app, _ := New(Identity{Name: "example"})
|
|
|
|
|
if err := RegisterConfig(app, suite); err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
var out, diagnostics bytes.Buffer
|
|
|
|
|
result := app.Execute(context.Background(), append([]string{"config", "validate"}, tc.scope...), Streams{Out: &out, Err: &diagnostics})
|
|
|
|
|
if result.Kind != OutcomeUsage || runtimeCalls != 0 || out.Len() != 0 || !strings.Contains(diagnostics.String(), tc.choices) {
|
|
|
|
|
t.Fatalf("result=%#v calls=%d out=%q diagnostics=%q", result, runtimeCalls, out.String(), diagnostics.String())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|