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 }