github.com/go-kivik/kivik/v4@v4.3.2/updates_test.go (about) 1 // Licensed under the Apache License, Version 2.0 (the "License"); you may not 2 // use this file except in compliance with the License. You may obtain a copy of 3 // the License at 4 // 5 // http://www.apache.org/licenses/LICENSE-2.0 6 // 7 // Unless required by applicable law or agreed to in writing, software 8 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 9 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 10 // License for the specific language governing permissions and limitations under 11 // the License. 12 13 package kivik 14 15 import ( 16 "context" 17 "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 }