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  }