Define invocation outcome reporting types
This commit is contained in:
129
outcome_test.go
129
outcome_test.go
@@ -2,39 +2,130 @@ package commandkit
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestOutcomeConstructors(t *testing.T) {
|
||||
cause := errors.New("operation failed")
|
||||
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
|
||||
wantCause error
|
||||
name string
|
||||
got Outcome
|
||||
kind OutcomeKind
|
||||
status int
|
||||
domain string
|
||||
}{
|
||||
{name: "success", got: Success(), kind: OutcomeSuccess, status: 0},
|
||||
{name: "usage", got: Usage(cause), kind: OutcomeUsage, status: 2, wantCause: cause},
|
||||
{name: "failure", got: Failure(cause), kind: OutcomeOperational, status: 1, wantCause: cause},
|
||||
{name: "domain", got: Domain("record", 7, cause), kind: OutcomeDomain, status: 7, domain: "record", wantCause: cause},
|
||||
{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 {
|
||||
t.Fatalf("Kind = %q, want %q", test.got.Kind, test.kind)
|
||||
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 test.got.Status != test.status {
|
||||
t.Fatalf("Status = %d, want %d", test.got.Status, test.status)
|
||||
})
|
||||
}
|
||||
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)
|
||||
}
|
||||
if test.got.Cause != test.wantCause {
|
||||
t.Fatalf("Cause = %v, want original cause %v", test.got.Cause, test.wantCause)
|
||||
var rendered strings.Builder
|
||||
if err := outcome.Explanation.Details[0](&rendered); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if test.got.Domain != test.domain {
|
||||
t.Fatalf("Domain = %q, want %q", test.got.Domain, test.domain)
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user