...

Source file src/github.com/go-kivik/kivik/v4/db_test.go

Documentation: github.com/go-kivik/kivik/v4

     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  }
  3137  

View as plain text