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  }