github.com/cozy/cozy-stack@v0.0.0-20240603063001-31110fa4cae1/web/data/data_test.go (about) 1 package data 2 3 import ( 4 "context" 5 "fmt" 6 "strings" 7 "testing" 8 9 "github.com/cozy/cozy-stack/model/instance" 10 "github.com/cozy/cozy-stack/pkg/config/config" 11 "github.com/cozy/cozy-stack/pkg/couchdb" 12 "github.com/cozy/cozy-stack/tests/testutils" 13 "github.com/stretchr/testify/assert" 14 "github.com/stretchr/testify/require" 15 "golang.org/x/sync/errgroup" 16 ) 17 18 type M map[string]interface{} 19 type S []interface{} 20 21 func TestData(t *testing.T) { 22 if testing.Short() { 23 t.Skip("an instance is required for this test: test skipped due to the use of --short flag") 24 } 25 26 const Type = "io.cozy.events" 27 const ID = "4521C325F6478E45" 28 29 config.UseTestFile(t) 30 testutils.NeedCouchdb(t) 31 setup := testutils.NewSetup(t, t.Name()) 32 testInstance := setup.GetTestInstance() 33 scope := "io.cozy.doctypes io.cozy.files io.cozy.events " + 34 "io.cozy.anothertype io.cozy.nottype" 35 36 _, token := setup.GetTestClient(scope) 37 ts := setup.GetTestServer("/data", Routes) 38 t.Cleanup(ts.Close) 39 40 _ = couchdb.ResetDB(testInstance, Type) 41 _ = couchdb.CreateNamedDoc(testInstance, &couchdb.JSONDoc{ 42 Type: Type, 43 M: map[string]interface{}{ 44 "_id": ID, 45 "test": "testvalue", 46 }, 47 }) 48 49 t.Run("SuccessGet", func(t *testing.T) { 50 e := testutils.CreateTestClient(t, ts.URL) 51 52 e.GET("/data/"+Type+"/"+ID). 53 WithHeader("Authorization", "Bearer "+token). 54 Expect().Status(200). 55 JSON().Object(). 56 ValueEqual("test", "testvalue") 57 }) 58 59 t.Run("GetForMissingDoc", func(t *testing.T) { 60 e := testutils.CreateTestClient(t, ts.URL) 61 62 e.GET("/data/no.such.doctype/id"). 63 Expect().Status(401) 64 65 e.GET("/data/" + Type + "/no.such.id"). 66 Expect().Status(401) 67 68 e.GET("/data/"+Type+"/no.such.id"). 69 WithHeader("Authorization", "Bearer "+token). 70 Expect().Status(404) 71 }) 72 73 t.Run("GetWithSlash", func(t *testing.T) { 74 e := testutils.CreateTestClient(t, ts.URL) 75 76 err := couchdb.CreateNamedDoc(testInstance, &couchdb.JSONDoc{ 77 Type: Type, M: map[string]interface{}{ 78 "_id": "with/slash", 79 "test": "valueslash", 80 }}) 81 require.NoError(t, err) 82 83 e.GET("/data/"+Type+"/with%2Fslash"). 84 WithHeader("Authorization", "Bearer "+token). 85 Expect().Status(200). 86 JSON().Object(). 87 ValueEqual("test", "valueslash") 88 }) 89 90 t.Run("WrongDoctype", func(t *testing.T) { 91 e := testutils.CreateTestClient(t, ts.URL) 92 93 _ = couchdb.DeleteDB(testInstance, "io.cozy.nottype") 94 95 e.GET("/data/io.cozy.nottype/"+ID). 96 WithHeader("Authorization", "Bearer "+token). 97 Expect().Status(404). 98 JSON().Object(). 99 ValueEqual("error", "not_found"). 100 ValueEqual("reason", "wrong_doctype") 101 }) 102 103 t.Run("UnderscoreName", func(t *testing.T) { 104 e := testutils.CreateTestClient(t, ts.URL) 105 106 e.GET("/data/"+Type+"/_foo"). 107 WithHeader("Authorization", "Bearer "+token). 108 Expect().Status(400) 109 }) 110 111 t.Run("VFSDoctype", func(t *testing.T) { 112 e := testutils.CreateTestClient(t, ts.URL) 113 114 e.POST("/data/io.cozy.files/"). 115 WithHeader("Authorization", "Bearer "+token). 116 WithHeader("Content-Type", "application/json"). 117 WithBytes([]byte(`{ "wrong-vfs": "structure" }`)). 118 Expect().Status(403). 119 JSON().Object(). 120 Value("error").String().Contains("reserved") 121 }) 122 123 t.Run("WrongID", func(t *testing.T) { 124 e := testutils.CreateTestClient(t, ts.URL) 125 126 e.GET("/data/"+Type+"/NOTID"). 127 WithHeader("Authorization", "Bearer "+token). 128 Expect().Status(404). 129 JSON().Object(). 130 ValueEqual("error", "not_found"). 131 ValueEqual("reason", "missing") 132 }) 133 134 t.Run("SuccessCreateKnownDoctype", func(t *testing.T) { 135 e := testutils.CreateTestClient(t, ts.URL) 136 137 obj := e.POST("/data/"+Type+"/"). 138 WithHeader("Authorization", "Bearer "+token). 139 WithHeader("Content-Type", "application/json"). 140 WithBytes([]byte(`{ "somefield": "avalue" }`)). 141 Expect().Status(201). 142 JSON().Object() 143 144 obj.ValueEqual("ok", true) 145 obj.Value("id").String().NotEmpty() 146 obj.ValueEqual("type", Type) 147 obj.Value("rev").String().NotEmpty() 148 149 var data couchdb.JSONDoc 150 151 obj.Value("data").Decode(&data) 152 obj.ValueEqual("id", data.ID()) 153 obj.ValueEqual("type", data.Type) 154 obj.ValueEqual("rev", data.Rev()) 155 assert.Equal(t, "avalue", data.Get("somefield")) 156 }) 157 158 t.Run("SuccessCreateUnknownDoctype", func(t *testing.T) { 159 e := testutils.CreateTestClient(t, ts.URL) 160 161 type2 := "io.cozy.anothertype" 162 obj := e.POST("/data/"+type2+"/"). 163 WithHeader("Authorization", "Bearer "+token). 164 WithHeader("Content-Type", "application/json"). 165 WithBytes([]byte(`{ "somefield": "avalue" }`)). 166 Expect().Status(201). 167 JSON().Object() 168 169 obj.ValueEqual("ok", true) 170 obj.Value("id").String().NotEmpty() 171 obj.ValueEqual("type", type2) 172 obj.Value("rev").String().NotEmpty() 173 174 var data couchdb.JSONDoc 175 176 obj.Value("data").Decode(&data) 177 obj.ValueEqual("id", data.ID()) 178 obj.ValueEqual("type", data.Type) 179 obj.ValueEqual("rev", data.Rev()) 180 assert.Equal(t, "avalue", data.Get("somefield")) 181 }) 182 183 t.Run("WrongCreateWithID", func(t *testing.T) { 184 e := testutils.CreateTestClient(t, ts.URL) 185 186 e.POST("/data/"+Type+"/"). 187 WithHeader("Authorization", "Bearer "+token). 188 WithHeader("Content-Type", "application/json"). 189 WithBytes([]byte(`{ 190 "_id": "this-should-not-be-an-id", 191 "somefield": "avalue" 192 }`)). 193 Expect().Status(400) 194 }) 195 196 t.Run("SuccessUpdate", func(t *testing.T) { 197 e := testutils.CreateTestClient(t, ts.URL) 198 199 // Get revision 200 doc := getDocForTest(Type, testInstance) 201 202 // update it 203 obj := e.PUT("/data/"+doc.DocType()+"/"+doc.ID()). 204 WithHeader("Authorization", "Bearer "+token). 205 WithHeader("Content-Type", "application/json"). 206 WithJSON(map[string]interface{}{ 207 "_id": doc.ID(), 208 "_rev": doc.Rev(), 209 "test": doc.Get("test"), 210 "somefield": "anewvalue", 211 }). 212 Expect().Status(200). 213 JSON().Object() 214 215 obj.NotContainsKey("error") 216 obj.ValueEqual("id", doc.ID()) 217 obj.ValueEqual("ok", true) 218 obj.Value("rev").String().NotEmpty() 219 obj.ValueNotEqual("rev", doc.Rev()) 220 221 var data couchdb.JSONDoc 222 223 obj.Value("data").Decode(&data) 224 obj.ValueEqual("id", data.ID()) 225 obj.ValueEqual("type", data.Type) 226 obj.ValueEqual("rev", data.Rev()) 227 assert.Equal(t, "anewvalue", data.Get("somefield")) 228 }) 229 230 t.Run("WrongIDInDocUpdate", func(t *testing.T) { 231 e := testutils.CreateTestClient(t, ts.URL) 232 233 // Get revision 234 doc := getDocForTest(Type, testInstance) 235 236 // update it 237 e.PUT("/data/"+doc.DocType()+"/"+doc.ID()). 238 WithHeader("Authorization", "Bearer "+token). 239 WithHeader("Content-Type", "application/json"). 240 WithJSON(map[string]interface{}{ 241 "_id": "this is not the id in the URL", 242 "_rev": doc.Rev(), 243 "test": doc.M["test"], 244 "somefield": "anewvalue", 245 }). 246 Expect().Status(400) 247 }) 248 249 t.Run("CreateDocWithAFixedID", func(t *testing.T) { 250 e := testutils.CreateTestClient(t, ts.URL) 251 252 // update it 253 obj := e.PUT("/data/"+Type+"/specific-id"). 254 WithHeader("Authorization", "Bearer "+token). 255 WithHeader("Content-Type", "application/json"). 256 WithJSON(map[string]interface{}{ 257 "test": "value", 258 "somefield": "anewvalue", 259 }). 260 Expect().Status(200). 261 JSON().Object() 262 263 obj.NotContainsKey("error") 264 obj.ValueEqual("id", "specific-id") 265 obj.ValueEqual("ok", true) 266 obj.Value("rev").String().NotEmpty() 267 268 var data couchdb.JSONDoc 269 obj.Value("data").Decode(&data) 270 obj.ValueEqual("id", data.ID()) 271 obj.ValueEqual("type", data.Type) 272 obj.ValueEqual("rev", data.Rev()) 273 assert.Equal(t, "anewvalue", data.Get("somefield")) 274 }) 275 276 t.Run("NoRevInDocUpdate", func(t *testing.T) { 277 e := testutils.CreateTestClient(t, ts.URL) 278 279 // Get revision 280 doc := getDocForTest(Type, testInstance) 281 282 // update it 283 e.PUT("/data/"+doc.DocType()+"/"+doc.ID()). 284 WithHeader("Authorization", "Bearer "+token). 285 WithHeader("Content-Type", "application/json"). 286 WithJSON(map[string]interface{}{ 287 "_id": doc.ID(), 288 "test": doc.M["test"], 289 "somefield": "anewvalue", 290 }). 291 Expect().Status(400) 292 }) 293 294 t.Run("PreviousRevInDocUpdate", func(t *testing.T) { 295 e := testutils.CreateTestClient(t, ts.URL) 296 297 // Get revision 298 doc := getDocForTest(Type, testInstance) 299 firstRev := doc.Rev() 300 301 // correcly update it 302 e.PUT("/data/"+doc.DocType()+"/"+doc.ID()). 303 WithHeader("Authorization", "Bearer "+token). 304 WithHeader("Content-Type", "application/json"). 305 WithJSON(map[string]interface{}{ 306 "_id": doc.ID(), 307 "_rev": doc.Rev(), 308 "somefield": "anewvalue", 309 }). 310 Expect().Status(200) 311 312 // update it 313 e.PUT("/data/"+doc.DocType()+"/"+doc.ID()). 314 WithHeader("Authorization", "Bearer "+token). 315 WithHeader("Content-Type", "application/json"). 316 WithJSON(map[string]interface{}{ 317 "_id": doc.ID(), 318 "_rev": firstRev, 319 "somefield": "anewvalue2", 320 }). 321 Expect().Status(409) 322 }) 323 324 t.Run("SuccessDeleteIfMatch", func(t *testing.T) { 325 e := testutils.CreateTestClient(t, ts.URL) 326 327 // Get revision 328 doc := getDocForTest(Type, testInstance) 329 rev := doc.Rev() 330 331 // Do deletion 332 obj := e.DELETE("/data/"+doc.DocType()+"/"+doc.ID()). 333 WithHeader("Authorization", "Bearer "+token). 334 WithHeader("If-Match", rev). 335 Expect().Status(200). 336 JSON().Object() 337 338 obj.ValueEqual("id", doc.ID()) 339 obj.ValueEqual("ok", true) 340 obj.ValueEqual("deleted", true) 341 obj.ValueNotEqual("rev", doc.Rev()) 342 }) 343 344 t.Run("FailDeleteIfNotMatch", func(t *testing.T) { 345 e := testutils.CreateTestClient(t, ts.URL) 346 347 // Get revision 348 doc := getDocForTest(Type, testInstance) 349 350 // Do deletion 351 e.DELETE("/data/"+doc.DocType()+"/"+doc.ID()). 352 WithHeader("Authorization", "Bearer "+token). 353 WithHeader("If-Match", "1-238238232322121"). // invalid rev 354 Expect().Status(409) 355 }) 356 357 t.Run("FailDeleteIfHeaderAndRevMismatch", func(t *testing.T) { 358 e := testutils.CreateTestClient(t, ts.URL) 359 360 // Get revision 361 doc := getDocForTest(Type, testInstance) 362 363 // Do deletion 364 e.DELETE("/data/"+doc.DocType()+"/"+doc.ID()). 365 WithQuery("rev", "1-238238232322121"). 366 WithHeader("Authorization", "Bearer "+token). 367 WithHeader("If-Match", "1-23823823231"). // not the same rev 368 Expect().Status(400) 369 }) 370 371 t.Run("FailDeleteIfNoRev", func(t *testing.T) { 372 e := testutils.CreateTestClient(t, ts.URL) 373 374 // Get revision 375 doc := getDocForTest(Type, testInstance) 376 377 // Do deletion 378 e.DELETE("/data/"+doc.DocType()+"/"+doc.ID()). 379 WithHeader("Authorization", "Bearer "+token). 380 Expect().Status(400) 381 }) 382 383 t.Run("DefineIndex", func(t *testing.T) { 384 e := testutils.CreateTestClient(t, ts.URL) 385 386 obj := e.POST("/data/"+Type+"/_index"). 387 WithHeader("Authorization", "Bearer "+token). 388 WithHeader("Content-Type", "application/json"). 389 WithBytes([]byte(`{ "index": { "fields": ["foo"] } }`)). 390 Expect().Status(200). 391 JSON().Object() 392 393 obj.NotContainsKey("error") 394 obj.NotContainsKey("reason") 395 obj.ValueEqual("result", "created") 396 obj.Value("name").String().NotEmpty() 397 obj.Value("id").String().NotEmpty() 398 }) 399 400 t.Run("ReDefineIndex", func(t *testing.T) { 401 e := testutils.CreateTestClient(t, ts.URL) 402 403 obj := e.POST("/data/"+Type+"/_index"). 404 WithHeader("Authorization", "Bearer "+token). 405 WithHeader("Content-Type", "application/json"). 406 WithBytes([]byte(`{ "index": { "fields": ["foo"] } }`)). 407 Expect().Status(200). 408 JSON().Object() 409 410 obj.NotContainsKey("error") 411 obj.NotContainsKey("reason") 412 obj.ValueEqual("result", "exists") 413 obj.Value("name").String().NotEmpty() 414 obj.Value("id").String().NotEmpty() 415 }) 416 417 t.Run("DefineIndexUnexistingDoctype", func(t *testing.T) { 418 e := testutils.CreateTestClient(t, ts.URL) 419 420 _ = couchdb.DeleteDB(testInstance, "io.cozy.nottype") 421 422 obj := e.POST("/data/io.cozy.nottype/_index"). 423 WithHeader("Authorization", "Bearer "+token). 424 WithHeader("Content-Type", "application/json"). 425 WithBytes([]byte(`{ "index": { "fields": ["foo"] } }`)). 426 Expect().Status(200). 427 JSON().Object() 428 429 obj.NotContainsKey("error") 430 obj.NotContainsKey("reason") 431 obj.ValueEqual("result", "created") 432 obj.Value("name").String().NotEmpty() 433 obj.Value("id").String().NotEmpty() 434 }) 435 436 t.Run("FindDocuments", func(t *testing.T) { 437 e := testutils.CreateTestClient(t, ts.URL) 438 439 _ = couchdb.ResetDB(testInstance, Type) 440 441 // Insert some docs 442 _ = getDocForTest(Type, testInstance) 443 _ = getDocForTest(Type, testInstance) 444 _ = getDocForTest(Type, testInstance) 445 446 // Create the index 447 e.POST("/data/"+Type+"/_index"). 448 WithHeader("Authorization", "Bearer "+token). 449 WithHeader("Content-Type", "application/json"). 450 WithBytes([]byte(`{ "index": { "fields": ["test"] } }`)). 451 Expect().Status(200). 452 JSON().Object(). 453 NotContainsKey("error") 454 455 // Select with the index 456 obj := e.POST("/data/"+Type+"/_find"). 457 WithHeader("Authorization", "Bearer "+token). 458 WithHeader("Content-Type", "application/json"). 459 WithBytes([]byte(`{ "selector": { "test": "value" } }`)). 460 Expect().Status(200). 461 JSON().Object() 462 463 docs := obj.Value("docs").Array() 464 docs.Length().Equal(3) 465 obj.NotContainsKey("execution_stats") 466 }) 467 468 t.Run("FindDocumentsWithStats", func(t *testing.T) { 469 e := testutils.CreateTestClient(t, ts.URL) 470 471 _ = couchdb.ResetDB(testInstance, Type) 472 _ = getDocForTest(Type, testInstance) 473 474 // Create the index 475 e.POST("/data/"+Type+"/_index"). 476 WithHeader("Authorization", "Bearer "+token). 477 WithHeader("Content-Type", "application/json"). 478 WithBytes([]byte(`{ "index": { "fields": ["test"] } }`)). 479 Expect().Status(200). 480 JSON().Object(). 481 NotContainsKey("error") 482 483 // Select with the index 484 e.POST("/data/"+Type+"/_find"). 485 WithHeader("Authorization", "Bearer "+token). 486 WithHeader("Content-Type", "application/json"). 487 WithBytes([]byte(`{ "selector": { "test": "value" }, "execution_stats": true }`)). 488 Expect().Status(200). 489 JSON().Object(). 490 Value("execution_stats").Object().NotEmpty() 491 }) 492 493 t.Run("FindDocumentsPaginated", func(t *testing.T) { 494 e := testutils.CreateTestClient(t, ts.URL) 495 496 _ = couchdb.ResetDB(testInstance, Type) 497 498 // Push 150 docs 499 for i := 1; i <= 150; i++ { 500 _ = getDocForTest(Type, testInstance) 501 } 502 503 // Create an index 504 e.POST("/data/"+Type+"/_index"). 505 WithHeader("Authorization", "Bearer "+token). 506 WithHeader("Content-Type", "application/json"). 507 WithBytes([]byte(`{ "index": { "fields": ["test"] } }`)). 508 Expect().Status(200). 509 JSON().Object(). 510 NotContainsKey("error") 511 512 // Select with the index 513 obj := e.POST("/data/"+Type+"/_find"). 514 WithHeader("Authorization", "Bearer "+token). 515 WithHeader("Content-Type", "application/json"). 516 WithBytes([]byte(`{ "selector": { "test": "value" } }`)). 517 Expect().Status(200). 518 JSON().Object() 519 520 docs := obj.Value("docs").Array() 521 docs.Length().Equal(100) 522 obj.ValueEqual("next", true) 523 524 // A new select with the index and a limit 525 obj = e.POST("/data/"+Type+"/_find"). 526 WithHeader("Authorization", "Bearer "+token). 527 WithHeader("Content-Type", "application/json"). 528 WithBytes([]byte(`{ "selector": { "test": "value" }, "limit": 10 }`)). 529 Expect().Status(200). 530 JSON().Object() 531 532 docs = obj.Value("docs").Array() 533 docs.Length().Equal(10) 534 obj.ValueEqual("next", true) 535 }) 536 537 t.Run("FindDocumentsPaginatedBookmark", func(t *testing.T) { 538 e := testutils.CreateTestClient(t, ts.URL) 539 540 _ = couchdb.ResetDB(testInstance, Type) 541 542 // Insert 200 docs 543 for i := 1; i <= 200; i++ { 544 _ = getDocForTest(Type, testInstance) 545 } 546 547 // Create an index 548 e.POST("/data/"+Type+"/_index"). 549 WithHeader("Authorization", "Bearer "+token). 550 WithHeader("Content-Type", "application/json"). 551 WithBytes([]byte(`{ "index": { "fields": ["test"] } }`)). 552 Expect().Status(200). 553 JSON().Object(). 554 NotContainsKey("error") 555 556 // Select with the index 557 obj := e.POST("/data/"+Type+"/_find"). 558 WithHeader("Authorization", "Bearer "+token). 559 WithHeader("Content-Type", "application/json"). 560 WithBytes([]byte(`{ "selector": { "test": "value" } }`)). 561 Expect().Status(200). 562 JSON().Object() 563 564 docs := obj.Value("docs").Array() 565 docs.Length().Equal(100) 566 obj.ValueEqual("limit", 100) 567 obj.ValueEqual("next", true) 568 bm := obj.Value("bookmark").String().NotEmpty().Raw() 569 570 // New select with the index and a bookmark 571 obj = e.POST("/data/"+Type+"/_find"). 572 WithHeader("Authorization", "Bearer "+token). 573 WithHeader("Content-Type", "application/json"). 574 WithBytes([]byte(`{ "selector": { "test": "value"}, "bookmark": "` + bm + `" }`)). 575 Expect().Status(200). 576 JSON().Object() 577 578 docs = obj.Value("docs").Array() 579 docs.Length().Equal(100) 580 obj.ValueEqual("limit", 100) 581 obj.ValueEqual("next", true) 582 bm = obj.Value("bookmark").String().NotEmpty().Raw() 583 584 // Select 3 with the index and the same bookmark 585 obj = e.POST("/data/"+Type+"/_find"). 586 WithHeader("Authorization", "Bearer "+token). 587 WithHeader("Content-Type", "application/json"). 588 WithBytes([]byte(`{ "selector": { "test": "value"}, "bookmark": "` + bm + `" }`)). 589 Expect().Status(200). 590 JSON().Object() 591 592 docs = obj.Value("docs").Array() 593 docs.Length().Equal(0) 594 obj.ValueEqual("next", false) 595 596 // Select 4 with the index a value matching nothing 597 obj = e.POST("/data/"+Type+"/_find"). 598 WithHeader("Authorization", "Bearer "+token). 599 WithHeader("Content-Type", "application/json"). 600 WithBytes([]byte(`{ "selector": { "test": "novalue" }}`)). 601 Expect().Status(200). 602 JSON().Object() 603 604 docs = obj.Value("docs").Array() 605 docs.Length().Equal(0) 606 obj.Value("bookmark").String().Empty() 607 }) 608 609 t.Run("FindDocumentsWithoutIndex", func(t *testing.T) { 610 e := testutils.CreateTestClient(t, ts.URL) 611 612 obj := e.POST("/data/"+Type+"/_find"). 613 WithHeader("Authorization", "Bearer "+token). 614 WithHeader("Content-Type", "application/json"). 615 WithBytes([]byte(`{ "selector": { "no-index-for-this-field": "value" } }`)). 616 Expect().Status(400). 617 JSON().Object() 618 619 obj.Value("error").String().Contains("no_inde") 620 obj.Value("reason").String().Contains("no matching index") 621 }) 622 623 t.Run("GetChanges", func(t *testing.T) { 624 e := testutils.CreateTestClient(t, ts.URL) 625 626 assert.NoError(t, couchdb.ResetDB(testInstance, Type)) 627 628 seqno := e.GET("/data/"+Type+"/_changes"). 629 WithQuery("style", "all_docs"). 630 WithHeader("Authorization", "Bearer "+token). 631 Expect().Status(200). 632 JSON().Object(). 633 Value("last_seq").String().NotEmpty().Raw() 634 635 // creates 3 docs 636 _ = getDocForTest(Type, testInstance) 637 _ = getDocForTest(Type, testInstance) 638 _ = getDocForTest(Type, testInstance) 639 640 e.GET("/data/"+Type+"/_changes"). 641 WithQuery("limit", 2). 642 WithQuery("since", seqno). 643 WithHeader("Authorization", "Bearer "+token). 644 Expect().Status(200). 645 JSON().Object(). 646 Value("results").Array().Length().Equal(2) 647 648 e.GET("/data/"+Type+"/_changes"). 649 WithQuery("since", seqno). 650 WithHeader("Authorization", "Bearer "+token). 651 Expect().Status(200). 652 JSON().Object(). 653 Value("results").Array().Length().Equal(3) 654 }) 655 656 t.Run("PostChanges", func(t *testing.T) { 657 e := testutils.CreateTestClient(t, ts.URL) 658 659 assert.NoError(t, couchdb.ResetDB(testInstance, Type)) 660 661 // creates 3 docs 662 doc1 := getDocForTest(Type, testInstance) 663 _ = getDocForTest(Type, testInstance) 664 doc2 := getDocForTest(Type, testInstance) 665 666 e.POST("/data/"+Type+"/_changes"). 667 WithQuery("include_docs", true). 668 WithQuery("filter", "_doc_ids"). 669 WithHeader("Authorization", "Bearer "+token). 670 WithBytes([]byte(fmt.Sprintf(`{"doc_ids": ["%s", "%s"]}`, doc1.ID(), doc2.ID()))). 671 Expect().Status(200). 672 JSON().Object(). 673 Value("results").Array().Length().Equal(2) 674 }) 675 676 t.Run("WrongFeedChanges", func(t *testing.T) { 677 e := testutils.CreateTestClient(t, ts.URL) 678 679 e.POST("/data/"+Type+"/_changes"). 680 WithQuery("feed", "continuous"). 681 WithHeader("Authorization", "Bearer "+token). 682 Expect().Status(400) 683 }) 684 685 t.Run("WrongStyleChanges", func(t *testing.T) { 686 e := testutils.CreateTestClient(t, ts.URL) 687 688 e.POST("/data/"+Type+"/_changes"). 689 WithQuery("style", "not_a_valid_style"). 690 WithHeader("Authorization", "Bearer "+token). 691 Expect().Status(400) 692 }) 693 694 t.Run("LimitIsNoNumber", func(t *testing.T) { 695 e := testutils.CreateTestClient(t, ts.URL) 696 697 e.POST("/data/"+Type+"/_changes"). 698 WithQuery("limit", "not_a_number"). 699 WithHeader("Authorization", "Bearer "+token). 700 Expect().Status(400) 701 }) 702 703 t.Run("UnsupportedOption", func(t *testing.T) { 704 e := testutils.CreateTestClient(t, ts.URL) 705 706 e.POST("/data/"+Type+"/_changes"). 707 WithQuery("inlude_docs", true). // typo (inlude instead of include) 708 WithHeader("Authorization", "Bearer "+token). 709 Expect().Status(400) 710 }) 711 712 t.Run("GetAllDocs", func(t *testing.T) { 713 e := testutils.CreateTestClient(t, ts.URL) 714 715 obj := e.GET("/data/"+Type+"/_all_docs"). 716 WithQuery("include_docs", true). 717 WithHeader("Authorization", "Bearer "+token). 718 Expect().Status(200). 719 JSON().Object() 720 721 obj.ValueEqual("total_rows", 3) 722 obj.ValueEqual("offset", 0) 723 rows := obj.Value("rows").Array() 724 rows.Length().Equal(3) 725 726 first := rows.First().Object() 727 first.Value("id").String().NotEmpty() 728 doc := first.Value("doc").Object() 729 doc.ValueEqual("test", "value") 730 doc.Path("$.foo.bar").Equal("one") 731 doc.Path("$.foo.baz").Equal("two") 732 doc.Path("$.foo.qux").Equal("quux") 733 }) 734 735 t.Run("GetAllDocsWithFields", func(t *testing.T) { 736 e := testutils.CreateTestClient(t, ts.URL) 737 738 obj := e.GET("/data/"+Type+"/_all_docs"). 739 WithQuery("include_docs", true). 740 WithQuery("Fields", "test,nosuchfield,foo.qux"). 741 WithHeader("Authorization", "Bearer "+token). 742 Expect().Status(200). 743 JSON().Object() 744 745 obj.ValueEqual("total_rows", 3) 746 obj.ValueEqual("offset", 0) 747 rows := obj.Value("rows").Array() 748 rows.Length().Equal(3) 749 750 first := rows.First().Object() 751 first.Value("id").String().NotEmpty() 752 doc := first.Value("doc").Object() 753 doc.ValueEqual("test", "value") 754 foo := doc.Value("foo").Object() 755 foo.NotContainsKey("bar") 756 foo.NotContainsKey("baz") 757 foo.ValueEqual("qux", "quux") 758 }) 759 760 t.Run("NormalDocs", func(t *testing.T) { 761 e := testutils.CreateTestClient(t, ts.URL) 762 763 view := &couchdb.View{ 764 Name: "foobar", 765 Doctype: Type, 766 Map: ` 767 function(doc) { 768 emit(doc.foobar, doc); 769 }`, 770 } 771 g, _ := errgroup.WithContext(context.Background()) 772 couchdb.DefineViews(g, testInstance, []*couchdb.View{view}) 773 require.NoError(t, g.Wait()) 774 775 err := couchdb.CreateNamedDoc(testInstance, &couchdb.JSONDoc{ 776 Type: Type, 777 M: map[string]interface{}{ 778 "_id": "four", 779 "test": "fourthvalue", 780 }, 781 }) 782 require.NoError(t, err) 783 784 obj := e.GET("/data/"+Type+"/_normal_docs"). 785 WithQuery("limit", 2). 786 WithHeader("Authorization", "Bearer "+token). 787 Expect().Status(200). 788 JSON().Object() 789 790 obj.ValueEqual("total_rows", 4) 791 obj.Value("bookmark").String().NotEmpty() 792 bookmark := obj.Value("bookmark").String().NotEmpty().Raw() 793 obj.NotContainsKey("execution_stats") 794 795 rows := obj.Value("rows").Array() 796 rows.Length().Equal(2) 797 798 elem := rows.Element(1).Object() 799 elem.Value("_id").String().NotEmpty() 800 elem.ValueEqual("test", "value") 801 802 // skip pagination 803 obj = e.GET("/data/"+Type+"/_normal_docs"). 804 WithQuery("limit", 2). 805 WithQuery("skip", 2). 806 WithHeader("Authorization", "Bearer "+token). 807 Expect().Status(200). 808 JSON().Object() 809 810 obj.ValueEqual("total_rows", 4) 811 obj.Value("bookmark").String().NotEmpty() 812 rows = obj.Value("rows").Array() 813 rows.Length().Equal(2) 814 815 elem = rows.Element(1).Object() 816 elem.Value("_id").String().NotEmpty() 817 elem.ValueEqual("test", "fourthvalue") 818 819 // bookmark pagination 820 obj = e.GET("/data/"+Type+"/_normal_docs"). 821 WithQuery("bookmark", bookmark). 822 WithHeader("Authorization", "Bearer "+token). 823 Expect().Status(200). 824 JSON().Object() 825 826 obj.ValueEqual("total_rows", 4) 827 rows = obj.Value("rows").Array() 828 rows.Length().Equal(2) 829 830 elem = rows.Element(1).Object() 831 elem.Value("_id").String().NotEmpty() 832 elem.ValueEqual("test", "fourthvalue") 833 834 // _normal_docs with no results 835 emptyType := "io.cozy.anothertype" 836 _ = couchdb.ResetDB(testInstance, emptyType) 837 838 obj = e.GET("/data/"+emptyType+"/_normal_docs"). 839 WithHeader("Authorization", "Bearer "+token). 840 Expect().Status(200). 841 JSON().Object() 842 843 obj.ValueEqual("total_rows", 0) 844 obj.Value("bookmark").String().Empty() 845 846 // execution stats 847 obj = e.GET("/data/"+emptyType+"/_normal_docs"). 848 WithQuery("execution_stats", true). 849 WithHeader("Authorization", "Bearer "+token). 850 Expect().Status(200). 851 JSON().Object() 852 853 obj.Value("execution_stats").Object().NotEmpty() 854 }) 855 856 t.Run("GetDesignDocs", func(t *testing.T) { 857 e := testutils.CreateTestClient(t, ts.URL) 858 859 def := M{"index": M{"fields": S{"foo"}}} 860 _, err := couchdb.DefineIndexRaw(testInstance, Type, &def) 861 require.NoError(t, err) 862 863 obj := e.GET("/data/"+Type+"/_design_docs"). 864 WithHeader("Authorization", "Bearer "+token). 865 Expect().Status(200). 866 JSON().Object() 867 868 rows := obj.Value("rows").Array() 869 rows.Length().Gt(0) 870 871 elem := rows.First().Object() 872 elem.Value("id").String().NotEmpty() 873 elem.Path("$.value.rev").String().NotEmpty() 874 }) 875 876 t.Run("GetDesignDoc", func(t *testing.T) { 877 e := testutils.CreateTestClient(t, ts.URL) 878 879 ddoc := "myindex" 880 def := M{"index": M{"fields": S{"foo"}}, "ddoc": ddoc} 881 _, err := couchdb.DefineIndexRaw(testInstance, Type, &def) 882 assert.NoError(t, err) 883 884 obj := e.GET("/data/"+Type+"/_design/"+ddoc). 885 WithHeader("Authorization", "Bearer "+token). 886 Expect().Status(200). 887 JSON().Object() 888 889 obj.ValueEqual("_id", "_design/"+ddoc) 890 obj.Value("_rev").String().NotEmpty() 891 }) 892 893 t.Run("DeleteDesignDoc", func(t *testing.T) { 894 e := testutils.CreateTestClient(t, ts.URL) 895 896 def := M{"index": M{"fields": S{"foo"}}} 897 _, err := couchdb.DefineIndexRaw(testInstance, Type, &def) 898 require.NoError(t, err) 899 900 // Get the number of design document 901 obj := e.GET("/data/"+Type+"/_design_docs"). 902 WithHeader("Authorization", "Bearer "+token). 903 Expect().Status(200). 904 JSON().Object() 905 906 rows := obj.Value("rows").Array() 907 nbDD := rows.Length().Gt(0).Raw() 908 909 elem := rows.Element(0).Object() 910 id := elem.Value("id").String().NotEmpty().Raw() 911 912 ddoc := strings.Split(id, "/")[1] 913 rev := elem.Path("$.value.rev").String().NotEmpty().Raw() 914 915 // Delete the first design document 916 e.DELETE("/data/"+Type+"/_design/"+ddoc). 917 WithQuery("rev", rev). 918 WithHeader("Authorization", "Bearer "+token). 919 Expect().Status(200) 920 921 // Check that the number of design document have decreased 922 obj = e.GET("/data/"+Type+"/_design_docs"). 923 WithHeader("Authorization", "Bearer "+token). 924 Expect().Status(200). 925 JSON().Object() 926 927 rows = obj.Value("rows").Array() 928 rows.Length().Lt(nbDD) 929 }) 930 931 t.Run("CannotDeleteStackDesignDoc", func(t *testing.T) { 932 e := testutils.CreateTestClient(t, ts.URL) 933 934 // Fetch the dir-by-path and by-parent-type-name indexes rev 935 obj := e.GET("/data/io.cozy.files/_design_docs"). 936 WithHeader("Authorization", "Bearer "+token). 937 Expect().Status(200). 938 JSON().Object() 939 940 var indexRev, viewRev string 941 942 for _, row := range obj.Value("rows").Array().Iter() { 943 info := row.Object() 944 if info.Value("id").String().Raw() == "_design/dir-by-path" { 945 indexRev = info.Path("$.value.rev").String().NotEmpty().Raw() 946 } 947 if info.Value("id").String().Raw() == "_design/by-parent-type-name" { 948 viewRev = info.Path("$.value.rev").String().NotEmpty().Raw() 949 } 950 } 951 952 // Try to delete the dir-by-path index 953 e.DELETE("/data/io.cozy.files/_design/dir-by-path"). 954 WithQuery("rev", indexRev). 955 WithHeader("Authorization", "Bearer "+token). 956 Expect().Status(403) 957 958 // Try to delete the dir-by-path index 959 e.DELETE("/data/io.cozy.files/_design/by-parent-type-name"). 960 WithQuery("rev", viewRev). 961 WithHeader("Authorization", "Bearer "+token). 962 Expect().Status(403) 963 }) 964 965 t.Run("CopyDesignDoc", func(t *testing.T) { 966 e := testutils.CreateTestClient(t, ts.URL) 967 968 srcDdoc := "indextocopy" 969 targetID := "_design/indexcopied" 970 def := M{"index": M{"fields": S{"foo"}}, "ddoc": srcDdoc} 971 _, err := couchdb.DefineIndexRaw(testInstance, Type, &def) 972 assert.NoError(t, err) 973 974 rev := e.GET("/data/"+Type+"/_design/"+srcDdoc). 975 WithHeader("Authorization", "Bearer "+token). 976 Expect().Status(200). 977 JSON().Object(). 978 Value("_rev").String().NotEmpty().Raw() 979 980 e.POST("/data/"+Type+"/_design/"+srcDdoc+"/copy"). 981 WithQuery("rev", rev). 982 WithHeader("Authorization", "Bearer "+token). 983 WithHeader("Destination", targetID). 984 Expect().Status(201). 985 JSON().Object(). 986 ValueEqual("id", targetID). 987 ValueEqual("rev", rev) 988 }) 989 990 t.Run("DeleteDatabase", func(t *testing.T) { 991 e := testutils.CreateTestClient(t, ts.URL) 992 993 e.DELETE("/data/"+Type+"/"). 994 WithHeader("Authorization", "Bearer "+token). 995 Expect().Status(200). 996 JSON().Object(). 997 ValueEqual("deleted", true) 998 }) 999 1000 t.Run("DeleteDatabaseNoPermission", func(t *testing.T) { 1001 e := testutils.CreateTestClient(t, ts.URL) 1002 1003 doctype := "io.cozy.forbidden" 1004 e.DELETE("/data/"+doctype+"/"). 1005 WithHeader("Authorization", "Bearer "+token). 1006 Expect().Status(403) 1007 }) 1008 } 1009 1010 func getDocForTest(t string, instance *instance.Instance) *couchdb.JSONDoc { 1011 doc := couchdb.JSONDoc{ 1012 Type: t, 1013 M: map[string]interface{}{ 1014 "test": "value", 1015 "foo": map[string]interface{}{"bar": "one", "baz": "two", "qux": "quux"}, 1016 "courge": 1, 1017 }, 1018 } 1019 _ = couchdb.CreateDoc(instance, &doc) 1020 return &doc 1021 }