core-services-sdk 1.3.40 → 1.3.41
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
package/src/mongodb/paginate.js
CHANGED
|
@@ -11,7 +11,7 @@ import { ObjectId } from 'mongodb'
|
|
|
11
11
|
* @param {'asc'|'desc'} [options.order='asc']
|
|
12
12
|
* @param {number} [options.limit=10]
|
|
13
13
|
*/
|
|
14
|
-
export async function
|
|
14
|
+
export async function paginateCursor(
|
|
15
15
|
collection,
|
|
16
16
|
{
|
|
17
17
|
limit = 10,
|
|
@@ -52,7 +52,7 @@ export async function paginate(
|
|
|
52
52
|
collection,
|
|
53
53
|
cursorField,
|
|
54
54
|
})
|
|
55
|
-
: { hasNext:
|
|
55
|
+
: { hasNext: null, hasPrevious: null }
|
|
56
56
|
|
|
57
57
|
const { hasNext, hasPrevious } = paginationEdges
|
|
58
58
|
return {
|
|
@@ -96,3 +96,60 @@ export async function getPaginationEdges({
|
|
|
96
96
|
hasPrevious,
|
|
97
97
|
}
|
|
98
98
|
}
|
|
99
|
+
|
|
100
|
+
import { ObjectId } from 'mongodb'
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Classic page/limit pagination with total count
|
|
104
|
+
*
|
|
105
|
+
* @param {import('mongodb').Collection} collection
|
|
106
|
+
* @param {Object} options
|
|
107
|
+
* @param {Object} [options.filter={}] - MongoDB filter
|
|
108
|
+
* @param {string} [options.cursorField='_id'] - Field to sort by
|
|
109
|
+
* @param {'asc'|'desc'} [options.order='desc'] - Sort order
|
|
110
|
+
* @param {number} [options.limit=10] - Items per page
|
|
111
|
+
* @param {number} [options.page=1] - Page number (1-based)
|
|
112
|
+
* @param {Object} [options.projection] - Projection fields
|
|
113
|
+
*/
|
|
114
|
+
export async function paginate(
|
|
115
|
+
collection,
|
|
116
|
+
{
|
|
117
|
+
filter = {},
|
|
118
|
+
projection,
|
|
119
|
+
order = 'desc',
|
|
120
|
+
cursorField = '_id',
|
|
121
|
+
limit = 10,
|
|
122
|
+
page = 1,
|
|
123
|
+
} = {},
|
|
124
|
+
) {
|
|
125
|
+
// Validation
|
|
126
|
+
if (page < 1) page = 1
|
|
127
|
+
if (limit < 1) limit = 10
|
|
128
|
+
|
|
129
|
+
const sort = { [cursorField]: order === 'asc' ? 1 : -1 }
|
|
130
|
+
const skip = (page - 1) * limit
|
|
131
|
+
|
|
132
|
+
const [results, totalCount] = await Promise.all([
|
|
133
|
+
collection
|
|
134
|
+
.find(filter, { projection })
|
|
135
|
+
.sort(sort)
|
|
136
|
+
.skip(skip)
|
|
137
|
+
.limit(limit)
|
|
138
|
+
.toArray(),
|
|
139
|
+
collection.countDocuments(filter),
|
|
140
|
+
])
|
|
141
|
+
|
|
142
|
+
const totalPages = Math.ceil(totalCount / limit)
|
|
143
|
+
const hasNext = page < totalPages
|
|
144
|
+
const hasPrevious = page > 1
|
|
145
|
+
|
|
146
|
+
return {
|
|
147
|
+
order,
|
|
148
|
+
totalCount,
|
|
149
|
+
totalPages,
|
|
150
|
+
currentPage: page,
|
|
151
|
+
hasNext,
|
|
152
|
+
hasPrevious,
|
|
153
|
+
list: results,
|
|
154
|
+
}
|
|
155
|
+
}
|
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
import { describe, it, expect, beforeAll, afterAll, beforeEach } from 'vitest'
|
|
2
|
+
|
|
3
|
+
import { paginateCursor } from '../../src/mongodb/paginate.js'
|
|
4
|
+
import { startMongo, stopMongo } from '../resources/docker-mongo-test.js'
|
|
5
|
+
import { initializeMongoDb } from '../../src/mongodb/initialize-mongodb.js'
|
|
6
|
+
|
|
7
|
+
const MONGO_PORT = 29050
|
|
8
|
+
const CONTAINER_NAME = 'mongo-auth-attempts-paginate-cursor-test'
|
|
9
|
+
|
|
10
|
+
let db
|
|
11
|
+
let collection
|
|
12
|
+
describe('paginateCursor - Integration', () => {
|
|
13
|
+
beforeAll(async () => {
|
|
14
|
+
startMongo(MONGO_PORT, CONTAINER_NAME)
|
|
15
|
+
|
|
16
|
+
db = await initializeMongoDb({
|
|
17
|
+
config: {
|
|
18
|
+
uri: `mongodb://0.0.0.0:${MONGO_PORT}`,
|
|
19
|
+
options: { dbName: 'users-management' },
|
|
20
|
+
},
|
|
21
|
+
collectionNames: {
|
|
22
|
+
TestDocs: 'test_docs',
|
|
23
|
+
},
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
collection = db.TestDocs
|
|
27
|
+
})
|
|
28
|
+
afterAll(() => {
|
|
29
|
+
stopMongo(CONTAINER_NAME)
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
beforeEach(async () => {
|
|
33
|
+
await collection.deleteMany({})
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
const seedDocs = async (count = 12) => {
|
|
37
|
+
const docs = Array.from({ length: count }).map((_, i) => ({
|
|
38
|
+
name: `doc_${i}`,
|
|
39
|
+
createdAt: new Date(Date.now() + i * 1000),
|
|
40
|
+
}))
|
|
41
|
+
await collection.insertMany(docs)
|
|
42
|
+
return docs
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
it('returns first page with limit', async () => {
|
|
46
|
+
await seedDocs(12)
|
|
47
|
+
|
|
48
|
+
const result = await paginateCursor(collection, {
|
|
49
|
+
filter: {},
|
|
50
|
+
limit: 5,
|
|
51
|
+
order: 'desc',
|
|
52
|
+
cursorField: 'createdAt',
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
expect(result.list).toHaveLength(5)
|
|
56
|
+
expect(result.next).toBeDefined()
|
|
57
|
+
expect(result.previous).toBeNull()
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
it('returns empty when no docs match filter', async () => {
|
|
61
|
+
const result = await paginateCursor(collection, {
|
|
62
|
+
filter: { name: 'not-exist' },
|
|
63
|
+
limit: 5,
|
|
64
|
+
order: 'desc',
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
expect(result.list).toHaveLength(0)
|
|
68
|
+
expect(result.next).toBeNull()
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
it('supports ascending order', async () => {
|
|
72
|
+
const docs = await seedDocs(3)
|
|
73
|
+
|
|
74
|
+
const ascResult = await paginateCursor(collection, {
|
|
75
|
+
filter: {},
|
|
76
|
+
limit: 3,
|
|
77
|
+
order: 'asc',
|
|
78
|
+
cursorField: 'createdAt',
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
expect(ascResult.list[0].name).toBe(docs[0].name)
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
it('supports descending order', async () => {
|
|
85
|
+
const docs = await seedDocs(3)
|
|
86
|
+
|
|
87
|
+
const descResult = await paginateCursor(collection, {
|
|
88
|
+
filter: {},
|
|
89
|
+
limit: 3,
|
|
90
|
+
order: 'desc',
|
|
91
|
+
cursorField: 'createdAt',
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
expect(descResult.list[0].name).toBe(docs[2].name)
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
it('paginateCursors with cursor (next page)', async () => {
|
|
98
|
+
await seedDocs(7)
|
|
99
|
+
|
|
100
|
+
// first page
|
|
101
|
+
const firstPage = await paginateCursor(collection, {
|
|
102
|
+
filter: {},
|
|
103
|
+
limit: 3,
|
|
104
|
+
order: 'asc',
|
|
105
|
+
cursorField: 'createdAt',
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
expect(firstPage.list).toHaveLength(3)
|
|
109
|
+
expect(firstPage.next).toBeDefined()
|
|
110
|
+
|
|
111
|
+
// second page using cursor
|
|
112
|
+
const secondPage = await paginateCursor(collection, {
|
|
113
|
+
filter: {},
|
|
114
|
+
limit: 3,
|
|
115
|
+
order: 'asc',
|
|
116
|
+
cursorField: 'createdAt',
|
|
117
|
+
cursor: firstPage.next,
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
expect(secondPage.list).toHaveLength(3)
|
|
121
|
+
// validate no overlap between first and second page
|
|
122
|
+
expect(secondPage.list[0]._id.toString()).not.toBe(
|
|
123
|
+
firstPage.list[0]._id.toString(),
|
|
124
|
+
)
|
|
125
|
+
expect(secondPage.list.map((d) => d._id.toString())).not.toEqual(
|
|
126
|
+
firstPage.list.map((d) => d._id.toString()),
|
|
127
|
+
)
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
it('paginateCursors with stringified ObjectId as cursor', async () => {
|
|
131
|
+
const docs = await seedDocs(5)
|
|
132
|
+
|
|
133
|
+
// @ts-ignore
|
|
134
|
+
const stringCursor = docs[2]._id.toString()
|
|
135
|
+
|
|
136
|
+
const page = await paginateCursor(collection, {
|
|
137
|
+
filter: {},
|
|
138
|
+
limit: 2,
|
|
139
|
+
order: 'asc',
|
|
140
|
+
cursorField: '_id',
|
|
141
|
+
cursor: stringCursor,
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
expect(page.list).toHaveLength(2)
|
|
145
|
+
|
|
146
|
+
// @ts-ignore
|
|
147
|
+
expect(page.list[0]._id.toString()).toBe(docs[3]._id.toString())
|
|
148
|
+
// @ts-ignore
|
|
149
|
+
expect(page.list[1]._id.toString()).toBe(docs[4]._id.toString())
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
// ---------- PAGINATION EDGES (ASC ORDER) ----------
|
|
153
|
+
|
|
154
|
+
it('ASC - first page: has next, no previous', async () => {
|
|
155
|
+
await seedDocs(7)
|
|
156
|
+
|
|
157
|
+
const page = await paginateCursor(collection, {
|
|
158
|
+
limit: 3,
|
|
159
|
+
order: 'asc',
|
|
160
|
+
cursorField: 'createdAt',
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
expect(page.list).toHaveLength(3)
|
|
164
|
+
expect(page.previous).toBeNull()
|
|
165
|
+
expect(page.next).not.toBeNull()
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
it('ASC - middle page: has both next and previous', async () => {
|
|
169
|
+
await seedDocs(9)
|
|
170
|
+
|
|
171
|
+
const firstPage = await paginateCursor(collection, {
|
|
172
|
+
limit: 3,
|
|
173
|
+
order: 'asc',
|
|
174
|
+
cursorField: 'createdAt',
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
const middlePage = await paginateCursor(collection, {
|
|
178
|
+
limit: 3,
|
|
179
|
+
order: 'asc',
|
|
180
|
+
cursorField: 'createdAt',
|
|
181
|
+
cursor: firstPage.next,
|
|
182
|
+
})
|
|
183
|
+
|
|
184
|
+
expect(middlePage.list).toHaveLength(3)
|
|
185
|
+
expect(middlePage.previous).not.toBeNull()
|
|
186
|
+
expect(middlePage.next).not.toBeNull()
|
|
187
|
+
})
|
|
188
|
+
|
|
189
|
+
it('ASC - last page: has previous, no next', async () => {
|
|
190
|
+
await seedDocs(7)
|
|
191
|
+
|
|
192
|
+
const firstPage = await paginateCursor(collection, {
|
|
193
|
+
limit: 3,
|
|
194
|
+
order: 'asc',
|
|
195
|
+
cursorField: 'createdAt',
|
|
196
|
+
})
|
|
197
|
+
const secondPage = await paginateCursor(collection, {
|
|
198
|
+
limit: 3,
|
|
199
|
+
order: 'asc',
|
|
200
|
+
cursorField: 'createdAt',
|
|
201
|
+
cursor: firstPage.next,
|
|
202
|
+
})
|
|
203
|
+
const lastPage = await paginateCursor(collection, {
|
|
204
|
+
limit: 3,
|
|
205
|
+
order: 'asc',
|
|
206
|
+
cursorField: 'createdAt',
|
|
207
|
+
cursor: secondPage.next,
|
|
208
|
+
})
|
|
209
|
+
|
|
210
|
+
expect(lastPage.list.length).toBeGreaterThan(0)
|
|
211
|
+
expect(lastPage.previous).not.toBeNull()
|
|
212
|
+
expect(lastPage.next).toBeNull()
|
|
213
|
+
})
|
|
214
|
+
|
|
215
|
+
it('ASC - single page: no next, no previous', async () => {
|
|
216
|
+
await seedDocs(2)
|
|
217
|
+
|
|
218
|
+
const singlePage = await paginateCursor(collection, {
|
|
219
|
+
limit: 5,
|
|
220
|
+
order: 'asc',
|
|
221
|
+
cursorField: 'createdAt',
|
|
222
|
+
})
|
|
223
|
+
|
|
224
|
+
expect(singlePage.list).toHaveLength(2)
|
|
225
|
+
expect(singlePage.previous).toBeNull()
|
|
226
|
+
expect(singlePage.next).toBeNull()
|
|
227
|
+
})
|
|
228
|
+
|
|
229
|
+
// ---------- PAGINATION EDGES (DESC ORDER) ----------
|
|
230
|
+
|
|
231
|
+
it('DESC - first page: has next, no previous', async () => {
|
|
232
|
+
await seedDocs(7)
|
|
233
|
+
|
|
234
|
+
const page = await paginateCursor(collection, {
|
|
235
|
+
limit: 3,
|
|
236
|
+
order: 'desc',
|
|
237
|
+
cursorField: 'createdAt',
|
|
238
|
+
})
|
|
239
|
+
|
|
240
|
+
expect(page.list).toHaveLength(3)
|
|
241
|
+
expect(page.previous).toBeNull()
|
|
242
|
+
expect(page.next).not.toBeNull()
|
|
243
|
+
})
|
|
244
|
+
|
|
245
|
+
it('DESC - middle page: has both next and previous', async () => {
|
|
246
|
+
await seedDocs(9)
|
|
247
|
+
|
|
248
|
+
const firstPage = await paginateCursor(collection, {
|
|
249
|
+
limit: 3,
|
|
250
|
+
order: 'desc',
|
|
251
|
+
cursorField: 'createdAt',
|
|
252
|
+
})
|
|
253
|
+
|
|
254
|
+
const middlePage = await paginateCursor(collection, {
|
|
255
|
+
limit: 3,
|
|
256
|
+
order: 'desc',
|
|
257
|
+
cursorField: 'createdAt',
|
|
258
|
+
cursor: firstPage.next,
|
|
259
|
+
})
|
|
260
|
+
|
|
261
|
+
expect(middlePage.list).toHaveLength(3)
|
|
262
|
+
expect(middlePage.previous).not.toBeNull()
|
|
263
|
+
expect(middlePage.next).not.toBeNull()
|
|
264
|
+
})
|
|
265
|
+
|
|
266
|
+
it('DESC - last page: has previous, no next', async () => {
|
|
267
|
+
await seedDocs(7)
|
|
268
|
+
|
|
269
|
+
const firstPage = await paginateCursor(collection, {
|
|
270
|
+
limit: 3,
|
|
271
|
+
order: 'desc',
|
|
272
|
+
cursorField: 'createdAt',
|
|
273
|
+
})
|
|
274
|
+
const secondPage = await paginateCursor(collection, {
|
|
275
|
+
limit: 3,
|
|
276
|
+
order: 'desc',
|
|
277
|
+
cursorField: 'createdAt',
|
|
278
|
+
cursor: firstPage.next,
|
|
279
|
+
})
|
|
280
|
+
const lastPage = await paginateCursor(collection, {
|
|
281
|
+
limit: 3,
|
|
282
|
+
order: 'desc',
|
|
283
|
+
cursorField: 'createdAt',
|
|
284
|
+
cursor: secondPage.next,
|
|
285
|
+
})
|
|
286
|
+
|
|
287
|
+
expect(lastPage.list.length).toBeGreaterThan(0)
|
|
288
|
+
expect(lastPage.previous).not.toBeNull()
|
|
289
|
+
expect(lastPage.next).toBeNull()
|
|
290
|
+
})
|
|
291
|
+
|
|
292
|
+
it('DESC - single page: no next, no previous', async () => {
|
|
293
|
+
await seedDocs(2)
|
|
294
|
+
|
|
295
|
+
const singlePage = await paginateCursor(collection, {
|
|
296
|
+
limit: 5,
|
|
297
|
+
order: 'desc',
|
|
298
|
+
cursorField: 'createdAt',
|
|
299
|
+
})
|
|
300
|
+
|
|
301
|
+
expect(singlePage.list).toHaveLength(2)
|
|
302
|
+
expect(singlePage.previous).toBeNull()
|
|
303
|
+
expect(singlePage.next).toBeNull()
|
|
304
|
+
})
|
|
305
|
+
|
|
306
|
+
// ---------- STABILITY CHECKS ----------
|
|
307
|
+
|
|
308
|
+
it('returns consistent next/previous cursors across calls', async () => {
|
|
309
|
+
await seedDocs(6)
|
|
310
|
+
|
|
311
|
+
const firstPage = await paginateCursor(collection, {
|
|
312
|
+
limit: 3,
|
|
313
|
+
order: 'asc',
|
|
314
|
+
cursorField: 'createdAt',
|
|
315
|
+
})
|
|
316
|
+
|
|
317
|
+
const secondPage = await paginateCursor(collection, {
|
|
318
|
+
limit: 3,
|
|
319
|
+
order: 'asc',
|
|
320
|
+
cursorField: 'createdAt',
|
|
321
|
+
cursor: firstPage.next,
|
|
322
|
+
})
|
|
323
|
+
console.log(secondPage)
|
|
324
|
+
const firstPageLast = firstPage.list[firstPage.list.length - 1].createdAt
|
|
325
|
+
const secondPageFirst = secondPage.list[0].createdAt
|
|
326
|
+
|
|
327
|
+
expect(secondPageFirst.getTime()).toBeGreaterThan(firstPageLast.getTime())
|
|
328
|
+
expect(secondPage.previous).toEqual(secondPage.list[0].createdAt)
|
|
329
|
+
expect(secondPage.next).toBeNull()
|
|
330
|
+
})
|
|
331
|
+
|
|
332
|
+
it('totalCount remains constant across pages', async () => {
|
|
333
|
+
await seedDocs(8)
|
|
334
|
+
|
|
335
|
+
const firstPage = await paginateCursor(collection, {
|
|
336
|
+
limit: 3,
|
|
337
|
+
order: 'asc',
|
|
338
|
+
cursorField: 'createdAt',
|
|
339
|
+
})
|
|
340
|
+
|
|
341
|
+
const secondPage = await paginateCursor(collection, {
|
|
342
|
+
limit: 3,
|
|
343
|
+
order: 'asc',
|
|
344
|
+
cursorField: 'createdAt',
|
|
345
|
+
cursor: firstPage.next,
|
|
346
|
+
})
|
|
347
|
+
|
|
348
|
+
expect(firstPage.totalCount).toBe(8)
|
|
349
|
+
expect(secondPage.totalCount).toBe(8)
|
|
350
|
+
})
|
|
351
|
+
})
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
import { describe, it, expect, beforeAll, afterAll, beforeEach } from 'vitest'
|
|
2
|
-
|
|
3
2
|
import { paginate } from '../../src/mongodb/paginate.js'
|
|
4
3
|
import { startMongo, stopMongo } from '../resources/docker-mongo-test.js'
|
|
5
4
|
import { initializeMongoDb } from '../../src/mongodb/initialize-mongodb.js'
|
|
6
5
|
|
|
7
|
-
const MONGO_PORT =
|
|
6
|
+
const MONGO_PORT = 29051
|
|
8
7
|
const CONTAINER_NAME = 'mongo-auth-attempts-paginate-test'
|
|
9
8
|
|
|
10
9
|
let db
|
|
11
10
|
let collection
|
|
11
|
+
|
|
12
12
|
describe('paginate - Integration', () => {
|
|
13
13
|
beforeAll(async () => {
|
|
14
|
-
startMongo(MONGO_PORT, CONTAINER_NAME)
|
|
14
|
+
await startMongo(MONGO_PORT, CONTAINER_NAME)
|
|
15
15
|
|
|
16
16
|
db = await initializeMongoDb({
|
|
17
17
|
config: {
|
|
@@ -19,21 +19,22 @@ describe('paginate - Integration', () => {
|
|
|
19
19
|
options: { dbName: 'users-management' },
|
|
20
20
|
},
|
|
21
21
|
collectionNames: {
|
|
22
|
-
TestDocs: '
|
|
22
|
+
TestDocs: 'test_docs_page',
|
|
23
23
|
},
|
|
24
24
|
})
|
|
25
25
|
|
|
26
26
|
collection = db.TestDocs
|
|
27
27
|
})
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
|
|
29
|
+
afterAll(async () => {
|
|
30
|
+
await stopMongo(CONTAINER_NAME)
|
|
30
31
|
})
|
|
31
32
|
|
|
32
33
|
beforeEach(async () => {
|
|
33
34
|
await collection.deleteMany({})
|
|
34
35
|
})
|
|
35
36
|
|
|
36
|
-
const seedDocs = async (count =
|
|
37
|
+
const seedDocs = async (count = 25) => {
|
|
37
38
|
const docs = Array.from({ length: count }).map((_, i) => ({
|
|
38
39
|
name: `doc_${i}`,
|
|
39
40
|
createdAt: new Date(Date.now() + i * 1000),
|
|
@@ -42,110 +43,152 @@ describe('paginate - Integration', () => {
|
|
|
42
43
|
return docs
|
|
43
44
|
}
|
|
44
45
|
|
|
45
|
-
it('returns first page with
|
|
46
|
-
await seedDocs(
|
|
46
|
+
it('returns first page with correct size and metadata', async () => {
|
|
47
|
+
await seedDocs(15)
|
|
47
48
|
|
|
48
49
|
const result = await paginate(collection, {
|
|
49
|
-
filter: {},
|
|
50
50
|
limit: 5,
|
|
51
|
-
|
|
51
|
+
page: 1,
|
|
52
|
+
order: 'asc',
|
|
52
53
|
cursorField: 'createdAt',
|
|
53
54
|
})
|
|
54
55
|
|
|
55
56
|
expect(result.list).toHaveLength(5)
|
|
56
|
-
expect(result.
|
|
57
|
-
expect(result.
|
|
57
|
+
expect(result.currentPage).toBe(1)
|
|
58
|
+
expect(result.totalPages).toBe(3)
|
|
59
|
+
expect(result.hasNext).toBe(true)
|
|
60
|
+
expect(result.hasPrevious).toBe(false)
|
|
58
61
|
})
|
|
59
62
|
|
|
60
|
-
it('returns
|
|
63
|
+
it('returns middle page correctly', async () => {
|
|
64
|
+
await seedDocs(15)
|
|
65
|
+
|
|
61
66
|
const result = await paginate(collection, {
|
|
62
|
-
filter: { name: 'not-exist' },
|
|
63
67
|
limit: 5,
|
|
64
|
-
|
|
68
|
+
page: 2,
|
|
69
|
+
order: 'asc',
|
|
70
|
+
cursorField: 'createdAt',
|
|
65
71
|
})
|
|
66
72
|
|
|
67
|
-
expect(result.list).toHaveLength(
|
|
68
|
-
expect(result.
|
|
73
|
+
expect(result.list).toHaveLength(5)
|
|
74
|
+
expect(result.currentPage).toBe(2)
|
|
75
|
+
expect(result.hasNext).toBe(true)
|
|
76
|
+
expect(result.hasPrevious).toBe(true)
|
|
69
77
|
})
|
|
70
78
|
|
|
71
|
-
it('
|
|
72
|
-
|
|
79
|
+
it('returns last page correctly', async () => {
|
|
80
|
+
await seedDocs(15)
|
|
73
81
|
|
|
74
|
-
const
|
|
75
|
-
|
|
76
|
-
|
|
82
|
+
const result = await paginate(collection, {
|
|
83
|
+
limit: 5,
|
|
84
|
+
page: 3,
|
|
85
|
+
order: 'asc',
|
|
86
|
+
cursorField: 'createdAt',
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
expect(result.list).toHaveLength(5)
|
|
90
|
+
expect(result.hasNext).toBe(false)
|
|
91
|
+
expect(result.hasPrevious).toBe(true)
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
it('returns empty list for page out of range', async () => {
|
|
95
|
+
await seedDocs(10)
|
|
96
|
+
|
|
97
|
+
const result = await paginate(collection, {
|
|
98
|
+
limit: 5,
|
|
99
|
+
page: 5,
|
|
77
100
|
order: 'asc',
|
|
78
101
|
cursorField: 'createdAt',
|
|
79
102
|
})
|
|
80
103
|
|
|
81
|
-
expect(
|
|
104
|
+
expect(result.list).toHaveLength(0)
|
|
105
|
+
expect(result.hasNext).toBe(false)
|
|
106
|
+
expect(result.hasPrevious).toBe(true)
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
it('returns totalCount consistent across pages', async () => {
|
|
110
|
+
await seedDocs(9)
|
|
111
|
+
|
|
112
|
+
const page1 = await paginate(collection, {
|
|
113
|
+
limit: 3,
|
|
114
|
+
page: 1,
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
const page2 = await paginate(collection, {
|
|
118
|
+
limit: 3,
|
|
119
|
+
page: 2,
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
expect(page1.totalCount).toBe(9)
|
|
123
|
+
expect(page2.totalCount).toBe(9)
|
|
82
124
|
})
|
|
83
125
|
|
|
84
126
|
it('supports descending order', async () => {
|
|
85
|
-
const docs = await seedDocs(
|
|
127
|
+
const docs = await seedDocs(6)
|
|
86
128
|
|
|
87
|
-
const
|
|
88
|
-
filter: {},
|
|
129
|
+
const result = await paginate(collection, {
|
|
89
130
|
limit: 3,
|
|
131
|
+
page: 1,
|
|
90
132
|
order: 'desc',
|
|
91
133
|
cursorField: 'createdAt',
|
|
92
134
|
})
|
|
93
135
|
|
|
94
|
-
expect(
|
|
136
|
+
expect(result.list[0].name).toBe(docs[5].name)
|
|
137
|
+
expect(result.list[2].name).toBe(docs[3].name)
|
|
95
138
|
})
|
|
96
139
|
|
|
97
|
-
it('
|
|
98
|
-
await seedDocs(
|
|
140
|
+
it('supports ascending order', async () => {
|
|
141
|
+
const docs = await seedDocs(6)
|
|
99
142
|
|
|
100
|
-
|
|
101
|
-
const firstPage = await paginate(collection, {
|
|
102
|
-
filter: {},
|
|
143
|
+
const result = await paginate(collection, {
|
|
103
144
|
limit: 3,
|
|
145
|
+
page: 1,
|
|
104
146
|
order: 'asc',
|
|
105
147
|
cursorField: 'createdAt',
|
|
106
148
|
})
|
|
107
149
|
|
|
108
|
-
expect(
|
|
109
|
-
expect(
|
|
150
|
+
expect(result.list[0].name).toBe(docs[0].name)
|
|
151
|
+
expect(result.list[2].name).toBe(docs[2].name)
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
it('returns no documents when filter excludes all', async () => {
|
|
155
|
+
await seedDocs(5)
|
|
110
156
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
filter: {},
|
|
157
|
+
const result = await paginate(collection, {
|
|
158
|
+
filter: { name: 'not_existing' },
|
|
114
159
|
limit: 3,
|
|
115
|
-
|
|
116
|
-
cursorField: 'createdAt',
|
|
117
|
-
cursor: firstPage.next,
|
|
160
|
+
page: 1,
|
|
118
161
|
})
|
|
119
162
|
|
|
120
|
-
expect(
|
|
121
|
-
|
|
122
|
-
expect(
|
|
123
|
-
firstPage.list[0]._id.toString(),
|
|
124
|
-
)
|
|
125
|
-
expect(secondPage.list.map((d) => d._id.toString())).not.toEqual(
|
|
126
|
-
firstPage.list.map((d) => d._id.toString()),
|
|
127
|
-
)
|
|
163
|
+
expect(result.list).toHaveLength(0)
|
|
164
|
+
expect(result.totalCount).toBe(0)
|
|
165
|
+
expect(result.totalPages).toBe(0)
|
|
128
166
|
})
|
|
129
167
|
|
|
130
|
-
it('paginates
|
|
131
|
-
const docs = await seedDocs(
|
|
168
|
+
it('paginates deterministically across pages', async () => {
|
|
169
|
+
const docs = await seedDocs(9)
|
|
132
170
|
|
|
133
|
-
|
|
134
|
-
|
|
171
|
+
const page1 = await paginate(collection, {
|
|
172
|
+
limit: 3,
|
|
173
|
+
page: 1,
|
|
174
|
+
order: 'asc',
|
|
175
|
+
cursorField: 'createdAt',
|
|
176
|
+
})
|
|
135
177
|
|
|
136
|
-
const
|
|
137
|
-
|
|
138
|
-
|
|
178
|
+
const page2 = await paginate(collection, {
|
|
179
|
+
limit: 3,
|
|
180
|
+
page: 2,
|
|
139
181
|
order: 'asc',
|
|
140
|
-
cursorField: '
|
|
141
|
-
cursor: stringCursor,
|
|
182
|
+
cursorField: 'createdAt',
|
|
142
183
|
})
|
|
143
184
|
|
|
144
|
-
|
|
185
|
+
// ensure continuity: last of page1 == item before first of page2
|
|
186
|
+
const lastOfPage1 = page1.list[page1.list.length - 1].name
|
|
187
|
+
const firstOfPage2 = page2.list[0].name
|
|
145
188
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
expect(
|
|
189
|
+
const indexDiff =
|
|
190
|
+
docs.findIndex((d) => d.name === firstOfPage2) -
|
|
191
|
+
docs.findIndex((d) => d.name === lastOfPage1)
|
|
192
|
+
expect(indexDiff).toBe(1)
|
|
150
193
|
})
|
|
151
194
|
})
|