1package kivik 2 3import ( 4 "context" 5 "errors" 6 "fmt" 7 "io" 8 "reflect" 9 "testing" 10 11 "github.com/flimzy/diff" 12 "github.com/flimzy/kivik/driver" 13 "github.com/flimzy/testy" 14) 15 16func TestBulkNext(t *testing.T) { 17 tests := []struct { 18 name string 19 r *BulkResults 20 expected bool 21 }{ 22 { 23 name: "true", 24 r: &BulkResults{ 25 iter: &iter{ 26 feed: &TestFeed{max: 1}, 27 curVal: new(int64), 28 }, 29 }, 30 expected: true, 31 }, 32 { 33 name: "false", 34 r: &BulkResults{ 35 iter: &iter{ 36 feed: &TestFeed{max: 0}, 37 curVal: new(int64), 38 }, 39 }, 40 expected: false, 41 }, 42 } 43 for _, test := range tests { 44 t.Run(test.name, func(t *testing.T) { 45 result := test.r.Next() 46 if result != test.expected { 47 t.Errorf("Unexpected result: %v", result) 48 } 49 }) 50 } 51} 52 53func TestBulkErr(t *testing.T) { 54 expected := "bulk error" 55 r := &BulkResults{ 56 iter: &iter{lasterr: errors.New(expected)}, 57 } 58 err := r.Err() 59 testy.Error(t, expected, err) 60} 61 62func TestBulkClose(t *testing.T) { 63 expected := "close error" 64 r := &BulkResults{ 65 iter: &iter{ 66 feed: &TestFeed{closeErr: errors.New(expected)}, 67 }, 68 } 69 err := r.Close() 70 testy.Error(t, expected, err) 71} 72 73func TestBulkIteratorNext(t *testing.T) { 74 tests := []struct { 75 name string 76 r *bulkIterator 77 err string 78 expected *driver.BulkResult 79 }{ 80 { 81 name: "error", 82 r: &bulkIterator{&mockBulkResults{err: errors.New("iter error")}}, 83 err: "iter error", 84 }, 85 { 86 name: "success", 87 r: &bulkIterator{&mockBulkResults{ 88 result: &driver.BulkResult{ID: "foo"}, 89 }}, 90 expected: &driver.BulkResult{ID: "foo"}, 91 }, 92 } 93 for _, test := range tests { 94 t.Run(test.name, func(t *testing.T) { 95 result := new(driver.BulkResult) 96 err := test.r.Next(result) 97 testy.Error(t, test.err, err) 98 if d := diff.Interface(test.expected, result); d != nil { 99 t.Error(d) 100 } 101 }) 102 } 103} 104 105func TestRLOCK(t *testing.T) { 106 tests := []struct { 107 name string 108 iter *iter 109 err string 110 }{ 111 { 112 name: "not ready", 113 iter: &iter{}, 114 err: "kivik: Iterator access before calling Next", 115 }, 116 { 117 name: "closed", 118 iter: &iter{closed: true}, 119 err: "kivik: Iterator is closed", 120 }, 121 { 122 name: "success", 123 iter: &iter{ready: true}, 124 }, 125 } 126 for _, test := range tests { 127 t.Run(test.name, func(t *testing.T) { 128 close, err := test.iter.rlock() 129 testy.Error(t, test.err, err) 130 if close == nil { 131 t.Fatal("close is nil") 132 } 133 close() 134 }) 135 } 136} 137 138func TestDocsInterfaceSlice(t *testing.T) { 139 type diTest struct { 140 name string 141 input interface{} 142 expected interface{} 143 error string 144 } 145 str := "foo" 146 intSlice := []int{1, 2, 3} 147 tests := []diTest{ 148 { 149 name: "Nil", 150 input: nil, 151 expected: nil, 152 error: "must be slice or array, got <nil>", 153 }, 154 { 155 name: "InterfaceSlice", 156 input: []interface{}{map[string]string{"foo": "bar"}}, 157 expected: []interface{}{map[string]string{"foo": "bar"}}, 158 }, 159 { 160 name: "String", 161 input: "foo", 162 error: "must be slice or array, got string", 163 }, 164 { 165 name: "IntSlice", 166 input: []int{1, 2, 3}, 167 expected: []interface{}{1, 2, 3}, 168 }, 169 { 170 name: "IntArray", 171 input: [3]int{1, 2, 3}, 172 expected: []interface{}{1, 2, 3}, 173 }, 174 { 175 name: "StringPointer", 176 input: &str, 177 error: "must be slice or array, got *string", 178 }, 179 { 180 name: "SlicePointer", 181 input: &intSlice, 182 expected: []interface{}{1, 2, 3}, 183 }, 184 { 185 name: "JSONDoc", 186 input: []interface{}{ 187 map[string]string{"foo": "bar"}, 188 []byte(`{"foo":"bar"}`), 189 }, 190 expected: []interface{}{ 191 map[string]string{"foo": "bar"}, 192 map[string]string{"foo": "bar"}, 193 }, 194 }, 195 { 196 name: "BytesArrays", 197 input: [][]byte{ 198 []byte(`{"foo":"bar"}`), 199 []byte(`{"foo":"bar"}`), 200 }, 201 expected: []interface{}{ 202 map[string]string{"foo": "bar"}, 203 map[string]string{"foo": "bar"}, 204 }, 205 }, 206 { 207 name: "InvalidJSON", 208 input: []interface{}{[]byte(`invalid`)}, 209 error: "invalid character 'i' looking for beginning of value", 210 }, 211 { 212 name: "BytesInvalidJSON", 213 input: [][]byte{[]byte(`invalid`)}, 214 error: "invalid character 'i' looking for beginning of value", 215 }, 216 } 217 for _, test := range tests { 218 func(test diTest) { 219 t.Run(test.name, func(t *testing.T) { 220 result, err := docsInterfaceSlice(test.input) 221 var msg string 222 if err != nil { 223 msg = err.Error() 224 } 225 if msg != test.error { 226 t.Errorf("Unexpected error: %s", err) 227 } 228 if d := diff.AsJSON(test.expected, result); d != nil { 229 t.Errorf("%s", d) 230 } 231 }) 232 }(test) 233 } 234} 235 236func TestBulkDocsNotSlice(t *testing.T) { 237 err := func() (err error) { 238 defer func() { 239 if r := recover(); r != nil { 240 err = r.(error) 241 } 242 }() 243 db := &DB{} 244 _, _ = db.BulkDocs(context.Background(), nil) 245 return nil 246 }() 247 var msg string 248 if err != nil { 249 msg = err.Error() 250 } 251 expected := "must be slice or array, got <nil>" 252 if msg != expected { 253 t.Errorf("Unexpected error: %s", msg) 254 } 255} 256 257type bdDB struct { 258 driver.DB 259 err error 260 options map[string]interface{} 261} 262 263var _ driver.DB = &bdDB{} 264 265func (db *bdDB) BulkDocs(_ context.Context, docs []interface{}, options map[string]interface{}) (driver.BulkResults, error) { 266 if db.options != nil { 267 if !reflect.DeepEqual(db.options, options) { 268 return nil, fmt.Errorf("Unexpected options. Got: %v, Expected: %v", options, db.options) 269 } 270 } 271 return nil, db.err 272} 273 274type legacyDB struct { 275 driver.DB 276 err error 277} 278 279var _ driver.OldBulkDocer = &legacyDB{} 280 281func (db *legacyDB) BulkDocs(_ context.Context, docs []interface{}) (driver.BulkResults, error) { 282 return nil, db.err 283} 284 285type nonbdDB struct { 286 driver.DB 287} 288 289var _ driver.DB = &nonbdDB{} 290 291func (db *nonbdDB) Put(_ context.Context, _ string, _ interface{}) (string, error) { 292 return "", nil 293} 294func (db *nonbdDB) CreateDoc(_ context.Context, _ interface{}) (string, string, error) { 295 return "", "", nil 296} 297 298func TestBulkDocs(t *testing.T) { 299 type bdTest struct { 300 name string 301 dbDriver driver.DB 302 docs interface{} 303 options Options 304 err string 305 } 306 tests := []bdTest{ 307 { 308 name: "no docs", 309 dbDriver: &bdDB{}, 310 docs: []int{}, 311 }, 312 { 313 name: "invalid JSON", 314 dbDriver: &bdDB{}, 315 docs: []interface{}{[]byte("invalid json")}, 316 err: "invalid character 'i' looking for beginning of value", 317 }, 318 { 319 name: "query fails", 320 dbDriver: &bdDB{err: errors.New("bulkdocs failed")}, 321 docs: []int{1, 2, 3}, 322 err: "bulkdocs failed", 323 }, 324 { 325 name: "emulated BulkDocs support", 326 dbDriver: &nonbdDB{}, 327 docs: []interface{}{ 328 map[string]string{"_id": "foo"}, 329 123, 330 }, 331 }, 332 { 333 name: "new_edits", 334 dbDriver: &bdDB{options: map[string]interface{}{"new_edits": true}}, 335 docs: []interface{}{ 336 map[string]string{"_id": "foo"}, 337 123, 338 }, 339 options: Options{"new_edits": true}, 340 }, 341 { 342 name: "legacy bulkDocer", 343 dbDriver: &legacyDB{}, 344 docs: []interface{}{ 345 map[string]string{"_id": "foo"}, 346 123, 347 }, 348 }, 349 { 350 name: "legacy failure", 351 dbDriver: &legacyDB{err: errors.New("fail")}, 352 docs: []interface{}{1, 2, 3}, 353 err: "fail", 354 }, 355 } 356 for _, test := range tests { 357 t.Run(test.name, func(t *testing.T) { 358 db := &DB{driverDB: test.dbDriver} 359 _, err := db.BulkDocs(context.Background(), test.docs, test.options) 360 var msg string 361 if err != nil { 362 msg = err.Error() 363 } 364 if msg != test.err { 365 t.Errorf("Unexpected error: %s", msg) 366 } 367 }) 368 } 369} 370 371func TestEmulatedBulkResults(t *testing.T) { 372 results := []driver.BulkResult{ 373 { 374 ID: "chicken", 375 Rev: "foo", 376 Error: nil, 377 }, 378 { 379 ID: "duck", 380 Rev: "bar", 381 Error: errors.New("fail"), 382 }, 383 { 384 ID: "dog", 385 Rev: "baz", 386 Error: nil, 387 }, 388 } 389 br := &emulatedBulkResults{results} 390 result := &driver.BulkResult{} 391 if err := br.Next(result); err != nil { 392 t.Errorf("Unexpected error: %s", err) 393 } 394 if d := diff.Interface(&results[0], result); d != nil { 395 t.Error(d) 396 } 397 if err := br.Next(result); err != nil { 398 t.Errorf("Unexpected error: %s", err) 399 } 400 if d := diff.Interface(&results[1], result); d != nil { 401 t.Error(d) 402 } 403 if err := br.Close(); err != nil { 404 t.Errorf("Unexpected error: %s", err) 405 } 406 if err := br.Next(result); err != io.EOF { 407 t.Error("Expected EOF") 408 } 409} 410 411func TestBulkResultsGetters(t *testing.T) { 412 id := "foo" 413 rev := "3-xxx" 414 err := "update error" 415 r := &BulkResults{ 416 iter: &iter{ 417 ready: true, 418 curVal: &driver.BulkResult{ 419 ID: id, 420 Rev: rev, 421 Error: errors.New(err), 422 }, 423 }, 424 } 425 426 t.Run("ID", func(t *testing.T) { 427 result := r.ID() 428 if result != id { 429 t.Errorf("Unexpected ID: %v", result) 430 } 431 }) 432 433 t.Run("Rev", func(t *testing.T) { 434 result := r.Rev() 435 if result != rev { 436 t.Errorf("Unexpected Rev: %v", result) 437 } 438 }) 439 440 t.Run("UpdateErr", func(t *testing.T) { 441 result := r.UpdateErr() 442 testy.Error(t, err, result) 443 }) 444 445 t.Run("Not ready", func(t *testing.T) { 446 r.ready = false 447 448 t.Run("ID", func(t *testing.T) { 449 result := r.ID() 450 if result != "" { 451 t.Errorf("Unexpected ID: %v", result) 452 } 453 }) 454 455 t.Run("Rev", func(t *testing.T) { 456 result := r.Rev() 457 if result != "" { 458 t.Errorf("Unexpected Rev: %v", result) 459 } 460 }) 461 462 t.Run("UpdateErr", func(t *testing.T) { 463 result := r.UpdateErr() 464 testy.Error(t, "", result) 465 }) 466 467 }) 468} 469