1package kivik
2
3import (
4	"context"
5	"errors"
6	"fmt"
7	"io"
8	"reflect"
9	"testing"
10
11	"github.com/flimzy/diff"
12	"github.com/flimzy/kivik/driver"
13	"github.com/flimzy/testy"
14)
15
16func TestBulkNext(t *testing.T) {
17	tests := []struct {
18		name     string
19		r        *BulkResults
20		expected bool
21	}{
22		{
23			name: "true",
24			r: &BulkResults{
25				iter: &iter{
26					feed:   &TestFeed{max: 1},
27					curVal: new(int64),
28				},
29			},
30			expected: true,
31		},
32		{
33			name: "false",
34			r: &BulkResults{
35				iter: &iter{
36					feed:   &TestFeed{max: 0},
37					curVal: new(int64),
38				},
39			},
40			expected: false,
41		},
42	}
43	for _, test := range tests {
44		t.Run(test.name, func(t *testing.T) {
45			result := test.r.Next()
46			if result != test.expected {
47				t.Errorf("Unexpected result: %v", result)
48			}
49		})
50	}
51}
52
53func TestBulkErr(t *testing.T) {
54	expected := "bulk error"
55	r := &BulkResults{
56		iter: &iter{lasterr: errors.New(expected)},
57	}
58	err := r.Err()
59	testy.Error(t, expected, err)
60}
61
62func TestBulkClose(t *testing.T) {
63	expected := "close error"
64	r := &BulkResults{
65		iter: &iter{
66			feed: &TestFeed{closeErr: errors.New(expected)},
67		},
68	}
69	err := r.Close()
70	testy.Error(t, expected, err)
71}
72
73func TestBulkIteratorNext(t *testing.T) {
74	tests := []struct {
75		name     string
76		r        *bulkIterator
77		err      string
78		expected *driver.BulkResult
79	}{
80		{
81			name: "error",
82			r:    &bulkIterator{&mockBulkResults{err: errors.New("iter error")}},
83			err:  "iter error",
84		},
85		{
86			name: "success",
87			r: &bulkIterator{&mockBulkResults{
88				result: &driver.BulkResult{ID: "foo"},
89			}},
90			expected: &driver.BulkResult{ID: "foo"},
91		},
92	}
93	for _, test := range tests {
94		t.Run(test.name, func(t *testing.T) {
95			result := new(driver.BulkResult)
96			err := test.r.Next(result)
97			testy.Error(t, test.err, err)
98			if d := diff.Interface(test.expected, result); d != nil {
99				t.Error(d)
100			}
101		})
102	}
103}
104
105func TestRLOCK(t *testing.T) {
106	tests := []struct {
107		name string
108		iter *iter
109		err  string
110	}{
111		{
112			name: "not ready",
113			iter: &iter{},
114			err:  "kivik: Iterator access before calling Next",
115		},
116		{
117			name: "closed",
118			iter: &iter{closed: true},
119			err:  "kivik: Iterator is closed",
120		},
121		{
122			name: "success",
123			iter: &iter{ready: true},
124		},
125	}
126	for _, test := range tests {
127		t.Run(test.name, func(t *testing.T) {
128			close, err := test.iter.rlock()
129			testy.Error(t, test.err, err)
130			if close == nil {
131				t.Fatal("close is nil")
132			}
133			close()
134		})
135	}
136}
137
138func TestDocsInterfaceSlice(t *testing.T) {
139	type diTest struct {
140		name     string
141		input    interface{}
142		expected interface{}
143		error    string
144	}
145	str := "foo"
146	intSlice := []int{1, 2, 3}
147	tests := []diTest{
148		{
149			name:     "Nil",
150			input:    nil,
151			expected: nil,
152			error:    "must be slice or array, got <nil>",
153		},
154		{
155			name:     "InterfaceSlice",
156			input:    []interface{}{map[string]string{"foo": "bar"}},
157			expected: []interface{}{map[string]string{"foo": "bar"}},
158		},
159		{
160			name:  "String",
161			input: "foo",
162			error: "must be slice or array, got string",
163		},
164		{
165			name:     "IntSlice",
166			input:    []int{1, 2, 3},
167			expected: []interface{}{1, 2, 3},
168		},
169		{
170			name:     "IntArray",
171			input:    [3]int{1, 2, 3},
172			expected: []interface{}{1, 2, 3},
173		},
174		{
175			name:  "StringPointer",
176			input: &str,
177			error: "must be slice or array, got *string",
178		},
179		{
180			name:     "SlicePointer",
181			input:    &intSlice,
182			expected: []interface{}{1, 2, 3},
183		},
184		{
185			name: "JSONDoc",
186			input: []interface{}{
187				map[string]string{"foo": "bar"},
188				[]byte(`{"foo":"bar"}`),
189			},
190			expected: []interface{}{
191				map[string]string{"foo": "bar"},
192				map[string]string{"foo": "bar"},
193			},
194		},
195		{
196			name: "BytesArrays",
197			input: [][]byte{
198				[]byte(`{"foo":"bar"}`),
199				[]byte(`{"foo":"bar"}`),
200			},
201			expected: []interface{}{
202				map[string]string{"foo": "bar"},
203				map[string]string{"foo": "bar"},
204			},
205		},
206		{
207			name:  "InvalidJSON",
208			input: []interface{}{[]byte(`invalid`)},
209			error: "invalid character 'i' looking for beginning of value",
210		},
211		{
212			name:  "BytesInvalidJSON",
213			input: [][]byte{[]byte(`invalid`)},
214			error: "invalid character 'i' looking for beginning of value",
215		},
216	}
217	for _, test := range tests {
218		func(test diTest) {
219			t.Run(test.name, func(t *testing.T) {
220				result, err := docsInterfaceSlice(test.input)
221				var msg string
222				if err != nil {
223					msg = err.Error()
224				}
225				if msg != test.error {
226					t.Errorf("Unexpected error: %s", err)
227				}
228				if d := diff.AsJSON(test.expected, result); d != nil {
229					t.Errorf("%s", d)
230				}
231			})
232		}(test)
233	}
234}
235
236func TestBulkDocsNotSlice(t *testing.T) {
237	err := func() (err error) {
238		defer func() {
239			if r := recover(); r != nil {
240				err = r.(error)
241			}
242		}()
243		db := &DB{}
244		_, _ = db.BulkDocs(context.Background(), nil)
245		return nil
246	}()
247	var msg string
248	if err != nil {
249		msg = err.Error()
250	}
251	expected := "must be slice or array, got <nil>"
252	if msg != expected {
253		t.Errorf("Unexpected error: %s", msg)
254	}
255}
256
257type bdDB struct {
258	driver.DB
259	err     error
260	options map[string]interface{}
261}
262
263var _ driver.DB = &bdDB{}
264
265func (db *bdDB) BulkDocs(_ context.Context, docs []interface{}, options map[string]interface{}) (driver.BulkResults, error) {
266	if db.options != nil {
267		if !reflect.DeepEqual(db.options, options) {
268			return nil, fmt.Errorf("Unexpected options. Got: %v, Expected: %v", options, db.options)
269		}
270	}
271	return nil, db.err
272}
273
274type legacyDB struct {
275	driver.DB
276	err error
277}
278
279var _ driver.OldBulkDocer = &legacyDB{}
280
281func (db *legacyDB) BulkDocs(_ context.Context, docs []interface{}) (driver.BulkResults, error) {
282	return nil, db.err
283}
284
285type nonbdDB struct {
286	driver.DB
287}
288
289var _ driver.DB = &nonbdDB{}
290
291func (db *nonbdDB) Put(_ context.Context, _ string, _ interface{}) (string, error) {
292	return "", nil
293}
294func (db *nonbdDB) CreateDoc(_ context.Context, _ interface{}) (string, string, error) {
295	return "", "", nil
296}
297
298func TestBulkDocs(t *testing.T) {
299	type bdTest struct {
300		name     string
301		dbDriver driver.DB
302		docs     interface{}
303		options  Options
304		err      string
305	}
306	tests := []bdTest{
307		{
308			name:     "no docs",
309			dbDriver: &bdDB{},
310			docs:     []int{},
311		},
312		{
313			name:     "invalid JSON",
314			dbDriver: &bdDB{},
315			docs:     []interface{}{[]byte("invalid json")},
316			err:      "invalid character 'i' looking for beginning of value",
317		},
318		{
319			name:     "query fails",
320			dbDriver: &bdDB{err: errors.New("bulkdocs failed")},
321			docs:     []int{1, 2, 3},
322			err:      "bulkdocs failed",
323		},
324		{
325			name:     "emulated BulkDocs support",
326			dbDriver: &nonbdDB{},
327			docs: []interface{}{
328				map[string]string{"_id": "foo"},
329				123,
330			},
331		},
332		{
333			name:     "new_edits",
334			dbDriver: &bdDB{options: map[string]interface{}{"new_edits": true}},
335			docs: []interface{}{
336				map[string]string{"_id": "foo"},
337				123,
338			},
339			options: Options{"new_edits": true},
340		},
341		{
342			name:     "legacy bulkDocer",
343			dbDriver: &legacyDB{},
344			docs: []interface{}{
345				map[string]string{"_id": "foo"},
346				123,
347			},
348		},
349		{
350			name:     "legacy failure",
351			dbDriver: &legacyDB{err: errors.New("fail")},
352			docs:     []interface{}{1, 2, 3},
353			err:      "fail",
354		},
355	}
356	for _, test := range tests {
357		t.Run(test.name, func(t *testing.T) {
358			db := &DB{driverDB: test.dbDriver}
359			_, err := db.BulkDocs(context.Background(), test.docs, test.options)
360			var msg string
361			if err != nil {
362				msg = err.Error()
363			}
364			if msg != test.err {
365				t.Errorf("Unexpected error: %s", msg)
366			}
367		})
368	}
369}
370
371func TestEmulatedBulkResults(t *testing.T) {
372	results := []driver.BulkResult{
373		{
374			ID:    "chicken",
375			Rev:   "foo",
376			Error: nil,
377		},
378		{
379			ID:    "duck",
380			Rev:   "bar",
381			Error: errors.New("fail"),
382		},
383		{
384			ID:    "dog",
385			Rev:   "baz",
386			Error: nil,
387		},
388	}
389	br := &emulatedBulkResults{results}
390	result := &driver.BulkResult{}
391	if err := br.Next(result); err != nil {
392		t.Errorf("Unexpected error: %s", err)
393	}
394	if d := diff.Interface(&results[0], result); d != nil {
395		t.Error(d)
396	}
397	if err := br.Next(result); err != nil {
398		t.Errorf("Unexpected error: %s", err)
399	}
400	if d := diff.Interface(&results[1], result); d != nil {
401		t.Error(d)
402	}
403	if err := br.Close(); err != nil {
404		t.Errorf("Unexpected error: %s", err)
405	}
406	if err := br.Next(result); err != io.EOF {
407		t.Error("Expected EOF")
408	}
409}
410
411func TestBulkResultsGetters(t *testing.T) {
412	id := "foo"
413	rev := "3-xxx"
414	err := "update error"
415	r := &BulkResults{
416		iter: &iter{
417			ready: true,
418			curVal: &driver.BulkResult{
419				ID:    id,
420				Rev:   rev,
421				Error: errors.New(err),
422			},
423		},
424	}
425
426	t.Run("ID", func(t *testing.T) {
427		result := r.ID()
428		if result != id {
429			t.Errorf("Unexpected ID: %v", result)
430		}
431	})
432
433	t.Run("Rev", func(t *testing.T) {
434		result := r.Rev()
435		if result != rev {
436			t.Errorf("Unexpected Rev: %v", result)
437		}
438	})
439
440	t.Run("UpdateErr", func(t *testing.T) {
441		result := r.UpdateErr()
442		testy.Error(t, err, result)
443	})
444
445	t.Run("Not ready", func(t *testing.T) {
446		r.ready = false
447
448		t.Run("ID", func(t *testing.T) {
449			result := r.ID()
450			if result != "" {
451				t.Errorf("Unexpected ID: %v", result)
452			}
453		})
454
455		t.Run("Rev", func(t *testing.T) {
456			result := r.Rev()
457			if result != "" {
458				t.Errorf("Unexpected Rev: %v", result)
459			}
460		})
461
462		t.Run("UpdateErr", func(t *testing.T) {
463			result := r.UpdateErr()
464			testy.Error(t, "", result)
465		})
466
467	})
468}
469