1// Copyright 2019 The Go Cloud Development Kit Authors
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//     https://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 docstore
16
17import (
18	"context"
19	"reflect"
20	"testing"
21	"time"
22
23	"github.com/google/go-cmp/cmp"
24	"gocloud.dev/docstore/driver"
25	"gocloud.dev/gcerrors"
26)
27
28type Book struct {
29	Title            string `docstore:"key"`
30	Author           Name   `docstore:"author"`
31	PublicationYears []int  `docstore:"pub_years,omitempty"`
32	NumPublications  int    `docstore:"-"`
33}
34
35type Name struct {
36	First, Last string
37}
38
39func TestIsIncNumber(t *testing.T) {
40	for _, x := range []interface{}{int(1), 'x', uint(1), byte(1), float32(1), float64(1), time.Duration(1)} {
41		if !isIncNumber(x) {
42			t.Errorf("%v: got false, want true", x)
43		}
44	}
45	for _, x := range []interface{}{1 + 1i, "3", time.Time{}} {
46		if isIncNumber(x) {
47			t.Errorf("%v: got true, want false", x)
48		}
49	}
50}
51
52func TestActionsDo(t *testing.T) {
53	c := newCollection(fakeDriverCollection{})
54	defer c.Close()
55	dn := map[string]interface{}{"key": nil}
56	d1 := map[string]interface{}{"key": 1}
57	d2 := map[string]interface{}{"key": 2}
58	dsn := &Book{}
59	ds1 := &Book{Title: "The Master and Margarita"}
60	ds2 := &Book{Title: "The Martian"}
61
62	for _, test := range []struct {
63		alist *ActionList
64		want  []int // error indexes; nil if no error
65	}{
66		{c.Actions().Get(d1).Get(d2).Get(ds1).Get(ds2), nil},
67		{c.Actions().Get(d1).Put(d1).Put(ds1).Get(ds1), nil},
68		{c.Actions().Get(d2).Replace(d1).Put(d2).Get(d1), nil},
69		{c.Actions().Get(ds2).Replace(ds1).Put(ds2).Get(ds1), nil},
70		// Missing keys.
71		{c.Actions().Put(dn).Put(dsn), []int{0, 1}},
72		{c.Actions().Get(dn).Replace(dn).Create(dn).Update(dn, Mods{"a": 1}), []int{0, 1, 3}},
73		{c.Actions().Get(dsn).Replace(dsn).Create(dsn).Update(dsn, Mods{"a": 1}), []int{0, 1, 3}},
74		// Duplicate documents.
75		{c.Actions().Create(dn).Create(dn).Create(dsn).Create(dsn), nil}, // each Create without a key is a separate document
76		{c.Actions().Create(d2).Create(ds2).Get(d2).Get(ds2).Create(d2).Put(ds2), []int{4, 5}},
77		{c.Actions().Get(d1).Get(ds1).Get(d1).Get(ds1), []int{2, 3}},
78		{c.Actions().Put(d1).Put(ds1).Get(d1).Get(ds1).Get(d1).Get(ds1), []int{4, 5}},
79		{c.Actions().Get(d1).Get(ds1).Put(d1).Put(d2).Put(ds1).Put(ds2).Put(d1).Replace(ds1), []int{6, 7}},
80		{c.Actions().Create(dn).Create(d1).Create(dsn).Create(ds1).Get(d1).Get(ds1), nil},
81		// Get with field paths.
82		{c.Actions().Get(d1, "a.b", "c"), nil},
83		{c.Actions().Get(ds1, "name.Last", "pub_years"), nil},
84		{c.Actions().Get(d1, ".c").Get(ds1, "").Get(ds2, "\xa0\xa1"), []int{0, 1, 2}}, // bad field path
85		// Mods.
86		{c.Actions().Update(d1, nil).Update(ds1, nil), []int{0, 1}},                                                 // empty mod
87		{c.Actions().Update(d1, Mods{"a.b.c": 1, "a.b": 2, "a.b+c": 3}), []int{0}},                                  // a.b is a prefix of a.b.c
88		{c.Actions().Update(d1, Mods{"": 1}).Update(ds1, Mods{".f": 2}), []int{0, 1}},                               // invalid field path
89		{c.Actions().Update(d1, Mods{"a": Increment(true)}).Update(ds1, Mods{"name": Increment("b")}), []int{0, 1}}, // invalid incOp
90	} {
91		err := test.alist.Do(context.Background())
92		if err == nil {
93			if len(test.want) > 0 {
94				t.Errorf("%s: got nil, want error", test.alist)
95			}
96			continue
97		}
98		var got []int
99		for _, e := range err.(ActionListError) {
100			if gcerrors.Code(e.Err) != gcerrors.InvalidArgument {
101				t.Errorf("%s: got %v, want InvalidArgument", test.alist, e.Err)
102			}
103			got = append(got, e.Index)
104		}
105		if !cmp.Equal(got, test.want) {
106			t.Errorf("%s: got %v, want %v", test.alist, got, test.want)
107		}
108	}
109}
110
111func TestClosedErrors(t *testing.T) {
112	// Check that all collection methods return errClosed if the collection is closed.
113	ctx := context.Background()
114	c := NewCollection(fakeDriverCollection{})
115	if err := c.Close(); err != nil {
116		t.Fatalf("got %v, want nil", err)
117	}
118
119	check := func(err error) {
120		t.Helper()
121		if alerr, ok := err.(ActionListError); ok {
122			err = alerr.Unwrap()
123		}
124		if err != errClosed {
125			t.Errorf("got %v, want errClosed", err)
126		}
127	}
128
129	doc := map[string]interface{}{"key": "k"}
130	check(c.Close())
131	check(c.Actions().Create(doc).Do(ctx))
132	check(c.Create(ctx, doc))
133	check(c.Replace(ctx, doc))
134	check(c.Put(ctx, doc))
135	check(c.Get(ctx, doc))
136	check(c.Delete(ctx, doc))
137	check(c.Update(ctx, doc, Mods{"a": 1}))
138	iter := c.Query().Get(ctx)
139	check(iter.Next(ctx, doc))
140
141	// Check that DocumentIterator.Next returns errClosed if Close is called
142	// in the middle of the iteration.
143	c = NewCollection(fakeDriverCollection{})
144	iter = c.Query().Get(ctx)
145	c.Close()
146	check(iter.Next(ctx, doc))
147}
148
149func TestSerializeRevisionErrors(t *testing.T) {
150	c := NewCollection(fakeDriverCollection{})
151	_, err := c.RevisionToString(nil)
152	if got := gcerrors.Code(err); got != gcerrors.InvalidArgument {
153		t.Errorf("got %v, want InvalidArgument", got)
154	}
155	_, err = c.StringToRevision("")
156	if got := gcerrors.Code(err); got != gcerrors.InvalidArgument {
157		t.Errorf("got %v, want InvalidArgument", got)
158	}
159}
160
161type fakeDriverCollection struct {
162	driver.Collection
163}
164
165func (fakeDriverCollection) Key(doc driver.Document) (interface{}, error) {
166	key, err := doc.GetField("key")
167	// TODO(#2589): remove this check once we check for empty key.
168	if err != nil || driver.IsEmptyValue(reflect.ValueOf(key)) {
169		return nil, err
170	}
171	return key, nil
172}
173
174func (fakeDriverCollection) RevisionField() string { return DefaultRevisionField }
175
176func (fakeDriverCollection) Close() error { return nil }
177
178func (fakeDriverCollection) RunActions(ctx context.Context, actions []*driver.Action, opts *driver.RunActionsOptions) driver.ActionListError {
179	return nil
180}
181
182func (fakeDriverCollection) RunGetQuery(context.Context, *driver.Query) (driver.DocumentIterator, error) {
183	return fakeDriverDocumentIterator{}, nil
184}
185
186type fakeDriverDocumentIterator struct {
187	driver.DocumentIterator
188}
189
190func (fakeDriverDocumentIterator) Next(context.Context, driver.Document) error { return nil }
191