1// Copyright 2017 Google LLC 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15package firestore 16 17import ( 18 "context" 19 "errors" 20 "flag" 21 "fmt" 22 "log" 23 "math" 24 "os" 25 "path/filepath" 26 "reflect" 27 "runtime" 28 "sort" 29 "testing" 30 "time" 31 32 "cloud.google.com/go/internal/pretty" 33 "cloud.google.com/go/internal/testutil" 34 "cloud.google.com/go/internal/uid" 35 "github.com/google/go-cmp/cmp" 36 "github.com/google/go-cmp/cmp/cmpopts" 37 "google.golang.org/api/option" 38 "google.golang.org/genproto/googleapis/type/latlng" 39 "google.golang.org/grpc/codes" 40 "google.golang.org/grpc/status" 41) 42 43func TestMain(m *testing.M) { 44 initIntegrationTest() 45 status := m.Run() 46 cleanupIntegrationTest() 47 os.Exit(status) 48} 49 50const ( 51 envProjID = "GCLOUD_TESTS_GOLANG_FIRESTORE_PROJECT_ID" 52 envPrivateKey = "GCLOUD_TESTS_GOLANG_FIRESTORE_KEY" 53) 54 55var ( 56 iClient *Client 57 iColl *CollectionRef 58 collectionIDs = uid.NewSpace("go-integration-test", nil) 59) 60 61func initIntegrationTest() { 62 flag.Parse() // needed for testing.Short() 63 if testing.Short() { 64 return 65 } 66 ctx := context.Background() 67 testProjectID := os.Getenv(envProjID) 68 if testProjectID == "" { 69 log.Println("Integration tests skipped. See CONTRIBUTING.md for details") 70 return 71 } 72 ts := testutil.TokenSourceEnv(ctx, envPrivateKey, 73 "https://www.googleapis.com/auth/cloud-platform", 74 "https://www.googleapis.com/auth/datastore") 75 if ts == nil { 76 log.Fatal("The project key must be set. See CONTRIBUTING.md for details") 77 } 78 wantDBPath := "projects/" + testProjectID + "/databases/(default)" 79 80 ti := &testutil.HeadersEnforcer{ 81 Checkers: []*testutil.HeaderChecker{ 82 testutil.XGoogClientHeaderChecker, 83 84 { 85 Key: "google-cloud-resource-prefix", 86 ValuesValidator: func(values ...string) error { 87 if len(values) == 0 { 88 return errors.New("expected non-blank header") 89 } 90 if values[0] != wantDBPath { 91 return fmt.Errorf("resource prefix mismatch; got %q want %q", values[0], wantDBPath) 92 } 93 return nil 94 }, 95 }, 96 }, 97 } 98 copts := append(ti.CallOptions(), option.WithTokenSource(ts)) 99 c, err := NewClient(ctx, testProjectID, copts...) 100 if err != nil { 101 log.Fatalf("NewClient: %v", err) 102 } 103 iClient = c 104 iColl = c.Collection(collectionIDs.New()) 105 refDoc := iColl.NewDoc() 106 integrationTestMap["ref"] = refDoc 107 wantIntegrationTestMap["ref"] = refDoc 108 integrationTestStruct.Ref = refDoc 109} 110 111func cleanupIntegrationTest() { 112 if iClient == nil { 113 return 114 } 115 // TODO(jba): delete everything in integrationColl. 116 iClient.Close() 117} 118 119// integrationClient should be called by integration tests to get a valid client. It will never 120// return nil. If integrationClient returns, an integration test can proceed without 121// further checks. 122func integrationClient(t *testing.T) *Client { 123 if testing.Short() { 124 t.Skip("Integration tests skipped in short mode") 125 } 126 if iClient == nil { 127 t.SkipNow() // log message printed in initIntegrationTest 128 } 129 return iClient 130} 131 132func integrationColl(t *testing.T) *CollectionRef { 133 _ = integrationClient(t) 134 return iColl 135} 136 137type integrationTestStructType struct { 138 Int int 139 Str string 140 Bool bool 141 Float float32 142 Null interface{} 143 Bytes []byte 144 Time time.Time 145 Geo, NilGeo *latlng.LatLng 146 Ref *DocumentRef 147} 148 149var ( 150 integrationTime = time.Date(2017, 3, 20, 1, 2, 3, 456789, time.UTC) 151 // Firestore times are accurate only to microseconds. 152 wantIntegrationTime = time.Date(2017, 3, 20, 1, 2, 3, 456000, time.UTC) 153 154 integrationGeo = &latlng.LatLng{Latitude: 30, Longitude: 70} 155 156 // Use this when writing a doc. 157 integrationTestMap = map[string]interface{}{ 158 "int": 1, 159 "int8": int8(2), 160 "int16": int16(3), 161 "int32": int32(4), 162 "int64": int64(5), 163 "uint8": uint8(6), 164 "uint16": uint16(7), 165 "uint32": uint32(8), 166 "str": "two", 167 "bool": true, 168 "float": 3.14, 169 "null": nil, 170 "bytes": []byte("bytes"), 171 "*": map[string]interface{}{"`": 4}, 172 "time": integrationTime, 173 "geo": integrationGeo, 174 "ref": nil, // populated by initIntegrationTest 175 } 176 177 // The returned data is slightly different. 178 wantIntegrationTestMap = map[string]interface{}{ 179 "int": int64(1), 180 "int8": int64(2), 181 "int16": int64(3), 182 "int32": int64(4), 183 "int64": int64(5), 184 "uint8": int64(6), 185 "uint16": int64(7), 186 "uint32": int64(8), 187 "str": "two", 188 "bool": true, 189 "float": 3.14, 190 "null": nil, 191 "bytes": []byte("bytes"), 192 "*": map[string]interface{}{"`": int64(4)}, 193 "time": wantIntegrationTime, 194 "geo": integrationGeo, 195 "ref": nil, // populated by initIntegrationTest 196 } 197 198 integrationTestStruct = integrationTestStructType{ 199 Int: 1, 200 Str: "two", 201 Bool: true, 202 Float: 3.14, 203 Null: nil, 204 Bytes: []byte("bytes"), 205 Time: integrationTime, 206 Geo: integrationGeo, 207 NilGeo: nil, 208 Ref: nil, // populated by initIntegrationTest 209 } 210) 211 212func TestIntegration_Create(t *testing.T) { 213 ctx := context.Background() 214 doc := integrationColl(t).NewDoc() 215 start := time.Now() 216 h := testHelper{t} 217 wr := h.mustCreate(doc, integrationTestMap) 218 end := time.Now() 219 checkTimeBetween(t, wr.UpdateTime, start, end) 220 _, err := doc.Create(ctx, integrationTestMap) 221 codeEq(t, "Create on a present doc", codes.AlreadyExists, err) 222 // OK to create an empty document. 223 _, err = integrationColl(t).NewDoc().Create(ctx, map[string]interface{}{}) 224 codeEq(t, "Create empty doc", codes.OK, err) 225} 226 227func TestIntegration_Get(t *testing.T) { 228 ctx := context.Background() 229 doc := integrationColl(t).NewDoc() 230 h := testHelper{t} 231 h.mustCreate(doc, integrationTestMap) 232 ds := h.mustGet(doc) 233 if ds.CreateTime != ds.UpdateTime { 234 t.Errorf("create time %s != update time %s", ds.CreateTime, ds.UpdateTime) 235 } 236 got := ds.Data() 237 if want := wantIntegrationTestMap; !testEqual(got, want) { 238 t.Errorf("got\n%v\nwant\n%v", pretty.Value(got), pretty.Value(want)) 239 } 240 241 doc = integrationColl(t).NewDoc() 242 empty := map[string]interface{}{} 243 h.mustCreate(doc, empty) 244 ds = h.mustGet(doc) 245 if ds.CreateTime != ds.UpdateTime { 246 t.Errorf("create time %s != update time %s", ds.CreateTime, ds.UpdateTime) 247 } 248 if got, want := ds.Data(), empty; !testEqual(got, want) { 249 t.Errorf("got\n%v\nwant\n%v", pretty.Value(got), pretty.Value(want)) 250 } 251 252 ds, err := integrationColl(t).NewDoc().Get(ctx) 253 codeEq(t, "Get on a missing doc", codes.NotFound, err) 254 if ds == nil || ds.Exists() { 255 t.Error("got nil or existing doc snapshot, want !ds.Exists") 256 } 257 if ds.ReadTime.IsZero() { 258 t.Error("got zero read time") 259 } 260} 261 262func TestIntegration_GetAll(t *testing.T) { 263 type getAll struct{ N int } 264 265 h := testHelper{t} 266 coll := integrationColl(t) 267 ctx := context.Background() 268 var docRefs []*DocumentRef 269 for i := 0; i < 5; i++ { 270 doc := coll.NewDoc() 271 docRefs = append(docRefs, doc) 272 if i != 3 { 273 h.mustCreate(doc, getAll{N: i}) 274 } 275 } 276 docSnapshots, err := iClient.GetAll(ctx, docRefs) 277 if err != nil { 278 t.Fatal(err) 279 } 280 if got, want := len(docSnapshots), len(docRefs); got != want { 281 t.Fatalf("got %d snapshots, want %d", got, want) 282 } 283 for i, ds := range docSnapshots { 284 if i == 3 { 285 if ds == nil || ds.Exists() { 286 t.Fatal("got nil or existing doc snapshot, want !ds.Exists") 287 } 288 err := ds.DataTo(nil) 289 codeEq(t, "DataTo on a missing doc", codes.NotFound, err) 290 } else { 291 var got getAll 292 if err := ds.DataTo(&got); err != nil { 293 t.Fatal(err) 294 } 295 want := getAll{N: i} 296 if got != want { 297 t.Errorf("%d: got %+v, want %+v", i, got, want) 298 } 299 } 300 if ds.ReadTime.IsZero() { 301 t.Errorf("%d: got zero read time", i) 302 } 303 } 304} 305 306func TestIntegration_Add(t *testing.T) { 307 start := time.Now() 308 _, wr, err := integrationColl(t).Add(context.Background(), integrationTestMap) 309 if err != nil { 310 t.Fatal(err) 311 } 312 end := time.Now() 313 checkTimeBetween(t, wr.UpdateTime, start, end) 314} 315 316func TestIntegration_Set(t *testing.T) { 317 coll := integrationColl(t) 318 h := testHelper{t} 319 ctx := context.Background() 320 321 // Set Should be able to create a new doc. 322 doc := coll.NewDoc() 323 wr1 := h.mustSet(doc, integrationTestMap) 324 // Calling Set on the doc completely replaces the contents. 325 // The update time should increase. 326 newData := map[string]interface{}{ 327 "str": "change", 328 "x": "1", 329 } 330 wr2 := h.mustSet(doc, newData) 331 if !wr1.UpdateTime.Before(wr2.UpdateTime) { 332 t.Errorf("update time did not increase: old=%s, new=%s", wr1.UpdateTime, wr2.UpdateTime) 333 } 334 ds := h.mustGet(doc) 335 if got := ds.Data(); !testEqual(got, newData) { 336 t.Errorf("got %v, want %v", got, newData) 337 } 338 339 newData = map[string]interface{}{ 340 "str": "1", 341 "x": "2", 342 "y": "3", 343 } 344 // SetOptions: 345 // Only fields mentioned in the Merge option will be changed. 346 // In this case, "str" will not be changed to "1". 347 wr3, err := doc.Set(ctx, newData, Merge([]string{"x"}, []string{"y"})) 348 if err != nil { 349 t.Fatal(err) 350 } 351 ds = h.mustGet(doc) 352 want := map[string]interface{}{ 353 "str": "change", 354 "x": "2", 355 "y": "3", 356 } 357 if got := ds.Data(); !testEqual(got, want) { 358 t.Errorf("got %v, want %v", got, want) 359 } 360 if !wr2.UpdateTime.Before(wr3.UpdateTime) { 361 t.Errorf("update time did not increase: old=%s, new=%s", wr2.UpdateTime, wr3.UpdateTime) 362 } 363 364 // Another way to change only x and y is to pass a map with only 365 // those keys, and use MergeAll. 366 wr4, err := doc.Set(ctx, map[string]interface{}{"x": "4", "y": "5"}, MergeAll) 367 if err != nil { 368 t.Fatal(err) 369 } 370 ds = h.mustGet(doc) 371 want = map[string]interface{}{ 372 "str": "change", 373 "x": "4", 374 "y": "5", 375 } 376 if got := ds.Data(); !testEqual(got, want) { 377 t.Errorf("got %v, want %v", got, want) 378 } 379 if !wr3.UpdateTime.Before(wr4.UpdateTime) { 380 t.Errorf("update time did not increase: old=%s, new=%s", wr3.UpdateTime, wr4.UpdateTime) 381 } 382 383 // use firestore.Delete to delete a field. 384 // TODO(deklerk): We should be able to use mustSet, but then we get a test error. We should investigate this. 385 _, err = doc.Set(ctx, map[string]interface{}{"str": Delete}, MergeAll) 386 if err != nil { 387 t.Fatal(err) 388 } 389 ds = h.mustGet(doc) 390 want = map[string]interface{}{ 391 "x": "4", 392 "y": "5", 393 } 394 if got := ds.Data(); !testEqual(got, want) { 395 t.Errorf("got %v, want %v", got, want) 396 } 397 398 // Writing an empty doc with MergeAll should create the doc. 399 doc2 := coll.NewDoc() 400 want = map[string]interface{}{} 401 h.mustSet(doc2, want, MergeAll) 402 ds = h.mustGet(doc2) 403 if got := ds.Data(); !testEqual(got, want) { 404 t.Errorf("got %v, want %v", got, want) 405 } 406} 407 408func TestIntegration_Delete(t *testing.T) { 409 ctx := context.Background() 410 doc := integrationColl(t).NewDoc() 411 h := testHelper{t} 412 h.mustCreate(doc, integrationTestMap) 413 h.mustDelete(doc) 414 // Confirm that doc doesn't exist. 415 if _, err := doc.Get(ctx); status.Code(err) != codes.NotFound { 416 t.Fatalf("got error <%v>, want NotFound", err) 417 } 418 419 er := func(_ *WriteResult, err error) error { return err } 420 421 codeEq(t, "Delete on a missing doc", codes.OK, 422 er(doc.Delete(ctx))) 423 // TODO(jba): confirm that the server should return InvalidArgument instead of 424 // FailedPrecondition. 425 wr := h.mustCreate(doc, integrationTestMap) 426 codeEq(t, "Delete with wrong LastUpdateTime", codes.FailedPrecondition, 427 er(doc.Delete(ctx, LastUpdateTime(wr.UpdateTime.Add(-time.Millisecond))))) 428 codeEq(t, "Delete with right LastUpdateTime", codes.OK, 429 er(doc.Delete(ctx, LastUpdateTime(wr.UpdateTime)))) 430} 431 432func TestIntegration_Update(t *testing.T) { 433 ctx := context.Background() 434 doc := integrationColl(t).NewDoc() 435 h := testHelper{t} 436 437 h.mustCreate(doc, integrationTestMap) 438 fpus := []Update{ 439 {Path: "bool", Value: false}, 440 {Path: "time", Value: 17}, 441 {FieldPath: []string{"*", "`"}, Value: 18}, 442 {Path: "null", Value: Delete}, 443 {Path: "noSuchField", Value: Delete}, // deleting a non-existent field is a no-op 444 } 445 wr := h.mustUpdate(doc, fpus) 446 ds := h.mustGet(doc) 447 got := ds.Data() 448 want := copyMap(wantIntegrationTestMap) 449 want["bool"] = false 450 want["time"] = int64(17) 451 want["*"] = map[string]interface{}{"`": int64(18)} 452 delete(want, "null") 453 if !testEqual(got, want) { 454 t.Errorf("got\n%#v\nwant\n%#v", got, want) 455 } 456 457 er := func(_ *WriteResult, err error) error { return err } 458 459 codeEq(t, "Update on missing doc", codes.NotFound, 460 er(integrationColl(t).NewDoc().Update(ctx, fpus))) 461 codeEq(t, "Update with wrong LastUpdateTime", codes.FailedPrecondition, 462 er(doc.Update(ctx, fpus, LastUpdateTime(wr.UpdateTime.Add(-time.Millisecond))))) 463 codeEq(t, "Update with right LastUpdateTime", codes.OK, 464 er(doc.Update(ctx, fpus, LastUpdateTime(wr.UpdateTime)))) 465} 466 467func TestIntegration_Collections(t *testing.T) { 468 ctx := context.Background() 469 c := integrationClient(t) 470 h := testHelper{t} 471 got, err := c.Collections(ctx).GetAll() 472 if err != nil { 473 t.Fatal(err) 474 } 475 // There should be at least one collection. 476 if len(got) == 0 { 477 t.Error("got 0 top-level collections, want at least one") 478 } 479 480 doc := integrationColl(t).NewDoc() 481 got, err = doc.Collections(ctx).GetAll() 482 if err != nil { 483 t.Fatal(err) 484 } 485 if len(got) != 0 { 486 t.Errorf("got %d collections, want 0", len(got)) 487 } 488 var want []*CollectionRef 489 for i := 0; i < 3; i++ { 490 id := collectionIDs.New() 491 cr := doc.Collection(id) 492 want = append(want, cr) 493 h.mustCreate(cr.NewDoc(), integrationTestMap) 494 } 495 got, err = doc.Collections(ctx).GetAll() 496 if err != nil { 497 t.Fatal(err) 498 } 499 if !testEqual(got, want) { 500 t.Errorf("got\n%#v\nwant\n%#v", got, want) 501 } 502} 503 504func TestIntegration_ServerTimestamp(t *testing.T) { 505 type S struct { 506 A int 507 B time.Time 508 C time.Time `firestore:"C.C,serverTimestamp"` 509 D map[string]interface{} 510 E time.Time `firestore:",omitempty,serverTimestamp"` 511 } 512 data := S{ 513 A: 1, 514 B: aTime, 515 // C is unset, so will get the server timestamp. 516 D: map[string]interface{}{"x": ServerTimestamp}, 517 // E is unset, so will get the server timestamp. 518 } 519 h := testHelper{t} 520 doc := integrationColl(t).NewDoc() 521 // Bound times of the RPC, with some slack for clock skew. 522 start := time.Now() 523 h.mustCreate(doc, data) 524 end := time.Now() 525 ds := h.mustGet(doc) 526 var got S 527 if err := ds.DataTo(&got); err != nil { 528 t.Fatal(err) 529 } 530 if !testEqual(got.B, aTime) { 531 t.Errorf("B: got %s, want %s", got.B, aTime) 532 } 533 checkTimeBetween(t, got.C, start, end) 534 if g, w := got.D["x"], got.C; !testEqual(g, w) { 535 t.Errorf(`D["x"] = %s, want equal to C (%s)`, g, w) 536 } 537 if g, w := got.E, got.C; !testEqual(g, w) { 538 t.Errorf(`E = %s, want equal to C (%s)`, g, w) 539 } 540} 541 542func TestIntegration_MergeServerTimestamp(t *testing.T) { 543 doc := integrationColl(t).NewDoc() 544 h := testHelper{t} 545 546 // Create a doc with an ordinary field "a" and a ServerTimestamp field "b". 547 h.mustSet(doc, map[string]interface{}{"a": 1, "b": ServerTimestamp}) 548 docSnap := h.mustGet(doc) 549 data1 := docSnap.Data() 550 // Merge with a document with a different value of "a". However, 551 // specify only "b" in the list of merge fields. 552 h.mustSet(doc, map[string]interface{}{"a": 2, "b": ServerTimestamp}, Merge([]string{"b"})) 553 // The result should leave "a" unchanged, while "b" is updated. 554 docSnap = h.mustGet(doc) 555 data2 := docSnap.Data() 556 if got, want := data2["a"], data1["a"]; got != want { 557 t.Errorf("got %v, want %v", got, want) 558 } 559 t1 := data1["b"].(time.Time) 560 t2 := data2["b"].(time.Time) 561 if !t1.Before(t2) { 562 t.Errorf("got t1=%s, t2=%s; want t1 before t2", t1, t2) 563 } 564} 565 566func TestIntegration_MergeNestedServerTimestamp(t *testing.T) { 567 doc := integrationColl(t).NewDoc() 568 h := testHelper{t} 569 570 // Create a doc with an ordinary field "a" a ServerTimestamp field "b", 571 // and a second ServerTimestamp field "c.d". 572 h.mustSet(doc, map[string]interface{}{ 573 "a": 1, 574 "b": ServerTimestamp, 575 "c": map[string]interface{}{"d": ServerTimestamp}, 576 }) 577 data1 := h.mustGet(doc).Data() 578 // Merge with a document with a different value of "a". However, 579 // specify only "c.d" in the list of merge fields. 580 h.mustSet(doc, map[string]interface{}{ 581 "a": 2, 582 "b": ServerTimestamp, 583 "c": map[string]interface{}{"d": ServerTimestamp}, 584 }, Merge([]string{"c", "d"})) 585 // The result should leave "a" and "b" unchanged, while "c.d" is updated. 586 data2 := h.mustGet(doc).Data() 587 if got, want := data2["a"], data1["a"]; got != want { 588 t.Errorf("a: got %v, want %v", got, want) 589 } 590 want := data1["b"].(time.Time) 591 got := data2["b"].(time.Time) 592 if !got.Equal(want) { 593 t.Errorf("b: got %s, want %s", got, want) 594 } 595 t1 := data1["c"].(map[string]interface{})["d"].(time.Time) 596 t2 := data2["c"].(map[string]interface{})["d"].(time.Time) 597 if !t1.Before(t2) { 598 t.Errorf("got t1=%s, t2=%s; want t1 before t2", t1, t2) 599 } 600} 601 602func TestIntegration_WriteBatch(t *testing.T) { 603 ctx := context.Background() 604 b := integrationClient(t).Batch() 605 h := testHelper{t} 606 doc1 := iColl.NewDoc() 607 doc2 := iColl.NewDoc() 608 b.Create(doc1, integrationTestMap) 609 b.Set(doc2, integrationTestMap) 610 b.Update(doc1, []Update{{Path: "bool", Value: false}}) 611 b.Update(doc1, []Update{{Path: "str", Value: Delete}}) 612 613 wrs, err := b.Commit(ctx) 614 if err != nil { 615 t.Fatal(err) 616 } 617 if got, want := len(wrs), 4; got != want { 618 t.Fatalf("got %d WriteResults, want %d", got, want) 619 } 620 got1 := h.mustGet(doc1).Data() 621 want := copyMap(wantIntegrationTestMap) 622 want["bool"] = false 623 delete(want, "str") 624 if !testEqual(got1, want) { 625 t.Errorf("got\n%#v\nwant\n%#v", got1, want) 626 } 627 got2 := h.mustGet(doc2).Data() 628 if !testEqual(got2, wantIntegrationTestMap) { 629 t.Errorf("got\n%#v\nwant\n%#v", got2, wantIntegrationTestMap) 630 } 631 // TODO(jba): test two updates to the same document when it is supported. 632 // TODO(jba): test verify when it is supported. 633} 634 635func TestIntegration_Query(t *testing.T) { 636 ctx := context.Background() 637 coll := integrationColl(t) 638 h := testHelper{t} 639 var wants []map[string]interface{} 640 for i := 0; i < 3; i++ { 641 doc := coll.NewDoc() 642 // To support running this test in parallel with the others, use a field name 643 // that we don't use anywhere else. 644 h.mustCreate(doc, map[string]interface{}{"q": i, "x": 1}) 645 wants = append(wants, map[string]interface{}{"q": int64(i)}) 646 } 647 q := coll.Select("q").OrderBy("q", Asc) 648 for i, test := range []struct { 649 q Query 650 want []map[string]interface{} 651 }{ 652 {q, wants}, 653 {q.Where("q", ">", 1), wants[2:]}, 654 {q.WherePath([]string{"q"}, ">", 1), wants[2:]}, 655 {q.Offset(1).Limit(1), wants[1:2]}, 656 {q.StartAt(1), wants[1:]}, 657 {q.StartAfter(1), wants[2:]}, 658 {q.EndAt(1), wants[:2]}, 659 {q.EndBefore(1), wants[:1]}, 660 } { 661 gotDocs, err := test.q.Documents(ctx).GetAll() 662 if err != nil { 663 t.Errorf("#%d: %+v: %v", i, test.q, err) 664 continue 665 } 666 if len(gotDocs) != len(test.want) { 667 t.Errorf("#%d: %+v: got %d docs, want %d", i, test.q, len(gotDocs), len(test.want)) 668 continue 669 } 670 for j, g := range gotDocs { 671 if got, want := g.Data(), test.want[j]; !testEqual(got, want) { 672 t.Errorf("#%d: %+v, #%d: got\n%+v\nwant\n%+v", i, test.q, j, got, want) 673 } 674 } 675 } 676 _, err := coll.Select("q").Where("x", "==", 1).OrderBy("q", Asc).Documents(ctx).GetAll() 677 codeEq(t, "Where and OrderBy on different fields without an index", codes.FailedPrecondition, err) 678 679 // Using the collection itself as the query should return the full documents. 680 allDocs, err := coll.Documents(ctx).GetAll() 681 if err != nil { 682 t.Fatal(err) 683 } 684 seen := map[int64]bool{} // "q" values we see 685 for _, d := range allDocs { 686 data := d.Data() 687 q, ok := data["q"] 688 if !ok { 689 // A document from another test. 690 continue 691 } 692 if seen[q.(int64)] { 693 t.Errorf("%v: duplicate doc", data) 694 } 695 seen[q.(int64)] = true 696 if data["x"] != int64(1) { 697 t.Errorf("%v: wrong or missing 'x'", data) 698 } 699 if len(data) != 2 { 700 t.Errorf("%v: want two keys", data) 701 } 702 } 703 if got, want := len(seen), len(wants); got != want { 704 t.Errorf("got %d docs with 'q', want %d", len(seen), len(wants)) 705 } 706} 707 708// Test unary filters. 709func TestIntegration_QueryUnary(t *testing.T) { 710 ctx := context.Background() 711 coll := integrationColl(t) 712 h := testHelper{t} 713 h.mustCreate(coll.NewDoc(), map[string]interface{}{"x": 2, "q": "a"}) 714 h.mustCreate(coll.NewDoc(), map[string]interface{}{"x": 2, "q": nil}) 715 h.mustCreate(coll.NewDoc(), map[string]interface{}{"x": 2, "q": math.NaN()}) 716 wantNull := map[string]interface{}{"q": nil} 717 wantNaN := map[string]interface{}{"q": math.NaN()} 718 719 base := coll.Select("q").Where("x", "==", 2) 720 for _, test := range []struct { 721 q Query 722 want map[string]interface{} 723 }{ 724 {base.Where("q", "==", nil), wantNull}, 725 {base.Where("q", "==", math.NaN()), wantNaN}, 726 } { 727 got, err := test.q.Documents(ctx).GetAll() 728 if err != nil { 729 t.Fatal(err) 730 } 731 if len(got) != 1 { 732 t.Errorf("got %d responses, want 1", len(got)) 733 continue 734 } 735 if g, w := got[0].Data(), test.want; !testEqual(g, w) { 736 t.Errorf("%v: got %v, want %v", test.q, g, w) 737 } 738 } 739} 740 741// Test the special DocumentID field in queries. 742func TestIntegration_QueryName(t *testing.T) { 743 ctx := context.Background() 744 h := testHelper{t} 745 746 checkIDs := func(q Query, wantIDs []string) { 747 gots, err := q.Documents(ctx).GetAll() 748 if err != nil { 749 t.Fatal(err) 750 } 751 if len(gots) != len(wantIDs) { 752 t.Fatalf("got %d, want %d", len(gots), len(wantIDs)) 753 } 754 for i, g := range gots { 755 if got, want := g.Ref.ID, wantIDs[i]; got != want { 756 t.Errorf("#%d: got %s, want %s", i, got, want) 757 } 758 } 759 } 760 761 coll := integrationColl(t) 762 var wantIDs []string 763 for i := 0; i < 3; i++ { 764 doc := coll.NewDoc() 765 h.mustCreate(doc, map[string]interface{}{"nm": 1}) 766 wantIDs = append(wantIDs, doc.ID) 767 } 768 sort.Strings(wantIDs) 769 q := coll.Where("nm", "==", 1).OrderBy(DocumentID, Asc) 770 checkIDs(q, wantIDs) 771 772 // Empty Select. 773 q = coll.Select().Where("nm", "==", 1).OrderBy(DocumentID, Asc) 774 checkIDs(q, wantIDs) 775 776 // Test cursors with __name__. 777 checkIDs(q.StartAt(wantIDs[1]), wantIDs[1:]) 778 checkIDs(q.EndAt(wantIDs[1]), wantIDs[:2]) 779} 780 781func TestIntegration_QueryNested(t *testing.T) { 782 ctx := context.Background() 783 h := testHelper{t} 784 coll1 := integrationColl(t) 785 doc1 := coll1.NewDoc() 786 coll2 := doc1.Collection(collectionIDs.New()) 787 doc2 := coll2.NewDoc() 788 wantData := map[string]interface{}{"x": int64(1)} 789 h.mustCreate(doc2, wantData) 790 q := coll2.Select("x") 791 got, err := q.Documents(ctx).GetAll() 792 if err != nil { 793 t.Fatal(err) 794 } 795 if len(got) != 1 { 796 t.Fatalf("got %d docs, want 1", len(got)) 797 } 798 if gotData := got[0].Data(); !testEqual(gotData, wantData) { 799 t.Errorf("got\n%+v\nwant\n%+v", gotData, wantData) 800 } 801} 802 803func TestIntegration_RunTransaction(t *testing.T) { 804 ctx := context.Background() 805 h := testHelper{t} 806 807 type Player struct { 808 Name string 809 Score int 810 Star bool `firestore:"*"` 811 } 812 813 pat := Player{Name: "Pat", Score: 3, Star: false} 814 client := integrationClient(t) 815 patDoc := iColl.Doc("pat") 816 var anError error 817 incPat := func(_ context.Context, tx *Transaction) error { 818 doc, err := tx.Get(patDoc) 819 if err != nil { 820 return err 821 } 822 score, err := doc.DataAt("Score") 823 if err != nil { 824 return err 825 } 826 // Since the Star field is called "*", we must use DataAtPath to get it. 827 star, err := doc.DataAtPath([]string{"*"}) 828 if err != nil { 829 return err 830 } 831 err = tx.Update(patDoc, []Update{{Path: "Score", Value: int(score.(int64) + 7)}}) 832 if err != nil { 833 return err 834 } 835 // Since the Star field is called "*", we must use Update to change it. 836 err = tx.Update(patDoc, 837 []Update{{FieldPath: []string{"*"}, Value: !star.(bool)}}) 838 if err != nil { 839 return err 840 } 841 return anError 842 } 843 844 h.mustCreate(patDoc, pat) 845 err := client.RunTransaction(ctx, incPat) 846 if err != nil { 847 t.Fatal(err) 848 } 849 ds := h.mustGet(patDoc) 850 var got Player 851 if err := ds.DataTo(&got); err != nil { 852 t.Fatal(err) 853 } 854 want := Player{Name: "Pat", Score: 10, Star: true} 855 if got != want { 856 t.Errorf("got %+v, want %+v", got, want) 857 } 858 859 // Function returns error, so transaction is rolled back and no writes happen. 860 anError = errors.New("bad") 861 err = client.RunTransaction(ctx, incPat) 862 if err != anError { 863 t.Fatalf("got %v, want %v", err, anError) 864 } 865 if err := ds.DataTo(&got); err != nil { 866 t.Fatal(err) 867 } 868 // want is same as before. 869 if got != want { 870 t.Errorf("got %+v, want %+v", got, want) 871 } 872} 873 874func TestIntegration_TransactionGetAll(t *testing.T) { 875 ctx := context.Background() 876 h := testHelper{t} 877 type Player struct { 878 Name string 879 Score int 880 } 881 lee := Player{Name: "Lee", Score: 3} 882 sam := Player{Name: "Sam", Score: 1} 883 client := integrationClient(t) 884 leeDoc := iColl.Doc("lee") 885 samDoc := iColl.Doc("sam") 886 h.mustCreate(leeDoc, lee) 887 h.mustCreate(samDoc, sam) 888 889 err := client.RunTransaction(ctx, func(_ context.Context, tx *Transaction) error { 890 docs, err := tx.GetAll([]*DocumentRef{samDoc, leeDoc}) 891 if err != nil { 892 return err 893 } 894 for i, want := range []Player{sam, lee} { 895 var got Player 896 if err := docs[i].DataTo(&got); err != nil { 897 return err 898 } 899 if !testutil.Equal(got, want) { 900 return fmt.Errorf("got %+v, want %+v", got, want) 901 } 902 } 903 return nil 904 }) 905 if err != nil { 906 t.Fatal(err) 907 } 908} 909 910func TestIntegration_WatchDocument(t *testing.T) { 911 coll := integrationColl(t) 912 ctx := context.Background() 913 h := testHelper{t} 914 doc := coll.NewDoc() 915 it := doc.Snapshots(ctx) 916 defer it.Stop() 917 918 next := func() *DocumentSnapshot { 919 snap, err := it.Next() 920 if err != nil { 921 t.Fatal(err) 922 } 923 return snap 924 } 925 926 snap := next() 927 if snap.Exists() { 928 t.Fatal("snapshot exists; it should not") 929 } 930 want := map[string]interface{}{"a": int64(1), "b": "two"} 931 h.mustCreate(doc, want) 932 snap = next() 933 if got := snap.Data(); !testutil.Equal(got, want) { 934 t.Fatalf("got %v, want %v", got, want) 935 } 936 937 h.mustUpdate(doc, []Update{{Path: "a", Value: int64(2)}}) 938 want["a"] = int64(2) 939 snap = next() 940 if got := snap.Data(); !testutil.Equal(got, want) { 941 t.Fatalf("got %v, want %v", got, want) 942 } 943 944 h.mustDelete(doc) 945 snap = next() 946 if snap.Exists() { 947 t.Fatal("snapshot exists; it should not") 948 } 949 950 h.mustCreate(doc, want) 951 snap = next() 952 if got := snap.Data(); !testutil.Equal(got, want) { 953 t.Fatalf("got %v, want %v", got, want) 954 } 955} 956 957func TestIntegration_ArrayUnion_Create(t *testing.T) { 958 path := "somePath" 959 data := map[string]interface{}{ 960 path: ArrayUnion("a", "b"), 961 } 962 963 doc := integrationColl(t).NewDoc() 964 h := testHelper{t} 965 h.mustCreate(doc, data) 966 ds := h.mustGet(doc) 967 var gotMap map[string][]string 968 if err := ds.DataTo(&gotMap); err != nil { 969 t.Fatal(err) 970 } 971 if _, ok := gotMap[path]; !ok { 972 t.Fatalf("expected a %v key in data, got %v", path, gotMap) 973 } 974 975 want := []string{"a", "b"} 976 for i, v := range gotMap[path] { 977 if v != want[i] { 978 t.Fatalf("got\n%#v\nwant\n%#v", gotMap[path], want) 979 } 980 } 981} 982 983func TestIntegration_ArrayUnion_Update(t *testing.T) { 984 doc := integrationColl(t).NewDoc() 985 h := testHelper{t} 986 path := "somePath" 987 988 h.mustCreate(doc, map[string]interface{}{ 989 path: []string{"a", "b"}, 990 }) 991 fpus := []Update{ 992 { 993 Path: path, 994 Value: ArrayUnion("this should be added"), 995 }, 996 } 997 h.mustUpdate(doc, fpus) 998 ds := h.mustGet(doc) 999 var gotMap map[string][]string 1000 if err := ds.DataTo(&gotMap); err != nil { 1001 t.Fatal(err) 1002 } 1003 if _, ok := gotMap[path]; !ok { 1004 t.Fatalf("expected a %v key in data, got %v", path, gotMap) 1005 } 1006 1007 want := []string{"a", "b", "this should be added"} 1008 for i, v := range gotMap[path] { 1009 if v != want[i] { 1010 t.Fatalf("got\n%#v\nwant\n%#v", gotMap[path], want) 1011 } 1012 } 1013} 1014 1015func TestIntegration_ArrayUnion_Set(t *testing.T) { 1016 coll := integrationColl(t) 1017 h := testHelper{t} 1018 path := "somePath" 1019 1020 doc := coll.NewDoc() 1021 newData := map[string]interface{}{ 1022 path: ArrayUnion("a", "b"), 1023 } 1024 h.mustSet(doc, newData) 1025 ds := h.mustGet(doc) 1026 var gotMap map[string][]string 1027 if err := ds.DataTo(&gotMap); err != nil { 1028 t.Fatal(err) 1029 } 1030 if _, ok := gotMap[path]; !ok { 1031 t.Fatalf("expected a %v key in data, got %v", path, gotMap) 1032 } 1033 1034 want := []string{"a", "b"} 1035 for i, v := range gotMap[path] { 1036 if v != want[i] { 1037 t.Fatalf("got\n%#v\nwant\n%#v", gotMap[path], want) 1038 } 1039 } 1040} 1041 1042func TestIntegration_ArrayRemove_Create(t *testing.T) { 1043 doc := integrationColl(t).NewDoc() 1044 h := testHelper{t} 1045 path := "somePath" 1046 1047 h.mustCreate(doc, map[string]interface{}{ 1048 path: ArrayRemove("a", "b"), 1049 }) 1050 1051 ds := h.mustGet(doc) 1052 var gotMap map[string][]string 1053 if err := ds.DataTo(&gotMap); err != nil { 1054 t.Fatal(err) 1055 } 1056 if _, ok := gotMap[path]; !ok { 1057 t.Fatalf("expected a %v key in data, got %v", path, gotMap) 1058 } 1059 1060 // A create with arrayRemove results in an empty array. 1061 want := []string(nil) 1062 if !testEqual(gotMap[path], want) { 1063 t.Fatalf("got\n%#v\nwant\n%#v", gotMap[path], want) 1064 } 1065} 1066 1067func TestIntegration_ArrayRemove_Update(t *testing.T) { 1068 doc := integrationColl(t).NewDoc() 1069 h := testHelper{t} 1070 path := "somePath" 1071 1072 h.mustCreate(doc, map[string]interface{}{ 1073 path: []string{"a", "this should be removed", "c"}, 1074 }) 1075 fpus := []Update{ 1076 { 1077 Path: path, 1078 Value: ArrayRemove("this should be removed"), 1079 }, 1080 } 1081 h.mustUpdate(doc, fpus) 1082 ds := h.mustGet(doc) 1083 var gotMap map[string][]string 1084 if err := ds.DataTo(&gotMap); err != nil { 1085 t.Fatal(err) 1086 } 1087 if _, ok := gotMap[path]; !ok { 1088 t.Fatalf("expected a %v key in data, got %v", path, gotMap) 1089 } 1090 1091 want := []string{"a", "c"} 1092 for i, v := range gotMap[path] { 1093 if v != want[i] { 1094 t.Fatalf("got\n%#v\nwant\n%#v", gotMap[path], want) 1095 } 1096 } 1097} 1098 1099func TestIntegration_ArrayRemove_Set(t *testing.T) { 1100 coll := integrationColl(t) 1101 h := testHelper{t} 1102 path := "somePath" 1103 1104 doc := coll.NewDoc() 1105 newData := map[string]interface{}{ 1106 path: ArrayRemove("a", "b"), 1107 } 1108 h.mustSet(doc, newData) 1109 ds := h.mustGet(doc) 1110 var gotMap map[string][]string 1111 if err := ds.DataTo(&gotMap); err != nil { 1112 t.Fatal(err) 1113 } 1114 if _, ok := gotMap[path]; !ok { 1115 t.Fatalf("expected a %v key in data, got %v", path, gotMap) 1116 } 1117 1118 want := []string(nil) 1119 if !testEqual(gotMap[path], want) { 1120 t.Fatalf("got\n%#v\nwant\n%#v", gotMap[path], want) 1121 } 1122} 1123 1124func TestIntegration_Increment_Create(t *testing.T) { 1125 doc := integrationColl(t).NewDoc() 1126 h := testHelper{t} 1127 path := "somePath" 1128 want := 7 1129 1130 h.mustCreate(doc, map[string]interface{}{ 1131 path: Increment(want), 1132 }) 1133 1134 ds := h.mustGet(doc) 1135 var gotMap map[string]int 1136 if err := ds.DataTo(&gotMap); err != nil { 1137 t.Fatal(err) 1138 } 1139 if _, ok := gotMap[path]; !ok { 1140 t.Fatalf("expected a %v key in data, got %v", path, gotMap) 1141 } 1142 1143 if gotMap[path] != want { 1144 t.Fatalf("want %d, got %d", want, gotMap[path]) 1145 } 1146} 1147 1148// Also checks that all appropriate types are supported. 1149func TestIntegration_Increment_Update(t *testing.T) { 1150 type MyInt = int // Test a custom type. 1151 for _, tc := range []struct { 1152 // All three should be same type. 1153 start interface{} 1154 inc interface{} 1155 want interface{} 1156 1157 wantErr bool 1158 }{ 1159 {start: int(7), inc: int(4), want: int(11)}, 1160 {start: int8(7), inc: int8(4), want: int8(11)}, 1161 {start: int16(7), inc: int16(4), want: int16(11)}, 1162 {start: int32(7), inc: int32(4), want: int32(11)}, 1163 {start: int64(7), inc: int64(4), want: int64(11)}, 1164 {start: uint8(7), inc: uint8(4), want: uint8(11)}, 1165 {start: uint16(7), inc: uint16(4), want: uint16(11)}, 1166 {start: uint32(7), inc: uint32(4), want: uint32(11)}, 1167 {start: float32(7.7), inc: float32(4.1), want: float32(11.8)}, 1168 {start: float64(7.7), inc: float64(4.1), want: float64(11.8)}, 1169 {start: MyInt(7), inc: MyInt(4), want: MyInt(11)}, 1170 {start: 7, inc: "strings are not allowed", wantErr: true}, 1171 {start: 7, inc: uint(3), wantErr: true}, 1172 {start: 7, inc: uint64(3), wantErr: true}, 1173 } { 1174 typeStr := reflect.TypeOf(tc.inc).String() 1175 t.Run(typeStr, func(t *testing.T) { 1176 doc := integrationColl(t).NewDoc() 1177 h := testHelper{t} 1178 path := "somePath" 1179 1180 h.mustCreate(doc, map[string]interface{}{ 1181 path: tc.start, 1182 }) 1183 fpus := []Update{ 1184 { 1185 Path: path, 1186 Value: Increment(tc.inc), 1187 }, 1188 } 1189 _, err := doc.Update(context.Background(), fpus) 1190 if err != nil { 1191 if tc.wantErr { 1192 return 1193 } 1194 h.t.Fatalf("%s: updating: %v", loc(), err) 1195 } 1196 ds := h.mustGet(doc) 1197 var gotMap map[string]interface{} 1198 if err := ds.DataTo(&gotMap); err != nil { 1199 t.Fatal(err) 1200 } 1201 1202 switch tc.want.(type) { 1203 case int, int8, int16, int32, int64: 1204 if _, ok := gotMap[path]; !ok { 1205 t.Fatalf("expected a %v key in data, got %v", path, gotMap) 1206 } 1207 if got, want := reflect.ValueOf(gotMap[path]).Int(), reflect.ValueOf(tc.want).Int(); got != want { 1208 t.Fatalf("want %v, got %v", want, got) 1209 } 1210 case uint8, uint16, uint32: 1211 if _, ok := gotMap[path]; !ok { 1212 t.Fatalf("expected a %v key in data, got %v", path, gotMap) 1213 } 1214 if got, want := uint64(reflect.ValueOf(gotMap[path]).Int()), reflect.ValueOf(tc.want).Uint(); got != want { 1215 t.Fatalf("want %v, got %v", want, got) 1216 } 1217 case float32, float64: 1218 if _, ok := gotMap[path]; !ok { 1219 t.Fatalf("expected a %v key in data, got %v", path, gotMap) 1220 } 1221 const precision = 1e-6 // Floats are never precisely comparable. 1222 if got, want := reflect.ValueOf(gotMap[path]).Float(), reflect.ValueOf(tc.want).Float(); math.Abs(got-want) > precision { 1223 t.Fatalf("want %v, got %v", want, got) 1224 } 1225 default: 1226 // Either some unsupported type was added without specifying 1227 // wantErr, or a supported type needs to be added to this 1228 // switch statement. 1229 t.Fatalf("unsupported type %T", tc.want) 1230 } 1231 }) 1232 } 1233} 1234 1235func TestIntegration_Increment_Set(t *testing.T) { 1236 coll := integrationColl(t) 1237 h := testHelper{t} 1238 path := "somePath" 1239 want := 9 1240 1241 doc := coll.NewDoc() 1242 newData := map[string]interface{}{ 1243 path: Increment(want), 1244 } 1245 h.mustSet(doc, newData) 1246 ds := h.mustGet(doc) 1247 var gotMap map[string]int 1248 if err := ds.DataTo(&gotMap); err != nil { 1249 t.Fatal(err) 1250 } 1251 if _, ok := gotMap[path]; !ok { 1252 t.Fatalf("expected a %v key in data, got %v", path, gotMap) 1253 } 1254 1255 if gotMap[path] != want { 1256 t.Fatalf("want %d, got %d", want, gotMap[path]) 1257 } 1258} 1259 1260type imap map[string]interface{} 1261 1262func TestIntegration_WatchQuery(t *testing.T) { 1263 ctx := context.Background() 1264 coll := integrationColl(t) 1265 h := testHelper{t} 1266 1267 q := coll.Where("e", ">", 1).OrderBy("e", Asc) 1268 it := q.Snapshots(ctx) 1269 defer it.Stop() 1270 1271 next := func() ([]*DocumentSnapshot, []DocumentChange) { 1272 qsnap, err := it.Next() 1273 if err != nil { 1274 t.Fatal(err) 1275 } 1276 if qsnap.ReadTime.IsZero() { 1277 t.Fatal("zero time") 1278 } 1279 ds, err := qsnap.Documents.GetAll() 1280 if err != nil { 1281 t.Fatal(err) 1282 } 1283 if qsnap.Size != len(ds) { 1284 t.Fatalf("Size=%d but we have %d docs", qsnap.Size, len(ds)) 1285 } 1286 return ds, qsnap.Changes 1287 } 1288 1289 copts := append([]cmp.Option{cmpopts.IgnoreFields(DocumentSnapshot{}, "ReadTime")}, cmpOpts...) 1290 check := func(msg string, wantd []*DocumentSnapshot, wantc []DocumentChange) { 1291 gotd, gotc := next() 1292 if diff := testutil.Diff(gotd, wantd, copts...); diff != "" { 1293 t.Errorf("%s: %s", msg, diff) 1294 } 1295 if diff := testutil.Diff(gotc, wantc, copts...); diff != "" { 1296 t.Errorf("%s: %s", msg, diff) 1297 } 1298 } 1299 1300 check("initial", nil, nil) 1301 doc1 := coll.NewDoc() 1302 h.mustCreate(doc1, imap{"e": int64(2), "b": "two"}) 1303 wds := h.mustGet(doc1) 1304 check("one", 1305 []*DocumentSnapshot{wds}, 1306 []DocumentChange{{Kind: DocumentAdded, Doc: wds, OldIndex: -1, NewIndex: 0}}) 1307 1308 // Add a doc that does not match. We won't see a snapshot for this. 1309 doc2 := coll.NewDoc() 1310 h.mustCreate(doc2, imap{"e": int64(1)}) 1311 1312 // Update the first doc. We should see the change. We won't see doc2. 1313 h.mustUpdate(doc1, []Update{{Path: "e", Value: int64(3)}}) 1314 wds = h.mustGet(doc1) 1315 check("update", 1316 []*DocumentSnapshot{wds}, 1317 []DocumentChange{{Kind: DocumentModified, Doc: wds, OldIndex: 0, NewIndex: 0}}) 1318 1319 // Now update doc so that it is not in the query. We should see a snapshot with no docs. 1320 h.mustUpdate(doc1, []Update{{Path: "e", Value: int64(0)}}) 1321 check("update2", nil, []DocumentChange{{Kind: DocumentRemoved, Doc: wds, OldIndex: 0, NewIndex: -1}}) 1322 1323 // Add two docs out of order. We should see them in order. 1324 doc3 := coll.NewDoc() 1325 doc4 := coll.NewDoc() 1326 want3 := imap{"e": int64(5)} 1327 want4 := imap{"e": int64(4)} 1328 h.mustCreate(doc3, want3) 1329 h.mustCreate(doc4, want4) 1330 wds4 := h.mustGet(doc4) 1331 wds3 := h.mustGet(doc3) 1332 check("two#1", 1333 []*DocumentSnapshot{wds3}, 1334 []DocumentChange{{Kind: DocumentAdded, Doc: wds3, OldIndex: -1, NewIndex: 0}}) 1335 check("two#2", 1336 []*DocumentSnapshot{wds4, wds3}, 1337 []DocumentChange{{Kind: DocumentAdded, Doc: wds4, OldIndex: -1, NewIndex: 0}}) 1338 // Delete a doc. 1339 h.mustDelete(doc4) 1340 check("after del", []*DocumentSnapshot{wds3}, []DocumentChange{{Kind: DocumentRemoved, Doc: wds4, OldIndex: 0, NewIndex: -1}}) 1341} 1342 1343func TestIntegration_WatchQueryCancel(t *testing.T) { 1344 ctx := context.Background() 1345 coll := integrationColl(t) 1346 1347 q := coll.Where("e", ">", 1).OrderBy("e", Asc) 1348 ctx, cancel := context.WithCancel(ctx) 1349 it := q.Snapshots(ctx) 1350 defer it.Stop() 1351 1352 // First call opens the stream. 1353 _, err := it.Next() 1354 if err != nil { 1355 t.Fatal(err) 1356 } 1357 cancel() 1358 _, err = it.Next() 1359 codeEq(t, "after cancel", codes.Canceled, err) 1360} 1361 1362func TestIntegration_MissingDocs(t *testing.T) { 1363 ctx := context.Background() 1364 h := testHelper{t} 1365 client := integrationClient(t) 1366 coll := client.Collection(collectionIDs.New()) 1367 dr1 := coll.NewDoc() 1368 dr2 := coll.NewDoc() 1369 dr3 := dr2.Collection("sub").NewDoc() 1370 h.mustCreate(dr1, integrationTestMap) 1371 defer h.mustDelete(dr1) 1372 h.mustCreate(dr3, integrationTestMap) 1373 defer h.mustDelete(dr3) 1374 1375 // dr1 is a document in coll. dr2 was never created, but there are documents in 1376 // its sub-collections. It is "missing". 1377 // The Collection.DocumentRefs method includes missing document refs. 1378 want := []string{dr1.Path, dr2.Path} 1379 drs, err := coll.DocumentRefs(ctx).GetAll() 1380 if err != nil { 1381 t.Fatal(err) 1382 } 1383 var got []string 1384 for _, dr := range drs { 1385 got = append(got, dr.Path) 1386 } 1387 sort.Strings(want) 1388 sort.Strings(got) 1389 if !testutil.Equal(got, want) { 1390 t.Errorf("got %v, want %v", got, want) 1391 } 1392} 1393 1394func TestIntegration_CollectionGroupQueries(t *testing.T) { 1395 shouldBeFoundID := collectionIDs.New() 1396 shouldNotBeFoundID := collectionIDs.New() 1397 1398 ctx := context.Background() 1399 h := testHelper{t} 1400 client := integrationClient(t) 1401 cr1 := client.Collection(shouldBeFoundID) 1402 dr1 := cr1.Doc("should-be-found-1") 1403 h.mustCreate(dr1, map[string]string{"some-key": "should-be-found"}) 1404 defer h.mustDelete(dr1) 1405 1406 dr1.Collection(shouldBeFoundID) 1407 dr2 := cr1.Doc("should-be-found-2") 1408 h.mustCreate(dr2, map[string]string{"some-key": "should-be-found"}) 1409 defer h.mustDelete(dr2) 1410 1411 cr3 := client.Collection(shouldNotBeFoundID) 1412 dr3 := cr3.Doc("should-not-be-found") 1413 h.mustCreate(dr3, map[string]string{"some-key": "should-NOT-be-found"}) 1414 defer h.mustDelete(dr3) 1415 1416 cg := client.CollectionGroup(shouldBeFoundID) 1417 snaps, err := cg.Documents(ctx).GetAll() 1418 if err != nil { 1419 t.Fatal(err) 1420 } 1421 if len(snaps) != 2 { 1422 t.Fatalf("expected 2 snapshots but got %d", len(snaps)) 1423 } 1424 if snaps[0].Ref.ID != "should-be-found-1" { 1425 t.Fatalf("expected ID 'should-be-found-1', got %s", snaps[0].Ref.ID) 1426 } 1427 if snaps[1].Ref.ID != "should-be-found-2" { 1428 t.Fatalf("expected ID 'should-be-found-2', got %s", snaps[1].Ref.ID) 1429 } 1430} 1431 1432func codeEq(t *testing.T, msg string, code codes.Code, err error) { 1433 if status.Code(err) != code { 1434 t.Fatalf("%s:\ngot <%v>\nwant code %s", msg, err, code) 1435 } 1436} 1437 1438func loc() string { 1439 _, file, line, ok := runtime.Caller(2) 1440 if !ok { 1441 return "???" 1442 } 1443 return fmt.Sprintf("%s:%d", filepath.Base(file), line) 1444} 1445 1446func copyMap(m map[string]interface{}) map[string]interface{} { 1447 c := map[string]interface{}{} 1448 for k, v := range m { 1449 c[k] = v 1450 } 1451 return c 1452} 1453 1454func checkTimeBetween(t *testing.T, got, low, high time.Time) { 1455 // Allow slack for clock skew. 1456 const slack = 4 * time.Second 1457 low = low.Add(-slack) 1458 high = high.Add(slack) 1459 if got.Before(low) || got.After(high) { 1460 t.Fatalf("got %s, not in [%s, %s]", got, low, high) 1461 } 1462} 1463 1464type testHelper struct { 1465 t *testing.T 1466} 1467 1468func (h testHelper) mustCreate(doc *DocumentRef, data interface{}) *WriteResult { 1469 wr, err := doc.Create(context.Background(), data) 1470 if err != nil { 1471 h.t.Fatalf("%s: creating: %v", loc(), err) 1472 } 1473 return wr 1474} 1475 1476func (h testHelper) mustUpdate(doc *DocumentRef, updates []Update) *WriteResult { 1477 wr, err := doc.Update(context.Background(), updates) 1478 if err != nil { 1479 h.t.Fatalf("%s: updating: %v", loc(), err) 1480 } 1481 return wr 1482} 1483 1484func (h testHelper) mustGet(doc *DocumentRef) *DocumentSnapshot { 1485 d, err := doc.Get(context.Background()) 1486 if err != nil { 1487 h.t.Fatalf("%s: getting: %v", loc(), err) 1488 } 1489 return d 1490} 1491 1492func (h testHelper) mustDelete(doc *DocumentRef) *WriteResult { 1493 wr, err := doc.Delete(context.Background()) 1494 if err != nil { 1495 h.t.Fatalf("%s: updating: %v", loc(), err) 1496 } 1497 return wr 1498} 1499 1500func (h testHelper) mustSet(doc *DocumentRef, data interface{}, opts ...SetOption) *WriteResult { 1501 wr, err := doc.Set(context.Background(), data, opts...) 1502 if err != nil { 1503 h.t.Fatalf("%s: updating: %v", loc(), err) 1504 } 1505 return wr 1506} 1507 1508func TestDetectProjectID(t *testing.T) { 1509 if testing.Short() { 1510 t.Skip("Integration tests skipped in short mode") 1511 } 1512 ctx := context.Background() 1513 1514 creds := testutil.Credentials(ctx) 1515 if creds == nil { 1516 t.Skip("Integration tests skipped. See CONTRIBUTING.md for details") 1517 } 1518 1519 // Use creds with project ID. 1520 if _, err := NewClient(ctx, DetectProjectID, option.WithCredentials(creds)); err != nil { 1521 t.Errorf("NewClient: %v", err) 1522 } 1523 1524 ts := testutil.ErroringTokenSource{} 1525 // Try to use creds without project ID. 1526 _, err := NewClient(ctx, DetectProjectID, option.WithTokenSource(ts)) 1527 if err == nil || err.Error() != "firestore: see the docs on DetectProjectID" { 1528 t.Errorf("expected an error while using TokenSource that does not have a project ID") 1529 } 1530} 1531