github.com/go-kivik/kivik/v4@v4.3.2/find_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 "encoding/json" 18 "errors" 19 "fmt" 20 "net/http" 21 "testing" 22 23 "gitlab.com/flimzy/testy" 24 25 "github.com/go-kivik/kivik/v4/driver" 26 internal "github.com/go-kivik/kivik/v4/int/errors" 27 "github.com/go-kivik/kivik/v4/int/mock" 28 ) 29 30 func TestFind(t *testing.T) { 31 tests := []struct { 32 name string 33 db *DB 34 query interface{} 35 options []Option 36 expected *ResultSet 37 status int 38 err string 39 }{ 40 { 41 name: "non-finder", 42 db: &DB{ 43 client: &Client{}, 44 driverDB: &mock.DB{}, 45 }, 46 status: http.StatusNotImplemented, 47 err: "kivik: driver does not support Find interface", 48 }, 49 { 50 name: "db error", 51 db: &DB{ 52 client: &Client{}, 53 driverDB: &mock.Finder{ 54 FindFunc: func(context.Context, interface{}, driver.Options) (driver.Rows, error) { 55 return nil, errors.New("db error") 56 }, 57 }, 58 }, 59 status: http.StatusInternalServerError, 60 err: "db error", 61 }, 62 { 63 name: "success", 64 db: &DB{ 65 client: &Client{}, 66 driverDB: &mock.Finder{ 67 FindFunc: func(_ context.Context, query interface{}, _ driver.Options) (driver.Rows, error) { 68 expectedQuery := json.RawMessage(`{"limit":3,"selector":{"foo":"bar"},"skip":10}`) 69 if d := testy.DiffInterface(expectedQuery, query); d != nil { 70 return nil, fmt.Errorf("Unexpected query:\n%s", d) 71 } 72 return &mock.Rows{ID: "a"}, nil 73 }, 74 }, 75 }, 76 query: map[string]interface{}{"selector": map[string]interface{}{"foo": "bar"}}, 77 options: []Option{ 78 Param("limit", 3), 79 Param("skip", 10), 80 }, 81 expected: &ResultSet{ 82 iter: &iter{ 83 feed: &rowsIterator{ 84 Rows: &mock.Rows{ID: "a"}, 85 }, 86 curVal: &driver.Row{}, 87 }, 88 rowsi: &mock.Rows{ID: "a"}, 89 }, 90 }, 91 { 92 name: "db error", 93 db: &DB{ 94 err: errors.New("db error"), 95 }, 96 status: http.StatusInternalServerError, 97 err: "db error", 98 }, 99 { 100 name: "client closed", 101 db: &DB{ 102 client: &Client{ 103 closed: true, 104 }, 105 driverDB: &mock.Finder{}, 106 }, 107 status: http.StatusServiceUnavailable, 108 err: "kivik: client closed", 109 }, 110 } 111 112 for _, test := range tests { 113 t.Run(test.name, func(t *testing.T) { 114 rs := test.db.Find(context.Background(), test.query, test.options...) 115 err := rs.Err() 116 if d := internal.StatusErrorDiff(test.err, test.status, err); d != "" { 117 t.Error(d) 118 } 119 if err != nil { 120 return 121 } 122 rs.cancel = nil // Determinism 123 rs.onClose = nil // Determinism 124 if d := testy.DiffInterface(test.expected, rs); d != nil { 125 t.Error(d) 126 } 127 }) 128 } 129 t.Run("standalone", func(t *testing.T) { 130 t.Run("after err, close doesn't block", func(t *testing.T) { 131 db := &DB{ 132 client: &Client{}, 133 driverDB: &mock.Finder{ 134 FindFunc: func(context.Context, interface{}, driver.Options) (driver.Rows, error) { 135 return nil, errors.New("sdfsdf") 136 }, 137 }, 138 } 139 rows := db.Find(context.Background(), nil) 140 if err := rows.Err(); err == nil { 141 t.Fatal("expected an error, got none") 142 } 143 _ = db.Close() // Should not block 144 }) 145 t.Run("not finder, close doesn't block", func(t *testing.T) { 146 db := &DB{ 147 client: &Client{}, 148 driverDB: &mock.DB{}, 149 } 150 rows := db.Find(context.Background(), nil) 151 if err := rows.Err(); err == nil { 152 t.Fatal("expected an error, got none") 153 } 154 _ = db.Close() // Should not block 155 }) 156 }) 157 } 158 159 func TestCreateIndex(t *testing.T) { 160 tests := []struct { 161 testName string 162 db *DB 163 ddoc, name string 164 index interface{} 165 status int 166 err string 167 }{ 168 { 169 testName: "non-finder", 170 db: &DB{ 171 client: &Client{}, 172 driverDB: &mock.DB{}, 173 }, 174 status: http.StatusNotImplemented, 175 err: "kivik: driver does not support Find interface", 176 }, 177 { 178 testName: "db error", 179 db: &DB{ 180 client: &Client{}, 181 driverDB: &mock.Finder{ 182 CreateIndexFunc: func(context.Context, string, string, interface{}, driver.Options) error { 183 return errors.New("db error") 184 }, 185 }, 186 }, 187 status: http.StatusInternalServerError, 188 err: "db error", 189 }, 190 { 191 testName: "success", 192 db: &DB{ 193 client: &Client{}, 194 driverDB: &mock.Finder{ 195 CreateIndexFunc: func(_ context.Context, ddoc, name string, index interface{}, _ driver.Options) error { 196 expectedDdoc := "foo" 197 expectedName := "bar" 198 expectedIndex := int(3) 199 if expectedDdoc != ddoc { 200 return fmt.Errorf("Unexpected ddoc: %s", ddoc) 201 } 202 if expectedName != name { 203 return fmt.Errorf("Unexpected name: %s", name) 204 } 205 if d := testy.DiffInterface(expectedIndex, index); d != nil { 206 return fmt.Errorf("Unexpected index:\n%s", d) 207 } 208 return nil 209 }, 210 }, 211 }, 212 ddoc: "foo", 213 name: "bar", 214 index: int(3), 215 }, 216 { 217 name: "closed", 218 db: &DB{ 219 client: &Client{ 220 closed: true, 221 }, 222 }, 223 status: http.StatusServiceUnavailable, 224 err: "kivik: client closed", 225 }, 226 { 227 name: "db error", 228 db: &DB{ 229 client: &Client{}, 230 err: errors.New("db error"), 231 }, 232 status: http.StatusInternalServerError, 233 err: "db error", 234 }, 235 } 236 237 for _, test := range tests { 238 t.Run(test.testName, func(t *testing.T) { 239 err := test.db.CreateIndex(context.Background(), test.ddoc, test.name, test.index) 240 if d := internal.StatusErrorDiff(test.err, test.status, err); d != "" { 241 t.Error(d) 242 } 243 }) 244 } 245 } 246 247 func TestDeleteIndex(t *testing.T) { 248 tests := []struct { 249 testName string 250 db *DB 251 ddoc, name string 252 status int 253 err string 254 }{ 255 { 256 testName: "non-finder", 257 db: &DB{ 258 client: &Client{}, 259 driverDB: &mock.DB{}, 260 }, 261 status: http.StatusNotImplemented, 262 err: "kivik: driver does not support Find interface", 263 }, 264 { 265 testName: "db error", 266 db: &DB{ 267 client: &Client{}, 268 driverDB: &mock.Finder{ 269 DeleteIndexFunc: func(context.Context, string, string, driver.Options) error { 270 return errors.New("db error") 271 }, 272 }, 273 }, 274 status: http.StatusInternalServerError, 275 err: "db error", 276 }, 277 { 278 testName: "success", 279 db: &DB{ 280 client: &Client{}, 281 driverDB: &mock.Finder{ 282 DeleteIndexFunc: func(_ context.Context, ddoc, name string, _ driver.Options) error { 283 expectedDdoc := "foo" 284 expectedName := "bar" 285 if expectedDdoc != ddoc { 286 return fmt.Errorf("Unexpected ddoc: %s", ddoc) 287 } 288 if expectedName != name { 289 return fmt.Errorf("Unexpected name: %s", name) 290 } 291 return nil 292 }, 293 }, 294 }, 295 ddoc: "foo", 296 name: "bar", 297 }, 298 { 299 testName: "client closed", 300 db: &DB{ 301 client: &Client{ 302 closed: true, 303 }, 304 }, 305 status: http.StatusServiceUnavailable, 306 err: "kivik: client closed", 307 }, 308 { 309 testName: "db error", 310 db: &DB{ 311 err: errors.New("db error"), 312 }, 313 status: http.StatusInternalServerError, 314 err: "db error", 315 }, 316 } 317 318 for _, test := range tests { 319 t.Run(test.testName, func(t *testing.T) { 320 err := test.db.DeleteIndex(context.Background(), test.ddoc, test.name) 321 if d := internal.StatusErrorDiff(test.err, test.status, err); d != "" { 322 t.Error(d) 323 } 324 }) 325 } 326 } 327 328 func TestGetIndexes(t *testing.T) { 329 tests := []struct { 330 testName string 331 db *DB 332 expected []Index 333 status int 334 err string 335 }{ 336 { 337 testName: "non-finder", 338 db: &DB{ 339 client: &Client{}, 340 driverDB: &mock.DB{}, 341 }, 342 status: http.StatusNotImplemented, 343 err: "kivik: driver does not support Find interface", 344 }, 345 { 346 testName: "db error", 347 db: &DB{ 348 client: &Client{}, 349 driverDB: &mock.Finder{ 350 GetIndexesFunc: func(context.Context, driver.Options) ([]driver.Index, error) { 351 return nil, errors.New("db error") 352 }, 353 }, 354 }, 355 status: http.StatusInternalServerError, 356 err: "db error", 357 }, 358 { 359 testName: "success", 360 db: &DB{ 361 client: &Client{}, 362 driverDB: &mock.Finder{ 363 GetIndexesFunc: func(context.Context, driver.Options) ([]driver.Index, error) { 364 return []driver.Index{ 365 {Name: "a"}, 366 {Name: "b"}, 367 }, nil 368 }, 369 }, 370 }, 371 expected: []Index{ 372 { 373 Name: "a", 374 }, 375 { 376 Name: "b", 377 }, 378 }, 379 }, 380 { 381 testName: "client closed", 382 db: &DB{ 383 client: &Client{ 384 closed: true, 385 }, 386 }, 387 status: http.StatusServiceUnavailable, 388 err: "kivik: client closed", 389 }, 390 { 391 testName: "db error", 392 db: &DB{ 393 err: errors.New("db error"), 394 }, 395 status: http.StatusInternalServerError, 396 err: "db error", 397 }, 398 } 399 400 for _, test := range tests { 401 t.Run(test.testName, func(t *testing.T) { 402 result, err := test.db.GetIndexes(context.Background()) 403 if d := internal.StatusErrorDiff(test.err, test.status, err); d != "" { 404 t.Error(d) 405 } 406 if err != nil { 407 return 408 } 409 if d := testy.DiffInterface(test.expected, result); d != nil { 410 t.Error(d) 411 } 412 }) 413 } 414 } 415 416 func TestExplain(t *testing.T) { 417 type tt struct { 418 db *DB 419 query interface{} 420 expected *QueryPlan 421 status int 422 err string 423 } 424 425 tests := testy.NewTable() 426 tests.Add("non-finder", tt{ 427 db: &DB{ 428 client: &Client{}, 429 driverDB: &mock.DB{}, 430 }, 431 status: http.StatusNotImplemented, 432 err: "kivik: driver does not support Find interface", 433 }) 434 tests.Add("explain error", tt{ 435 db: &DB{ 436 client: &Client{}, 437 driverDB: &mock.Finder{ 438 ExplainFunc: func(context.Context, interface{}, driver.Options) (*driver.QueryPlan, error) { 439 return nil, errors.New("explain error") 440 }, 441 }, 442 }, 443 status: http.StatusInternalServerError, 444 err: "explain error", 445 }) 446 tests.Add("success", tt{ 447 db: &DB{ 448 client: &Client{}, 449 driverDB: &mock.Finder{ 450 ExplainFunc: func(_ context.Context, query interface{}, _ driver.Options) (*driver.QueryPlan, error) { 451 expectedQuery := int(3) 452 if d := testy.DiffInterface(expectedQuery, query); d != nil { 453 return nil, fmt.Errorf("Unexpected query:\n%s", d) 454 } 455 return &driver.QueryPlan{DBName: "foo"}, nil 456 }, 457 }, 458 }, 459 query: int(3), 460 expected: &QueryPlan{DBName: "foo"}, 461 }) 462 tests.Add("client closed", tt{ 463 db: &DB{ 464 driverDB: &mock.Finder{}, 465 client: &Client{ 466 closed: true, 467 }, 468 }, 469 status: http.StatusServiceUnavailable, 470 err: "kivik: client closed", 471 }) 472 tests.Add("db error", tt{ 473 db: &DB{ 474 err: errors.New("db error"), 475 }, 476 status: http.StatusInternalServerError, 477 err: "db error", 478 }) 479 480 tests.Run(t, func(t *testing.T, tt tt) { 481 result, err := tt.db.Explain(context.Background(), tt.query) 482 if d := internal.StatusErrorDiff(tt.err, tt.status, err); d != "" { 483 t.Error(d) 484 } 485 if d := testy.DiffInterface(tt.expected, result); d != nil { 486 t.Error(d) 487 } 488 }) 489 }