590 lines
28 KiB
Go
590 lines
28 KiB
Go
package ckconfig
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"strings"
|
|
"testing"
|
|
|
|
commandkit "gitea.maximumdirect.net/eric/CommandKit"
|
|
configkit "gitea.maximumdirect.net/eric/ConfigKit"
|
|
)
|
|
|
|
func TestScopeDiscoveryIsOrderedAndDoesNotUseSuiteFactories(t *testing.T) {
|
|
inputCalls, stateCalls, acquireCalls := 0, 0, 0
|
|
suite := Suite[struct{}, struct{}]{
|
|
NewInput: func() struct{} { inputCalls++; return struct{}{} },
|
|
NewState: func() struct{} { stateCalls++; return struct{}{} },
|
|
Scopes: []Scope[struct{}, struct{}]{
|
|
{Name: "zeta"},
|
|
{Name: "promptkit", Operations: []Operation{}},
|
|
{Name: "app", Summary: "Application settings."},
|
|
{Name: "alpha"},
|
|
},
|
|
}
|
|
for index := range suite.Scopes {
|
|
suite.Scopes[index].Acquire = func(context.Context, Request, struct{}, struct{}) (commandkit.Acquired[View], error) {
|
|
acquireCalls++
|
|
return commandkit.Acquired[View]{}, nil
|
|
}
|
|
}
|
|
// An unused malformed binding has no generated command to validate.
|
|
suite.Selectors = []Selector[struct{}]{{Binding: commandkit.Flag[struct{}]{}, Scopes: []string{"app"}}}
|
|
app := newConfigTestApp(t, Command(suite))
|
|
var output, diagnostic bytes.Buffer
|
|
outcome := app.Execute(context.Background(), []string{"config", "scopes"}, commandkit.Streams{Out: &output, Err: &diagnostic})
|
|
if outcome.Kind != commandkit.OutcomeSuccess {
|
|
t.Fatalf("discovery outcome = %#v; diagnostics %q", outcome, diagnostic.String())
|
|
}
|
|
if got, want := output.String(), "app: \npromptkit: \nalpha: \nzeta: \n"; got != want {
|
|
t.Fatalf("discovery output = %q, want %q", got, want)
|
|
}
|
|
if inputCalls != 0 || stateCalls != 0 || acquireCalls != 0 {
|
|
t.Fatalf("discovery invoked suite work: input=%d state=%d acquire=%d", inputCalls, stateCalls, acquireCalls)
|
|
}
|
|
}
|
|
|
|
func TestOperationSelectorsAreCapturedForSupportedScopes(t *testing.T) {
|
|
type input struct{ Tenant, Campaign commandkit.Supplied[string] }
|
|
tenant := commandkit.ValueFlag(commandkit.FlagMeta{Name: "tenant", Required: true}, (*string)(nil), parseConfigString,
|
|
func(target *input, value commandkit.Supplied[string]) { target.Tenant = value })
|
|
campaign := commandkit.ValueFlag(commandkit.FlagMeta{Name: "campaign", Required: true}, (*string)(nil), parseConfigString,
|
|
func(target *input, value commandkit.Supplied[string]) { target.Campaign = value })
|
|
calls := 0
|
|
suite := Suite[input, struct{}]{
|
|
Selectors: []Selector[input]{{Binding: tenant, Scopes: []string{"app"}}, {Binding: campaign, Scopes: []string{"promptkit"}}},
|
|
Scopes: []Scope[input, struct{}]{
|
|
{Name: "app", Operations: []Operation{Validate}, Acquire: func(_ context.Context, _ Request, value input, _ struct{}) (commandkit.Acquired[View], error) {
|
|
calls++
|
|
if !value.Tenant.Set || value.Tenant.Value != "team" || value.Campaign.Set {
|
|
t.Errorf("app input = %#v", value)
|
|
}
|
|
return commandkit.Acquired[View]{}, nil
|
|
}},
|
|
{Name: "promptkit", Operations: []Operation{Sources}, Acquire: func(_ context.Context, _ Request, value input, _ struct{}) (commandkit.Acquired[View], error) {
|
|
calls++
|
|
if !value.Campaign.Set || value.Campaign.Value != "spring" || value.Tenant.Set {
|
|
t.Errorf("promptkit input = %#v", value)
|
|
}
|
|
return commandkit.Acquired[View]{Value: View{Sources: &sourceInventory{}}}, nil
|
|
}},
|
|
},
|
|
}
|
|
app := newConfigTestApp(t, Command(suite))
|
|
// Captured capabilities, applicability, and handles survive source edits.
|
|
suite.Scopes[0].Operations[0] = Sources
|
|
suite.Selectors[0].Scopes[0] = "promptkit"
|
|
suite.Selectors[1].Binding = commandkit.Flag[input]{}
|
|
for _, args := range [][]string{
|
|
{"config", "validate", "--tenant", "team"},
|
|
{"config", "sources", "--scope", "promptkit", "--campaign", "spring"},
|
|
} {
|
|
outcome := app.Execute(context.Background(), args, commandkit.Streams{Out: io.Discard, Err: io.Discard})
|
|
if outcome.Kind != commandkit.OutcomeSuccess {
|
|
t.Fatalf("%v: %#v", args, outcome)
|
|
}
|
|
}
|
|
for _, args := range [][]string{
|
|
{"config", "validate", "--campaign", "spring"},
|
|
{"config", "sources", "--scope", "promptkit", "--tenant", "team"},
|
|
} {
|
|
outcome := app.Execute(context.Background(), args, commandkit.Streams{Out: io.Discard, Err: io.Discard})
|
|
if outcome.Kind != commandkit.OutcomeUsage {
|
|
t.Fatalf("selector for another operation accepted: %v: %#v", args, outcome)
|
|
}
|
|
}
|
|
if calls != 2 {
|
|
t.Fatalf("acquisition calls = %d, want 2", calls)
|
|
}
|
|
}
|
|
|
|
func TestValidationPreparesIndependentScopeInputsAndOneSharedState(t *testing.T) {
|
|
type inspectInput struct {
|
|
Campaign commandkit.Supplied[string]
|
|
Retained string
|
|
}
|
|
type runState struct{ ID int }
|
|
var inputCalls, stateCalls, checkCalls int
|
|
var checked []*inspectInput
|
|
var acquired []*inspectInput
|
|
var states []*runState
|
|
var checkErr error
|
|
commandkitFlag := commandkit.ValueFlag[*inspectInput, string](
|
|
commandkit.FlagMeta{Name: "campaign", Usage: "Campaign selector.", Required: true},
|
|
(*string)(nil), func(value string) (string, error) { return value, nil },
|
|
func(input **inspectInput, value commandkit.Supplied[string]) { (*input).Campaign = value },
|
|
)
|
|
makeScope := func(name string) Scope[*inspectInput, *runState] {
|
|
return Scope[*inspectInput, *runState]{Name: name, Operations: []Operation{Validate}, Acquire: func(_ context.Context, request Request, input *inspectInput, state *runState) (commandkit.Acquired[View], error) {
|
|
if request.Operation != Validate || !request.Selection.Config.Set || request.Selection.Config.Value != "chosen.yaml" {
|
|
t.Errorf("request = %#v", request)
|
|
}
|
|
acquired = append(acquired, input)
|
|
states = append(states, state)
|
|
return commandkit.Acquired[View]{Diagnostics: []commandkit.Diagnostic{commandkit.MessageDiagnostic(commandkit.SeverityWarning, name+" warning")}}, nil
|
|
}}
|
|
}
|
|
suite := Suite[*inspectInput, *runState]{
|
|
NewInput: func() *inspectInput {
|
|
inputCalls++
|
|
return &inspectInput{Retained: fmt.Sprintf("initial-%d", inputCalls)}
|
|
},
|
|
NewState: func() *runState {
|
|
stateCalls++
|
|
if checkCalls != 2 {
|
|
t.Errorf("shared state created after %d input checks", checkCalls)
|
|
}
|
|
return &runState{ID: stateCalls}
|
|
},
|
|
Selectors: []Selector[*inspectInput]{{Binding: commandkitFlag, Scopes: []string{"promptkit"}}},
|
|
CheckInput: func(name string, selection Selection, input *inspectInput) error {
|
|
checkCalls++
|
|
checked = append(checked, input)
|
|
if !selection.Config.Set || input.Retained == "" {
|
|
return fmt.Errorf("missing initialized input for %s", name)
|
|
}
|
|
if name == "app" && input.Campaign.Set {
|
|
return fmt.Errorf("inapplicable selector was applied to app")
|
|
}
|
|
if name == "promptkit" && (!input.Campaign.Set || input.Campaign.Value != "spring") {
|
|
return fmt.Errorf("promptkit campaign = %#v", input.Campaign)
|
|
}
|
|
if checkErr != nil {
|
|
return checkErr
|
|
}
|
|
return nil
|
|
},
|
|
Scopes: []Scope[*inspectInput, *runState]{makeScope("promptkit"), makeScope("app")},
|
|
}
|
|
app := newConfigTestApp(t, Command(suite))
|
|
var output, diagnostic bytes.Buffer
|
|
outcome := app.Execute(context.Background(), []string{"config", "validate", "--scope", "all", "--config", "chosen.yaml", "--campaign", "spring", "--verbose"}, commandkit.Streams{Out: &output, Err: &diagnostic})
|
|
if outcome.Kind != commandkit.OutcomeSuccess || !strings.Contains(diagnostic.String(), "app warning") || !strings.Contains(diagnostic.String(), "promptkit warning") {
|
|
t.Fatalf("validation outcome=%#v diagnostics=%q", outcome, diagnostic.String())
|
|
}
|
|
if got, want := output.String(), "app: valid\npromptkit: valid\n"; got != want {
|
|
t.Fatalf("validation output = %q, want %q", got, want)
|
|
}
|
|
if inputCalls != 2 || checkCalls != 2 || stateCalls != 1 || len(acquired) != 2 {
|
|
t.Fatalf("factory/callback counts input=%d check=%d state=%d acquire=%d", inputCalls, checkCalls, stateCalls, len(acquired))
|
|
}
|
|
if checked[0] != acquired[0] || checked[1] != acquired[1] || checked[0] == checked[1] {
|
|
t.Fatalf("scope inputs were not retained independently: checked=%p,%p acquired=%p,%p", checked[0], checked[1], acquired[0], acquired[1])
|
|
}
|
|
if states[0] == nil || states[0] != states[1] || states[0].ID != 1 {
|
|
t.Fatalf("shared state values = %#v", states)
|
|
}
|
|
}
|
|
|
|
func TestValidationChecksAggregateCoverageBeforeFactories(t *testing.T) {
|
|
newInputCalls, newStateCalls, acquireCalls := 0, 0, 0
|
|
makeScope := func(name string, operations []Operation) Scope[struct{}, struct{}] {
|
|
return Scope[struct{}, struct{}]{Name: name, Operations: operations, Acquire: func(context.Context, Request, struct{}, struct{}) (commandkit.Acquired[View], error) {
|
|
acquireCalls++
|
|
return commandkit.Acquired[View]{}, nil
|
|
}}
|
|
}
|
|
app := newConfigTestApp(t, Command(Suite[struct{}, struct{}]{
|
|
NewInput: func() struct{} { newInputCalls++; return struct{}{} },
|
|
NewState: func() struct{} { newStateCalls++; return struct{}{} },
|
|
Scopes: []Scope[struct{}, struct{}]{makeScope("app", []Operation{Validate}), makeScope("promptkit", []Operation{Show})},
|
|
}))
|
|
var output bytes.Buffer
|
|
outcome := app.Execute(context.Background(), []string{"config", "validate", "--scope", "all"}, commandkit.Streams{Out: &output, Err: io.Discard})
|
|
if outcome.Kind != commandkit.OutcomeUsage || newInputCalls != 0 || newStateCalls != 0 || acquireCalls != 0 || output.Len() != 0 {
|
|
t.Fatalf("unsupported aggregate outcome=%#v counts=%d/%d/%d output=%q", outcome, newInputCalls, newStateCalls, acquireCalls, output.String())
|
|
}
|
|
}
|
|
|
|
func TestRequiredSelectorAppliesOnlyToItsSelectedScope(t *testing.T) {
|
|
type input struct{ Value commandkit.Supplied[string] }
|
|
var calls []string
|
|
flag := commandkit.ValueFlag(commandkit.FlagMeta{Name: "tenant", Required: true}, (*string)(nil), func(value string) (string, error) { return value, nil }, func(target *input, value commandkit.Supplied[string]) { target.Value = value })
|
|
makeScope := func(name string) Scope[input, struct{}] {
|
|
return Scope[input, struct{}]{Name: name, Operations: []Operation{Validate}, Acquire: func(_ context.Context, _ Request, value input, _ struct{}) (commandkit.Acquired[View], error) {
|
|
calls = append(calls, name)
|
|
if name == "app" && value.Value.Set {
|
|
t.Errorf("tenant selector applied to app: %#v", value)
|
|
}
|
|
return commandkit.Acquired[View]{}, nil
|
|
}}
|
|
}
|
|
app := newConfigTestApp(t, Command(Suite[input, struct{}]{Selectors: []Selector[input]{{Binding: flag, Scopes: []string{"promptkit"}}}, Scopes: []Scope[input, struct{}]{makeScope("app"), makeScope("promptkit")}}))
|
|
var output bytes.Buffer
|
|
outcome := app.Execute(context.Background(), []string{"config", "validate", "--scope", "app"}, commandkit.Streams{Out: &output, Err: io.Discard})
|
|
if outcome.Kind != commandkit.OutcomeSuccess || len(calls) != 1 || calls[0] != "app" {
|
|
t.Fatalf("unselected required selector outcome=%#v calls=%v", outcome, calls)
|
|
}
|
|
calls = nil
|
|
outcome = app.Execute(context.Background(), []string{"config", "validate", "--scope", "promptkit"}, commandkit.Streams{Out: &output, Err: io.Discard})
|
|
if outcome.Kind != commandkit.OutcomeUsage || len(calls) != 0 || !errors.Is(outcome.Cause, commandkit.ErrFlagInput) {
|
|
t.Fatalf("selected required selector outcome=%#v calls=%v", outcome, calls)
|
|
}
|
|
}
|
|
|
|
func TestConfigAliasesForwardSelectedTenantAndGlobalHelp(t *testing.T) {
|
|
type input struct{ Tenant commandkit.Supplied[string] }
|
|
flag := commandkit.ValueFlag(commandkit.FlagMeta{Name: "tenant"}, (*string)(nil),
|
|
func(value string) (string, error) { return value, nil },
|
|
func(target *input, value commandkit.Supplied[string]) { target.Tenant = value })
|
|
var acquired []commandkit.Supplied[string]
|
|
command := Command(Suite[input, struct{}]{
|
|
Selectors: []Selector[input]{{Binding: flag, Scopes: []string{"promptkit"}}},
|
|
Scopes: []Scope[input, struct{}]{
|
|
{
|
|
Name: "promptkit",
|
|
Operations: []Operation{Validate},
|
|
Acquire: func(_ context.Context, _ Request, value input, _ struct{}) (commandkit.Acquired[View], error) {
|
|
acquired = append(acquired, value.Tenant)
|
|
return commandkit.Acquired[View]{}, nil
|
|
},
|
|
},
|
|
},
|
|
})
|
|
app, err := commandkit.New(commandkit.Identity{Name: "example"},
|
|
command,
|
|
commandkit.Alias("check-config", "config validate"),
|
|
commandkit.FlagAliasFor("config validate", "--tenant-id", "--tenant"),
|
|
commandkit.FlagAlias("--hlep", "--help"),
|
|
)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
var output bytes.Buffer
|
|
outcome := app.Execute(context.Background(), []string{"check-config", "--hlep"}, commandkit.Streams{Out: &output, Err: io.Discard})
|
|
if outcome.Kind != commandkit.OutcomeSuccess || !strings.Contains(output.String(), "validate") || len(acquired) != 0 {
|
|
t.Fatalf("global help alias outcome/output/acquisitions = (%#v, %q, %#v)", outcome, output.String(), acquired)
|
|
}
|
|
|
|
outcome = app.Execute(context.Background(), []string{"check-config", "--scope", "promptkit", "--tenant-id=acme"}, commandkit.Streams{Out: io.Discard, Err: io.Discard})
|
|
if outcome.Kind != commandkit.OutcomeSuccess || len(acquired) != 1 || acquired[0] != (commandkit.Supplied[string]{Value: "acme", Set: true}) {
|
|
t.Fatalf("aliased selector outcome/acquisitions = (%#v, %#v)", outcome, acquired)
|
|
}
|
|
outcome = app.Execute(context.Background(), []string{"config", "validate", "--scope", "promptkit", "--tenant=canonical"}, commandkit.Streams{Out: io.Discard, Err: io.Discard})
|
|
if outcome.Kind != commandkit.OutcomeSuccess || len(acquired) != 2 || acquired[1] != (commandkit.Supplied[string]{Value: "canonical", Set: true}) {
|
|
t.Fatalf("canonical selector outcome/acquisitions = (%#v, %#v)", outcome, acquired)
|
|
}
|
|
}
|
|
|
|
func TestShowAcquiresAllViewsBeforeRenderingAndFramesNativeYAML(t *testing.T) {
|
|
var output bytes.Buffer
|
|
var acquisitionOutputLengths []int
|
|
makeScope := func(name, native string, acquireErr error) Scope[struct{}, *int] {
|
|
return Scope[struct{}, *int]{Name: name, Operations: []Operation{Show}, Acquire: func(_ context.Context, _ Request, _ struct{}, state *int) (commandkit.Acquired[View], error) {
|
|
*state++
|
|
acquisitionOutputLengths = append(acquisitionOutputLengths, output.Len())
|
|
if acquireErr != nil {
|
|
return commandkit.Acquired[View]{}, acquireErr
|
|
}
|
|
return commandkit.Acquired[View]{Value: View{Effective: effectiveValue(native)}}, nil
|
|
}}
|
|
}
|
|
providerErr := errors.New("second provider failed")
|
|
app := newConfigTestApp(t, Command(Suite[struct{}, *int]{NewState: func() *int { return new(int) }, Scopes: []Scope[struct{}, *int]{
|
|
makeScope("app", "---\nname: app\n", nil),
|
|
makeScope("promptkit", "---\nname: promptkit\n", providerErr),
|
|
}}))
|
|
outcome := app.Execute(context.Background(), []string{"config", "show", "--scope", "all"}, commandkit.Streams{Out: &output, Err: io.Discard})
|
|
if outcome.Kind != commandkit.OutcomeOperational || outcome.Cause != providerErr || output.Len() != 0 || outcome.Presentation.Output.State != commandkit.DeliveryNone {
|
|
t.Fatalf("aggregate provider failure outcome=%#v output=%q", outcome, output.String())
|
|
}
|
|
if len(acquisitionOutputLengths) != 2 || acquisitionOutputLengths[0] != 0 || acquisitionOutputLengths[1] != 0 {
|
|
t.Fatalf("show rendered before acquisition completed: %v", acquisitionOutputLengths)
|
|
}
|
|
|
|
output.Reset()
|
|
acquisitionOutputLengths = nil
|
|
app = newConfigTestApp(t, Command(Suite[struct{}, *int]{NewState: func() *int { return new(int) }, Scopes: []Scope[struct{}, *int]{
|
|
makeScope("app", "---\nname: app\n", nil),
|
|
makeScope("promptkit", "name: promptkit\n", nil),
|
|
}}))
|
|
outcome = app.Execute(context.Background(), []string{"config", "show", "--scope", "all"}, commandkit.Streams{Out: &output, Err: io.Discard})
|
|
if outcome.Kind != commandkit.OutcomeSuccess || outcome.Presentation.Output.State != commandkit.DeliveryComplete {
|
|
t.Fatalf("aggregate show outcome = %#v", outcome)
|
|
}
|
|
want := "---\n# scope: app\nname: app\n---\n# scope: promptkit\nname: promptkit\n"
|
|
if output.String() != want {
|
|
t.Fatalf("aggregate show output = %q, want %q", output.String(), want)
|
|
}
|
|
}
|
|
|
|
func TestConfigShowSeparatesWarningsFromTerminalContextAcrossModes(t *testing.T) {
|
|
providerErr := errors.New("private provider cause")
|
|
app := newConfigTestApp(t, Command(Suite[struct{}, struct{}]{Scopes: []Scope[struct{}, struct{}]{{
|
|
Name: "app", Operations: []Operation{Show}, Acquire: func(context.Context, Request, struct{}, struct{}) (commandkit.Acquired[View], error) {
|
|
return commandkit.Acquired[View]{
|
|
Diagnostics: []commandkit.Diagnostic{commandkit.MessageDiagnostic(commandkit.SeverityWarning, "independent warning")},
|
|
Explanation: commandkit.Explanation{Details: []commandkit.Detail{func(writer io.Writer) error {
|
|
_, err := io.WriteString(writer, "native terminal context\n")
|
|
return err
|
|
}}},
|
|
}, providerErr
|
|
},
|
|
}}}))
|
|
|
|
for _, test := range []struct {
|
|
name string
|
|
flags []string
|
|
wantWarning bool
|
|
wantTerminal bool
|
|
}{
|
|
{name: "ordinary", wantTerminal: true},
|
|
{name: "verbose", flags: []string{"--verbose"}, wantWarning: true, wantTerminal: true},
|
|
{name: "quiet", flags: []string{"--quiet"}, wantTerminal: true},
|
|
} {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
args := append([]string{"config", "show"}, test.flags...)
|
|
var output, diagnostic bytes.Buffer
|
|
outcome := app.Execute(context.Background(), args, commandkit.Streams{Out: &output, Err: &diagnostic})
|
|
if outcome.Kind != commandkit.OutcomeOperational || outcome.Cause != providerErr {
|
|
t.Fatalf("provider failure outcome = %#v", outcome)
|
|
}
|
|
if got := strings.Contains(diagnostic.String(), "independent warning"); got != test.wantWarning {
|
|
t.Fatalf("warning visibility = %v, output %q", got, diagnostic.String())
|
|
}
|
|
if got := strings.Contains(diagnostic.String(), "native terminal context"); got != test.wantTerminal {
|
|
t.Fatalf("terminal context visibility = %v, output %q", got, diagnostic.String())
|
|
}
|
|
if strings.Contains(diagnostic.String(), providerErr.Error()) {
|
|
t.Fatalf("cause text was duplicated into presentation: %q", diagnostic.String())
|
|
}
|
|
if test.wantWarning && strings.Index(diagnostic.String(), "independent warning") > strings.Index(diagnostic.String(), "native terminal context") {
|
|
t.Fatalf("deferred warning did not precede terminal context: %q", diagnostic.String())
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestConfigShowAndValidationUseManagedRendering(t *testing.T) {
|
|
t.Run("quiet show still records native render errors", func(t *testing.T) {
|
|
renderErr := errors.New("native renderer failed")
|
|
app := newConfigTestApp(t, Command(Suite[struct{}, struct{}]{Scopes: []Scope[struct{}, struct{}]{{
|
|
Name: "app", Operations: []Operation{Show}, Acquire: func(context.Context, Request, struct{}, struct{}) (commandkit.Acquired[View], error) {
|
|
return commandkit.Acquired[View]{Value: View{Effective: failedEffective{err: renderErr}}}, nil
|
|
},
|
|
}}}))
|
|
var output, diagnostic bytes.Buffer
|
|
outcome := app.Execute(context.Background(), []string{"config", "show", "--quiet"}, commandkit.Streams{Out: &output, Err: &diagnostic})
|
|
if outcome.Kind != commandkit.OutcomeOperational || !errors.Is(outcome.Presentation.Output.RenderError, renderErr) || outcome.Cause != nil || output.Len() != 0 {
|
|
t.Fatalf("quiet render failure outcome=%#v output=%q", outcome, output.String())
|
|
}
|
|
})
|
|
|
|
for _, operation := range []Operation{Show, Validate} {
|
|
t.Run(string(operation)+" write failure", func(t *testing.T) {
|
|
writeErr := errors.New("report destination failed")
|
|
app := newConfigTestApp(t, Command(Suite[struct{}, struct{}]{Scopes: []Scope[struct{}, struct{}]{{
|
|
Name: "app",
|
|
Operations: []Operation{operation},
|
|
Acquire: func(context.Context, Request, struct{}, struct{}) (commandkit.Acquired[View], error) {
|
|
return commandkit.Acquired[View]{Value: View{Effective: effectiveValue("value: ready\n")}}, nil
|
|
},
|
|
}}}))
|
|
commandName := "show"
|
|
if operation == Validate {
|
|
commandName = "validate"
|
|
}
|
|
outcome := app.Execute(context.Background(), []string{"config", commandName}, commandkit.Streams{Out: failingOutput{err: writeErr}, Err: io.Discard})
|
|
if outcome.Kind != commandkit.OutcomeOperational || !errors.Is(outcome.Presentation.Output.WriteError, writeErr) || outcome.Cause != nil {
|
|
t.Fatalf("report write failure outcome = %#v", outcome)
|
|
}
|
|
})
|
|
|
|
t.Run(string(operation)+" quiet suppression", func(t *testing.T) {
|
|
writeErr := errors.New("quiet destination should not be used")
|
|
app := newConfigTestApp(t, Command(Suite[struct{}, struct{}]{Scopes: []Scope[struct{}, struct{}]{{
|
|
Name: "app",
|
|
Operations: []Operation{operation},
|
|
Acquire: func(context.Context, Request, struct{}, struct{}) (commandkit.Acquired[View], error) {
|
|
return commandkit.Acquired[View]{Value: View{Effective: effectiveValue("value: ready\n")}}, nil
|
|
},
|
|
}}}))
|
|
commandName := "show"
|
|
if operation == Validate {
|
|
commandName = "validate"
|
|
}
|
|
outcome := app.Execute(context.Background(), []string{"config", commandName, "--quiet"}, commandkit.Streams{Out: failingOutput{err: writeErr}, Err: io.Discard})
|
|
if outcome.Kind != commandkit.OutcomeSuccess || outcome.Presentation.Output.State != commandkit.DeliverySuppressed || outcome.Presentation.Output.WriteError != nil || outcome.Cause != nil {
|
|
t.Fatalf("quiet report outcome = %#v", outcome)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestSourcesReadsNativeInventoryOnceAndPreservesZeroValues(t *testing.T) {
|
|
reader := &sourceInventory{values: []configkit.SourceInfo{{
|
|
ID: "synthetic", Label: "", Path: "", Layer: configkit.Layer("base"), Kind: configkit.SourceKind("mapping"), UnderlayIndex: 0,
|
|
ImportedFrom: &configkit.Location{SourceID: "", Line: 0, Column: 0},
|
|
}}}
|
|
app := newConfigTestApp(t, Command(Suite[struct{}, struct{}]{Scopes: []Scope[struct{}, struct{}]{{Name: "app", Operations: []Operation{Sources}, Acquire: func(context.Context, Request, struct{}, struct{}) (commandkit.Acquired[View], error) {
|
|
return commandkit.Acquired[View]{Value: View{Sources: reader}}, nil
|
|
}}}}))
|
|
var output bytes.Buffer
|
|
outcome := app.Execute(context.Background(), []string{"config", "sources", "--format", "yaml"}, commandkit.Streams{Out: &output, Err: io.Discard})
|
|
if outcome.Kind != commandkit.OutcomeSuccess || reader.calls != 1 {
|
|
t.Fatalf("sources outcome=%#v source calls=%d", outcome, reader.calls)
|
|
}
|
|
for _, fragment := range []string{"sources:", "id: synthetic", "label: \"\"", "path: \"\"", "underlay_index: 0", "imported_from:", "line: 0", "column: 0"} {
|
|
if !strings.Contains(output.String(), fragment) {
|
|
t.Errorf("source report missing %q: %s", fragment, output.String())
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestExplainParsesBeforeAcquisitionAndPreservesNativeSafeHistory(t *testing.T) {
|
|
reader := &nativeExplanation{
|
|
path: configkit.Path{},
|
|
value: configkit.Explanation{
|
|
Path: configkit.Path{},
|
|
Effective: configkit.ValueSnapshot{Present: true, Redacted: true},
|
|
History: []configkit.ValueChange{{
|
|
Before: configkit.ValueSnapshot{Present: true, Redacted: true},
|
|
After: configkit.ValueSnapshot{Present: true, Redacted: true},
|
|
Reasons: []string{"override"},
|
|
Dependencies: []configkit.Path{},
|
|
}},
|
|
SecretReference: &configkit.SecretReferenceInfo{Name: "APP_TOKEN"},
|
|
}, found: true,
|
|
}
|
|
acquireCalls := 0
|
|
app := newConfigTestApp(t, Command(Suite[struct{}, struct{}]{Scopes: []Scope[struct{}, struct{}]{{Name: "app", Operations: []Operation{Explain}, Acquire: func(context.Context, Request, struct{}, struct{}) (commandkit.Acquired[View], error) {
|
|
acquireCalls++
|
|
return commandkit.Acquired[View]{Value: View{Explanation: reader}}, nil
|
|
}}}}))
|
|
var output bytes.Buffer
|
|
outcome := app.Execute(context.Background(), []string{"config", "explain", "--path=", "--format", "yaml"}, commandkit.Streams{Out: &output, Err: io.Discard})
|
|
if outcome.Kind != commandkit.OutcomeSuccess || acquireCalls != 1 || reader.path.String() != "" {
|
|
t.Fatalf("root explanation outcome=%#v calls=%d path=%q", outcome, acquireCalls, reader.path.String())
|
|
}
|
|
if strings.Contains(output.String(), "APP_TOKEN_VALUE") || !strings.Contains(output.String(), "redacted: true") || !strings.Contains(output.String(), "history:") || !strings.Contains(output.String(), "dependencies: []") || !strings.Contains(output.String(), "name: APP_TOKEN") {
|
|
t.Fatalf("safe explanation report = %s", output.String())
|
|
}
|
|
|
|
output.Reset()
|
|
acquireCalls = 0
|
|
outcome = app.Execute(context.Background(), []string{"config", "explain", "--path", "/bad~2"}, commandkit.Streams{Out: &output, Err: io.Discard})
|
|
if outcome.Kind != commandkit.OutcomeUsage || acquireCalls != 0 || output.Len() != 0 {
|
|
t.Fatalf("malformed pointer was not rejected before acquisition: outcome=%#v calls=%d", outcome, acquireCalls)
|
|
}
|
|
}
|
|
|
|
func TestExplainTracksNativeRenderFailureWithoutReplacingOperationCause(t *testing.T) {
|
|
renderErr := errors.New("native explanation renderer failed")
|
|
reader := &failingNativeExplanation{err: renderErr}
|
|
app := newConfigTestApp(t, Command(Suite[struct{}, struct{}]{Scopes: []Scope[struct{}, struct{}]{{
|
|
Name: "app", Operations: []Operation{Explain}, Acquire: func(context.Context, Request, struct{}, struct{}) (commandkit.Acquired[View], error) {
|
|
return commandkit.Acquired[View]{Value: View{Explanation: reader}}, nil
|
|
},
|
|
}}}))
|
|
var output bytes.Buffer
|
|
outcome := app.Execute(context.Background(), []string{"config", "explain", "--path", "/token"}, commandkit.Streams{Out: &output, Err: io.Discard})
|
|
if outcome.Kind != commandkit.OutcomeOperational || outcome.Cause != nil || !errors.Is(outcome.Presentation.Output.RenderError, renderErr) || output.String() != "partial native explanation\n" {
|
|
t.Fatalf("native explanation failure outcome/output = %#v / %q", outcome, output.String())
|
|
}
|
|
}
|
|
|
|
func TestReportWriteFailureIsRecordedByReporterAndPreservesCancellation(t *testing.T) {
|
|
writeErr := errors.New("output failed")
|
|
app := newConfigTestApp(t, Command(Suite[struct{}, struct{}]{Scopes: []Scope[struct{}, struct{}]{{Name: "app", Operations: []Operation{Sources}, Acquire: func(context.Context, Request, struct{}, struct{}) (commandkit.Acquired[View], error) {
|
|
return commandkit.Acquired[View]{Value: View{Sources: &sourceInventory{values: []configkit.SourceInfo{}}}}, nil
|
|
}}}}))
|
|
outcome := app.Execute(context.Background(), []string{"config", "sources"}, commandkit.Streams{Out: failingOutput{err: writeErr}, Err: io.Discard})
|
|
if outcome.Kind != commandkit.OutcomeOperational || outcome.Cause != nil || !errors.Is(outcome.Presentation.Output.WriteError, writeErr) || outcome.Presentation.Output.State != commandkit.DeliveryIncomplete {
|
|
t.Fatalf("source delivery failure outcome = %#v", outcome)
|
|
}
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
calls := 0
|
|
app = newConfigTestApp(t, Command(Suite[struct{}, struct{}]{Scopes: []Scope[struct{}, struct{}]{
|
|
{Name: "app", Operations: []Operation{Validate}, Acquire: func(context.Context, Request, struct{}, struct{}) (commandkit.Acquired[View], error) {
|
|
calls++
|
|
return commandkit.Acquired[View]{}, nil
|
|
}},
|
|
{Name: "promptkit", Operations: []Operation{Validate}, Acquire: func(context.Context, Request, struct{}, struct{}) (commandkit.Acquired[View], error) {
|
|
calls++
|
|
cancel()
|
|
return commandkit.Acquired[View]{}, nil
|
|
}},
|
|
}}))
|
|
var output bytes.Buffer
|
|
outcome = app.Execute(ctx, []string{"config", "validate", "--scope", "all"}, commandkit.Streams{Out: &output, Err: io.Discard})
|
|
if !errors.Is(outcome.Cause, context.Canceled) || outcome.Presentation.Output.State != commandkit.DeliveryComplete || calls != 2 || output.String() != "app: valid\n" {
|
|
t.Fatalf("cancelled aggregate validation outcome=%#v calls=%d output=%q", outcome, calls, output.String())
|
|
}
|
|
}
|
|
|
|
type effectiveValue string
|
|
|
|
func (value effectiveValue) WriteEffective(writer io.Writer) error {
|
|
_, err := io.WriteString(writer, string(value))
|
|
return err
|
|
}
|
|
|
|
type failedEffective struct{ err error }
|
|
|
|
func (value failedEffective) WriteEffective(io.Writer) error {
|
|
return value.err
|
|
}
|
|
|
|
type sourceInventory struct {
|
|
values []configkit.SourceInfo
|
|
calls int
|
|
}
|
|
|
|
func (reader *sourceInventory) Sources() []configkit.SourceInfo {
|
|
reader.calls++
|
|
return reader.values
|
|
}
|
|
|
|
type nativeExplanation struct {
|
|
path configkit.Path
|
|
value configkit.Explanation
|
|
found bool
|
|
}
|
|
|
|
type failingNativeExplanation struct{ err error }
|
|
|
|
func (reader *failingNativeExplanation) Explain(configkit.Path) (configkit.Explanation, bool) {
|
|
return configkit.Explanation{}, true
|
|
}
|
|
|
|
func (reader *failingNativeExplanation) WriteExplanation(writer io.Writer, _ configkit.Path) error {
|
|
if _, err := io.WriteString(writer, "partial native explanation\n"); err != nil {
|
|
return err
|
|
}
|
|
return reader.err
|
|
}
|
|
|
|
func (reader *nativeExplanation) Explain(path configkit.Path) (configkit.Explanation, bool) {
|
|
reader.path = path
|
|
return reader.value, reader.found
|
|
}
|
|
|
|
func (reader *nativeExplanation) WriteExplanation(writer io.Writer, path configkit.Path) error {
|
|
reader.path = path
|
|
_, err := fmt.Fprintf(writer, "Explanation for %q\n", path.String())
|
|
return err
|
|
}
|
|
|
|
type failingOutput struct{ err error }
|
|
|
|
func (writer failingOutput) Write(value []byte) (int, error) {
|
|
if len(value) == 0 {
|
|
return 0, writer.err
|
|
}
|
|
return 1, writer.err
|
|
}
|
|
|
|
func newConfigTestApp(t *testing.T, command commandkit.Command) *commandkit.App {
|
|
t.Helper()
|
|
app, err := commandkit.New(commandkit.Identity{Name: "example"}, command)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return app
|
|
}
|