go.uber.org/yarpc@v1.72.1/encoding/protobuf/v2/error_integration_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_test
    22  
    23  import (
    24  	"context"
    25  	"net"
    26  	"testing"
    27  	"time"
    28  
    29  	"github.com/golang/protobuf/ptypes/wrappers"
    30  	"github.com/stretchr/testify/assert"
    31  	"github.com/stretchr/testify/require"
    32  	"go.uber.org/yarpc"
    33  	"go.uber.org/yarpc/api/transport"
    34  	"go.uber.org/yarpc/encoding/json"
    35  	"go.uber.org/yarpc/encoding/protobuf/internal/testpb/v2"
    36  	"go.uber.org/yarpc/encoding/protobuf/v2"
    37  	"go.uber.org/yarpc/encoding/raw"
    38  	"go.uber.org/yarpc/transport/grpc"
    39  	"go.uber.org/yarpc/yarpcerrors"
    40  	rpc_status "google.golang.org/genproto/googleapis/rpc/status"
    41  	"google.golang.org/grpc/status"
    42  	"google.golang.org/protobuf/proto"
    43  )
    44  
    45  const (
    46  	_clientName = "caller"
    47  	_serverName = "callee"
    48  )
    49  
    50  type errorServer struct{}
    51  
    52  func (errorServer) Unary(ctx context.Context, msg *testpb.TestMessage) (*testpb.TestMessage, error) {
    53  	testDetails := []proto.Message{
    54  		&wrappers.StringValue{Value: "string value"},
    55  		&wrappers.Int32Value{Value: 100},
    56  	}
    57  	return nil,
    58  		v2.NewError(yarpcerrors.CodeInvalidArgument, msg.Value,
    59  			v2.WithErrorDetails(testDetails...))
    60  }
    61  
    62  func (errorServer) Duplex(stream testpb.TestServiceDuplexYARPCServer) error {
    63  	testDetails := []proto.Message{
    64  		&wrappers.StringValue{Value: "string value"},
    65  		&wrappers.Int32Value{Value: 100},
    66  	}
    67  	msg, err := stream.Recv()
    68  	if err != nil {
    69  		return err
    70  	}
    71  	return v2.NewError(yarpcerrors.CodeInvalidArgument, msg.Value,
    72  		v2.WithErrorDetails(testDetails...))
    73  }
    74  
    75  func TestProtoGrpcServerErrorDetails(t *testing.T) {
    76  	listener, err := net.Listen("tcp", "127.0.0.1:0")
    77  	require.NoError(t, err)
    78  
    79  	inbound := grpc.NewTransport().NewInbound(listener)
    80  	dispatcher := yarpc.NewDispatcher(yarpc.Config{
    81  		Name:     _serverName,
    82  		Inbounds: yarpc.Inbounds{inbound},
    83  		Logging:  yarpc.LoggingConfig{},
    84  		Metrics:  yarpc.MetricsConfig{},
    85  	})
    86  
    87  	dispatcher.Register(testpb.BuildTestYARPCProcedures(&errorServer{}))
    88  	require.NoError(t, dispatcher.Start(), "could not start server dispatcher")
    89  
    90  	addr := inbound.Addr().String()
    91  	outbound := grpc.NewTransport().NewSingleOutbound(addr)
    92  	clientDispatcher := yarpc.NewDispatcher(yarpc.Config{
    93  		Name: _clientName,
    94  		Outbounds: map[string]transport.Outbounds{
    95  			_serverName: {
    96  				ServiceName: _serverName,
    97  				Unary:       outbound,
    98  				Stream:      outbound,
    99  			},
   100  		},
   101  		Logging: yarpc.LoggingConfig{},
   102  		Metrics: yarpc.MetricsConfig{},
   103  	})
   104  
   105  	client := testpb.NewTestYARPCClient(clientDispatcher.ClientConfig(_serverName))
   106  	require.NoError(t, clientDispatcher.Start(), "could not start client dispatcher")
   107  
   108  	defer func() {
   109  		assert.NoError(t, dispatcher.Stop(), "could not stop dispatcher")
   110  		assert.NoError(t, clientDispatcher.Stop(), "could not stop client dispatcher")
   111  	}()
   112  
   113  	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
   114  	defer cancel()
   115  
   116  	const errorMsg = "error msg"
   117  
   118  	_, err = client.Unary(ctx, &testpb.TestMessage{Value: errorMsg})
   119  	assert.NotNil(t, err, "unexpected nil error")
   120  	st := yarpcerrors.FromError(err)
   121  	assert.Equal(t, yarpcerrors.CodeInvalidArgument, st.Code(), "unexpected error code")
   122  	assert.Equal(t, errorMsg, st.Message(), "unexpected error message")
   123  	expectedDetails := []interface{}{
   124  		&wrappers.StringValue{Value: "string value"},
   125  		&wrappers.Int32Value{Value: 100},
   126  	}
   127  	actualDetails := v2.GetErrorDetails(err)
   128  	for i := 0; i < len(expectedDetails); i++ {
   129  		assert.True(t, proto.Equal(expectedDetails[i].(proto.Message), actualDetails[i].(proto.Message)), "unexpected error details")
   130  	}
   131  }
   132  
   133  func TestProtoGrpcStreamServerErrorDetails(t *testing.T) {
   134  	listener, err := net.Listen("tcp", "127.0.0.1:0")
   135  	require.NoError(t, err)
   136  
   137  	inbound := grpc.NewTransport().NewInbound(listener)
   138  	dispatcher := yarpc.NewDispatcher(yarpc.Config{
   139  		Name:     _serverName,
   140  		Inbounds: yarpc.Inbounds{inbound},
   141  		Logging:  yarpc.LoggingConfig{},
   142  		Metrics:  yarpc.MetricsConfig{},
   143  	})
   144  
   145  	dispatcher.Register(testpb.BuildTestYARPCProcedures(&errorServer{}))
   146  	require.NoError(t, dispatcher.Start(), "could not start server dispatcher")
   147  
   148  	addr := inbound.Addr().String()
   149  	outbound := grpc.NewTransport().NewSingleOutbound(addr)
   150  	clientDispatcher := yarpc.NewDispatcher(yarpc.Config{
   151  		Name: _clientName,
   152  		Outbounds: map[string]transport.Outbounds{
   153  			_serverName: {
   154  				ServiceName: _serverName,
   155  				Unary:       outbound,
   156  				Stream:      outbound,
   157  			},
   158  		},
   159  		Logging: yarpc.LoggingConfig{},
   160  		Metrics: yarpc.MetricsConfig{},
   161  	})
   162  
   163  	client := testpb.NewTestYARPCClient(clientDispatcher.ClientConfig(_serverName))
   164  	require.NoError(t, clientDispatcher.Start(), "could not start client dispatcher")
   165  
   166  	defer func() {
   167  		assert.NoError(t, dispatcher.Stop(), "could not stop dispatcher")
   168  		assert.NoError(t, clientDispatcher.Stop(), "could not stop client dispatcher")
   169  	}()
   170  
   171  	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
   172  	defer cancel()
   173  
   174  	const errorMsg = "stream error msg"
   175  	expectedDetails := []interface{}{
   176  		&wrappers.StringValue{Value: "string value"},
   177  		&wrappers.Int32Value{Value: 100},
   178  	}
   179  
   180  	streamHandle, err := client.Duplex(ctx)
   181  	assert.NoError(t, err, "unexpected error")
   182  
   183  	err = streamHandle.Send(&testpb.TestMessage{Value: errorMsg})
   184  	assert.NoError(t, err, "unexpected error")
   185  
   186  	msg, err := streamHandle.Recv()
   187  	assert.Nil(t, msg, "unexpected non-nil reply")
   188  	assert.Error(t, err, "unexpected nil error")
   189  
   190  	st := yarpcerrors.FromError(err)
   191  	assert.Equal(t, yarpcerrors.CodeInvalidArgument, st.Code(), "unexpected error code")
   192  	assert.Equal(t, errorMsg, st.Message(), "unexpected error message")
   193  
   194  	actualDetails := v2.GetErrorDetails(err)
   195  	for i := 0; i < len(expectedDetails); i++ {
   196  		assert.True(t, proto.Equal(expectedDetails[i].(proto.Message), actualDetails[i].(proto.Message)), "unexpected error details")
   197  	}
   198  }
   199  
   200  type errorRawServer struct{}
   201  
   202  func (errorRawServer) Handle(ctx context.Context, req *transport.Request, resw transport.ResponseWriter) error {
   203  	testDetails := []proto.Message{
   204  		&wrappers.StringValue{Value: "string value"},
   205  		&wrappers.Int32Value{Value: 100},
   206  	}
   207  	return v2.NewError(yarpcerrors.CodeInvalidArgument, "error message",
   208  		v2.WithErrorDetails(testDetails...))
   209  }
   210  
   211  func TestRawGrpcServerErrorDetails(t *testing.T) {
   212  	listener, err := net.Listen("tcp", "127.0.0.1:0")
   213  	require.NoError(t, err)
   214  
   215  	inbound := grpc.NewTransport().NewInbound(listener)
   216  	dispatcher := yarpc.NewDispatcher(yarpc.Config{
   217  		Name:     _serverName,
   218  		Inbounds: yarpc.Inbounds{inbound},
   219  		Logging:  yarpc.LoggingConfig{},
   220  		Metrics:  yarpc.MetricsConfig{},
   221  	})
   222  
   223  	dispatcher.Register([]transport.Procedure{{
   224  		Name:        "test::unary",
   225  		HandlerSpec: transport.NewUnaryHandlerSpec(&errorRawServer{}),
   226  		Encoding:    "raw",
   227  	}})
   228  	require.NoError(t, dispatcher.Start(), "could not start server dispatcher")
   229  
   230  	addr := inbound.Addr().String()
   231  	outbound := grpc.NewTransport().NewSingleOutbound(addr)
   232  	clientDispatcher := yarpc.NewDispatcher(yarpc.Config{
   233  		Name: _clientName,
   234  		Outbounds: map[string]transport.Outbounds{
   235  			_serverName: {
   236  				ServiceName: _serverName,
   237  				Unary:       outbound,
   238  				Stream:      outbound,
   239  			},
   240  		},
   241  		Logging: yarpc.LoggingConfig{},
   242  		Metrics: yarpc.MetricsConfig{},
   243  	})
   244  
   245  	client := raw.New(clientDispatcher.ClientConfig(_serverName))
   246  	require.NoError(t, clientDispatcher.Start(), "could not start client dispatcher")
   247  
   248  	defer func() {
   249  		assert.NoError(t, dispatcher.Stop(), "could not stop dispatcher")
   250  		assert.NoError(t, clientDispatcher.Stop(), "could not stop client dispatcher")
   251  	}()
   252  
   253  	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
   254  	defer cancel()
   255  
   256  	_, err = client.Call(ctx, "test::unary", nil)
   257  	assert.NotNil(t, err, "unexpected nil error")
   258  	yarpcStatus := yarpcerrors.FromError(err)
   259  	assert.Equal(t, yarpcerrors.CodeInvalidArgument, yarpcStatus.Code(), "unexpected error code")
   260  	assert.Equal(t, "error message", yarpcStatus.Message(), "unexpected error message")
   261  
   262  	var rpcStatus rpc_status.Status
   263  	proto.Unmarshal(yarpcStatus.Details(), &rpcStatus)
   264  	status := status.FromProto(&rpcStatus)
   265  	expectedDetails := []interface{}{
   266  		&wrappers.StringValue{Value: "string value"},
   267  		&wrappers.Int32Value{Value: 100},
   268  	}
   269  	for i := 0; i < len(expectedDetails); i++ {
   270  		assert.True(t, proto.Equal(expectedDetails[i].(proto.Message), status.Details()[i].(proto.Message)), "unexpected error details")
   271  	}
   272  }
   273  
   274  func TestJSONGrpcServerErrorDetails(t *testing.T) {
   275  	listener, err := net.Listen("tcp", "127.0.0.1:0")
   276  	require.NoError(t, err)
   277  
   278  	inbound := grpc.NewTransport().NewInbound(listener)
   279  	dispatcher := yarpc.NewDispatcher(yarpc.Config{
   280  		Name:     _serverName,
   281  		Inbounds: yarpc.Inbounds{inbound},
   282  		Logging:  yarpc.LoggingConfig{},
   283  		Metrics:  yarpc.MetricsConfig{},
   284  	})
   285  
   286  	dispatcher.Register(json.Procedure("test", func(ctx context.Context, req *struct{}) (*struct{}, error) {
   287  		testDetails := []proto.Message{
   288  			&wrappers.StringValue{Value: "string value"},
   289  			&wrappers.Int32Value{Value: 100},
   290  		}
   291  		return nil, v2.NewError(yarpcerrors.CodeInvalidArgument, "error message",
   292  			v2.WithErrorDetails(testDetails...))
   293  	}))
   294  	require.NoError(t, dispatcher.Start(), "could not start server dispatcher")
   295  
   296  	addr := inbound.Addr().String()
   297  	outbound := grpc.NewTransport().NewSingleOutbound(addr)
   298  	clientDispatcher := yarpc.NewDispatcher(yarpc.Config{
   299  		Name: _clientName,
   300  		Outbounds: map[string]transport.Outbounds{
   301  			_serverName: {
   302  				ServiceName: _serverName,
   303  				Unary:       outbound,
   304  				Stream:      outbound,
   305  			},
   306  		},
   307  		Logging: yarpc.LoggingConfig{},
   308  		Metrics: yarpc.MetricsConfig{},
   309  	})
   310  
   311  	client := json.New(clientDispatcher.ClientConfig(_serverName))
   312  	require.NoError(t, clientDispatcher.Start(), "could not start client dispatcher")
   313  
   314  	defer func() {
   315  		assert.NoError(t, dispatcher.Stop(), "could not stop dispatcher")
   316  		assert.NoError(t, clientDispatcher.Stop(), "could not stop client dispatcher")
   317  	}()
   318  
   319  	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
   320  	defer cancel()
   321  
   322  	err = client.Call(ctx, "test", nil, nil)
   323  	assert.NotNil(t, err, "unexpected nil error")
   324  	yarpcStatus := yarpcerrors.FromError(err)
   325  	assert.Equal(t, yarpcerrors.CodeInvalidArgument, yarpcStatus.Code(), "unexpected error code")
   326  	assert.Equal(t, "error message", yarpcStatus.Message(), "unexpected error message")
   327  
   328  	var rpcStatus rpc_status.Status
   329  	proto.Unmarshal(yarpcStatus.Details(), &rpcStatus)
   330  	status := status.FromProto(&rpcStatus)
   331  	expectedDetails := []interface{}{
   332  		&wrappers.StringValue{Value: "string value"},
   333  		&wrappers.Int32Value{Value: 100},
   334  	}
   335  	for i := 0; i < len(expectedDetails); i++ {
   336  		assert.True(t, proto.Equal(expectedDetails[i].(proto.Message), status.Details()[i].(proto.Message)), "unexpected error details")
   337  	}
   338  }