go.uber.org/yarpc@v1.72.1/encoding/thrift/inbound_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  //lint:file-ignore SA1019 'mock' usage
    22  package thrift
    23  
    24  import (
    25  	"bytes"
    26  	"context"
    27  	"errors"
    28  	"fmt"
    29  	"io"
    30  	"testing"
    31  
    32  	"github.com/golang/mock/gomock"
    33  	"github.com/stretchr/testify/assert"
    34  	"github.com/stretchr/testify/require"
    35  	"go.uber.org/thriftrw/protocol"
    36  	"go.uber.org/thriftrw/thrifttest"
    37  	"go.uber.org/thriftrw/wire"
    38  	"go.uber.org/yarpc/api/transport"
    39  	"go.uber.org/yarpc/api/transport/transporttest"
    40  	"go.uber.org/yarpc/internal/testtime"
    41  	"go.uber.org/yarpc/yarpcerrors"
    42  )
    43  
    44  func TestDecodeRequest(t *testing.T) {
    45  	mockCtrl := gomock.NewController(t)
    46  	defer mockCtrl.Finish()
    47  
    48  	proto := thrifttest.NewMockEnvelopeAgnosticProtocol(mockCtrl)
    49  	proto.EXPECT().DecodeRequest(wire.Call, gomock.Any()).Return(
    50  		wire.NewValueStruct(wire.Struct{}), protocol.NoEnvelopeResponder, nil)
    51  
    52  	ctx, cancel := context.WithTimeout(context.Background(), testtime.Second)
    53  	defer cancel()
    54  
    55  	handler := func(ctx context.Context, w wire.Value) (Response, error) {
    56  		return Response{Body: fakeEnveloper(wire.Reply)}, nil
    57  	}
    58  	h := thriftUnaryHandler{Protocol: proto, UnaryHandler: handler}
    59  
    60  	rw := new(transporttest.FakeResponseWriter)
    61  	err := h.Handle(ctx, request(), rw)
    62  
    63  	assert.NoError(t, err, "unexpected error")
    64  	assert.False(t, rw.IsApplicationError, "application error bit set")
    65  }
    66  
    67  func TestDecodeEnveloped(t *testing.T) {
    68  	mockCtrl := gomock.NewController(t)
    69  	defer mockCtrl.Finish()
    70  
    71  	proto := thrifttest.NewMockProtocol(mockCtrl)
    72  	// XXX DecodeEnveloped instead of DecodeRequest or Decode(TStruct, ...)
    73  	proto.EXPECT().DecodeEnveloped(gomock.Any()).Return(wire.Envelope{
    74  		Name:  "someMethod",
    75  		SeqID: 42,
    76  		Type:  wire.Call,
    77  		Value: wire.NewValueStruct(wire.Struct{}),
    78  	}, nil)
    79  
    80  	ctx, cancel := context.WithTimeout(context.Background(), testtime.Second)
    81  	defer cancel()
    82  
    83  	handler := func(ctx context.Context, w wire.Value) (Response, error) {
    84  		assert.True(t, wire.ValuesAreEqual(wire.NewValueStruct(wire.Struct{}), w), "request body did not match")
    85  		return Response{Body: fakeEnveloper(wire.Reply)}, nil
    86  	}
    87  	// XXX Enveloping true for this case, to induce DecodeEnveloped instead of
    88  	// DecodeRequest or Decode.
    89  	h := thriftUnaryHandler{Protocol: proto, UnaryHandler: handler, Enveloping: true}
    90  	err := unaryCall(ctx, h)
    91  
    92  	assert.NoError(t, err, "unexpected error")
    93  }
    94  
    95  func TestDecodeRequestApplicationError(t *testing.T) {
    96  	mockCtrl := gomock.NewController(t)
    97  	defer mockCtrl.Finish()
    98  
    99  	proto := thrifttest.NewMockEnvelopeAgnosticProtocol(mockCtrl)
   100  	proto.EXPECT().DecodeRequest(wire.Call, gomock.Any()).Return(
   101  		wire.NewValueStruct(wire.Struct{}), protocol.NoEnvelopeResponder, nil)
   102  
   103  	ctx, cancel := context.WithTimeout(context.Background(), testtime.Second)
   104  	defer cancel()
   105  
   106  	codeNotFound := yarpcerrors.CodeNotFound
   107  
   108  	handler := func(ctx context.Context, w wire.Value) (Response, error) {
   109  		// XXX setting application error bit
   110  		return Response{
   111  			Body:                 fakeEnveloper(wire.Reply),
   112  			IsApplicationError:   true,
   113  			ApplicationErrorName: "thrift-defined-error",
   114  			ApplicationErrorCode: &codeNotFound,
   115  		}, nil
   116  	}
   117  	h := thriftUnaryHandler{Protocol: proto, UnaryHandler: handler}
   118  
   119  	// XXX checking error bit
   120  	rw := new(transporttest.FakeResponseWriter)
   121  	err := h.Handle(ctx, request(), rw)
   122  	assert.True(t, rw.IsApplicationError, "application error bit unset")
   123  	assert.Equal(t, "thrift-defined-error", rw.ApplicationErrorMeta.Name,
   124  		"application error name mismatch")
   125  	assert.Equal(t, &codeNotFound, rw.ApplicationErrorMeta.Code,
   126  		"application error code mismatch")
   127  
   128  	assert.NoError(t, err, "unexpected error")
   129  }
   130  
   131  func TestDecodeRequestEncodingError(t *testing.T) {
   132  	mockCtrl := gomock.NewController(t)
   133  	defer mockCtrl.Finish()
   134  
   135  	ctx, cancel := context.WithTimeout(context.Background(), testtime.Second)
   136  	defer cancel()
   137  
   138  	// XXX handler and protocol superfluous for this case
   139  	h := thriftUnaryHandler{}
   140  	rw := new(transporttest.FakeResponseWriter)
   141  	req := request()
   142  	// XXX bogus encoding override
   143  	req.Encoding = transport.Encoding("bogus")
   144  	err := h.Handle(ctx, req, rw)
   145  
   146  	if assert.Error(t, err, "expected an error") {
   147  		assert.Contains(t, err.Error(), `expected encoding "thrift" but got "bogus"`)
   148  	}
   149  }
   150  
   151  func TestDecodeRequestReadError(t *testing.T) {
   152  	mockCtrl := gomock.NewController(t)
   153  	defer mockCtrl.Finish()
   154  
   155  	ctx, cancel := context.WithTimeout(context.Background(), testtime.Second)
   156  	defer cancel()
   157  
   158  	// XXX handler and protocol superfluous for this case
   159  	h := thriftUnaryHandler{}
   160  	rw := new(transporttest.FakeResponseWriter)
   161  	req := request()
   162  	// XXX override body with a bad reader, returns error on read
   163  	req.Body = &badReader{}
   164  	err := h.Handle(ctx, req, rw)
   165  
   166  	if assert.Error(t, err, "expected an error") {
   167  		assert.Contains(t, err.Error(), "bad reader error")
   168  	}
   169  }
   170  
   171  type badReader struct{}
   172  
   173  func (badReader) Read(buf []byte) (int, error) {
   174  	return 0, fmt.Errorf("bad reader error")
   175  }
   176  
   177  func TestDecodeRequestError(t *testing.T) {
   178  	mockCtrl := gomock.NewController(t)
   179  	defer mockCtrl.Finish()
   180  
   181  	proto := thrifttest.NewMockEnvelopeAgnosticProtocol(mockCtrl)
   182  	// XXX DecodeRequest returns decode request error
   183  	proto.EXPECT().DecodeRequest(wire.Call, gomock.Any()).Return(
   184  		wire.Value{}, protocol.NoEnvelopeResponder, fmt.Errorf("decode request error"))
   185  
   186  	ctx, cancel := context.WithTimeout(context.Background(), testtime.Second)
   187  	defer cancel()
   188  
   189  	h := thriftUnaryHandler{Protocol: proto}
   190  	err := unaryCall(ctx, h)
   191  
   192  	if assert.Error(t, err, "expected an error") {
   193  		assert.Contains(t, err.Error(), "decode request error")
   194  	}
   195  }
   196  
   197  func TestDecodeRequestResponseError(t *testing.T) {
   198  	mockCtrl := gomock.NewController(t)
   199  	defer mockCtrl.Finish()
   200  
   201  	proto := thrifttest.NewMockEnvelopeAgnosticProtocol(mockCtrl)
   202  	// XXX threads a fake error responder, which returns an error on
   203  	// EncodeResponse in thriftUnaryHandler.Handle.
   204  	proto.EXPECT().DecodeRequest(wire.Call, gomock.Any()).Return(
   205  		wire.Value{}, errorResponder{fmt.Errorf("encode response error")}, nil)
   206  
   207  	ctx, cancel := context.WithTimeout(context.Background(), testtime.Second)
   208  	defer cancel()
   209  
   210  	handler := func(ctx context.Context, w wire.Value) (Response, error) {
   211  		return Response{Body: fakeEnveloper(wire.Reply)}, nil
   212  	}
   213  	h := thriftUnaryHandler{Protocol: proto, UnaryHandler: handler}
   214  	err := unaryCall(ctx, h)
   215  
   216  	if assert.Error(t, err, "expected an error") {
   217  		assert.Contains(t, err.Error(), "encode response error")
   218  	}
   219  }
   220  
   221  type closeWrapper struct {
   222  	io.Reader
   223  	closeErr error
   224  }
   225  
   226  func (c closeWrapper) Close() error {
   227  	return c.closeErr
   228  }
   229  
   230  func TestDecodeRequestClose(t *testing.T) {
   231  	t.Run("successful close", func(t *testing.T) {
   232  		mockCtrl := gomock.NewController(t)
   233  		defer mockCtrl.Finish()
   234  
   235  		proto := thrifttest.NewMockEnvelopeAgnosticProtocol(mockCtrl)
   236  		proto.EXPECT().DecodeRequest(wire.Call, gomock.Any()).Return(
   237  			wire.Value{}, protocol.NoEnvelopeResponder, nil)
   238  
   239  		ctx, cancel := context.WithTimeout(context.Background(), testtime.Second)
   240  		defer cancel()
   241  
   242  		handler := func(ctx context.Context, w wire.Value) (Response, error) {
   243  			return Response{Body: fakeEnveloper(wire.Reply)}, nil
   244  		}
   245  		h := thriftUnaryHandler{Protocol: proto, UnaryHandler: handler}
   246  		req := request()
   247  
   248  		// Add close method to the body.
   249  		req.Body = closeWrapper{req.Body, nil /* close error */}
   250  		err := h.Handle(ctx, req, new(transporttest.FakeResponseWriter))
   251  		require.NoError(t, err)
   252  	})
   253  
   254  	t.Run("close error", func(t *testing.T) {
   255  		ctx, cancel := context.WithTimeout(context.Background(), testtime.Second)
   256  		defer cancel()
   257  
   258  		// Proto and Handler won't get used because of the close error.
   259  		h := thriftUnaryHandler{}
   260  		req := request()
   261  
   262  		// Add close method to the body that returns an error.
   263  		req.Body = closeWrapper{req.Body, errors.New("close failed")}
   264  		err := h.Handle(ctx, req, new(transporttest.FakeResponseWriter))
   265  		require.Error(t, err)
   266  	})
   267  }
   268  
   269  func TestDecodeEnvelopedError(t *testing.T) {
   270  	mockCtrl := gomock.NewController(t)
   271  	defer mockCtrl.Finish()
   272  
   273  	proto := thrifttest.NewMockProtocol(mockCtrl)
   274  	// XXX DecodeEnveloped returns error
   275  	proto.EXPECT().DecodeEnveloped(gomock.Any()).Return(wire.Envelope{}, fmt.Errorf("decode enveloped error"))
   276  
   277  	ctx, cancel := context.WithTimeout(context.Background(), testtime.Second)
   278  	defer cancel()
   279  
   280  	h := thriftUnaryHandler{Protocol: proto, Enveloping: true}
   281  	err := unaryCall(ctx, h)
   282  
   283  	if assert.Error(t, err, "expected an error") {
   284  		assert.Contains(t, err.Error(), "decode enveloped error")
   285  	}
   286  }
   287  
   288  func TestDecodeEnvelopedEnvelopeTypeError(t *testing.T) {
   289  	mockCtrl := gomock.NewController(t)
   290  	defer mockCtrl.Finish()
   291  
   292  	proto := thrifttest.NewMockProtocol(mockCtrl)
   293  	// XXX DecodeEnveloped returns OneWay instead of expected Call
   294  	proto.EXPECT().DecodeEnveloped(gomock.Any()).Return(wire.Envelope{Type: wire.OneWay}, nil)
   295  
   296  	ctx, cancel := context.WithTimeout(context.Background(), testtime.Second)
   297  	defer cancel()
   298  
   299  	h := thriftUnaryHandler{Protocol: proto, Enveloping: true}
   300  	err := unaryCall(ctx, h)
   301  
   302  	if assert.Error(t, err, "expected an error") {
   303  		assert.Contains(t, err.Error(), "unexpected envelope type: OneWay")
   304  	}
   305  }
   306  
   307  func TestDecodeNotEnvelopedError(t *testing.T) {
   308  	mockCtrl := gomock.NewController(t)
   309  	defer mockCtrl.Finish()
   310  
   311  	proto := thrifttest.NewMockProtocol(mockCtrl)
   312  	// XXX Mocked decode returns decode error
   313  	proto.EXPECT().Decode(gomock.Any(), wire.TStruct).Return(wire.Value{}, fmt.Errorf("decode error"))
   314  
   315  	ctx, cancel := context.WithTimeout(context.Background(), testtime.Second)
   316  	defer cancel()
   317  
   318  	h := thriftUnaryHandler{Protocol: proto}
   319  	err := unaryCall(ctx, h)
   320  
   321  	if assert.Error(t, err, "expected an error") {
   322  		assert.Contains(t, err.Error(), "decode error")
   323  	}
   324  }
   325  
   326  func TestUnaryHandlerError(t *testing.T) {
   327  	mockCtrl := gomock.NewController(t)
   328  	defer mockCtrl.Finish()
   329  
   330  	proto := thrifttest.NewMockProtocol(mockCtrl)
   331  	proto.EXPECT().Decode(gomock.Any(), wire.TStruct).Return(wire.Value{}, nil)
   332  
   333  	ctx, cancel := context.WithTimeout(context.Background(), testtime.Second)
   334  	defer cancel()
   335  
   336  	handler := func(ctx context.Context, w wire.Value) (Response, error) {
   337  		// XXX returns error
   338  		return Response{}, fmt.Errorf("unary handler error")
   339  	}
   340  	h := thriftUnaryHandler{Protocol: proto, UnaryHandler: handler}
   341  	err := unaryCall(ctx, h)
   342  
   343  	if assert.Error(t, err, "expected an error") {
   344  		assert.Contains(t, err.Error(), "unary handler error")
   345  	}
   346  }
   347  
   348  func TestUnaryHandlerResponseEnvelopeTypeError(t *testing.T) {
   349  	mockCtrl := gomock.NewController(t)
   350  	defer mockCtrl.Finish()
   351  
   352  	proto := thrifttest.NewMockProtocol(mockCtrl)
   353  	proto.EXPECT().Decode(gomock.Any(), wire.TStruct).Return(wire.Value{}, nil)
   354  
   355  	ctx, cancel := context.WithTimeout(context.Background(), testtime.Second)
   356  	defer cancel()
   357  
   358  	handler := func(ctx context.Context, w wire.Value) (Response, error) {
   359  		// XXX OneWay instead of Reply
   360  		return Response{Body: fakeEnveloper(wire.OneWay)}, nil
   361  	}
   362  	h := thriftUnaryHandler{Protocol: proto, UnaryHandler: handler}
   363  	err := unaryCall(ctx, h)
   364  
   365  	if assert.Error(t, err, "expected an error") {
   366  		assert.Contains(t, err.Error(), "unexpected envelope type: OneWay")
   367  	}
   368  }
   369  
   370  func TestUnaryHandlerBodyToWireError(t *testing.T) {
   371  	mockCtrl := gomock.NewController(t)
   372  	defer mockCtrl.Finish()
   373  
   374  	proto := thrifttest.NewMockProtocol(mockCtrl)
   375  	proto.EXPECT().Decode(gomock.Any(), wire.TStruct).Return(wire.Value{}, nil)
   376  
   377  	ctx, cancel := context.WithTimeout(context.Background(), testtime.Second)
   378  	defer cancel()
   379  
   380  	handler := func(ctx context.Context, w wire.Value) (Response, error) {
   381  		// XXX Body.ToWire returns error
   382  		return Response{Body: errorEnveloper{wire.Reply, fmt.Errorf("to wire error")}}, nil
   383  	}
   384  	h := thriftUnaryHandler{Protocol: proto, UnaryHandler: handler}
   385  	err := unaryCall(ctx, h)
   386  
   387  	if assert.Error(t, err, "expected an error") {
   388  		assert.Contains(t, err.Error(), "to wire error")
   389  	}
   390  }
   391  
   392  func TestOnewayHandler(t *testing.T) {
   393  	mockCtrl := gomock.NewController(t)
   394  	defer mockCtrl.Finish()
   395  
   396  	proto := thrifttest.NewMockEnvelopeAgnosticProtocol(mockCtrl)
   397  	// XXX expecting OneWay request instead of Call
   398  	proto.EXPECT().DecodeRequest(wire.OneWay, gomock.Any()).Return(
   399  		wire.NewValueStruct(wire.Struct{}), protocol.NoEnvelopeResponder, nil)
   400  
   401  	ctx, cancel := context.WithTimeout(context.Background(), testtime.Second)
   402  	defer cancel()
   403  
   404  	handler := func(ctx context.Context, v wire.Value) error {
   405  		return nil
   406  	}
   407  	h := thriftOnewayHandler{Protocol: proto, OnewayHandler: handler}
   408  	err := h.HandleOneway(ctx, request())
   409  
   410  	// XXX expecting success in this case
   411  	assert.NoError(t, err, "unexpected error")
   412  }
   413  
   414  func TestOnewayHandlerError(t *testing.T) {
   415  	mockCtrl := gomock.NewController(t)
   416  	defer mockCtrl.Finish()
   417  
   418  	proto := thrifttest.NewMockEnvelopeAgnosticProtocol(mockCtrl)
   419  	// XXX mock returns decode request error, to induce error path out of handleRequest in HandleOneway
   420  	proto.EXPECT().DecodeRequest(wire.OneWay, gomock.Any()).Return(
   421  		wire.Value{}, protocol.NoEnvelopeResponder, fmt.Errorf("decode request error"))
   422  
   423  	ctx, cancel := context.WithTimeout(context.Background(), testtime.Second)
   424  	defer cancel()
   425  
   426  	handler := func(ctx context.Context, v wire.Value) error {
   427  		return nil
   428  	}
   429  	h := thriftOnewayHandler{Protocol: proto, OnewayHandler: handler}
   430  	err := h.HandleOneway(ctx, request())
   431  
   432  	if assert.Error(t, err, "expected an error") {
   433  		assert.Contains(t, err.Error(), "decode request error")
   434  	}
   435  }
   436  
   437  func request() *transport.Request {
   438  	return &transport.Request{
   439  		Caller:    "caller",
   440  		Service:   "service",
   441  		Encoding:  "thrift",
   442  		Procedure: "MyService::someMethod",
   443  		Body:      bytes.NewReader([]byte("irrelevant")),
   444  	}
   445  }
   446  
   447  func unaryCall(ctx context.Context, h thriftUnaryHandler) error {
   448  	rw := new(transporttest.FakeResponseWriter)
   449  	return h.Handle(ctx, request(), rw)
   450  }