github.com/willyham/dosa@v2.3.1-0.20171024181418-1e446d37ee71+incompatible/client_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 dosa_test 22 23 import ( 24 "context" 25 "io/ioutil" 26 "os" 27 "path/filepath" 28 "testing" 29 30 "github.com/golang/mock/gomock" 31 "github.com/pkg/errors" 32 "github.com/stretchr/testify/assert" 33 34 "fmt" 35 "time" 36 37 dosaRenamed "github.com/uber-go/dosa" 38 _ "github.com/uber-go/dosa/connectors/devnull" 39 _ "github.com/uber-go/dosa/connectors/memory" 40 "github.com/uber-go/dosa/mocks" 41 "github.com/uber-go/dosa/testutil" 42 ) 43 44 type ClientTestEntity1 struct { 45 dosaRenamed.Entity `dosa:"primaryKey=(ID)"` 46 dosaRenamed.Index `dosa:"key=Name, name=username"` 47 ID int64 48 Name string 49 Email string 50 } 51 52 type ClientTestEntity2 struct { 53 dosaRenamed.Entity `dosa:"primaryKey=(UUID,Color)"` 54 SearchByColor dosaRenamed.Index `dosa:"key=Color"` 55 UUID string 56 Color string 57 IsActive bool 58 ignoreme int32 59 } 60 61 var ( 62 cte1 = &ClientTestEntity1{ID: int64(1), Name: "foo", Email: "foo@uber.com"} 63 cte2 = &ClientTestEntity2{UUID: "b1f23fa3-f453-45b4-a5d5-6d73078ac3bd", Color: "blue", IsActive: true} 64 ctx = context.TODO() 65 scope = "test" 66 namePrefix = "team.service" 67 nullConnector dosaRenamed.Connector 68 ) 69 70 func init() { 71 nullConnector, _ = dosaRenamed.GetConnector("devnull", nil) 72 } 73 74 // ExampleNewClient initializes a client using the devnull connector, which discards all 75 // the data you send it and always returns no rows. It's only useful for testing dosa. 76 func ExampleNewClient() { 77 // initialize registrar 78 reg, err := dosaRenamed.NewRegistrar("test", "myteam.myservice", cte1) 79 if err != nil { 80 // registration will fail if the object is tagged incorrectly 81 fmt.Printf("NewRegistrar error: %s", err) 82 return 83 } 84 85 // use a devnull connector for example purposes 86 conn, err := dosaRenamed.GetConnector("devnull", nil) 87 if err != nil { 88 fmt.Printf("GetConnector error: %s", err) 89 return 90 } 91 92 // create the client using the registry and connector 93 client := dosaRenamed.NewClient(reg, conn) 94 95 err = client.Initialize(context.Background()) 96 if err != nil { 97 fmt.Printf("Initialize error: %s", err) 98 return 99 } 100 } 101 102 // ExampleGetConnector gets an in-memory connector that can be used for testing your code. 103 // The in-memory connector always starts off with no rows, so you'll need to add rows to 104 // your "database" before reading them 105 func ExampleGetConnector() { 106 // register your entities so the engine can separate your data based on table names. 107 // Scopes and prefixes are not used by the in-memory connector, and are ignored, but 108 // your list of entities is important. In this case, we only have one, our ClientTestEntity1 109 reg, err := dosaRenamed.NewRegistrar("test", "myteam.myservice", &ClientTestEntity1{}) 110 if err != nil { 111 fmt.Printf("NewRegistrar error: %s", err) 112 return 113 } 114 115 // Find the memory connector. There is no configuration information so pass a nil 116 // For this to work, you must force the init method of memory to run first, which happens 117 // when we imported memory in the import list, with an underscore to just get the side effects 118 conn, _ := dosaRenamed.GetConnector("memory", nil) 119 120 // now construct a client from the registry and the connector 121 client := dosaRenamed.NewClient(reg, conn) 122 123 // initialize the client; this should always work for the in-memory connector 124 if err = client.Initialize(context.Background()); err != nil { 125 fmt.Printf("Initialize error: %s", err) 126 return 127 } 128 129 // now populate an entity and insert it into the memory store 130 if err := client.CreateIfNotExists(context.Background(), &ClientTestEntity1{ 131 ID: int64(1), 132 Name: "rkuris", 133 Email: "rkuris@uber.com"}); err != nil { 134 fmt.Printf("CreateIfNotExists error: %s", err) 135 return 136 } 137 138 // create an entity to hold the read result, just populate the key 139 e := ClientTestEntity1{ID: int64(1)} 140 // now read the data from the "database", all columns 141 err = client.Read(context.Background(), dosaRenamed.All(), &e) 142 if err != nil { 143 fmt.Printf("Read error: %s", err) 144 return 145 } 146 // great! It worked, so display the information we stored earlier 147 fmt.Printf("id:%d Name:%q Email:%q\n", e.ID, e.Name, e.Email) 148 // Output: id:1 Name:"rkuris" Email:"rkuris@uber.com" 149 } 150 151 func testAssert(t *testing.T) testutil.TestAssertFn { 152 return func(a, b interface{}) { 153 assert.Equal(t, a, b) 154 } 155 } 156 157 func TestNewClient(t *testing.T) { 158 // initialize registrar 159 reg, err := dosaRenamed.NewRegistrar("test", "myteam.myservice", cte1) 160 assert.NoError(t, err) 161 assert.NotNil(t, reg) 162 163 // initialize a pseudo-connected client 164 client := dosaRenamed.NewClient(reg, nullConnector) 165 err = client.Initialize(ctx) 166 assert.NoError(t, err) 167 } 168 169 func TestClient_Initialize(t *testing.T) { 170 ctrl := gomock.NewController(t) 171 defer ctrl.Finish() 172 emptyReg, _ := dosaRenamed.NewRegistrar("test", "team.service") 173 reg, _ := dosaRenamed.NewRegistrar("test", "team.service", cte1) 174 175 // find error 176 c1 := dosaRenamed.NewClient(emptyReg, nullConnector) 177 assert.Error(t, c1.Initialize(ctx)) 178 179 // CheckSchema error 180 errConn := mocks.NewMockConnector(ctrl) 181 errConn.EXPECT().CheckSchema(ctx, gomock.Any(), gomock.Any(), gomock.Any()).Return(int32(-1), errors.New("CheckSchema error")).AnyTimes() 182 c2 := dosaRenamed.NewClient(reg, errConn) 183 assert.Error(t, c2.Initialize(ctx)) 184 185 // happy path 186 c3 := dosaRenamed.NewClient(reg, nullConnector) 187 assert.NoError(t, c3.Initialize(ctx)) 188 189 // already initialized 190 assert.NoError(t, c3.Initialize(ctx)) 191 } 192 193 func TestClient_Read(t *testing.T) { 194 reg1, _ := dosaRenamed.NewRegistrar(scope, namePrefix, cte1) 195 reg2, _ := dosaRenamed.NewRegistrar(scope, namePrefix, cte1, cte2) 196 fieldsToRead := []string{"ID", "Email"} 197 results := map[string]dosaRenamed.FieldValue{ 198 "id": int64(2), 199 "name": "bar", 200 "email": "bar@email.com", 201 } 202 203 // uninitialized 204 c1 := dosaRenamed.NewClient(reg1, nullConnector) 205 assert.Error(t, c1.Read(ctx, fieldsToRead, cte1)) 206 207 // unregistered object 208 c1.Initialize(ctx) 209 err := c1.Read(ctx, dosaRenamed.All(), cte2) 210 assert.Error(t, err) 211 assert.Contains(t, err.Error(), "ClientTestEntity2") 212 213 // happy path, mock connector 214 ctrl := gomock.NewController(t) 215 defer ctrl.Finish() 216 mockConn := mocks.NewMockConnector(ctrl) 217 mockConn.EXPECT().CheckSchema(ctx, gomock.Any(), gomock.Any(), gomock.Any()).Return(int32(1), nil).AnyTimes() 218 mockConn.EXPECT().Read(ctx, gomock.Any(), gomock.Any(), gomock.Any()). 219 Do(func(_ context.Context, _ *dosaRenamed.EntityInfo, columnValues map[string]dosaRenamed.FieldValue, columnsToRead []string) { 220 assert.Equal(t, columnValues["id"], cte1.ID) 221 assert.Equal(t, columnsToRead, []string{"id", "email"}) 222 223 }).Return(results, nil).MinTimes(1) 224 c3 := dosaRenamed.NewClient(reg2, mockConn) 225 assert.NoError(t, c3.Initialize(ctx)) 226 assert.NoError(t, c3.Read(ctx, fieldsToRead, cte1)) 227 assert.Equal(t, cte1.ID, results["id"]) 228 assert.NotEqual(t, cte1.Name, results["name"]) 229 assert.Equal(t, cte1.Email, results["email"]) 230 } 231 232 func TestClient_Read_pointer_result(t *testing.T) { 233 reg1, _ := dosaRenamed.NewRegistrar(scope, namePrefix, cte1) 234 reg2, _ := dosaRenamed.NewRegistrar(scope, namePrefix, cte1, cte2) 235 fieldsToRead := []string{"ID", "Email"} 236 results := map[string]dosaRenamed.FieldValue{ 237 "id": testutil.TestInt64Ptr(int64(2)), 238 "name": testutil.TestStringPtr("bar"), 239 "email": testutil.TestStringPtr("bar@email.com"), 240 } 241 242 // uninitialized 243 c1 := dosaRenamed.NewClient(reg1, nullConnector) 244 assert.Error(t, c1.Read(ctx, fieldsToRead, cte1)) 245 246 // unregistered object 247 c1.Initialize(ctx) 248 err := c1.Read(ctx, dosaRenamed.All(), cte2) 249 assert.Error(t, err) 250 assert.Contains(t, err.Error(), "ClientTestEntity2") 251 252 // happy path, mock connector 253 ctrl := gomock.NewController(t) 254 defer ctrl.Finish() 255 mockConn := mocks.NewMockConnector(ctrl) 256 mockConn.EXPECT().CheckSchema(ctx, gomock.Any(), gomock.Any(), gomock.Any()).Return(int32(1), nil).AnyTimes() 257 mockConn.EXPECT().Read(ctx, gomock.Any(), gomock.Any(), gomock.Any()). 258 Do(func(_ context.Context, _ *dosaRenamed.EntityInfo, columnValues map[string]dosaRenamed.FieldValue, columnsToRead []string) { 259 assert.Equal(t, columnValues["id"], cte1.ID) 260 assert.Equal(t, columnsToRead, []string{"id", "email"}) 261 262 }).Return(results, nil).MinTimes(1) 263 c3 := dosaRenamed.NewClient(reg2, mockConn) 264 assert.NoError(t, c3.Initialize(ctx)) 265 assert.NoError(t, c3.Read(ctx, fieldsToRead, cte1)) 266 testutil.AssertEqForPointer(testAssert(t), cte1.ID, results["id"]) 267 assert.NotEqual(t, cte1.Name, results["name"]) 268 testutil.AssertEqForPointer(testAssert(t), cte1.Email, results["email"]) 269 } 270 271 type AllFieldTypes struct { 272 dosaRenamed.Entity `dosa:"primaryKey=ID"` 273 ID int64 274 BoolType bool 275 Int32Type int32 276 Int64Type int64 277 DoubleType float64 278 StringType string 279 BlobType []byte 280 TimeType time.Time 281 UUIDType dosaRenamed.UUID 282 NullBoolType *bool 283 NullInt32Type *int32 284 NullInt64Type *int64 285 NullDoubleType *float64 286 NullStringType *string 287 NullTimeType *time.Time 288 NullUUIDType *dosaRenamed.UUID 289 } 290 291 func TestClient_Read_pointer(t *testing.T) { 292 allTypes := &AllFieldTypes{ID: int64(3212)} 293 reg1, err := dosaRenamed.NewRegistrar(scope, namePrefix, allTypes) 294 assert.NoError(t, err) 295 results := map[string]dosaRenamed.FieldValue{ 296 "id": int64(3212), 297 "booltype": true, 298 "int32type": int32(1), 299 "int64type": int64(2), 300 "doubletype": float64(8.9), 301 "stringtype": "faa@email.com", 302 "blobtype": []byte("hello world"), 303 "timetype": time.Now(), 304 "uuidtype": dosaRenamed.NewUUID(), 305 "nullbooltype": testutil.TestBoolPtr(true), 306 "nullint32type": testutil.TestInt32Ptr(int32(123)), 307 "nullint64type": testutil.TestInt64Ptr(int64(2)), 308 "nulldoubletype": testutil.TestFloat64Ptr(float64(8.9)), 309 "nullstringtype": testutil.TestStringPtr("bar@email.com"), 310 "nulltimetype": testutil.TestTimePtr(time.Now()), 311 "nulluuidtype": testutil.TestUUIDPtr(dosaRenamed.NewUUID()), 312 } 313 314 // uninitialized 315 c1 := dosaRenamed.NewClient(reg1, nullConnector) 316 assert.Error(t, c1.Read(ctx, dosaRenamed.All(), allTypes)) 317 318 // happy path, mock connector 319 ctrl := gomock.NewController(t) 320 defer ctrl.Finish() 321 mockConn := mocks.NewMockConnector(ctrl) 322 mockConn.EXPECT().CheckSchema(ctx, gomock.Any(), gomock.Any(), gomock.Any()).Return(int32(1), nil).AnyTimes() 323 mockConn.EXPECT().Read(ctx, gomock.Any(), gomock.Any(), gomock.Any()). 324 Do(func(_ context.Context, _ *dosaRenamed.EntityInfo, columnValues map[string]dosaRenamed.FieldValue, columnsToRead []string) { 325 assert.Equal(t, columnValues["id"], allTypes.ID) 326 }).Return(results, nil).MinTimes(1) 327 c3 := dosaRenamed.NewClient(reg1, mockConn) 328 assert.NoError(t, c3.Initialize(ctx)) 329 assert.NoError(t, c3.Read(ctx, dosaRenamed.All(), allTypes)) 330 assert.Equal(t, allTypes.ID, results["id"]) 331 assert.Equal(t, allTypes.BoolType, results["booltype"]) 332 assert.Equal(t, allTypes.Int32Type, results["int32type"]) 333 assert.Equal(t, allTypes.Int64Type, results["int64type"]) 334 assert.Equal(t, allTypes.DoubleType, results["doubletype"]) 335 assert.Equal(t, allTypes.StringType, results["stringtype"]) 336 assert.Equal(t, allTypes.BlobType, results["blobtype"]) 337 assert.Equal(t, allTypes.TimeType, results["timetype"]) 338 assert.Equal(t, allTypes.UUIDType, results["uuidtype"]) 339 testutil.AssertEqForPointer(testAssert(t), *allTypes.NullBoolType, results["nullbooltype"]) 340 testutil.AssertEqForPointer(testAssert(t), *allTypes.NullInt32Type, results["nullint32type"]) 341 testutil.AssertEqForPointer(testAssert(t), *allTypes.NullInt64Type, results["nullint64type"]) 342 testutil.AssertEqForPointer(testAssert(t), *allTypes.NullDoubleType, results["nulldoubletype"]) 343 testutil.AssertEqForPointer(testAssert(t), *allTypes.NullStringType, results["nullstringtype"]) 344 testutil.AssertEqForPointer(testAssert(t), *allTypes.NullTimeType, results["nulltimetype"]) 345 testutil.AssertEqForPointer(testAssert(t), *allTypes.NullUUIDType, results["nulluuidtype"]) 346 } 347 348 func TestClient_Read_Errors(t *testing.T) { 349 reg1, _ := dosaRenamed.NewRegistrar(scope, namePrefix, cte1) 350 ctrl := gomock.NewController(t) 351 defer ctrl.Finish() 352 readError := errors.New("oops") 353 mockConn := mocks.NewMockConnector(ctrl) 354 mockConn.EXPECT().CheckSchema(ctx, gomock.Any(), gomock.Any(), gomock.Any()).Return(int32(1), nil).AnyTimes() 355 mockConn.EXPECT().Read(ctx, gomock.Any(), gomock.Any(), gomock.Any()). 356 Do(func(_ context.Context, _ *dosaRenamed.EntityInfo, columnValues map[string]dosaRenamed.FieldValue, columnsToRead []string) { 357 assert.Equal(t, columnValues["id"], cte1.ID) 358 assert.NotEmpty(t, columnsToRead) 359 }).Return(nil, readError) 360 361 c1 := dosaRenamed.NewClient(reg1, mockConn) 362 assert.NoError(t, c1.Initialize(ctx)) 363 err := c1.Read(ctx, dosaRenamed.All(), cte1) 364 assert.Error(t, err) 365 assert.Equal(t, err, readError) 366 err = c1.Read(ctx, []string{"badcol"}, cte1) 367 assert.Error(t, err) 368 assert.Contains(t, err.Error(), "badcol") 369 } 370 371 func TestClient_Upsert(t *testing.T) { 372 reg1, _ := dosaRenamed.NewRegistrar("test", "team.service", cte1) 373 reg2, _ := dosaRenamed.NewRegistrar("test", "team.service", cte1, cte2) 374 fieldsToUpdate := []string{"Email"} 375 updatedEmail := "bar@email.com" 376 377 // uninitialized 378 c1 := dosaRenamed.NewClient(reg1, nullConnector) 379 assert.Error(t, c1.Upsert(ctx, fieldsToUpdate, cte1)) 380 381 // unregistered object error 382 c2 := dosaRenamed.NewClient(reg1, nullConnector) 383 c2.Initialize(ctx) 384 assert.Error(t, c2.Upsert(ctx, fieldsToUpdate, cte2)) 385 386 // happy path, mock connector 387 ctrl := gomock.NewController(t) 388 defer ctrl.Finish() 389 mockConn := mocks.NewMockConnector(ctrl) 390 mockConn.EXPECT().CheckSchema(ctx, gomock.Any(), gomock.Any(), gomock.Any()).Return(int32(1), nil).AnyTimes() 391 mockConn.EXPECT().Upsert(ctx, gomock.Any(), gomock.Any()). 392 Do(func(_ context.Context, _ *dosaRenamed.EntityInfo, columnValues map[string]dosaRenamed.FieldValue) { 393 assert.Equal(t, columnValues["id"], cte1.ID) 394 assert.Equal(t, columnValues["email"], cte1.Email) 395 cte1.Email = updatedEmail 396 }). 397 Return(nil).MinTimes(1) 398 c3 := dosaRenamed.NewClient(reg2, mockConn) 399 assert.NoError(t, c3.Initialize(ctx)) 400 assert.NoError(t, c3.Upsert(ctx, fieldsToUpdate, cte1)) 401 assert.Equal(t, cte1.Email, updatedEmail) 402 } 403 func TestClient_CreateIfNotExists(t *testing.T) { 404 reg1, _ := dosaRenamed.NewRegistrar("test", "team.service", cte1) 405 reg2, _ := dosaRenamed.NewRegistrar("test", "team.service", cte1, cte2) 406 updatedEmail := "bar@email.com" 407 408 // uninitialized 409 c1 := dosaRenamed.NewClient(reg1, nullConnector) 410 assert.Error(t, c1.CreateIfNotExists(ctx, cte1)) 411 412 // unregistered object error 413 c2 := dosaRenamed.NewClient(reg1, nullConnector) 414 c2.Initialize(ctx) 415 assert.Error(t, c2.CreateIfNotExists(ctx, cte2)) 416 417 // happy path, mock connector 418 ctrl := gomock.NewController(t) 419 defer ctrl.Finish() 420 mockConn := mocks.NewMockConnector(ctrl) 421 mockConn.EXPECT().CheckSchema(ctx, gomock.Any(), gomock.Any(), gomock.Any()).Return(int32(1), nil).AnyTimes() 422 mockConn.EXPECT().CreateIfNotExists(ctx, gomock.Any(), gomock.Any()). 423 Do(func(_ context.Context, _ *dosaRenamed.EntityInfo, columnValues map[string]dosaRenamed.FieldValue) { 424 assert.Equal(t, columnValues["id"], cte1.ID) 425 assert.Equal(t, columnValues["email"], cte1.Email) 426 cte1.Email = updatedEmail 427 }). 428 Return(nil).MinTimes(1) 429 c3 := dosaRenamed.NewClient(reg2, mockConn) 430 assert.NoError(t, c3.Initialize(ctx)) 431 assert.NoError(t, c3.CreateIfNotExists(ctx, cte1)) 432 assert.Equal(t, cte1.Email, updatedEmail) 433 } 434 435 func TestClient_Upsert_Errors(t *testing.T) { 436 reg1, _ := dosaRenamed.NewRegistrar(scope, namePrefix, cte1) 437 ctrl := gomock.NewController(t) 438 defer ctrl.Finish() 439 readError := errors.New("oops") 440 mockConn := mocks.NewMockConnector(ctrl) 441 mockConn.EXPECT().CheckSchema(ctx, gomock.Any(), gomock.Any(), gomock.Any()).Return(int32(1), nil).AnyTimes() 442 443 c1 := dosaRenamed.NewClient(reg1, mockConn) 444 assert.NoError(t, c1.Initialize(ctx)) 445 mockConn.EXPECT().Upsert(ctx, gomock.Any(), gomock.Not(dosaRenamed.All())).Return(nil) 446 err := c1.Upsert(ctx, dosaRenamed.All(), cte1) 447 assert.NoError(t, err) 448 449 mockConn.EXPECT().Upsert(ctx, gomock.Any(), map[string]dosaRenamed.FieldValue{"id": dosaRenamed.FieldValue(int64(2))}).Return(readError) 450 err = c1.Upsert(ctx, []string{"ID"}, cte1) 451 assert.Error(t, err) 452 assert.Equal(t, err, readError) 453 err = c1.Upsert(ctx, []string{"badcol"}, cte1) 454 assert.Error(t, err) 455 assert.Contains(t, err.Error(), "badcol") 456 } 457 458 func TestClient_RemoveRange(t *testing.T) { 459 reg1, _ := dosaRenamed.NewRegistrar(scope, namePrefix, cte1) 460 461 c1 := dosaRenamed.NewClient(reg1, nullConnector) 462 rop := dosaRenamed.NewRemoveRangeOp(cte1).Eq("ID", "123") 463 err := c1.RemoveRange(ctx, rop) 464 assert.True(t, dosaRenamed.ErrorIsNotInitialized(err)) 465 466 c1.Initialize(ctx) 467 468 //bad entity 469 rop = dosaRenamed.NewRemoveRangeOp(cte2) 470 err = c1.RemoveRange(ctx, rop) 471 assert.Error(t, err) 472 assert.Contains(t, err.Error(), "ClientTestEntity2") 473 474 // bad column in range 475 rop = dosaRenamed.NewRemoveRangeOp(cte1).Eq("borkborkbork", int64(1)) 476 err = c1.RemoveRange(ctx, rop) 477 assert.Error(t, err) 478 assert.Contains(t, err.Error(), "ClientTestEntity1") 479 assert.Contains(t, err.Error(), "borkborkbork") 480 481 // success case 482 ctrl := gomock.NewController(t) 483 defer ctrl.Finish() 484 mockConn := mocks.NewMockConnector(ctrl) 485 mockConn.EXPECT().CheckSchema(ctx, gomock.Any(), gomock.Any(), gomock.Any()).Return(int32(1), nil).AnyTimes() 486 mockConn.EXPECT().RemoveRange(ctx, gomock.Any(), gomock.Any()).Return(nil) 487 c2 := dosaRenamed.NewClient(reg1, mockConn) 488 c2.Initialize(ctx) 489 rop = dosaRenamed.NewRemoveRangeOp(cte1) 490 err = c2.RemoveRange(ctx, rop) 491 assert.NoError(t, err) 492 } 493 494 func TestClient_Range(t *testing.T) { 495 reg1, _ := dosaRenamed.NewRegistrar(scope, namePrefix, cte1) 496 fieldsToRead := []string{"ID", "Email"} 497 resultRow := map[string]dosaRenamed.FieldValue{ 498 "id": int64(2), 499 "name": "bar", 500 "email": "bar@email.com", 501 } 502 503 // uninitialized 504 c1 := dosaRenamed.NewClient(reg1, nullConnector) 505 rop := dosaRenamed.NewRangeOp(cte1).Fields(fieldsToRead).Eq("ID", "123").Offset("tokeytoketoke") 506 _, _, err := c1.Range(ctx, rop) 507 assert.True(t, dosaRenamed.ErrorIsNotInitialized(err)) 508 509 c1.Initialize(ctx) 510 511 // bad entity 512 rop = dosaRenamed.NewRangeOp(cte2) 513 _, _, err = c1.Range(ctx, rop) 514 assert.Error(t, err) 515 assert.Contains(t, err.Error(), "ClientTestEntity2") 516 517 // bad column in range 518 // we don't test other failed RangeOpConditions since those are unit tested elsewhere 519 rop = dosaRenamed.NewRangeOp(cte1).Eq("borkborkbork", int64(1)) 520 _, _, err = c1.Range(ctx, rop) 521 assert.Error(t, err) 522 assert.Contains(t, err.Error(), "ClientTestEntity1") 523 assert.Contains(t, err.Error(), "borkborkbork") 524 525 // bad projected column 526 rop = dosaRenamed.NewRangeOp(cte1).Fields([]string{"borkborkbork"}) 527 _, _, err = c1.Range(ctx, rop) 528 assert.Error(t, err) 529 assert.Contains(t, err.Error(), "ClientTestEntity1") 530 assert.Contains(t, err.Error(), "borkborkbork") 531 532 // success case 533 ctrl := gomock.NewController(t) 534 defer ctrl.Finish() 535 mockConn := mocks.NewMockConnector(ctrl) 536 mockConn.EXPECT().CheckSchema(ctx, gomock.Any(), gomock.Any(), gomock.Any()).Return(int32(1), nil).AnyTimes() 537 mockConn.EXPECT().Range(ctx, gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). 538 Return([]map[string]dosaRenamed.FieldValue{resultRow}, "continuation-token", nil) 539 c2 := dosaRenamed.NewClient(reg1, mockConn) 540 c2.Initialize(ctx) 541 rop = dosaRenamed.NewRangeOp(cte1) 542 rows, token, err := c2.Range(ctx, rop) 543 assert.NoError(t, err) 544 assert.NotNil(t, rows) 545 assert.Equal(t, 1, len(rows)) 546 for _, obj := range rows { 547 assert.Equal(t, resultRow["id"], obj.(*ClientTestEntity1).ID) 548 assert.Equal(t, resultRow["name"], obj.(*ClientTestEntity1).Name) 549 assert.Equal(t, resultRow["email"], obj.(*ClientTestEntity1).Email) 550 } 551 assert.Equal(t, "continuation-token", token) 552 553 // no resulting rows, just use the devnull connector 554 rop = dosaRenamed.NewRangeOp(cte1) 555 _, _, err = c1.Range(ctx, rop) 556 assert.True(t, dosaRenamed.ErrorIsNotFound(err)) 557 } 558 559 func TestClient_WalkRange(t *testing.T) { 560 reg1, _ := dosaRenamed.NewRegistrar(scope, namePrefix, cte1) 561 fieldsToRead := []string{"ID", "Email"} 562 resultRow0 := map[string]dosaRenamed.FieldValue{ 563 "id": int64(2), 564 "name": "bar", 565 "email": "bar@email.com", 566 } 567 resultRow1 := map[string]dosaRenamed.FieldValue{ 568 "id": int64(3), 569 "name": "jeff", 570 "email": "jeff@email.com", 571 } 572 573 // uninitialized 574 c0 := dosaRenamed.NewClient(reg1, nullConnector) 575 rop := dosaRenamed.NewRangeOp(cte1).Fields(fieldsToRead).Eq("ID", "123").Offset("tokeytoketoke") 576 err := c0.WalkRange(ctx, rop, func(value dosaRenamed.DomainObject) error { 577 return nil 578 }) 579 assert.True(t, dosaRenamed.ErrorIsNotInitialized(err)) 580 581 // success case 582 ctrl := gomock.NewController(t) 583 defer ctrl.Finish() 584 mockConn1 := mocks.NewMockConnector(ctrl) 585 mockConn1.EXPECT().CheckSchema(ctx, gomock.Any(), gomock.Any(), gomock.Any()).Return(int32(1), nil).AnyTimes() 586 mockConn1.EXPECT().Range(ctx, gomock.Any(), gomock.Any(), gomock.Any(), "", gomock.Any()). 587 Return([]map[string]dosaRenamed.FieldValue{resultRow0}, "token0", nil) 588 mockConn1.EXPECT().Range(ctx, gomock.Any(), gomock.Any(), gomock.Any(), "token0", gomock.Any()). 589 Return([]map[string]dosaRenamed.FieldValue{resultRow1}, "", nil) 590 c1 := dosaRenamed.NewClient(reg1, mockConn1) 591 c1.Initialize(ctx) 592 rop = dosaRenamed.NewRangeOp(cte1) 593 594 var fetched []*ClientTestEntity1 595 err = c1.WalkRange(ctx, rop, func(value dosaRenamed.DomainObject) error { 596 fetched = append(fetched, value.(*ClientTestEntity1)) 597 return nil 598 }) 599 assert.NoError(t, err) 600 assert.NotNil(t, fetched) 601 assert.Equal(t, 2, len(fetched)) 602 assert.Equal(t, resultRow0["id"], fetched[0].ID) 603 assert.Equal(t, resultRow0["name"], fetched[0].Name) 604 assert.Equal(t, resultRow0["email"], fetched[0].Email) 605 assert.Equal(t, resultRow1["id"], fetched[1].ID) 606 assert.Equal(t, resultRow1["name"], fetched[1].Name) 607 assert.Equal(t, resultRow1["email"], fetched[1].Email) 608 609 // Range with closure error 610 mockConn2 := mocks.NewMockConnector(ctrl) 611 mockConn2.EXPECT().CheckSchema(ctx, gomock.Any(), gomock.Any(), gomock.Any()).Return(int32(1), nil) 612 mockConn2.EXPECT().Range(ctx, gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). 613 Return([]map[string]dosaRenamed.FieldValue{resultRow0}, "", nil) 614 c2 := dosaRenamed.NewClient(reg1, mockConn2) 615 c2.Initialize(ctx) 616 rop = dosaRenamed.NewRangeOp(cte1) 617 err = c2.WalkRange(ctx, rop, func(value dosaRenamed.DomainObject) error { 618 return errors.New("woops!") 619 }) 620 assert.EqualError(t, err, "woops!") 621 } 622 623 func TestClient_ScanEverything(t *testing.T) { 624 reg1, _ := dosaRenamed.NewRegistrar(scope, namePrefix, cte1) 625 fieldsToRead := []string{"ID", "Email"} 626 resultRow := map[string]dosaRenamed.FieldValue{ 627 "id": int64(2), 628 "name": "bar", 629 "email": "bar@email.com", 630 "straycolumn": "this_should_be_discarded", 631 } 632 633 // uninitialized 634 c1 := dosaRenamed.NewClient(reg1, nullConnector) 635 sop := dosaRenamed.NewScanOp(cte1).Fields(fieldsToRead).Offset("tokeytoketoke") 636 _, _, err := c1.ScanEverything(ctx, sop) 637 assert.True(t, dosaRenamed.ErrorIsNotInitialized(err)) 638 639 c1.Initialize(ctx) 640 641 // bad entity 642 sop = dosaRenamed.NewScanOp(cte2) 643 _, _, err = c1.ScanEverything(ctx, sop) 644 assert.Error(t, err) 645 assert.Contains(t, err.Error(), "ClientTestEntity2") 646 647 // bad projected column 648 sop = dosaRenamed.NewScanOp(cte1).Fields([]string{"borkborkbork"}) 649 _, _, err = c1.ScanEverything(ctx, sop) 650 assert.Error(t, err) 651 assert.Contains(t, err.Error(), "ClientTestEntity1") 652 assert.Contains(t, err.Error(), "borkborkbork") 653 654 // success case 655 ctrl := gomock.NewController(t) 656 defer ctrl.Finish() 657 mockConn := mocks.NewMockConnector(ctrl) 658 mockConn.EXPECT().CheckSchema(ctx, gomock.Any(), gomock.Any(), gomock.Any()).Return(int32(1), nil).AnyTimes() 659 mockConn.EXPECT().Scan(ctx, gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). 660 Return([]map[string]dosaRenamed.FieldValue{resultRow}, "continuation-token", nil) 661 c2 := dosaRenamed.NewClient(reg1, mockConn) 662 c2.Initialize(ctx) 663 sop = dosaRenamed.NewScanOp(cte1) 664 rows, token, err := c2.ScanEverything(ctx, sop) 665 assert.NoError(t, err) 666 assert.NotNil(t, rows) 667 assert.Equal(t, 1, len(rows)) 668 for _, obj := range rows { 669 assert.Equal(t, resultRow["id"], obj.(*ClientTestEntity1).ID) 670 assert.Equal(t, resultRow["name"], obj.(*ClientTestEntity1).Name) 671 assert.Equal(t, resultRow["email"], obj.(*ClientTestEntity1).Email) 672 } 673 assert.Equal(t, "continuation-token", token) 674 675 // no resulting rows, just use the devnull connector 676 sop = dosaRenamed.NewScanOp(cte1) 677 _, _, err = c1.ScanEverything(ctx, sop) 678 assert.True(t, dosaRenamed.ErrorIsNotFound(err)) 679 } 680 681 func TestClient_Remove(t *testing.T) { 682 reg1, _ := dosaRenamed.NewRegistrar(scope, namePrefix, cte1) 683 684 // uninitialized 685 c1 := dosaRenamed.NewClient(reg1, nullConnector) 686 err := c1.Remove(ctx, cte1) 687 assert.True(t, dosaRenamed.ErrorIsNotInitialized(err)) 688 689 c1.Initialize(ctx) 690 691 // bad entity 692 err = c1.Remove(ctx, cte2) 693 assert.Error(t, err) 694 assert.Contains(t, err.Error(), "ClientTestEntity2") 695 // success case 696 ctrl := gomock.NewController(t) 697 defer ctrl.Finish() 698 mockConn := mocks.NewMockConnector(ctrl) 699 mockConn.EXPECT().CheckSchema(ctx, gomock.Any(), gomock.Any(), gomock.Any()).Return(int32(1), nil).AnyTimes() 700 mockConn.EXPECT().Remove(ctx, gomock.Any(), map[string]dosaRenamed.FieldValue{"id": dosaRenamed.FieldValue(int64(123))}).Return(nil) 701 c2 := dosaRenamed.NewClient(reg1, mockConn) 702 c2.Initialize(ctx) 703 err = c2.Remove(ctx, &ClientTestEntity1{ID: int64(123)}) 704 assert.NoError(t, err) 705 706 } 707 708 /* TODO: Coming in v2.1 709 func TestClient_Unimplemented(t *testing.T) { 710 reg1, _ := dosaRenamed.NewRegistrar(scope, namePrefix, cte1) 711 712 c := dosaRenamed.NewClient(reg1, nullConnector) 713 assert.Panics(t, func() { 714 c.MultiRead(ctx, dosaRenamed.All(), &ClientTestEntity1{}) 715 }) 716 assert.Panics(t, func() { 717 c.MultiUpsert(ctx, dosaRenamed.All(), &ClientTestEntity1{}) 718 }) 719 assert.Panics(t, func() { 720 c.MultiRemove(ctx, &ClientTestEntity1{}) 721 }) 722 } 723 */ 724 725 func TestAdminClient_CreateScope(t *testing.T) { 726 c := dosaRenamed.NewAdminClient(nullConnector) 727 assert.NotNil(t, c) 728 729 err := c.CreateScope(context.TODO(), scope) 730 assert.NoError(t, err) 731 } 732 733 func TestAdminClient_TruncateScope(t *testing.T) { 734 c := dosaRenamed.NewAdminClient(nullConnector) 735 assert.NotNil(t, c) 736 737 err := c.TruncateScope(context.TODO(), scope) 738 assert.NoError(t, err) 739 } 740 741 func TestAdminClient_DropScope(t *testing.T) { 742 c := dosaRenamed.NewAdminClient(nullConnector) 743 assert.NotNil(t, c) 744 745 err := c.DropScope(context.TODO(), scope) 746 assert.NoError(t, err) 747 } 748 749 func TestAdminClient_CheckSchema(t *testing.T) { 750 // write some entities to disk 751 tmpdir := ".testcheckschema" 752 os.RemoveAll(tmpdir) 753 defer os.RemoveAll(tmpdir) 754 content := ` 755 package main 756 757 import "github.com/uber-go/dosa" 758 759 type TestEntityA struct { 760 dosa.Entity ` + "`dosa:\"primaryKey=(ID)\"`" + ` 761 ID int32 762 } 763 type TestEntityB struct { 764 dosa.Entity ` + "`dosa:\"primaryKey=(ID)\"`" + ` 765 ID int32 766 } 767 ` 768 assert.NoError(t, os.MkdirAll(tmpdir, 0770)) 769 assert.NoError(t, ioutil.WriteFile(filepath.Join(tmpdir, "f1.go"), []byte(content), 0700)) 770 771 data := []struct { 772 dirs []string 773 excludes []string 774 scope string 775 namePrefix string 776 errContains string 777 }{ 778 // cannot get schema 779 { 780 dirs: []string{"/foo/bar/baz"}, 781 scope: scope, 782 errContains: "/foo/bar/baz", 783 }, 784 // connector error 785 { 786 dirs: []string{tmpdir}, 787 scope: scope, 788 namePrefix: "error", 789 errContains: "connector error", 790 }, 791 // happy path 792 { 793 dirs: []string{tmpdir}, 794 scope: scope, 795 namePrefix: namePrefix, 796 }, 797 } 798 799 // calls with "error" prefix will fail, rest succeed 800 ctrl := gomock.NewController(t) 801 defer ctrl.Finish() 802 mockConn := mocks.NewMockConnector(ctrl) 803 mockConn.EXPECT().CheckSchema(ctx, scope, "error", gomock.Any()).Return(int32(-1), errors.New("connector error")).Times(1) 804 mockConn.EXPECT().CheckSchema(ctx, scope, namePrefix, gomock.Any()).Return(int32(1), nil).Times(1) 805 806 for _, d := range data { 807 _, err := dosaRenamed.NewAdminClient(mockConn). 808 Directories(d.dirs). 809 Scope(d.scope). 810 CheckSchema(ctx, d.namePrefix) 811 if d.errContains != "" { 812 assert.Contains(t, err.Error(), d.errContains) 813 continue 814 } 815 assert.NoError(t, err) 816 } 817 } 818 819 func TestAdminClient_CheckSchemaStatus(t *testing.T) { 820 data := []struct { 821 version int32 822 scope string 823 namePrefix string 824 errContains string 825 }{ 826 { // connector error 827 version: int32(1), 828 scope: scope, 829 namePrefix: "error", 830 errContains: "connector error", 831 }, 832 // happy path 833 { 834 version: int32(1), 835 scope: scope, 836 namePrefix: namePrefix, 837 }, 838 } 839 840 // calls with "error" prefix will fail, rest succeed 841 ctrl := gomock.NewController(t) 842 defer ctrl.Finish() 843 mockConn := mocks.NewMockConnector(ctrl) 844 mockConn.EXPECT().CheckSchemaStatus(ctx, scope, "error", int32(1)).Return(nil, errors.New("connector error")).Times(1) 845 mockConn.EXPECT().CheckSchemaStatus(ctx, scope, namePrefix, gomock.Any()).Return(&dosaRenamed.SchemaStatus{Version: int32(1)}, nil).Times(1) 846 847 for _, d := range data { 848 status, err := dosaRenamed.NewAdminClient(mockConn). 849 Scope(d.scope). 850 CheckSchemaStatus(ctx, d.namePrefix, d.version) 851 if d.errContains != "" { 852 assert.Contains(t, err.Error(), d.errContains) 853 continue 854 } 855 assert.NoError(t, err) 856 assert.Equal(t, d.version, status.Version) 857 } 858 } 859 860 func TestAdminClient_UpsertSchema(t *testing.T) { 861 // write some entities to disk 862 tmpdir := ".testupsertschema" 863 os.RemoveAll(tmpdir) 864 defer os.RemoveAll(tmpdir) 865 content := ` 866 package main 867 868 import "github.com/uber-go/dosa" 869 870 type TestEntityA struct { 871 dosa.Entity ` + "`dosa:\"primaryKey=(ID)\"`" + ` 872 ID int32 873 } 874 type TestEntityB struct { 875 dosa.Entity ` + "`dosa:\"primaryKey=(ID)\"`" + ` 876 ID int32 877 } 878 ` 879 assert.NoError(t, os.MkdirAll(tmpdir, 0770)) 880 assert.NoError(t, ioutil.WriteFile(filepath.Join(tmpdir, "f1.go"), []byte(content), 0700)) 881 882 data := []struct { 883 dirs []string 884 scope string 885 namePrefix string 886 errContains string 887 }{ 888 // cannot get schema 889 { 890 dirs: []string{"/foo/bar/baz"}, 891 scope: scope, 892 errContains: "/foo/bar/baz", 893 }, 894 // connector error 895 { 896 dirs: []string{tmpdir}, 897 scope: scope, 898 namePrefix: "error", 899 errContains: "connector error", 900 }, 901 // happy path 902 { 903 dirs: []string{tmpdir}, 904 scope: scope, 905 namePrefix: namePrefix, 906 }, 907 } 908 909 // calls with "error" prefix will fail, rest succeed 910 ctrl := gomock.NewController(t) 911 defer ctrl.Finish() 912 mockConn := mocks.NewMockConnector(ctrl) 913 mockConn.EXPECT().UpsertSchema(ctx, scope, "error", gomock.Any()).Return(nil, errors.New("connector error")).Times(1) 914 mockConn.EXPECT().UpsertSchema(ctx, scope, namePrefix, gomock.Any()).Return(&dosaRenamed.SchemaStatus{Version: int32(1)}, nil).Times(1) 915 916 for _, d := range data { 917 _, err := dosaRenamed.NewAdminClient(mockConn). 918 Directories(d.dirs). 919 Scope(d.scope). 920 UpsertSchema(ctx, d.namePrefix) 921 if d.errContains != "" { 922 assert.Contains(t, err.Error(), d.errContains) 923 continue 924 } 925 assert.NoError(t, err) 926 } 927 } 928 929 func TestAdminClient_GetSchema(t *testing.T) { 930 // write some entities to disk 931 tmpdir := ".testgetschema" 932 os.RemoveAll(tmpdir) 933 defer os.RemoveAll(tmpdir) 934 path1 := filepath.Join(tmpdir, "f1.go") 935 path2 := filepath.Join(tmpdir, "f2.go") 936 content := ` 937 package main 938 939 import renamed "github.com/uber-go/dosa" 940 941 type TestEntityA struct { 942 renamed.Entity ` + "`dosa:\"primaryKey=(ID)\"`" + ` 943 ID int32 944 } 945 type TestEntityB struct { 946 renamed.Entity ` + "`dosa:\"primaryKey=(ID)\"`" + ` 947 ID renamed.UUID 948 } 949 ` 950 invalid := ` 951 package main 952 953 import "github.com/uber-go/dosa" 954 955 type TestEntityC struct { 956 dosa.Entity ` + "`dosa:\"invalidtag\"`" + ` 957 ID int32 958 } 959 ` 960 assert.NoError(t, os.MkdirAll(tmpdir, 0770)) 961 assert.NoError(t, ioutil.WriteFile(path1, []byte(content), 0700)) 962 assert.NoError(t, ioutil.WriteFile(path2, []byte(invalid), 0700)) 963 964 data := []struct { 965 dirs []string 966 excludes []string 967 scope string 968 namePrefix string 969 errContains string 970 }{ 971 // invalid scope 972 { 973 scope: "***", 974 errContains: "invalid scope name", 975 }, 976 // invalid directory 977 { 978 dirs: []string{"/foo/bar/baz"}, 979 scope: scope, 980 errContains: "/foo/bar/baz", 981 }, 982 // no entities found 983 { 984 dirs: []string{tmpdir}, 985 excludes: []string{"f1.go", "f2.go", "f3.go"}, 986 scope: scope, 987 errContains: "no entities found", 988 }, 989 // invalid struct tag 990 { 991 dirs: []string{tmpdir}, 992 scope: scope, 993 errContains: "invalidtag", 994 }, 995 } 996 997 for _, d := range data { 998 _, err := dosaRenamed.NewAdminClient(nullConnector). 999 Directories(d.dirs). 1000 Excludes(d.excludes). 1001 Scope(d.scope). 1002 UpsertSchema(ctx, d.namePrefix) 1003 if d.errContains != "" { 1004 assert.Contains(t, err.Error(), d.errContains) 1005 continue 1006 } 1007 assert.NoError(t, err) 1008 } 1009 } 1010 1011 func TestErrorIsNotFound(t *testing.T) { 1012 assert.False(t, dosaRenamed.ErrorIsNotFound(errors.New("not a IsNotFound error"))) 1013 assert.False(t, dosaRenamed.ErrorIsNotFound(&dosaRenamed.ErrNotInitialized{})) 1014 assert.True(t, dosaRenamed.ErrorIsNotFound(&dosaRenamed.ErrNotFound{})) 1015 assert.True(t, dosaRenamed.ErrorIsNotFound(errors.Wrap(&dosaRenamed.ErrNotFound{}, "wrapped"))) 1016 assert.Equal(t, (&dosaRenamed.ErrNotFound{}).Error(), "not found") 1017 } 1018 1019 func TestErrNotInitialized_Error(t *testing.T) { 1020 assert.False(t, dosaRenamed.ErrorIsNotInitialized(errors.New("not a IsNotInitializedError"))) 1021 assert.False(t, dosaRenamed.ErrorIsNotInitialized(&dosaRenamed.ErrNotFound{})) 1022 assert.True(t, dosaRenamed.ErrorIsNotInitialized(&dosaRenamed.ErrNotInitialized{})) 1023 assert.True(t, dosaRenamed.ErrorIsNotInitialized(errors.Wrap(&dosaRenamed.ErrNotInitialized{}, "wrapped"))) 1024 assert.Equal(t, (&dosaRenamed.ErrNotInitialized{}).Error(), "client not initialized") 1025 1026 } 1027 1028 func TestErrorIsAlreadyExists(t *testing.T) { 1029 assert.False(t, dosaRenamed.ErrorIsAlreadyExists(errors.New("not an already exists error"))) 1030 assert.False(t, dosaRenamed.ErrorIsAlreadyExists(&dosaRenamed.ErrNotInitialized{})) 1031 assert.False(t, dosaRenamed.ErrorIsAlreadyExists(&dosaRenamed.ErrNotFound{})) 1032 assert.True(t, dosaRenamed.ErrorIsAlreadyExists(errors.Wrap(&dosaRenamed.ErrAlreadyExists{}, "wrapped"))) 1033 assert.Equal(t, "already exists", (&dosaRenamed.ErrAlreadyExists{}).Error()) 1034 }