132 lines
4.5 KiB
Go
132 lines
4.5 KiB
Go
package commandkit
|
|
|
|
import (
|
|
"errors"
|
|
"io"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
type outcomeTestCause struct{ message string }
|
|
|
|
func (e *outcomeTestCause) Error() string { return e.message }
|
|
|
|
func TestOutcomeConstructorsAndModifiers(t *testing.T) {
|
|
var calls int
|
|
detail := Detail(func(io.Writer) error {
|
|
calls++
|
|
return nil
|
|
})
|
|
cause := &outcomeTestCause{message: "operation failed"}
|
|
wrapped := errors.Join(errors.New("context"), cause)
|
|
|
|
tests := []struct {
|
|
name string
|
|
got Outcome
|
|
kind OutcomeKind
|
|
status int
|
|
domain string
|
|
}{
|
|
{name: "success", got: Success(), kind: OutcomeSuccess, status: 0},
|
|
{name: "usage", got: Usage("invalid input", detail), kind: OutcomeUsage, status: 2},
|
|
{name: "failure", got: Failure("operation failed", detail), kind: OutcomeOperational, status: 1},
|
|
{name: "domain", got: Domain("record", 7), kind: OutcomeDomain, status: 7, domain: "record"},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
if test.got.Kind != test.kind || test.got.Status != test.status || test.got.Domain != test.domain {
|
|
t.Fatalf("outcome = %#v, want kind %q, status %d, domain %q", test.got, test.kind, test.status, test.domain)
|
|
}
|
|
})
|
|
}
|
|
if calls != 0 {
|
|
t.Fatalf("constructors called detail renderer %d times", calls)
|
|
}
|
|
|
|
base := Domain("validation", 3).WithCause(wrapped)
|
|
updated := base.WithExplanation("invalid record", detail)
|
|
if updated.Kind != base.Kind || updated.Status != base.Status || updated.Domain != base.Domain {
|
|
t.Fatalf("WithExplanation changed classification: base %#v, updated %#v", base, updated)
|
|
}
|
|
if !errors.Is(updated.Cause, cause) {
|
|
t.Fatalf("WithExplanation lost cause identity: %v", updated.Cause)
|
|
}
|
|
var gotCause *outcomeTestCause
|
|
if !errors.As(updated.Cause, &gotCause) || gotCause != cause {
|
|
t.Fatalf("WithExplanation lost typed cause: %#v", gotCause)
|
|
}
|
|
if updated.Explanation.Message != "invalid record" || len(updated.Explanation.Details) != 1 {
|
|
t.Fatalf("WithExplanation = %#v", updated.Explanation)
|
|
}
|
|
if calls != 0 {
|
|
t.Fatalf("WithExplanation called detail renderer %d times", calls)
|
|
}
|
|
|
|
changedCause := updated.WithCause(errors.New("replacement"))
|
|
if changedCause.Kind != updated.Kind || changedCause.Status != updated.Status || changedCause.Domain != updated.Domain {
|
|
t.Fatalf("WithCause changed classification: %#v", changedCause)
|
|
}
|
|
if changedCause.Explanation.Message != updated.Explanation.Message || len(changedCause.Explanation.Details) != 1 {
|
|
t.Fatalf("WithCause changed explanation: %#v", changedCause.Explanation)
|
|
}
|
|
}
|
|
|
|
func TestOutcomeCopiesDetailSlices(t *testing.T) {
|
|
one := Detail(func(w io.Writer) error { _, err := io.WriteString(w, "one"); return err })
|
|
two := Detail(func(w io.Writer) error { _, err := io.WriteString(w, "two"); return err })
|
|
tests := []struct {
|
|
name string
|
|
make func([]Detail) Outcome
|
|
}{
|
|
{name: "failure", make: func(details []Detail) Outcome { return Failure("failed", details...) }},
|
|
{name: "usage", make: func(details []Detail) Outcome { return Usage("invalid", details...) }},
|
|
{name: "modifier", make: func(details []Detail) Outcome { return Success().WithExplanation("failed", details...) }},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
supplied := []Detail{one}
|
|
outcome := test.make(supplied)
|
|
supplied[0] = two
|
|
supplied = append(supplied, two)
|
|
if len(outcome.Explanation.Details) != 1 || outcome.Explanation.Details[0] == nil {
|
|
t.Fatalf("captured details = %#v", outcome.Explanation.Details)
|
|
}
|
|
var rendered strings.Builder
|
|
if err := outcome.Explanation.Details[0](&rendered); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if rendered.String() != "one" {
|
|
t.Fatalf("captured callback rendered %q, want original callback", rendered.String())
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestWithCausePreservesErrorIdentity(t *testing.T) {
|
|
cause := &outcomeTestCause{message: "typed failure"}
|
|
outcome := Failure("failed").WithCause(cause)
|
|
if !errors.Is(outcome.Cause, cause) {
|
|
t.Fatalf("errors.Is(%v, cause) = false", outcome.Cause)
|
|
}
|
|
var got *outcomeTestCause
|
|
if !errors.As(outcome.Cause, &got) || got != cause {
|
|
t.Fatalf("errors.As cause = %#v", got)
|
|
}
|
|
}
|
|
|
|
func TestMessageDiagnosticIsPassiveData(t *testing.T) {
|
|
diagnostic := MessageDiagnostic(SeverityWarning, "review needed\n")
|
|
if diagnostic.Severity != SeverityWarning || diagnostic.Write == nil {
|
|
t.Fatalf("diagnostic = %#v", diagnostic)
|
|
}
|
|
var rendered strings.Builder
|
|
if err := diagnostic.Write(&rendered); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got := rendered.String(); got != "review needed\n" {
|
|
t.Fatalf("rendered diagnostic = %q", got)
|
|
}
|
|
}
|