184 lines
9.2 KiB
Go
184 lines
9.2 KiB
Go
package ckprompt
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"errors"
|
|
"io"
|
|
"strings"
|
|
"testing"
|
|
|
|
commandkit "gitea.maximumdirect.net/eric/CommandKit"
|
|
promptkit "gitea.maximumdirect.net/eric/promptkit"
|
|
"go.yaml.in/yaml/v4"
|
|
)
|
|
|
|
func TestSmokeTestFindingsDoNotChangeSuccessfulOutcome(t *testing.T) {
|
|
falseValue := false
|
|
app := newPromptTestApp(t, Command(Suite[struct{}, struct{}]{
|
|
SmokeTest: func(context.Context, SmokeRequest, struct{}, struct{}) (commandkit.Acquired[*promptkit.SmokeTestReport], error) {
|
|
return commandkit.Acquired[*promptkit.SmokeTestReport]{Value: &promptkit.SmokeTestReport{
|
|
Kind: promptkit.SmokeTestReachability, Supported: false, Applicable: nil, Completed: false,
|
|
Facts: promptkit.SmokeTestFacts{EndpointReachable: &falseValue},
|
|
}}, nil
|
|
},
|
|
}))
|
|
var output bytes.Buffer
|
|
outcome := app.Execute(context.Background(), []string{"promptkit", "test", "reachability"}, commandkit.Streams{Out: &output, Err: io.Discard})
|
|
if outcome.Kind != commandkit.OutcomeSuccess || outcome.Cause != nil {
|
|
t.Fatalf("false and unknown findings changed outcome: %#v", outcome)
|
|
}
|
|
var report struct {
|
|
Supported bool `yaml:"supported"`
|
|
Applicable *bool `yaml:"applicable"`
|
|
Completed bool `yaml:"completed"`
|
|
Facts struct {
|
|
EndpointReachable *bool `yaml:"endpoint_reachable"`
|
|
} `yaml:"facts"`
|
|
}
|
|
if err := yaml.Unmarshal(output.Bytes(), &report); err != nil {
|
|
t.Fatalf("decode smoke findings %q: %v", output.String(), err)
|
|
}
|
|
if report.Supported || report.Applicable != nil || report.Completed || report.Facts.EndpointReachable == nil || *report.Facts.EndpointReachable {
|
|
t.Fatalf("native findings were not retained: %#v", report)
|
|
}
|
|
}
|
|
|
|
func TestSmokeTestCancellationAfterAcquisitionStillDeliversReport(t *testing.T) {
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
t.Cleanup(cancel)
|
|
calls := 0
|
|
app := newPromptTestApp(t, Command(Suite[struct{}, struct{}]{
|
|
SmokeTest: func(context.Context, SmokeRequest, struct{}, struct{}) (commandkit.Acquired[*promptkit.SmokeTestReport], error) {
|
|
calls++
|
|
cancel()
|
|
return commandkit.Acquired[*promptkit.SmokeTestReport]{Value: &promptkit.SmokeTestReport{Kind: promptkit.SmokeTestAuthentication}}, nil
|
|
},
|
|
}))
|
|
var output bytes.Buffer
|
|
outcome := app.Execute(ctx, []string{"promptkit", "test", "authentication"}, commandkit.Streams{Out: &output, Err: io.Discard})
|
|
if outcome.Kind != commandkit.OutcomeOperational || !errors.Is(outcome.Cause, context.Canceled) || calls != 1 || output.Len() == 0 {
|
|
t.Fatalf("post-acquisition cancellation = (%#v, %q), calls=%d", outcome, output.String(), calls)
|
|
}
|
|
}
|
|
|
|
func TestSmokeTestCancellationBeforeAcquisitionSkipsProvider(t *testing.T) {
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
cancel()
|
|
calls, stateCalls := 0, 0
|
|
app := newPromptTestApp(t, Command(Suite[struct{}, struct{}]{
|
|
NewState: func() struct{} { stateCalls++; return struct{}{} },
|
|
SmokeTest: func(context.Context, SmokeRequest, struct{}, struct{}) (commandkit.Acquired[*promptkit.SmokeTestReport], error) {
|
|
calls++
|
|
return commandkit.Acquired[*promptkit.SmokeTestReport]{Value: &promptkit.SmokeTestReport{}}, nil
|
|
},
|
|
}))
|
|
var output bytes.Buffer
|
|
outcome := app.Execute(ctx, []string{"promptkit", "test", "authentication"}, commandkit.Streams{Out: &output, Err: io.Discard})
|
|
if outcome.Kind != commandkit.OutcomeOperational || !errors.Is(outcome.Cause, context.Canceled) || calls != 0 || stateCalls != 0 || output.Len() != 0 {
|
|
t.Fatalf("pre-acquisition cancellation = (%#v, %q), calls/state=%d/%d", outcome, output.String(), calls, stateCalls)
|
|
}
|
|
}
|
|
|
|
func TestSmokeTestEncodingAndWriterFailuresRetainOutcomeFields(t *testing.T) {
|
|
operationErr := errors.New("native operation failed")
|
|
diagnostic := commandkit.Diagnostic{Severity: commandkit.SeverityWarning, Write: func(writer io.Writer) error {
|
|
_, err := io.WriteString(writer, "smoke warning\n")
|
|
return err
|
|
}}
|
|
app := newPromptTestApp(t, Command(Suite[struct{}, struct{}]{
|
|
SmokeTest: func(context.Context, SmokeRequest, struct{}, struct{}) (commandkit.Acquired[*promptkit.SmokeTestReport], error) {
|
|
return commandkit.Acquired[*promptkit.SmokeTestReport]{
|
|
Value: &promptkit.SmokeTestReport{Kind: promptkit.SmokeTestAuthentication},
|
|
Diagnostics: []commandkit.Diagnostic{diagnostic},
|
|
}, operationErr
|
|
},
|
|
}))
|
|
writeErr := errors.New("smoke report writer failed")
|
|
for _, test := range []struct {
|
|
name string
|
|
writer io.Writer
|
|
want error
|
|
}{
|
|
{name: "writer error", writer: partialReportWriter{err: writeErr}, want: writeErr},
|
|
{name: "short write", writer: partialReportWriter{}, want: io.ErrShortWrite},
|
|
} {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
var diagnosticOutput bytes.Buffer
|
|
outcome := app.Execute(context.Background(), []string{"promptkit", "test", "authentication", "--verbose"}, commandkit.Streams{Out: test.writer, Err: &diagnosticOutput})
|
|
warningIndex := strings.Index(diagnosticOutput.String(), "smoke warning")
|
|
if outcome.Kind != commandkit.OutcomeOperational || !errors.Is(outcome.Cause, operationErr) || !errors.Is(outcome.Presentation.Output.WriteError, test.want) || outcome.Presentation.Output.State != commandkit.DeliveryIncomplete || outcome.Presentation.Diagnostics.State != commandkit.DeliveryComplete || warningIndex < 0 || strings.Contains(diagnosticOutput.String(), operationErr.Error()) {
|
|
t.Fatalf("report delivery = %#v, diagnostics=%q", outcome, diagnosticOutput.String())
|
|
}
|
|
})
|
|
}
|
|
|
|
diagnosticErr := errors.New("smoke diagnostic writer failed")
|
|
var output bytes.Buffer
|
|
outcome := app.Execute(context.Background(), []string{"promptkit", "test", "authentication", "--verbose"}, commandkit.Streams{Out: &output, Err: partialReportWriter{err: diagnosticErr}})
|
|
if outcome.Kind != commandkit.OutcomeOperational || !errors.Is(outcome.Cause, operationErr) || !errors.Is(outcome.Presentation.Diagnostics.WriteError, diagnosticErr) || outcome.Presentation.Diagnostics.State != commandkit.DeliveryIncomplete || output.Len() == 0 {
|
|
t.Fatalf("diagnostic delivery = (%#v, %q)", outcome, output.String())
|
|
}
|
|
|
|
encodeApp := newPromptTestApp(t, Command(Suite[struct{}, struct{}]{
|
|
SmokeTest: func(context.Context, SmokeRequest, struct{}, struct{}) (commandkit.Acquired[*promptkit.SmokeTestReport], error) {
|
|
return commandkit.Acquired[*promptkit.SmokeTestReport]{Value: &promptkit.SmokeTestReport{
|
|
Target: promptkit.SmokeTestTarget{EffectiveSettings: promptkit.ExecutionTarget{ExtraParams: map[string]any{"unsupported": func() {}}}},
|
|
}}, nil
|
|
},
|
|
}))
|
|
output.Reset()
|
|
outcome = encodeApp.Execute(context.Background(), []string{"promptkit", "test", "authentication"}, commandkit.Streams{Out: &output, Err: io.Discard})
|
|
if outcome.Kind != commandkit.OutcomeOperational || outcome.Cause != nil || outcome.Presentation.Output.RenderError == nil || output.Len() != 0 {
|
|
t.Fatalf("encoding failure = (%#v, %q)", outcome, output.String())
|
|
}
|
|
}
|
|
|
|
type smokeTypedError struct{ text string }
|
|
|
|
func (err *smokeTypedError) Error() string { return err.text }
|
|
|
|
func TestSmokeAggregatePreservesMultipleTypedCauses(t *testing.T) {
|
|
firstErr := &smokeTypedError{text: "reachability failed"}
|
|
secondErr := &smokeTypedError{text: ""}
|
|
app := newPromptTestApp(t, Command(Suite[struct{}, struct{}]{
|
|
SmokeTest: func(_ context.Context, request SmokeRequest, _ struct{}, _ struct{}) (commandkit.Acquired[*promptkit.SmokeTestReport], error) {
|
|
var err error
|
|
switch request.Request.Kind {
|
|
case promptkit.SmokeTestReachability:
|
|
err = firstErr
|
|
case promptkit.SmokeTestAuthentication:
|
|
err = secondErr
|
|
}
|
|
acquired := commandkit.Acquired[*promptkit.SmokeTestReport]{Value: &promptkit.SmokeTestReport{Kind: request.Request.Kind}}
|
|
if err != nil {
|
|
acquired.Explanation = commandkit.Explanation{Message: string(request.Request.Kind) + " native context"}
|
|
}
|
|
return acquired, err
|
|
},
|
|
}))
|
|
var output, diagnosticOutput bytes.Buffer
|
|
outcome := app.Execute(context.Background(), []string{"promptkit", "test", "all"}, commandkit.Streams{Out: &output, Err: &diagnosticOutput})
|
|
var retained *smokeTypedError
|
|
if outcome.Kind != commandkit.OutcomeOperational || !errors.Is(outcome.Cause, firstErr) || !errors.Is(outcome.Cause, secondErr) || !errors.As(outcome.Cause, &retained) || retained != firstErr {
|
|
t.Fatalf("joined aggregate errors lost identity: %#v, retained=%#v", outcome, retained)
|
|
}
|
|
firstContext := strings.Index(diagnosticOutput.String(), "reachability native context")
|
|
secondContext := strings.Index(diagnosticOutput.String(), "authentication native context")
|
|
if firstContext < 0 || secondContext < 0 || firstContext >= secondContext {
|
|
t.Fatalf("aggregate native explanations were not retained in order: %q", diagnosticOutput.String())
|
|
}
|
|
var document struct {
|
|
Tests []struct {
|
|
Kind string `yaml:"kind"`
|
|
Error *string `yaml:"error"`
|
|
} `yaml:"tests"`
|
|
}
|
|
if err := yaml.Unmarshal(output.Bytes(), &document); err != nil {
|
|
t.Fatalf("decode aggregate errors %q: %v", output.String(), err)
|
|
}
|
|
if len(document.Tests) != 4 || document.Tests[0].Kind != string(promptkit.SmokeTestReachability) || document.Tests[0].Error == nil || *document.Tests[0].Error != firstErr.Error() || document.Tests[1].Kind != string(promptkit.SmokeTestAuthentication) || document.Tests[1].Error == nil || *document.Tests[1].Error != secondErr.Error() || document.Tests[2].Error != nil || document.Tests[3].Error != nil {
|
|
t.Fatalf("aggregate error entries = %#v", document.Tests)
|
|
}
|
|
}
|