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) } }