github.com/go-kivik/kivik/v4@v4.3.2/db_test.go (about) 1 // Licensed under the Apache License, Version 2.0 (the "License"); you may not 2 // use this file except in compliance with the License. You may obtain a copy of 3 // the License at 4 // 5 // http://www.apache.org/licenses/LICENSE-2.0 6 // 7 // Unless required by applicable law or agreed to in writing, software 8 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 9 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 10 // License for the specific language governing permissions and limitations under 11 // the License. 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 // Determinism 130 rs.onClose = nil // Determinism 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() // Should not block 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 // Determinism 323 rs.onClose = nil // Determinism 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() // Should not block 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 // Determinism 436 rs.onClose = nil // Determinism 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() // Should not block 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" // nolint: goconst 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 // Determinism 551 rs.onClose = nil // Determinism 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 // Determinism 761 rs.onClose = nil // Determinism 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) { // nolint: gocyclo 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) { // nolint: gocyclo 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 // Determinism 2835 rs.onClose = nil // Determinism 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() // Should not block 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) { //nolint:thelper // Not a helper 2934 u := db.AllDocs(context.Background()) 2935 for u.Next() { //nolint:revive // intentional empty block 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) { //nolint:thelper // Not a helper 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 // Determinism 3054 rs.onClose = nil // Determinism 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 }