...

Source file src/github.com/go-kivik/kivik/v4/updates_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  	"io"
    19  	"net/http"
    20  	"strconv"
    21  	"testing"
    22  
    23  	"github.com/google/go-cmp/cmp"
    24  	"gitlab.com/flimzy/testy"
    25  
    26  	"github.com/go-kivik/kivik/v4/driver"
    27  	internal "github.com/go-kivik/kivik/v4/int/errors"
    28  	"github.com/go-kivik/kivik/v4/int/mock"
    29  )
    30  
    31  func TestDBUpdatesNext(t *testing.T) {
    32  	tests := []struct {
    33  		name     string
    34  		updates  *DBUpdates
    35  		expected bool
    36  	}{
    37  		{
    38  			name: "nothing more",
    39  			updates: &DBUpdates{
    40  				iter: &iter{state: stateClosed},
    41  			},
    42  			expected: false,
    43  		},
    44  		{
    45  			name: "more",
    46  			updates: &DBUpdates{
    47  				iter: &iter{
    48  					feed: &mockIterator{
    49  						NextFunc: func(_ interface{}) error { return nil },
    50  					},
    51  				},
    52  			},
    53  			expected: true,
    54  		},
    55  	}
    56  	for _, test := range tests {
    57  		t.Run(test.name, func(t *testing.T) {
    58  			result := test.updates.Next()
    59  			if result != test.expected {
    60  				t.Errorf("Unexpected result: %v", result)
    61  			}
    62  		})
    63  	}
    64  }
    65  
    66  func TestDBUpdatesClose(t *testing.T) {
    67  	expected := "close error"
    68  	u := &DBUpdates{
    69  		iter: &iter{
    70  			feed: &mockIterator{CloseFunc: func() error { return errors.New(expected) }},
    71  		},
    72  	}
    73  	err := u.Close()
    74  	if !testy.ErrorMatches(expected, err) {
    75  		t.Errorf("Unexpected error: %s", err)
    76  	}
    77  }
    78  
    79  func TestDBUpdatesErr(t *testing.T) {
    80  	expected := "foo error"
    81  	u := &DBUpdates{
    82  		iter: &iter{err: errors.New(expected)},
    83  	}
    84  	err := u.Err()
    85  	if !testy.ErrorMatches(expected, err) {
    86  		t.Errorf("Unexpected error: %s", err)
    87  	}
    88  }
    89  
    90  func TestDBUpdatesIteratorNext(t *testing.T) {
    91  	expected := "foo error"
    92  	u := &updatesIterator{
    93  		DBUpdates: &mock.DBUpdates{
    94  			NextFunc: func(_ *driver.DBUpdate) error { return errors.New(expected) },
    95  		},
    96  	}
    97  	var i driver.DBUpdate
    98  	err := u.Next(&i)
    99  	if !testy.ErrorMatches(expected, err) {
   100  		t.Errorf("Unexpected error: %s", err)
   101  	}
   102  }
   103  
   104  func TestDBUpdatesIteratorNew(t *testing.T) {
   105  	u := newDBUpdates(context.Background(), nil, &mock.DBUpdates{})
   106  	expected := &DBUpdates{
   107  		iter: &iter{
   108  			feed: &updatesIterator{
   109  				DBUpdates: &mock.DBUpdates{},
   110  			},
   111  			curVal: &driver.DBUpdate{},
   112  		},
   113  	}
   114  	u.cancel = nil // determinism
   115  	if d := testy.DiffInterface(expected, u); d != nil {
   116  		t.Error(d)
   117  	}
   118  }
   119  
   120  func TestDBUpdateGetters(t *testing.T) {
   121  	dbname := "foo"
   122  	updateType := "chicken"
   123  	seq := "abc123"
   124  	u := &DBUpdates{
   125  		iter: &iter{
   126  			state: stateRowReady,
   127  			curVal: &driver.DBUpdate{
   128  				DBName: dbname,
   129  				Type:   updateType,
   130  				Seq:    seq,
   131  			},
   132  		},
   133  	}
   134  
   135  	t.Run("DBName", func(t *testing.T) {
   136  		result := u.DBName()
   137  		if result != dbname {
   138  			t.Errorf("Unexpected result: %s", result)
   139  		}
   140  	})
   141  
   142  	t.Run("Type", func(t *testing.T) {
   143  		result := u.Type()
   144  		if result != updateType {
   145  			t.Errorf("Unexpected result: %s", result)
   146  		}
   147  	})
   148  
   149  	t.Run("Seq", func(t *testing.T) {
   150  		result := u.Seq()
   151  		if result != seq {
   152  			t.Errorf("Unexpected result: %s", result)
   153  		}
   154  	})
   155  
   156  	t.Run("LastSeq, should error during iteration", func(t *testing.T) {
   157  		result, err := u.LastSeq()
   158  		if result != "" {
   159  			t.Errorf("Unexpected result: %s", result)
   160  		}
   161  		if !testy.ErrorMatches("LastSeq must not be called until results iteration is complete", err) {
   162  			t.Errorf("Unexpected error: %s", err)
   163  		}
   164  	})
   165  
   166  	t.Run("Not Ready", func(t *testing.T) {
   167  		u.state = stateReady
   168  
   169  		t.Run("DBName", func(t *testing.T) {
   170  			result := u.DBName()
   171  			if result != "" {
   172  				t.Errorf("Unexpected result: %s", result)
   173  			}
   174  		})
   175  
   176  		t.Run("Type", func(t *testing.T) {
   177  			result := u.Type()
   178  			if result != "" {
   179  				t.Errorf("Unexpected result: %s", result)
   180  			}
   181  		})
   182  
   183  		t.Run("Seq", func(t *testing.T) {
   184  			result := u.Seq()
   185  			if result != "" {
   186  				t.Errorf("Unexpected result: %s", result)
   187  			}
   188  		})
   189  	})
   190  }
   191  
   192  func TestDBUpdates(t *testing.T) {
   193  	tests := []struct {
   194  		name     string
   195  		client   *Client
   196  		expected *DBUpdates
   197  		status   int
   198  		err      string
   199  	}{
   200  		{
   201  			name: "non-DBUpdater",
   202  			client: &Client{
   203  				driverClient: &mock.Client{},
   204  			},
   205  			status: http.StatusNotImplemented,
   206  			err:    "kivik: driver does not implement DBUpdater",
   207  		},
   208  		{
   209  			name: "db error",
   210  			client: &Client{
   211  				driverClient: &mock.DBUpdater{
   212  					DBUpdatesFunc: func(context.Context, driver.Options) (driver.DBUpdates, error) {
   213  						return nil, errors.New("db error")
   214  					},
   215  				},
   216  			},
   217  			status: http.StatusInternalServerError,
   218  			err:    "db error",
   219  		},
   220  		{
   221  			name: "success",
   222  			client: &Client{
   223  				driverClient: &mock.DBUpdater{
   224  					DBUpdatesFunc: func(context.Context, driver.Options) (driver.DBUpdates, error) {
   225  						return &mock.DBUpdates{ID: "a"}, nil
   226  					},
   227  				},
   228  			},
   229  			expected: &DBUpdates{
   230  				iter: &iter{
   231  					feed: &updatesIterator{
   232  						DBUpdates: &mock.DBUpdates{ID: "a"},
   233  					},
   234  					curVal: &driver.DBUpdate{},
   235  				},
   236  			},
   237  		},
   238  		{
   239  			name: "client closed",
   240  			client: &Client{
   241  				closed:       true,
   242  				driverClient: &mock.DBUpdater{},
   243  			},
   244  			status: http.StatusServiceUnavailable,
   245  			err:    "kivik: client closed",
   246  		},
   247  	}
   248  	for _, test := range tests {
   249  		t.Run(test.name, func(t *testing.T) {
   250  			result := test.client.DBUpdates(context.Background())
   251  			err := result.Err()
   252  			if d := internal.StatusErrorDiff(test.err, test.status, err); d != "" {
   253  				t.Error(d)
   254  			}
   255  			if err != nil {
   256  				return
   257  			}
   258  			result.cancel = nil  // Determinism
   259  			result.onClose = nil // Determinism
   260  			if d := testy.DiffInterface(test.expected, result); d != nil {
   261  				t.Error(d)
   262  			}
   263  		})
   264  	}
   265  	t.Run("standalone", func(t *testing.T) {
   266  		t.Run("after err, close doesn't block", func(t *testing.T) {
   267  			client := &Client{
   268  				driverClient: &mock.DBUpdater{
   269  					DBUpdatesFunc: func(context.Context, driver.Options) (driver.DBUpdates, error) {
   270  						return nil, errors.New("asdfsad")
   271  					},
   272  				},
   273  			}
   274  			rows := client.DBUpdates(context.Background())
   275  			if err := rows.Err(); err == nil {
   276  				t.Fatal("expected an error, got none")
   277  			}
   278  			_ = client.Close() // Should not block
   279  		})
   280  		t.Run("not updater, close doesn't block", func(t *testing.T) {
   281  			client := &Client{
   282  				driverClient: &mock.Client{},
   283  			}
   284  			rows := client.DBUpdates(context.Background())
   285  			if err := rows.Err(); err == nil {
   286  				t.Fatal("expected an error, got none")
   287  			}
   288  			_ = client.Close() // Should not block
   289  		})
   290  	})
   291  }
   292  
   293  func TestDBUpdates_Next_resets_iterator_value(t *testing.T) {
   294  	idx := 0
   295  	client := &Client{
   296  		driverClient: &mock.DBUpdater{
   297  			DBUpdatesFunc: func(context.Context, driver.Options) (driver.DBUpdates, error) {
   298  				return &mock.DBUpdates{
   299  					NextFunc: func(update *driver.DBUpdate) error {
   300  						idx++
   301  						switch idx {
   302  						case 1:
   303  							update.DBName = strconv.Itoa(idx)
   304  							return nil
   305  						case 2:
   306  							return nil
   307  						}
   308  						return io.EOF
   309  					},
   310  				}, nil
   311  			},
   312  		},
   313  	}
   314  
   315  	updates := client.DBUpdates(context.Background())
   316  
   317  	wantDBNames := []string{"1", ""}
   318  	gotDBNames := []string{}
   319  	for updates.Next() {
   320  		gotDBNames = append(gotDBNames, updates.DBName())
   321  	}
   322  	if d := cmp.Diff(wantDBNames, gotDBNames); d != "" {
   323  		t.Error(d)
   324  	}
   325  }
   326  
   327  func TestDBUpdates_LastSeq(t *testing.T) {
   328  	t.Run("non-LastSeqer", func(t *testing.T) {
   329  		client := &Client{
   330  			driverClient: &mock.DBUpdater{
   331  				DBUpdatesFunc: func(context.Context, driver.Options) (driver.DBUpdates, error) {
   332  					return &mock.DBUpdates{
   333  						NextFunc: func(_ *driver.DBUpdate) error {
   334  							return io.EOF
   335  						},
   336  					}, nil
   337  				},
   338  			},
   339  		}
   340  
   341  		updates := client.DBUpdates(context.Background())
   342  		for updates.Next() {
   343  			/* .. do nothing .. */
   344  		}
   345  		lastSeq, err := updates.LastSeq()
   346  		if err != nil {
   347  			t.Fatal(err)
   348  		}
   349  		if lastSeq != "" {
   350  			t.Errorf("Unexpected lastSeq: %s", lastSeq)
   351  		}
   352  	})
   353  	t.Run("LastSeqer", func(t *testing.T) {
   354  		client := &Client{
   355  			driverClient: &mock.DBUpdater{
   356  				DBUpdatesFunc: func(context.Context, driver.Options) (driver.DBUpdates, error) {
   357  					return &mock.LastSeqer{
   358  						DBUpdates: &mock.DBUpdates{
   359  							NextFunc: func(_ *driver.DBUpdate) error {
   360  								return io.EOF
   361  							},
   362  						},
   363  						LastSeqFunc: func() (string, error) {
   364  							return "99-last", nil
   365  						},
   366  					}, nil
   367  				},
   368  			},
   369  		}
   370  
   371  		updates := client.DBUpdates(context.Background())
   372  		for updates.Next() {
   373  			/* .. do nothing .. */
   374  		}
   375  		lastSeq, err := updates.LastSeq()
   376  		if err != nil {
   377  			t.Fatal(err)
   378  		}
   379  		if lastSeq != "99-last" {
   380  			t.Errorf("Unexpected lastSeq: %s", lastSeq)
   381  		}
   382  	})
   383  }
   384  

View as plain text