2026-10-26 14:32:03 +00:00
|
|
|
package ckconfig
|
|
|
|
|
|
|
|
|
|
import (
|
2026-11-07 15:42:22 +00:00
|
|
|
"bytes"
|
2026-10-26 14:32:03 +00:00
|
|
|
"context"
|
|
|
|
|
"errors"
|
|
|
|
|
"io"
|
|
|
|
|
"testing"
|
|
|
|
|
|
2026-11-04 03:52:46 +00:00
|
|
|
commandkit "go.maximumdirect.net/commandkit"
|
2026-10-26 14:32:03 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
func TestBufferedReportCancellationBeforeDelivery(t *testing.T) {
|
|
|
|
|
for _, quiet := range []bool{false, true} {
|
|
|
|
|
t.Run(map[bool]string{false: "ordinary", true: "quiet"}[quiet], func(t *testing.T) {
|
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
|
|
|
defer cancel()
|
|
|
|
|
prepared := false
|
|
|
|
|
app := newConfigTestApp(t, commandkit.NewCommand(commandkit.CommandSpec[struct{}]{
|
|
|
|
|
Name: "report",
|
|
|
|
|
Run: func(invocation commandkit.Invocation, _ struct{}) commandkit.Outcome {
|
2026-11-07 03:57:10 +00:00
|
|
|
return renderPreparedConfigReport(invocation, commandkit.Explanation{Message: "selected context"}, func() (any, error) {
|
2026-10-26 14:32:03 +00:00
|
|
|
prepared = true
|
|
|
|
|
cancel()
|
2026-11-07 03:57:10 +00:00
|
|
|
return "prepared report", nil
|
2026-10-26 14:32:03 +00:00
|
|
|
})
|
|
|
|
|
},
|
|
|
|
|
}))
|
|
|
|
|
args := []string{"report"}
|
|
|
|
|
if quiet {
|
|
|
|
|
args = append(args, "--quiet")
|
|
|
|
|
}
|
|
|
|
|
writes := 0
|
|
|
|
|
got := app.Execute(ctx, args, commandkit.Streams{Out: reportWriteFunc(func(p []byte) (int, error) {
|
|
|
|
|
writes++
|
|
|
|
|
return len(p), nil
|
|
|
|
|
})})
|
|
|
|
|
if !prepared || writes != 0 || got.Status != 1 || !errors.Is(got.Cause, context.Canceled) || got.Explanation.Message != "selected context" {
|
|
|
|
|
t.Fatalf("prepared=%t writes=%d outcome=%#v", prepared, writes, got)
|
|
|
|
|
}
|
|
|
|
|
if got.Presentation.Output.RenderError != nil || got.Presentation.Output.WriteError != nil {
|
|
|
|
|
t.Fatalf("cancellation became a presentation failure: %#v", got.Presentation.Output)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type reportWriteFunc func([]byte) (int, error)
|
|
|
|
|
|
|
|
|
|
func (write reportWriteFunc) Write(p []byte) (int, error) { return write(p) }
|
|
|
|
|
|
|
|
|
|
func TestBufferedReportDoesNotRejectCancellationAfterDeliveryStarts(t *testing.T) {
|
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
|
|
|
defer cancel()
|
|
|
|
|
app := newConfigTestApp(t, commandkit.NewCommand(commandkit.CommandSpec[struct{}]{
|
|
|
|
|
Name: "report",
|
|
|
|
|
Run: func(invocation commandkit.Invocation, _ struct{}) commandkit.Outcome {
|
2026-11-07 03:57:10 +00:00
|
|
|
return renderPreparedConfigReport(invocation, commandkit.Explanation{}, func() (any, error) {
|
|
|
|
|
return "report", nil
|
2026-10-26 14:32:03 +00:00
|
|
|
})
|
|
|
|
|
},
|
|
|
|
|
}))
|
|
|
|
|
got := app.Execute(ctx, []string{"report"}, commandkit.Streams{Out: reportWriteFunc(func(p []byte) (int, error) {
|
|
|
|
|
cancel()
|
|
|
|
|
return io.Discard.Write(p)
|
|
|
|
|
})})
|
|
|
|
|
if got.Status != 0 || got.Cause != nil {
|
|
|
|
|
t.Fatalf("post-delivery cancellation changed the operation outcome: %#v", got)
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-11-07 15:42:22 +00:00
|
|
|
|
|
|
|
|
type effectiveWriterFunc func(io.Writer) error
|
|
|
|
|
|
|
|
|
|
func (write effectiveWriterFunc) WriteEffective(w io.Writer) error { return write(w) }
|
|
|
|
|
|
|
|
|
|
func TestShowCancellationDuringBufferedPreparation(t *testing.T) {
|
|
|
|
|
renderErr := errors.New("native rendering failed")
|
|
|
|
|
for _, tc := range []struct {
|
|
|
|
|
name string
|
|
|
|
|
renderErr error
|
|
|
|
|
}{{name: "cancellation"}, {name: "cancellation and rendering failure", renderErr: renderErr}} {
|
|
|
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
|
|
|
defer cancel()
|
|
|
|
|
acquired, secondWrites := 0, 0
|
|
|
|
|
scope := func(name string, writer EffectiveWriter) Scope[struct{}, struct{}] {
|
|
|
|
|
return Scope[struct{}, struct{}]{Name: name, Operations: []Operation{Show}, Acquire: func(context.Context, Request, struct{}, struct{}) (commandkit.Acquired[View], error) {
|
|
|
|
|
acquired++
|
|
|
|
|
return commandkit.Acquired[View]{Value: View{Effective: writer}}, nil
|
|
|
|
|
}}
|
|
|
|
|
}
|
|
|
|
|
app := newConfigTestApp(t, Command(Suite[struct{}, struct{}]{Scopes: []Scope[struct{}, struct{}]{
|
|
|
|
|
scope("app", effectiveWriterFunc(func(w io.Writer) error { _, _ = io.WriteString(w, "name: app\n"); cancel(); return tc.renderErr })),
|
|
|
|
|
scope("promptkit", effectiveWriterFunc(func(w io.Writer) error { secondWrites++; _, err := io.WriteString(w, "name: promptkit\n"); return err })),
|
|
|
|
|
}}))
|
|
|
|
|
var output bytes.Buffer
|
|
|
|
|
got := app.Execute(ctx, []string{"config", "show", "--scope=all"}, commandkit.Streams{Out: &output, Err: io.Discard})
|
|
|
|
|
if acquired != 2 || secondWrites != 0 || output.Len() != 0 || !errors.Is(got.Cause, context.Canceled) || got.Presentation.Output.WriteError != nil {
|
|
|
|
|
t.Fatalf("acquired=%d second=%d output=%q outcome=%#v", acquired, secondWrites, output.String(), got)
|
|
|
|
|
}
|
|
|
|
|
if tc.renderErr == nil && got.Presentation.Output.RenderError != nil || tc.renderErr != nil && !errors.Is(got.Presentation.Output.RenderError, tc.renderErr) {
|
|
|
|
|
t.Fatalf("render evidence = %#v", got.Presentation.Output)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|