Skip to content

Commit ecf79a5

Browse files
Enhance pagination functionality and add comprehensive tests for pagination features
1 parent 9a433ed commit ecf79a5

File tree

4 files changed

+488
-5
lines changed

4 files changed

+488
-5
lines changed

src/lib/server.ts

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,10 @@ export class JsonServer {
172172
page: number = 1,
173173
perPage: number = 10
174174
): Record<string, any> {
175+
// Ensure valid page and perPage values
176+
page = Math.max(1, page);
177+
perPage = Math.max(1, perPage);
178+
175179
const total = collection.length;
176180
const start = (page - 1) * perPage;
177181
const end = Math.min(start + perPage, total);
@@ -400,7 +404,7 @@ export class JsonServer {
400404
if (Object.keys(req.query).length > 0) {
401405
filteredData = resourceData.filter((item) => {
402406
return Object.entries(req.query).every(([key, value]) => {
403-
// Skip special query parameters like _page, _per_page, _sort
407+
// Skip special query parameters like _page, _per_page, _limit
404408
if (key.startsWith('_')) return true;
405409
return String(item[key]) === String(value);
406410
});
@@ -409,14 +413,17 @@ export class JsonServer {
409413

410414
// Handle pagination
411415
const pageParam = req.query._page;
412-
const perPageParam = req.query._per_page;
416+
const perPageParam = req.query._per_page || req.query._limit;
413417

418+
// Check if any pagination parameter is present
414419
if (pageParam !== undefined || perPageParam !== undefined) {
415-
const page = pageParam ? parseInt(pageParam as string) : 1;
416-
const perPage = perPageParam ? parseInt(perPageParam as string) : 10;
420+
// Convert to integers with defaults if parsing fails
421+
const page = pageParam ? Math.max(1, parseInt(pageParam as string) || 1) : 1;
422+
const perPage = perPageParam ? Math.max(1, parseInt(perPageParam as string) || 10) : 10;
417423

418424
// Apply pagination and return the paginated result
419-
return res.json(this.getPaginatedData(filteredData, page, perPage));
425+
const paginatedData = this.getPaginatedData(filteredData, page, perPage);
426+
return res.json(paginatedData);
420427
}
421428

422429
res.json(filteredData);

test/debug-pagination.js

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
const request = require('supertest');
2+
const { JsonServer } = require('../dist/lib/server');
3+
const express = require('express');
4+
5+
// Create a simple test database
6+
const testDb = {
7+
items: Array.from({ length: 100 }, (_, i) => ({
8+
id: String(i + 1),
9+
name: `Item ${i + 1}`
10+
}))
11+
};
12+
13+
// Create server instance
14+
const options = {
15+
port: 3000,
16+
host: 'localhost',
17+
quiet: true,
18+
readOnly: false,
19+
bodyParser: true,
20+
noCors: false
21+
};
22+
23+
const server = new JsonServer(options);
24+
25+
// Mock database loading
26+
server.loadDatabase = () => {
27+
server.db = testDb;
28+
return server;
29+
};
30+
31+
// Load database and start server
32+
server.loadDatabase();
33+
34+
// Get Express app
35+
const app = server.getApp();
36+
server.createResourceRoutes(); // Make this method accessible for testing
37+
38+
async function testPagination() {
39+
console.log('Testing pagination with _page and _per_page parameters');
40+
const response1 = await request(app).get('/items?_page=2&_per_page=10');
41+
console.log('Response with _page and _per_page:');
42+
console.log(`Status: ${response1.status}`);
43+
console.log(`Data length: ${response1.body.data ? response1.body.data.length : 'N/A'}`);
44+
console.log(`First item ID: ${response1.body.data && response1.body.data[0] ? response1.body.data[0].id : 'N/A'}`);
45+
console.log('Full response:', JSON.stringify(response1.body, null, 2));
46+
47+
console.log('\nTesting pagination with only _per_page parameter');
48+
const response2 = await request(app).get('/items?_per_page=10');
49+
console.log('Response with only _per_page:');
50+
console.log(`Status: ${response2.status}`);
51+
console.log(`Data length: ${response2.body.data ? response2.body.data.length : 'N/A'}`);
52+
console.log(`First item ID: ${response2.body.data && response2.body.data[0] ? response2.body.data[0].id : 'N/A'}`);
53+
console.log('Full response:', JSON.stringify(response2.body, null, 2));
54+
}
55+
56+
testPagination().catch(console.error);

test/pagination-comprehensive.test.ts

Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
import { JsonServer } from '../src/lib/server';
2+
import { ServerOptions } from '../src/types';
3+
import request from 'supertest';
4+
import express from 'express';
5+
6+
// Mock the file utilities
7+
jest.mock('../src/utils/utils', () => ({
8+
fileExists: jest.fn(() => true),
9+
loadJsonFile: jest.fn(() => ({
10+
// Create 100 test posts
11+
posts: Array.from({ length: 100 }, (_, i) => ({
12+
id: String(i + 1),
13+
title: `Post ${i + 1}`,
14+
body: `Body of post ${i + 1}`,
15+
authorId: Math.floor(i / 10) + 1,
16+
})),
17+
// Create 10 test users
18+
users: Array.from({ length: 10 }, (_, i) => ({
19+
id: String(i + 1),
20+
name: `User ${i + 1}`,
21+
email: `user${i + 1}@example.com`,
22+
})),
23+
// Empty collection
24+
emptyCollection: [],
25+
// Small collection with just 3 items
26+
smallCollection: Array.from({ length: 3 }, (_, i) => ({
27+
id: String(i + 1),
28+
name: `Item ${i + 1}`,
29+
})),
30+
})),
31+
saveJsonFile: jest.fn(),
32+
parseRoutesFile: jest.fn(),
33+
}));
34+
35+
describe('Comprehensive Pagination Tests', () => {
36+
let app: express.Application;
37+
let server: JsonServer;
38+
const options: ServerOptions = {
39+
port: 3000,
40+
host: 'localhost',
41+
quiet: true,
42+
readOnly: false,
43+
noCors: false,
44+
bodyParser: true,
45+
};
46+
47+
beforeEach(() => {
48+
server = new JsonServer(options);
49+
server.loadDatabase('fake-db.json');
50+
app = server.getApp();
51+
});
52+
53+
// Test standard endpoint pagination with _page and _per_page
54+
describe('Standard Endpoint Pagination', () => {
55+
test('should paginate data with _page=2 and _per_page=10', async () => {
56+
const response = await request(app).get('/posts?_page=2&_per_page=10').expect(200);
57+
58+
// Check if response is properly paginated
59+
expect(response.body).toHaveProperty('data');
60+
expect(response.body.data).toHaveLength(10);
61+
expect(response.body.data[0].id).toBe('11');
62+
expect(response.body.data[9].id).toBe('20');
63+
64+
// Check pagination metadata
65+
expect(response.body).toHaveProperty('first', 1);
66+
expect(response.body).toHaveProperty('prev', 1);
67+
expect(response.body).toHaveProperty('next', 3);
68+
expect(response.body).toHaveProperty('last', 10);
69+
expect(response.body).toHaveProperty('pages', 10);
70+
expect(response.body).toHaveProperty('items', 100);
71+
});
72+
73+
test('should paginate data with only _page parameter', async () => {
74+
const response = await request(app).get('/posts?_page=3').expect(200);
75+
76+
expect(response.body).toHaveProperty('data');
77+
expect(response.body.data).toHaveLength(10);
78+
expect(response.body.data[0].id).toBe('21');
79+
expect(response.body.data[9].id).toBe('30');
80+
});
81+
82+
test('should paginate data with only _per_page parameter', async () => {
83+
const response = await request(app).get('/posts?_per_page=5').expect(200);
84+
85+
expect(response.body).toHaveProperty('data');
86+
expect(response.body.data).toHaveLength(5);
87+
expect(response.body.data[0].id).toBe('1');
88+
expect(response.body.data[4].id).toBe('5');
89+
});
90+
91+
test('should return the first page when _page=1', async () => {
92+
const response = await request(app).get('/posts?_page=1&_per_page=15').expect(200);
93+
94+
expect(response.body).toHaveProperty('data');
95+
expect(response.body.data).toHaveLength(15);
96+
expect(response.body.data[0].id).toBe('1');
97+
expect(response.body.data[14].id).toBe('15');
98+
});
99+
100+
test('should return empty data array for page beyond total', async () => {
101+
const response = await request(app).get('/posts?_page=20&_per_page=10').expect(200);
102+
103+
expect(response.body).toHaveProperty('data');
104+
expect(response.body.data).toHaveLength(0);
105+
});
106+
107+
test('should handle invalid _page parameter', async () => {
108+
const response = await request(app).get('/posts?_page=invalid&_per_page=10').expect(200);
109+
110+
// Should default to page 1
111+
expect(response.body).toHaveProperty('data');
112+
expect(response.body.data).toHaveLength(10);
113+
expect(response.body.data[0].id).toBe('1');
114+
});
115+
116+
test('should handle invalid _per_page parameter', async () => {
117+
const response = await request(app).get('/posts?_page=2&_per_page=invalid').expect(200);
118+
119+
// Should default to 10 items per page
120+
expect(response.body).toHaveProperty('data');
121+
expect(response.body.data).toHaveLength(10);
122+
expect(response.body.data[0].id).toBe('11');
123+
});
124+
});
125+
126+
// Test dedicated paginate endpoint
127+
describe('Dedicated Paginate Endpoint', () => {
128+
test('should paginate data with default parameters', async () => {
129+
const response = await request(app).get('/posts/paginate').expect(200);
130+
131+
expect(response.body).toHaveProperty('data');
132+
expect(response.body.data).toHaveLength(10);
133+
expect(response.body.pagination.currentPage).toBe(1);
134+
expect(response.body.pagination.pageSize).toBe(10);
135+
expect(response.body.pagination.totalItems).toBe(100);
136+
expect(response.body.pagination.totalPages).toBe(10);
137+
expect(response.body.pagination.hasMore).toBe(true);
138+
});
139+
140+
test('should paginate data with custom page and limit', async () => {
141+
const response = await request(app).get('/posts/paginate?_page=3&_limit=5').expect(200);
142+
143+
expect(response.body).toHaveProperty('data');
144+
expect(response.body.data).toHaveLength(5);
145+
expect(response.body.pagination.currentPage).toBe(3);
146+
expect(response.body.pagination.pageSize).toBe(5);
147+
expect(response.body.data[0].id).toBe('11'); // Items 11-15 should be on page 3 with pageSize 5
148+
});
149+
150+
test('should indicate no more pages on last page', async () => {
151+
const response = await request(app).get('/posts/paginate?_page=10&_limit=10').expect(200);
152+
153+
expect(response.body.pagination.hasMore).toBe(false);
154+
});
155+
});
156+
157+
// Test edge cases
158+
describe('Pagination Edge Cases', () => {
159+
test('should handle pagination for empty collections', async () => {
160+
const response = await request(app).get('/emptyCollection?_page=1&_per_page=10').expect(200);
161+
162+
expect(response.body).toHaveProperty('data');
163+
expect(response.body.data).toHaveLength(0);
164+
expect(response.body).toHaveProperty('pages', 1);
165+
expect(response.body).toHaveProperty('items', 0);
166+
});
167+
168+
test('should handle pagination for small collections', async () => {
169+
const response = await request(app).get('/smallCollection?_page=1&_per_page=10').expect(200);
170+
171+
expect(response.body).toHaveProperty('data');
172+
expect(response.body.data).toHaveLength(3); // Collection only has 3 items
173+
expect(response.body).toHaveProperty('pages', 1);
174+
expect(response.body).toHaveProperty('items', 3);
175+
expect(response.body).toHaveProperty('next', null); // No next page
176+
});
177+
178+
test('should handle second page pagination for small collections', async () => {
179+
const response = await request(app).get('/smallCollection?_page=2&_per_page=2').expect(200);
180+
181+
expect(response.body).toHaveProperty('data');
182+
expect(response.body.data).toHaveLength(1); // Only 1 item on second page
183+
expect(response.body).toHaveProperty('pages', 2);
184+
expect(response.body).toHaveProperty('items', 3);
185+
expect(response.body).toHaveProperty('next', null); // No next page
186+
});
187+
});
188+
189+
// Test loading ALL data when no pagination params are provided
190+
test('should return all data when no pagination parameters provided', async () => {
191+
const response = await request(app).get('/posts').expect(200);
192+
193+
// Without pagination, all 100 posts should be returned
194+
expect(Array.isArray(response.body)).toBe(true);
195+
expect(response.body.length).toBe(100);
196+
expect(response.body[0].id).toBe('1');
197+
expect(response.body[99].id).toBe('100');
198+
});
199+
});

0 commit comments

Comments
 (0)