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