github.com/go-kivik/kivik/v4@v4.3.2/changes_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 "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 }