go.uber.org/yarpc@v1.72.1/encoding/protobuf/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 protobuf 22 23 import ( 24 "errors" 25 "fmt" 26 "testing" 27 28 "github.com/gogo/googleapis/google/rpc" 29 "github.com/gogo/protobuf/proto" 30 "github.com/gogo/protobuf/types" 31 "github.com/gogo/status" 32 "github.com/stretchr/testify/assert" 33 "github.com/stretchr/testify/require" 34 "go.uber.org/yarpc/api/transport/transporttest" 35 "go.uber.org/yarpc/yarpcerrors" 36 "google.golang.org/grpc/codes" 37 ) 38 39 func TestNewOK(t *testing.T) { 40 err := NewError(yarpcerrors.CodeOK, "okay") 41 assert.Nil(t, err) 42 43 assert.Equal(t, yarpcerrors.FromError(err).Code(), yarpcerrors.CodeOK) 44 assert.Equal(t, yarpcerrors.FromError(err).Message(), "") 45 } 46 47 func TestNew(t *testing.T) { 48 err := NewError(yarpcerrors.CodeNotFound, "unfounded accusation") 49 assert.Equal(t, yarpcerrors.FromError(err).Code(), yarpcerrors.CodeNotFound) 50 assert.Equal(t, yarpcerrors.FromError(err).Message(), "unfounded accusation") 51 assert.Contains(t, err.Error(), "unfounded accusation") 52 } 53 54 func TestForeignError(t *testing.T) { 55 err := errors.New("to err is go") 56 assert.Equal(t, yarpcerrors.FromError(err).Code(), yarpcerrors.CodeUnknown) 57 assert.Equal(t, yarpcerrors.FromError(err).Message(), "to err is go") 58 } 59 60 func TestConvertToYARPCErrorWithWrappedError(t *testing.T) { 61 errDetail := &types.BytesValue{Value: []byte("err detail bytes")} 62 63 pbErr := NewError( 64 yarpcerrors.CodeAborted, 65 "aborted", 66 WithErrorDetails(errDetail)) 67 68 wrappedErr := fmt.Errorf("wrapped err 2: %w", fmt.Errorf("wrapped err 1: %w", pbErr)) 69 70 err := convertToYARPCError(Encoding, wrappedErr, &codec{}, nil /* resw */) 71 require.True(t, yarpcerrors.IsStatus(err), "unexpected error") 72 assert.Equal(t, yarpcerrors.FromError(err).Code(), yarpcerrors.CodeAborted, "unexpected err code") 73 assert.Equal(t, yarpcerrors.FromError(err).Message(), "aborted", "unexpected error message") 74 75 gotDetails := yarpcerrors.FromError(err).Details() 76 assert.NotEmpty(t, gotDetails, "no details marshaled") 77 } 78 79 func TestConvertToYARPCErrorApplicationErrorMeta(t *testing.T) { 80 errDetails := []proto.Message{ 81 &types.StringValue{Value: "detail message"}, 82 &types.Int32Value{Value: 42}, 83 &types.BytesValue{Value: []byte("detail bytes")}, 84 } 85 86 pbErr := NewError( 87 yarpcerrors.CodeAborted, 88 "aborted", 89 WithErrorDetails(errDetails...)) 90 91 resw := &transporttest.FakeResponseWriter{} 92 err := convertToYARPCError(Encoding, pbErr, &codec{}, resw) 93 require.Error(t, err) 94 95 require.NotNil(t, resw.ApplicationErrorMeta) 96 assert.Equal(t, "StringValue", resw.ApplicationErrorMeta.Name, "expected first error detail name") 97 assert.Equal(t, 98 `[]{ StringValue{value:"detail message" } , Int32Value{value:42 } , BytesValue{value:"detail bytes" } }`, 99 resw.ApplicationErrorMeta.Details, 100 "unexpected string of error details") 101 assert.Nil(t, resw.ApplicationErrorMeta.Code, "code should nil") 102 } 103 104 func TestMessageNameWithoutPackage(t *testing.T) { 105 tests := []struct { 106 name string 107 give string 108 want string 109 }{ 110 { 111 name: "fqn", 112 give: "uber.foo.bar.baz.MessageName", 113 want: "MessageName", 114 }, 115 { 116 name: "not fully qualified", 117 give: "MyMessage", 118 want: "MyMessage", 119 }, 120 } 121 122 for _, tt := range tests { 123 t.Run(tt.name, func(t *testing.T) { 124 assert.Equal(t, tt.want, messageNameWithoutPackage(tt.give), "unexpected trim") 125 }) 126 } 127 } 128 129 type yarpcError interface{ YARPCError() *yarpcerrors.Status } 130 131 func TestPbErrorToYARPCError(t *testing.T) { 132 tests := []struct { 133 name string 134 code yarpcerrors.Code 135 message string 136 details []proto.Message 137 expectedGRPCCode codes.Code 138 }{ 139 { 140 name: "pbError without details", 141 code: yarpcerrors.CodeAborted, 142 message: "simple test", 143 expectedGRPCCode: codes.Aborted, 144 }, 145 { 146 name: "pbError with single detail", 147 code: yarpcerrors.CodeInternal, 148 message: "internal error", 149 expectedGRPCCode: codes.Internal, 150 details: []proto.Message{ 151 &types.StringValue{Value: "test value"}, 152 }, 153 }, 154 { 155 name: "pbError with multiple details", 156 code: yarpcerrors.CodeNotFound, 157 message: "not found error", 158 expectedGRPCCode: codes.NotFound, 159 details: []proto.Message{ 160 &types.StringValue{Value: "test value"}, 161 &types.Int32Value{Value: 45}, 162 &types.Any{Value: []byte{1, 2, 3, 4, 5}}, 163 }, 164 }, 165 } 166 167 for _, tt := range tests { 168 t.Run(tt.name, func(t *testing.T) { 169 var errOpts []ErrorOption 170 if len(tt.details) > 0 { 171 errOpts = append(errOpts, WithErrorDetails(tt.details...)) 172 } 173 pberror := NewError(tt.code, tt.message, errOpts...) 174 st := pberror.(yarpcError).YARPCError() 175 assert.Equal(t, st.Code(), tt.code) 176 assert.Equal(t, st.Message(), tt.message) 177 178 statusPb := rpc.Status{} 179 err := proto.Unmarshal(st.Details(), &statusPb) 180 assert.NoError(t, err, "unexpected unmarshal error") 181 182 status := status.FromProto(&statusPb) 183 assert.Equal(t, tt.expectedGRPCCode, status.Code(), "unexpected grpc status code") 184 assert.Equal(t, tt.message, status.Message(), "unexpected grpc status message") 185 assert.Len(t, status.Details(), len(tt.details), "unexpected details length") 186 for i, detail := range tt.details { 187 assert.Equal(t, detail, status.Details()[i]) 188 } 189 }) 190 } 191 } 192 193 func TestConvertToYARPCErrorWithIncorrectEncoding(t *testing.T) { 194 pberr := &pberror{code: yarpcerrors.CodeAborted, message: "test"} 195 err := convertToYARPCError("thrift", pberr, &codec{}, nil) 196 assert.Error(t, err, "unexpected empty error") 197 assert.Equal(t, err.Error(), 198 "code:internal message:encoding.Expect should have handled encoding \"thrift\" but did not") 199 } 200 201 func TestConvertFromYARPCError(t *testing.T) { 202 t.Run("incorrect encoding", func(t *testing.T) { 203 yerr := yarpcerrors.Newf(yarpcerrors.CodeAborted, "test").WithDetails([]byte{1, 2}) 204 err := convertFromYARPCError("thrift", yerr, &codec{}) 205 assert.Equal(t, err.Error(), 206 `code:internal message:encoding.Expect should have handled encoding "thrift" but did not`) 207 }) 208 t.Run("empty details", func(t *testing.T) { 209 yerr := yarpcerrors.Newf(yarpcerrors.CodeAborted, "test") 210 err := convertFromYARPCError(Encoding, yerr, &codec{}) 211 assert.Equal(t, err.Error(), "code:aborted message:test") 212 }) 213 } 214 215 func TestCreateStatusWithDetailErrors(t *testing.T) { 216 t.Run("code ok", func(t *testing.T) { 217 pberr := &pberror{code: yarpcerrors.CodeOK, message: "test"} 218 _, err := createStatusWithDetail(pberr, Encoding, &codec{}) 219 assert.Error(t, err, "unexpected empty error") 220 assert.Equal(t, err.Error(), "no status error for error with code OK") 221 }) 222 223 t.Run("unsupported code", func(t *testing.T) { 224 pberr := &pberror{code: 99, message: "test"} 225 _, err := createStatusWithDetail(pberr, Encoding, &codec{}) 226 assert.Error(t, err, "unexpected empty error") 227 assert.Equal(t, err.Error(), "no status error for error with code 99") 228 }) 229 230 t.Run("unsupported encoding", func(t *testing.T) { 231 pberr := &pberror{code: yarpcerrors.CodeAborted} 232 _, err := createStatusWithDetail(pberr, "thrift", &codec{}) 233 assert.Error(t, err, "unexpected empty error") 234 assert.Equal(t, err.Error(), 235 "code:internal message:encoding.Expect should have handled encoding \"thrift\" but did not") 236 }) 237 } 238 239 func TestErrorHandling(t *testing.T) { 240 t.Run("GetErrorDetail empty error handling", func(t *testing.T) { 241 assert.Nil(t, GetErrorDetails(nil), "unexpected details") 242 }) 243 t.Run("GetErrorDetail non pberror", func(t *testing.T) { 244 assert.Nil(t, GetErrorDetails(errors.New("test")), "unexpected details") 245 }) 246 t.Run("GetErrorDetail with no error detail", func(t *testing.T) { 247 err := NewError( 248 yarpcerrors.CodeAborted, 249 "aborted") 250 assert.Nil(t, GetErrorDetails(err), "unexpected details") 251 }) 252 t.Run("PbError empty error handling", func(t *testing.T) { 253 var pbErr *pberror 254 assert.Nil(t, pbErr.YARPCError(), "unexpected yarpcerror") 255 }) 256 t.Run("PbError with nil detail message", func(t *testing.T) { 257 pbErr := &pberror{ 258 details: []*types.Any{nil}, 259 } 260 require.Len(t, GetErrorDetails(pbErr), 1) 261 assert.Equal(t, GetErrorDetails(pbErr)[0], fmt.Errorf("message is nil")) 262 }) 263 t.Run("NewError with nil error detail", func(t *testing.T) { 264 err := NewError( 265 yarpcerrors.CodeAborted, 266 "aborted", 267 WithErrorDetails([]proto.Message{nil}...)) 268 assert.Equal(t, err, fmt.Errorf("proto: Marshal called with nil")) 269 }) 270 } 271 272 func TestSetApplicationErrorMeta(t *testing.T) { 273 respErr := NewError( 274 yarpcerrors.CodeAborted, 275 "aborted", 276 ) 277 278 anyString, err := types.MarshalAny(&types.StringValue{Value: "baz"}) 279 require.NoError(t, err) 280 281 pbErr := respErr.(*pberror) 282 pbErr.details = append(pbErr.details, &types.Any{TypeUrl: "foo", Value: []byte("bar")}) 283 pbErr.details = append(pbErr.details, anyString) 284 285 resw := &transporttest.FakeResponseWriter{} 286 setApplicationErrorMeta(pbErr, resw) 287 288 assert.Equal(t, "StringValue", resw.ApplicationErrorMeta.Name) 289 assert.Equal(t, `[]{ StringValue{value:"baz" } }`, resw.ApplicationErrorMeta.Details) 290 }