github.com/willyham/dosa@v2.3.1-0.20171024181418-1e446d37ee71+incompatible/connectors/yarpc/yarpc_test.go (about)

     1  // Copyright (c) 2017 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 yarpc_test
    22  
    23  import (
    24  	"context"
    25  	"testing"
    26  	"time"
    27  
    28  	"go.uber.org/yarpc/api/transport/transporttest"
    29  
    30  	"github.com/golang/mock/gomock"
    31  	"github.com/pkg/errors"
    32  	"github.com/stretchr/testify/assert"
    33  	"github.com/uber-go/dosa"
    34  	"github.com/uber-go/dosa/connectors/yarpc"
    35  	"github.com/uber-go/dosa/testutil"
    36  	drpc "github.com/uber/dosa-idl/.gen/dosa"
    37  	"github.com/uber/dosa-idl/.gen/dosa/dosatest"
    38  	tchan "github.com/uber/tchannel-go"
    39  	yarpc2 "go.uber.org/yarpc"
    40  )
    41  
    42  var testEi = &dosa.EntityInfo{
    43  	Ref: &testSchemaRef,
    44  	Def: &dosa.EntityDefinition{
    45  		Columns: []*dosa.ColumnDefinition{
    46  			{Name: "f1", Type: dosa.String},
    47  			{Name: "c1", Type: dosa.Int64},
    48  			{Name: "c2", Type: dosa.Double},
    49  			{Name: "c3", Type: dosa.String},
    50  			{Name: "c4", Type: dosa.Blob},
    51  			{Name: "c5", Type: dosa.Bool},
    52  			{Name: "c6", Type: dosa.Int32},
    53  			{Name: "c7", Type: dosa.TUUID},
    54  		},
    55  		Key: &dosa.PrimaryKey{
    56  			PartitionKeys: []string{"f1"},
    57  		},
    58  		Name: "t1",
    59  	},
    60  }
    61  
    62  var testSchemaRef = dosa.SchemaRef{
    63  	Scope:      "scope1",
    64  	NamePrefix: "namePrefix",
    65  	EntityName: "eName",
    66  	Version:    12345,
    67  }
    68  
    69  var testRPCSchemaRef = drpc.SchemaRef{
    70  	Scope:      testutil.TestStringPtr("scope1"),
    71  	NamePrefix: testutil.TestStringPtr("namePrefix"),
    72  	EntityName: testutil.TestStringPtr("eName"),
    73  	Version:    testutil.TestInt32Ptr(12345),
    74  }
    75  
    76  var testCfg = &yarpc.Config{
    77  	CallerName:  "test",
    78  	ServiceName: "test",
    79  }
    80  
    81  var ctx = context.Background()
    82  
    83  func testAssert(t *testing.T) testutil.TestAssertFn {
    84  	return func(a, b interface{}) {
    85  		assert.Equal(t, a, b)
    86  	}
    87  }
    88  
    89  func TestYaRPCClient_NewConnectorWithTransport(t *testing.T) {
    90  	ctrl := gomock.NewController(t)
    91  	cc := transporttest.NewMockClientConfig(ctrl)
    92  	cc.EXPECT().Caller().Return("test")
    93  	cc.EXPECT().Service().Return("test")
    94  	assert.NotNil(t, yarpc.NewConnectorWithTransport(cc))
    95  	ctrl.Finish()
    96  }
    97  
    98  func TestYaRPCClient_NewConnectorWithChannel(t *testing.T) {
    99  	// if we can call this with a real tchannel instance, only errors can occur
   100  	// when trying to initialize the dispatcher, which also shouldn't return
   101  	// an error since we're providing a known, compatible configuration.
   102  	ch, err := tchan.NewChannel("mysvc", &tchan.ChannelOptions{
   103  		ProcessName: "pname",
   104  	})
   105  	assert.NoError(t, err)
   106  	assert.NotNil(t, ch)
   107  	conn, err := yarpc.NewConnectorWithChannel(ch)
   108  	assert.NoError(t, err)
   109  	assert.NotNil(t, conn)
   110  }
   111  
   112  func TestYaRPCClient_NewConnector(t *testing.T) {
   113  	cases := []struct {
   114  		cfg     yarpc.Config
   115  		isErr   bool
   116  		isPanic bool
   117  	}{
   118  		{
   119  			// invalid host
   120  			cfg:   yarpc.Config{},
   121  			isErr: true,
   122  		}, {
   123  			// invalid port
   124  			cfg: yarpc.Config{
   125  				Host: "localhost",
   126  			},
   127  			isErr: true,
   128  		}, {
   129  			// invalid transport
   130  			cfg: yarpc.Config{
   131  				Host: "localhost",
   132  				Port: "8080",
   133  			},
   134  			isErr: true,
   135  		}, {
   136  			// dispatcher start error (panic)
   137  			cfg: yarpc.Config{
   138  				Transport:   "http",
   139  				Host:        "localhost",
   140  				Port:        "8080",
   141  				CallerName:  "-",
   142  				ServiceName: "dosa-gateway",
   143  			},
   144  			isPanic: true,
   145  		}, {
   146  			// success
   147  			cfg: yarpc.Config{
   148  				Transport:   "http",
   149  				Host:        "localhost",
   150  				Port:        "8080",
   151  				CallerName:  "dosa-test",
   152  				ServiceName: "dosa-gateway",
   153  			},
   154  		}, {
   155  			// success
   156  			cfg: yarpc.Config{
   157  				Transport:   "tchannel",
   158  				Host:        "localhost",
   159  				Port:        "8080",
   160  				CallerName:  "dosa-test",
   161  				ServiceName: "dosa-gateway",
   162  			},
   163  		},
   164  	}
   165  
   166  	for _, c := range cases {
   167  		if c.isPanic {
   168  			assert.Panics(t, func() {
   169  				yarpc.NewConnector(&c.cfg)
   170  			})
   171  			continue
   172  		}
   173  
   174  		conn, err := yarpc.NewConnector(&c.cfg)
   175  		if c.isErr {
   176  			assert.Error(t, err)
   177  			assert.Nil(t, conn)
   178  			continue
   179  		}
   180  		assert.NoError(t, err)
   181  		assert.NotNil(t, conn)
   182  	}
   183  }
   184  
   185  // Test a happy path read of one column and specify the primary key
   186  func TestYaRPCClient_Read(t *testing.T) {
   187  	// build a mock RPC client
   188  	ctrl := gomock.NewController(t)
   189  	mockedClient := dosatest.NewMockClient(ctrl)
   190  
   191  	// set up the parameters
   192  	readRequest := &drpc.ReadRequest{
   193  		Ref:          &testRPCSchemaRef,
   194  		KeyValues:    map[string]*drpc.Value{"f1": {ElemValue: &drpc.RawValue{Int64Value: testutil.TestInt64Ptr(5)}}},
   195  		FieldsToRead: map[string]struct{}{"f1": {}},
   196  	}
   197  
   198  	// we expect a single call to Read, and we return back two fields, f1 which is in the typemap and another field that is not
   199  	mockedClient.EXPECT().Read(ctx, readRequest, gomock.Any()).Return(&drpc.ReadResponse{drpc.FieldValueMap{
   200  		"c1":               {ElemValue: &drpc.RawValue{Int64Value: testutil.TestInt64Ptr(1)}},
   201  		"fieldNotInSchema": {ElemValue: &drpc.RawValue{Int64Value: testutil.TestInt64Ptr(5)}},
   202  		"c2":               {ElemValue: &drpc.RawValue{DoubleValue: testutil.TestFloat64Ptr(2.2)}},
   203  		"c3":               {ElemValue: &drpc.RawValue{StringValue: testutil.TestStringPtr("f3value")}},
   204  		"c4":               {ElemValue: &drpc.RawValue{BinaryValue: []byte{'b', 'i', 'n', 'a', 'r', 'y'}}},
   205  		"c5":               {ElemValue: &drpc.RawValue{BoolValue: testutil.TestBoolPtr(false)}},
   206  		"c6":               {ElemValue: &drpc.RawValue{Int32Value: testutil.TestInt32Ptr(1)}},
   207  	}}, nil)
   208  
   209  	// Prepare the dosa client interface using the mocked RPC layer
   210  	sut := yarpc.Connector{Client: mockedClient, Config: testCfg}
   211  
   212  	// perform the read
   213  	values, err := sut.Read(ctx, testEi, map[string]dosa.FieldValue{"f1": dosa.FieldValue(int64(5))}, []string{"f1"})
   214  	assert.Nil(t, err)                                                 // not an error
   215  	assert.NotNil(t, values)                                           // found some values
   216  	testutil.AssertEqForPointer(testAssert(t), int64(1), values["c1"]) // the mapped field is found, and is the right type
   217  	testutil.AssertEqForPointer(testAssert(t), float64(2.2), values["c2"])
   218  	testutil.AssertEqForPointer(testAssert(t), "f3value", values["c3"])
   219  	assert.Equal(t, []byte{'b', 'i', 'n', 'a', 'r', 'y'}, values["c4"])
   220  	testutil.AssertEqForPointer(testAssert(t), false, values["c5"])
   221  	testutil.AssertEqForPointer(testAssert(t), int32(1), values["c6"])
   222  	assert.Empty(t, values["fieldNotInSchema"]) // the unknown field is not present
   223  
   224  	errCode := int32(404)
   225  	mockedClient.EXPECT().Read(ctx, readRequest, gomock.Any()).Return(nil, &drpc.BadRequestError{ErrorCode: &errCode})
   226  	_, err = sut.Read(ctx, testEi, map[string]dosa.FieldValue{"f1": dosa.FieldValue(int64(5))}, []string{"f1"})
   227  	assert.True(t, dosa.ErrorIsNotFound(err))
   228  
   229  	// make sure we actually called Read on the interface
   230  	ctrl.Finish()
   231  }
   232  
   233  func TestYaRPCClient_MultiRead(t *testing.T) {
   234  	// build a mock RPC client
   235  	ctrl := gomock.NewController(t)
   236  	mockedClient := dosatest.NewMockClient(ctrl)
   237  
   238  	// Prepare the dosa client interface using the mocked RPC layer
   239  	sut := yarpc.Connector{Client: mockedClient, Config: testCfg}
   240  
   241  	data := []struct {
   242  		Request     *drpc.MultiReadRequest
   243  		Response    *drpc.MultiReadResponse
   244  		ResponseErr error
   245  	}{
   246  		{
   247  			Request: &drpc.MultiReadRequest{
   248  				Ref: &testRPCSchemaRef,
   249  				KeyValues: []drpc.FieldValueMap{
   250  					{
   251  						"f1": &drpc.Value{ElemValue: &drpc.RawValue{Int64Value: testutil.TestInt64Ptr(5)}},
   252  					},
   253  					{
   254  						"f2": &drpc.Value{ElemValue: &drpc.RawValue{Int64Value: testutil.TestInt64Ptr(6)}},
   255  					},
   256  				},
   257  				FieldsToRead: map[string]struct{}{"f1": {}},
   258  			},
   259  			Response: &drpc.MultiReadResponse{
   260  				Results: []*drpc.EntityOrError{
   261  					{
   262  						EntityValues: drpc.FieldValueMap{
   263  							"c1":               {ElemValue: &drpc.RawValue{Int64Value: testutil.TestInt64Ptr(1)}},
   264  							"fieldNotInSchema": {ElemValue: &drpc.RawValue{Int64Value: testutil.TestInt64Ptr(5)}},
   265  							"c2":               {ElemValue: &drpc.RawValue{DoubleValue: testutil.TestFloat64Ptr(2.2)}},
   266  							"c3":               {ElemValue: &drpc.RawValue{StringValue: testutil.TestStringPtr("f3value")}},
   267  							"c4":               {ElemValue: &drpc.RawValue{BinaryValue: []byte{'b', 'i', 'n', 'a', 'r', 'y'}}},
   268  							"c5":               {ElemValue: &drpc.RawValue{BoolValue: testutil.TestBoolPtr(false)}},
   269  							"c6":               {ElemValue: &drpc.RawValue{Int32Value: testutil.TestInt32Ptr(1)}},
   270  						},
   271  						Error: nil,
   272  					},
   273  					{
   274  						EntityValues: drpc.FieldValueMap{
   275  							"c1":               {ElemValue: &drpc.RawValue{Int64Value: testutil.TestInt64Ptr(2)}},
   276  							"fieldNotInSchema": {ElemValue: &drpc.RawValue{Int64Value: testutil.TestInt64Ptr(15)}},
   277  							"c2":               {ElemValue: &drpc.RawValue{DoubleValue: testutil.TestFloat64Ptr(12.2)}},
   278  							"c3":               {ElemValue: &drpc.RawValue{StringValue: testutil.TestStringPtr("f3value1")}},
   279  							"c4":               {ElemValue: &drpc.RawValue{BinaryValue: []byte{'a', 'i', '1', 'a', 'r', 'y'}}},
   280  							"c5":               {ElemValue: &drpc.RawValue{BoolValue: testutil.TestBoolPtr(true)}},
   281  							"c6":               {ElemValue: &drpc.RawValue{Int32Value: testutil.TestInt32Ptr(2)}},
   282  						},
   283  						Error: nil,
   284  					},
   285  				},
   286  			},
   287  			ResponseErr: nil,
   288  		},
   289  		{
   290  			Request: &drpc.MultiReadRequest{
   291  				Ref: &testRPCSchemaRef,
   292  				KeyValues: []drpc.FieldValueMap{
   293  					{
   294  						"f1": &drpc.Value{ElemValue: &drpc.RawValue{Int64Value: testutil.TestInt64Ptr(5)}},
   295  					},
   296  					{
   297  						"f2": &drpc.Value{ElemValue: &drpc.RawValue{Int64Value: testutil.TestInt64Ptr(6)}},
   298  					},
   299  				},
   300  				FieldsToRead: map[string]struct{}{"f1": {}},
   301  			},
   302  			Response:    nil,
   303  			ResponseErr: errors.New("test error"),
   304  		},
   305  		{
   306  			Request: &drpc.MultiReadRequest{
   307  				Ref: &testRPCSchemaRef,
   308  				KeyValues: []drpc.FieldValueMap{
   309  					{
   310  						"f1": &drpc.Value{ElemValue: &drpc.RawValue{Int64Value: testutil.TestInt64Ptr(5)}},
   311  					},
   312  					{
   313  						"f2": &drpc.Value{ElemValue: &drpc.RawValue{Int64Value: testutil.TestInt64Ptr(6)}},
   314  					},
   315  				},
   316  				FieldsToRead: map[string]struct{}{"f1": {}},
   317  			},
   318  			Response: &drpc.MultiReadResponse{
   319  				Results: []*drpc.EntityOrError{
   320  					{
   321  						EntityValues: drpc.FieldValueMap{
   322  							"c1":               {ElemValue: &drpc.RawValue{Int64Value: testutil.TestInt64Ptr(1)}},
   323  							"fieldNotInSchema": {ElemValue: &drpc.RawValue{Int64Value: testutil.TestInt64Ptr(5)}},
   324  							"c2":               {ElemValue: &drpc.RawValue{DoubleValue: testutil.TestFloat64Ptr(2.2)}},
   325  							"c3":               {ElemValue: &drpc.RawValue{StringValue: testutil.TestStringPtr("f3value")}},
   326  							"c4":               {ElemValue: &drpc.RawValue{BinaryValue: []byte{'b', 'i', 'n', 'a', 'r', 'y'}}},
   327  							"c5":               {ElemValue: &drpc.RawValue{BoolValue: testutil.TestBoolPtr(false)}},
   328  							"c6":               {ElemValue: &drpc.RawValue{Int32Value: testutil.TestInt32Ptr(1)}},
   329  						},
   330  						Error: nil,
   331  					},
   332  					{
   333  						Error: &drpc.Error{Msg: testutil.TestStringPtr("not found")},
   334  					},
   335  				},
   336  			},
   337  			ResponseErr: nil,
   338  		},
   339  	}
   340  
   341  	for _, d := range data {
   342  		mockedClient.EXPECT().MultiRead(ctx, d.Request, gomock.Any()).Return(d.Response, d.ResponseErr)
   343  		// perform the multi read
   344  		values, err := sut.MultiRead(ctx, testEi, []map[string]dosa.FieldValue{{"f1": dosa.FieldValue(int64(5))}, {"f2": dosa.FieldValue(int64(6))}}, []string{"f1"})
   345  		if d.ResponseErr == nil {
   346  			assert.Nil(t, err)       // not an error
   347  			assert.NotNil(t, values) // found some values
   348  			for i, v := range values {
   349  				if v.Error != nil {
   350  					assert.Contains(t, v.Error.Error(), *d.Response.Results[i].Error.Msg)
   351  					continue
   352  				}
   353  				testutil.AssertEqForPointer(testAssert(t), *d.Response.Results[i].EntityValues["c1"].ElemValue.Int64Value, v.Values["c1"])
   354  				assert.Empty(t, v.Values["fieldNotInSchema"])
   355  				testutil.AssertEqForPointer(testAssert(t), *d.Response.Results[i].EntityValues["c2"].ElemValue.DoubleValue, v.Values["c2"])
   356  				testutil.AssertEqForPointer(testAssert(t), *d.Response.Results[i].EntityValues["c3"].ElemValue.StringValue, v.Values["c3"])
   357  				assert.Equal(t, d.Response.Results[i].EntityValues["c4"].ElemValue.BinaryValue, v.Values["c4"])
   358  				testutil.AssertEqForPointer(testAssert(t), *d.Response.Results[i].EntityValues["c5"].ElemValue.BoolValue, v.Values["c5"])
   359  				testutil.AssertEqForPointer(testAssert(t), *d.Response.Results[i].EntityValues["c6"].ElemValue.Int32Value, v.Values["c6"])
   360  			}
   361  			continue
   362  		}
   363  
   364  		assert.Error(t, err)
   365  		assert.Contains(t, err.Error(), d.ResponseErr.Error())
   366  	}
   367  
   368  	// make sure we actually called Read on the interface
   369  	ctrl.Finish()
   370  }
   371  
   372  func TestYaRPCClient_CreateIfNotExists(t *testing.T) {
   373  	// build a mock RPC client
   374  	ctrl := gomock.NewController(t)
   375  	mockedClient := dosatest.NewMockClient(ctrl)
   376  
   377  	// here are the data types to test; the names are random
   378  	valss := [][]struct {
   379  		Name  string
   380  		Value interface{}
   381  	}{
   382  		{
   383  			{"c1", int64(1)},
   384  			{"c2", float64(2.2)},
   385  			{"c3", "string"},
   386  			{"c4", []byte{'b', 'i', 'n', 'a', 'r', 'y'}},
   387  			{"c5", false},
   388  			{"c6", int32(2)},
   389  			{"c7", time.Now()},
   390  		},
   391  		{
   392  			{"c1", testutil.TestInt64Ptr(1)},
   393  			{"c2", testutil.TestFloat64Ptr(2.2)},
   394  			{"c3", testutil.TestStringPtr("string")},
   395  			{"c4", []byte{'b', 'i', 'n', 'a', 'r', 'y'}},
   396  			{"c5", testutil.TestBoolPtr(false)},
   397  			{"c6", testutil.TestInt32Ptr(2)},
   398  			{"c7", testutil.TestTimePtr(time.Now())},
   399  		},
   400  	}
   401  
   402  	for _, vals := range valss {
   403  		// build up the input field list and the output field list
   404  		// the layout is quite different; inputs are a simple map but the actual RPC call expects a messier format
   405  		inFields := map[string]dosa.FieldValue{}
   406  		outFields := drpc.FieldValueMap{}
   407  		for _, item := range vals {
   408  			inFields[item.Name] = item.Value
   409  			rv, _ := yarpc.RawValueFromInterface(item.Value)
   410  			outFields[item.Name] = &drpc.Value{ElemValue: rv}
   411  		}
   412  
   413  		mockedClient.EXPECT().CreateIfNotExists(ctx, &drpc.CreateRequest{Ref: &testRPCSchemaRef, EntityValues: outFields}, gomock.Any())
   414  
   415  		// create the YaRPCClient and give it the mocked RPC interface
   416  		// see https://en.wiktionary.org/wiki/SUT for the reason this is called sut
   417  		sut := yarpc.Connector{Client: mockedClient, Config: testCfg}
   418  
   419  		// and run the test
   420  		err := sut.CreateIfNotExists(ctx, testEi, inFields)
   421  		assert.Nil(t, err)
   422  
   423  		errCode := int32(409)
   424  		mockedClient.EXPECT().CreateIfNotExists(ctx, &drpc.CreateRequest{Ref: &testRPCSchemaRef, EntityValues: outFields}, gomock.Any()).Return(
   425  			&drpc.BadRequestError{ErrorCode: &errCode},
   426  		)
   427  
   428  		err = sut.CreateIfNotExists(ctx, testEi, inFields)
   429  		assert.True(t, dosa.ErrorIsAlreadyExists(err))
   430  		// make sure we actually called CreateIfNotExists on the interface
   431  		ctrl.Finish()
   432  
   433  		// cover the conversion error case
   434  		err = sut.CreateIfNotExists(ctx, testEi, map[string]dosa.FieldValue{"c7": dosa.UUID("")})
   435  		assert.Error(t, err)
   436  		assert.Contains(t, err.Error(), "\"c7\"")    // must contain name of bad field
   437  		assert.Contains(t, err.Error(), "too short") // must mention that the uuid is too short
   438  
   439  		assert.NoError(t, sut.Shutdown())
   440  	}
   441  }
   442  
   443  func TestYaRPCClient_Upsert(t *testing.T) {
   444  	// build a mock RPC client
   445  	ctrl := gomock.NewController(t)
   446  	mockedClient := dosatest.NewMockClient(ctrl)
   447  
   448  	// here are the data types to test; the names are random
   449  	valss := [][]struct {
   450  		Name  string
   451  		Value interface{}
   452  	}{
   453  		{
   454  			{"c1", int64(1)},
   455  			{"c2", float64(2.2)},
   456  			{"c3", "string"},
   457  			{"c4", []byte{'b', 'i', 'n', 'a', 'r', 'y'}},
   458  			{"c5", false},
   459  			{"c6", int32(2)},
   460  			{"c7", time.Now()},
   461  		},
   462  		{
   463  			{"c1", testutil.TestInt64Ptr(1)},
   464  			{"c2", testutil.TestFloat64Ptr(2.2)},
   465  			{"c3", testutil.TestStringPtr("string")},
   466  			{"c4", []byte{'b', 'i', 'n', 'a', 'r', 'y'}},
   467  			{"c5", testutil.TestBoolPtr(false)},
   468  			{"c6", testutil.TestInt32Ptr(2)},
   469  			{"c7", testutil.TestTimePtr(time.Now())},
   470  		},
   471  	}
   472  
   473  	for _, vals := range valss {
   474  		// build up the input field list and the output field list
   475  		// the layout is quite different; inputs are a simple map but the actual RPC call expects a messier format
   476  		inFields := map[string]dosa.FieldValue{}
   477  		outFields := map[string]*drpc.Value{}
   478  		for _, item := range vals {
   479  			inFields[item.Name] = item.Value
   480  			rv, _ := yarpc.RawValueFromInterface(item.Value)
   481  			outFields[item.Name] = &drpc.Value{ElemValue: rv}
   482  		}
   483  
   484  		mockedClient.EXPECT().Upsert(ctx, &drpc.UpsertRequest{
   485  			Ref:          &testRPCSchemaRef,
   486  			EntityValues: outFields,
   487  		}, gomock.Any())
   488  
   489  		// create the YaRPCClient and give it the mocked RPC interface
   490  		// see https://en.wiktionary.org/wiki/SUT for the reason this is called sut
   491  		sut := yarpc.Connector{Client: mockedClient, Config: testCfg}
   492  
   493  		// and run the test, first with a nil FieldsToUpdate, then with a specific list
   494  		err := sut.Upsert(ctx, testEi, inFields)
   495  		assert.Nil(t, err)
   496  
   497  		// cover the conversion error case
   498  		err = sut.Upsert(ctx, testEi, map[string]dosa.FieldValue{"c7": dosa.UUID("")})
   499  		assert.Error(t, err)
   500  		assert.Contains(t, err.Error(), "\"c7\"")    // must contain name of bad field
   501  		assert.Contains(t, err.Error(), "too short") // must mention that the uuid is too short
   502  
   503  		// make sure we actually called CreateIfNotExists on the interface
   504  		ctrl.Finish()
   505  	}
   506  }
   507  
   508  type TestDosaObject struct {
   509  	dosa.Entity `dosa:"primaryKey=(F1, F2)"`
   510  	F1          int64
   511  	F2          int32
   512  }
   513  
   514  func TestClient_CheckSchema(t *testing.T) {
   515  	// build a mock RPC client
   516  	ctrl := gomock.NewController(t)
   517  	mockedClient := dosatest.NewMockClient(ctrl)
   518  	sp := "scope"
   519  	prefix := "prefix"
   520  
   521  	sut := yarpc.Connector{Client: mockedClient, Config: testCfg}
   522  
   523  	ed, err := dosa.TableFromInstance(&TestDosaObject{})
   524  	assert.NoError(t, err)
   525  	expectedRequest := &drpc.CheckSchemaRequest{
   526  		Scope:      &sp,
   527  		NamePrefix: &prefix,
   528  		EntityDefs: []*drpc.EntityDefinition{yarpc.EntityDefinitionToThrift(&ed.EntityDefinition)},
   529  	}
   530  	v := int32(1)
   531  	mockedClient.EXPECT().CheckSchema(ctx, gomock.Any(), gomock.Any()).Do(func(_ context.Context, request *drpc.CheckSchemaRequest, opts yarpc2.CallOption) {
   532  		assert.Equal(t, expectedRequest, request)
   533  	}).Return(&drpc.CheckSchemaResponse{Version: &v}, nil)
   534  
   535  	sr, err := sut.CheckSchema(ctx, sp, prefix, []*dosa.EntityDefinition{&ed.EntityDefinition})
   536  	assert.NoError(t, err)
   537  	assert.Equal(t, v, sr)
   538  }
   539  
   540  func TestClient_CheckSchemaStatus(t *testing.T) {
   541  	// build a mock RPC client
   542  	ctrl := gomock.NewController(t)
   543  	mockedClient := dosatest.NewMockClient(ctrl)
   544  	sp := "scope"
   545  	prefix := "prefix"
   546  	version := int32(1)
   547  	sut := yarpc.Connector{Client: mockedClient, Config: testCfg}
   548  
   549  	expectedRequest := &drpc.CheckSchemaStatusRequest{
   550  		Scope:      &sp,
   551  		NamePrefix: &prefix,
   552  		Version:    &version,
   553  	}
   554  
   555  	mockedClient.EXPECT().CheckSchemaStatus(ctx, gomock.Any(), gomock.Any()).Do(func(_ context.Context, request *drpc.CheckSchemaStatusRequest, opts yarpc2.CallOption) {
   556  		assert.Equal(t, expectedRequest, request)
   557  	}).Return(&drpc.CheckSchemaStatusResponse{Version: &version}, nil)
   558  
   559  	sr, err := sut.CheckSchemaStatus(ctx, sp, prefix, version)
   560  	assert.NoError(t, err)
   561  	assert.Equal(t, version, sr.Version)
   562  }
   563  
   564  func TestClient_UpsertSchema(t *testing.T) {
   565  	// build a mock RPC client
   566  	ctrl := gomock.NewController(t)
   567  	mockedClient := dosatest.NewMockClient(ctrl)
   568  	sut := yarpc.Connector{Client: mockedClient, Config: testCfg}
   569  
   570  	ed, err := dosa.TableFromInstance(&TestDosaObject{})
   571  	assert.NoError(t, err)
   572  	sp := "scope"
   573  	prefix := "prefix"
   574  
   575  	expectedRequest := &drpc.UpsertSchemaRequest{
   576  		Scope:      &sp,
   577  		NamePrefix: &prefix,
   578  		EntityDefs: []*drpc.EntityDefinition{yarpc.EntityDefinitionToThrift(&ed.EntityDefinition)},
   579  	}
   580  	v := int32(1)
   581  	mockedClient.EXPECT().UpsertSchema(ctx, gomock.Any(), gomock.Any()).Do(func(_ context.Context, request *drpc.UpsertSchemaRequest, option yarpc2.CallOption) {
   582  		assert.Equal(t, expectedRequest, request)
   583  	}).Return(&drpc.UpsertSchemaResponse{Version: &v}, nil)
   584  	result, err := sut.UpsertSchema(ctx, sp, prefix, []*dosa.EntityDefinition{&ed.EntityDefinition})
   585  	assert.NoError(t, err)
   586  	assert.Equal(t, &dosa.SchemaStatus{Version: v}, result)
   587  
   588  	mockedClient.EXPECT().UpsertSchema(ctx, gomock.Any(), gomock.Any()).Return(nil, errors.New("test error"))
   589  	_, err = sut.UpsertSchema(ctx, sp, prefix, []*dosa.EntityDefinition{&ed.EntityDefinition})
   590  	assert.Error(t, err)
   591  	assert.Contains(t, err.Error(), "test error")
   592  }
   593  
   594  func TestClient_CreateScope(t *testing.T) {
   595  	// build a mock RPC client
   596  	ctrl := gomock.NewController(t)
   597  	mockedClient := dosatest.NewMockClient(ctrl)
   598  
   599  	sut := yarpc.Connector{Client: mockedClient, Config: testCfg}
   600  	mockedClient.EXPECT().CreateScope(ctx, gomock.Any(), gomock.Any()).Return(nil)
   601  	err := sut.CreateScope(ctx, "scope")
   602  	assert.NoError(t, err)
   603  
   604  	mockedClient.EXPECT().CreateScope(ctx, gomock.Any(), gomock.Any()).Return(errors.New("test error"))
   605  	err = sut.CreateScope(ctx, "scope")
   606  	assert.Error(t, err)
   607  	assert.Contains(t, err.Error(), "test error")
   608  }
   609  
   610  func TestClient_TruncateScope(t *testing.T) {
   611  	// build a mock RPC client
   612  	ctrl := gomock.NewController(t)
   613  	mockedClient := dosatest.NewMockClient(ctrl)
   614  	sut := yarpc.Connector{Client: mockedClient, Config: testCfg}
   615  
   616  	mockedClient.EXPECT().TruncateScope(ctx, gomock.Any(), gomock.Any()).Return(nil)
   617  	err := sut.TruncateScope(ctx, "scope")
   618  	assert.NoError(t, err)
   619  
   620  	mockedClient.EXPECT().TruncateScope(ctx, gomock.Any(), gomock.Any()).Return(errors.New("test error"))
   621  	err = sut.TruncateScope(ctx, "scope")
   622  	assert.Error(t, err)
   623  	assert.Contains(t, err.Error(), "test error")
   624  }
   625  
   626  func TestClient_DropScope(t *testing.T) {
   627  	// build a mock RPC client
   628  	ctrl := gomock.NewController(t)
   629  	mockedClient := dosatest.NewMockClient(ctrl)
   630  	sut := yarpc.Connector{Client: mockedClient, Config: testCfg}
   631  
   632  	mockedClient.EXPECT().DropScope(ctx, gomock.Any(), gomock.Any()).Return(nil)
   633  	err := sut.DropScope(ctx, "scope")
   634  	assert.NoError(t, err)
   635  
   636  	mockedClient.EXPECT().DropScope(ctx, gomock.Any(), gomock.Any()).Return(errors.New("test error"))
   637  	err = sut.DropScope(ctx, "scope")
   638  	assert.Error(t, err)
   639  	assert.Contains(t, err.Error(), "test error")
   640  }
   641  
   642  func TestConnector_Range(t *testing.T) {
   643  	ctrl := gomock.NewController(t)
   644  	defer ctrl.Finish()
   645  	mockedClient := dosatest.NewMockClient(ctrl)
   646  
   647  	testToken := "testToken"
   648  	responseToken := "responseToken"
   649  	testLimit := int32(32)
   650  	// set up the parameters
   651  	op := drpc.OperatorEq
   652  	fieldName := "c1"
   653  	fieldName1 := "c2"
   654  	field := drpc.Field{&fieldName, &drpc.Value{ElemValue: &drpc.RawValue{Int64Value: testutil.TestInt64Ptr(10)}}}
   655  	field1 := drpc.Field{&fieldName1, &drpc.Value{ElemValue: &drpc.RawValue{Int64Value: testutil.TestInt64Ptr(10)}}}
   656  
   657  	// Prepare the dosa client interface using the mocked RPC layer
   658  	sut := yarpc.Connector{Client: mockedClient, Config: testCfg}
   659  
   660  	// successful call, return results
   661  	mockedClient.EXPECT().Range(ctx, gomock.Any(), gomock.Any()).Do(func(_ context.Context, request *drpc.RangeRequest, opts yarpc2.CallOption) {
   662  		assert.Equal(t, map[string]struct{}{"c1": {}}, request.FieldsToRead)
   663  		assert.Equal(t, testLimit, *request.Limit)
   664  		assert.Equal(t, testRPCSchemaRef, *request.Ref)
   665  		assert.Equal(t, testToken, *request.Token)
   666  		for _, c := range request.Conditions {
   667  			assert.Equal(t, c.Op, &op)
   668  			if *c.Field.Name == fieldName {
   669  				assert.Equal(t, c.Field, &field)
   670  			} else {
   671  				assert.Equal(t, c.Field, &field1)
   672  			}
   673  		}
   674  		assert.Equal(t, len(request.Conditions), 2)
   675  	}).Return(&drpc.RangeResponse{
   676  		Entities: []drpc.FieldValueMap{
   677  			{
   678  				"c1":               {ElemValue: &drpc.RawValue{Int64Value: testutil.TestInt64Ptr(1)}},
   679  				"fieldNotInSchema": {ElemValue: &drpc.RawValue{Int64Value: testutil.TestInt64Ptr(5)}},
   680  				"c2":               {ElemValue: &drpc.RawValue{DoubleValue: testutil.TestFloat64Ptr(2.2)}},
   681  			},
   682  		},
   683  		NextToken: &responseToken,
   684  	}, nil)
   685  
   686  	values, token, err := sut.Range(ctx, testEi, map[string][]*dosa.Condition{
   687  		"c1": {&dosa.Condition{
   688  			Value: int64(10),
   689  			Op:    dosa.Eq,
   690  		}},
   691  		"c2": {&dosa.Condition{
   692  			Value: int64(10),
   693  			Op:    dosa.Eq,
   694  		}},
   695  	}, []string{"c1"}, testToken, 32)
   696  	assert.NoError(t, err)
   697  	assert.Equal(t, responseToken, token)
   698  	assert.NotNil(t, values)
   699  	assert.Equal(t, 1, len(values))
   700  	testutil.AssertEqForPointer(testAssert(t), int64(1), values[0]["c1"])
   701  	testutil.AssertEqForPointer(testAssert(t), float64(2.2), values[0]["c2"])
   702  
   703  	// perform a not found request
   704  	mockedClient.EXPECT().Range(ctx, gomock.Any(), gomock.Any()).
   705  		Return(nil, &dosa.ErrNotFound{}).Times(1)
   706  	values, token, err = sut.Range(ctx, testEi, map[string][]*dosa.Condition{"c2": {&dosa.Condition{
   707  		Value: float64(3.3),
   708  		Op:    dosa.Eq,
   709  	}}}, nil, "", 64)
   710  	assert.Nil(t, values)
   711  	assert.Empty(t, token)
   712  	assert.Error(t, err)
   713  	assert.True(t, dosa.ErrorIsNotFound(err))
   714  
   715  	// perform a generic error request
   716  	mockedClient.EXPECT().Range(ctx, gomock.Any(), gomock.Any()).
   717  		Return(nil, errors.New("test error")).Times(1)
   718  	values, token, err = sut.Range(ctx, testEi, map[string][]*dosa.Condition{"c2": {&dosa.Condition{
   719  		Value: float64(3.3),
   720  		Op:    dosa.Eq,
   721  	}}}, nil, "", 64)
   722  	assert.Nil(t, values)
   723  	assert.Empty(t, token)
   724  	assert.Error(t, err)
   725  	assert.Contains(t, err.Error(), "test error")
   726  
   727  	// perform remove range with a bad field value
   728  	_, _, err = sut.Range(ctx, testEi, map[string][]*dosa.Condition{
   729  		"c7": {&dosa.Condition{
   730  			Value: dosa.UUID("baduuid"),
   731  			Op:    dosa.Eq,
   732  		}},
   733  	}, nil, "", 64)
   734  	assert.Error(t, err)
   735  	assert.EqualError(t, errors.Cause(err), "uuid: UUID string too short: baduuid")
   736  }
   737  
   738  func TestConnector_RemoveRange(t *testing.T) {
   739  	ctrl := gomock.NewController(t)
   740  	defer ctrl.Finish()
   741  	mockedClient := dosatest.NewMockClient(ctrl)
   742  
   743  	sut := yarpc.Connector{Client: mockedClient, Config: testCfg}
   744  	fieldName := "c1"
   745  	field := drpc.Field{&fieldName, &drpc.Value{ElemValue: &drpc.RawValue{Int64Value: testutil.TestInt64Ptr(10)}}}
   746  	op := drpc.OperatorEq
   747  
   748  	mockedClient.EXPECT().RemoveRange(ctx, gomock.Any(), gomock.Any()).Do(func(_ context.Context, request *drpc.RemoveRangeRequest, option yarpc2.CallOption) {
   749  		assert.Equal(t, testRPCSchemaRef, *request.Ref)
   750  		assert.Equal(t, len(request.Conditions), 1)
   751  		condition := request.Conditions[0]
   752  		assert.Equal(t, fieldName, *condition.Field.Name)
   753  		assert.Equal(t, field.Value, condition.Field.Value)
   754  		assert.Equal(t, &op, condition.Op)
   755  	}).Return(nil)
   756  
   757  	err := sut.RemoveRange(ctx, testEi, map[string][]*dosa.Condition{
   758  		"c1": {&dosa.Condition{
   759  			Value: int64(10),
   760  			Op:    dosa.Eq,
   761  		}},
   762  	})
   763  	assert.NoError(t, err)
   764  
   765  	// perform a generic error request
   766  	mockedClient.EXPECT().RemoveRange(ctx, gomock.Any(), gomock.Any()).Return(errors.New("test error")).Times(1)
   767  	err = sut.RemoveRange(ctx, testEi, map[string][]*dosa.Condition{"c2": {&dosa.Condition{
   768  		Value: 3.3,
   769  		Op:    dosa.Eq,
   770  	}}})
   771  	assert.Error(t, err)
   772  	assert.Contains(t, err.Error(), "test error")
   773  
   774  	// perform remove range with a bad field value
   775  	err = sut.RemoveRange(ctx, testEi, map[string][]*dosa.Condition{
   776  		"c7": {&dosa.Condition{
   777  			Value: dosa.UUID("baduuid"),
   778  			Op:    dosa.Eq,
   779  		}},
   780  	})
   781  	assert.Error(t, err)
   782  	assert.EqualError(t, errors.Cause(err), "uuid: UUID string too short: baduuid")
   783  }
   784  
   785  func TestConnector_Scan(t *testing.T) {
   786  	ctrl := gomock.NewController(t)
   787  	defer ctrl.Finish()
   788  	mockedClient := dosatest.NewMockClient(ctrl)
   789  
   790  	testToken := "testToken"
   791  	responseToken := "responseToken"
   792  	testLimit := int32(32)
   793  	// set up the parameters
   794  	sr := &drpc.ScanRequest{
   795  		Ref:          &testRPCSchemaRef,
   796  		Token:        &testToken,
   797  		Limit:        &testLimit,
   798  		FieldsToRead: map[string]struct{}{"c1": {}},
   799  	}
   800  	// successful call, return results
   801  	mockedClient.EXPECT().Scan(ctx, sr, gomock.Any()).
   802  		Do(func(_ context.Context, r *drpc.ScanRequest, option yarpc2.CallOption) {
   803  			assert.Equal(t, sr, r)
   804  		}).
   805  		Return(&drpc.ScanResponse{
   806  			Entities: []drpc.FieldValueMap{
   807  				{
   808  					"c1":               {ElemValue: &drpc.RawValue{Int64Value: testutil.TestInt64Ptr(1)}},
   809  					"fieldNotInSchema": {ElemValue: &drpc.RawValue{Int64Value: testutil.TestInt64Ptr(5)}},
   810  					"c2":               {ElemValue: &drpc.RawValue{DoubleValue: testutil.TestFloat64Ptr(2.2)}},
   811  				},
   812  			},
   813  			NextToken: &responseToken,
   814  		}, nil)
   815  	// failed call, return error
   816  	mockedClient.EXPECT().Scan(ctx, gomock.Any(), gomock.Any()).
   817  		Return(nil, errors.New("test error")).Times(1)
   818  	// no results, make sure error is exact
   819  	mockedClient.EXPECT().Scan(ctx, gomock.Any(), gomock.Any()).
   820  		Return(nil, &dosa.ErrNotFound{})
   821  
   822  	// Prepare the dosa client interface using the mocked RPC layer
   823  	sut := yarpc.Connector{Client: mockedClient, Config: testCfg}
   824  
   825  	// perform the successful request
   826  	values, token, err := sut.Scan(ctx, testEi, []string{"c1"}, testToken, 32)
   827  	assert.NoError(t, err)
   828  	assert.Equal(t, responseToken, token)
   829  	assert.NotNil(t, values)
   830  	assert.Equal(t, 1, len(values))
   831  	testutil.AssertEqForPointer(testAssert(t), int64(1), values[0]["c1"])
   832  	testutil.AssertEqForPointer(testAssert(t), float64(2.2), values[0]["c2"])
   833  
   834  	// perform a not found request
   835  	values, token, err = sut.Scan(ctx, testEi, nil, "", 64)
   836  	assert.Nil(t, values)
   837  	assert.Empty(t, token)
   838  	assert.Error(t, err)
   839  	assert.True(t, dosa.ErrorIsNotFound(err))
   840  
   841  	// perform a generic error request
   842  	values, token, err = sut.Scan(ctx, testEi, nil, "", 64)
   843  	assert.Nil(t, values)
   844  	assert.Empty(t, token)
   845  	assert.Error(t, err)
   846  	assert.Contains(t, err.Error(), "test error")
   847  }
   848  
   849  func TestConnector_Remove(t *testing.T) {
   850  	// build a mock RPC client
   851  	ctrl := gomock.NewController(t)
   852  	mockedClient := dosatest.NewMockClient(ctrl)
   853  
   854  	// set up the parameters
   855  	removeRequest := &drpc.RemoveRequest{
   856  		Ref:       &testRPCSchemaRef,
   857  		KeyValues: map[string]*drpc.Value{"f1": {ElemValue: &drpc.RawValue{Int64Value: testutil.TestInt64Ptr(5)}}},
   858  	}
   859  
   860  	// we expect a single call to Read, and we return back two fields, f1 which is in the typemap and another field that is not
   861  	mockedClient.EXPECT().Remove(ctx, removeRequest, gomock.Any()).Return(nil)
   862  
   863  	// Prepare the dosa client interface using the mocked RPC layer
   864  	sut := yarpc.Connector{Client: mockedClient, Config: testCfg}
   865  
   866  	// perform the read
   867  	err := sut.Remove(ctx, testEi, map[string]dosa.FieldValue{"f1": dosa.FieldValue(int64(5))})
   868  	assert.Nil(t, err) // not an error
   869  
   870  	// cover the conversion error case
   871  	err = sut.Remove(ctx, testEi, map[string]dosa.FieldValue{"c7": dosa.UUID("321")})
   872  	assert.Error(t, err)
   873  	assert.Contains(t, err.Error(), "\"c7\"")    // must contain name of bad field
   874  	assert.Contains(t, err.Error(), "too short") // must mention that the uuid is too short
   875  
   876  	// make sure we actually called Read on the interface
   877  	ctrl.Finish()
   878  }
   879  
   880  // TestPanic is an unimplemented method test for coverage, remove these as they are implemented
   881  func TestPanic(t *testing.T) {
   882  	ctrl := gomock.NewController(t)
   883  	mockedClient := dosatest.NewMockClient(ctrl)
   884  
   885  	sut := yarpc.Connector{Client: mockedClient, Config: testCfg}
   886  
   887  	assert.Panics(t, func() {
   888  		sut.MultiUpsert(ctx, testEi, nil)
   889  	})
   890  
   891  	assert.Panics(t, func() {
   892  		sut.MultiRemove(ctx, testEi, nil)
   893  	})
   894  
   895  	assert.Panics(t, func() {
   896  		sut.ScopeExists(ctx, "")
   897  	})
   898  }