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