template/pkg/result/wrap_test.go
djmil 13f6a6812a result.Wrap - propagate a failed Expect into a new type U
- public API streamline
- Failf[T]("msg") -	originate a failure from a message; embed a cause with %w
- Err[T](err) - sets .err verbatim (the return zero, err / sentinel case)
2026-06-14 11:43:23 +00:00

83 lines
2.6 KiB
Go

package result_test
import (
"errors"
"strings"
"testing"
"gitea.djmil.dev/go/template/pkg/result"
)
var errRoot = errors.New("root cause")
// Intended usage of Wrap is demonstrated in example_test.go (parsePort). These
// tests pin the behavioral guarantees that an example's Output cannot assert:
// chain preservation, file:line stamping, stack survival, and the panic
// contract on misuse.
// TestWrapRetypesVerbatim verifies that Wrap with no message carries a failure
// from one value type to another, preserving the error chain unchanged.
func TestWrapRetypesVerbatim(t *testing.T) {
src := result.Err[int](errRoot) // Expect[int]
dst := result.Wrap[string](src) // Expect[string]
if !errors.Is(dst.Err(), errRoot) {
t.Fatalf("errors.Is chain not preserved: %v", dst.Err())
}
if dst.Err().Error() != errRoot.Error() {
t.Fatalf("verbatim wrap changed the message: got %q want %q", dst.Err(), errRoot)
}
}
// TestWrapAnnotates verifies that a context message is prepended with the
// caller's file:line while the underlying chain stays intact and leads the
// trace (outermost context first, root cause last).
func TestWrapAnnotates(t *testing.T) {
src := result.Failf[int]("parse %d", 7)
dst := result.Wrap[string](src, "load config id=%d", 42)
msg := dst.Err().Error()
if !strings.Contains(msg, "load config id=42") {
t.Fatalf("formatted context missing: %q", msg)
}
if !strings.Contains(msg, "wrap_test.go:") {
t.Fatalf("caller file:line not prepended: %q", msg)
}
if !strings.Contains(msg, "parse 7") {
t.Fatalf("underlying cause lost: %q", msg)
}
if first, _, _ := strings.Cut(msg, "\n"); !strings.Contains(first, "load config id=42") {
t.Fatalf("context should head the trace, got first line %q", first)
}
}
// TestWrapPreservesStackError verifies that a failure captured by Expect (a
// *stackError) survives the retype, so StackTrace still resolves it.
func TestWrapPreservesStackError(t *testing.T) {
captured := result.Run(func() {
result.Err[int](errRoot).Expect("inner")
})
if captured == nil {
t.Fatal("setup: expected Run to collect an error")
}
dst := result.Wrap[string](result.Err[int](captured), "outer")
if result.StackTrace(dst.Err()) == "" {
t.Fatal("stack trace lost through Wrap")
}
}
// TestWrapOnSuccessPanics locks in the documented contract: Wrap is only valid
// on a failed result; calling it on a success is a programmer error.
func TestWrapOnSuccessPanics(t *testing.T) {
defer func() {
if recover() == nil {
t.Fatal("expected Wrap on a successful Expect to panic")
}
}()
_ = result.Wrap[string](result.Ok(1))
}