...

Source file src/github.com/go-kivik/kivik/v4/changes_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  	"errors"
    18  	"fmt"
    19  	"io"
    20  	"net/http"
    21  	"strconv"
    22  	"testing"
    23  
    24  	"github.com/google/go-cmp/cmp"
    25  	"gitlab.com/flimzy/testy"
    26  
    27  	"github.com/go-kivik/kivik/v4/driver"
    28  	internal "github.com/go-kivik/kivik/v4/int/errors"
    29  	"github.com/go-kivik/kivik/v4/int/mock"
    30  )
    31  
    32  func TestChangesNext(t *testing.T) {
    33  	tests := []struct {
    34  		name     string
    35  		changes  *Changes
    36  		expected bool
    37  	}{
    38  		{
    39  			name: "nothing more",
    40  			changes: &Changes{
    41  				iter: &iter{state: stateClosed},
    42  			},
    43  			expected: false,
    44  		},
    45  		{
    46  			name: "more",
    47  			changes: &Changes{
    48  				iter: &iter{
    49  					feed: &mockIterator{
    50  						NextFunc: func(_ interface{}) error { return nil },
    51  					},
    52  				},
    53  			},
    54  			expected: true,
    55  		},
    56  	}
    57  	for _, test := range tests {
    58  		t.Run(test.name, func(t *testing.T) {
    59  			result := test.changes.Next()
    60  			if result != test.expected {
    61  				t.Errorf("Unexpected result: %v", result)
    62  			}
    63  		})
    64  	}
    65  }
    66  
    67  func TestChangesErr(t *testing.T) {
    68  	const expected = "foo error"
    69  	c := &Changes{
    70  		iter: &iter{err: errors.New(expected)},
    71  	}
    72  	err := c.Err()
    73  	if !testy.ErrorMatches(expected, err) {
    74  		t.Errorf("Unexpected error: %s", err)
    75  	}
    76  }
    77  
    78  func TestChangesClose(t *testing.T) {
    79  	const expected = "close error"
    80  	c := &Changes{
    81  		iter: &iter{
    82  			feed: &mockIterator{CloseFunc: func() error { return errors.New(expected) }},
    83  		},
    84  	}
    85  	err := c.Close()
    86  	if !testy.ErrorMatches(expected, err) {
    87  		t.Errorf("Unexpected error: %s", err)
    88  	}
    89  }
    90  
    91  func TestChangesIteratorNext(t *testing.T) {
    92  	const expected = "foo error"
    93  	c := &changesIterator{
    94  		Changes: &mock.Changes{
    95  			NextFunc: func(_ *driver.Change) error { return errors.New(expected) },
    96  		},
    97  	}
    98  	var i driver.Change
    99  	err := c.Next(&i)
   100  	if !testy.ErrorMatches(expected, err) {
   101  		t.Errorf("Unexpected error: %s", err)
   102  	}
   103  }
   104  
   105  func TestChangesIteratorNew(t *testing.T) {
   106  	ch := newChanges(context.Background(), nil, &mock.Changes{})
   107  	expected := &Changes{
   108  		iter: &iter{
   109  			feed: &changesIterator{
   110  				Changes: &mock.Changes{},
   111  			},
   112  			curVal: &driver.Change{},
   113  		},
   114  		changesi: &mock.Changes{},
   115  	}
   116  	ch.cancel = nil // determinism
   117  	if d := testy.DiffInterface(expected, ch); d != nil {
   118  		t.Error(d)
   119  	}
   120  }
   121  
   122  func TestChangesGetters(t *testing.T) {
   123  	changes := []*driver.Change{
   124  		{
   125  			ID:      "foo",
   126  			Deleted: true,
   127  			Changes: []string{"1", "2", "3"},
   128  			Seq:     "2-foo",
   129  		},
   130  	}
   131  	c := newChanges(context.Background(), nil, &mock.Changes{
   132  		NextFunc: func(c *driver.Change) error {
   133  			if len(changes) == 0 {
   134  				return io.EOF
   135  			}
   136  			change := changes[0]
   137  			changes = changes[1:]
   138  			*c = *change
   139  			return nil
   140  		},
   141  		PendingFunc: func() int64 { return 123 },
   142  		LastSeqFunc: func() string { return "3-bar" },
   143  		ETagFunc:    func() string { return "etag-foo" },
   144  	})
   145  	_ = c.Next()
   146  
   147  	t.Run("Changes", func(t *testing.T) {
   148  		expected := []string{"1", "2", "3"}
   149  		result := c.Changes()
   150  		if d := testy.DiffInterface(expected, result); d != nil {
   151  			t.Error(d)
   152  		}
   153  	})
   154  
   155  	t.Run("Deleted", func(t *testing.T) {
   156  		expected := true
   157  		result := c.Deleted()
   158  		if expected != result {
   159  			t.Errorf("Unexpected result: %v", result)
   160  		}
   161  	})
   162  
   163  	t.Run("ID", func(t *testing.T) {
   164  		expected := "foo"
   165  		result := c.ID()
   166  		if expected != result {
   167  			t.Errorf("Unexpected result: %v", result)
   168  		}
   169  	})
   170  	t.Run("Seq", func(t *testing.T) {
   171  		expected := "2-foo"
   172  		result := c.Seq()
   173  		if expected != result {
   174  			t.Errorf("Unexpected result: %v", result)
   175  		}
   176  	})
   177  	t.Run("ETag", func(t *testing.T) {
   178  		expected := "etag-foo"
   179  		result := c.ETag()
   180  		if expected != result {
   181  			t.Errorf("Unexpected result: %v", result)
   182  		}
   183  	})
   184  	t.Run("Metadata", func(t *testing.T) {
   185  		_ = c.Next()
   186  		t.Run("LastSeq", func(t *testing.T) {
   187  			expected := "3-bar"
   188  			meta, err := c.Metadata()
   189  			if err != nil {
   190  				t.Fatal(err)
   191  			}
   192  			if expected != meta.LastSeq {
   193  				t.Errorf("Unexpected LastSeq: %v", meta.LastSeq)
   194  			}
   195  		})
   196  		t.Run("Pending", func(t *testing.T) {
   197  			expected := int64(123)
   198  			meta, err := c.Metadata()
   199  			if err != nil {
   200  				t.Fatal(err)
   201  			}
   202  			if expected != meta.Pending {
   203  				t.Errorf("Unexpected Pending: %v", meta.Pending)
   204  			}
   205  		})
   206  	})
   207  }
   208  
   209  func TestChangesScanDoc(t *testing.T) {
   210  	tests := []struct {
   211  		name     string
   212  		changes  *Changes
   213  		expected interface{}
   214  		status   int
   215  		err      string
   216  	}{
   217  		{
   218  			name: "success",
   219  			changes: &Changes{
   220  				iter: &iter{
   221  					state: stateRowReady,
   222  					curVal: &driver.Change{
   223  						Doc: []byte(`{"foo":123.4}`),
   224  					},
   225  				},
   226  			},
   227  			expected: map[string]interface{}{"foo": 123.4},
   228  		},
   229  		{
   230  			name: "closed",
   231  			changes: &Changes{
   232  				iter: &iter{
   233  					state: stateClosed,
   234  				},
   235  			},
   236  			status: http.StatusBadRequest,
   237  			err:    "kivik: Iterator is closed",
   238  		},
   239  	}
   240  	for _, test := range tests {
   241  		t.Run(test.name, func(t *testing.T) {
   242  			var result interface{}
   243  			err := test.changes.ScanDoc(&result)
   244  			if d := internal.StatusErrorDiff(test.err, test.status, err); d != "" {
   245  				t.Error(d)
   246  			}
   247  			if d := testy.DiffInterface(test.expected, result); d != nil {
   248  				t.Error(d)
   249  			}
   250  		})
   251  	}
   252  }
   253  
   254  func TestChanges(t *testing.T) {
   255  	tests := []struct {
   256  		name     string
   257  		db       *DB
   258  		opts     Option
   259  		expected *Changes
   260  		status   int
   261  		err      string
   262  	}{
   263  		{
   264  			name: "db error",
   265  			db: &DB{
   266  				client: &Client{},
   267  				driverDB: &mock.DB{
   268  					ChangesFunc: func(context.Context, driver.Options) (driver.Changes, error) {
   269  						return nil, errors.New("db error")
   270  					},
   271  				},
   272  			},
   273  			status: 500,
   274  			err:    "db error",
   275  		},
   276  		{
   277  			name: "success",
   278  			db: &DB{
   279  				client: &Client{},
   280  				driverDB: &mock.DB{
   281  					ChangesFunc: func(_ context.Context, options driver.Options) (driver.Changes, error) {
   282  						expectedOpts := map[string]interface{}{"foo": 123.4}
   283  						gotOpts := map[string]interface{}{}
   284  						options.Apply(gotOpts)
   285  						if d := testy.DiffInterface(expectedOpts, gotOpts); d != nil {
   286  							return nil, fmt.Errorf("Unexpected options:\n%s", d)
   287  						}
   288  						return &mock.Changes{}, nil
   289  					},
   290  				},
   291  			},
   292  			opts: Param("foo", 123.4),
   293  			expected: &Changes{
   294  				iter: &iter{
   295  					feed: &changesIterator{
   296  						Changes: &mock.Changes{},
   297  					},
   298  					curVal: &driver.Change{},
   299  				},
   300  				changesi: &mock.Changes{},
   301  			},
   302  		},
   303  		{
   304  			name: "client closed",
   305  			db: &DB{
   306  				client: &Client{
   307  					closed: true,
   308  				},
   309  			},
   310  			status: http.StatusServiceUnavailable,
   311  			err:    "kivik: client closed",
   312  		},
   313  		{
   314  			name: "db error",
   315  			db: &DB{
   316  				err: errors.New("db error"),
   317  			},
   318  			status: http.StatusInternalServerError,
   319  			err:    "db error",
   320  		},
   321  	}
   322  	for _, test := range tests {
   323  		t.Run(test.name, func(t *testing.T) {
   324  			result := test.db.Changes(context.Background(), test.opts)
   325  			err := result.Err()
   326  			if d := internal.StatusErrorDiff(test.err, test.status, err); d != "" {
   327  				t.Error(d)
   328  			}
   329  			if err != nil {
   330  				return
   331  			}
   332  			result.cancel = nil  // Determinism
   333  			result.onClose = nil // Determinism
   334  			if d := testy.DiffInterface(test.expected, result); d != nil {
   335  				t.Error(d)
   336  			}
   337  		})
   338  	}
   339  	t.Run("standalone", func(t *testing.T) {
   340  		t.Run("after err, close doesn't block", func(t *testing.T) {
   341  			db := &DB{
   342  				client: &Client{},
   343  				driverDB: &mock.DB{
   344  					ChangesFunc: func(context.Context, driver.Options) (driver.Changes, error) {
   345  						return nil, errors.New("unf")
   346  					},
   347  				},
   348  			}
   349  			rows := db.Changes(context.Background())
   350  			if err := rows.Err(); err == nil {
   351  				t.Fatal("expected an error, got none")
   352  			}
   353  			_ = db.Close() // Should not block
   354  		})
   355  	})
   356  }
   357  
   358  func TestChanges_uninitialized_should_not_panic(*testing.T) {
   359  	// These must not panic, because they can be called before iterating
   360  	// begins.
   361  	c := &Changes{}
   362  	_, _ = c.Metadata()
   363  	_ = c.ETag()
   364  }
   365  
   366  func TestChanges_Next_resets_iterator_value(t *testing.T) {
   367  	idx := 0
   368  	db := &DB{
   369  		client: &Client{},
   370  		driverDB: &mock.DB{
   371  			ChangesFunc: func(context.Context, driver.Options) (driver.Changes, error) {
   372  				return &mock.Changes{
   373  					NextFunc: func(change *driver.Change) error {
   374  						idx++
   375  						switch idx {
   376  						case 1:
   377  							change.ID = strconv.Itoa(idx)
   378  							return nil
   379  						case 2:
   380  							return nil
   381  						}
   382  						return io.EOF
   383  					},
   384  				}, nil
   385  			},
   386  		},
   387  	}
   388  
   389  	changes := db.Changes(context.Background())
   390  
   391  	wantIDs := []string{"1", ""}
   392  	gotIDs := []string{}
   393  	for changes.Next() {
   394  		gotIDs = append(gotIDs, changes.ID())
   395  	}
   396  	if d := cmp.Diff(wantIDs, gotIDs); d != "" {
   397  		t.Error(d)
   398  	}
   399  }
   400  

View as plain text