...

Source file src/github.com/go-kivik/kivik/v4/couchdb/attachments_test.go

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

     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 couchdb
    14  
    15  import (
    16  	"context"
    17  	"errors"
    18  	"fmt"
    19  	"io"
    20  	"mime"
    21  	"net/http"
    22  	"strings"
    23  	"testing"
    24  
    25  	"gitlab.com/flimzy/testy"
    26  
    27  	kivik "github.com/go-kivik/kivik/v4"
    28  	"github.com/go-kivik/kivik/v4/driver"
    29  	internal "github.com/go-kivik/kivik/v4/int/errors"
    30  	"github.com/go-kivik/kivik/v4/int/mock"
    31  )
    32  
    33  type closer struct {
    34  	io.Reader
    35  	closed bool
    36  }
    37  
    38  var _ io.ReadCloser = &closer{}
    39  
    40  func (c *closer) Close() error {
    41  	c.closed = true
    42  	return nil
    43  }
    44  
    45  func TestPutAttachment(t *testing.T) {
    46  	type paoTest struct {
    47  		name    string
    48  		db      *db
    49  		id      string
    50  		att     *driver.Attachment
    51  		options kivik.Option
    52  
    53  		newRev string
    54  		status int
    55  		err    string
    56  		final  func(*testing.T)
    57  	}
    58  	tests := []paoTest{
    59  		{
    60  			name:   "missing docID",
    61  			status: http.StatusBadRequest,
    62  			err:    "kivik: docID required",
    63  		},
    64  		{
    65  			name:    "nil attachment",
    66  			id:      "foo",
    67  			options: kivik.Rev("1-xxx"),
    68  			status:  http.StatusBadRequest,
    69  			err:     "kivik: att required",
    70  		},
    71  		{
    72  			name:    "missing filename",
    73  			id:      "foo",
    74  			options: kivik.Rev("1-xxx"),
    75  			att:     &driver.Attachment{},
    76  			status:  http.StatusBadRequest,
    77  			err:     "kivik: att.Filename required",
    78  		},
    79  		{
    80  			name:    "no body",
    81  			id:      "foo",
    82  			options: kivik.Rev("1-xxx"),
    83  			att: &driver.Attachment{
    84  				Filename:    "x.jpg",
    85  				ContentType: "image/jpeg",
    86  			},
    87  			status: http.StatusBadRequest,
    88  			err:    "kivik: att.Content required",
    89  		},
    90  		{
    91  			name:    "network error",
    92  			db:      newTestDB(nil, errors.New("net error")),
    93  			id:      "foo",
    94  			options: kivik.Rev("1-xxx"),
    95  			att: &driver.Attachment{
    96  				Filename:    "x.jpg",
    97  				ContentType: "image/jpeg",
    98  				Content:     Body("x"),
    99  			},
   100  			status: http.StatusBadGateway,
   101  			err:    `Put "?http://example.com/testdb/foo/x.jpg\?rev=1-xxx"?: net error`,
   102  		},
   103  		{
   104  			name:    "1.6.1",
   105  			id:      "foo",
   106  			options: kivik.Rev("1-4c6114c65e295552ab1019e2b046b10e"),
   107  			att: &driver.Attachment{
   108  				Filename:    "foo.txt",
   109  				ContentType: "text/plain",
   110  				Content:     Body("Hello, World!"),
   111  			},
   112  			db: newCustomDB(func(req *http.Request) (*http.Response, error) {
   113  				defer req.Body.Close() // nolint: errcheck
   114  				if ct, _, _ := mime.ParseMediaType(req.Header.Get("Content-Type")); ct != "text/plain" {
   115  					return nil, fmt.Errorf("Unexpected Content-Type: %s", ct)
   116  				}
   117  				expectedRev := "1-4c6114c65e295552ab1019e2b046b10e"
   118  				if rev := req.URL.Query().Get("rev"); rev != expectedRev {
   119  					return nil, fmt.Errorf("Unexpected rev: %s", rev)
   120  				}
   121  				body, err := io.ReadAll(req.Body)
   122  				if err != nil {
   123  					return nil, err
   124  				}
   125  				expected := "Hello, World!"
   126  				if d := testy.DiffText(expected, string(body)); d != nil {
   127  					t.Errorf("Unexpected body:\n%s", d)
   128  				}
   129  				return &http.Response{
   130  					StatusCode: 201,
   131  					Header: http.Header{
   132  						"Server":         {"CouchDB/1.6.1 (Erlang OTP/17)"},
   133  						"Location":       {"http://localhost:5984/foo/foo/foo.txt"},
   134  						"ETag":           {`"2-8ee3381d24ee4ac3e9f8c1f6c7395641"`},
   135  						"Date":           {"Thu, 26 Oct 2017 20:51:35 GMT"},
   136  						"Content-Type":   {"text/plain; charset=utf-8"},
   137  						"Content-Length": {"66"},
   138  						"Cache-Control":  {"must-revalidate"},
   139  					},
   140  					Body: Body(`{"ok":true,"id":"foo","rev":"2-8ee3381d24ee4ac3e9f8c1f6c7395641"}`),
   141  				}, nil
   142  			}),
   143  			newRev: "2-8ee3381d24ee4ac3e9f8c1f6c7395641",
   144  		},
   145  		{
   146  			name: "no rev",
   147  			db: newCustomDB(func(req *http.Request) (*http.Response, error) {
   148  				if _, ok := req.URL.Query()["rev"]; ok {
   149  					t.Errorf("'rev' should not be present in the query")
   150  				}
   151  				return nil, errors.New("ignore this error")
   152  			}),
   153  			id: "foo",
   154  			att: &driver.Attachment{
   155  				Filename:    "foo.txt",
   156  				ContentType: "text/plain",
   157  				Content:     Body("x"),
   158  			},
   159  			status: http.StatusBadGateway,
   160  			err:    `Put "?http://example.com/testdb/foo/foo.txt"?: ignore this error`,
   161  		},
   162  		{
   163  			name: "with options",
   164  			db:   newTestDB(nil, errors.New("success")),
   165  			id:   "foo",
   166  			att: &driver.Attachment{
   167  				Filename:    "foo.txt",
   168  				ContentType: "text/plain",
   169  				Content:     Body("x"),
   170  			},
   171  			options: kivik.Params(map[string]interface{}{
   172  				"foo": "oink",
   173  				"rev": "1-xxx",
   174  			}),
   175  			status: http.StatusBadGateway,
   176  			err:    "foo=oink",
   177  		},
   178  		{
   179  			name: "invalid options",
   180  			db:   &db{},
   181  			id:   "foo",
   182  			att: &driver.Attachment{
   183  				Filename:    "foo.txt",
   184  				ContentType: "text/plain",
   185  				Content:     Body("x"),
   186  			},
   187  			options: kivik.Param("foo", make(chan int)),
   188  			status:  http.StatusBadRequest,
   189  			err:     "kivik: invalid type chan int for options",
   190  		},
   191  		{
   192  			name: "full commit",
   193  			db: newCustomDB(func(req *http.Request) (*http.Response, error) {
   194  				if err := consume(req.Body); err != nil {
   195  					return nil, err
   196  				}
   197  				if fullCommit := req.Header.Get("X-Couch-Full-Commit"); fullCommit != "true" { // nolint: goconst
   198  					return nil, errors.New("X-Couch-Full-Commit not true")
   199  				}
   200  				return nil, errors.New("success")
   201  			}),
   202  			id: "foo",
   203  			att: &driver.Attachment{
   204  				Filename:    "foo.txt",
   205  				ContentType: "text/plain",
   206  				Content:     Body("x"),
   207  			},
   208  			options: multiOptions{
   209  				OptionFullCommit(),
   210  				kivik.Rev("1-xxx"),
   211  			},
   212  			status: http.StatusBadGateway,
   213  			err:    "success",
   214  		},
   215  		func() paoTest {
   216  			body := &closer{Reader: strings.NewReader("x")}
   217  			return paoTest{
   218  				name: "ReadCloser",
   219  				db: newCustomDB(func(req *http.Request) (*http.Response, error) {
   220  					if err := consume(req.Body); err != nil {
   221  						return nil, err
   222  					}
   223  					if fullCommit := req.Header.Get("X-Couch-Full-Commit"); fullCommit != "true" {
   224  						return nil, errors.New("X-Couch-Full-Commit not true")
   225  					}
   226  					return nil, errors.New("success")
   227  				}),
   228  				id: "foo",
   229  				att: &driver.Attachment{
   230  					Filename:    "foo.txt",
   231  					ContentType: "text/plain",
   232  					Content:     Body("x"),
   233  				},
   234  				options: multiOptions{
   235  					kivik.Rev("1-xxx"),
   236  					OptionFullCommit(),
   237  				},
   238  				status: http.StatusBadGateway,
   239  				err:    "success",
   240  				final: func(t *testing.T) { //nolint:thelper // Not a helper
   241  					if !body.closed {
   242  						t.Fatal("body wasn't closed")
   243  					}
   244  				},
   245  			}
   246  		}(),
   247  	}
   248  	for _, test := range tests {
   249  		t.Run(test.name, func(t *testing.T) {
   250  			opts := test.options
   251  			if opts == nil {
   252  				opts = mock.NilOption
   253  			}
   254  			newRev, err := test.db.PutAttachment(context.Background(), test.id, test.att, opts)
   255  			if d := internal.StatusErrorDiffRE(test.err, test.status, err); d != "" {
   256  				t.Error(d)
   257  			}
   258  			if err != nil {
   259  				return
   260  			}
   261  			if newRev != test.newRev {
   262  				t.Errorf("Expected %s, got %s\n", test.newRev, newRev)
   263  			}
   264  			if test.final != nil {
   265  				test.final(t)
   266  			}
   267  		})
   268  	}
   269  }
   270  
   271  func TestGetAttachmentMeta(t *testing.T) {
   272  	tests := []struct {
   273  		name         string
   274  		db           *db
   275  		id, filename string
   276  
   277  		expected *driver.Attachment
   278  		status   int
   279  		err      string
   280  	}{
   281  		{
   282  			name:     "network error",
   283  			id:       "foo",
   284  			filename: "foo.txt",
   285  			db:       newTestDB(nil, errors.New("net error")),
   286  			status:   http.StatusBadGateway,
   287  			err:      `^Head "?http://example.com/testdb/foo/foo.txt"?: net error$`,
   288  		},
   289  		{
   290  			name:     "1.6.1",
   291  			id:       "foo",
   292  			filename: "foo.txt",
   293  			db: newTestDB(&http.Response{
   294  				StatusCode: 200,
   295  				Header: http.Header{
   296  					"Server":         {"CouchDB/1.6.1 (Erlang OTP/17)"},
   297  					"ETag":           {`"gSr8dSmynwAoomH7V6RVYw=="`},
   298  					"Date":           {"Thu, 26 Oct 2017 21:15:13 GMT"},
   299  					"Content-Type":   {"text/plain"},
   300  					"Content-Length": {"13"},
   301  					"Cache-Control":  {"must-revalidate"},
   302  					"Accept-Ranges":  {"none"},
   303  				},
   304  				Body: Body(""),
   305  			}, nil),
   306  			expected: &driver.Attachment{
   307  				ContentType: "text/plain",
   308  				Digest:      "gSr8dSmynwAoomH7V6RVYw==",
   309  				Content:     Body(""),
   310  			},
   311  		},
   312  	}
   313  	for _, test := range tests {
   314  		t.Run(test.name, func(t *testing.T) {
   315  			att, err := test.db.GetAttachmentMeta(context.Background(), test.id, test.filename, mock.NilOption)
   316  			if d := internal.StatusErrorDiffRE(test.err, test.status, err); d != "" {
   317  				t.Error(d)
   318  			}
   319  			if d := testy.DiffInterface(test.expected, att); d != nil {
   320  				t.Errorf("Unexpected attachment:\n%s", d)
   321  			}
   322  		})
   323  	}
   324  }
   325  
   326  func TestGetDigest(t *testing.T) {
   327  	tests := []struct {
   328  		name     string
   329  		resp     *http.Response
   330  		expected string
   331  		status   int
   332  		err      string
   333  	}{
   334  		{
   335  			name:   "no etag header",
   336  			resp:   &http.Response{},
   337  			status: http.StatusBadGateway,
   338  			err:    "ETag header not found",
   339  		},
   340  		{
   341  			name: "Standard ETag header",
   342  			resp: &http.Response{
   343  				Header: http.Header{"ETag": []string{`"ENGoH7oK8V9R3BMnfDHZmw=="`}},
   344  			},
   345  			expected: "ENGoH7oK8V9R3BMnfDHZmw==",
   346  		},
   347  		{
   348  			name: "normalized Etag header",
   349  			resp: &http.Response{
   350  				Header: http.Header{"Etag": []string{`"ENGoH7oK8V9R3BMnfDHZmw=="`}},
   351  			},
   352  			expected: "ENGoH7oK8V9R3BMnfDHZmw==",
   353  		},
   354  	}
   355  	for _, test := range tests {
   356  		t.Run(test.name, func(t *testing.T) {
   357  			digest, err := getDigest(test.resp)
   358  			if !testy.ErrorMatches(test.err, err) {
   359  				t.Errorf("Unexpected error: %s", err)
   360  			}
   361  			if digest != test.expected {
   362  				t.Errorf("Unexpected result: %0x", digest)
   363  			}
   364  		})
   365  	}
   366  }
   367  
   368  func TestGetAttachment(t *testing.T) {
   369  	tests := []struct {
   370  		name         string
   371  		db           *db
   372  		id, filename string
   373  		options      kivik.Option
   374  
   375  		expected *driver.Attachment
   376  		content  string
   377  		status   int
   378  		err      string
   379  	}{
   380  		{
   381  			name:     "network error",
   382  			id:       "foo",
   383  			filename: "foo.txt",
   384  			db:       newTestDB(nil, errors.New("net error")),
   385  			status:   http.StatusBadGateway,
   386  			err:      `Get "?http://example.com/testdb/foo/foo.txt"?: net error`,
   387  		},
   388  		{
   389  			name:     "1.6.1",
   390  			id:       "foo",
   391  			filename: "foo.txt",
   392  			db: newCustomDB(func(*http.Request) (*http.Response, error) {
   393  				return &http.Response{
   394  					StatusCode: 200,
   395  					Header: http.Header{
   396  						"Server":         {"CouchDB/1.6.1 (Erlang OTP/17)"},
   397  						"ETag":           {`"gSr8dSmynwAoomH7V6RVYw=="`},
   398  						"Date":           {"Fri, 27 Oct 2017 11:24:50 GMT"},
   399  						"Content-Type":   {"text/plain"},
   400  						"Content-Length": {"13"},
   401  						"Cache-Control":  {"must-revalidate"},
   402  						"Accept-Ranges":  {"none"},
   403  					},
   404  					Body: Body(`Hello, world!`),
   405  				}, nil
   406  			}),
   407  			expected: &driver.Attachment{
   408  				ContentType: "text/plain",
   409  				Digest:      "gSr8dSmynwAoomH7V6RVYw==",
   410  			},
   411  			content: "Hello, world!",
   412  		},
   413  	}
   414  	for _, test := range tests {
   415  		t.Run(test.name, func(t *testing.T) {
   416  			opts := test.options
   417  			if opts == nil {
   418  				opts = mock.NilOption
   419  			}
   420  			att, err := test.db.GetAttachment(context.Background(), test.id, test.filename, opts)
   421  			if d := internal.StatusErrorDiffRE(test.err, test.status, err); d != "" {
   422  				t.Error(d)
   423  			}
   424  			if err != nil {
   425  				return
   426  			}
   427  			fileContent, err := io.ReadAll(att.Content)
   428  			if err != nil {
   429  				t.Fatal(err)
   430  			}
   431  			if d := testy.DiffText(test.content, string(fileContent)); d != nil {
   432  				t.Errorf("Unexpected content:\n%s", d)
   433  			}
   434  			_ = att.Content.Close()
   435  			att.Content = nil // Determinism
   436  			if d := testy.DiffInterface(test.expected, att); d != nil {
   437  				t.Errorf("Unexpected attachment:\n%s", d)
   438  			}
   439  		})
   440  	}
   441  }
   442  
   443  func TestFetchAttachment(t *testing.T) {
   444  	tests := []struct {
   445  		name                 string
   446  		db                   *db
   447  		method, id, filename string
   448  		options              kivik.Option
   449  
   450  		resp   *http.Response
   451  		status int
   452  		err    string
   453  	}{
   454  		{
   455  			name:   "no method",
   456  			status: http.StatusInternalServerError,
   457  			err:    "method required",
   458  		},
   459  		{
   460  			name:   "no docID",
   461  			method: "GET",
   462  			status: http.StatusBadRequest,
   463  			err:    "kivik: docID required",
   464  		},
   465  		{
   466  			name:   "no filename",
   467  			method: "GET",
   468  			id:     "foo",
   469  			status: http.StatusBadRequest,
   470  			err:    "kivik: filename required",
   471  		},
   472  		{
   473  			name:     "no rev",
   474  			method:   "GET",
   475  			id:       "foo",
   476  			filename: "foo.txt",
   477  			db:       newTestDB(nil, errors.New("ignore this error")),
   478  			status:   http.StatusBadGateway,
   479  			err:      "http://example.com/testdb/foo/foo.txt",
   480  		},
   481  		{
   482  			name:     "success",
   483  			method:   "GET",
   484  			id:       "foo",
   485  			filename: "foo.txt",
   486  			db: newTestDB(&http.Response{
   487  				StatusCode: 200,
   488  			}, nil),
   489  			resp: &http.Response{
   490  				StatusCode: 200,
   491  			},
   492  		},
   493  		{
   494  			name:     "options",
   495  			db:       newTestDB(nil, errors.New("success")),
   496  			method:   "GET",
   497  			id:       "foo",
   498  			filename: "foo.txt",
   499  			options:  kivik.Param("foo", "bar"),
   500  			status:   http.StatusBadGateway,
   501  			err:      "foo=bar",
   502  		},
   503  		{
   504  			name:     "invalid option",
   505  			db:       &db{},
   506  			method:   "GET",
   507  			id:       "foo",
   508  			filename: "foo.txt",
   509  			options:  kivik.Param("foo", make(chan int)),
   510  			status:   http.StatusBadRequest,
   511  			err:      "kivik: invalid type chan int for options",
   512  		},
   513  		{
   514  			name: "If-None-Match",
   515  			db: newCustomDB(func(req *http.Request) (*http.Response, error) {
   516  				if err := consume(req.Body); err != nil {
   517  					return nil, err
   518  				}
   519  				if inm := req.Header.Get("If-None-Match"); inm != `"foo"` {
   520  					return nil, fmt.Errorf(`If-None-Match: %s != "foo"`, inm)
   521  				}
   522  				return nil, errors.New("success")
   523  			}),
   524  			method:   "GET",
   525  			id:       "foo",
   526  			filename: "foo.txt",
   527  			options:  OptionIfNoneMatch("foo"),
   528  			status:   http.StatusBadGateway,
   529  			err:      "success",
   530  		},
   531  	}
   532  	for _, test := range tests {
   533  		t.Run(test.name, func(t *testing.T) {
   534  			opts := test.options
   535  			if opts == nil {
   536  				opts = mock.NilOption
   537  			}
   538  			resp, err := test.db.fetchAttachment(context.Background(), test.method, test.id, test.filename, opts)
   539  			if d := internal.StatusErrorDiffRE(test.err, test.status, err); d != "" {
   540  				t.Error(d)
   541  			}
   542  			if err != nil {
   543  				return
   544  			}
   545  			if d := testy.DiffJSON(test.resp.Body, resp.Body); d != nil {
   546  				t.Errorf("Response body: %s", d)
   547  			}
   548  			// Normalize the response for diffing
   549  			resp.Request = nil
   550  			resp.Body = nil
   551  			test.resp.Body = nil
   552  
   553  			if d := testy.DiffInterface(test.resp, resp); d != nil {
   554  				t.Error(d)
   555  			}
   556  		})
   557  	}
   558  }
   559  
   560  func TestDecodeAttachment(t *testing.T) {
   561  	tests := []struct {
   562  		name     string
   563  		resp     *http.Response
   564  		expected *driver.Attachment
   565  		content  string
   566  		status   int
   567  		err      string
   568  	}{
   569  		{
   570  			name:   "no content type",
   571  			resp:   &http.Response{},
   572  			status: http.StatusBadGateway,
   573  			err:    "no Content-Type in response",
   574  		},
   575  		{
   576  			name: "no etag header",
   577  			resp: &http.Response{
   578  				Header: http.Header{"Content-Type": {"text/plain"}},
   579  			},
   580  			status: http.StatusBadGateway,
   581  			err:    "ETag header not found",
   582  		},
   583  		{
   584  			name: "success",
   585  			resp: &http.Response{
   586  				Header: http.Header{
   587  					"Content-Type": {"text/plain"},
   588  					"ETag":         {`"gSr8dSmynwAoomH7V6RVYw=="`},
   589  				},
   590  				Body: Body("Hello, World!"),
   591  			},
   592  			expected: &driver.Attachment{
   593  				ContentType: "text/plain",
   594  				Digest:      "gSr8dSmynwAoomH7V6RVYw==",
   595  			},
   596  			content: "Hello, World!",
   597  		},
   598  	}
   599  	for _, test := range tests {
   600  		t.Run(test.name, func(t *testing.T) {
   601  			att, err := decodeAttachment(test.resp)
   602  			if d := internal.StatusErrorDiff(test.err, test.status, err); d != "" {
   603  				t.Error(d)
   604  			}
   605  			if err != nil {
   606  				return
   607  			}
   608  			fileContent, err := io.ReadAll(att.Content)
   609  			if err != nil {
   610  				t.Fatal(err)
   611  			}
   612  			if d := testy.DiffText(test.content, string(fileContent)); d != nil {
   613  				t.Errorf("Unexpected content:\n%s", d)
   614  			}
   615  			_ = att.Content.Close()
   616  			att.Content = nil // Determinism
   617  			if d := testy.DiffInterface(test.expected, att); d != nil {
   618  				t.Errorf("Unexpected attachment:\n%s", d)
   619  			}
   620  		})
   621  	}
   622  }
   623  
   624  func TestDeleteAttachment(t *testing.T) {
   625  	tests := []struct {
   626  		name         string
   627  		db           *db
   628  		id, filename string
   629  		options      kivik.Option
   630  
   631  		newRev string
   632  		status int
   633  		err    string
   634  	}{
   635  		{
   636  			name:   "no doc id",
   637  			status: http.StatusBadRequest,
   638  			err:    "kivik: docID required",
   639  		},
   640  		{
   641  			name:   "no rev",
   642  			id:     "foo",
   643  			status: http.StatusBadRequest,
   644  			err:    "kivik: rev required",
   645  		},
   646  		{
   647  			name:    "no filename",
   648  			id:      "foo",
   649  			options: kivik.Rev("1-xxx"),
   650  			status:  http.StatusBadRequest,
   651  			err:     "kivik: filename required",
   652  		},
   653  		{
   654  			name:     "network error",
   655  			id:       "foo",
   656  			options:  kivik.Rev("1-xxx"),
   657  			filename: "foo.txt",
   658  			db:       newTestDB(nil, errors.New("net error")),
   659  			status:   http.StatusBadGateway,
   660  			err:      `(Delete "?http://example.com/testdb/foo/foo.txt\\?rev=1-xxx"?: )?net error`,
   661  		},
   662  		{
   663  			name:     "success 1.6.1",
   664  			id:       "foo",
   665  			options:  kivik.Rev("2-8ee3381d24ee4ac3e9f8c1f6c7395641"),
   666  			filename: "foo.txt",
   667  			db: newTestDB(&http.Response{
   668  				StatusCode: 200,
   669  				Header: http.Header{
   670  					"Server":         {"CouchDB/1.6.1 (Erlang OTP/17)"},
   671  					"ETag":           {`"3-231a932924f61816915289fecd35b14a"`},
   672  					"Date":           {"Fri, 27 Oct 2017 13:30:40 GMT"},
   673  					"Content-Type":   {"text/plain; charset=utf-8"},
   674  					"Content-Length": {"66"},
   675  					"Cache-Control":  {"must-revalidate"},
   676  				},
   677  				Body: Body(`{"ok":true,"id":"foo","rev":"3-231a932924f61816915289fecd35b14a"}`),
   678  			}, nil),
   679  			newRev: "3-231a932924f61816915289fecd35b14a",
   680  		},
   681  		{
   682  			name: "with options",
   683  			db: newCustomDB(func(req *http.Request) (*http.Response, error) {
   684  				if err := consume(req.Body); err != nil {
   685  					return nil, err
   686  				}
   687  				if foo := req.URL.Query().Get("foo"); foo != "oink" {
   688  					return nil, fmt.Errorf("Unexpected query foo=%s", foo)
   689  				}
   690  				return nil, errors.New("success")
   691  			}),
   692  			id:       "foo",
   693  			filename: "foo.txt",
   694  			options: kivik.Params(map[string]interface{}{
   695  				"rev": "1-xxx",
   696  				"foo": "oink",
   697  			}),
   698  			status: http.StatusBadGateway,
   699  			err:    "success",
   700  		},
   701  		{
   702  			name:     "invalid option",
   703  			db:       &db{},
   704  			id:       "foo",
   705  			filename: "foo.txt",
   706  			options: kivik.Params(map[string]interface{}{
   707  				"rev": "1-xxx",
   708  				"foo": make(chan int),
   709  			}),
   710  			status: http.StatusBadRequest,
   711  			err:    "kivik: invalid type chan int for options",
   712  		},
   713  		{
   714  			name: "full commit",
   715  			db: newCustomDB(func(req *http.Request) (*http.Response, error) {
   716  				if err := consume(req.Body); err != nil {
   717  					return nil, err
   718  				}
   719  				if fullCommit := req.Header.Get("X-Couch-Full-Commit"); fullCommit != "true" {
   720  					return nil, errors.New("X-Couch-Full-Commit not true")
   721  				}
   722  				return nil, errors.New("success")
   723  			}),
   724  			id:       "foo",
   725  			filename: "foo.txt",
   726  			options: multiOptions{
   727  				kivik.Rev("1-xxx"),
   728  				OptionFullCommit(),
   729  			},
   730  			status: http.StatusBadGateway,
   731  			err:    "success",
   732  		},
   733  	}
   734  	for _, test := range tests {
   735  		t.Run(test.name, func(t *testing.T) {
   736  			opts := test.options
   737  			if opts == nil {
   738  				opts = mock.NilOption
   739  			}
   740  			newRev, err := test.db.DeleteAttachment(context.Background(), test.id, test.filename, opts)
   741  			if d := internal.StatusErrorDiffRE(test.err, test.status, err); d != "" {
   742  				t.Error(d)
   743  			}
   744  			if newRev != test.newRev {
   745  				t.Errorf("Unexpected new rev: %s", newRev)
   746  			}
   747  		})
   748  	}
   749  }
   750  

View as plain text