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 }