github.com/cozy/cozy-stack@v0.0.0-20240603063001-31110fa4cae1/web/files/files_test.go (about) 1 package files 2 3 import ( 4 "fmt" 5 "io" 6 "net/http" 7 "net/url" 8 "os" 9 "path" 10 "strconv" 11 "sync/atomic" 12 "testing" 13 "time" 14 15 "github.com/cozy/cozy-stack/model/instance/lifecycle" 16 "github.com/cozy/cozy-stack/model/permission" 17 "github.com/cozy/cozy-stack/model/vfs" 18 "github.com/cozy/cozy-stack/pkg/config/config" 19 "github.com/cozy/cozy-stack/pkg/consts" 20 "github.com/cozy/cozy-stack/pkg/couchdb" 21 "github.com/cozy/cozy-stack/pkg/i18n" 22 "github.com/cozy/cozy-stack/pkg/limits" 23 "github.com/cozy/cozy-stack/tests/testutils" 24 "github.com/cozy/cozy-stack/web/errors" 25 "github.com/cozy/cozy-stack/web/middlewares" 26 "github.com/gavv/httpexpect/v2" 27 "github.com/labstack/echo/v4" 28 "github.com/stretchr/testify/assert" 29 "github.com/stretchr/testify/require" 30 31 _ "github.com/cozy/cozy-stack/web/statik" 32 _ "github.com/cozy/cozy-stack/worker/thumbnail" 33 ) 34 35 func TestFiles(t *testing.T) { 36 if testing.Short() { 37 t.Skip("an instance is required for this test: test skipped due to the use of --short flag") 38 } 39 40 var imgID string 41 var token string 42 var fileID string 43 44 config.UseTestFile(t) 45 require.NoError(t, loadLocale(), "Could not load default locale translations") 46 47 testutils.NeedCouchdb(t) 48 setup := testutils.NewSetup(t, t.Name()) 49 50 config.GetConfig().Fs.URL = &url.URL{ 51 Scheme: "file", 52 Host: "localhost", 53 Path: t.TempDir(), 54 } 55 56 testInstance := setup.GetTestInstance() 57 _, tok := setup.GetTestClient(consts.Files + " " + consts.CertifiedCarbonCopy + " " + consts.CertifiedElectronicSafe) 58 token = tok 59 ts := setup.GetTestServer("/files", Routes, func(r *echo.Echo) *echo.Echo { 60 secure := middlewares.Secure(&middlewares.SecureConfig{ 61 CSPDefaultSrc: []middlewares.CSPSource{middlewares.CSPSrcSelf}, 62 CSPFrameAncestors: []middlewares.CSPSource{middlewares.CSPSrcNone}, 63 }) 64 r.Use(secure) 65 return r 66 }) 67 ts.Config.Handler.(*echo.Echo).HTTPErrorHandler = errors.ErrorHandler 68 t.Cleanup(ts.Close) 69 70 t.Run("Changes", func(t *testing.T) { 71 e := testutils.CreateTestClient(t, ts.URL) 72 73 // Create dir "/foo" 74 fooID := e.POST("/files/"). 75 WithQuery("Name", "foo"). 76 WithQuery("Type", "directory"). 77 WithHeader("Authorization", "Bearer "+token). 78 Expect().Status(201). 79 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 80 Object().Path("$.data.id").String().NotEmpty().Raw() 81 82 // Create dir "/foo/bar" 83 barID := e.POST("/files/"+fooID). 84 WithQuery("Name", "bar"). 85 WithQuery("Type", "directory"). 86 WithHeader("Authorization", "Bearer "+token). 87 Expect().Status(201). 88 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 89 Object().Path("$.data.id").String().NotEmpty().Raw() 90 91 // Create the file "/foo/bar/baz" 92 e.POST("/files/"+barID). 93 WithQuery("Name", "baz"). 94 WithQuery("Type", "file"). 95 WithHeader("Content-Type", "text/plain"). 96 WithHeader("Authorization", "Bearer "+token). 97 WithBytes([]byte("baz")). 98 Expect().Status(201) 99 100 // Create dir "/foo/qux" 101 quxID := e.POST("/files/"+fooID). 102 WithQuery("Name", "quz"). 103 WithQuery("Type", "directory"). 104 WithHeader("Authorization", "Bearer "+token). 105 Expect().Status(201). 106 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 107 Object().Path("$.data.id").String().NotEmpty().Raw() 108 109 // Delete dir "/foo/qux" 110 e.DELETE("/files/"+quxID). 111 WithHeader("Authorization", "Bearer "+token). 112 Expect().Status(200) 113 114 // Empty Trash 115 e.DELETE("/files/trash"). 116 WithHeader("Authorization", "Bearer "+token). 117 Expect().Status(204) 118 119 obj := e.GET("/files/_changes"). 120 WithQuery("include_docs", true). 121 WithQuery("include_file_path", true). 122 WithHeader("Authorization", "Bearer "+token). 123 Expect().Status(200). 124 JSON().Object() 125 126 obj.Value("last_seq").String().NotEmpty() 127 obj.Value("pending").Number() 128 results := obj.Value("results").Array() 129 130 // Check if there is a deleted doc 131 results.Find(func(_ int, value *httpexpect.Value) bool { 132 value.Object().Value("id").String().NotEmpty() 133 value.Object().ValueEqual("deleted", true) 134 return true 135 }). 136 NotNull() 137 138 // Check if we can find the trashed "/foo/qux" 139 results.Find(func(_ int, value *httpexpect.Value) bool { 140 doc := value.Object().Value("doc").Object() 141 doc.ValueEqual("type", "directory") 142 doc.ValueEqual("path", "/.cozy_trash") 143 return true 144 }). 145 NotNull() 146 147 obj = e.GET("/files/_changes"). 148 WithQuery("include_docs", true). 149 WithQuery("fields", "type,name,dir_id"). 150 WithHeader("Authorization", "Bearer "+token). 151 Expect().Status(200). 152 JSON(). 153 Object() 154 155 obj.Value("last_seq").String().NotEmpty() 156 obj.Value("pending").Number() 157 158 results = obj.Value("results").Array() 159 results.Every(func(_ int, value *httpexpect.Value) { 160 res := value.Object() 161 162 res.Value("id").String().NotEmpty() 163 164 // Skip the deleted entry and the root dir 165 if _, ok := res.Raw()["deleted"]; ok || res.Value("id").String().Raw() == "io.cozy.files.root-dir" { 166 return 167 } 168 169 doc := res.Value("doc").Object() 170 doc.Value("type").String().NotEmpty() 171 doc.Value("name").String().NotEmpty() 172 doc.Value("dir_id").String().NotEmpty() 173 doc.NotContainsKey("path") 174 doc.NotContainsKey("metadata") 175 doc.NotContainsKey("created_at") 176 }) 177 178 // Delete dir "/foo/bar" 179 e.DELETE("/files/"+barID). 180 WithHeader("Authorization", "Bearer "+token). 181 Expect().Status(200) 182 183 obj = e.GET("/files/_changes"). 184 WithQuery("include_docs", true). 185 WithQuery("skip_deleted", "true"). 186 WithQuery("skip_trashed", "true"). 187 WithHeader("Authorization", "Bearer "+token). 188 Expect().Status(200). 189 JSON(). 190 Object() 191 192 obj.Value("last_seq").String().NotEmpty() 193 obj.Value("pending").Number() 194 195 results = obj.Value("results").Array() 196 197 // Check if there is a deleted doc 198 results.NotFind(func(_ int, value *httpexpect.Value) bool { 199 value.Object().ValueEqual("deleted", true) 200 return true 201 }) 202 203 // Check if we can find a trashed file 204 results.NotFind(func(_ int, value *httpexpect.Value) bool { 205 doc := value.Object().Value("doc").Object() 206 doc.Value("path").String().HasPrefix("/.cozy_trash") 207 return true 208 }) 209 210 // Delete dir "/foo" 211 e.DELETE("/files/"+fooID). 212 WithHeader("Authorization", "Bearer "+token). 213 Expect().Status(200) 214 215 // Empty Trash 216 e.DELETE("/files/trash"). 217 WithHeader("Authorization", "Bearer "+token). 218 Expect().Status(204) 219 }) 220 221 t.Run("CreateDirWithNoType", func(t *testing.T) { 222 e := testutils.CreateTestClient(t, ts.URL) 223 224 e.POST("/files/"). 225 WithHeader("Authorization", "Bearer "+token). 226 Expect().Status(422) 227 }) 228 229 t.Run("CreateDirWithNoName", func(t *testing.T) { 230 e := testutils.CreateTestClient(t, ts.URL) 231 232 e.POST("/files/"). 233 WithQuery("Type", "directory"). 234 WithHeader("Authorization", "Bearer "+token). 235 Expect().Status(422) 236 }) 237 238 t.Run("CreateDirOnNonExistingParent", func(t *testing.T) { 239 e := testutils.CreateTestClient(t, ts.URL) 240 241 e.POST("/files/noooooop"). 242 WithQuery("Name", "foo"). 243 WithQuery("Type", "directory"). 244 WithHeader("Authorization", "Bearer "+token). 245 Expect().Status(404) 246 }) 247 248 t.Run("CreateDirAlreadyExists", func(t *testing.T) { 249 e := testutils.CreateTestClient(t, ts.URL) 250 251 e.POST("/files/"). 252 WithQuery("Name", "iexist"). 253 WithQuery("Type", "directory"). 254 WithHeader("Authorization", "Bearer "+token). 255 Expect().Status(201) 256 257 e.POST("/files/"). 258 WithQuery("Name", "iexist"). 259 WithQuery("Type", "directory"). 260 WithHeader("Authorization", "Bearer "+token). 261 Expect().Status(409) 262 }) 263 264 t.Run("CreateDirRootSuccess", func(t *testing.T) { 265 e := testutils.CreateTestClient(t, ts.URL) 266 267 e.POST("/files/"). 268 WithQuery("Name", "coucou"). 269 WithQuery("Type", "directory"). 270 WithHeader("Authorization", "Bearer "+token). 271 Expect().Status(201) 272 273 storage := testInstance.VFS() 274 exists, err := vfs.DirExists(storage, "/coucou") 275 assert.NoError(t, err) 276 assert.True(t, exists) 277 }) 278 279 t.Run("CreateDirWithDateSuccess", func(t *testing.T) { 280 e := testutils.CreateTestClient(t, ts.URL) 281 282 obj := e.POST("/files/"). 283 WithQuery("Name", "dir-with-date"). 284 WithQuery("Type", "directory"). 285 WithQuery("CreatedAt", "2016-09-18T10:24:53Z"). 286 WithHeader("Authorization", "Bearer "+token). 287 WithHeader("Date", "Mon, 19 Sep 2016 12:35:08 GMT"). 288 Expect().Status(201). 289 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 290 Object() 291 292 attrs := obj.Path("$.data.attributes").Object() 293 attrs.ValueEqual("created_at", "2016-09-18T10:24:53Z") 294 attrs.ValueEqual("updated_at", "2016-09-19T12:35:08Z") 295 296 fcm := attrs.Value("cozyMetadata").Object() 297 fcm.ValueEqual("metadataVersion", 1.0) 298 fcm.ValueEqual("doctypeVersion", "1") 299 fcm.Value("createdOn").String().Contains(testInstance.Domain) 300 fcm.Value("createdAt").String().DateTime(time.RFC3339) 301 fcm.Value("updatedAt").String().DateTime(time.RFC3339) 302 fcm.NotContainsKey("uploadedAt") 303 }) 304 305 t.Run("CreateDirWithDateSuccessAndUpdatedAt", func(t *testing.T) { 306 e := testutils.CreateTestClient(t, ts.URL) 307 308 obj := e.POST("/files/"). 309 WithQuery("Type", "directory"). 310 WithQuery("Name", "dir-with-date-and-updatedat"). 311 WithQuery("CreatedAt", "2016-09-18T10:24:53Z"). 312 WithQuery("UpdatedAt", "2020-05-12T12:25:00Z"). 313 WithHeader("Authorization", "Bearer "+token). 314 WithHeader("Date", "Mon, 19 Sep 2016 12:35:08 GMT"). 315 Expect().Status(201). 316 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 317 Object() 318 319 attrs := obj.Path("$.data.attributes").Object() 320 attrs.ValueEqual("created_at", "2016-09-18T10:24:53Z") 321 attrs.ValueEqual("updated_at", "2020-05-12T12:25:00Z") 322 323 fcm := attrs.Value("cozyMetadata").Object() 324 fcm.ValueEqual("metadataVersion", 1.0) 325 fcm.ValueEqual("doctypeVersion", "1") 326 fcm.Value("createdOn").String().Contains(testInstance.Domain) 327 fcm.Value("createdAt").String().DateTime(time.RFC3339) 328 fcm.Value("updatedAt").String().DateTime(time.RFC3339) 329 fcm.NotContainsKey("uploadedAt") 330 }) 331 332 t.Run("CreateDirWithParentSuccess", func(t *testing.T) { 333 e := testutils.CreateTestClient(t, ts.URL) 334 335 // Create dir "/dirparent" 336 parentID := e.POST("/files/"). 337 WithQuery("Name", "dirparent"). 338 WithQuery("Type", "directory"). 339 WithHeader("Authorization", "Bearer "+token). 340 Expect().Status(201). 341 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 342 Object().Path("$.data.id").String().NotEmpty().Raw() 343 344 // Create dir "/dirparent/child" 345 e.POST("/files/"+parentID). 346 WithQuery("Name", "child"). 347 WithQuery("Type", "directory"). 348 WithHeader("Authorization", "Bearer "+token). 349 Expect().Status(201) 350 351 storage := testInstance.VFS() 352 exists, err := vfs.DirExists(storage, "/dirparent/child") 353 assert.NoError(t, err) 354 assert.True(t, exists) 355 }) 356 357 t.Run("CreateDirWithIllegalCharacter", func(t *testing.T) { 358 e := testutils.CreateTestClient(t, ts.URL) 359 360 e.POST("/files/"). 361 WithQuery("Name", "coucou/with/slashs!"). 362 WithQuery("Type", "directory"). 363 WithHeader("Authorization", "Bearer "+token). 364 Expect().Status(422) 365 }) 366 367 t.Run("CreateDirWithMetadata", func(t *testing.T) { 368 e := testutils.CreateTestClient(t, ts.URL) 369 370 obj := e.POST("/files/upload/metadata"). 371 WithHeader("Content-Type", "application/json"). 372 WithHeader("Authorization", "Bearer "+token). 373 WithBytes([]byte(`{ 374 "data": { 375 "type": "io.cozy.files.metadata", 376 "attributes": { 377 "device-id": "123456789" 378 } 379 } 380 }`)). 381 Expect().Status(201). 382 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 383 Object() 384 385 data := obj.Value("data").Object() 386 data.ValueEqual("type", consts.FilesMetadata) 387 attrs := data.Value("attributes").Object() 388 attrs.ValueEqual("device-id", "123456789") 389 secret := data.Value("id").String().NotEmpty().Raw() 390 391 obj = e.POST("/files/"). 392 WithQuery("Name", "dir-with-metadata"). 393 WithQuery("Type", "directory"). 394 WithQuery("MetadataID", secret). 395 WithHeader("Authorization", "Bearer "+token). 396 Expect().Status(201). 397 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 398 Object() 399 400 dirID := obj.Path("$.data.id").String().NotEmpty().Raw() 401 meta := obj.Path("$.data.attributes.metadata").Object() 402 meta.ValueEqual("device-id", "123456789") 403 404 // Check that the metadata are still here after an update 405 obj = e.PATCH("/files/"+dirID). 406 WithQuery("Path", "/dir-with-metadata"). 407 WithHeader("Content-Type", "application/json"). 408 WithHeader("Authorization", "Bearer "+token). 409 WithBytes([]byte(`{ 410 "data": { 411 "type": "file", 412 "id": "` + dirID + `", 413 "attributes": { 414 "name": "new-name-for-dir-with-metadata" 415 } 416 } 417 }`)). 418 Expect().Status(200). 419 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 420 Object() 421 422 meta = obj.Path("$.data.attributes.metadata").Object() 423 meta.ValueEqual("device-id", "123456789") 424 425 obj = e.GET("/files/"+dirID). 426 WithHeader("Authorization", "Bearer "+token). 427 Expect().Status(200). 428 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 429 Object() 430 431 meta = obj.Path("$.data.attributes.metadata").Object() 432 meta.ValueEqual("device-id", "123456789") 433 }) 434 435 t.Run("CreateDirConcurrently", func(t *testing.T) { 436 e := testutils.CreateTestClient(t, ts.URL) 437 438 done := make(chan int) 439 errs := make(chan int) 440 441 doCreateDir := func(name string) { 442 res := e.POST("/files/"). 443 WithQuery("Name", name). 444 WithQuery("Type", "directory"). 445 WithHeader("Authorization", "Bearer "+token). 446 Expect().Raw() 447 _ = res.Body.Close() 448 449 if res.StatusCode == 201 { 450 done <- res.StatusCode 451 } else { 452 errs <- res.StatusCode 453 } 454 } 455 456 n := 100 457 c := 0 458 459 for i := 0; i < n; i++ { 460 go doCreateDir("foo") 461 } 462 463 for i := 0; i < n; i++ { 464 select { 465 case res := <-errs: 466 assert.True(t, res == 409 || res == 503) 467 case <-done: 468 c += 1 469 } 470 } 471 472 assert.Equal(t, 1, c) 473 }) 474 475 t.Run("UploadWithNoType", func(t *testing.T) { 476 e := testutils.CreateTestClient(t, ts.URL) 477 478 e.POST("/files/"). 479 WithQuery("Name", "baz"). 480 WithHeader("Content-Type", "text/plain"). 481 WithHeader("Authorization", "Bearer "+token). 482 WithBytes([]byte("baz")). 483 Expect().Status(422) 484 }) 485 486 t.Run("UploadWithNoName", func(t *testing.T) { 487 e := testutils.CreateTestClient(t, ts.URL) 488 489 e.POST("/files/"). 490 WithQuery("Type", "file"). 491 WithHeader("Content-Type", "text/plain"). 492 WithHeader("Authorization", "Bearer "+token). 493 WithBytes([]byte("baz")). 494 Expect().Status(422) 495 }) 496 497 t.Run("UploadToNonExistingParent", func(t *testing.T) { 498 e := testutils.CreateTestClient(t, ts.URL) 499 500 e.POST("/files/noooop"). 501 WithQuery("Type", "file"). 502 WithQuery("Name", "no-parent"). 503 WithHeader("Content-Type", "text/plain"). 504 WithHeader("Authorization", "Bearer "+token). 505 WithBytes([]byte("baz")). 506 Expect().Status(404) 507 }) 508 509 t.Run("UploadWithInvalidContentType", func(t *testing.T) { 510 e := testutils.CreateTestClient(t, ts.URL) 511 512 e.POST("/files/"). 513 WithQuery("Type", "file"). 514 WithQuery("Name", "invalid-mime"). 515 WithHeader("Content-Type", "foo € / bar"). 516 WithHeader("Authorization", "Bearer "+token). 517 WithBytes([]byte("baz")). 518 Expect().Status(422) 519 }) 520 521 t.Run("UploadToTrashedFolder", func(t *testing.T) { 522 e := testutils.CreateTestClient(t, ts.URL) 523 524 // Create dir "/foo" 525 dirID := e.POST("/files/"). 526 WithQuery("Name", "trashed-parent"). 527 WithQuery("Type", "directory"). 528 WithHeader("Authorization", "Bearer "+token). 529 Expect().Status(201). 530 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 531 Object().Path("$.data.id").String().NotEmpty().Raw() 532 533 e.DELETE("/files/"+dirID). 534 WithHeader("Authorization", "Bearer "+token). 535 Expect().Status(200) 536 537 e.POST("/files/"+dirID). 538 WithQuery("Type", "file"). 539 WithQuery("Name", "foo"). 540 WithHeader("Content-Type", "text/plain"). 541 WithHeader("Authorization", "Bearer "+token). 542 WithBytes([]byte("baz")). 543 Expect().Status(404) 544 }) 545 546 t.Run("UploadBadSize", func(t *testing.T) { 547 e := testutils.CreateTestClient(t, ts.URL) 548 549 e.POST("/files/"). 550 WithQuery("Type", "file"). 551 WithQuery("Name", "bad-size"). 552 WithQuery("Size", 42). 553 WithHeader("Content-Type", "text/plain"). 554 WithHeader("Authorization", "Bearer "+token). 555 WithTransformer(func(r *http.Request) { r.ContentLength = -1 }). 556 WithBytes([]byte("baz")). // not 42 byte 557 Expect().Status(412) 558 559 storage := testInstance.VFS() 560 _, err := readFile(storage, "/badsize") 561 assert.Error(t, err) 562 }) 563 564 t.Run("UploadBadHash", func(t *testing.T) { 565 e := testutils.CreateTestClient(t, ts.URL) 566 567 e.POST("/files/"). 568 WithQuery("Type", "file"). 569 WithQuery("Name", "bad-hash"). 570 WithHeader("Content-Type", "text/plain"). 571 WithHeader("Content-MD5", "3FbbMXfH+PdjAlWFfVb1dQ=="). // invalid md5 572 WithHeader("Authorization", "Bearer "+token). 573 WithBytes([]byte("foo")). 574 Expect().Status(412) 575 576 storage := testInstance.VFS() 577 _, err := readFile(storage, "/badhash") 578 assert.Error(t, err) 579 }) 580 581 t.Run("UploadAtRootSuccess", func(t *testing.T) { 582 e := testutils.CreateTestClient(t, ts.URL) 583 584 e.POST("/files/"). 585 WithQuery("Type", "file"). 586 WithQuery("Name", "goodhash"). 587 WithHeader("Content-Type", "text/plain"). 588 WithHeader("Content-MD5", "rL0Y20zC+Fzt72VPzMSk2A=="). 589 WithHeader("Authorization", "Bearer "+token). 590 WithBytes([]byte("foo")). 591 Expect().Status(201) 592 593 storage := testInstance.VFS() 594 buf, err := readFile(storage, "/goodhash") 595 assert.NoError(t, err) 596 assert.Equal(t, "foo", string(buf)) 597 }) 598 599 t.Run("UploadExceedingQuota", func(t *testing.T) { 600 e := testutils.CreateTestClient(t, ts.URL) 601 602 lifecycle.Patch(testInstance, &lifecycle.Options{DiskQuota: 3}) 603 604 e.POST("/files/"). 605 WithQuery("Type", "file"). 606 WithQuery("Name", "too-large"). 607 WithQuery("Size", 3). 608 WithHeader("Content-Type", "text/plain"). 609 WithHeader("Authorization", "Bearer "+token). 610 WithTransformer(func(r *http.Request) { r.ContentLength = -1 }). 611 WithBytes([]byte("baz")). 612 Expect(). 613 Status(413). 614 Body().Contains(vfs.ErrFileTooBig.Error()) 615 616 storage := testInstance.VFS() 617 _, err := readFile(storage, "/too-large") 618 assert.Error(t, err) 619 620 lifecycle.Patch(testInstance, &lifecycle.Options{DiskQuota: -1}) 621 }) 622 623 t.Run("UploadImage", func(t *testing.T) { 624 e := testutils.CreateTestClient(t, ts.URL) 625 626 rawFile, err := os.ReadFile("../../tests/fixtures/wet-cozy_20160910__M4Dz.jpg") 627 require.NoError(t, err) 628 629 obj := e.POST("/files/"). 630 WithQuery("Type", "file"). 631 WithQuery("Name", "wet.jpg"). 632 WithQuery("Metadata", `{"gps":{"city":"Paris","country":"France"}}`). 633 WithHeader("Content-MD5", "tHWYYuXBBflJ8wXgJ2c2yg=="). 634 WithHeader("Content-Type", "image/jpeg"). 635 WithHeader("Authorization", "Bearer "+token). 636 WithBytes(rawFile). 637 Expect().Status(201). 638 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 639 Object() 640 641 data := obj.Value("data").Object() 642 imgID = data.Value("id").String().NotEmpty().Raw() 643 644 data.Path("$.attributes.created_at").String().HasPrefix("2016-09-10T") 645 646 meta := data.Path("$.attributes.metadata").Object() 647 meta.ValueEqual("extractor_version", float64(vfs.MetadataExtractorVersion)) 648 meta.ValueEqual("flash", "Off, Did not fire") 649 650 gps := meta.Value("gps").Object() 651 gps.ValueEqual("city", "Paris") 652 gps.ValueEqual("country", "France") 653 }) 654 655 t.Run("UploadShortcut", func(t *testing.T) { 656 e := testutils.CreateTestClient(t, ts.URL) 657 658 rawFile, err := os.ReadFile("../../tests/fixtures/shortcut.url") 659 require.NoError(t, err) 660 661 obj := e.POST("/files/"). 662 WithQuery("Type", "file"). 663 WithQuery("Name", "shortcut.url"). 664 WithHeader("Content-MD5", "+tHtr9V8+4gcCDxTFAqt3w=="). 665 WithHeader("Content-Type", "application/octet-stream"). 666 WithHeader("Authorization", "Bearer "+token). 667 WithBytes(rawFile). 668 Expect().Status(201). 669 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 670 Object() 671 672 attrs := obj.Path("$.data.attributes").Object() 673 attrs.ValueEqual("mime", "application/internet-shortcut") 674 attrs.ValueEqual("class", "shortcut") 675 }) 676 677 t.Run("UploadWithParentSuccess", func(t *testing.T) { 678 e := testutils.CreateTestClient(t, ts.URL) 679 680 parentID := e.POST("/files/"). 681 WithQuery("Name", "fileparent"). 682 WithQuery("Type", "directory"). 683 WithHeader("Authorization", "Bearer "+token). 684 Expect().Status(201). 685 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 686 Object().Path("$.data.id").String().NotEmpty().Raw() 687 688 e.POST("/files/"+parentID). 689 WithQuery("Name", "goodhash"). 690 WithQuery("Type", "file"). 691 WithHeader("Content-Type", "text/plain"). 692 WithHeader("Authorization", "Bearer "+token). 693 WithHeader("Content-MD5", "rL0Y20zC+Fzt72VPzMSk2A=="). 694 WithBytes([]byte("foo")). 695 Expect().Status(201) 696 697 storage := testInstance.VFS() 698 buf, err := readFile(storage, "/fileparent/goodhash") 699 assert.NoError(t, err) 700 assert.Equal(t, "foo", string(buf)) 701 }) 702 703 t.Run("UploadAtRootAlreadyExists", func(t *testing.T) { 704 e := testutils.CreateTestClient(t, ts.URL) 705 706 e.POST("/files/"). 707 WithQuery("Name", "iexistfile"). 708 WithQuery("Type", "file"). 709 WithHeader("Content-Type", "text/plain"). 710 WithHeader("Authorization", "Bearer "+token). 711 WithHeader("Content-MD5", "rL0Y20zC+Fzt72VPzMSk2A=="). 712 WithBytes([]byte("foo")). 713 Expect().Status(201) 714 715 // Same file 716 e.POST("/files/"). 717 WithQuery("Name", "iexistfile"). 718 WithQuery("Type", "file"). 719 WithHeader("Content-Type", "text/plain"). 720 WithHeader("Authorization", "Bearer "+token). 721 WithHeader("Content-MD5", "rL0Y20zC+Fzt72VPzMSk2A=="). 722 WithBytes([]byte("foo")). 723 Expect().Status(409) 724 }) 725 726 t.Run("UploadWithParentAlreadyExists", func(t *testing.T) { 727 e := testutils.CreateTestClient(t, ts.URL) 728 729 parentID := e.POST("/files/"). 730 WithQuery("Name", "container"). 731 WithQuery("Type", "directory"). 732 WithHeader("Authorization", "Bearer "+token). 733 Expect().Status(201). 734 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 735 Object().Path("$.data.id").String().NotEmpty().Raw() 736 737 e.POST("/files/"+parentID). 738 WithQuery("Name", "iexistfile"). 739 WithQuery("Type", "file"). 740 WithHeader("Content-Type", "text/plain"). 741 WithHeader("Authorization", "Bearer "+token). 742 WithHeader("Content-MD5", "rL0Y20zC+Fzt72VPzMSk2A=="). 743 WithBytes([]byte("foo")). 744 Expect().Status(201) 745 746 // Same file, same path 747 e.POST("/files/"+parentID). 748 WithQuery("Name", "iexistfile"). 749 WithQuery("Type", "file"). 750 WithHeader("Content-Type", "text/plain"). 751 WithHeader("Authorization", "Bearer "+token). 752 WithHeader("Content-MD5", "rL0Y20zC+Fzt72VPzMSk2A=="). 753 WithBytes([]byte("foo")). 754 Expect().Status(409) 755 }) 756 757 t.Run("UploadWithCreatedAtAndHeaderDate", func(t *testing.T) { 758 e := testutils.CreateTestClient(t, ts.URL) 759 760 obj := e.POST("/files/"). 761 WithQuery("Name", "withcdate"). 762 WithQuery("Type", "file"). 763 WithQuery("CreatedAt", "2016-09-18T10:24:53Z"). 764 WithHeader("Content-Type", "text/plain"). 765 WithHeader("Date", "Mon, 19 Sep 2016 12:38:04 GMT"). 766 WithHeader("Authorization", "Bearer "+token). 767 WithHeader("Content-MD5", "rL0Y20zC+Fzt72VPzMSk2A=="). 768 WithBytes([]byte("foo")). 769 Expect().Status(201). 770 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 771 Object() 772 773 attrs := obj.Path("$.data.attributes").Object() 774 attrs.ValueEqual("created_at", "2016-09-18T10:24:53Z") 775 attrs.ValueEqual("updated_at", "2016-09-19T12:38:04Z") 776 }) 777 778 t.Run("UploadWithCreatedAtAndUpdatedAt", func(t *testing.T) { 779 e := testutils.CreateTestClient(t, ts.URL) 780 781 obj := e.POST("/files/"). 782 WithQuery("Name", "TestUploadWithCreatedAtAndUpdatedAt"). 783 WithQuery("Type", "file"). 784 WithQuery("CreatedAt", "2016-09-18T10:24:53Z"). 785 WithQuery("UpdatedAt", "2020-05-12T12:25:00Z"). 786 WithHeader("Content-Type", "text/plain"). 787 WithHeader("Authorization", "Bearer "+token). 788 WithHeader("Content-MD5", "rL0Y20zC+Fzt72VPzMSk2A=="). 789 WithBytes([]byte("foo")). 790 Expect().Status(201). 791 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 792 Object() 793 794 attrs := obj.Path("$.data.attributes").Object() 795 attrs.ValueEqual("created_at", "2016-09-18T10:24:53Z") 796 attrs.ValueEqual("updated_at", "2020-05-12T12:25:00Z") 797 }) 798 799 t.Run("UploadWithCreatedAtAndUpdatedAtAndDateHeader", func(t *testing.T) { 800 e := testutils.CreateTestClient(t, ts.URL) 801 802 obj := e.POST("/files/"). 803 WithQuery("Name", "TestUploadWithCreatedAtAndUpdatedAtAndDateHeader"). 804 WithQuery("Type", "file"). 805 WithQuery("CreatedAt", "2016-09-18T10:24:53Z"). 806 WithQuery("UpdatedAt", "2020-05-12T12:25:00Z"). 807 WithHeader("Content-Type", "text/plain"). 808 WithHeader("Authorization", "Bearer "+token). 809 WithHeader("Date", "Mon, 19 Sep 2016 12:38:04 GMT"). 810 WithHeader("Content-MD5", "rL0Y20zC+Fzt72VPzMSk2A=="). 811 WithBytes([]byte("foo")). 812 Expect().Status(201). 813 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 814 Object() 815 816 attrs := obj.Path("$.data.attributes").Object() 817 attrs.ValueEqual("created_at", "2016-09-18T10:24:53Z") 818 attrs.ValueEqual("updated_at", "2020-05-12T12:25:00Z") 819 }) 820 821 t.Run("UploadWithMetadata", func(t *testing.T) { 822 e := testutils.CreateTestClient(t, ts.URL) 823 824 obj := e.POST("/files/upload/metadata"). 825 WithHeader("Content-Type", "application/json"). 826 WithHeader("Authorization", "Bearer "+token). 827 WithBytes([]byte(`{ 828 "data": { 829 "type": "io.cozy.files.metadata", 830 "attributes": { 831 "category": "report", 832 "subCategory": "theft", 833 "datetime": "2017-04-22T01:00:00-05:00", 834 "label": "foobar" 835 } 836 } 837 }`)). 838 Expect().Status(201). 839 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 840 Object() 841 842 data := obj.Value("data").Object() 843 data.ValueEqual("type", consts.FilesMetadata) 844 845 secret := data.Value("id").String().NotEmpty().Raw() 846 847 attrs := data.Value("attributes").Object() 848 attrs.ValueEqual("category", "report") 849 attrs.ValueEqual("subCategory", "theft") 850 attrs.ValueEqual("label", "foobar") 851 attrs.ValueEqual("datetime", "2017-04-22T01:00:00-05:00") 852 853 obj = e.POST("/files/"). 854 WithQuery("Name", "withmetadataid"). 855 WithQuery("Type", "file"). 856 WithQuery("MetadataID", secret). 857 WithHeader("Content-Type", "text/plain"). 858 WithHeader("Content-MD5", "rL0Y20zC+Fzt72VPzMSk2A=="). 859 WithHeader("Authorization", "Bearer "+token). 860 WithBytes([]byte("foo")). 861 Expect().Status(201). 862 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 863 Object() 864 865 meta := obj.Path("$.data.attributes.metadata").Object() 866 meta.ValueEqual("category", "report") 867 meta.ValueEqual("subCategory", "theft") 868 meta.ValueEqual("label", "foobar") 869 meta.ValueEqual("datetime", "2017-04-22T01:00:00-05:00") 870 }) 871 872 t.Run("UploadWithSourceAccount", func(t *testing.T) { 873 e := testutils.CreateTestClient(t, ts.URL) 874 875 account := "0c5a0a1e-8eb1-11e9-93f3-934f3a2c181d" 876 identifier := "11f68e48" 877 878 obj := e.POST("/files/"). 879 WithQuery("Name", "with-sourceAccount"). 880 WithQuery("Type", "file"). 881 WithQuery("SourceAccount", account). 882 WithQuery("SourceAccountIdentifier", identifier). 883 WithHeader("Content-Type", "text/plain"). 884 WithHeader("Content-MD5", "rL0Y20zC+Fzt72VPzMSk2A=="). 885 WithHeader("Authorization", "Bearer "+token). 886 WithBytes([]byte("foo")). 887 Expect().Status(201). 888 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 889 Object() 890 891 fcm := obj.Path("$.data.attributes.cozyMetadata").Object() 892 fcm.ValueEqual("sourceAccount", account) 893 fcm.ValueEqual("sourceAccountIdentifier", identifier) 894 }) 895 896 t.Run("CopyFile", func(t *testing.T) { 897 e := testutils.CreateTestClient(t, ts.URL) 898 899 e.POST("/files/"). 900 WithQuery("Name", "copyFileDir"). 901 WithQuery("Type", "directory"). 902 WithHeader("Authorization", "Bearer "+token). 903 Expect().Status(201) 904 905 fileName := "bar" 906 fileExt := ".txt" 907 fileContent := "file content" 908 909 // 1. Upload file and get its id 910 obj := e.POST("/files/"). 911 WithQuery("Name", fileName+fileExt). 912 WithQuery("Type", "file"). 913 WithHeader("Content-Type", "text/plain"). 914 WithHeader("Authorization", "Bearer "+token). 915 WithBytes([]byte(fileContent)). 916 Expect().Status(201). 917 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 918 Object() 919 920 fileID = obj.Path("$.data.id").String().NotEmpty().Raw() 921 file1Attrs := obj.Path("$.data.attributes").Object() 922 923 // 2. Send file copy request 924 obj = e.POST("/files/"+fileID+"/copy"). 925 WithHeader("Authorization", "Bearer "+token). 926 Expect().Status(201). 927 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 928 Object() 929 930 copyID := obj.Path("$.data.id").String().NotEmpty().NotEqual(fileID).Raw() 931 932 // 3. Fetch copy metadata and compare with file 933 obj = e.GET("/files/"+copyID). 934 WithHeader("Authorization", "Bearer "+token). 935 Expect().Status(200). 936 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 937 Object() 938 939 obj.Path("$.data.relationships").Object().NotContainsKey("old_versions") 940 941 attrs := obj.Path("$.data.attributes").Object() 942 attrs.Value("created_at").String().NotEmpty(). 943 NotEqual(file1Attrs.Value("created_at").String().Raw()). 944 DateTime(time.RFC3339) 945 attrs.ValueEqual("dir_id", file1Attrs.Value("dir_id").String().Raw()) 946 attrs.ValueEqual("name", fileName+" (copy)"+fileExt) 947 948 // 4. fetch copy and check its content 949 res := e.GET("/files/download/"+copyID). 950 WithHeader("Authorization", "Bearer "+token). 951 Expect().Status(200) 952 953 res.Header("Content-Disposition").HasPrefix("inline") 954 res.Header("Content-Disposition").Contains(`filename="` + fileName + "(copy)" + fileExt + `"`) 955 res.Header("Content-Type").Contains("text/plain") 956 res.Header("Etag").NotEmpty() 957 res.Header("Content-Length").Equal(strconv.Itoa(len(fileContent))) 958 res.Body().Equal(fileContent) 959 960 // 5. Send file copy request specifying copy name and parent id 961 destDirID := e.POST("/files/"). 962 WithQuery("Name", "destDir"). 963 WithQuery("Type", "directory"). 964 WithHeader("Authorization", "Bearer "+token). 965 Expect().Status(201). 966 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 967 Object().Path("$.data.id").String().NotEmpty().Raw() 968 969 copyName := "My-file-copy" 970 971 obj = e.POST("/files/"+fileID+"/copy"). 972 WithQuery("Name", copyName). 973 WithQuery("DirID", destDirID). 974 WithHeader("Authorization", "Bearer "+token). 975 Expect().Status(201). 976 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 977 Object() 978 979 copyID = obj.Path("$.data.id").String().NotEqual(fileID).Raw() 980 981 // 6. Fetch copy metadata and compare with file 982 obj = e.GET("/files/"+copyID). 983 WithHeader("Authorization", "Bearer "+token). 984 Expect().Status(200). 985 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 986 Object() 987 988 attrs = obj.Path("$.data.attributes").Object() 989 attrs.ValueEqual("dir_id", destDirID) 990 attrs.ValueEqual("name", copyName) 991 }) 992 993 t.Run("ModifyMetadataByPath", func(t *testing.T) { 994 e := testutils.CreateTestClient(t, ts.URL) 995 996 fileID = e.POST("/files/"). 997 WithQuery("Name", "file-move-me-by-path"). 998 WithQuery("Type", "file"). 999 WithHeader("Content-Type", "text/plain"). 1000 WithHeader("Authorization", "Bearer "+token). 1001 WithHeader("Content-MD5", "rL0Y20zC+Fzt72VPzMSk2A=="). 1002 WithBytes([]byte("foo")). 1003 Expect().Status(201). 1004 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 1005 Object(). 1006 Path("$.data.id").String().NotEmpty().Raw() 1007 1008 dirID := e.POST("/files/"). 1009 WithQuery("Name", "move-by-path"). 1010 WithQuery("Type", "directory"). 1011 WithHeader("Authorization", "Bearer "+token). 1012 Expect().Status(201). 1013 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 1014 Object().Path("$.data.id").String().NotEmpty().Raw() 1015 1016 obj := e.PATCH("/files/metadata"). 1017 WithQuery("Path", "/file-move-me-by-path"). 1018 WithHeader("Content-Type", "application/json"). 1019 WithHeader("Authorization", "Bearer "+token). 1020 WithBytes([]byte(`{ 1021 "data": { 1022 "type": "file", 1023 "id": "` + fileID + `", 1024 "attributes": { 1025 "tags": ["bar", "bar", "baz"], 1026 "name": "moved", 1027 "dir_id": "` + dirID + `", 1028 "executable": true, 1029 "cozyMetadata": { 1030 "favorite": true 1031 } 1032 } 1033 } 1034 }`)). 1035 Expect().Status(200). 1036 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 1037 Object() 1038 1039 attrs := obj.Path("$.data.attributes").Object() 1040 attrs.ValueEqual("mime", "text/plain") 1041 attrs.ValueEqual("name", "moved") 1042 attrs.ValueEqual("tags", []string{"bar", "baz"}) 1043 attrs.ValueEqual("class", "text") 1044 attrs.ValueEqual("md5sum", "rL0Y20zC+Fzt72VPzMSk2A==") 1045 attrs.ValueEqual("executable", true) 1046 attrs.ValueEqual("size", "3") 1047 attrs.Value("cozyMetadata").Object().ValueEqual("favorite", true) 1048 }) 1049 1050 t.Run("ModifyMetadataFileMove", func(t *testing.T) { 1051 e := testutils.CreateTestClient(t, ts.URL) 1052 1053 fileID = e.POST("/files/"). 1054 WithQuery("Name", "filemoveme"). 1055 WithQuery("Type", "file"). 1056 WithQuery("Tags", "foo,bar"). 1057 WithHeader("Content-Type", "text/plain"). 1058 WithHeader("Authorization", "Bearer "+token). 1059 WithHeader("Content-MD5", "rL0Y20zC+Fzt72VPzMSk2A=="). 1060 WithBytes([]byte("foo")). 1061 Expect().Status(201). 1062 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 1063 Object(). 1064 Path("$.data.id").String().NotEmpty().Raw() 1065 1066 dirID := e.POST("/files/"). 1067 WithQuery("Name", "movemeinme"). 1068 WithQuery("Type", "directory"). 1069 WithHeader("Authorization", "Bearer "+token). 1070 Expect().Status(201). 1071 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 1072 Object().Path("$.data.id").String().NotEmpty().Raw() 1073 1074 obj := e.PATCH("/files/"+fileID). 1075 WithQuery("Path", "/file-move-me-by-path"). 1076 WithHeader("Content-Type", "application/json"). 1077 WithHeader("Authorization", "Bearer "+token). 1078 WithBytes([]byte(`{ 1079 "data": { 1080 "type": "file", 1081 "id": "` + fileID + `", 1082 "attributes": { 1083 "tags": ["bar", "bar", "baz"], 1084 "name": "moved", 1085 "dir_id": "` + dirID + `", 1086 "executable": true 1087 } 1088 } 1089 }`)). 1090 Expect().Status(200). 1091 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 1092 Object() 1093 1094 attrs := obj.Path("$.data.attributes").Object() 1095 attrs.ValueEqual("mime", "text/plain") 1096 attrs.ValueEqual("name", "moved") 1097 attrs.ValueEqual("tags", []string{"bar", "baz"}) 1098 attrs.ValueEqual("class", "text") 1099 attrs.ValueEqual("md5sum", "rL0Y20zC+Fzt72VPzMSk2A==") 1100 attrs.ValueEqual("executable", true) 1101 attrs.ValueEqual("size", "3") 1102 }) 1103 1104 t.Run("ModifyMetadataFileConflict", func(t *testing.T) { 1105 e := testutils.CreateTestClient(t, ts.URL) 1106 1107 fileID = e.POST("/files/"). 1108 WithQuery("Name", "fmodme1"). 1109 WithQuery("Type", "file"). 1110 WithQuery("Tags", "foo,bar"). 1111 WithHeader("Content-Type", "text/plain"). 1112 WithHeader("Authorization", "Bearer "+token). 1113 WithHeader("Content-MD5", "rL0Y20zC+Fzt72VPzMSk2A=="). 1114 WithBytes([]byte("foo")). 1115 Expect().Status(201). 1116 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 1117 Object(). 1118 Path("$.data.id").String().NotEmpty().Raw() 1119 1120 e.POST("/files/"). 1121 WithQuery("Name", "fmodme2"). 1122 WithQuery("Type", "file"). 1123 WithQuery("Tags", "foo,bar"). 1124 WithHeader("Content-Type", "text/plain"). 1125 WithHeader("Authorization", "Bearer "+token). 1126 WithHeader("Content-MD5", "rL0Y20zC+Fzt72VPzMSk2A=="). 1127 WithBytes([]byte("foo")). 1128 Expect().Status(201). 1129 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 1130 Object(). 1131 Path("$.data.id").String().NotEmpty().Raw() 1132 1133 // Try to rename fmodme1 into the existing fmodme2 1134 e.PATCH("/files/"+fileID). 1135 WithQuery("Path", "/file-move-me-by-path"). 1136 WithHeader("Content-Type", "application/json"). 1137 WithHeader("Authorization", "Bearer "+token). 1138 WithBytes([]byte(`{ 1139 "data": { 1140 "type": "file", 1141 "id": "` + fileID + `", 1142 "attributes": { 1143 "name": "fmodme2" 1144 } 1145 } 1146 }`)). 1147 Expect().Status(409) 1148 }) 1149 1150 t.Run("ModifyMetadataDirMove", func(t *testing.T) { 1151 e := testutils.CreateTestClient(t, ts.URL) 1152 1153 // Create dir "/dirmodme" 1154 dir1ID := e.POST("/files/"). 1155 WithQuery("Name", "dirmodme"). 1156 WithQuery("Type", "directory"). 1157 WithQuery("Tags", "foo,bar,bar"). 1158 WithHeader("Authorization", "Bearer "+token). 1159 Expect().Status(201). 1160 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 1161 Object().Path("$.data.id").String().NotEmpty().Raw() 1162 1163 // Create dir "/dirmodme/child1" 1164 e.POST("/files/"+dir1ID). 1165 WithQuery("Name", "child1"). 1166 WithQuery("Type", "directory"). 1167 WithQuery("Tags", "foo,bar,bar"). 1168 WithHeader("Authorization", "Bearer "+token). 1169 Expect().Status(201). 1170 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 1171 Object().Path("$.data.id").String().NotEmpty().Raw() 1172 1173 // Create dir "/dirmodme/child2" 1174 e.POST("/files/"+dir1ID). 1175 WithQuery("Name", "child2"). 1176 WithQuery("Type", "directory"). 1177 WithQuery("Tags", "foo,bar,bar"). 1178 WithHeader("Authorization", "Bearer "+token). 1179 Expect().Status(201). 1180 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 1181 Object().Path("$.data.id").String().NotEmpty().Raw() 1182 1183 // Create dir "/dirmodmemoveinme" 1184 dir2ID := e.POST("/files/"). 1185 WithQuery("Name", "dirmodmemoveinme"). 1186 WithQuery("Type", "directory"). 1187 WithQuery("Tags", "foo,bar,bar"). 1188 WithHeader("Authorization", "Bearer "+token). 1189 Expect().Status(201). 1190 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 1191 Object().Path("$.data.id").String().NotEmpty().Raw() 1192 1193 // Move the folder "/dirmodme" into "/dirmodmemoveinme" 1194 e.PATCH("/files/"+dir1ID). 1195 WithHeader("Content-Type", "application/json"). 1196 WithHeader("Authorization", "Bearer "+token). 1197 WithBytes([]byte(`{ 1198 "data": { 1199 "type": "directory", 1200 "id": "` + dir1ID + `", 1201 "attributes": { 1202 "tags": ["bar", "baz"], 1203 "name": "renamed", 1204 "dir_id": "` + dir2ID + `" 1205 } 1206 } 1207 }`)). 1208 Expect().Status(200) 1209 1210 storage := testInstance.VFS() 1211 exists, err := vfs.DirExists(storage, "/dirmodmemoveinme/renamed") 1212 assert.NoError(t, err) 1213 assert.True(t, exists) 1214 1215 // Try to move the folder "/dirmodmemoveinme" into it sub folder "/dirmodmemoveinme/dirmodme" 1216 e.PATCH("/files/"+dir2ID). 1217 WithHeader("Content-Type", "application/json"). 1218 WithHeader("Authorization", "Bearer "+token). 1219 WithBytes([]byte(`{ 1220 "data": { 1221 "type": "directory", 1222 "id": "` + dir2ID + `", 1223 "attributes": { 1224 "tags": ["bar", "baz"], 1225 "name": "rename", 1226 "dir_id": "` + dir1ID + `" 1227 } 1228 } 1229 }`)). 1230 Expect().Status(412) 1231 1232 // Try to move the folder "/dirmodme" into itself 1233 e.PATCH("/files/"+dir1ID). 1234 WithHeader("Content-Type", "application/json"). 1235 WithHeader("Authorization", "Bearer "+token). 1236 WithBytes([]byte(`{ 1237 "data": { 1238 "type": "directory", 1239 "id": "` + dir1ID + `", 1240 "attributes": { 1241 "tags": ["bar", "baz"], 1242 "name": "rename", 1243 "dir_id": "` + dir1ID + `" 1244 } 1245 } 1246 }`)). 1247 Expect().Status(412) 1248 }) 1249 1250 t.Run("ModifyMetadataDirMoveWithRel", func(t *testing.T) { 1251 e := testutils.CreateTestClient(t, ts.URL) 1252 1253 // Create dir "/dirmodmewithrel" 1254 dir1ID := e.POST("/files/"). 1255 WithQuery("Name", "dirmodmewithrel"). 1256 WithQuery("Type", "directory"). 1257 WithQuery("Tags", "foo,bar,bar"). 1258 WithHeader("Authorization", "Bearer "+token). 1259 Expect().Status(201). 1260 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 1261 Object().Path("$.data.id").String().NotEmpty().Raw() 1262 1263 // Create dir "/dirmodmewithrel/child1" 1264 e.POST("/files/"+dir1ID). 1265 WithQuery("Name", "child1"). 1266 WithQuery("Type", "directory"). 1267 WithQuery("Tags", "foo,bar,bar"). 1268 WithHeader("Authorization", "Bearer "+token). 1269 Expect().Status(201). 1270 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 1271 Object().Path("$.data.id").String().NotEmpty().Raw() 1272 1273 // Create dir "/dirmodmewithrel/child2" 1274 e.POST("/files/"+dir1ID). 1275 WithQuery("Name", "child2"). 1276 WithQuery("Type", "directory"). 1277 WithQuery("Tags", "foo,bar,bar"). 1278 WithHeader("Authorization", "Bearer "+token). 1279 Expect().Status(201). 1280 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 1281 Object().Path("$.data.id").String().NotEmpty().Raw() 1282 1283 // Create dir "/dirmodmemoveinmewithrel" 1284 dir2ID := e.POST("/files/"). 1285 WithQuery("Name", "dirmodmemoveinmewithrel"). 1286 WithQuery("Type", "directory"). 1287 WithQuery("Tags", "foo,bar,bar"). 1288 WithHeader("Authorization", "Bearer "+token). 1289 Expect().Status(201). 1290 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 1291 Object().Path("$.data.id").String().NotEmpty().Raw() 1292 1293 // Move the folder "/dirmodme" into "/dirmodmemoveinme" 1294 e.PATCH("/files/"+dir1ID). 1295 WithHeader("Content-Type", "application/json"). 1296 WithHeader("Authorization", "Bearer "+token). 1297 WithBytes([]byte(`{ 1298 "data": { 1299 "type": "directory", 1300 "id": "` + dir1ID + `", 1301 "attributes": { 1302 "type": "io.cozy.files", 1303 "dir_id": "` + dir2ID + `" 1304 } 1305 } 1306 }`)). 1307 Expect().Status(200) 1308 1309 storage := testInstance.VFS() 1310 exists, err := vfs.DirExists(storage, "/dirmodmemoveinmewithrel/dirmodmewithrel") 1311 assert.NoError(t, err) 1312 assert.True(t, exists) 1313 }) 1314 1315 t.Run("ModifyMetadataDirMoveConflict", func(t *testing.T) { 1316 e := testutils.CreateTestClient(t, ts.URL) 1317 1318 // Create dir "/conflictmodme1" 1319 e.POST("/files/"). 1320 WithQuery("Name", "conflictmodme1"). 1321 WithQuery("Type", "directory"). 1322 WithQuery("Tags", "foo,bar,bar"). 1323 WithHeader("Authorization", "Bearer "+token). 1324 Expect().Status(201) 1325 1326 // Create dir "/conflictmodme2" 1327 dir2ID := e.POST("/files/"). 1328 WithQuery("Name", "conflictmodme2"). 1329 WithQuery("Type", "directory"). 1330 WithQuery("Tags", "foo,bar,bar"). 1331 WithHeader("Authorization", "Bearer "+token). 1332 Expect().Status(201). 1333 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 1334 Object().Path("$.data.id").String().NotEmpty().Raw() 1335 1336 // Try to rename "/confictmodme2" into the already taken name "/confilctmodme1" 1337 e.PATCH("/files/"+dir2ID). 1338 WithHeader("Content-Type", "application/json"). 1339 WithHeader("Authorization", "Bearer "+token). 1340 WithBytes([]byte(`{ 1341 "data": { 1342 "type": "directory", 1343 "id": "` + dir2ID + `", 1344 "attributes": { 1345 "tags": ["bar", "baz"], 1346 "name": "conflictmodme1" 1347 } 1348 } 1349 }`)). 1350 Expect().Status(409) 1351 }) 1352 1353 t.Run("ModifyContentBadRev", func(t *testing.T) { 1354 e := testutils.CreateTestClient(t, ts.URL) 1355 1356 obj := e.POST("/files/"). 1357 WithQuery("Type", "file"). 1358 WithQuery("Name", "modbadrev"). 1359 WithQuery("Executable", true). 1360 WithHeader("Content-Type", "text/plain"). 1361 WithHeader("Authorization", "Bearer "+token). 1362 WithBytes([]byte("foo")). 1363 Expect().Status(201). 1364 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 1365 Object() 1366 1367 fileID = obj.Path("$.data.id").String().NotEmpty().Raw() 1368 fileRev := obj.Path("$.data.meta.rev").String().NotEmpty().Raw() 1369 1370 e.PUT("/files/"+fileID). 1371 WithHeader("Authorization", "Bearer "+token). 1372 WithHeader("If-Match", "badrev"). // invalid 1373 WithBytes([]byte("newcontent :)")). 1374 Expect().Status(412) 1375 1376 e.PUT("/files/"+fileID). 1377 WithHeader("Authorization", "Bearer "+token). 1378 WithHeader("If-Match", fileRev). 1379 WithBytes([]byte("newcontent :)")). 1380 Expect().Status(200) 1381 }) 1382 1383 t.Run("ModifyContentSuccess", func(t *testing.T) { 1384 e := testutils.CreateTestClient(t, ts.URL) 1385 storage := testInstance.VFS() 1386 1387 obj := e.POST("/files/"). 1388 WithQuery("Type", "file"). 1389 WithQuery("Name", "willbemodified"). 1390 WithQuery("Executable", true). 1391 WithHeader("Content-Type", "text/plain"). 1392 WithHeader("Authorization", "Bearer "+token). 1393 WithBytes([]byte("foo")). 1394 Expect().Status(201). 1395 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 1396 Object() 1397 1398 data1 := obj.Value("data").Object() 1399 file1ID := data1.Value("id").String().NotEmpty().Raw() 1400 attrs1 := data1.Value("attributes").Object() 1401 uploadedAt1 := attrs1.Path("$.cozyMetadata.uploadedAt").String().NotEmpty().Raw() 1402 1403 buf, err := readFile(storage, "/willbemodified") 1404 assert.NoError(t, err) 1405 assert.Equal(t, "foo", string(buf)) 1406 1407 fileInfo, err := storage.FileByPath("/willbemodified") 1408 assert.NoError(t, err) 1409 assert.Equal(t, fileInfo.Mode().String(), "-rwxr-xr-x") 1410 1411 newContent := "newcontent :)" 1412 1413 // Upload a new content to the file creating a new version 1414 obj = e.PUT("/files/"+file1ID). 1415 WithQuery("Executable", false). 1416 WithHeader("Content-Type", "audio/mp3"). 1417 WithHeader("Authorization", "Bearer "+token). 1418 WithBytes([]byte(newContent)). 1419 Expect().Status(200). 1420 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 1421 Object() 1422 1423 data := obj.Value("data").Object() 1424 data.ValueEqual("id", data1.Value("id").Raw()) 1425 data.Path("$.links.self").Equal(data1.Path("$.links.self").Raw()) 1426 1427 meta := data.Value("meta").Object() 1428 meta.Value("rev").NotEqual(data1.Path("$.meta.rev").String().Raw()) 1429 1430 attrs := data.Value("attributes").Object() 1431 attrs.ValueEqual("name", attrs1.Value("name").String().Raw()) 1432 attrs.ValueEqual("created_at", attrs1.Value("created_at").String().Raw()) 1433 attrs.Value("updated_at").NotEqual(attrs1.Value("updated_at").String().Raw()) 1434 attrs.Value("size").NotEqual(attrs1.Value("size").String().Raw()) 1435 attrs.ValueEqual("size", strconv.Itoa(len(newContent))) 1436 attrs.Value("md5sum").NotEqual(attrs1.Value("md5sum").String().Raw()) 1437 attrs.Value("class").NotEqual(attrs1.Value("class").String().Raw()) 1438 attrs.Value("mime").NotEqual(attrs1.Value("mime").String().Raw()) 1439 attrs.Value("executable").NotEqual(attrs1.Value("executable").Boolean().Raw()) 1440 attrs.ValueEqual("class", "audio") 1441 attrs.ValueEqual("mime", "audio/mp3") 1442 attrs.ValueEqual("executable", false) 1443 attrs.Path("$.cozyMetadata.uploadedAt").NotEqual(uploadedAt1) 1444 1445 buf, err = readFile(storage, "/willbemodified") 1446 assert.NoError(t, err) 1447 assert.Equal(t, newContent, string(buf)) 1448 fileInfo, err = storage.FileByPath("/willbemodified") 1449 assert.NoError(t, err) 1450 assert.Equal(t, fileInfo.Mode().String(), "-rw-r--r--") 1451 1452 e.PUT("/files/"+fileID). 1453 WithHeader("Date", "Mon, 02 Jan 2006 15:04:05 MST"). 1454 WithHeader("Content-Type", "what/ever"). 1455 WithHeader("Authorization", "Bearer "+token). 1456 WithBytes([]byte("")). 1457 Expect().Status(200). 1458 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 1459 Object(). 1460 Path("$.data.attributes.updated_at").Equal("2006-01-02T15:04:05Z") 1461 1462 newContent = "encryptedcontent" 1463 1464 e.PUT("/files/"+fileID). 1465 WithQuery("Encrypted", true). 1466 WithHeader("Date", "Mon, 02 Jan 2006 15:04:05 MST"). 1467 WithHeader("Content-Type", "audio/mp3"). 1468 WithHeader("Authorization", "Bearer "+token). 1469 WithBytes([]byte(newContent)). 1470 Expect().Status(200). 1471 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 1472 Object(). 1473 Path("$.data.attributes.encrypted").Equal(true) 1474 }) 1475 1476 t.Run("ModifyContentWithSourceAccount", func(t *testing.T) { 1477 e := testutils.CreateTestClient(t, ts.URL) 1478 1479 fileID = e.POST("/files/"). 1480 WithQuery("Name", "old-file-to-migrate"). 1481 WithQuery("Type", "file"). 1482 WithHeader("Content-Type", "text/plain"). 1483 WithHeader("Content-MD5", "rL0Y20zC+Fzt72VPzMSk2A=="). 1484 WithHeader("Authorization", "Bearer "+token). 1485 WithBytes([]byte("foo")). 1486 Expect().Status(201). 1487 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 1488 Object(). 1489 Path("$.data.id").String().NotEmpty().Raw() 1490 1491 account := "0c5a0a1e-8eb1-11e9-93f3-934f3a2c181d" 1492 identifier := "11f68e48" 1493 newContent := "updated by a konnector to add the sourceAccount" 1494 1495 obj := e.PUT("/files/"+fileID). 1496 WithQuery("Name", "old-file-to-migrate"). 1497 WithQuery("SourceAccount", account). 1498 WithQuery("SourceAccountIdentifier", identifier). 1499 WithQuery("Type", "file"). 1500 WithHeader("Content-Type", "text/plain"). 1501 WithHeader("Authorization", "Bearer "+token). 1502 WithBytes([]byte(newContent)). 1503 Expect().Status(200). 1504 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 1505 Object() 1506 1507 fcm := obj.Path("$.data.attributes.cozyMetadata").Object() 1508 fcm.ValueEqual("sourceAccount", account) 1509 fcm.ValueEqual("sourceAccountIdentifier", identifier) 1510 }) 1511 1512 t.Run("ModifyContentWithCreatedAt", func(t *testing.T) { 1513 e := testutils.CreateTestClient(t, ts.URL) 1514 1515 obj := e.POST("/files/"). 1516 WithQuery("Name", "old-file-with-c"). 1517 WithQuery("Type", "file"). 1518 WithHeader("Content-Type", "text/plain"). 1519 WithHeader("Content-MD5", "rL0Y20zC+Fzt72VPzMSk2A=="). 1520 WithHeader("Authorization", "Bearer "+token). 1521 WithBytes([]byte("foo")). 1522 Expect().Status(201). 1523 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 1524 Object() 1525 1526 fileID = obj.Path("$.data.id").String().NotEmpty().Raw() 1527 createdAt := obj.Path("$.data.attributes.created_at").String().NotEmpty().Raw() 1528 updatedAt := obj.Path("$.data.attributes.updated_at").String().NotEmpty().Raw() 1529 1530 createdAt2 := "2017-11-16T13:37:01.345Z" 1531 newContent := "updated by a client with a new CreatedAt" 1532 1533 obj = e.PUT("/files/"+fileID). 1534 WithQuery("Name", "old-file-to-migrate"). 1535 WithQuery("CreatedAt", createdAt2). 1536 WithQuery("Type", "file"). 1537 WithHeader("Content-Type", "text/plain"). 1538 WithHeader("Authorization", "Bearer "+token). 1539 WithBytes([]byte(newContent)). 1540 Expect().Status(200). 1541 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 1542 Object() 1543 1544 obj.Path("$.data.attributes.created_at").Equal(createdAt) 1545 obj.Path("$.data.attributes.updated_at").NotEqual(updatedAt) 1546 }) 1547 1548 t.Run("ModifyContentWithUpdatedAt", func(t *testing.T) { 1549 e := testutils.CreateTestClient(t, ts.URL) 1550 1551 createdAt := "2017-11-16T13:37:01.345Z" 1552 1553 fileID = e.POST("/files/"). 1554 WithQuery("Name", "old-file-with-u"). 1555 WithQuery("Type", "file"). 1556 WithQuery("CreatedAt", createdAt). 1557 WithHeader("Content-Type", "text/plain"). 1558 WithHeader("Content-MD5", "rL0Y20zC+Fzt72VPzMSk2A=="). 1559 WithHeader("Authorization", "Bearer "+token). 1560 WithBytes([]byte("foo")). 1561 Expect().Status(201). 1562 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 1563 Object(). 1564 Path("$.data.id").String().NotEmpty().Raw() 1565 1566 updatedAt2 := "2017-12-16T13:37:01.345Z" 1567 newContent := "updated by a client with a new UpdatedAt" 1568 1569 obj := e.PUT("/files/"+fileID). 1570 WithQuery("UpdatedAt", updatedAt2). 1571 WithQuery("Type", "file"). 1572 WithHeader("Content-Type", "text/plain"). 1573 WithHeader("Authorization", "Bearer "+token). 1574 WithBytes([]byte(newContent)). 1575 Expect().Status(200). 1576 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 1577 Object() 1578 1579 obj.Path("$.data.attributes.created_at").Equal(createdAt) 1580 obj.Path("$.data.attributes.updated_at").Equal(updatedAt2) 1581 }) 1582 1583 t.Run("ModifyContentWithUpdatedAtAndCreatedAt", func(t *testing.T) { 1584 e := testutils.CreateTestClient(t, ts.URL) 1585 1586 createdAt := "2017-11-16T13:37:01.345Z" 1587 1588 fileID = e.POST("/files/"). 1589 WithQuery("Name", "old-file-with-u-and-c"). 1590 WithQuery("Type", "file"). 1591 WithQuery("CreatedAt", createdAt). 1592 WithHeader("Content-Type", "text/plain"). 1593 WithHeader("Content-MD5", "rL0Y20zC+Fzt72VPzMSk2A=="). 1594 WithHeader("Authorization", "Bearer "+token). 1595 WithBytes([]byte("foo")). 1596 Expect().Status(201). 1597 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 1598 Object(). 1599 Path("$.data.id").String().NotEmpty().Raw() 1600 1601 createdAt2 := "2017-10-16T13:37:01.345Z" 1602 updatedAt2 := "2017-12-16T13:37:01.345Z" 1603 newContent := "updated by a client with a CreatedAt older than UpdatedAt" 1604 1605 obj := e.PUT("/files/"+fileID). 1606 WithQuery("UpdatedAt", updatedAt2). 1607 WithQuery("CreateddAt", createdAt2). 1608 WithQuery("Type", "file"). 1609 WithHeader("Content-Type", "text/plain"). 1610 WithHeader("Authorization", "Bearer "+token). 1611 WithBytes([]byte(newContent)). 1612 Expect().Status(200). 1613 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 1614 Object() 1615 1616 obj.Path("$.data.attributes.created_at").Equal(createdAt) 1617 obj.Path("$.data.attributes.updated_at").Equal(updatedAt2) 1618 }) 1619 1620 t.Run("ModifyContentConcurrently", func(t *testing.T) { 1621 e := testutils.CreateTestClient(t, ts.URL) 1622 1623 fileID = e.POST("/files/"). 1624 WithQuery("Name", "willbemodifiedconcurrently"). 1625 WithQuery("Type", "file"). 1626 WithHeader("Content-Type", "text/plain"). 1627 WithHeader("Content-MD5", "rL0Y20zC+Fzt72VPzMSk2A=="). 1628 WithHeader("Authorization", "Bearer "+token). 1629 WithBytes([]byte("foo")). 1630 Expect().Status(201). 1631 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 1632 Object(). 1633 Path("$.data.id").String().NotEmpty().Raw() 1634 1635 var c int64 1636 1637 type resC struct { 1638 obj *httpexpect.Object 1639 idx int64 1640 } 1641 1642 errs := make(chan int) 1643 done := make(chan resC) 1644 1645 doModContent := func() { 1646 idx := atomic.AddInt64(&c, 1) 1647 1648 res := e.PUT("/files/"+fileID). 1649 WithQuery("Type", "file"). 1650 WithHeader("Content-Type", "text/plain"). 1651 WithHeader("Authorization", "Bearer "+token). 1652 WithBytes([]byte("newcontent " + strconv.FormatInt(idx, 10))). 1653 Expect() 1654 1655 rawRes := res.Raw() 1656 _ = rawRes.Body.Close() 1657 1658 if rawRes.StatusCode == 200 { 1659 done <- resC{ 1660 obj: res.JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}).Object(), 1661 idx: idx, 1662 } 1663 } else { 1664 errs <- rawRes.StatusCode 1665 } 1666 } 1667 1668 n := 100 1669 1670 for i := 0; i < n; i++ { 1671 go doModContent() 1672 } 1673 1674 var successes []resC 1675 for i := 0; i < n; i++ { 1676 select { 1677 case res := <-errs: 1678 assert.True(t, res == 409 || res == 503, "status code is %d and not 409 or 503", res) 1679 case res := <-done: 1680 successes = append(successes, res) 1681 } 1682 } 1683 1684 assert.True(t, len(successes) >= 1, "there is at least one success") 1685 1686 for i, res := range successes { 1687 res.obj.Path("$.data.meta.rev").String().HasPrefix(strconv.Itoa(i+2) + "-") 1688 } 1689 1690 storage := testInstance.VFS() 1691 buf, err := readFile(storage, "/willbemodifiedconcurrently") 1692 assert.NoError(t, err) 1693 1694 found := false 1695 for _, res := range successes { 1696 t.Logf("succ: %#v\n\n", res) 1697 if string(buf) == "newcontent "+strconv.FormatInt(res.idx, 10) { 1698 found = true 1699 break 1700 } 1701 } 1702 1703 assert.True(t, found) 1704 }) 1705 1706 t.Run("DownloadFileBadID", func(t *testing.T) { 1707 e := testutils.CreateTestClient(t, ts.URL) 1708 1709 e.GET("/files/download/badid"). 1710 WithHeader("Authorization", "Bearer "+token). 1711 Expect().Status(404) 1712 }) 1713 1714 t.Run("DownloadFileBadPath", func(t *testing.T) { 1715 e := testutils.CreateTestClient(t, ts.URL) 1716 1717 e.GET("/files/download/"). 1718 WithQuery("Path", "/i/do/not/exists"). 1719 WithHeader("Authorization", "Bearer "+token). 1720 Expect().Status(404) 1721 }) 1722 1723 t.Run("DownloadFileByIDSuccess", func(t *testing.T) { 1724 e := testutils.CreateTestClient(t, ts.URL) 1725 1726 fileID = e.POST("/files/"). 1727 WithQuery("Name", "downloadme1"). 1728 WithQuery("Type", "file"). 1729 WithHeader("Content-Type", "text/plain"). 1730 WithHeader("Content-MD5", "rL0Y20zC+Fzt72VPzMSk2A=="). 1731 WithHeader("Authorization", "Bearer "+token). 1732 WithBytes([]byte("foo")). 1733 Expect().Status(201). 1734 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 1735 Object(). 1736 Path("$.data.id").String().NotEmpty().Raw() 1737 1738 res := e.GET("/files/download/"+fileID). 1739 WithHeader("Authorization", "Bearer "+token). 1740 Expect().Status(200). 1741 ContentType("text/plain", "") 1742 1743 res.Header("Content-Disposition").HasPrefix("inline") 1744 res.Header("Content-Disposition").Contains(`filename="downloadme1"`) 1745 res.Header("Etag").NotEmpty() 1746 res.Header("Content-Length").Equal("3") 1747 res.Header("Accept-Ranges").Equal("bytes") 1748 1749 res.Body().Equal("foo") 1750 }) 1751 1752 t.Run("DownloadFileByPathSuccess", func(t *testing.T) { 1753 e := testutils.CreateTestClient(t, ts.URL) 1754 1755 e.POST("/files/"). 1756 WithQuery("Name", "downloadme2"). 1757 WithQuery("Type", "file"). 1758 WithHeader("Content-Type", "text/plain"). 1759 WithHeader("Content-MD5", "rL0Y20zC+Fzt72VPzMSk2A=="). 1760 WithHeader("Authorization", "Bearer "+token). 1761 WithBytes([]byte("foo")). 1762 Expect().Status(201). 1763 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 1764 Object(). 1765 Path("$.data.id").String().NotEmpty().Raw() 1766 1767 res := e.GET("/files/download"). 1768 WithQuery("Dl", "1"). 1769 WithQuery("Path", "/downloadme2"). 1770 WithHeader("Authorization", "Bearer "+token). 1771 Expect().Status(200). 1772 ContentType("text/plain", "") 1773 1774 res.Header("Content-Disposition").HasPrefix("attachment") 1775 res.Header("Content-Disposition").Contains(`filename="downloadme2"`) 1776 res.Header("Content-Length").Equal("3") 1777 res.Header("Accept-Ranges").Equal("bytes") 1778 1779 res.Body().Equal("foo") 1780 }) 1781 1782 t.Run("DownloadRangeSuccess", func(t *testing.T) { 1783 e := testutils.CreateTestClient(t, ts.URL) 1784 1785 e.POST("/files/"). 1786 WithQuery("Name", "downloadmebyrange"). 1787 WithQuery("Type", "file"). 1788 WithHeader("Content-Type", "text/plain"). 1789 WithHeader("Content-MD5", "UmfjCVWct/albVkURcJJfg=="). 1790 WithHeader("Authorization", "Bearer "+token). 1791 WithBytes([]byte("foo,bar")). 1792 Expect().Status(201) 1793 1794 e.GET("/files/download"). 1795 WithQuery("Path", "/downloadmebyrange"). 1796 WithQuery("", "/downloadmebyrange"). 1797 WithHeader("Range", "nimp"). 1798 WithHeader("Authorization", "Bearer "+token). 1799 Expect().Status(416) 1800 1801 e.GET("/files/download"). 1802 WithQuery("Path", "/downloadmebyrange"). 1803 WithQuery("", "/downloadmebyrange"). 1804 WithHeader("Range", "bytes=0-2"). 1805 WithHeader("Authorization", "Bearer "+token). 1806 Expect().Status(206). 1807 Body().Equal("foo") 1808 1809 e.GET("/files/download"). 1810 WithQuery("Path", "/downloadmebyrange"). 1811 WithQuery("", "/downloadmebyrange"). 1812 WithHeader("Range", "bytes=4-"). 1813 WithHeader("Authorization", "Bearer "+token). 1814 Expect().Status(206). 1815 Body().Equal("bar") 1816 }) 1817 1818 t.Run("GetFileMetadataFromPath", func(t *testing.T) { 1819 e := testutils.CreateTestClient(t, ts.URL) 1820 1821 e.GET("/files/metadata"). 1822 WithQuery("Path", "/nooooop"). 1823 WithHeader("Authorization", "Bearer "+token). 1824 Expect().Status(404) 1825 1826 e.POST("/files/"). 1827 WithQuery("Name", "getmetadata"). 1828 WithQuery("Type", "file"). 1829 WithHeader("Content-Type", "text/plain"). 1830 WithHeader("Content-MD5", "UmfjCVWct/albVkURcJJfg=="). 1831 WithHeader("Authorization", "Bearer "+token). 1832 WithBytes([]byte("foo,bar")). 1833 Expect().Status(201) 1834 1835 e.GET("/files/metadata"). 1836 WithQuery("Path", "/getmetadata"). 1837 WithHeader("Authorization", "Bearer "+token). 1838 Expect().Status(200) 1839 }) 1840 1841 t.Run("GetDirMetadataFromPath", func(t *testing.T) { 1842 e := testutils.CreateTestClient(t, ts.URL) 1843 1844 e.POST("/files/"). 1845 WithQuery("Name", "getdirmeta"). 1846 WithQuery("Type", "directory"). 1847 WithHeader("Authorization", "Bearer "+token). 1848 Expect().Status(201) 1849 1850 e.GET("/files/metadata"). 1851 WithQuery("Path", "/getdirmeta"). 1852 WithHeader("Authorization", "Bearer "+token). 1853 Expect().Status(200) 1854 }) 1855 1856 t.Run("GetFileMetadataFromID", func(t *testing.T) { 1857 e := testutils.CreateTestClient(t, ts.URL) 1858 1859 e.GET("/files/qsdqsd"). 1860 WithHeader("Authorization", "Bearer "+token). 1861 Expect().Status(404) 1862 1863 fileID = e.POST("/files/"). 1864 WithQuery("Name", "getmetadatafromid"). 1865 WithQuery("Type", "file"). 1866 WithHeader("Content-Type", "text/plain"). 1867 WithHeader("Authorization", "Bearer "+token). 1868 WithBytes([]byte("baz")). 1869 Expect().Status(201). 1870 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 1871 Object().Path("$.data.id").String().NotEmpty().Raw() 1872 1873 e.GET("/files/"+fileID). 1874 WithHeader("Authorization", "Bearer "+token). 1875 Expect().Status(200) 1876 }) 1877 1878 t.Run("GetDirMetadataFromID", func(t *testing.T) { 1879 e := testutils.CreateTestClient(t, ts.URL) 1880 1881 dirID := e.POST("/files/"). 1882 WithQuery("Name", "getdirmetafromid"). 1883 WithQuery("Type", "directory"). 1884 WithHeader("Authorization", "Bearer "+token). 1885 Expect().Status(201). 1886 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 1887 Object().Path("$.data.id").String().NotEmpty().Raw() 1888 1889 fileID = e.POST("/files/"+dirID). 1890 WithQuery("Name", "firstfile"). 1891 WithQuery("Type", "file"). 1892 WithHeader("Content-Type", "text/plain"). 1893 WithHeader("Authorization", "Bearer "+token). 1894 WithBytes([]byte("baz")). 1895 Expect().Status(201). 1896 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 1897 Object().Path("$.data.id").String().NotEmpty().Raw() 1898 1899 e.GET("/files/"+fileID). 1900 WithHeader("Authorization", "Bearer "+token). 1901 Expect().Status(200) 1902 }) 1903 1904 t.Run("Versions", func(t *testing.T) { 1905 e := testutils.CreateTestClient(t, ts.URL) 1906 1907 cfg := config.GetConfig() 1908 oldDelay := cfg.Fs.Versioning.MinDelayBetweenTwoVersions 1909 cfg.Fs.Versioning.MinDelayBetweenTwoVersions = 10 * time.Millisecond 1910 t.Cleanup(func() { cfg.Fs.Versioning.MinDelayBetweenTwoVersions = oldDelay }) 1911 1912 obj := e.POST("/files/"). 1913 WithQuery("Name", "versioned"). 1914 WithQuery("Type", "file"). 1915 WithHeader("Content-Type", "text/plain"). 1916 WithHeader("Authorization", "Bearer "+token). 1917 WithBytes([]byte("one")). 1918 Expect().Status(201). 1919 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 1920 Object() 1921 1922 fileID = obj.Path("$.data.id").String().NotEmpty().Raw() 1923 sum1 := obj.Path("$.data.attributes.md5sum").String().NotEmpty().Raw() 1924 1925 time.Sleep(20 * time.Millisecond) 1926 1927 obj = e.PUT("/files/"+fileID). 1928 WithHeader("Content-Type", "text/plain"). 1929 WithHeader("Authorization", "Bearer "+token). 1930 WithBytes([]byte("two")). 1931 Expect().Status(200). 1932 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 1933 Object() 1934 1935 sum2 := obj.Path("$.data.attributes.md5sum").String().NotEmpty().Raw() 1936 1937 time.Sleep(20 * time.Millisecond) 1938 1939 obj3 := e.PUT("/files/"+fileID). 1940 WithHeader("Content-Type", "text/plain"). 1941 WithHeader("Authorization", "Bearer "+token). 1942 WithBytes([]byte("three")). 1943 Expect().Status(200). 1944 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 1945 Object() 1946 1947 resObj := e.GET("/files/"+fileID). 1948 WithHeader("Authorization", "Bearer "+token). 1949 Expect().Status(200). 1950 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 1951 Object() 1952 1953 resObj.Path("$.data.attributes.md5sum").Equal(obj3.Path("$.data.attributes.md5sum").String().Raw()) 1954 1955 oldRefs := resObj.Path("$.data.relationships.old_versions.data").Array() 1956 oldRefs.Length().Equal(2) 1957 1958 first := oldRefs.Element(0).Object() 1959 first.ValueEqual("type", consts.FilesVersions) 1960 oneID := first.Value("id").String().NotEmpty().Raw() 1961 1962 second := oldRefs.Element(1).Object() 1963 second.ValueEqual("type", consts.FilesVersions) 1964 secondID := second.Value("id").String().NotEmpty().Raw() 1965 1966 included := resObj.Value("included").Array() 1967 included.Length().Equal(2) 1968 1969 vOne := included.Element(0).Object() 1970 vOne.ValueEqual("id", oneID) 1971 vOne.Path("$.attributes.md5sum").Equal(sum1) 1972 1973 vTwo := included.Element(1).Object() 1974 vTwo.ValueEqual("id", secondID) 1975 vTwo.Path("$.attributes.md5sum").Equal(sum2) 1976 }) 1977 1978 t.Run("PatchVersion", func(t *testing.T) { 1979 e := testutils.CreateTestClient(t, ts.URL) 1980 1981 // Create the file 1982 fileID = e.POST("/files/"). 1983 WithQuery("Name", "patch-version"). 1984 WithQuery("Type", "file"). 1985 WithHeader("Content-Type", "text/plain"). 1986 WithHeader("Authorization", "Bearer "+token). 1987 WithBytes([]byte("one")). 1988 Expect().Status(201). 1989 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 1990 Object().Path("$.data.id").String().NotEmpty().Raw() 1991 1992 // Upload a new version 1993 e.PUT("/files/"+fileID). 1994 WithHeader("Content-Type", "text/plain"). 1995 WithHeader("Authorization", "Bearer "+token). 1996 WithBytes([]byte("two")). 1997 Expect().Status(200). 1998 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 1999 Object() 2000 2001 // Get file informations 2002 obj := e.GET("/files/"+fileID). 2003 WithHeader("Authorization", "Bearer "+token). 2004 Expect().Status(200). 2005 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 2006 Object() 2007 2008 refs := obj.Path("$.data.relationships.old_versions.data").Array() 2009 refs.Length().Equal(1) 2010 2011 ref := refs.Element(0).Object() 2012 ref.ValueEqual("type", consts.FilesVersions) 2013 versionID := ref.Value("id").String().NotEmpty().Raw() 2014 2015 obj = e.PATCH("/files/"+versionID). 2016 WithHeader("Authorization", "Bearer "+token). 2017 WithHeader("Content-Type", "application/json"). 2018 WithBytes([]byte(`{ 2019 "data": { 2020 "type": "` + consts.FilesVersions + `", 2021 "id": "` + versionID + `", 2022 "attributes": { "tags": ["qux"] } 2023 } 2024 }`)). 2025 Expect().Status(200). 2026 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 2027 Object() 2028 2029 obj.Path("$.data.id").Equal(versionID) 2030 tags := obj.Path("$.data.attributes.tags").Array() 2031 tags.Length().Equal(1) 2032 tags.First().Equal("qux") 2033 }) 2034 2035 t.Run("DownloadVersion", func(t *testing.T) { 2036 e := testutils.CreateTestClient(t, ts.URL) 2037 2038 obj := e.POST("/files/"). 2039 WithQuery("Name", "downloadme-versioned"). 2040 WithQuery("Type", "file"). 2041 WithHeader("Content-Type", "text/plain"). 2042 WithHeader("Authorization", "Bearer "+token). 2043 WithBytes([]byte("one")). 2044 Expect().Status(201). 2045 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 2046 Object() 2047 2048 fileID = obj.Path("$.data.id").String().NotEmpty().Raw() 2049 firstRev := obj.Path("$.data.meta.rev").String().NotEmpty().Raw() 2050 2051 e.PUT("/files/"+fileID). 2052 WithHeader("Authorization", "Bearer "+token). 2053 WithHeader("Content-Type", "text/plain"). 2054 WithBytes([]byte(`two`)). 2055 Expect().Status(200). 2056 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 2057 Object() 2058 2059 res := e.GET("/files/download/"+fileID+"/"+firstRev). 2060 WithHeader("Authorization", "Bearer "+token). 2061 Expect().Status(200) 2062 2063 res.Header("Content-Disposition").HasPrefix("inline") 2064 res.Header("Content-Disposition").Contains(`filename="downloadme-versioned"`) 2065 res.Header("Content-Type").HasPrefix("text/plain") 2066 res.Body().Equal("one") 2067 }) 2068 2069 t.Run("FileCreateAndDownloadByVersionID", func(t *testing.T) { 2070 e := testutils.CreateTestClient(t, ts.URL) 2071 2072 obj := e.POST("/files/"). 2073 WithQuery("Name", "direct-downloadme-versioned"). 2074 WithQuery("Type", "file"). 2075 WithHeader("Content-Type", "text/plain"). 2076 WithHeader("Authorization", "Bearer "+token). 2077 WithBytes([]byte("one")). 2078 Expect().Status(201). 2079 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 2080 Object() 2081 2082 fileID = obj.Path("$.data.id").String().NotEmpty().Raw() 2083 firstRev := obj.Path("$.data.meta.rev").String().NotEmpty().Raw() 2084 2085 // Upload a new content to the file creating a new version 2086 e.PUT("/files/"+fileID). 2087 WithHeader("Authorization", "Bearer "+token). 2088 WithHeader("Content-Type", "text/plain"). 2089 WithBytes([]byte(`two`)). 2090 Expect().Status(200) 2091 2092 obj = e.POST("/files/downloads"). 2093 WithQuery("VersionId", fileID+"/"+firstRev). 2094 WithHeader("Authorization", "Bearer "+token). 2095 Expect().Status(200). 2096 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 2097 Object() 2098 2099 related := obj.Path("$.links.related").String().NotEmpty().Raw() 2100 2101 // Get display url 2102 e.GET(related). 2103 Expect().Status(200). 2104 Header("Content-Disposition").Equal(`inline; filename="direct-downloadme-versioned"`) 2105 }) 2106 2107 t.Run("RevertVersion", func(t *testing.T) { 2108 e := testutils.CreateTestClient(t, ts.URL) 2109 2110 // Create a file 2111 fileID = e.POST("/files/"). 2112 WithQuery("Name", "direct-downloadme-reverted"). 2113 WithQuery("Type", "file"). 2114 WithHeader("Content-Type", "text/plain"). 2115 WithHeader("Authorization", "Bearer "+token). 2116 WithBytes([]byte("one")). 2117 Expect().Status(201). 2118 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 2119 Object().Path("$.data.id").String().NotEmpty().Raw() 2120 2121 // Upload a new content to the file creating a new version 2122 e.PUT("/files/"+fileID). 2123 WithHeader("Authorization", "Bearer "+token). 2124 WithHeader("Content-Type", "text/plain"). 2125 WithBytes([]byte(`two`)). 2126 Expect().Status(200) 2127 2128 // Get the current file version id 2129 versionID := e.GET("/files/"+fileID). 2130 WithHeader("Authorization", "Bearer "+token). 2131 Expect().Status(200). 2132 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 2133 Object().Path("$.data.relationships.old_versions.data[0].id"). 2134 String().NotEmpty().Raw() 2135 2136 // Revert the last version 2137 e.POST("/files/revert/"+versionID). 2138 WithHeader("Authorization", "Bearer "+token). 2139 Expect().Status(200) 2140 2141 // Check that the file have the reverted content 2142 res := e.GET("/files/download/"+fileID). 2143 WithHeader("Authorization", "Bearer "+token). 2144 Expect().Status(200) 2145 2146 res.Header("Content-Disposition").HasPrefix("inline") 2147 res.Header("Content-Disposition").Contains(`filename="direct-downloadme-reverted"`) 2148 res.Header("Content-Type").HasPrefix("text/plain") 2149 res.Body().Equal("one") 2150 }) 2151 2152 t.Run("CleanOldVersion", func(t *testing.T) { 2153 e := testutils.CreateTestClient(t, ts.URL) 2154 2155 // Create a file 2156 fileID = e.POST("/files/"). 2157 WithQuery("Name", "downloadme-toclean"). 2158 WithQuery("Type", "file"). 2159 WithHeader("Content-Type", "text/plain"). 2160 WithHeader("Authorization", "Bearer "+token). 2161 WithBytes([]byte("one")). 2162 Expect().Status(201). 2163 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 2164 Object().Path("$.data.id").String().NotEmpty().Raw() 2165 2166 // Upload a new content to the file creating a new version 2167 e.PUT("/files/"+fileID). 2168 WithHeader("Authorization", "Bearer "+token). 2169 WithHeader("Content-Type", "text/plain"). 2170 WithBytes([]byte(`two`)). 2171 Expect().Status(200) 2172 2173 // Get the current file version id 2174 versionID := e.GET("/files/"+fileID). 2175 WithHeader("Authorization", "Bearer "+token). 2176 Expect().Status(200). 2177 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 2178 Object().Path("$.data.relationships.old_versions.data[0].id"). 2179 String().NotEmpty().Raw() 2180 2181 // Delete the last versionID. 2182 e.DELETE("/files/"+versionID). 2183 WithHeader("Authorization", "Bearer "+token). 2184 Expect().Status(204) 2185 2186 // Check that the last version have been deleted and is not downloadable 2187 e.GET("/files/download/"+versionID). 2188 WithHeader("Authorization", "Bearer "+token). 2189 Expect().Status(404) 2190 }) 2191 2192 t.Run("CopyVersion", func(t *testing.T) { 2193 e := testutils.CreateTestClient(t, ts.URL) 2194 2195 // Upload some file metadata 2196 metadataID := e.POST("/files/upload/metadata"). 2197 WithHeader("Content-Type", "application/json"). 2198 WithHeader("Authorization", "Bearer "+token). 2199 WithBytes([]byte(`{ 2200 "data": { 2201 "type": "io.cozy.files.metadata", 2202 "attributes": { 2203 "category": "report", 2204 "label": "foo" 2205 } 2206 } 2207 }`)). 2208 Expect().Status(201). 2209 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 2210 Object().Path("$.data.id").String().NotEmpty().Raw() 2211 2212 // Upload a file content linke to the previous metadata. 2213 fileID = e.POST("/files/"). 2214 WithQuery("Name", "version-to-be-copied"). 2215 WithQuery("Type", "file"). 2216 WithQuery("MetadataID", metadataID). 2217 WithHeader("Content-Type", "text/plain"). 2218 WithHeader("Authorization", "Bearer "+token). 2219 WithBytes([]byte("should-be-the-same-after-copy")). 2220 Expect().Status(201). 2221 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 2222 Object().Path("$.data.id").String().NotEmpty().Raw() 2223 2224 // Push some attributes the the last version 2225 obj := e.POST("/files/"+fileID+"/versions"). 2226 WithQuery("Tags", "qux"). 2227 WithHeader("Content-Type", "application/json"). 2228 WithHeader("Authorization", "Bearer "+token). 2229 WithBytes([]byte(`{ 2230 "data": { 2231 "type": "io.cozy.files.metadata", 2232 "attributes": { 2233 "label": "bar" 2234 } 2235 } 2236 }`)). 2237 Expect().Status(200). 2238 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 2239 Object() 2240 2241 attrs := obj.Path("$.data.attributes").Object() 2242 tags := attrs.Value("tags").Array() 2243 tags.Length().Equal(1) 2244 tags.First().Equal("qux") 2245 2246 meta := attrs.Value("metadata").Object() 2247 meta.NotContainsKey("category") 2248 meta.ValueEqual("label", "bar") 2249 }) 2250 2251 t.Run("CopyVersionWithCertified", func(t *testing.T) { 2252 e := testutils.CreateTestClient(t, ts.URL) 2253 2254 // Upload some file metadata 2255 metadataID := e.POST("/files/upload/metadata"). 2256 WithHeader("Content-Type", "application/json"). 2257 WithHeader("Authorization", "Bearer "+token). 2258 WithBytes([]byte(`{ 2259 "data": { 2260 "type": "io.cozy.files.metadata", 2261 "attributes": { 2262 "carbonCopy": true, 2263 "electronicSafe": true 2264 } 2265 } 2266 }`)). 2267 Expect().Status(201). 2268 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 2269 Object().Path("$.data.id").String().NotEmpty().Raw() 2270 2271 // Upload a file content linke to the previous metadata. 2272 fileID = e.POST("/files/"). 2273 WithQuery("Name", "copy-version-with-certified"). 2274 WithQuery("Type", "file"). 2275 WithQuery("MetadataID", metadataID). 2276 WithHeader("Content-Type", "text/plain"). 2277 WithHeader("Authorization", "Bearer "+token). 2278 WithBytes([]byte("certified carbonCopy and electronicSafe must be kept if only the qualification change")). 2279 Expect().Status(201). 2280 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 2281 Object().Path("$.data.id").String().NotEmpty().Raw() 2282 2283 // Push some attributes the the last version 2284 e.POST("/files/"+fileID+"/versions"). 2285 WithQuery("Tags", "qux"). 2286 WithHeader("Content-Type", "application/json"). 2287 WithHeader("Authorization", "Bearer "+token). 2288 WithBytes([]byte(`{ 2289 "data": { 2290 "type": "io.cozy.files.metadata", 2291 "attributes": { "qualification": { "purpose": "attestation" } } 2292 } 2293 }`)). 2294 Expect().Status(200) 2295 2296 // Push overide the attributes push by the the last call. 2297 obj := e.POST("/files/"+fileID+"/versions"). 2298 WithQuery("Tags", "qux"). 2299 WithHeader("Content-Type", "application/json"). 2300 WithHeader("Authorization", "Bearer "+token). 2301 WithBytes([]byte(`{ 2302 "data": { 2303 "type": "io.cozy.files.metadata", 2304 "attributes": { "label": "bar" } 2305 } 2306 }`)). 2307 Expect().Status(200). 2308 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 2309 Object() 2310 2311 // Ensure that only the last attributres remains 2312 meta := obj.Path("$.data.attributes.metadata").Object() 2313 meta.ContainsKey("label") 2314 meta.NotContainsKey("qualification") 2315 meta.NotContainsKey("carbonCopy") 2316 meta.NotContainsKey("electronicSafe") 2317 }) 2318 2319 t.Run("CopyVersionWorksForNotes", func(t *testing.T) { 2320 e := testutils.CreateTestClient(t, ts.URL) 2321 2322 // Upload a file note. 2323 obj := e.POST("/files/"). 2324 WithQuery("Name", "test.cozy-note"). 2325 WithQuery("Type", "file"). 2326 WithHeader("Content-Type", consts.NoteMimeType). 2327 WithHeader("Authorization", "Bearer "+token). 2328 WithBytes([]byte("# Title\n\n* foo\n* bar\n")). 2329 Expect().Status(201). 2330 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 2331 Object() 2332 2333 fileID = obj.Path("$.data.id").String().NotEmpty().Raw() 2334 meta1 := obj.Path("$.data.attributes.metadata").Object() 2335 meta1.ContainsKey("title") 2336 meta1.ContainsKey("content") 2337 meta1.ContainsKey("schema") 2338 meta1.ContainsKey("version") 2339 2340 // Update the metadatas. 2341 obj = e.POST("/files/"+fileID+"/versions"). 2342 WithHeader("Authorization", "Bearer "+token). 2343 WithBytes([]byte(`{ 2344 "data": { 2345 "type": "io.cozy.files.metadata", 2346 "attributes": { 2347 "qualification": { "purpose": "attestation" } 2348 } 2349 } 2350 }`)). 2351 Expect().Status(200). 2352 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 2353 Object() 2354 2355 meta := obj.Path("$.data.attributes.metadata").Object() 2356 meta.ValueEqual("title", meta1.Value("title").Raw()) 2357 meta.ValueEqual("content", meta1.Value("content").Raw()) 2358 meta.ValueEqual("schema", meta1.Value("schema").Raw()) 2359 meta.ValueEqual("version", meta1.Value("version").Raw()) 2360 }) 2361 2362 t.Run("ArchiveNoFiles", func(t *testing.T) { 2363 e := testutils.CreateTestClient(t, ts.URL) 2364 2365 e.POST("/files/archive"). 2366 WithHeader("Content-Type", "application/vnd.api+json"). 2367 WithHeader("Authorization", "Bearer "+token). 2368 WithBytes([]byte(`{ 2369 "data": { 2370 "attributes": {} 2371 } 2372 }`)). 2373 Expect().Status(400). 2374 JSON().Equal("Can't create an archive with no files") 2375 }) 2376 2377 t.Run("ArchiveDirectDownload", func(t *testing.T) { 2378 e := testutils.CreateTestClient(t, ts.URL) 2379 2380 // Create the dir "/archive" 2381 dirID := e.POST("/files/"). 2382 WithQuery("Name", "archive"). 2383 WithQuery("Type", "directory"). 2384 WithHeader("Authorization", "Bearer "+token). 2385 Expect().Status(201). 2386 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 2387 Object().Path("$.data.id").String().NotEmpty().Raw() 2388 2389 // Create the 3 empty files "/archive/{foo,bar,baz}" 2390 names := []string{"foo", "bar", "baz"} 2391 for _, name := range names { 2392 e.POST("/files/"+dirID). 2393 WithQuery("Name", name+".jpg"). 2394 WithQuery("Type", "file"). 2395 WithHeader("Authorization", "Bearer "+token). 2396 Expect().Status(201) 2397 } 2398 2399 // Archive several files in a single call and receive a zip file. 2400 e.POST("/files/archive"). 2401 WithHeader("Content-Type", "application/zip"). 2402 WithHeader("Accept", "application/zip"). 2403 WithHeader("Authorization", "Bearer "+token). 2404 WithBytes([]byte(`{ 2405 "data": { 2406 "attributes": { 2407 "files": [ 2408 "/archive/foo.jpg", 2409 "/archive/bar.jpg", 2410 "/archive/baz.jpg" 2411 ] 2412 } 2413 } 2414 }`)). 2415 Expect().Status(200). 2416 Header("Content-Type").Equal("application/zip") 2417 }) 2418 2419 t.Run("ArchiveCreateAndDownload", func(t *testing.T) { 2420 e := testutils.CreateTestClient(t, ts.URL) 2421 2422 // Create the dir "/archive2" 2423 dirID := e.POST("/files/"). 2424 WithQuery("Name", "archive2"). 2425 WithQuery("Type", "directory"). 2426 WithHeader("Authorization", "Bearer "+token). 2427 Expect().Status(201). 2428 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 2429 Object().Path("$.data.id").String().NotEmpty().Raw() 2430 2431 // Create the 3 empty files "/archive/{foo,bar,baz}" 2432 names := []string{"foo", "bar", "baz"} 2433 for _, name := range names { 2434 e.POST("/files/"+dirID). 2435 WithQuery("Name", name+".jpg"). 2436 WithQuery("Type", "file"). 2437 WithHeader("Authorization", "Bearer "+token). 2438 Expect().Status(201) 2439 } 2440 2441 // Archive several files in a single call 2442 related := e.POST("/files/archive"). 2443 WithHeader("Content-Type", "application/zip"). 2444 WithHeader("Authorization", "Bearer "+token). 2445 WithBytes([]byte(`{ 2446 "data": { 2447 "attributes": { 2448 "files": [ 2449 "/archive/foo.jpg", 2450 "/archive/bar.jpg", 2451 "/archive/baz.jpg" 2452 ] 2453 } 2454 } 2455 }`)). 2456 Expect().Status(200). 2457 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 2458 Object().Path("$.links.related").String().NotEmpty().Raw() 2459 2460 // Fetch the the file preview in order to check if it's accessible. 2461 e.GET(related). 2462 Expect().Status(200). 2463 Header("Content-Disposition").Equal(`attachment; filename="archive.zip"`) 2464 }) 2465 2466 t.Run("FileCreateAndDownloadByPath", func(t *testing.T) { 2467 e := testutils.CreateTestClient(t, ts.URL) 2468 2469 // Create the file "/todownload2steps" 2470 e.POST("/files/"). 2471 WithQuery("Name", "todownload2steps"). 2472 WithQuery("Type", "file"). 2473 WithHeader("Content-Type", "text/plain"). 2474 WithHeader("Authorization", "Bearer "+token). 2475 WithBytes([]byte("foo,bar")). 2476 Expect().Status(201) 2477 2478 // Start the download in two steps by requiring the url 2479 related := e.POST("/files/downloads"). 2480 WithQuery("Path", "/todownload2steps"). 2481 WithHeader("Authorization", "Bearer "+token). 2482 Expect().Status(200). 2483 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 2484 Object().Path("$.links.related").String().NotEmpty().Raw() 2485 2486 // Fetch the display url. 2487 e.GET(related). 2488 Expect().Status(200). 2489 Header("Content-Disposition").Equal(`inline; filename="todownload2steps"`) 2490 2491 // Fetch the display url with the Dl query returning an attachment instead of an inline file. 2492 e.GET(related). 2493 WithQuery("Dl", "1"). 2494 Expect().Status(200). 2495 Header("Content-Disposition").Equal(`attachment; filename="todownload2steps"`) 2496 }) 2497 2498 t.Run("FileCreateAndDownloadByID", func(t *testing.T) { 2499 e := testutils.CreateTestClient(t, ts.URL) 2500 2501 // Create the file "/todownload2steps" 2502 fileID = e.POST("/files/"). 2503 WithQuery("Name", "todownload2stepsbis"). 2504 WithQuery("Type", "file"). 2505 WithHeader("Content-Type", "text/plain"). 2506 WithHeader("Authorization", "Bearer "+token). 2507 WithBytes([]byte("foo,bar")). 2508 Expect().Status(201). 2509 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 2510 Object().Path("$.data.id").String().NotEmpty().Raw() 2511 2512 // Start the download in two steps by requiring the url 2513 related := e.POST("/files/downloads"). 2514 WithQuery("Id", fileID). 2515 WithHeader("Authorization", "Bearer "+token). 2516 WithHeader("Content-Type", ""). 2517 Expect().Status(200). 2518 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 2519 Object().Path("$.links.related").String().NotEmpty().Raw() 2520 2521 // Fetch the display url. 2522 e.GET(related). 2523 Expect().Status(200). 2524 Header("Content-Disposition").Equal(`inline; filename="todownload2stepsbis"`) 2525 }) 2526 2527 t.Run("EncryptedFileCreate", func(t *testing.T) { 2528 e := testutils.CreateTestClient(t, ts.URL) 2529 2530 // Create the file "/todownload2steps" 2531 obj := e.POST("/files/"). 2532 WithQuery("Name", "encryptedfile"). 2533 WithQuery("Type", "file"). 2534 WithQuery("Encrypted", true). 2535 WithHeader("Content-Type", "text/plain"). 2536 WithHeader("Authorization", "Bearer "+token). 2537 WithBytes([]byte("foo")). 2538 Expect().Status(201). 2539 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 2540 Object() 2541 2542 attrs := obj.Path("$.data.attributes").Object() 2543 attrs.ValueEqual("name", "encryptedfile") 2544 attrs.ValueEqual("encrypted", true) 2545 }) 2546 2547 t.Run("HeadDirOrFileNotFound", func(t *testing.T) { 2548 e := testutils.CreateTestClient(t, ts.URL) 2549 2550 e.HEAD("/files/fakeid"). 2551 WithQuery("Type", "directory"). 2552 WithHeader("Authorization", "Bearer "+token). 2553 Expect().Status(404) 2554 }) 2555 2556 t.Run("HeadDirOrFileExists", func(t *testing.T) { 2557 e := testutils.CreateTestClient(t, ts.URL) 2558 2559 // Create the dir "/hellothere" 2560 dirID := e.POST("/files/"). 2561 WithQuery("Name", "hellothere"). 2562 WithQuery("Type", "directory"). 2563 WithHeader("Authorization", "Bearer "+token). 2564 Expect().Status(201). 2565 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 2566 Object().Path("$.data.id").String().NotEmpty().Raw() 2567 2568 e.HEAD("/files/"+dirID). 2569 WithQuery("Type", "directory"). 2570 WithHeader("Authorization", "Bearer "+token). 2571 Expect().Status(200) 2572 }) 2573 2574 t.Run("ArchiveNotFound", func(t *testing.T) { 2575 e := testutils.CreateTestClient(t, ts.URL) 2576 2577 e.POST("/files/archive"). 2578 WithHeader("Content-Type", "application/vnd.api+json"). 2579 WithHeader("Authorization", "Bearer "+token). 2580 WithBytes([]byte(`{ 2581 "data": { 2582 "attributes": { 2583 "files": [ 2584 "/archive/foo.jpg", 2585 "/no/such/file", 2586 "/archive/baz.jpg" 2587 ] 2588 } 2589 } 2590 }`)). 2591 Expect().Status(404) 2592 }) 2593 2594 t.Run("DirTrash", func(t *testing.T) { 2595 e := testutils.CreateTestClient(t, ts.URL) 2596 2597 // Create the dir "/totrashdir" 2598 dirID := e.POST("/files/"). 2599 WithQuery("Name", "totrashdir"). 2600 WithQuery("Type", "directory"). 2601 WithHeader("Authorization", "Bearer "+token). 2602 Expect().Status(201). 2603 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 2604 Object().Path("$.data.id").String().NotEmpty().Raw() 2605 2606 // Create the file "/totrashdir/child1" 2607 e.POST("/files/"+dirID). 2608 WithQuery("Name", "child1"). 2609 WithQuery("Type", "file"). 2610 WithHeader("Authorization", "Bearer "+token). 2611 Expect().Status(201) 2612 2613 // Create the file "/totrashdir/child2" 2614 e.POST("/files/"+dirID). 2615 WithQuery("Name", "child2"). 2616 WithQuery("Type", "file"). 2617 WithHeader("Authorization", "Bearer "+token). 2618 Expect().Status(201) 2619 2620 // Trash "/totrashdir" 2621 e.DELETE("/files/"+dirID). 2622 WithHeader("Authorization", "Bearer "+token). 2623 Expect().Status(200) 2624 2625 // Get "/totrashdir" 2626 e.GET("/files/"+dirID). 2627 WithHeader("Authorization", "Bearer "+token). 2628 Expect().Status(200) 2629 2630 // Get "/totrashdir/child1" from the trash 2631 e.GET("/files/download"). 2632 WithQuery("Path", vfs.TrashDirName+"/totrashdir/child1"). 2633 WithHeader("Authorization", "Bearer "+token). 2634 Expect().Status(200) 2635 2636 // Get "/totrashdir/child2" from the trash 2637 e.GET("/files/download"). 2638 WithQuery("Path", vfs.TrashDirName+"/totrashdir/child2"). 2639 WithHeader("Authorization", "Bearer "+token). 2640 Expect().Status(200) 2641 2642 // Trash again "/totrashdir" and fail 2643 e.DELETE("/files/"+dirID). 2644 WithHeader("Authorization", "Bearer "+token). 2645 Expect().Status(400) 2646 }) 2647 2648 t.Run("FileTrash", func(t *testing.T) { 2649 e := testutils.CreateTestClient(t, ts.URL) 2650 2651 // Create the file "/totrashfile" 2652 fileID = e.POST("/files/"). 2653 WithQuery("Name", "totrashfile"). 2654 WithQuery("Type", "file"). 2655 WithHeader("Content-Type", "text/plain"). 2656 WithHeader("Content-MD5", "UmfjCVWct/albVkURcJJfg=="). 2657 WithHeader("Authorization", "Bearer "+token). 2658 WithBytes([]byte("foo,bar")). 2659 Expect().Status(201). 2660 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 2661 Object().Path("$.data.id").String().NotEmpty().Raw() 2662 2663 // Trash the file 2664 e.DELETE("/files/"+fileID). 2665 WithHeader("Authorization", "Bearer "+token). 2666 Expect().Status(200) 2667 2668 // Get the file from the trash 2669 e.GET("/files/download"). 2670 WithQuery("Path", vfs.TrashDirName+"/totrashfile"). 2671 WithHeader("Authorization", "Bearer "+token). 2672 Expect().Status(200) 2673 2674 // Create the file "/totrashfile2" 2675 obj := e.POST("/files/"). 2676 WithQuery("Name", "totrashfile2"). 2677 WithQuery("Type", "file"). 2678 WithHeader("Content-Type", "text/plain"). 2679 WithHeader("Content-MD5", "UmfjCVWct/albVkURcJJfg=="). 2680 WithHeader("Authorization", "Bearer "+token). 2681 WithBytes([]byte("foo,bar")). 2682 Expect().Status(201). 2683 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 2684 Object() 2685 2686 fileID = obj.Path("$.data.id").String().NotEmpty().Raw() 2687 rev := obj.Path("$.data.meta.rev").String().NotEmpty().Raw() 2688 2689 // Trash the file with an invalid rev 2690 e.DELETE("/files/"+fileID). 2691 WithHeader("Authorization", "Bearer "+token). 2692 WithHeader("If-Match", "badrev"). // Invalid 2693 Expect().Status(412) 2694 2695 // Trash the file with a valid rev 2696 e.DELETE("/files/"+fileID). 2697 WithHeader("Authorization", "Bearer "+token). 2698 WithHeader("If-Match", rev). 2699 Expect().Status(200) 2700 2701 // Try to trash an the already trashed file 2702 e.DELETE("/files/"+fileID). 2703 WithHeader("Authorization", "Bearer "+token). 2704 Expect().Status(400) 2705 }) 2706 2707 t.Run("ForbidMovingTrashedFile", func(t *testing.T) { 2708 e := testutils.CreateTestClient(t, ts.URL) 2709 2710 // Create the file "/forbidmovingtrashedfile" 2711 fileID = e.POST("/files/"). 2712 WithQuery("Name", "forbidmovingtrashedfile"). 2713 WithQuery("Type", "file"). 2714 WithHeader("Content-Type", "text/plain"). 2715 WithHeader("Content-MD5", "UmfjCVWct/albVkURcJJfg=="). 2716 WithHeader("Authorization", "Bearer "+token). 2717 WithBytes([]byte("foo,bar")). 2718 Expect().Status(201). 2719 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 2720 Object().Path("$.data.id").String().NotEmpty().Raw() 2721 2722 // Trash the file 2723 e.DELETE("/files/"+fileID). 2724 WithHeader("Authorization", "Bearer "+token). 2725 Expect().Status(200) 2726 2727 // Try to patch a trashed file. 2728 e.PATCH("/files/"+fileID). 2729 WithHeader("Content-Type", "application/json"). 2730 WithHeader("Authorization", "Bearer "+token). 2731 WithBytes([]byte(`{ 2732 "data": { 2733 "type": "file", 2734 "id": "` + fileID + `", 2735 "attributes": { "dir_id": "` + consts.RootDirID + `" } 2736 } 2737 }`)). 2738 Expect().Status(400) 2739 }) 2740 2741 t.Run("FileRestore", func(t *testing.T) { 2742 e := testutils.CreateTestClient(t, ts.URL) 2743 2744 // Create the file "/torestorefile" 2745 fileID = e.POST("/files/"). 2746 WithQuery("Name", "torestorefile"). 2747 WithQuery("Type", "file"). 2748 WithHeader("Content-Type", "text/plain"). 2749 WithHeader("Content-MD5", "UmfjCVWct/albVkURcJJfg=="). 2750 WithHeader("Authorization", "Bearer "+token). 2751 WithBytes([]byte("foo,bar")). 2752 Expect().Status(201). 2753 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 2754 Object().Path("$.data.id").String().NotEmpty().Raw() 2755 2756 // Trash the file 2757 e.DELETE("/files/"+fileID). 2758 WithHeader("Authorization", "Bearer "+token). 2759 Expect().Status(200). 2760 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 2761 Object(). 2762 Path("$.data.attributes.trashed").Boolean().True() 2763 2764 // Restore the file 2765 e.POST("/files/trash/"+fileID). 2766 WithHeader("Authorization", "Bearer "+token). 2767 Expect().Status(200). 2768 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 2769 Object(). 2770 Path("$.data.attributes.trashed").Boolean().False() 2771 2772 // Download the file. 2773 e.GET("/files/download"). 2774 WithQuery("Path", "/torestorefile"). 2775 WithHeader("Authorization", "Bearer "+token). 2776 Expect().Status(200) 2777 }) 2778 2779 t.Run("FileRestoreWithConflicts", func(t *testing.T) { 2780 e := testutils.CreateTestClient(t, ts.URL) 2781 2782 // Create the file "/torestorefilewithconflict" 2783 fileID = e.POST("/files/"). 2784 WithQuery("Name", "torestorefilewithconflict"). 2785 WithQuery("Type", "file"). 2786 WithHeader("Content-Type", "text/plain"). 2787 WithHeader("Content-MD5", "UmfjCVWct/albVkURcJJfg=="). 2788 WithHeader("Authorization", "Bearer "+token). 2789 WithBytes([]byte("foo,bar")). 2790 Expect().Status(201). 2791 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 2792 Object().Path("$.data.id").String().NotEmpty().Raw() 2793 2794 // Trash the file 2795 e.DELETE("/files/"+fileID). 2796 WithHeader("Authorization", "Bearer "+token). 2797 Expect().Status(200) 2798 2799 // Create a new file "/torestorefilewithconflict" while the old one is 2800 // in the trash 2801 e.POST("/files/"). 2802 WithQuery("Name", "torestorefilewithconflict"). 2803 WithQuery("Type", "file"). 2804 WithHeader("Content-Type", "text/plain"). 2805 WithHeader("Content-MD5", "UmfjCVWct/albVkURcJJfg=="). 2806 WithHeader("Authorization", "Bearer "+token). 2807 WithBytes([]byte("foo,bar")). 2808 Expect().Status(201) 2809 2810 // Restore the trashed file 2811 obj := e.POST("/files/trash/"+fileID). 2812 WithHeader("Authorization", "Bearer "+token). 2813 Expect().Status(200). 2814 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 2815 Object() 2816 2817 // The file should be restore but with a new name. 2818 obj.Path("$.data.id").Equal(fileID) 2819 attrs := obj.Path("$.data.attributes").Object() 2820 attrs.Value("name").String().HasPrefix("torestorefilewithconflict") 2821 attrs.Value("name").String().NotEqual("torestorefilewithconflict") 2822 }) 2823 2824 t.Run("FileRestoreWithWithoutParent", func(t *testing.T) { 2825 e := testutils.CreateTestClient(t, ts.URL) 2826 2827 // Create the dir "/torestorein" 2828 dirID := e.POST("/files/"). 2829 WithQuery("Name", "torestorein"). 2830 WithQuery("Type", "directory"). 2831 WithHeader("Authorization", "Bearer "+token). 2832 Expect().Status(201). 2833 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 2834 Object().Path("$.data.id").String().NotEmpty().Raw() 2835 2836 // Create the file "/torestorein/torestorefilewithconflict" 2837 fileID = e.POST("/files/"+dirID). 2838 WithQuery("Name", "torestorefilewithconflict"). 2839 WithQuery("Type", "file"). 2840 WithHeader("Content-Type", "text/plain"). 2841 WithHeader("Content-MD5", "UmfjCVWct/albVkURcJJfg=="). 2842 WithHeader("Authorization", "Bearer "+token). 2843 WithBytes([]byte("foo,bar")). 2844 Expect().Status(201). 2845 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 2846 Object().Path("$.data.id").String().NotEmpty().Raw() 2847 2848 // Trash the file 2849 e.DELETE("/files/"+fileID). 2850 WithHeader("Authorization", "Bearer "+token). 2851 Expect().Status(200) 2852 2853 // Trash the dir 2854 e.DELETE("/files/"+dirID). 2855 WithHeader("Authorization", "Bearer "+token). 2856 Expect().Status(200) 2857 2858 // Restore the file without the dir 2859 obj := e.POST("/files/trash/"+fileID). 2860 WithHeader("Authorization", "Bearer "+token). 2861 Expect().Status(200). 2862 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 2863 Object() 2864 2865 attrs := obj.Path("$.data.attributes").Object() 2866 attrs.ValueEqual("name", "torestorefilewithconflict") 2867 attrs.NotHasValue("dir_id", consts.RootDirID) 2868 }) 2869 2870 t.Run("FileRestoreWithWithoutParent2", func(t *testing.T) { 2871 e := testutils.CreateTestClient(t, ts.URL) 2872 2873 // Create the dir "/torestorein2" 2874 dirID := e.POST("/files/"). 2875 WithQuery("Name", "torestorein2"). 2876 WithQuery("Type", "directory"). 2877 WithHeader("Authorization", "Bearer "+token). 2878 Expect().Status(201). 2879 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 2880 Object().Path("$.data.id").String().NotEmpty().Raw() 2881 2882 // Create the file "/torestorein2/torestorefilewithconflict2" 2883 fileID = e.POST("/files/"+dirID). 2884 WithQuery("Name", "torestorefilewithconflict2"). 2885 WithQuery("Type", "file"). 2886 WithHeader("Content-Type", "text/plain"). 2887 WithHeader("Content-MD5", "UmfjCVWct/albVkURcJJfg=="). 2888 WithHeader("Authorization", "Bearer "+token). 2889 WithBytes([]byte("foo,bar")). 2890 Expect().Status(201). 2891 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 2892 Object().Path("$.data.id").String().NotEmpty().Raw() 2893 2894 // Trash the dir 2895 e.DELETE("/files/"+dirID). 2896 WithHeader("Authorization", "Bearer "+token). 2897 Expect().Status(200) 2898 2899 // Restore the file deleted with the dir 2900 obj := e.POST("/files/trash/"+fileID). 2901 WithHeader("Authorization", "Bearer "+token). 2902 Expect().Status(200). 2903 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 2904 Object() 2905 2906 attrs := obj.Path("$.data.attributes").Object() 2907 attrs.ValueEqual("name", "torestorefilewithconflict2") 2908 attrs.NotHasValue("dir_id", consts.RootDirID) 2909 }) 2910 2911 t.Run("DirRestore", func(t *testing.T) { 2912 e := testutils.CreateTestClient(t, ts.URL) 2913 2914 // Create the dir "/torestorein3" 2915 dirID := e.POST("/files/"). 2916 WithQuery("Name", "torestorein3"). 2917 WithQuery("Type", "directory"). 2918 WithHeader("Authorization", "Bearer "+token). 2919 Expect().Status(201). 2920 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 2921 Object().Path("$.data.id").String().NotEmpty().Raw() 2922 2923 // Create the file "/torestorein3/totrashfile" 2924 fileID = e.POST("/files/"+dirID). 2925 WithQuery("Name", "totrashfile"). 2926 WithQuery("Type", "file"). 2927 WithHeader("Content-Type", "text/plain"). 2928 WithHeader("Content-MD5", "UmfjCVWct/albVkURcJJfg=="). 2929 WithHeader("Authorization", "Bearer "+token). 2930 WithBytes([]byte("foo,bar")). 2931 Expect().Status(201). 2932 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 2933 Object().Path("$.data.id").String().NotEmpty().Raw() 2934 2935 // Trash the dir 2936 e.DELETE("/files/"+dirID). 2937 WithHeader("Authorization", "Bearer "+token). 2938 Expect().Status(200) 2939 2940 // Get that the file is marked as trashed. 2941 e.GET("/files/"+fileID). 2942 WithHeader("Authorization", "Bearer "+token). 2943 Expect().Status(200). 2944 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 2945 Object().Path("$.data.attributes.trashed").Boolean().True() 2946 2947 // Restore the dir. 2948 e.POST("/files/trash/"+dirID). 2949 WithHeader("Authorization", "Bearer "+token). 2950 Expect().Status(200) 2951 2952 // Get that the file is not marked as trashed. 2953 e.GET("/files/"+fileID). 2954 WithHeader("Authorization", "Bearer "+token). 2955 Expect().Status(200). 2956 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 2957 Object().Path("$.data.attributes.trashed").Boolean().False() 2958 }) 2959 2960 t.Run("DirRestoreWithConflicts", func(t *testing.T) { 2961 e := testutils.CreateTestClient(t, ts.URL) 2962 2963 // Create the dir "/torestoredirwithconflict" 2964 dirID := e.POST("/files/"). 2965 WithQuery("Name", "torestoredirwithconflict"). 2966 WithQuery("Type", "directory"). 2967 WithHeader("Authorization", "Bearer "+token). 2968 Expect().Status(201). 2969 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 2970 Object().Path("$.data.id").String().NotEmpty().Raw() 2971 2972 // Trash the dir 2973 e.DELETE("/files/"+dirID). 2974 WithHeader("Authorization", "Bearer "+token). 2975 Expect().Status(200) 2976 2977 // Create a new dir with the same name 2978 e.POST("/files/"). 2979 WithQuery("Name", "torestoredirwithconflict"). 2980 WithQuery("Type", "directory"). 2981 WithHeader("Authorization", "Bearer "+token). 2982 Expect().Status(201) 2983 2984 // Restore the deleted dir 2985 obj := e.POST("/files/trash/"+dirID). 2986 WithHeader("Authorization", "Bearer "+token). 2987 Expect().Status(200). 2988 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 2989 Object() 2990 2991 attrs := obj.Path("$.data.attributes").Object() 2992 attrs.Value("name").String().HasPrefix("torestoredirwithconflict") 2993 attrs.Value("name").String().NotEqual("torestoredirwithconflict") 2994 }) 2995 2996 t.Run("TrashList", func(t *testing.T) { 2997 e := testutils.CreateTestClient(t, ts.URL) 2998 2999 // Create the file "/tolistfile" 3000 fileID = e.POST("/files/"). 3001 WithQuery("Name", "tolistfile"). 3002 WithQuery("Type", "file"). 3003 WithHeader("Content-Type", "text/plain"). 3004 WithHeader("Content-MD5", "UmfjCVWct/albVkURcJJfg=="). 3005 WithHeader("Authorization", "Bearer "+token). 3006 WithBytes([]byte("foo,bar")). 3007 Expect().Status(201). 3008 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 3009 Object().Path("$.data.id").String().NotEmpty().Raw() 3010 3011 // Create the dir "/tolistdir" 3012 dirID := e.POST("/files/"). 3013 WithQuery("Name", "tolistdir"). 3014 WithQuery("Type", "directory"). 3015 WithHeader("Authorization", "Bearer "+token). 3016 Expect().Status(201). 3017 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 3018 Object().Path("$.data.id").String().NotEmpty().Raw() 3019 3020 // Trash the dir 3021 e.DELETE("/files/"+dirID). 3022 WithHeader("Authorization", "Bearer "+token). 3023 Expect().Status(200) 3024 3025 // Trash the file 3026 e.DELETE("/files/"+fileID). 3027 WithHeader("Authorization", "Bearer "+token). 3028 Expect().Status(200) 3029 3030 // Get the number of elements in trash 3031 e.GET("/files/trash"). 3032 WithHeader("Authorization", "Bearer "+token). 3033 Expect().Status(200). 3034 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 3035 Object(). 3036 Value("data").Array().Length().Gt(2) 3037 }) 3038 3039 t.Run("TrashClear", func(t *testing.T) { 3040 e := testutils.CreateTestClient(t, ts.URL) 3041 3042 // Check that the trash is not empty 3043 e.GET("/files/trash"). 3044 WithHeader("Authorization", "Bearer "+token). 3045 Expect().Status(200). 3046 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 3047 Object(). 3048 Value("data").Array().Length().Gt(2) 3049 3050 // Empty the trash 3051 e.DELETE("/files/trash"). 3052 WithHeader("Authorization", "Bearer "+token). 3053 Expect().Status(204) 3054 3055 // Check that the trash is empty 3056 e.GET("/files/trash"). 3057 WithHeader("Authorization", "Bearer "+token). 3058 Expect().Status(200). 3059 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 3060 Object(). 3061 Value("data").Array().Length().Equal(0) 3062 }) 3063 3064 t.Run("DestroyFile", func(t *testing.T) { 3065 e := testutils.CreateTestClient(t, ts.URL) 3066 3067 // Create the file "/tolistfile" 3068 fileID = e.POST("/files/"). 3069 WithQuery("Name", "tolistfile"). 3070 WithQuery("Type", "file"). 3071 WithHeader("Content-Type", "text/plain"). 3072 WithHeader("Content-MD5", "UmfjCVWct/albVkURcJJfg=="). 3073 WithHeader("Authorization", "Bearer "+token). 3074 WithBytes([]byte("foo,bar")). 3075 Expect().Status(201). 3076 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 3077 Object().Path("$.data.id").String().NotEmpty().Raw() 3078 3079 // Create the dir "/tolistdir" 3080 dirID := e.POST("/files/"). 3081 WithQuery("Name", "tolistdir"). 3082 WithQuery("Type", "directory"). 3083 WithHeader("Authorization", "Bearer "+token). 3084 Expect().Status(201). 3085 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 3086 Object().Path("$.data.id").String().NotEmpty().Raw() 3087 3088 // Trash the dir 3089 e.DELETE("/files/"+dirID). 3090 WithHeader("Authorization", "Bearer "+token). 3091 Expect().Status(200) 3092 3093 // Trash the file 3094 e.DELETE("/files/"+fileID). 3095 WithHeader("Authorization", "Bearer "+token). 3096 Expect().Status(200) 3097 3098 // Destroy the file 3099 e.DELETE("/files/trash/"+fileID). 3100 WithHeader("Authorization", "Bearer "+token). 3101 Expect().Status(204) 3102 3103 // List the elements in trash 3104 e.GET("/files/trash"). 3105 WithHeader("Authorization", "Bearer "+token). 3106 Expect().Status(200). 3107 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 3108 Object(). 3109 Value("data").Array().Length().Equal(1) 3110 3111 // Detroy the dir 3112 e.DELETE("/files/trash/"+dirID). 3113 WithHeader("Authorization", "Bearer "+token). 3114 Expect().Status(204) 3115 3116 // List the elements in trash, should be empty 3117 e.GET("/files/trash"). 3118 WithHeader("Authorization", "Bearer "+token). 3119 Expect().Status(200). 3120 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 3121 Object(). 3122 Value("data").Array().Length().Equal(0) 3123 }) 3124 3125 t.Run("ThumbnailImages", func(t *testing.T) { 3126 e := testutils.CreateTestClient(t, ts.URL) 3127 3128 // Get the image file infos. 3129 obj := e.GET("/files/"+imgID). 3130 WithHeader("Authorization", "Bearer "+token). 3131 Expect().Status(200). 3132 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 3133 Object() 3134 3135 // Retrieve the thumbnail links 3136 links := obj.Path("$.data.links").Object() 3137 large := links.Value("large").String().NotEmpty().Raw() 3138 medium := links.Value("medium").String().NotEmpty().Raw() 3139 small := links.Value("small").String().NotEmpty().Raw() 3140 tiny := links.Value("tiny").String().NotEmpty().Raw() 3141 3142 // Test the large thumbnail 3143 e.GET(large). 3144 WithHeader("Authorization", "Bearer "+token). 3145 Expect().Status(200). 3146 Header("Content-Type").Equal("image/jpeg") 3147 3148 // Test the medium thumbnail 3149 e.GET(medium). 3150 WithHeader("Authorization", "Bearer "+token). 3151 Expect().Status(200). 3152 Header("Content-Type").Equal("image/jpeg") 3153 3154 // Test the small thumbnail 3155 e.GET(small). 3156 WithHeader("Authorization", "Bearer "+token). 3157 Expect().Status(200). 3158 Header("Content-Type").Equal("image/jpeg") 3159 3160 // Test the tiny thumbnail 3161 e.GET(tiny). 3162 WithHeader("Authorization", "Bearer "+token). 3163 Expect().Status(200). 3164 Header("Content-Type").Equal("image/jpeg") 3165 }) 3166 3167 t.Run("ThumbnailPDFs", func(t *testing.T) { 3168 e := testutils.CreateTestClient(t, ts.URL) 3169 3170 rawPDF, err := os.ReadFile("../../tests/fixtures/dev-desktop.pdf") 3171 require.NoError(t, err) 3172 3173 // Upload a PDF file 3174 pdfID := e.POST("/files/"). 3175 WithQuery("Name", "dev-desktop.pdf"). 3176 WithQuery("Type", "file"). 3177 WithHeader("Authorization", "Bearer "+token). 3178 WithHeader("Content-Type", "application/pdf"). 3179 WithBytes(rawPDF). 3180 Expect().Status(201). 3181 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 3182 Object(). 3183 Path("$.data.id").String().NotEmpty().Raw() 3184 3185 // Get the PDF file 3186 obj := e.GET("/files/"+pdfID). 3187 WithHeader("Authorization", "Bearer "+token). 3188 Expect().Status(200). 3189 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 3190 Object() 3191 3192 // Retrieve the thumbnail links 3193 links := obj.Path("$.data.links").Object() 3194 large := links.Value("large").String().NotEmpty().Raw() 3195 medium := links.Value("medium").String().NotEmpty().Raw() 3196 small := links.Value("small").String().NotEmpty().Raw() 3197 3198 // Wait for tiny thumbnail generation 3199 time.Sleep(time.Second) 3200 3201 tiny := links.Value("tiny").String().NotEmpty().Raw() 3202 3203 // Large, medium, and small are not generated automatically 3204 e.GET(large). 3205 WithHeader("Authorization", "Bearer "+token). 3206 Expect().Status(404). 3207 Header("Content-Type").Equal("image/png") 3208 e.GET(medium). 3209 WithHeader("Authorization", "Bearer "+token). 3210 Expect().Status(404). 3211 Header("Content-Type").Equal("image/png") 3212 e.GET(small). 3213 WithHeader("Authorization", "Bearer "+token). 3214 Expect().Status(404). 3215 Header("Content-Type").Equal("image/png") 3216 3217 // Wait for tiny thumbnail generation 3218 time.Sleep(1 * time.Second) 3219 3220 // Test the tiny thumbnail 3221 e.GET(tiny). 3222 WithHeader("Authorization", "Bearer "+token). 3223 Expect().Status(200). 3224 Header("Content-Type").Equal("image/jpeg") 3225 3226 // Wait for other thumbnails generation 3227 time.Sleep(3 * time.Second) 3228 3229 e.GET(large). 3230 WithHeader("Authorization", "Bearer "+token). 3231 Expect().Status(200). 3232 Header("Content-Type").Equal("image/jpeg") 3233 e.GET(medium). 3234 WithHeader("Authorization", "Bearer "+token). 3235 Expect().Status(200). 3236 Header("Content-Type").Equal("image/jpeg") 3237 e.GET(small). 3238 WithHeader("Authorization", "Bearer "+token). 3239 Expect().Status(200). 3240 Header("Content-Type").Equal("image/jpeg") 3241 }) 3242 3243 t.Run("GetFileByPublicLink", func(t *testing.T) { 3244 var publicToken string 3245 var err error 3246 3247 t.Run("success", func(t *testing.T) { 3248 e := testutils.CreateTestClient(t, ts.URL) 3249 3250 // Upload a file 3251 fileID = e.POST("/files/"). 3252 WithQuery("Name", "publicfile"). 3253 WithQuery("Type", "file"). 3254 WithHeader("Content-Type", "application/pdf"). 3255 WithHeader("Content-MD5", "rL0Y20zC+Fzt72VPzMSk2A=="). 3256 WithHeader("Authorization", "Bearer "+token). 3257 WithBytes([]byte("foo")). 3258 Expect().Status(201). 3259 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 3260 Object(). 3261 Path("$.data.id").String().NotEmpty().Raw() 3262 3263 // Generating a new token 3264 publicToken, err = testInstance.MakeJWT(consts.ShareAudience, "email", "io.cozy.files", "", time.Now()) 3265 require.NoError(t, err) 3266 3267 expires := time.Now().Add(2 * time.Minute) 3268 rules := permission.Set{ 3269 permission.Rule{ 3270 Type: "io.cozy.files", 3271 Verbs: permission.Verbs(permission.GET), 3272 Values: []string{fileID}, 3273 }, 3274 } 3275 perms := permission.Permission{ 3276 Permissions: rules, 3277 } 3278 _, err = permission.CreateShareSet(testInstance, &permission.Permission{Type: "app", Permissions: rules}, "", map[string]string{"email": publicToken}, nil, perms, &expires) 3279 require.NoError(t, err) 3280 3281 // Use the public token to get the file 3282 e.GET("/files/"+fileID). 3283 WithHeader("Authorization", "Bearer "+publicToken). 3284 Expect().Status(200) 3285 }) 3286 3287 t.Run("GetFileByPublicLinkRateExceeded", func(t *testing.T) { 3288 e := testutils.CreateTestClient(t, ts.URL) 3289 3290 // Blocking the file by accessing it a lot of times 3291 for i := 0; i < 1999; i++ { 3292 err = config.GetRateLimiter().CheckRateLimitKey(fileID, limits.SharingPublicLinkType) 3293 assert.NoError(t, err) 3294 } 3295 3296 err = config.GetRateLimiter().CheckRateLimitKey(fileID, limits.SharingPublicLinkType) 3297 require.Error(t, err) 3298 require.ErrorIs(t, err, limits.ErrRateLimitReached) 3299 3300 e.GET("/files/"+fileID). 3301 WithHeader("Authorization", "Bearer "+publicToken). 3302 Expect().Status(500). 3303 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 3304 Object().Path("$.errors[0].detail").String().Equal("Rate limit exceeded") 3305 }) 3306 }) 3307 3308 t.Run("Find", func(t *testing.T) { 3309 e := testutils.CreateTestClient(t, ts.URL) 3310 3311 type M map[string]interface{} 3312 type S []interface{} 3313 3314 defIndex := M{"index": M{"fields": S{"_id"}}} 3315 _, err := couchdb.DefineIndexRaw(testInstance, "io.cozy.files", &defIndex) 3316 assert.NoError(t, err) 3317 3318 defIndex2 := M{"index": M{"fields": S{"type"}}} 3319 _, err = couchdb.DefineIndexRaw(testInstance, "io.cozy.files", &defIndex2) 3320 assert.NoError(t, err) 3321 3322 obj := e.POST("/files/_find"). 3323 WithHeader("Content-Type", "application/json"). 3324 WithHeader("Authorization", "Bearer "+token). 3325 WithBytes([]byte(`{ 3326 "selector": { 3327 "type": "file" 3328 }, 3329 "limit": 1 3330 }`)). 3331 Expect().Status(200). 3332 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 3333 Object() 3334 3335 meta := obj.Value("meta").Object() 3336 meta.NotEmpty() 3337 meta.NotContainsKey("execution_stats") 3338 3339 data := obj.Value("data").Array() 3340 data.Length().Equal(1) 3341 3342 attrs := data.First().Object().Value("attributes").Object() 3343 attrs.Value("name").String().NotEmpty() 3344 attrs.Value("type").String().NotEmpty() 3345 attrs.Value("size").String().AsNumber(10) 3346 attrs.Value("path").String().NotEmpty() 3347 3348 obj = e.POST("/files/_find"). 3349 WithHeader("Content-Type", "application/json"). 3350 WithHeader("Authorization", "Bearer "+token). 3351 WithBytes([]byte(`{ 3352 "selector": { 3353 "_id": { 3354 "$gt": null 3355 } 3356 }, 3357 "limit": 1, 3358 "execution_stats": true 3359 }`)). 3360 Expect().Status(200). 3361 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 3362 Object() 3363 3364 data = obj.Value("data").Array() 3365 data.Length().Equal(1) 3366 3367 nextURL, err := url.Parse(obj.Path("$.links.next").String().NotEmpty().Raw()) 3368 require.NoError(t, err) 3369 3370 e.POST(nextURL.Path). 3371 WithQueryString(nextURL.RawQuery). 3372 WithHeader("Content-Type", "application/json"). 3373 WithHeader("Authorization", "Bearer "+token). 3374 WithBytes([]byte(`{ 3375 "selector": { 3376 "_id": { 3377 "$gt": null 3378 } 3379 }, 3380 "limit": 1, 3381 "execution_stats": true 3382 }`)). 3383 Expect().Status(200). 3384 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 3385 Object() 3386 3387 data = obj.Value("data").Array() 3388 data.Length().Equal(1) 3389 3390 // Make a request with a whitelist of fields 3391 obj = e.POST("/files/_find"). 3392 WithHeader("Content-Type", "application/json"). 3393 WithHeader("Authorization", "Bearer "+token). 3394 WithBytes([]byte(`{ 3395 "selector": { 3396 "_id": { 3397 "$gt": null 3398 } 3399 }, 3400 "fields": ["dir_id", "name", "name"], 3401 "limit": 1 3402 }`)). 3403 Expect().Status(200). 3404 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 3405 Object() 3406 3407 attrs = obj.Path("$.data[0].attributes").Object() 3408 attrs.Value("name").String().NotEmpty() 3409 attrs.Value("dir_id").String().NotEmpty() 3410 attrs.Value("type").String().NotEmpty() 3411 attrs.NotContainsKey("path") 3412 attrs.NotContainsKey("create_at") 3413 attrs.NotContainsKey("updated_at") 3414 attrs.NotContainsKey("tags") 3415 3416 obj = e.POST("/files/_find"). 3417 WithHeader("Content-Type", "application/json"). 3418 WithHeader("Authorization", "Bearer "+token). 3419 WithBytes([]byte(`{ 3420 "selector": { 3421 "type": "file" 3422 }, 3423 "fields": ["name"], 3424 "limit": 1 3425 }`)). 3426 Expect().Status(200). 3427 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 3428 Object() 3429 3430 data = obj.Value("data").Array() 3431 data.Length().Equal(1) 3432 3433 attrs = data.First().Object().Value("attributes").Object() 3434 attrs.Value("name").String().NotEmpty() 3435 attrs.Value("type").String().NotEmpty() 3436 attrs.Value("size").String().AsNumber(10) 3437 attrs.ValueEqual("trashed", false) 3438 attrs.NotContainsKey("encrypted") 3439 attrs.NotContainsKey("created_at") 3440 attrs.NotContainsKey("updated_at") 3441 attrs.NotContainsKey("tags") 3442 attrs.NotContainsKey("executable") 3443 attrs.NotContainsKey("dir_id") 3444 attrs.NotContainsKey("path") 3445 3446 obj = e.POST("/files/_find"). 3447 WithHeader("Content-Type", "application/json"). 3448 WithHeader("Authorization", "Bearer "+token). 3449 WithBytes([]byte(`{ 3450 "selector": { 3451 "type": "file" 3452 }, 3453 "fields": ["type", "path"], 3454 "limit": 1 3455 }`)). 3456 Expect().Status(200). 3457 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 3458 Object() 3459 3460 data = obj.Value("data").Array() 3461 data.Length().Equal(1) 3462 3463 attrs = data.First().Object().Value("attributes").Object() 3464 attrs.Value("path").String().NotEmpty() 3465 attrs.Value("type").String().NotEmpty() 3466 attrs.NotContainsKey("name") 3467 attrs.NotContainsKey("created_at") 3468 attrs.NotContainsKey("updated_at") 3469 attrs.NotContainsKey("tags") 3470 attrs.NotContainsKey("executable") 3471 3472 // Create dir "/aDirectoryWithReferencedBy" 3473 dirID := e.POST("/files/"). 3474 WithQuery("Name", "aDirectoryWithReferencedBy"). 3475 WithQuery("Type", "directory"). 3476 WithHeader("Authorization", "Bearer "+token). 3477 Expect().Status(201). 3478 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 3479 Object().Path("$.data.id").String().NotEmpty().Raw() 3480 3481 // Create a reference 3482 e.POST("/files/"+dirID+"/relationships/referenced_by"). 3483 WithHeader("Content-Type", "application/json"). 3484 WithHeader("Authorization", "Bearer "+token). 3485 WithBytes([]byte(`{ 3486 "data": { 3487 "id": "fooalbumid", 3488 "type": "io.cozy.photos.albums" 3489 } 3490 }`)). 3491 Expect().Status(200) 3492 3493 obj = e.POST("/files/_find"). 3494 WithHeader("Content-Type", "application/json"). 3495 WithHeader("Authorization", "Bearer "+token). 3496 WithBytes([]byte(`{ 3497 "selector": { 3498 "name": "aDirectoryWithReferencedBy" 3499 }, 3500 "limit": 1 3501 }`)). 3502 Expect().Status(200). 3503 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 3504 Object() 3505 3506 data = obj.Value("data").Array() 3507 data.Length().Equal(1) 3508 3509 elem := data.First().Object() 3510 3511 attrs = elem.Value("attributes").Object() 3512 attrs.ValueEqual("name", "aDirectoryWithReferencedBy") 3513 attrs.ContainsKey("referenced_by") 3514 3515 dataRefs := elem.Path("$.relationships.referenced_by.data").Array() 3516 dataRefs.Length().Equal(1) 3517 ref := dataRefs.First().Object() 3518 ref.ValueEqual("id", "fooalbumid") 3519 ref.ValueEqual("type", "io.cozy.photos.albums") 3520 }) 3521 3522 t.Run("DirSize", func(t *testing.T) { 3523 e := testutils.CreateTestClient(t, ts.URL) 3524 3525 parentID := e.POST("/files/"). 3526 WithQuery("Name", "dirsizeparent"). 3527 WithQuery("Type", "directory"). 3528 WithHeader("Authorization", "Bearer "+token). 3529 Expect().Status(201). 3530 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 3531 Object().Path("$.data.id").String().NotEmpty().Raw() 3532 3533 subID := e.POST("/files/"+parentID). 3534 WithQuery("Name", "dirsizesub"). 3535 WithQuery("Type", "directory"). 3536 WithHeader("Authorization", "Bearer "+token). 3537 Expect().Status(201). 3538 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 3539 Object().Path("$.data.id").String().NotEmpty().Raw() 3540 3541 subsubID := e.POST("/files/"+subID). 3542 WithQuery("Name", "dirsizesub"). 3543 WithQuery("Type", "directory"). 3544 WithHeader("Authorization", "Bearer "+token). 3545 Expect().Status(201). 3546 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 3547 Object().Path("$.data.id").String().NotEmpty().Raw() 3548 3549 // Upload files into each folder 10 times. 3550 for i := 0; i < 10; i++ { 3551 name := "file" + strconv.Itoa(i) 3552 3553 e.POST("/files/"+parentID). 3554 WithQuery("Name", name). 3555 WithQuery("Type", "file"). 3556 WithHeader("Content-Type", "text/plain"). 3557 WithHeader("Content-MD5", "rL0Y20zC+Fzt72VPzMSk2A=="). 3558 WithHeader("Authorization", "Bearer "+token). 3559 WithBytes([]byte("foo")). 3560 Expect().Status(201) 3561 3562 e.POST("/files/"+subID). 3563 WithQuery("Name", name). 3564 WithQuery("Type", "file"). 3565 WithHeader("Content-Type", "text/plain"). 3566 WithHeader("Content-MD5", "rL0Y20zC+Fzt72VPzMSk2A=="). 3567 WithHeader("Authorization", "Bearer "+token). 3568 WithBytes([]byte("foo")). 3569 Expect().Status(201) 3570 3571 e.POST("/files/"+subsubID). 3572 WithQuery("Name", name). 3573 WithQuery("Type", "file"). 3574 WithHeader("Content-Type", "text/plain"). 3575 WithHeader("Content-MD5", "rL0Y20zC+Fzt72VPzMSk2A=="). 3576 WithHeader("Authorization", "Bearer "+token). 3577 WithBytes([]byte("foo")). 3578 Expect().Status(201) 3579 } 3580 3581 // validate the subsub dir 3582 obj := e.GET("/files/"+subsubID+"/size"). 3583 WithHeader("Authorization", "Bearer "+token). 3584 Expect().Status(200). 3585 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 3586 Object() 3587 3588 data := obj.Value("data").Object() 3589 data.ValueEqual("type", consts.DirSizes) 3590 data.ValueEqual("id", subsubID) 3591 data.Value("attributes").Object().ValueEqual("size", "30") 3592 3593 // validate the sub dir 3594 obj = e.GET("/files/"+subID+"/size"). 3595 WithHeader("Authorization", "Bearer "+token). 3596 Expect().Status(200). 3597 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 3598 Object() 3599 3600 data = obj.Value("data").Object() 3601 data.ValueEqual("type", consts.DirSizes) 3602 data.ValueEqual("id", subID) 3603 data.Value("attributes").Object().ValueEqual("size", "60") 3604 3605 // validate the parent dir 3606 obj = e.GET("/files/"+parentID+"/size"). 3607 WithHeader("Authorization", "Bearer "+token). 3608 Expect().Status(200). 3609 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 3610 Object() 3611 3612 data = obj.Value("data").Object() 3613 data.ValueEqual("type", consts.DirSizes) 3614 data.ValueEqual("id", parentID) 3615 data.Value("attributes").Object().ValueEqual("size", "90") 3616 }) 3617 3618 t.Run("DeprecatePreviewAndIcon", func(t *testing.T) { 3619 testutils.TODO(t, "2024-05-01", "Remove the deprecated preview and icon for PDF files") 3620 }) 3621 } 3622 3623 func readFile(fs vfs.VFS, name string) ([]byte, error) { 3624 doc, err := fs.FileByPath(name) 3625 if err != nil { 3626 return nil, err 3627 } 3628 f, err := fs.OpenFile(doc) 3629 if err != nil { 3630 return nil, err 3631 } 3632 defer f.Close() 3633 return io.ReadAll(f) 3634 } 3635 3636 func loadLocale() error { 3637 locale := consts.DefaultLocale 3638 assetsPath := config.GetConfig().Assets 3639 if assetsPath != "" { 3640 pofile := path.Join("../..", assetsPath, "locales", locale+".po") 3641 po, err := os.ReadFile(pofile) 3642 if err != nil { 3643 return fmt.Errorf("Can't load the po file for %s", locale) 3644 } 3645 i18n.LoadLocale(locale, "", po) 3646 } 3647 return nil 3648 }