package ckconfig import ( "bytes" "context" "encoding/json" "errors" "io" "reflect" "testing" commandkit "go.maximumdirect.net/commandkit" configkit "go.maximumdirect.net/configkit" ) func TestShowSharedFormatsAndScopeShape(t *testing.T) { 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: effectiveValue("'false'\n")}}, nil }, }}})) for _, tc := range []struct { name string args []string want string }{ {"automatic", []string{"config", "show"}, "false\n"}, {"text", []string{"config", "show", "--format=text"}, "false\n"}, {"yaml", []string{"config", "show", "--format=yaml"}, "\"false\"\n"}, {"json", []string{"config", "show", "--format=json"}, "\"false\"\n"}, } { t.Run(tc.name, func(t *testing.T) { var out bytes.Buffer result := app.Execute(t.Context(), tc.args, commandkit.Streams{Out: &out, Err: io.Discard}) if tc.name == "yaml" { value, err := commandkit.DecodeYAMLReport(&out) if err != nil || value != "false" || result.Status != 0 { t.Fatalf("%#v value=%#v err=%v", result, value, err) } return } if result.Status != 0 || out.String() != tc.want { t.Fatalf("%#v output=%q", result, out.String()) } }) } var out bytes.Buffer result := app.Execute(t.Context(), []string{"config", "show", "--scope=all", "--format=json"}, commandkit.Streams{Out: &out, Err: io.Discard}) var decoded map[string]any if err := json.Unmarshal(out.Bytes(), &decoded); err != nil || result.Status != 0 || !reflect.DeepEqual(decoded, map[string]any{"app": "false"}) { t.Fatalf("%#v output=%q decoded=%#v err=%v", result, out.String(), decoded, err) } } func TestShowConversionFailureIsPresentationEvidence(t *testing.T) { for _, quiet := range []bool{false, true} { t.Run(map[bool]string{false: "ordinary", true: "quiet"}[quiet], func(t *testing.T) { 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: effectiveValue("x: [invalid\n")}}, nil }}}})) args := []string{"config", "show"} if quiet { args = append(args, "--quiet") } var out bytes.Buffer result := app.Execute(t.Context(), args, commandkit.Streams{Out: &out, Err: io.Discard}) if result.Kind != commandkit.OutcomeOperational || result.Presentation.Output.RenderError == nil || out.Len() != 0 { t.Fatalf("%#v output=%q", result, out.String()) } }) } } func TestValidationSubmitsOneReportAfterAcquisitionStops(t *testing.T) { failure := errors.New("invalid configuration") 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: "middle", Operations: []Operation{Validate}, Acquire: func(context.Context, Request, struct{}, struct{}) (commandkit.Acquired[View], error) { calls++ return commandkit.Acquired[View]{}, failure }}, {Name: "third", Operations: []Operation{Validate}, Acquire: func(context.Context, Request, struct{}, struct{}) (commandkit.Acquired[View], error) { calls++ return commandkit.Acquired[View]{}, nil }}, }})) var out bytes.Buffer result := app.Execute(t.Context(), []string{"config", "validate", "--scope=all", "--format=json"}, commandkit.Streams{Out: &out, Err: io.Discard}) var report struct { Scopes []struct{ Scope, Outcome string } } dec := json.NewDecoder(&out) if err := dec.Decode(&report); err != nil { t.Fatal(err) } if err := dec.Decode(new(any)); err != io.EOF { t.Fatalf("additional JSON: %v", err) } if !errors.Is(result.Cause, failure) || calls != 2 || len(report.Scopes) != 2 || report.Scopes[0].Outcome != "valid" || report.Scopes[1].Outcome != "failed" { t.Fatalf("result=%#v calls=%d report=%#v", result, calls, report) } } func TestValidationDeliveryFailureDoesNotStopAcquisition(t *testing.T) { writeErr := errors.New("write failed") calls := 0 acquire := func(context.Context, Request, struct{}, struct{}) (commandkit.Acquired[View], error) { calls++ return commandkit.Acquired[View]{}, nil } app := newConfigTestApp(t, Command(Suite[struct{}, struct{}]{Scopes: []Scope[struct{}, struct{}]{{Name: "app", Operations: []Operation{Validate}, Acquire: acquire}, {Name: "second", Operations: []Operation{Validate}, Acquire: acquire}}})) result := app.Execute(t.Context(), []string{"config", "validate", "--scope=all"}, commandkit.Streams{Out: failingOutput{err: writeErr}, Err: io.Discard}) if calls != 2 || !errors.Is(result.Presentation.Output.WriteError, writeErr) || result.Status == 0 { t.Fatalf("result=%#v calls=%d", result, calls) } } func TestConfigurationReportsSupportSharedFormats(t *testing.T) { type settings struct { Greeting string `yaml:"greeting"` } app := newConfigTestApp(t, AppCommand(func(context.Context, Selection) (*configkit.Result[settings], error) { return configkit.Load(configkit.Mapping("fixture", map[string]any{"greeting": "hello"}), configkit.Options[settings]{}) })) for _, args := range [][]string{{"scopes"}, {"show"}, {"sources"}, {"explain", "--path=/greeting"}, {"diff"}, {"validate"}} { for _, format := range []string{"text", "yaml", "json"} { t.Run(args[0]+"/"+format, func(t *testing.T) { argv := append([]string{"config"}, args...) argv = append(argv, "--format="+format) var out bytes.Buffer result := app.Execute(t.Context(), argv, commandkit.Streams{Out: &out, Err: io.Discard}) if result.Status != 0 { t.Fatalf("%#v output=%q", result, out.String()) } if format == "json" { dec := json.NewDecoder(&out) var value any if err := dec.Decode(&value); err != nil { t.Fatal(err) } if err := dec.Decode(&value); err != io.EOF { t.Fatalf("extra JSON: %v", err) } } }) } } }