package merrors import ( "errors" "strings" "testing" ) type testReasonedError struct { error reason string } func (e testReasonedError) Reason() string { return e.reason } func TestReasonUsesReasonedError(t *testing.T) { err := testReasonedError{ error: InvalidArgument("value is invalid"), reason: "amount_below_minimum", } reason, ok := Reason(err) if !ok { t.Fatalf("expected reason") } if reason != "amount_below_minimum" { t.Fatalf("reason mismatch: got=%q", reason) } } func TestInvalidArgumentSupportsBrokenArgumentName(t *testing.T) { t.Run("without argument name keeps old behavior", func(t *testing.T) { err := InvalidArgument("value is missing") expected := "invalidArgError: value is missing" if err.Error() != expected { t.Fatalf("unexpected error message: %s", err) } if !errors.Is(err, ErrInvalidArg) { t.Fatalf("error should wrap ErrInvalidArg") } }) t.Run("single argument name", func(t *testing.T) { err := InvalidArgument("value is missing", "bot_token_env") expected := `invalidArgError: broken argument "bot_token_env": value is missing` if err.Error() != expected { t.Fatalf("unexpected error message: %s", err) } }) t.Run("multiple argument names", func(t *testing.T) { err := InvalidArgument("value mismatch", "bot_token_env", "chat_id_env", " ") expected := `invalidArgError: broken arguments "bot_token_env", "chat_id_env": value mismatch` if err.Error() != expected { t.Fatalf("unexpected error message: %s", err) } }) } func TestInvalidArgumentWithNamesfSupportsBrokenArgumentName(t *testing.T) { err := InvalidArgumentWithNamesf([]string{"gateway.supported_assets.currency"}, "asset[%d] %s", 2, "is invalid") expected := `invalidArgError: broken argument "gateway.supported_assets.currency": asset[2] is invalid` if err.Error() != expected { t.Fatalf("unexpected error message: %s", err) } if !errors.Is(err, ErrInvalidArg) { t.Fatalf("error should wrap ErrInvalidArg") } } func TestInvalidArgumentWrapSupportsBrokenArgumentName(t *testing.T) { base := Define("root cause") err := InvalidArgumentWrap(base, "value is missing", "bot_token_env") if !strings.Contains(err.Error(), `invalidArgError: broken argument "bot_token_env": value is missing`) { t.Fatalf("wrapped error should include broken argument name: %s", err) } if !errors.Is(err, ErrInvalidArg) || !errors.Is(err, base) { t.Fatalf("wrapped error should preserve all error layers") } } func TestInternalWrapAllPreservesAllCauses(t *testing.T) { first := Define("first cause") second := Define("second cause") err := InternalWrapAll("combined failure", first, nil, second) if !strings.Contains(err.Error(), "internal: combined failure") { t.Fatalf("wrapped error should include context: %s", err) } if !errors.Is(err, ErrInternal) || !errors.Is(err, first) || !errors.Is(err, second) { t.Fatalf("wrapped error should preserve all error layers") } } func TestOpaqueWrapPreservesCauseWithoutExposingItsText(t *testing.T) { cause := DataConflict("restricted storage key") err := OpaqueWrap(ErrInternal, cause, "safe persistence failure") if got, want := err.Error(), "internal: safe persistence failure"; got != want { t.Fatalf("opaque error mismatch: got=%q want=%q", got, want) } if !errors.Is(err, ErrInternal) || !errors.Is(err, ErrDataConflict) { t.Fatalf("opaque error did not preserve classifications: %v", err) } } func TestOperationalWrapsPreserveClassificationAndCause(t *testing.T) { cause := Define("dependency failure") tests := []struct { name string err error kind error }{ {name: "failed precondition", err: FailedPreconditionWrap(cause, "AA capability unavailable"), kind: ErrFailedPrecondition}, {name: "service unavailable", err: ServiceUnavailableWrap(cause, "Arbitrum RPC unavailable"), kind: ErrServiceUnavailable}, {name: "data conflict", err: DataConflictWrap(cause, "managed wallet replay differs"), kind: ErrDataConflict}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if !errors.Is(tt.err, tt.kind) || !errors.Is(tt.err, cause) { t.Fatalf("wrapped error should preserve classification and cause: %s", tt.err) } }) } }