go.uber.org/yarpc@v1.72.1/encoding/protobuf/v2/error_test.go (about) 1 // Copyright (c) 2022 Uber Technologies, Inc. 2 // 3 // Permission is hereby granted, free of charge, to any person obtaining a copy 4 // of this software and associated documentation files (the "Software"), to deal 5 // in the Software without restriction, including without limitation the rights 6 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 // copies of the Software, and to permit persons to whom the Software is 8 // furnished to do so, subject to the following conditions: 9 // 10 // The above copyright notice and this permission notice shall be included in 11 // all copies or substantial portions of the Software. 12 // 13 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 // THE SOFTWARE. 20 21 package v2 22 23 import ( 24 "errors" 25 "fmt" 26 "strings" 27 "testing" 28 29 "github.com/golang/protobuf/ptypes/any" 30 "github.com/golang/protobuf/ptypes/wrappers" 31 "github.com/stretchr/testify/assert" 32 "github.com/stretchr/testify/require" 33 "go.uber.org/yarpc/api/transport/transporttest" 34 "go.uber.org/yarpc/yarpcerrors" 35 rpc_status "google.golang.org/genproto/googleapis/rpc/status" 36 "google.golang.org/grpc/codes" 37 "google.golang.org/grpc/status" 38 "google.golang.org/protobuf/proto" 39 anypb "google.golang.org/protobuf/types/known/anypb" 40 ) 41 42 func TestNewOK(t *testing.T) { 43 err := NewError(yarpcerrors.CodeOK, "okay") 44 assert.Nil(t, err) 45 46 assert.Equal(t, yarpcerrors.FromError(err).Code(), yarpcerrors.CodeOK) 47 assert.Equal(t, yarpcerrors.FromError(err).Message(), "") 48 } 49 50 func TestNew(t *testing.T) { 51 err := NewError(yarpcerrors.CodeNotFound, "unfounded accusation") 52 assert.Equal(t, yarpcerrors.FromError(err).Code(), yarpcerrors.CodeNotFound) 53 assert.Equal(t, yarpcerrors.FromError(err).Message(), "unfounded accusation") 54 assert.Contains(t, err.Error(), "unfounded accusation") 55 } 56 57 func TestForeignError(t *testing.T) { 58 err := errors.New("to err is go") 59 assert.Equal(t, yarpcerrors.FromError(err).Code(), yarpcerrors.CodeUnknown) 60 assert.Equal(t, yarpcerrors.FromError(err).Message(), "to err is go") 61 } 62 63 func TestConvertToYARPCErrorWithWrappedError(t *testing.T) { 64 errDetail := &wrappers.BytesValue{Value: []byte("err detail bytes")} 65 66 pbErr := NewError( 67 yarpcerrors.CodeAborted, 68 "aborted", 69 WithErrorDetails(errDetail)) 70 71 wrappedErr := fmt.Errorf("wrapped err 2: %w", fmt.Errorf("wrapped err 1: %w", pbErr)) 72 73 err := convertToYARPCError(Encoding, wrappedErr, &codec{}, nil /* resw */) 74 require.True(t, yarpcerrors.IsStatus(err), "unexpected error") 75 assert.Equal(t, yarpcerrors.FromError(err).Code(), yarpcerrors.CodeAborted, "unexpected err code") 76 assert.Equal(t, yarpcerrors.FromError(err).Message(), "aborted", "unexpected error message") 77 78 gotDetails := yarpcerrors.FromError(err).Details() 79 assert.NotEmpty(t, gotDetails, "no details marshaled") 80 } 81 82 func TestConvertToYARPCErrorApplicationErrorMeta(t *testing.T) { 83 errDetails := []proto.Message{ 84 &wrappers.StringValue{Value: "detail message"}, 85 &wrappers.Int32Value{Value: 42}, 86 &wrappers.BytesValue{Value: []byte("detail bytes")}, 87 } 88 89 pbErr := NewError( 90 yarpcerrors.CodeAborted, 91 "aborted", 92 WithErrorDetails(errDetails...)) 93 94 resw := &transporttest.FakeResponseWriter{} 95 err := convertToYARPCError(Encoding, pbErr, &codec{}, resw) 96 require.Error(t, err) 97 98 require.NotNil(t, resw.ApplicationErrorMeta) 99 assert.Equal(t, "StringValue", resw.ApplicationErrorMeta.Name, "expected first error detail name") 100 assert.Equal(t, 101 `[]{ StringValue{value:"detail message"} , Int32Value{value:42} , BytesValue{value:"detail bytes"} }`, 102 resw.ApplicationErrorMeta.Details, 103 "unexpected string of error details") 104 assert.Nil(t, resw.ApplicationErrorMeta.Code, "code should nil") 105 } 106 107 func TestMessageNameWithoutPackage(t *testing.T) { 108 tests := []struct { 109 name string 110 give string 111 want string 112 }{ 113 { 114 name: "fqn", 115 give: "uber.foo.bar.baz.MessageName", 116 want: "MessageName", 117 }, 118 { 119 name: "not fully qualified", 120 give: "MyMessage", 121 want: "MyMessage", 122 }, 123 } 124 125 for _, tt := range tests { 126 t.Run(tt.name, func(t *testing.T) { 127 assert.Equal(t, tt.want, messageNameWithoutPackage(tt.give), "unexpected trim") 128 }) 129 } 130 } 131 132 type yarpcError interface{ YARPCError() *yarpcerrors.Status } 133 134 func TestPbErrorToYARPCError(t *testing.T) { 135 tests := []struct { 136 name string 137 code yarpcerrors.Code 138 message string 139 details []proto.Message 140 expectedGRPCCode codes.Code 141 }{ 142 { 143 name: "pbError without details", 144 code: yarpcerrors.CodeAborted, 145 message: "simple test", 146 expectedGRPCCode: codes.Aborted, 147 }, 148 { 149 name: "pbError with single detail", 150 code: yarpcerrors.CodeInternal, 151 message: "internal error", 152 expectedGRPCCode: codes.Internal, 153 details: []proto.Message{ 154 &wrappers.StringValue{Value: "test value"}, 155 }, 156 }, 157 { 158 name: "pbError with multiple details", 159 code: yarpcerrors.CodeNotFound, 160 message: "not found error", 161 expectedGRPCCode: codes.NotFound, 162 details: []proto.Message{ 163 &wrappers.StringValue{Value: "test value"}, 164 &wrappers.Int32Value{Value: 45}, 165 &any.Any{Value: []byte{1, 2, 3, 4, 5}}, 166 }, 167 }, 168 } 169 170 for _, tt := range tests { 171 t.Run(tt.name, func(t *testing.T) { 172 var errOpts []ErrorOption 173 if len(tt.details) > 0 { 174 errOpts = append(errOpts, WithErrorDetails(tt.details...)) 175 } 176 pberror := NewError(tt.code, tt.message, errOpts...) 177 st := pberror.(yarpcError).YARPCError() 178 assert.Equal(t, st.Code(), tt.code) 179 assert.Equal(t, st.Message(), tt.message) 180 181 statusPb := rpc_status.Status{} 182 err := proto.Unmarshal(st.Details(), &statusPb) 183 assert.NoError(t, err, "unexpected unmarshal error") 184 185 status := status.FromProto(&statusPb) 186 assert.Equal(t, tt.expectedGRPCCode, status.Code(), "unexpected grpc status code") 187 assert.Equal(t, tt.message, status.Message(), "unexpected grpc status message") 188 assert.Len(t, status.Details(), len(tt.details), "unexpected details length") 189 for i, detail := range tt.details { 190 assert.True(t, proto.Equal(detail, status.Details()[i].(proto.Message))) 191 } 192 }) 193 } 194 } 195 196 func TestConvertToYARPCErrorWithIncorrectEncoding(t *testing.T) { 197 pberr := &pberror{code: yarpcerrors.CodeAborted, message: "test"} 198 err := convertToYARPCError("thrift", pberr, &codec{}, nil) 199 assert.Error(t, err, "unexpected empty error") 200 assert.Equal(t, err.Error(), 201 "code:internal message:encoding.Expect should have handled encoding \"thrift\" but did not") 202 } 203 204 func TestConvertFromYARPCError(t *testing.T) { 205 t.Run("incorrect encoding", func(t *testing.T) { 206 yerr := yarpcerrors.Newf(yarpcerrors.CodeAborted, "test").WithDetails([]byte{1, 2}) 207 err := convertFromYARPCError("thrift", yerr, &codec{}) 208 assert.Equal(t, err.Error(), 209 `code:internal message:encoding.Expect should have handled encoding "thrift" but did not`) 210 }) 211 t.Run("empty details", func(t *testing.T) { 212 yerr := yarpcerrors.Newf(yarpcerrors.CodeAborted, "test") 213 err := convertFromYARPCError(Encoding, yerr, &codec{}) 214 assert.Equal(t, err.Error(), "code:aborted message:test") 215 }) 216 } 217 218 func TestCreateStatusWithDetailErrors(t *testing.T) { 219 t.Run("code ok", func(t *testing.T) { 220 pberr := &pberror{code: yarpcerrors.CodeOK, message: "test"} 221 _, err := createStatusWithDetail(pberr, Encoding, &codec{}) 222 assert.Error(t, err, "unexpected empty error") 223 assert.Equal(t, err.Error(), "no status error for error with code OK") 224 }) 225 226 t.Run("unsupported code", func(t *testing.T) { 227 pberr := &pberror{code: 99, message: "test"} 228 _, err := createStatusWithDetail(pberr, Encoding, &codec{}) 229 assert.Error(t, err, "unexpected empty error") 230 assert.Equal(t, err.Error(), "no status error for error with code 99") 231 }) 232 233 t.Run("unsupported encoding", func(t *testing.T) { 234 pberr := &pberror{code: yarpcerrors.CodeAborted} 235 _, err := createStatusWithDetail(pberr, "thrift", &codec{}) 236 assert.Error(t, err, "unexpected empty error") 237 assert.Equal(t, err.Error(), 238 "code:internal message:encoding.Expect should have handled encoding \"thrift\" but did not") 239 }) 240 } 241 242 func TestErrorHandling(t *testing.T) { 243 t.Run("GetErrorDetail empty error handling", func(t *testing.T) { 244 assert.Nil(t, GetErrorDetails(nil), "unexpected details") 245 }) 246 t.Run("GetErrorDetail non pberror", func(t *testing.T) { 247 assert.Nil(t, GetErrorDetails(errors.New("test")), "unexpected details") 248 }) 249 t.Run("GetErrorDetail with no error detail", func(t *testing.T) { 250 err := NewError( 251 yarpcerrors.CodeAborted, 252 "aborted") 253 assert.Nil(t, GetErrorDetails(err), "unexpected details") 254 }) 255 t.Run("PbError empty error handling", func(t *testing.T) { 256 var pbErr *pberror 257 assert.Nil(t, pbErr.YARPCError(), "unexpected yarpcerror") 258 }) 259 t.Run("PbError with nil detail message", func(t *testing.T) { 260 pbErr := &pberror{ 261 details: []*any.Any{nil}, 262 } 263 require.Len(t, GetErrorDetails(pbErr), 1) 264 assert.Equal(t, strings.ReplaceAll(GetErrorDetails(pbErr)[0].(error).Error(), "\u00a0", " "), 265 fmt.Errorf("proto: invalid empty type URL").Error()) 266 }) 267 t.Run("NewError with nil error detail", func(t *testing.T) { 268 err := NewError( 269 yarpcerrors.CodeAborted, 270 "aborted", 271 WithErrorDetails([]proto.Message{nil}...)) 272 assert.Equal(t, strings.ReplaceAll(err.Error(), "\u00a0", " "), 273 fmt.Errorf("proto: invalid nil source message").Error()) 274 }) 275 } 276 277 func TestSetApplicationErrorMeta(t *testing.T) { 278 respErr := NewError( 279 yarpcerrors.CodeAborted, 280 "aborted", 281 ) 282 283 anyString, err := anypb.New(&wrappers.StringValue{Value: "baz"}) 284 require.NoError(t, err) 285 286 pbErr := respErr.(*pberror) 287 pbErr.details = append(pbErr.details, &any.Any{TypeUrl: "foo", Value: []byte("bar")}) 288 pbErr.details = append(pbErr.details, anyString) 289 290 resw := &transporttest.FakeResponseWriter{} 291 setApplicationErrorMeta(pbErr, resw) 292 293 assert.Equal(t, "StringValue", resw.ApplicationErrorMeta.Name) 294 assert.Equal(t, `[]{ StringValue{value:"baz"} }`, resw.ApplicationErrorMeta.Details) 295 }