errors_test.go 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  1. //go:build unit
  2. package errors
  3. import (
  4. stderrors "errors"
  5. "fmt"
  6. "io"
  7. "net/http"
  8. "testing"
  9. "github.com/stretchr/testify/require"
  10. )
  11. func TestApplicationError_Basics(t *testing.T) {
  12. tests := []struct {
  13. name string
  14. err *ApplicationError
  15. want Status
  16. wantIs bool
  17. target error
  18. wrapped error
  19. }{
  20. {
  21. name: "new",
  22. err: New(400, "BAD_REQUEST", "invalid input"),
  23. want: Status{
  24. Code: 400,
  25. Reason: "BAD_REQUEST",
  26. Message: "invalid input",
  27. },
  28. },
  29. {
  30. name: "is_matches_code_and_reason",
  31. err: New(401, "UNAUTHORIZED", "nope"),
  32. want: Status{Code: 401, Reason: "UNAUTHORIZED", Message: "nope"},
  33. target: New(401, "UNAUTHORIZED", "ignored message"),
  34. wantIs: true,
  35. },
  36. {
  37. name: "is_does_not_match_reason",
  38. err: New(401, "UNAUTHORIZED", "nope"),
  39. want: Status{Code: 401, Reason: "UNAUTHORIZED", Message: "nope"},
  40. target: New(401, "DIFFERENT", "ignored message"),
  41. wantIs: false,
  42. },
  43. {
  44. name: "from_error_unwraps_wrapped_application_error",
  45. err: New(404, "NOT_FOUND", "missing"),
  46. wrapped: fmt.Errorf("wrap: %w", New(404, "NOT_FOUND", "missing")),
  47. want: Status{
  48. Code: 404,
  49. Reason: "NOT_FOUND",
  50. Message: "missing",
  51. },
  52. },
  53. }
  54. for _, tt := range tests {
  55. t.Run(tt.name, func(t *testing.T) {
  56. if tt.err != nil {
  57. require.Equal(t, tt.want, tt.err.Status)
  58. }
  59. if tt.target != nil {
  60. require.Equal(t, tt.wantIs, stderrors.Is(tt.err, tt.target))
  61. }
  62. if tt.wrapped != nil {
  63. got := FromError(tt.wrapped)
  64. require.Equal(t, tt.want, got.Status)
  65. }
  66. })
  67. }
  68. }
  69. func TestApplicationError_WithMetadataDeepCopy(t *testing.T) {
  70. tests := []struct {
  71. name string
  72. md map[string]string
  73. }{
  74. {name: "non_nil", md: map[string]string{"a": "1"}},
  75. {name: "nil", md: nil},
  76. }
  77. for _, tt := range tests {
  78. t.Run(tt.name, func(t *testing.T) {
  79. appErr := BadRequest("BAD_REQUEST", "invalid input").WithMetadata(tt.md)
  80. if tt.md == nil {
  81. require.Nil(t, appErr.Metadata)
  82. return
  83. }
  84. tt.md["a"] = "changed"
  85. require.Equal(t, "1", appErr.Metadata["a"])
  86. })
  87. }
  88. }
  89. func TestFromError_Generic(t *testing.T) {
  90. tests := []struct {
  91. name string
  92. err error
  93. wantCode int32
  94. wantReason string
  95. wantMsg string
  96. }{
  97. {
  98. name: "plain_error",
  99. err: stderrors.New("boom"),
  100. wantCode: UnknownCode,
  101. wantReason: UnknownReason,
  102. wantMsg: UnknownMessage,
  103. },
  104. {
  105. name: "wrapped_plain_error",
  106. err: fmt.Errorf("wrap: %w", io.EOF),
  107. wantCode: UnknownCode,
  108. wantReason: UnknownReason,
  109. wantMsg: UnknownMessage,
  110. },
  111. }
  112. for _, tt := range tests {
  113. t.Run(tt.name, func(t *testing.T) {
  114. got := FromError(tt.err)
  115. require.Equal(t, tt.wantCode, got.Code)
  116. require.Equal(t, tt.wantReason, got.Reason)
  117. require.Equal(t, tt.wantMsg, got.Message)
  118. require.Equal(t, tt.err, got.Unwrap())
  119. })
  120. }
  121. }
  122. func TestToHTTP(t *testing.T) {
  123. tests := []struct {
  124. name string
  125. err error
  126. wantStatusCode int
  127. wantBody Status
  128. }{
  129. {
  130. name: "nil_error",
  131. err: nil,
  132. wantStatusCode: http.StatusOK,
  133. wantBody: Status{Code: int32(http.StatusOK)},
  134. },
  135. {
  136. name: "application_error",
  137. err: Forbidden("FORBIDDEN", "no access"),
  138. wantStatusCode: http.StatusForbidden,
  139. wantBody: Status{
  140. Code: int32(http.StatusForbidden),
  141. Reason: "FORBIDDEN",
  142. Message: "no access",
  143. },
  144. },
  145. }
  146. for _, tt := range tests {
  147. t.Run(tt.name, func(t *testing.T) {
  148. code, body := ToHTTP(tt.err)
  149. require.Equal(t, tt.wantStatusCode, code)
  150. require.Equal(t, tt.wantBody, body)
  151. })
  152. }
  153. }
  154. func TestToHTTP_MetadataDeepCopy(t *testing.T) {
  155. md := map[string]string{"k": "v"}
  156. appErr := BadRequest("BAD_REQUEST", "invalid").WithMetadata(md)
  157. code, body := ToHTTP(appErr)
  158. require.Equal(t, http.StatusBadRequest, code)
  159. require.Equal(t, "v", body.Metadata["k"])
  160. md["k"] = "changed"
  161. require.Equal(t, "v", body.Metadata["k"])
  162. appErr.Metadata["k"] = "changed-again"
  163. require.Equal(t, "v", body.Metadata["k"])
  164. }