1
2
3
4
5
6
7
8
9
10
11
12
13 package kivik
14
15 import (
16 "context"
17 "encoding/json"
18 "errors"
19 "fmt"
20 "io"
21 "net/http"
22 "strings"
23 "testing"
24 "time"
25
26 "github.com/google/go-cmp/cmp"
27 "gitlab.com/flimzy/testy"
28
29 "github.com/go-kivik/kivik/v4/driver"
30 internal "github.com/go-kivik/kivik/v4/int/errors"
31 "github.com/go-kivik/kivik/v4/int/mock"
32 )
33
34 func TestClient(t *testing.T) {
35 client := &Client{}
36 db := &DB{client: client}
37 result := db.Client()
38 if result != client {
39 t.Errorf("Unexpected result. Expected %p, got %p", client, result)
40 }
41 }
42
43 func TestName(t *testing.T) {
44 dbName := "foo"
45 db := &DB{name: dbName}
46 result := db.Name()
47 if result != dbName {
48 t.Errorf("Unexpected result. Expected %s, got %s", dbName, result)
49 }
50 }
51
52 func TestAllDocs(t *testing.T) {
53 tests := []struct {
54 name string
55 db *DB
56 options Option
57 expected *ResultSet
58 status int
59 err string
60 }{
61 {
62 name: "db error",
63 db: &DB{
64 client: &Client{},
65 driverDB: &mock.DB{
66 AllDocsFunc: func(context.Context, driver.Options) (driver.Rows, error) {
67 return nil, errors.New("db error")
68 },
69 },
70 },
71 status: http.StatusInternalServerError,
72 err: "db error",
73 },
74 {
75 name: "success",
76 db: &DB{
77 client: &Client{},
78 driverDB: &mock.DB{
79 AllDocsFunc: func(_ context.Context, options driver.Options) (driver.Rows, error) {
80 gotOpts := map[string]interface{}{}
81 options.Apply(gotOpts)
82 if d := testy.DiffInterface(testOptions, gotOpts); d != nil {
83 return nil, fmt.Errorf("Unexpected options: %s", d)
84 }
85 return &mock.Rows{ID: "a"}, nil
86 },
87 },
88 },
89 options: Params(testOptions),
90 expected: &ResultSet{
91 iter: &iter{
92 feed: &rowsIterator{
93 Rows: &mock.Rows{ID: "a"},
94 },
95 curVal: &driver.Row{},
96 },
97 rowsi: &mock.Rows{ID: "a"},
98 },
99 },
100 {
101 name: "client closed",
102 db: &DB{
103 client: &Client{
104 closed: true,
105 },
106 },
107 status: http.StatusServiceUnavailable,
108 err: "kivik: client closed",
109 },
110 {
111 name: "db error",
112 db: &DB{
113 err: errors.New("db error"),
114 },
115 status: http.StatusInternalServerError,
116 err: "db error",
117 },
118 }
119 for _, test := range tests {
120 t.Run(test.name, func(t *testing.T) {
121 rs := test.db.AllDocs(context.Background(), test.options)
122 err := rs.Err()
123 if d := internal.StatusErrorDiff(test.err, test.status, err); d != "" {
124 t.Error(d)
125 }
126 if err != nil {
127 return
128 }
129 rs.cancel = nil
130 rs.onClose = nil
131 if d := testy.DiffInterface(test.expected, rs); d != nil {
132 t.Error(d)
133 }
134 })
135 }
136 t.Run("standalone", func(t *testing.T) {
137 t.Run("after err, close doesn't block", func(t *testing.T) {
138 db := &DB{
139 client: &Client{},
140 driverDB: &mock.DB{
141 AllDocsFunc: func(context.Context, driver.Options) (driver.Rows, error) {
142 return nil, errors.New("unf")
143 },
144 },
145 }
146 rows := db.AllDocs(context.Background())
147 if err := rows.Err(); err == nil {
148 t.Fatal("expected an error, got none")
149 }
150 _ = db.Close()
151 })
152 t.Run("missing ids", func(t *testing.T) {
153 rows := []*driver.Row{
154 {
155 ID: "i-exist",
156 Key: json.RawMessage("i-exist"),
157 Value: strings.NewReader(`{"rev":"1-967a00dff5e02add41819138abb3284d"}`),
158 },
159 {
160 Key: json.RawMessage("i-dont"),
161 Error: errors.New("not found"),
162 },
163 }
164 db := &DB{
165 client: &Client{},
166 driverDB: &mock.DB{
167 AllDocsFunc: func(context.Context, driver.Options) (driver.Rows, error) {
168 return &mock.Rows{
169 NextFunc: func(r *driver.Row) error {
170 if len(rows) == 0 {
171 return io.EOF
172 }
173 row := rows[0]
174 rows = rows[1:]
175 *r = *row
176 return nil
177 },
178 }, nil
179 },
180 },
181 }
182 rs := db.AllDocs(context.Background(), Params(map[string]interface{}{
183 "include_docs": true,
184 "keys": []string{"i-exist", "i-dont"},
185 }))
186 type row struct {
187 ID string
188 Key string
189 Value string
190 Doc string
191 Error string
192 }
193 want := []row{
194 {
195 ID: "i-exist",
196 Key: "i-exist",
197 Value: `{"rev":"1-967a00dff5e02add41819138abb3284d"}`,
198 },
199 {
200 Key: "i-dont",
201 Error: "not found",
202 },
203 }
204 var got []row
205 for rs.Next() {
206 var doc, value json.RawMessage
207 _ = rs.ScanDoc(&doc)
208 _ = rs.ScanValue(&value)
209 var errStr string
210 id, err := rs.ID()
211 key, _ := rs.Key()
212 if err != nil {
213 errStr = err.Error()
214 }
215 got = append(got, row{
216 ID: id,
217 Key: key,
218 Doc: string(doc),
219 Value: string(value),
220 Error: errStr,
221 })
222 }
223 if d := cmp.Diff(want, got, cmp.Transformer("Error", func(t error) string {
224 if t == nil {
225 return ""
226 }
227 return t.Error()
228 })); d != "" {
229 t.Error(d)
230 }
231 })
232 })
233 }
234
235 func TestDesignDocs(t *testing.T) {
236 tests := []struct {
237 name string
238 db *DB
239 options Option
240 expected *ResultSet
241 status int
242 err string
243 }{
244 {
245 name: "db error",
246 db: &DB{
247 client: &Client{},
248 driverDB: &mock.DesignDocer{
249 DesignDocsFunc: func(context.Context, driver.Options) (driver.Rows, error) {
250 return nil, errors.New("db error")
251 },
252 },
253 },
254 status: http.StatusInternalServerError,
255 err: "db error",
256 },
257 {
258 name: "success",
259 db: &DB{
260 client: &Client{},
261 driverDB: &mock.DesignDocer{
262 DesignDocsFunc: func(_ context.Context, options driver.Options) (driver.Rows, error) {
263 opts := map[string]interface{}{}
264 options.Apply(opts)
265 if d := testy.DiffInterface(testOptions, opts); d != nil {
266 return nil, fmt.Errorf("Unexpected options: %s", d)
267 }
268 return &mock.Rows{ID: "a"}, nil
269 },
270 },
271 },
272 options: Params(testOptions),
273 expected: &ResultSet{
274 iter: &iter{
275 feed: &rowsIterator{
276 Rows: &mock.Rows{ID: "a"},
277 },
278 curVal: &driver.Row{},
279 },
280 rowsi: &mock.Rows{ID: "a"},
281 },
282 },
283 {
284 name: "not supported",
285 db: &DB{
286 client: &Client{},
287 driverDB: &mock.DB{},
288 },
289 status: http.StatusNotImplemented,
290 err: "kivik: design doc view not supported by driver",
291 },
292 {
293 name: "db error",
294 db: &DB{
295 err: errors.New("db error"),
296 },
297 status: http.StatusInternalServerError,
298 err: "db error",
299 },
300 {
301 name: "client closed",
302 db: &DB{
303 client: &Client{
304 closed: true,
305 },
306 driverDB: &mock.DesignDocer{},
307 },
308 status: http.StatusServiceUnavailable,
309 err: "kivik: client closed",
310 },
311 }
312 for _, test := range tests {
313 t.Run(test.name, func(t *testing.T) {
314 rs := test.db.DesignDocs(context.Background(), test.options)
315 err := rs.Err()
316 if d := internal.StatusErrorDiff(test.err, test.status, err); d != "" {
317 t.Error(d)
318 }
319 if err != nil {
320 return
321 }
322 rs.cancel = nil
323 rs.onClose = nil
324 if d := testy.DiffInterface(test.expected, rs); d != nil {
325 t.Error(d)
326 }
327 })
328 }
329 t.Run("standalone", func(t *testing.T) {
330 t.Run("after err, close doesn't block", func(t *testing.T) {
331 db := &DB{
332 client: &Client{},
333 driverDB: &mock.DesignDocer{
334 DesignDocsFunc: func(context.Context, driver.Options) (driver.Rows, error) {
335 return nil, errors.New("unf")
336 },
337 },
338 }
339 rows := db.DesignDocs(context.Background())
340 if err := rows.Err(); err == nil {
341 t.Fatal("expected an error, got none")
342 }
343 _ = db.Close()
344 })
345 })
346 }
347
348 func TestLocalDocs(t *testing.T) {
349 tests := []struct {
350 name string
351 db *DB
352 options Option
353 expected *ResultSet
354 status int
355 err string
356 }{
357 {
358 name: "error",
359 db: &DB{
360 client: &Client{},
361 driverDB: &mock.LocalDocer{
362 LocalDocsFunc: func(context.Context, driver.Options) (driver.Rows, error) {
363 return nil, errors.New("db error")
364 },
365 },
366 },
367 status: http.StatusInternalServerError,
368 err: "db error",
369 },
370 {
371 name: "success",
372 db: &DB{
373 client: &Client{},
374 driverDB: &mock.LocalDocer{
375 LocalDocsFunc: func(_ context.Context, options driver.Options) (driver.Rows, error) {
376 opts := map[string]interface{}{}
377 options.Apply(opts)
378 if d := testy.DiffInterface(testOptions, opts); d != nil {
379 return nil, fmt.Errorf("Unexpected options: %s", d)
380 }
381 return &mock.Rows{ID: "a"}, nil
382 },
383 },
384 },
385 options: Params(testOptions),
386 expected: &ResultSet{
387 iter: &iter{
388 feed: &rowsIterator{
389 Rows: &mock.Rows{ID: "a"},
390 },
391 curVal: &driver.Row{},
392 },
393 rowsi: &mock.Rows{ID: "a"},
394 },
395 },
396 {
397 name: "not supported",
398 db: &DB{
399 client: &Client{},
400 driverDB: &mock.DB{},
401 },
402 status: http.StatusNotImplemented,
403 err: "kivik: local doc view not supported by driver",
404 },
405 {
406 name: "db error",
407 db: &DB{
408 err: errors.New("db error"),
409 },
410 status: http.StatusInternalServerError,
411 err: "db error",
412 },
413 {
414 name: "client closed",
415 db: &DB{
416 client: &Client{
417 closed: true,
418 },
419 driverDB: &mock.LocalDocer{},
420 },
421 status: http.StatusServiceUnavailable,
422 err: "kivik: client closed",
423 },
424 }
425 for _, test := range tests {
426 t.Run(test.name, func(t *testing.T) {
427 rs := test.db.LocalDocs(context.Background(), test.options)
428 err := rs.Err()
429 if d := internal.StatusErrorDiff(test.err, test.status, err); d != "" {
430 t.Error(d)
431 }
432 if err != nil {
433 return
434 }
435 rs.cancel = nil
436 rs.onClose = nil
437 if d := testy.DiffInterface(test.expected, rs); d != nil {
438 t.Error(d)
439 }
440 })
441 }
442 t.Run("standalone", func(t *testing.T) {
443 t.Run("after err, close doesn't block", func(t *testing.T) {
444 db := &DB{
445 client: &Client{},
446 driverDB: &mock.LocalDocer{
447 LocalDocsFunc: func(context.Context, driver.Options) (driver.Rows, error) {
448 return nil, errors.New("unf")
449 },
450 },
451 }
452 rows := db.LocalDocs(context.Background())
453 if err := rows.Err(); err == nil {
454 t.Fatal("expected an error, got none")
455 }
456 _ = db.Close()
457 })
458 })
459 }
460
461 func TestQuery(t *testing.T) {
462 tests := []struct {
463 name string
464 db *DB
465 ddoc, view string
466 options Option
467 expected *ResultSet
468 status int
469 err string
470 }{
471 {
472 name: "db error",
473 db: &DB{
474 client: &Client{},
475 driverDB: &mock.DB{
476 QueryFunc: func(context.Context, string, string, driver.Options) (driver.Rows, error) {
477 return nil, errors.New("db error")
478 },
479 },
480 },
481 status: http.StatusInternalServerError,
482 err: "db error",
483 },
484 {
485 name: "success",
486 db: &DB{
487 client: &Client{},
488 driverDB: &mock.DB{
489 QueryFunc: func(_ context.Context, ddoc, view string, options driver.Options) (driver.Rows, error) {
490 expectedDdoc := "foo"
491 expectedView := "bar"
492 if ddoc != expectedDdoc {
493 return nil, fmt.Errorf("Unexpected ddoc: %s", ddoc)
494 }
495 if view != expectedView {
496 return nil, fmt.Errorf("Unexpected view: %s", view)
497 }
498 gotOpts := map[string]interface{}{}
499 options.Apply(gotOpts)
500 if d := testy.DiffInterface(testOptions, gotOpts); d != nil {
501 return nil, fmt.Errorf("Unexpected options: %s", d)
502 }
503 return &mock.Rows{ID: "a"}, nil
504 },
505 },
506 },
507 ddoc: "foo",
508 view: "bar",
509 options: Params(testOptions),
510 expected: &ResultSet{
511 iter: &iter{
512 feed: &rowsIterator{
513 Rows: &mock.Rows{ID: "a"},
514 },
515 curVal: &driver.Row{},
516 },
517 rowsi: &mock.Rows{ID: "a"},
518 },
519 },
520 {
521 name: "db error",
522 db: &DB{
523 err: errors.New("db error"),
524 },
525 status: http.StatusInternalServerError,
526 err: "db error",
527 },
528 {
529 name: "client closed",
530 db: &DB{
531 client: &Client{
532 closed: true,
533 },
534 },
535 status: http.StatusServiceUnavailable,
536 err: "kivik: client closed",
537 },
538 }
539
540 for _, test := range tests {
541 t.Run(test.name, func(t *testing.T) {
542 rs := test.db.Query(context.Background(), test.ddoc, test.view, test.options)
543 err := rs.Err()
544 if d := internal.StatusErrorDiff(test.err, test.status, err); d != "" {
545 t.Error(d)
546 }
547 if err != nil {
548 return
549 }
550 rs.cancel = nil
551 rs.onClose = nil
552 if d := testy.DiffInterface(test.expected, rs); d != nil {
553 t.Error(d)
554 }
555 })
556 }
557 }
558
559 func TestGet(t *testing.T) {
560 type tt struct {
561 db *DB
562 docID string
563 options Option
564 expected string
565 status int
566 err string
567 }
568
569 tests := testy.NewTable()
570 tests.Add("db error", tt{
571 db: &DB{
572 client: &Client{},
573 driverDB: &mock.DB{
574 GetFunc: func(context.Context, string, driver.Options) (*driver.Document, error) {
575 return nil, fmt.Errorf("db error")
576 },
577 },
578 },
579 status: http.StatusInternalServerError,
580 err: "db error",
581 })
582 tests.Add("success", tt{
583 db: &DB{
584 client: &Client{},
585 driverDB: &mock.DB{
586 GetFunc: func(_ context.Context, docID string, options driver.Options) (*driver.Document, error) {
587 expectedDocID := "foo"
588 if docID != expectedDocID {
589 return nil, fmt.Errorf("Unexpected docID: %s", docID)
590 }
591 gotOpts := map[string]interface{}{}
592 options.Apply(gotOpts)
593 if d := testy.DiffInterface(testOptions, gotOpts); d != nil {
594 return nil, fmt.Errorf("Unexpected options:\n%s", d)
595 }
596 return &driver.Document{
597 Rev: "1-xxx",
598 Body: body(`{"_id":"foo"}`),
599 }, nil
600 },
601 },
602 },
603 docID: "foo",
604 options: Params(testOptions),
605 expected: `{"_id":"foo"}`,
606 })
607 tests.Add("streaming attachments", tt{
608 db: &DB{
609 client: &Client{},
610 driverDB: &mock.DB{
611 GetFunc: func(_ context.Context, docID string, options driver.Options) (*driver.Document, error) {
612 expectedDocID := "foo"
613 gotOpts := map[string]interface{}{}
614 options.Apply(gotOpts)
615 wantOpts := map[string]interface{}{"include_docs": true}
616 if docID != expectedDocID {
617 return nil, fmt.Errorf("Unexpected docID: %s", docID)
618 }
619 if d := testy.DiffInterface(wantOpts, gotOpts); d != nil {
620 return nil, fmt.Errorf("Unexpected options:\n%s", d)
621 }
622 return &driver.Document{
623 Rev: "1-xxx",
624 Body: body(`{"_id":"foo"}`),
625 Attachments: &mock.Attachments{ID: "asdf"},
626 }, nil
627 },
628 },
629 },
630 docID: "foo",
631 options: IncludeDocs(),
632 expected: `{"_id":"foo"}`,
633 })
634 tests.Add("client closed", tt{
635 db: &DB{
636 client: &Client{
637 closed: true,
638 },
639 },
640 status: http.StatusServiceUnavailable,
641 err: "kivik: client closed",
642 })
643
644 tests.Run(t, func(t *testing.T, tt tt) {
645 var doc json.RawMessage
646 err := tt.db.Get(context.Background(), tt.docID, tt.options).ScanDoc(&doc)
647 if !testy.ErrorMatches(tt.err, err) {
648 t.Errorf("Unexpected error: %s", err)
649 }
650 if status := HTTPStatus(err); status != tt.status {
651 t.Errorf("Unexpected error status: %v", status)
652 }
653 if d := testy.DiffJSON([]byte(tt.expected), []byte(doc)); d != nil {
654 t.Error(d)
655 }
656 })
657 }
658
659 func TestOpenRevs(t *testing.T) {
660 tests := []struct {
661 name string
662 db *DB
663 ddoc string
664 revs []string
665 options Option
666 expected *ResultSet
667 status int
668 err string
669 }{
670 {
671 name: "db error",
672 db: &DB{
673 client: &Client{},
674 driverDB: &mock.OpenRever{
675 OpenRevsFunc: func(context.Context, string, []string, driver.Options) (driver.Rows, error) {
676 return nil, errors.New("db error")
677 },
678 },
679 },
680 status: http.StatusInternalServerError,
681 err: "db error",
682 },
683 {
684 name: "success",
685 db: &DB{
686 client: &Client{},
687 driverDB: &mock.OpenRever{
688 OpenRevsFunc: func(_ context.Context, ddoc string, revs []string, options driver.Options) (driver.Rows, error) {
689 const expectedDdoc = "foo"
690 expectedRevs := []string{"all"}
691 if ddoc != expectedDdoc {
692 return nil, fmt.Errorf("Unexpected ddoc: %s", ddoc)
693 }
694 if d := cmp.Diff(expectedRevs, revs); d != "" {
695 return nil, fmt.Errorf("Unexpected revs: %s", d)
696 }
697 gotOpts := map[string]interface{}{}
698 options.Apply(gotOpts)
699 if d := testy.DiffInterface(testOptions, gotOpts); d != nil {
700 return nil, fmt.Errorf("Unexpected options: %s", d)
701 }
702 return &mock.Rows{ID: "a"}, nil
703 },
704 },
705 },
706 ddoc: "foo",
707 revs: []string{"all"},
708 options: Params(testOptions),
709 expected: &ResultSet{
710 iter: &iter{
711 feed: &rowsIterator{
712 Rows: &mock.Rows{ID: "a"},
713 },
714 curVal: &driver.Row{},
715 },
716 rowsi: &mock.Rows{ID: "a"},
717 },
718 },
719 {
720 name: "db error",
721 db: &DB{
722 err: errors.New("db error"),
723 },
724 status: http.StatusInternalServerError,
725 err: "db error",
726 },
727 {
728 name: "unsupported by driver",
729 db: &DB{
730 client: &Client{
731 closed: true,
732 },
733 },
734 status: http.StatusNotImplemented,
735 err: "kivik: driver does not support OpenRevs interface",
736 },
737 {
738 name: "client closed",
739 db: &DB{
740 driverDB: &mock.OpenRever{},
741 client: &Client{
742 closed: true,
743 },
744 },
745 status: http.StatusServiceUnavailable,
746 err: "kivik: client closed",
747 },
748 }
749
750 for _, test := range tests {
751 t.Run(test.name, func(t *testing.T) {
752 rs := test.db.OpenRevs(context.Background(), test.ddoc, test.revs, test.options)
753 err := rs.Err()
754 if d := internal.StatusErrorDiff(test.err, test.status, err); d != "" {
755 t.Error(d)
756 }
757 if err != nil {
758 return
759 }
760 rs.cancel = nil
761 rs.onClose = nil
762 if d := testy.DiffInterface(test.expected, rs); d != nil {
763 t.Error(d)
764 }
765 })
766 }
767 }
768
769 func TestFlush(t *testing.T) {
770 tests := []struct {
771 name string
772 db *DB
773 status int
774 err string
775 }{
776 {
777 name: "non-Flusher",
778 db: &DB{
779 client: &Client{},
780 driverDB: &mock.DB{},
781 },
782 status: http.StatusNotImplemented,
783 err: "kivik: flush not supported by driver",
784 },
785 {
786 name: "db error",
787 db: &DB{
788 client: &Client{},
789 driverDB: &mock.Flusher{
790 FlushFunc: func(context.Context) error {
791 return &internal.Error{Status: http.StatusBadGateway, Err: errors.New("flush error")}
792 },
793 },
794 },
795 status: http.StatusBadGateway,
796 err: "flush error",
797 },
798 {
799 name: "success",
800 db: &DB{
801 client: &Client{},
802 driverDB: &mock.Flusher{
803 FlushFunc: func(context.Context) error {
804 return nil
805 },
806 },
807 },
808 },
809 {
810 name: "client closed",
811 db: &DB{
812 client: &Client{
813 closed: true,
814 },
815 },
816 status: http.StatusServiceUnavailable,
817 err: "kivik: client closed",
818 },
819 {
820 name: "db error",
821 db: &DB{
822 err: errors.New("db error"),
823 },
824 status: http.StatusInternalServerError,
825 err: "db error",
826 },
827 {
828 name: "database closed",
829 db: &DB{
830 closed: true,
831 client: &Client{
832 closed: true,
833 },
834 },
835 status: http.StatusServiceUnavailable,
836 err: "kivik: database closed",
837 },
838 }
839 for _, test := range tests {
840 t.Run(test.name, func(t *testing.T) {
841 err := test.db.Flush(context.Background())
842 if d := internal.StatusErrorDiff(test.err, test.status, err); d != "" {
843 t.Error(d)
844 }
845 })
846 }
847 }
848
849 func TestStats(t *testing.T) {
850 tests := []struct {
851 name string
852 db *DB
853 expected *DBStats
854 status int
855 err string
856 }{
857 {
858 name: "stats error",
859 db: &DB{
860 client: &Client{},
861 driverDB: &mock.DB{
862 StatsFunc: func(context.Context) (*driver.DBStats, error) {
863 return nil, &internal.Error{Status: http.StatusBadGateway, Err: errors.New("stats error")}
864 },
865 },
866 },
867 status: http.StatusBadGateway,
868 err: "stats error",
869 },
870 {
871 name: "success",
872 db: &DB{
873 client: &Client{},
874 driverDB: &mock.DB{
875 StatsFunc: func(context.Context) (*driver.DBStats, error) {
876 return &driver.DBStats{
877 Name: "foo",
878 CompactRunning: true,
879 DocCount: 1,
880 DeletedCount: 2,
881 UpdateSeq: "abc",
882 DiskSize: 3,
883 ActiveSize: 4,
884 ExternalSize: 5,
885 Cluster: &driver.ClusterStats{
886 Replicas: 6,
887 Shards: 7,
888 ReadQuorum: 8,
889 WriteQuorum: 9,
890 },
891 RawResponse: []byte("foo"),
892 }, nil
893 },
894 },
895 },
896 expected: &DBStats{
897 Name: "foo",
898 CompactRunning: true,
899 DocCount: 1,
900 DeletedCount: 2,
901 UpdateSeq: "abc",
902 DiskSize: 3,
903 ActiveSize: 4,
904 ExternalSize: 5,
905 Cluster: &ClusterConfig{
906 Replicas: 6,
907 Shards: 7,
908 ReadQuorum: 8,
909 WriteQuorum: 9,
910 },
911 RawResponse: []byte("foo"),
912 },
913 },
914 {
915 name: "client closed",
916 db: &DB{
917 client: &Client{
918 closed: true,
919 },
920 },
921 status: http.StatusServiceUnavailable,
922 err: "kivik: client closed",
923 },
924 }
925 for _, test := range tests {
926 t.Run(test.name, func(t *testing.T) {
927 result, err := test.db.Stats(context.Background())
928 if d := internal.StatusErrorDiff(test.err, test.status, err); d != "" {
929 t.Error(d)
930 }
931 if d := testy.DiffInterface(test.expected, result); d != nil {
932 t.Error(d)
933 }
934 })
935 }
936 }
937
938 func TestCompact(t *testing.T) {
939 t.Run("error", func(t *testing.T) {
940 expected := "compact error"
941 db := &DB{
942 client: &Client{},
943 driverDB: &mock.DB{
944 CompactFunc: func(context.Context) error {
945 return &internal.Error{Status: http.StatusBadRequest, Err: errors.New(expected)}
946 },
947 },
948 }
949 err := db.Compact(context.Background())
950 if d := internal.StatusErrorDiff(expected, http.StatusBadRequest, err); d != "" {
951 t.Error(d)
952 }
953 })
954 t.Run("closed", func(t *testing.T) {
955 const expected = "kivik: client closed"
956 db := &DB{
957 client: &Client{
958 closed: true,
959 },
960 }
961 err := db.Compact(context.Background())
962 if d := internal.StatusErrorDiff(expected, http.StatusServiceUnavailable, err); d != "" {
963 t.Error(d)
964 }
965 })
966 t.Run("db error", func(t *testing.T) {
967 db := &DB{
968 client: &Client{},
969 err: errors.New("db error"),
970 }
971 err := db.Compact(context.Background())
972 if !testy.ErrorMatches("db error", err) {
973 t.Errorf("Unexpected error: %s", err)
974 }
975 })
976 }
977
978 func TestCompactView(t *testing.T) {
979 t.Run("error", func(t *testing.T) {
980 expectedDDocID := "foo"
981 expected := "compact view error"
982 db := &DB{
983 client: &Client{},
984 driverDB: &mock.DB{
985 CompactViewFunc: func(_ context.Context, ddocID string) error {
986 if ddocID != expectedDDocID {
987 return fmt.Errorf("Unexpected ddocID: %s", ddocID)
988 }
989 return &internal.Error{Status: http.StatusBadRequest, Err: errors.New(expected)}
990 },
991 },
992 }
993 err := db.CompactView(context.Background(), expectedDDocID)
994 if d := internal.StatusErrorDiff(expected, http.StatusBadRequest, err); d != "" {
995 t.Error(d)
996 }
997 })
998 t.Run("closed", func(t *testing.T) {
999 const expected = "kivik: client closed"
1000 db := &DB{
1001 client: &Client{
1002 closed: true,
1003 },
1004 }
1005 err := db.CompactView(context.Background(), "")
1006 if d := internal.StatusErrorDiff(expected, http.StatusServiceUnavailable, err); d != "" {
1007 t.Error(d)
1008 }
1009 })
1010 t.Run("db error", func(t *testing.T) {
1011 db := &DB{
1012 client: &Client{},
1013 err: errors.New("db error"),
1014 }
1015 err := db.CompactView(context.Background(), "")
1016 if !testy.ErrorMatches("db error", err) {
1017 t.Errorf("Unexpected error: %s", err)
1018 }
1019 })
1020 }
1021
1022 func TestViewCleanup(t *testing.T) {
1023 t.Run("compact error", func(t *testing.T) {
1024 expected := "compact error"
1025 db := &DB{
1026 client: &Client{},
1027 driverDB: &mock.DB{
1028 ViewCleanupFunc: func(context.Context) error {
1029 return &internal.Error{Status: http.StatusBadRequest, Err: errors.New(expected)}
1030 },
1031 },
1032 }
1033 err := db.ViewCleanup(context.Background())
1034 if d := internal.StatusErrorDiff(expected, http.StatusBadRequest, err); d != "" {
1035 t.Error(d)
1036 }
1037 })
1038 t.Run("client closed", func(t *testing.T) {
1039 const expected = "kivik: client closed"
1040 db := &DB{
1041 client: &Client{
1042 closed: true,
1043 },
1044 }
1045 err := db.ViewCleanup(context.Background())
1046 if d := internal.StatusErrorDiff(expected, http.StatusServiceUnavailable, err); d != "" {
1047 t.Error(d)
1048 }
1049 })
1050 t.Run("db error", func(t *testing.T) {
1051 const expected = "db error"
1052 db := &DB{
1053 err: errors.New(expected),
1054 }
1055 err := db.ViewCleanup(context.Background())
1056 if d := internal.StatusErrorDiff(expected, http.StatusInternalServerError, err); d != "" {
1057 t.Error(d)
1058 }
1059 })
1060 }
1061
1062 func TestSecurity(t *testing.T) {
1063 tests := []struct {
1064 name string
1065 db *DB
1066 expected *Security
1067 status int
1068 err string
1069 }{
1070 {
1071 name: "security error",
1072 db: &DB{
1073 client: &Client{},
1074 driverDB: &mock.SecurityDB{
1075 SecurityFunc: func(context.Context) (*driver.Security, error) {
1076 return nil, &internal.Error{Status: http.StatusBadGateway, Err: errors.New("security error")}
1077 },
1078 },
1079 },
1080 status: http.StatusBadGateway,
1081 err: "security error",
1082 },
1083 {
1084 name: "success",
1085 db: &DB{
1086 client: &Client{},
1087 driverDB: &mock.SecurityDB{
1088 SecurityFunc: func(context.Context) (*driver.Security, error) {
1089 return &driver.Security{
1090 Admins: driver.Members{
1091 Names: []string{"a"},
1092 Roles: []string{"b"},
1093 },
1094 Members: driver.Members{
1095 Names: []string{"c"},
1096 Roles: []string{"d"},
1097 },
1098 }, nil
1099 },
1100 },
1101 },
1102 expected: &Security{
1103 Admins: Members{
1104 Names: []string{"a"},
1105 Roles: []string{"b"},
1106 },
1107 Members: Members{
1108 Names: []string{"c"},
1109 Roles: []string{"d"},
1110 },
1111 },
1112 },
1113 {
1114 name: "client closed",
1115 db: &DB{
1116 client: &Client{
1117 closed: true,
1118 },
1119 driverDB: &mock.SecurityDB{},
1120 },
1121 status: http.StatusServiceUnavailable,
1122 err: "kivik: client closed",
1123 },
1124 {
1125 name: "db error",
1126 db: &DB{
1127 err: errors.New("db error"),
1128 },
1129 status: http.StatusInternalServerError,
1130 err: "db error",
1131 },
1132 }
1133 for _, test := range tests {
1134 t.Run(test.name, func(t *testing.T) {
1135 result, err := test.db.Security(context.Background())
1136 if d := internal.StatusErrorDiff(test.err, test.status, err); d != "" {
1137 t.Error(d)
1138 }
1139 if d := testy.DiffInterface(test.expected, result); d != nil {
1140 t.Error(d)
1141 }
1142 })
1143 }
1144 }
1145
1146 func TestSetSecurity(t *testing.T) {
1147 tests := []struct {
1148 name string
1149 db *DB
1150 security *Security
1151 status int
1152 err string
1153 }{
1154 {
1155 name: "nil security",
1156 db: &DB{
1157 client: &Client{},
1158 driverDB: &mock.SecurityDB{},
1159 },
1160 status: http.StatusBadRequest,
1161 err: "kivik: security required",
1162 },
1163 {
1164 name: "set error",
1165 db: &DB{
1166 client: &Client{},
1167 driverDB: &mock.SecurityDB{
1168 SetSecurityFunc: func(context.Context, *driver.Security) error {
1169 return &internal.Error{Status: http.StatusBadGateway, Err: errors.New("set security error")}
1170 },
1171 },
1172 },
1173 security: &Security{},
1174 status: http.StatusBadGateway,
1175 err: "set security error",
1176 },
1177 {
1178 name: "success",
1179 db: &DB{
1180 client: &Client{},
1181 driverDB: &mock.SecurityDB{
1182 SetSecurityFunc: func(_ context.Context, security *driver.Security) error {
1183 expectedSecurity := &driver.Security{
1184 Admins: driver.Members{
1185 Names: []string{"a"},
1186 Roles: []string{"b"},
1187 },
1188 Members: driver.Members{
1189 Names: []string{"c"},
1190 Roles: []string{"d"},
1191 },
1192 }
1193 if d := testy.DiffInterface(expectedSecurity, security); d != nil {
1194 return fmt.Errorf("Unexpected security:\n%s", d)
1195 }
1196 return nil
1197 },
1198 },
1199 },
1200 security: &Security{
1201 Admins: Members{
1202 Names: []string{"a"},
1203 Roles: []string{"b"},
1204 },
1205 Members: Members{
1206 Names: []string{"c"},
1207 Roles: []string{"d"},
1208 },
1209 },
1210 },
1211 {
1212 name: "client closed",
1213 db: &DB{
1214 client: &Client{
1215 closed: true,
1216 },
1217 driverDB: &mock.SecurityDB{},
1218 },
1219 security: &Security{},
1220 status: http.StatusServiceUnavailable,
1221 err: "kivik: client closed",
1222 },
1223 {
1224 name: "db error",
1225 db: &DB{
1226 err: errors.New("db error"),
1227 },
1228 status: http.StatusInternalServerError,
1229 err: "db error",
1230 },
1231 }
1232 for _, test := range tests {
1233 t.Run(test.name, func(t *testing.T) {
1234 err := test.db.SetSecurity(context.Background(), test.security)
1235 if d := internal.StatusErrorDiff(test.err, test.status, err); d != "" {
1236 t.Error(d)
1237 }
1238 })
1239 }
1240 }
1241
1242 func TestGetRev(t *testing.T) {
1243 tests := []struct {
1244 name string
1245 db *DB
1246 docID string
1247 rev string
1248 options Option
1249 status int
1250 err string
1251 }{
1252 {
1253 name: "meta getter error",
1254 db: &DB{
1255 client: &Client{},
1256 driverDB: &mock.RevGetter{
1257 GetRevFunc: func(context.Context, string, driver.Options) (string, error) {
1258 return "", &internal.Error{Status: http.StatusBadGateway, Err: errors.New("get meta error")}
1259 },
1260 },
1261 },
1262 status: http.StatusBadGateway,
1263 err: "get meta error",
1264 },
1265 {
1266 name: "meta getter success",
1267 db: &DB{
1268 client: &Client{},
1269 driverDB: &mock.RevGetter{
1270 GetRevFunc: func(_ context.Context, docID string, options driver.Options) (string, error) {
1271 expectedDocID := "foo"
1272 if docID != expectedDocID {
1273 return "", fmt.Errorf("Unexpected docID: %s", docID)
1274 }
1275 gotOpts := map[string]interface{}{}
1276 options.Apply(gotOpts)
1277 if d := testy.DiffInterface(testOptions, gotOpts); d != nil {
1278 return "", fmt.Errorf("Unexpected options:\n%s", d)
1279 }
1280 return "1-xxx", nil
1281 },
1282 },
1283 },
1284 docID: "foo",
1285 options: Params(testOptions),
1286 rev: "1-xxx",
1287 },
1288 {
1289 name: "non-meta getter error",
1290 db: &DB{
1291 client: &Client{},
1292 driverDB: &mock.DB{
1293 GetFunc: func(context.Context, string, driver.Options) (*driver.Document, error) {
1294 return nil, &internal.Error{Status: http.StatusBadGateway, Err: errors.New("get error")}
1295 },
1296 },
1297 },
1298 status: http.StatusBadGateway,
1299 err: "get error",
1300 },
1301 {
1302 name: "non-meta getter success with rev",
1303 db: &DB{
1304 client: &Client{},
1305 driverDB: &mock.DB{
1306 GetFunc: func(_ context.Context, docID string, _ driver.Options) (*driver.Document, error) {
1307 expectedDocID := "foo"
1308 if docID != expectedDocID {
1309 return nil, fmt.Errorf("Unexpected docID: %s", docID)
1310 }
1311 return &driver.Document{
1312 Rev: "1-xxx",
1313 Body: body(`{"_rev":"1-xxx"}`),
1314 }, nil
1315 },
1316 },
1317 },
1318 docID: "foo",
1319 rev: "1-xxx",
1320 },
1321 {
1322 name: "non-meta getter success without rev",
1323 db: &DB{
1324 client: &Client{},
1325 driverDB: &mock.DB{
1326 GetFunc: func(_ context.Context, docID string, _ driver.Options) (*driver.Document, error) {
1327 expectedDocID := "foo"
1328 if docID != expectedDocID {
1329 return nil, fmt.Errorf("Unexpected docID: %s", docID)
1330 }
1331 return &driver.Document{
1332 Body: body(`{"_rev":"1-xxx"}`),
1333 }, nil
1334 },
1335 },
1336 },
1337 docID: "foo",
1338 rev: "1-xxx",
1339 },
1340 {
1341 name: "non-meta getter success without rev, invalid json",
1342 db: &DB{
1343 client: &Client{},
1344 driverDB: &mock.DB{
1345 GetFunc: func(_ context.Context, docID string, _ driver.Options) (*driver.Document, error) {
1346 expectedDocID := "foo"
1347 if docID != expectedDocID {
1348 return nil, fmt.Errorf("Unexpected docID: %s", docID)
1349 }
1350 return &driver.Document{
1351 Body: body(`invalid json`),
1352 }, nil
1353 },
1354 },
1355 },
1356 docID: "foo",
1357 status: http.StatusInternalServerError,
1358 err: "invalid character 'i' looking for beginning of value",
1359 },
1360 {
1361 name: "client closed",
1362 db: &DB{
1363 client: &Client{
1364 closed: true,
1365 },
1366 driverDB: &mock.RevGetter{},
1367 },
1368 status: http.StatusServiceUnavailable,
1369 err: "kivik: client closed",
1370 },
1371 {
1372 name: "db error",
1373 db: &DB{
1374 err: errors.New("db error"),
1375 },
1376 status: http.StatusInternalServerError,
1377 err: "db error",
1378 },
1379 }
1380 for _, test := range tests {
1381 t.Run(test.name, func(t *testing.T) {
1382 rev, err := test.db.GetRev(context.Background(), test.docID, test.options)
1383 if d := internal.StatusErrorDiff(test.err, test.status, err); d != "" {
1384 t.Error(d)
1385 }
1386 if rev != test.rev {
1387 t.Errorf("Unexpected rev: %v", rev)
1388 }
1389 })
1390 }
1391 }
1392
1393 func TestCopy(t *testing.T) {
1394 tests := []struct {
1395 name string
1396 db *DB
1397 target, source string
1398 options Option
1399 expected string
1400 status int
1401 err string
1402 }{
1403 {
1404 name: "missing target",
1405 db: &DB{
1406 client: &Client{},
1407 },
1408 status: http.StatusBadRequest,
1409 err: "kivik: targetID required",
1410 },
1411 {
1412 name: "missing source",
1413 db: &DB{
1414 client: &Client{},
1415 },
1416 target: "foo",
1417 status: http.StatusBadRequest,
1418 err: "kivik: sourceID required",
1419 },
1420 {
1421 name: "copier error",
1422 db: &DB{
1423 client: &Client{},
1424 driverDB: &mock.Copier{
1425 CopyFunc: func(context.Context, string, string, driver.Options) (string, error) {
1426 return "", &internal.Error{Status: http.StatusBadRequest, Err: errors.New("copy error")}
1427 },
1428 },
1429 },
1430 target: "foo",
1431 source: "bar",
1432 status: http.StatusBadRequest,
1433 err: "copy error",
1434 },
1435 {
1436 name: "copier success",
1437 db: &DB{
1438 client: &Client{},
1439 driverDB: &mock.Copier{
1440 CopyFunc: func(_ context.Context, target, source string, options driver.Options) (string, error) {
1441 expectedTarget := "foo"
1442 expectedSource := "bar"
1443 if target != expectedTarget {
1444 return "", fmt.Errorf("Unexpected target: %s", target)
1445 }
1446 if source != expectedSource {
1447 return "", fmt.Errorf("Unexpected source: %s", source)
1448 }
1449 gotOpts := map[string]interface{}{}
1450 options.Apply(gotOpts)
1451 if d := testy.DiffInterface(testOptions, gotOpts); d != nil {
1452 return "", fmt.Errorf("Unexpected options:\n%s", d)
1453 }
1454 return "1-xxx", nil
1455 },
1456 },
1457 },
1458 target: "foo",
1459 source: "bar",
1460 options: Params(testOptions),
1461 expected: "1-xxx",
1462 },
1463 {
1464 name: "non-copier get error",
1465 db: &DB{
1466 client: &Client{},
1467 driverDB: &mock.DB{
1468 GetFunc: func(context.Context, string, driver.Options) (*driver.Document, error) {
1469 return nil, &internal.Error{Status: http.StatusBadGateway, Err: errors.New("get error")}
1470 },
1471 },
1472 },
1473 target: "foo",
1474 source: "bar",
1475 status: http.StatusBadGateway,
1476 err: "get error",
1477 },
1478 {
1479 name: "non-copier invalid JSON",
1480 db: &DB{
1481 client: &Client{},
1482 driverDB: &mock.DB{
1483 GetFunc: func(context.Context, string, driver.Options) (*driver.Document, error) {
1484 return &driver.Document{
1485 Body: body("invalid json"),
1486 }, nil
1487 },
1488 },
1489 },
1490 target: "foo",
1491 source: "bar",
1492 status: http.StatusInternalServerError,
1493 err: "invalid character 'i' looking for beginning of value",
1494 },
1495 {
1496 name: "non-copier put error",
1497 db: &DB{
1498 client: &Client{},
1499 driverDB: &mock.DB{
1500 GetFunc: func(context.Context, string, driver.Options) (*driver.Document, error) {
1501 return &driver.Document{
1502 Body: body(`{"_id":"foo","_rev":"1-xxx"}`),
1503 }, nil
1504 },
1505 PutFunc: func(context.Context, string, interface{}, driver.Options) (string, error) {
1506 return "", &internal.Error{Status: http.StatusBadGateway, Err: errors.New("put error")}
1507 },
1508 },
1509 },
1510 target: "foo",
1511 source: "bar",
1512 status: http.StatusBadGateway,
1513 err: "put error",
1514 },
1515 {
1516 name: "success",
1517 db: &DB{
1518 client: &Client{},
1519 driverDB: &mock.DB{
1520 GetFunc: func(_ context.Context, docID string, _ driver.Options) (*driver.Document, error) {
1521 expectedDocID := "bar"
1522 if docID != expectedDocID {
1523 return nil, fmt.Errorf("Unexpected get docID: %s", docID)
1524 }
1525 return &driver.Document{
1526 Body: body(`{"_id":"bar","_rev":"1-xxx","foo":123.4}`),
1527 }, nil
1528 },
1529 PutFunc: func(_ context.Context, docID string, doc interface{}, options driver.Options) (string, error) {
1530 expectedDocID := "foo"
1531 expectedDoc := map[string]interface{}{"_id": "foo", "foo": 123.4}
1532 expectedOpts := map[string]interface{}{"batch": true}
1533 if docID != expectedDocID {
1534 return "", fmt.Errorf("Unexpected put docID: %s", docID)
1535 }
1536 if d := testy.DiffInterface(expectedDoc, doc); d != nil {
1537 return "", fmt.Errorf("Unexpected doc:\n%s", doc)
1538 }
1539 gotOpts := map[string]interface{}{}
1540 options.Apply(gotOpts)
1541 if d := testy.DiffInterface(expectedOpts, gotOpts); d != nil {
1542 return "", fmt.Errorf("Unexpected opts:\n%s", d)
1543 }
1544 return "1-xxx", nil
1545 },
1546 },
1547 },
1548 target: "foo",
1549 source: "bar",
1550 options: Params(map[string]interface{}{"rev": "1-xxx", "batch": true}),
1551 expected: "1-xxx",
1552 },
1553 {
1554 name: "closed",
1555 db: &DB{
1556 client: &Client{
1557 closed: true,
1558 },
1559 },
1560 target: "x",
1561 source: "y",
1562 status: http.StatusServiceUnavailable,
1563 err: "kivik: client closed",
1564 },
1565 }
1566 for _, test := range tests {
1567 t.Run(test.name, func(t *testing.T) {
1568 result, err := test.db.Copy(context.Background(), test.target, test.source, test.options)
1569 if d := internal.StatusErrorDiff(test.err, test.status, err); d != "" {
1570 t.Error(d)
1571 }
1572 if result != test.expected {
1573 t.Errorf("Unexpected result: %s", result)
1574 }
1575 })
1576 }
1577 }
1578
1579 type errorReader struct{}
1580
1581 var _ io.Reader = &errorReader{}
1582
1583 func (r *errorReader) Read(_ []byte) (int, error) {
1584 return 0, errors.New("errorReader")
1585 }
1586
1587 func TestNormalizeFromJSON(t *testing.T) {
1588 type njTest struct {
1589 Name string
1590 Input interface{}
1591 Expected interface{}
1592 Status int
1593 Error string
1594 }
1595 tests := []njTest{
1596 {
1597 Name: "Interface",
1598 Input: int(5),
1599 Expected: int(5),
1600 },
1601 {
1602 Name: "RawMessage",
1603 Input: json.RawMessage(`{"foo":"bar"}`),
1604 Expected: map[string]interface{}{"foo": "bar"},
1605 },
1606 {
1607 Name: "ioReader",
1608 Input: strings.NewReader(`{"foo":"bar"}`),
1609 Expected: map[string]interface{}{"foo": "bar"},
1610 },
1611 {
1612 Name: "ErrorReader",
1613 Input: &errorReader{},
1614 Status: http.StatusBadRequest,
1615 Error: "errorReader",
1616 },
1617 }
1618 for _, test := range tests {
1619 func(test njTest) {
1620 t.Run(test.Name, func(t *testing.T) {
1621 result, err := normalizeFromJSON(test.Input)
1622 if d := internal.StatusErrorDiff(test.Error, test.Status, err); d != "" {
1623 t.Error(d)
1624 }
1625 if d := testy.DiffAsJSON(test.Expected, result); d != nil {
1626 t.Error(d)
1627 }
1628 })
1629 }(test)
1630 }
1631 }
1632
1633 func TestPut(t *testing.T) {
1634 putFunc := func(_ context.Context, docID string, doc interface{}, options driver.Options) (string, error) {
1635 expectedDocID := "foo"
1636 expectedDoc := map[string]interface{}{"foo": "bar"}
1637 if expectedDocID != docID {
1638 return "", fmt.Errorf("Unexpected docID: %s", docID)
1639 }
1640 if d := testy.DiffAsJSON(expectedDoc, doc); d != nil {
1641 return "", fmt.Errorf("Unexpected doc: %s", d)
1642 }
1643 gotOpts := map[string]interface{}{}
1644 options.Apply(gotOpts)
1645 if d := testy.DiffInterface(testOptions, gotOpts); d != nil {
1646 return "", fmt.Errorf("Unexpected opts: %s", d)
1647 }
1648 return "1-xxx", nil
1649 }
1650 type putTest struct {
1651 name string
1652 db *DB
1653 docID string
1654 input interface{}
1655 options Option
1656 status int
1657 err string
1658 newRev string
1659 }
1660 tests := []putTest{
1661 {
1662 name: "no docID",
1663 db: &DB{
1664 client: &Client{},
1665 },
1666 status: http.StatusBadRequest,
1667 err: "kivik: docID required",
1668 },
1669 {
1670 name: "error",
1671 db: &DB{
1672 client: &Client{},
1673 driverDB: &mock.DB{
1674 PutFunc: func(context.Context, string, interface{}, driver.Options) (string, error) {
1675 return "", &internal.Error{Status: http.StatusBadRequest, Err: errors.New("db error")}
1676 },
1677 },
1678 },
1679 docID: "foo",
1680 status: http.StatusBadRequest,
1681 err: "db error",
1682 },
1683 {
1684 name: "Interface",
1685 db: &DB{
1686 client: &Client{},
1687 driverDB: &mock.DB{
1688 PutFunc: putFunc,
1689 },
1690 },
1691 docID: "foo",
1692 input: map[string]interface{}{"foo": "bar"},
1693 options: Params(testOptions),
1694 newRev: "1-xxx",
1695 },
1696 {
1697 name: "InvalidJSON",
1698 db: &DB{
1699 client: &Client{},
1700 driverDB: &mock.DB{
1701 PutFunc: putFunc,
1702 },
1703 },
1704 docID: "foo",
1705 input: json.RawMessage("Something bogus"),
1706 status: http.StatusInternalServerError,
1707 err: "Unexpected doc: failed to marshal actual value: invalid character 'S' looking for beginning of value",
1708 },
1709 {
1710 name: "Bytes",
1711 db: &DB{
1712 client: &Client{},
1713 driverDB: &mock.DB{
1714 PutFunc: putFunc,
1715 },
1716 },
1717 docID: "foo",
1718 input: []byte(`{"foo":"bar"}`),
1719 options: Params(testOptions),
1720 newRev: "1-xxx",
1721 },
1722 {
1723 name: "RawMessage",
1724 db: &DB{
1725 client: &Client{},
1726 driverDB: &mock.DB{
1727 PutFunc: putFunc,
1728 },
1729 },
1730 docID: "foo",
1731 input: json.RawMessage(`{"foo":"bar"}`),
1732 options: Params(testOptions),
1733 newRev: "1-xxx",
1734 },
1735 {
1736 name: "Reader",
1737 db: &DB{
1738 client: &Client{},
1739 driverDB: &mock.DB{
1740 PutFunc: putFunc,
1741 },
1742 },
1743 docID: "foo",
1744 input: strings.NewReader(`{"foo":"bar"}`),
1745 options: Params(testOptions),
1746 newRev: "1-xxx",
1747 },
1748 {
1749 name: "ErrorReader",
1750 db: &DB{
1751 client: &Client{},
1752 },
1753 docID: "foo",
1754 input: &errorReader{},
1755 status: http.StatusBadRequest,
1756 err: "errorReader",
1757 },
1758 {
1759 name: "client closed",
1760 db: &DB{
1761 client: &Client{
1762 closed: true,
1763 },
1764 },
1765 docID: "foo",
1766 status: http.StatusServiceUnavailable,
1767 err: "kivik: client closed",
1768 },
1769 {
1770 name: "db error",
1771 db: &DB{
1772 err: errors.New("db error"),
1773 },
1774 status: http.StatusInternalServerError,
1775 err: "db error",
1776 },
1777 }
1778 for _, test := range tests {
1779 func(test putTest) {
1780 t.Run(test.name, func(t *testing.T) {
1781 newRev, err := test.db.Put(context.Background(), test.docID, test.input, test.options)
1782 if d := internal.StatusErrorDiff(test.err, test.status, err); d != "" {
1783 t.Error(d)
1784 }
1785 if newRev != test.newRev {
1786 t.Errorf("Unexpected new rev: %s", newRev)
1787 }
1788 })
1789 }(test)
1790 }
1791 }
1792
1793 func TestExtractDocID(t *testing.T) {
1794 type ediTest struct {
1795 name string
1796 i interface{}
1797 id string
1798 expected bool
1799 }
1800 tests := []ediTest{
1801 {
1802 name: "nil",
1803 },
1804 {
1805 name: "string/interface map, no id",
1806 i: map[string]interface{}{
1807 "value": "foo",
1808 },
1809 },
1810 {
1811 name: "string/interface map, with id",
1812 i: map[string]interface{}{
1813 "_id": "foo",
1814 },
1815 id: "foo",
1816 expected: true,
1817 },
1818 {
1819 name: "string/string map, with id",
1820 i: map[string]string{
1821 "_id": "foo",
1822 },
1823 id: "foo",
1824 expected: true,
1825 },
1826 {
1827 name: "invalid JSON",
1828 i: make(chan int),
1829 },
1830 {
1831 name: "valid JSON",
1832 i: struct {
1833 ID string `json:"_id"`
1834 }{ID: "oink"},
1835 id: "oink",
1836 expected: true,
1837 },
1838 }
1839 for _, test := range tests {
1840 t.Run(test.name, func(t *testing.T) {
1841 id, ok := extractDocID(test.i)
1842 if ok != test.expected || test.id != id {
1843 t.Errorf("Expected %t/%s, got %t/%s", test.expected, test.id, ok, id)
1844 }
1845 })
1846 }
1847 }
1848
1849 func TestCreateDoc(t *testing.T) {
1850 tests := []struct {
1851 name string
1852 db *DB
1853 doc interface{}
1854 options Option
1855 docID, rev string
1856 status int
1857 err string
1858 }{
1859 {
1860 name: "error",
1861 db: &DB{
1862 client: &Client{},
1863 driverDB: &mock.DocCreator{
1864 CreateDocFunc: func(context.Context, interface{}, driver.Options) (string, string, error) {
1865 return "", "", &internal.Error{Status: http.StatusBadRequest, Err: errors.New("create error")}
1866 },
1867 },
1868 },
1869 status: http.StatusBadRequest,
1870 err: "create error",
1871 },
1872 {
1873 name: "success",
1874 db: &DB{
1875 client: &Client{},
1876 driverDB: &mock.DocCreator{
1877 CreateDocFunc: func(_ context.Context, doc interface{}, options driver.Options) (string, string, error) {
1878 gotOpts := map[string]interface{}{}
1879 options.Apply(gotOpts)
1880 expectedDoc := map[string]string{"type": "test"}
1881 if d := testy.DiffInterface(expectedDoc, doc); d != nil {
1882 return "", "", fmt.Errorf("Unexpected doc:\n%s", d)
1883 }
1884 if d := testy.DiffInterface(testOptions, gotOpts); d != nil {
1885 return "", "", fmt.Errorf("Unexpected options:\n%s", d)
1886 }
1887 return "foo", "1-xxx", nil
1888 },
1889 },
1890 },
1891 doc: map[string]string{"type": "test"},
1892 options: Params(testOptions),
1893 docID: "foo",
1894 rev: "1-xxx",
1895 },
1896 {
1897 name: "closed",
1898 db: &DB{
1899 client: &Client{
1900 closed: true,
1901 },
1902 driverDB: &mock.DocCreator{},
1903 },
1904 status: http.StatusServiceUnavailable,
1905 err: "kivik: client closed",
1906 },
1907 {
1908 name: "emulated with docID",
1909 db: &DB{
1910 client: &Client{},
1911 driverDB: &mock.DB{
1912 PutFunc: func(_ context.Context, docID string, doc interface{}, options driver.Options) (string, error) {
1913 gotOpts := map[string]interface{}{}
1914 options.Apply(gotOpts)
1915 expectedDoc := map[string]string{"_id": "foo", "type": "test"}
1916 if docID != "foo" {
1917 return "", fmt.Errorf("Unexpected docID: %s", docID)
1918 }
1919 if d := testy.DiffInterface(expectedDoc, doc); d != nil {
1920 return "", fmt.Errorf("Unexpected doc:\n%s", d)
1921 }
1922 if d := testy.DiffInterface(testOptions, gotOpts); d != nil {
1923 return "", fmt.Errorf("Unexpected options:\n%s", d)
1924 }
1925 return "1-xxx", nil
1926 },
1927 },
1928 },
1929 doc: map[string]string{"type": "test", "_id": "foo"},
1930 options: Params(testOptions),
1931 docID: "foo",
1932 rev: "1-xxx",
1933 },
1934 }
1935 for _, test := range tests {
1936 t.Run(test.name, func(t *testing.T) {
1937 docID, rev, err := test.db.CreateDoc(context.Background(), test.doc, test.options)
1938 if d := internal.StatusErrorDiff(test.err, test.status, err); d != "" {
1939 t.Error(d)
1940 }
1941 if docID != test.docID || rev != test.rev {
1942 t.Errorf("Unexpected result: %s / %s", docID, rev)
1943 }
1944 })
1945 }
1946 }
1947
1948 func TestDelete(t *testing.T) {
1949 tests := []struct {
1950 name string
1951 db *DB
1952 docID, rev string
1953 options Option
1954 newRev string
1955 status int
1956 err string
1957 }{
1958 {
1959 name: "no doc ID",
1960 db: &DB{
1961 client: &Client{},
1962 },
1963 status: http.StatusBadRequest,
1964 err: "kivik: docID required",
1965 },
1966 {
1967 name: "error",
1968 db: &DB{
1969 client: &Client{},
1970 driverDB: &mock.DB{
1971 DeleteFunc: func(context.Context, string, driver.Options) (string, error) {
1972 return "", &internal.Error{Status: http.StatusBadRequest, Err: errors.New("delete error")}
1973 },
1974 },
1975 },
1976 docID: "foo",
1977 status: http.StatusBadRequest,
1978 err: "delete error",
1979 },
1980 {
1981 name: "rev in opts",
1982 db: &DB{
1983 client: &Client{},
1984 driverDB: &mock.DB{
1985 DeleteFunc: func(_ context.Context, docID string, options driver.Options) (string, error) {
1986 const expectedDocID = "foo"
1987 if docID != expectedDocID {
1988 return "", fmt.Errorf("Unexpected docID: %s", docID)
1989 }
1990 gotOpts := map[string]interface{}{}
1991 options.Apply(gotOpts)
1992 wantOpts := map[string]interface{}{"rev": "1-xxx"}
1993 if d := testy.DiffInterface(wantOpts, gotOpts); d != nil {
1994 return "", fmt.Errorf("Unexpected options:\n%s", d)
1995 }
1996 return "2-xxx", nil
1997 },
1998 },
1999 },
2000 docID: "foo",
2001 options: Rev("1-xxx"),
2002 newRev: "2-xxx",
2003 },
2004 {
2005 name: "success",
2006 db: &DB{
2007 client: &Client{},
2008 driverDB: &mock.DB{
2009 DeleteFunc: func(_ context.Context, docID string, options driver.Options) (string, error) {
2010 const expectedDocID = "foo"
2011 if docID != expectedDocID {
2012 return "", fmt.Errorf("Unexpected docID: %s", docID)
2013 }
2014 wantOpts := map[string]interface{}{
2015 "foo": 123,
2016 "rev": "1-xxx",
2017 }
2018 gotOpts := map[string]interface{}{}
2019 options.Apply(gotOpts)
2020 if d := testy.DiffInterface(wantOpts, gotOpts); d != nil {
2021 return "", fmt.Errorf("Unexpected options:\n%s", d)
2022 }
2023 return "2-xxx", nil
2024 },
2025 },
2026 },
2027 docID: "foo",
2028 rev: "1-xxx",
2029 options: Params(testOptions),
2030 newRev: "2-xxx",
2031 },
2032 {
2033 name: "success, no opts",
2034 db: &DB{
2035 client: &Client{},
2036 driverDB: &mock.DB{
2037 DeleteFunc: func(_ context.Context, docID string, options driver.Options) (string, error) {
2038 const expectedDocID = "foo"
2039 if docID != expectedDocID {
2040 return "", fmt.Errorf("Unexpected docID: %s", docID)
2041 }
2042 wantOpts := map[string]interface{}{
2043 "rev": "1-xxx",
2044 }
2045 gotOpts := map[string]interface{}{}
2046 options.Apply(gotOpts)
2047 if d := testy.DiffInterface(wantOpts, gotOpts); d != nil {
2048 return "", fmt.Errorf("Unexpected options:\n%s", d)
2049 }
2050 return "2-xxx", nil
2051 },
2052 },
2053 },
2054 docID: "foo",
2055 rev: "1-xxx",
2056 newRev: "2-xxx",
2057 },
2058 {
2059 name: "closed",
2060 db: &DB{
2061 client: &Client{
2062 closed: true,
2063 },
2064 },
2065 status: http.StatusServiceUnavailable,
2066 err: "kivik: client closed",
2067 },
2068 {
2069 name: "db error",
2070 db: &DB{
2071 err: errors.New("db error"),
2072 },
2073 status: http.StatusInternalServerError,
2074 err: "db error",
2075 },
2076 }
2077
2078 for _, test := range tests {
2079 t.Run(test.name, func(t *testing.T) {
2080 newRev, err := test.db.Delete(context.Background(), test.docID, test.rev, test.options)
2081 if d := internal.StatusErrorDiff(test.err, test.status, err); d != "" {
2082 t.Error(d)
2083 }
2084 if newRev != test.newRev {
2085 t.Errorf("Unexpected newRev: %s", newRev)
2086 }
2087 })
2088 }
2089 }
2090
2091 func TestPutAttachment(t *testing.T) {
2092 tests := []struct {
2093 name string
2094 db *DB
2095 docID string
2096 att *Attachment
2097 options Option
2098 newRev string
2099 status int
2100 err string
2101
2102 body string
2103 }{
2104 {
2105 name: "db error",
2106 docID: "foo",
2107 db: &DB{
2108 client: &Client{},
2109 driverDB: &mock.DB{
2110 PutAttachmentFunc: func(context.Context, string, *driver.Attachment, driver.Options) (string, error) {
2111 return "", &internal.Error{Status: http.StatusBadRequest, Err: errors.New("db error")}
2112 },
2113 },
2114 },
2115 att: &Attachment{
2116 Filename: "foo.txt",
2117 Content: io.NopCloser(strings.NewReader("")),
2118 },
2119 status: http.StatusBadRequest,
2120 err: "db error",
2121 },
2122 {
2123 name: "no doc id",
2124 db: &DB{
2125 client: &Client{},
2126 },
2127 status: http.StatusBadRequest,
2128 err: "kivik: docID required",
2129 },
2130 {
2131 name: "no filename",
2132 db: &DB{
2133 client: &Client{},
2134 },
2135 docID: "foo",
2136 att: &Attachment{},
2137 status: http.StatusBadRequest,
2138 err: "kivik: filename required",
2139 },
2140 {
2141 name: "success",
2142 docID: "foo",
2143 db: &DB{
2144 client: &Client{},
2145 driverDB: &mock.DB{
2146 PutAttachmentFunc: func(_ context.Context, docID string, att *driver.Attachment, options driver.Options) (string, error) {
2147 const expectedDocID = "foo"
2148 const expectedContent = "Test file"
2149 expectedAtt := &driver.Attachment{
2150 Filename: "foo.txt",
2151 ContentType: "text/plain",
2152 }
2153 if docID != expectedDocID {
2154 return "", fmt.Errorf("Unexpected docID: %s", docID)
2155 }
2156 content, err := io.ReadAll(att.Content)
2157 if err != nil {
2158 t.Fatal(err)
2159 }
2160 if d := testy.DiffText(expectedContent, string(content)); d != nil {
2161 return "", fmt.Errorf("Unexpected content:\n%s", string(content))
2162 }
2163 att.Content = nil
2164 if d := testy.DiffInterface(expectedAtt, att); d != nil {
2165 return "", fmt.Errorf("Unexpected attachment:\n%s", d)
2166 }
2167 wantOpts := map[string]interface{}{"rev": "1-xxx"}
2168 gotOpts := map[string]interface{}{}
2169 options.Apply(gotOpts)
2170 if d := testy.DiffInterface(wantOpts, gotOpts); d != nil {
2171 return "", fmt.Errorf("Unexpected options:\n%s", d)
2172 }
2173 return "2-xxx", nil
2174 },
2175 },
2176 },
2177 att: &Attachment{
2178 Filename: "foo.txt",
2179 ContentType: "text/plain",
2180 Content: io.NopCloser(strings.NewReader("Test file")),
2181 },
2182 options: Rev("1-xxx"),
2183 newRev: "2-xxx",
2184 body: "Test file",
2185 },
2186 {
2187 name: "nil attachment",
2188 db: &DB{
2189 client: &Client{},
2190 },
2191 docID: "foo",
2192 status: http.StatusBadRequest,
2193 err: "kivik: attachment required",
2194 },
2195 {
2196 name: "client closed",
2197 db: &DB{
2198 client: &Client{
2199 closed: true,
2200 },
2201 },
2202 docID: "foo",
2203 att: &Attachment{
2204 Filename: "foo.txt",
2205 },
2206 status: http.StatusServiceUnavailable,
2207 err: "kivik: client closed",
2208 },
2209 }
2210 for _, test := range tests {
2211 t.Run(test.name, func(t *testing.T) {
2212 newRev, err := test.db.PutAttachment(context.Background(), test.docID, test.att, test.options)
2213 if d := internal.StatusErrorDiff(test.err, test.status, err); d != "" {
2214 t.Error(d)
2215 }
2216 if newRev != test.newRev {
2217 t.Errorf("Unexpected newRev: %s", newRev)
2218 }
2219 })
2220 }
2221 }
2222
2223 func TestDeleteAttachment(t *testing.T) {
2224 const (
2225 expectedDocID = "foo"
2226 expectedRev = "1-xxx"
2227 expectedFilename = "foo.txt"
2228 )
2229
2230 type tt struct {
2231 db *DB
2232 docID, rev, filename string
2233 options Option
2234
2235 newRev string
2236 status int
2237 err string
2238 }
2239
2240 tests := testy.NewTable()
2241 tests.Add("missing doc id", tt{
2242 db: &DB{
2243 client: &Client{},
2244 },
2245 status: http.StatusBadRequest,
2246 err: "kivik: docID required",
2247 })
2248 tests.Add("missing filename", tt{
2249 db: &DB{
2250 client: &Client{},
2251 },
2252 docID: "foo",
2253 status: http.StatusBadRequest,
2254 err: "kivik: filename required",
2255 })
2256 tests.Add("error", tt{
2257 docID: "foo",
2258 filename: expectedFilename,
2259 db: &DB{
2260 client: &Client{},
2261 driverDB: &mock.DB{
2262 DeleteAttachmentFunc: func(context.Context, string, string, driver.Options) (string, error) {
2263 return "", &internal.Error{Status: http.StatusBadRequest, Err: errors.New("db error")}
2264 },
2265 },
2266 },
2267 status: http.StatusBadRequest,
2268 err: "db error",
2269 })
2270 tests.Add("rev in options", func(t *testing.T) interface{} {
2271 return tt{
2272 docID: expectedDocID,
2273 filename: expectedFilename,
2274 options: Rev(expectedRev),
2275 db: &DB{
2276 client: &Client{},
2277 driverDB: &mock.DB{
2278 DeleteAttachmentFunc: func(_ context.Context, docID, filename string, options driver.Options) (string, error) {
2279 if docID != expectedDocID {
2280 t.Errorf("Unexpected docID: %s", docID)
2281 }
2282 if filename != expectedFilename {
2283 t.Errorf("Unexpected filename: %s", filename)
2284 }
2285 wantOpts := map[string]interface{}{"rev": expectedRev}
2286 gotOpts := map[string]interface{}{}
2287 options.Apply(gotOpts)
2288 if d := testy.DiffInterface(wantOpts, gotOpts); d != nil {
2289 t.Errorf("Unexpected options:\n%s", d)
2290 }
2291 return "2-xxx", nil
2292 },
2293 },
2294 },
2295 newRev: "2-xxx",
2296 }
2297 })
2298 tests.Add("success", func(t *testing.T) interface{} {
2299 return tt{
2300 docID: expectedDocID,
2301 rev: expectedRev,
2302 filename: expectedFilename,
2303 options: Params(testOptions),
2304 newRev: "2-xxx",
2305 db: &DB{
2306 client: &Client{},
2307 driverDB: &mock.DB{
2308 DeleteAttachmentFunc: func(_ context.Context, docID, filename string, options driver.Options) (string, error) {
2309 if docID != expectedDocID {
2310 t.Errorf("Unexpected docID: %s", docID)
2311 }
2312 if filename != expectedFilename {
2313 t.Errorf("Unexpected filename: %s", filename)
2314 }
2315 wantOpts := map[string]interface{}{
2316 "foo": 123,
2317 "rev": "1-xxx",
2318 }
2319 gotOpts := map[string]interface{}{}
2320 options.Apply(gotOpts)
2321 if d := testy.DiffInterface(wantOpts, gotOpts); d != nil {
2322 t.Errorf("Unexpected options:\n%s", d)
2323 }
2324 return "2-xxx", nil
2325 },
2326 },
2327 },
2328 }
2329 })
2330 tests.Add("closed", tt{
2331 db: &DB{
2332 client: &Client{
2333 closed: true,
2334 },
2335 },
2336 status: http.StatusServiceUnavailable,
2337 err: "kivik: client closed",
2338 })
2339 tests.Add("db error", tt{
2340 db: &DB{
2341 err: errors.New("db error"),
2342 },
2343 status: http.StatusInternalServerError,
2344 err: "db error",
2345 })
2346
2347 tests.Run(t, func(t *testing.T, tt tt) {
2348 newRev, err := tt.db.DeleteAttachment(context.Background(), tt.docID, tt.rev, tt.filename, tt.options)
2349 if d := internal.StatusErrorDiff(tt.err, tt.status, err); d != "" {
2350 t.Error(d)
2351 }
2352 if newRev != tt.newRev {
2353 t.Errorf("Unexpected new rev: %s", newRev)
2354 }
2355 })
2356 }
2357
2358 func TestGetAttachment(t *testing.T) {
2359 expectedDocID, expectedFilename := "foo", "foo.txt"
2360 type tt struct {
2361 db *DB
2362 docID, filename string
2363 options Option
2364
2365 content string
2366 expected *Attachment
2367 status int
2368 err string
2369 }
2370
2371 tests := testy.NewTable()
2372 tests.Add("error", tt{
2373 db: &DB{
2374 client: &Client{},
2375 driverDB: &mock.DB{
2376 GetAttachmentFunc: func(context.Context, string, string, driver.Options) (*driver.Attachment, error) {
2377 return nil, errors.New("fail")
2378 },
2379 },
2380 },
2381 docID: expectedDocID,
2382 filename: expectedFilename,
2383 status: 500,
2384 err: "fail",
2385 })
2386 tests.Add("success", func(t *testing.T) interface{} {
2387 return tt{
2388 docID: expectedDocID,
2389 filename: expectedFilename,
2390 options: Params(testOptions),
2391 content: "Test",
2392 expected: &Attachment{
2393 Filename: expectedFilename,
2394 ContentType: "text/plain",
2395 Size: 4,
2396 Digest: "md5-foo",
2397 },
2398 db: &DB{
2399 client: &Client{},
2400 driverDB: &mock.DB{
2401 GetAttachmentFunc: func(_ context.Context, docID, filename string, options driver.Options) (*driver.Attachment, error) {
2402 if docID != expectedDocID {
2403 t.Errorf("Unexpected docID: %s", docID)
2404 }
2405 if filename != expectedFilename {
2406 t.Errorf("Unexpected filename: %s", filename)
2407 }
2408 gotOpts := map[string]interface{}{}
2409 options.Apply(gotOpts)
2410 if d := testy.DiffInterface(testOptions, gotOpts); d != nil {
2411 t.Errorf("Unexpected options:\n%s", d)
2412 }
2413 return &driver.Attachment{
2414 Filename: expectedFilename,
2415 ContentType: "text/plain",
2416 Digest: "md5-foo",
2417 Size: 4,
2418 Content: body("Test"),
2419 }, nil
2420 },
2421 },
2422 },
2423 }
2424 })
2425 tests.Add("no docID", tt{
2426 db: &DB{
2427 client: &Client{},
2428 },
2429 status: http.StatusBadRequest,
2430 err: "kivik: docID required",
2431 })
2432 tests.Add("no filename", tt{
2433 db: &DB{
2434 client: &Client{},
2435 },
2436 docID: "foo",
2437 status: http.StatusBadRequest,
2438 err: "kivik: filename required",
2439 })
2440 tests.Add("client closed", tt{
2441 db: &DB{
2442 client: &Client{
2443 closed: true,
2444 },
2445 },
2446 status: http.StatusServiceUnavailable,
2447 err: "kivik: client closed",
2448 })
2449 tests.Add("db error", tt{
2450 db: &DB{
2451 err: errors.New("db error"),
2452 },
2453 status: http.StatusInternalServerError,
2454 err: "db error",
2455 })
2456
2457 tests.Run(t, func(t *testing.T, tt tt) {
2458 result, err := tt.db.GetAttachment(context.Background(), tt.docID, tt.filename, tt.options)
2459 if d := internal.StatusErrorDiff(tt.err, tt.status, err); d != "" {
2460 t.Error(d)
2461 }
2462 if err != nil {
2463 return
2464 }
2465 content, err := io.ReadAll(result.Content)
2466 if err != nil {
2467 t.Fatal(err)
2468 }
2469 if d := testy.DiffText(tt.content, string(content)); d != nil {
2470 t.Errorf("Unexpected content:\n%s", d)
2471 }
2472 _ = result.Content.Close()
2473 result.Content = nil
2474 if d := testy.DiffInterface(tt.expected, result); d != nil {
2475 t.Error(d)
2476 }
2477 })
2478 }
2479
2480 func TestGetAttachmentMeta(t *testing.T) {
2481 const expectedDocID, expectedFilename = "foo", "foo.txt"
2482 tests := []struct {
2483 name string
2484 db *DB
2485 docID, filename string
2486 options Option
2487
2488 expected *Attachment
2489 status int
2490 err string
2491 }{
2492 {
2493 name: "plain db, error",
2494 db: &DB{
2495 client: &Client{},
2496 driverDB: &mock.DB{
2497 GetAttachmentFunc: func(context.Context, string, string, driver.Options) (*driver.Attachment, error) {
2498 return nil, errors.New("fail")
2499 },
2500 },
2501 },
2502 docID: "foo",
2503 filename: expectedFilename,
2504 status: 500,
2505 err: "fail",
2506 },
2507 {
2508 name: "plain db, success",
2509 db: &DB{
2510 client: &Client{},
2511 driverDB: &mock.DB{
2512 GetAttachmentFunc: func(_ context.Context, docID, filename string, options driver.Options) (*driver.Attachment, error) {
2513 if docID != expectedDocID {
2514 return nil, fmt.Errorf("Unexpected docID: %s", docID)
2515 }
2516 if filename != expectedFilename {
2517 return nil, fmt.Errorf("Unexpected filename: %s", filename)
2518 }
2519 gotOpts := map[string]interface{}{}
2520 options.Apply(gotOpts)
2521 if d := testy.DiffInterface(testOptions, gotOpts); d != nil {
2522 return nil, fmt.Errorf("Unexpected options:\n%s", d)
2523 }
2524 return &driver.Attachment{
2525 Filename: "foo.txt",
2526 ContentType: "text/plain",
2527 Digest: "md5-foo",
2528 Size: 4,
2529 Content: body("Test"),
2530 }, nil
2531 },
2532 },
2533 },
2534 docID: "foo",
2535 filename: "foo.txt",
2536 options: Params(testOptions),
2537 expected: &Attachment{
2538 Filename: "foo.txt",
2539 ContentType: "text/plain",
2540 Digest: "md5-foo",
2541 Size: 4,
2542 Content: nilContent,
2543 },
2544 },
2545 {
2546 name: "error",
2547 db: &DB{
2548 client: &Client{},
2549 driverDB: &mock.AttachmentMetaGetter{
2550 GetAttachmentMetaFunc: func(context.Context, string, string, driver.Options) (*driver.Attachment, error) {
2551 return nil, errors.New("fail")
2552 },
2553 },
2554 },
2555 docID: "foo",
2556 filename: "foo.txt",
2557 status: 500,
2558 err: "fail",
2559 },
2560 {
2561 name: "success",
2562 db: &DB{
2563 client: &Client{},
2564 driverDB: &mock.AttachmentMetaGetter{
2565 GetAttachmentMetaFunc: func(_ context.Context, docID, filename string, options driver.Options) (*driver.Attachment, error) {
2566 expectedDocID, expectedFilename := "foo", "foo.txt"
2567 if docID != expectedDocID {
2568 return nil, fmt.Errorf("Unexpected docID: %s", docID)
2569 }
2570 if filename != expectedFilename {
2571 return nil, fmt.Errorf("Unexpected filename: %s", filename)
2572 }
2573 gotOpts := map[string]interface{}{}
2574 options.Apply(gotOpts)
2575 if d := testy.DiffInterface(testOptions, gotOpts); d != nil {
2576 return nil, fmt.Errorf("Unexpected options:\n%s", d)
2577 }
2578 return &driver.Attachment{
2579 Filename: "foo.txt",
2580 ContentType: "text/plain",
2581 Digest: "md5-foo",
2582 Size: 4,
2583 }, nil
2584 },
2585 },
2586 },
2587 docID: "foo",
2588 filename: "foo.txt",
2589 options: Params(testOptions),
2590 expected: &Attachment{
2591 Filename: "foo.txt",
2592 ContentType: "text/plain",
2593 Digest: "md5-foo",
2594 Size: 4,
2595 Content: nilContent,
2596 },
2597 },
2598 {
2599 name: "no doc id",
2600 db: &DB{
2601 client: &Client{},
2602 },
2603 status: http.StatusBadRequest,
2604 err: "kivik: docID required",
2605 },
2606 {
2607 name: "no filename",
2608 db: &DB{
2609 client: &Client{},
2610 },
2611 docID: "foo",
2612 status: http.StatusBadRequest,
2613 err: "kivik: filename required",
2614 },
2615 {
2616 name: "client closed",
2617 db: &DB{
2618 client: &Client{
2619 closed: true,
2620 },
2621 },
2622 docID: "foo",
2623 filename: "bar.txt",
2624 status: http.StatusServiceUnavailable,
2625 err: "kivik: client closed",
2626 },
2627 {
2628 name: "db error",
2629 db: &DB{
2630 err: errors.New("db error"),
2631 },
2632 status: http.StatusInternalServerError,
2633 err: "db error",
2634 },
2635 }
2636 for _, test := range tests {
2637 t.Run(test.name, func(t *testing.T) {
2638 result, err := test.db.GetAttachmentMeta(context.Background(), test.docID, test.filename, test.options)
2639 if d := internal.StatusErrorDiff(test.err, test.status, err); d != "" {
2640 t.Error(d)
2641 }
2642 if d := testy.DiffInterface(test.expected, result); d != nil {
2643 t.Error(d)
2644 }
2645 })
2646 }
2647 }
2648
2649 func TestPurge(t *testing.T) {
2650 type purgeTest struct {
2651 name string
2652 db *DB
2653 docMap map[string][]string
2654
2655 expected *PurgeResult
2656 status int
2657 err string
2658 }
2659
2660 docMap := map[string][]string{
2661 "foo": {"1-abc", "2-xyz"},
2662 }
2663
2664 tests := []purgeTest{
2665 {
2666 name: "success, nothing purged",
2667 db: &DB{
2668 client: &Client{},
2669 driverDB: &mock.Purger{
2670 PurgeFunc: func(_ context.Context, dm map[string][]string) (*driver.PurgeResult, error) {
2671 if d := testy.DiffInterface(docMap, dm); d != nil {
2672 return nil, fmt.Errorf("Unexpected docmap: %s", d)
2673 }
2674 return &driver.PurgeResult{Seq: 2}, nil
2675 },
2676 },
2677 },
2678 docMap: docMap,
2679 expected: &PurgeResult{
2680 Seq: 2,
2681 },
2682 },
2683 {
2684 name: "success, all purged",
2685 db: &DB{
2686 client: &Client{},
2687 driverDB: &mock.Purger{
2688 PurgeFunc: func(_ context.Context, dm map[string][]string) (*driver.PurgeResult, error) {
2689 if d := testy.DiffInterface(docMap, dm); d != nil {
2690 return nil, fmt.Errorf("Unexpected docmap: %s", d)
2691 }
2692 return &driver.PurgeResult{Seq: 2, Purged: docMap}, nil
2693 },
2694 },
2695 },
2696 docMap: docMap,
2697 expected: &PurgeResult{
2698 Seq: 2,
2699 Purged: docMap,
2700 },
2701 },
2702 {
2703 name: "non-purger",
2704 db: &DB{
2705 client: &Client{},
2706 driverDB: &mock.DB{},
2707 },
2708 status: http.StatusNotImplemented,
2709 err: "kivik: purge not supported by driver",
2710 },
2711 {
2712 name: "couch 2.0-2.1 example",
2713 db: &DB{
2714 client: &Client{},
2715 driverDB: &mock.Purger{
2716 PurgeFunc: func(context.Context, map[string][]string) (*driver.PurgeResult, error) {
2717 return nil, &internal.Error{Status: http.StatusNotImplemented, Message: "this feature is not yet implemented"}
2718 },
2719 },
2720 },
2721 status: http.StatusNotImplemented,
2722 err: "this feature is not yet implemented",
2723 },
2724 {
2725 name: "client closed",
2726 db: &DB{
2727 client: &Client{
2728 closed: true,
2729 },
2730 },
2731 status: http.StatusServiceUnavailable,
2732 err: "kivik: client closed",
2733 },
2734 {
2735 name: "db error",
2736 db: &DB{
2737 err: errors.New("db error"),
2738 },
2739 status: http.StatusInternalServerError,
2740 err: "db error",
2741 },
2742 }
2743 for _, test := range tests {
2744 t.Run(test.name, func(t *testing.T) {
2745 result, err := test.db.Purge(context.Background(), test.docMap)
2746 if d := internal.StatusErrorDiff(test.err, test.status, err); d != "" {
2747 t.Error(d)
2748 }
2749 if d := testy.DiffInterface(test.expected, result); d != nil {
2750 t.Error(d)
2751 }
2752 })
2753 }
2754 }
2755
2756 func TestBulkGet(t *testing.T) {
2757 type bulkGetTest struct {
2758 name string
2759 db *DB
2760 docs []BulkGetReference
2761 options Option
2762
2763 expected *ResultSet
2764 status int
2765 err string
2766 }
2767
2768 tests := []bulkGetTest{
2769 {
2770 name: "non-bulkGetter",
2771 db: &DB{
2772 client: &Client{},
2773 driverDB: &mock.DB{},
2774 },
2775 status: http.StatusNotImplemented,
2776 err: "kivik: bulk get not supported by driver",
2777 },
2778 {
2779 name: "query error",
2780 db: &DB{
2781 client: &Client{},
2782 driverDB: &mock.BulkGetter{
2783 BulkGetFunc: func(context.Context, []driver.BulkGetReference, driver.Options) (driver.Rows, error) {
2784 return nil, errors.New("query error")
2785 },
2786 },
2787 },
2788 status: http.StatusInternalServerError,
2789 err: "query error",
2790 },
2791 {
2792 name: "success",
2793 db: &DB{
2794 client: &Client{},
2795 driverDB: &mock.BulkGetter{
2796 BulkGetFunc: func(context.Context, []driver.BulkGetReference, driver.Options) (driver.Rows, error) {
2797 return &mock.Rows{ID: "bulkGet1"}, nil
2798 },
2799 },
2800 },
2801 expected: &ResultSet{
2802 iter: &iter{
2803 feed: &rowsIterator{
2804 Rows: &mock.Rows{ID: "bulkGet1"},
2805 },
2806 curVal: &driver.Row{},
2807 },
2808 rowsi: &mock.Rows{ID: "bulkGet1"},
2809 },
2810 },
2811 {
2812 name: "client closed",
2813 db: &DB{
2814 client: &Client{
2815 closed: true,
2816 },
2817 driverDB: &mock.BulkGetter{},
2818 },
2819 status: http.StatusServiceUnavailable,
2820 err: "kivik: client closed",
2821 },
2822 }
2823
2824 for _, test := range tests {
2825 t.Run(test.name, func(t *testing.T) {
2826 rs := test.db.BulkGet(context.Background(), test.docs, test.options)
2827 err := rs.Err()
2828 if d := internal.StatusErrorDiff(test.err, test.status, err); d != "" {
2829 t.Error(d)
2830 }
2831 if err != nil {
2832 return
2833 }
2834 rs.cancel = nil
2835 rs.onClose = nil
2836 if d := testy.DiffInterface(test.expected, rs); d != nil {
2837 t.Error(d)
2838 }
2839 })
2840 }
2841 t.Run("standalone", func(t *testing.T) {
2842 t.Run("after err, close doesn't block", func(t *testing.T) {
2843 db := &DB{
2844 client: &Client{},
2845 driverDB: &mock.BulkGetter{
2846 BulkGetFunc: func(context.Context, []driver.BulkGetReference, driver.Options) (driver.Rows, error) {
2847 return nil, errors.New("unf")
2848 },
2849 },
2850 }
2851 rows := db.BulkGet(context.Background(), nil)
2852 if err := rows.Err(); err == nil {
2853 t.Fatal("expected an error, got none")
2854 }
2855 _ = db.Close()
2856 })
2857 })
2858 }
2859
2860 func newDB(db driver.DB) *DB {
2861 client := &Client{}
2862 client.wg.Add(1)
2863 return &DB{
2864 client: client,
2865 driverDB: db,
2866 }
2867 }
2868
2869 func TestDBClose(t *testing.T) {
2870 t.Parallel()
2871
2872 type tst struct {
2873 db *DB
2874 err string
2875 }
2876 tests := testy.NewTable()
2877 tests.Add("error", tst{
2878 db: newDB(&mock.DB{
2879 CloseFunc: func() error {
2880 return errors.New("close err")
2881 },
2882 }),
2883 err: "close err",
2884 })
2885 tests.Add("success", tst{
2886 db: newDB(&mock.DB{
2887 CloseFunc: func() error {
2888 return nil
2889 },
2890 }),
2891 })
2892
2893 tests.Run(t, func(t *testing.T, test tst) {
2894 err := test.db.Close()
2895 if !testy.ErrorMatches(test.err, err) {
2896 t.Errorf("Unexpected error: %s", err)
2897 }
2898 })
2899
2900 t.Run("blocks", func(t *testing.T) {
2901 t.Parallel()
2902
2903 const delay = 100 * time.Millisecond
2904
2905 type tt struct {
2906 db driver.DB
2907 work func(*testing.T, *DB)
2908 }
2909
2910 tests := testy.NewTable()
2911 tests.Add("Flush", tt{
2912 db: &mock.Flusher{
2913 FlushFunc: func(context.Context) error {
2914 time.Sleep(delay)
2915 return nil
2916 },
2917 },
2918 work: func(_ *testing.T, db *DB) {
2919 _ = db.Flush(context.Background())
2920 },
2921 })
2922 tests.Add("AllDocs", tt{
2923 db: &mock.DB{
2924 AllDocsFunc: func(context.Context, driver.Options) (driver.Rows, error) {
2925 return &mock.Rows{
2926 NextFunc: func(*driver.Row) error {
2927 time.Sleep(delay)
2928 return io.EOF
2929 },
2930 }, nil
2931 },
2932 },
2933 work: func(t *testing.T, db *DB) {
2934 u := db.AllDocs(context.Background())
2935 for u.Next() {
2936 }
2937 if u.Err() != nil {
2938 t.Fatal(u.Err())
2939 }
2940 },
2941 })
2942 tests.Add("BulkDocs", tt{
2943 db: &mock.BulkDocer{
2944 BulkDocsFunc: func(context.Context, []interface{}, driver.Options) ([]driver.BulkResult, error) {
2945 time.Sleep(delay)
2946 return []driver.BulkResult{}, nil
2947 },
2948 },
2949 work: func(t *testing.T, db *DB) {
2950 _, err := db.BulkDocs(context.Background(), []interface{}{
2951 map[string]string{"_id": "foo"},
2952 })
2953 if err != nil {
2954 t.Fatal(err)
2955 }
2956 },
2957 })
2958
2959 tests.Run(t, func(t *testing.T, tt tt) {
2960 t.Parallel()
2961
2962 db := &DB{
2963 client: &Client{},
2964 driverDB: tt.db,
2965 }
2966
2967 start := time.Now()
2968 tt.work(t, db)
2969 time.Sleep(delay / 2)
2970 _ = db.Close()
2971 if elapsed := time.Since(start); elapsed < delay {
2972 t.Errorf("db.Close() didn't block long enough (%v < %v)", elapsed, delay)
2973 }
2974 })
2975 })
2976 }
2977
2978 func TestRevsDiff(t *testing.T) {
2979 type tt struct {
2980 db *DB
2981 revMap interface{}
2982 status int
2983 err string
2984 expected *ResultSet
2985 }
2986 tests := testy.NewTable()
2987 tests.Add("non-DBReplicator", tt{
2988 db: &DB{
2989 client: &Client{},
2990 driverDB: &mock.DB{},
2991 },
2992 status: http.StatusNotImplemented,
2993 err: "kivik: _revs_diff not supported by driver",
2994 })
2995 tests.Add("network error", tt{
2996 db: &DB{
2997 client: &Client{},
2998 driverDB: &mock.RevsDiffer{
2999 RevsDiffFunc: func(context.Context, interface{}) (driver.Rows, error) {
3000 return nil, errors.New("net error")
3001 },
3002 },
3003 },
3004 status: http.StatusInternalServerError,
3005 err: "net error",
3006 })
3007 tests.Add("success", tt{
3008 db: &DB{
3009 client: &Client{},
3010 driverDB: &mock.RevsDiffer{
3011 RevsDiffFunc: func(context.Context, interface{}) (driver.Rows, error) {
3012 return &mock.Rows{ID: "a"}, nil
3013 },
3014 },
3015 },
3016 expected: &ResultSet{
3017 iter: &iter{
3018 feed: &rowsIterator{
3019 Rows: &mock.Rows{ID: "a"},
3020 },
3021 curVal: &driver.Row{},
3022 },
3023 rowsi: &mock.Rows{ID: "a"},
3024 },
3025 })
3026 tests.Add("client closed", tt{
3027 db: &DB{
3028 client: &Client{
3029 closed: true,
3030 },
3031 driverDB: &mock.RevsDiffer{},
3032 },
3033 status: http.StatusServiceUnavailable,
3034 err: "kivik: client closed",
3035 })
3036 tests.Add("db error", tt{
3037 db: &DB{
3038 err: errors.New("db error"),
3039 },
3040 status: http.StatusInternalServerError,
3041 err: "db error",
3042 })
3043
3044 tests.Run(t, func(t *testing.T, tt tt) {
3045 rs := tt.db.RevsDiff(context.Background(), tt.revMap)
3046 err := rs.Err()
3047 if d := internal.StatusErrorDiff(tt.err, tt.status, err); d != "" {
3048 t.Error(d)
3049 }
3050 if err != nil {
3051 return
3052 }
3053 rs.cancel = nil
3054 rs.onClose = nil
3055 if d := testy.DiffInterface(tt.expected, rs); d != nil {
3056 t.Error(d)
3057 }
3058 })
3059 }
3060
3061 func TestPartitionStats(t *testing.T) {
3062 type tt struct {
3063 db *DB
3064 name string
3065 status int
3066 err string
3067 }
3068 tests := testy.NewTable()
3069 tests.Add("non-PartitionedDB", tt{
3070 db: &DB{
3071 client: &Client{},
3072 driverDB: &mock.DB{},
3073 },
3074 status: http.StatusNotImplemented,
3075 err: "kivik: partitions not supported by driver",
3076 })
3077 tests.Add("error", tt{
3078 db: &DB{
3079 client: &Client{},
3080 driverDB: &mock.PartitionedDB{
3081 PartitionStatsFunc: func(context.Context, string) (*driver.PartitionStats, error) {
3082 return nil, &internal.Error{Status: http.StatusBadGateway, Err: errors.New("stats error")}
3083 },
3084 },
3085 },
3086 status: http.StatusBadGateway,
3087 err: "stats error",
3088 })
3089 tests.Add("success", tt{
3090 db: &DB{
3091 client: &Client{},
3092 driverDB: &mock.PartitionedDB{
3093 PartitionStatsFunc: func(_ context.Context, name string) (*driver.PartitionStats, error) {
3094 if name != "partXX" {
3095 return nil, fmt.Errorf("Unexpected name: %s", name)
3096 }
3097 return &driver.PartitionStats{
3098 DBName: "dbXX",
3099 Partition: name,
3100 DocCount: 123,
3101 }, nil
3102 },
3103 },
3104 },
3105 name: "partXX",
3106 })
3107 tests.Add("client closed", tt{
3108 db: &DB{
3109 client: &Client{
3110 closed: true,
3111 },
3112 },
3113 status: http.StatusServiceUnavailable,
3114 err: "kivik: client closed",
3115 })
3116 tests.Add("db error", tt{
3117 db: &DB{
3118 err: errors.New("db error"),
3119 },
3120 status: http.StatusInternalServerError,
3121 err: "db error",
3122 })
3123
3124 tests.Run(t, func(t *testing.T, tt tt) {
3125 result, err := tt.db.PartitionStats(context.Background(), tt.name)
3126 if d := internal.StatusErrorDiff(tt.err, tt.status, err); d != "" {
3127 t.Error(d)
3128 }
3129 if err != nil {
3130 return
3131 }
3132 if d := testy.DiffInterface(testy.Snapshot(t), result); d != nil {
3133 t.Error(d)
3134 }
3135 })
3136 }
3137
View as plain text