github.com/cozy/cozy-stack@v0.0.0-20240603063001-31110fa4cae1/web/files/pagination_test.go (about)

     1  package files
     2  
     3  import (
     4  	"net/url"
     5  	"strconv"
     6  	"testing"
     7  
     8  	"github.com/cozy/cozy-stack/pkg/config/config"
     9  	"github.com/cozy/cozy-stack/pkg/consts"
    10  	"github.com/cozy/cozy-stack/tests/testutils"
    11  	"github.com/cozy/cozy-stack/web/errors"
    12  	"github.com/cozy/cozy-stack/web/middlewares"
    13  	"github.com/gavv/httpexpect/v2"
    14  	"github.com/labstack/echo/v4"
    15  	"github.com/stretchr/testify/assert"
    16  	"github.com/stretchr/testify/require"
    17  )
    18  
    19  func TestPagination(t *testing.T) {
    20  	if testing.Short() {
    21  		t.Skip("an instance is required for this test: test skipped due to the use of --short flag")
    22  	}
    23  
    24  	config.UseTestFile(t)
    25  	require.NoError(t, loadLocale(), "Could not load default locale translations")
    26  
    27  	testutils.NeedCouchdb(t)
    28  	setup := testutils.NewSetup(t, t.Name())
    29  
    30  	config.GetConfig().Fs.URL = &url.URL{
    31  		Scheme: "file",
    32  		Host:   "localhost",
    33  		Path:   t.TempDir(),
    34  	}
    35  
    36  	_, token := setup.GetTestClient(consts.Files + " " + consts.CertifiedCarbonCopy + " " + consts.CertifiedElectronicSafe)
    37  	ts := setup.GetTestServer("/files", Routes, func(r *echo.Echo) *echo.Echo {
    38  		secure := middlewares.Secure(&middlewares.SecureConfig{
    39  			CSPDefaultSrc:     []middlewares.CSPSource{middlewares.CSPSrcSelf},
    40  			CSPFrameAncestors: []middlewares.CSPSource{middlewares.CSPSrcNone},
    41  		})
    42  		r.Use(secure)
    43  		return r
    44  	})
    45  	ts.Config.Handler.(*echo.Echo).HTTPErrorHandler = errors.ErrorHandler
    46  	t.Cleanup(ts.Close)
    47  
    48  	t.Run("TrashIsSkipped", func(t *testing.T) {
    49  		e := testutils.CreateTestClient(t, ts.URL)
    50  
    51  		nb := 15
    52  		for i := 0; i < nb; i++ {
    53  			name := "foo" + strconv.Itoa(i)
    54  
    55  			e.POST("/files/").
    56  				WithQuery("Name", name).
    57  				WithQuery("Type", "file").
    58  				WithQuery("CreatedAt", "2016-09-18T10:24:53Z").
    59  				WithHeader("Content-Type", "text/plain").
    60  				WithHeader("Date", "Mon, 19 Sep 2016 12:38:04 GMT").
    61  				WithHeader("Authorization", "Bearer "+token).
    62  				WithHeader("Content-MD5", "rL0Y20zC+Fzt72VPzMSk2A==").
    63  				WithBytes([]byte("foo")).
    64  				Expect().Status(201)
    65  		}
    66  
    67  		ids := []string{}
    68  
    69  		// Get the first page
    70  		obj := e.GET("/files/io.cozy.files.root-dir").
    71  			WithQuery("page[limit]", "5").
    72  			WithHeader("Accept", "application/json").
    73  			WithHeader("Authorization", "Bearer "+token).
    74  			Expect().Status(200).
    75  			JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}).
    76  			Object()
    77  
    78  		obj.Path("$.data.relationships.contents.data").Array().Length().Equal(5)
    79  		obj.Value("included").Array().Length().Equal(5)
    80  
    81  		for i, ref := range obj.Path("$.data.relationships.contents.data").Array().Iter() {
    82  			id := obj.Value("included").Array().Element(i).Object().Value("id").String().Raw()
    83  			ref.Object().Value("id").Equal(id).NotEqual(consts.TrashDirID)
    84  
    85  			for _, seen := range ids {
    86  				assert.NotEqual(t, id, seen)
    87  			}
    88  
    89  			ids = append(ids, id)
    90  		}
    91  
    92  		nextURL, err := url.Parse(obj.Path("$.links.next").String().NotEmpty().Raw())
    93  		require.NoError(t, err)
    94  
    95  		// Get the second page
    96  		obj = e.GET(nextURL.Path).
    97  			WithQueryString(nextURL.RawQuery).
    98  			WithHeader("Accept", "application/json").
    99  			WithHeader("Authorization", "Bearer "+token).
   100  			Expect().Status(200).
   101  			JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}).
   102  			Object()
   103  
   104  		obj.Path("$.data.relationships.contents.data").Array().Length().Equal(5)
   105  		obj.Value("included").Array().Length().Equal(5)
   106  
   107  		for i, ref := range obj.Path("$.data.relationships.contents.data").Array().Iter() {
   108  			id := obj.Value("included").Array().Element(i).Object().Value("id").String().Raw()
   109  			ref.Object().Value("id").Equal(id).NotEqual(consts.TrashDirID)
   110  
   111  			for _, seen := range ids {
   112  				assert.NotEqual(t, id, seen)
   113  			}
   114  
   115  			ids = append(ids, id)
   116  		}
   117  
   118  		nextURL, err = url.Parse(obj.Path("$.links.next").String().NotEmpty().Raw())
   119  		require.NoError(t, err)
   120  
   121  		// Get the third page and skip 10 elements
   122  		obj = e.GET(nextURL.Path).
   123  			WithQueryString(nextURL.RawQuery).
   124  			WithQuery("page[skip]", "10").
   125  			WithHeader("Accept", "application/json").
   126  			WithHeader("Authorization", "Bearer "+token).
   127  			Expect().Status(200).
   128  			JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}).
   129  			Object()
   130  
   131  		obj.Path("$.data.relationships.contents.data").Array().Length().Equal(5)
   132  		obj.Value("included").Array().Length().Equal(5)
   133  
   134  		for i, ref := range obj.Path("$.data.relationships.contents.data").Array().Iter() {
   135  			id := obj.Value("included").Array().Element(i).Object().Value("id").String().Raw()
   136  			ref.Object().Value("id").Equal(id).NotEqual(consts.TrashDirID)
   137  
   138  			for _, seen := range ids {
   139  				assert.NotEqual(t, id, seen)
   140  			}
   141  
   142  			ids = append(ids, id)
   143  		}
   144  	})
   145  
   146  	t.Run("ZeroCountIsPresent", func(t *testing.T) {
   147  		e := testutils.CreateTestClient(t, ts.URL)
   148  
   149  		parentID := e.POST("/files/").
   150  			WithQuery("Name", "emptydirectory").
   151  			WithQuery("Type", "directory").
   152  			WithHeader("Authorization", "Bearer "+token).
   153  			Expect().Status(201).
   154  			JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}).
   155  			Object().Path("$.data.id").String().NotEmpty().Raw()
   156  
   157  		obj := e.GET("/files/"+parentID).
   158  			WithHeader("Accept", "application/json").
   159  			WithHeader("Authorization", "Bearer "+token).
   160  			Expect().Status(200).
   161  			JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}).
   162  			Object()
   163  
   164  		obj.Path("$.data.relationships.contents.meta.count").Equal(0)
   165  	})
   166  
   167  	t.Run("ListDirPaginated", func(t *testing.T) {
   168  		e := testutils.CreateTestClient(t, ts.URL)
   169  
   170  		parentID := e.POST("/files/").
   171  			WithQuery("Name", "paginationcontainer").
   172  			WithQuery("Type", "directory").
   173  			WithHeader("Authorization", "Bearer "+token).
   174  			Expect().Status(201).
   175  			JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}).
   176  			Object().Path("$.data.id").String().NotEmpty().Raw()
   177  
   178  		nb := 15
   179  		for i := 0; i < nb; i++ {
   180  			name := "file" + strconv.Itoa(i)
   181  
   182  			e.POST("/files/"+parentID).
   183  				WithQuery("Name", name).
   184  				WithQuery("Type", "file").
   185  				WithQuery("CreatedAt", "2016-09-18T10:24:53Z").
   186  				WithHeader("Content-Type", "text/plain").
   187  				WithHeader("Date", "Mon, 19 Sep 2016 12:38:04 GMT").
   188  				WithHeader("Authorization", "Bearer "+token).
   189  				WithHeader("Content-MD5", "rL0Y20zC+Fzt72VPzMSk2A==").
   190  				WithBytes([]byte("foo")).
   191  				Expect().Status(201)
   192  		}
   193  
   194  		obj := e.GET("/files/"+parentID).
   195  			WithQuery("page[limit]", "7").
   196  			WithHeader("Accept", "application/json").
   197  			WithHeader("Authorization", "Bearer "+token).
   198  			Expect().Status(200).
   199  			JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}).
   200  			Object()
   201  
   202  		data1 := obj.Path("$.data.relationships.contents.data").Array()
   203  		data1.Length().Equal(7)
   204  		obj.Value("included").Array().Length().Equal(7)
   205  
   206  		for i, ref := range obj.Path("$.data.relationships.contents.data").Array().Iter() {
   207  			id := obj.Value("included").Array().Element(i).Object().Value("id").String().Raw()
   208  			ref.Object().Value("id").Equal(id).NotEqual(consts.TrashDirID)
   209  		}
   210  
   211  		obj.Path("$.data.relationships.contents.meta.count").Equal(15)
   212  
   213  		nextURL, err := url.Parse(obj.Path("$.data.relationships.contents.links.next").String().NotEmpty().Raw())
   214  		require.NoError(t, err)
   215  
   216  		// Get the second page
   217  		obj = e.GET(nextURL.Path).
   218  			WithQueryString(nextURL.RawQuery).
   219  			WithHeader("Accept", "application/json").
   220  			WithHeader("Authorization", "Bearer "+token).
   221  			Expect().Status(200).
   222  			JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}).
   223  			Object()
   224  
   225  		data2 := obj.Value("data").Array()
   226  		data2.Length().Equal(7)
   227  		obj.Path("$.meta.count").Equal(15)
   228  
   229  		data2.Element(0).Object().Value("id").
   230  			NotEqual(data1.Element(0).Object().Value("id").String().Raw())
   231  
   232  		nextURL, err = url.Parse(obj.Path("$.links.next").String().NotEmpty().Raw())
   233  		require.NoError(t, err)
   234  
   235  		// Get the third page
   236  		obj = e.GET(nextURL.Path).
   237  			WithQueryString(nextURL.RawQuery).
   238  			WithHeader("Accept", "application/json").
   239  			WithHeader("Authorization", "Bearer "+token).
   240  			Expect().Status(200).
   241  			JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}).
   242  			Object()
   243  
   244  		data3 := obj.Value("data").Array()
   245  		data3.Length().Equal(1)
   246  		obj.Path("$.meta.count").Equal(15)
   247  
   248  		data3.Element(0).Object().Value("id").
   249  			NotEqual(data1.Element(0).Object().Value("id").String().Raw())
   250  
   251  			// Trash the dir
   252  		e.DELETE("/files/"+parentID).
   253  			WithHeader("Authorization", "Bearer "+token).
   254  			Expect().Status(200)
   255  	})
   256  
   257  	t.Run("ListDirPaginatedSkip", func(t *testing.T) {
   258  		e := testutils.CreateTestClient(t, ts.URL)
   259  
   260  		parentID := e.POST("/files/").
   261  			WithQuery("Name", "paginationcontainerskip").
   262  			WithQuery("Type", "directory").
   263  			WithHeader("Authorization", "Bearer "+token).
   264  			Expect().Status(201).
   265  			JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}).
   266  			Object().Path("$.data.id").String().NotEmpty().Raw()
   267  
   268  		nb := 15
   269  		for i := 0; i < nb; i++ {
   270  			name := "file" + strconv.Itoa(i)
   271  
   272  			e.POST("/files/"+parentID).
   273  				WithQuery("Name", name).
   274  				WithQuery("Type", "file").
   275  				WithQuery("CreatedAt", "2016-09-18T10:24:53Z").
   276  				WithHeader("Content-Type", "text/plain").
   277  				WithHeader("Date", "Mon, 19 Sep 2016 12:38:04 GMT").
   278  				WithHeader("Authorization", "Bearer "+token).
   279  				WithHeader("Content-MD5", "rL0Y20zC+Fzt72VPzMSk2A==").
   280  				WithBytes([]byte("foo")).
   281  				Expect().Status(201)
   282  		}
   283  
   284  		obj := e.GET("/files/"+parentID).
   285  			WithQuery("page[limit]", "7").
   286  			WithQuery("page[skip]", "0").
   287  			WithHeader("Accept", "application/json").
   288  			WithHeader("Authorization", "Bearer "+token).
   289  			Expect().Status(200).
   290  			JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}).
   291  			Object()
   292  
   293  		data1 := obj.Path("$.data.relationships.contents.data").Array()
   294  		data1.Length().Equal(7)
   295  		obj.Value("included").Array().Length().Equal(7)
   296  		obj.Path("$.data.relationships.contents.meta.count").Equal(15)
   297  
   298  		rawNext := obj.Path("$.data.relationships.contents.links.next").String().NotEmpty().Raw()
   299  		assert.Contains(t, rawNext, "skip")
   300  
   301  		nextURL, err := url.Parse(rawNext)
   302  		require.NoError(t, err)
   303  
   304  		// Get the second page
   305  		obj = e.GET(nextURL.Path).
   306  			WithQueryString(nextURL.RawQuery).
   307  			WithHeader("Accept", "application/json").
   308  			WithHeader("Authorization", "Bearer "+token).
   309  			Expect().Status(200).
   310  			JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}).
   311  			Object()
   312  
   313  		data2 := obj.Value("data").Array()
   314  		data2.Length().Equal(7)
   315  		obj.Path("$.meta.count").Equal(15)
   316  
   317  		data2.Element(0).Object().Value("id").
   318  			NotEqual(data1.Element(0).Object().Value("id").String().Raw())
   319  
   320  		nextURL, err = url.Parse(obj.Path("$.links.next").String().NotEmpty().Raw())
   321  		require.NoError(t, err)
   322  
   323  		// Get the third page
   324  		obj = e.GET(nextURL.Path).
   325  			WithQueryString(nextURL.RawQuery).
   326  			WithHeader("Accept", "application/json").
   327  			WithHeader("Authorization", "Bearer "+token).
   328  			Expect().Status(200).
   329  			JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}).
   330  			Object()
   331  
   332  		data3 := obj.Value("data").Array()
   333  		data3.Length().Equal(1)
   334  		obj.Path("$.meta.count").Equal(15)
   335  
   336  		data2.Element(0).Object().Value("id").
   337  			NotEqual(data1.Element(0).Object().Value("id").String().Raw())
   338  
   339  			// Trash the dir
   340  		e.DELETE("/files/"+parentID).
   341  			WithHeader("Authorization", "Bearer "+token).
   342  			Expect().Status(200)
   343  	})
   344  }