From 30758f5bc0f892bf870dc8e2c339c5b14a9a9b28 Mon Sep 17 00:00:00 2001 From: anshul23102 Date: Sat, 23 May 2026 22:42:10 +0530 Subject: [PATCH] fix(backend): validate pagination parameters in views endpoint --- apps/backend/src/__tests__/analytics.test.ts | 40 ++++++++++++++++++++ apps/backend/src/routes/analytics.ts | 2 +- 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/apps/backend/src/__tests__/analytics.test.ts b/apps/backend/src/__tests__/analytics.test.ts index 4f0d07a..67b17e6 100644 --- a/apps/backend/src/__tests__/analytics.test.ts +++ b/apps/backend/src/__tests__/analytics.test.ts @@ -427,6 +427,46 @@ describe( } ); + it( + '200 — clamps non-numeric page to 1', + async () => { + prismaMock.cardView.count.mockResolvedValue(0); + prismaMock.cardView.findMany.mockResolvedValue([]); + + const res = await app.inject({ + method: 'GET', + url: '/api/analytics/views?page=abc', + headers: authHeader(), + }); + + expect(res.statusCode).toBe(200); + expect( + prismaMock.cardView.findMany.mock.calls[0][0] + ).toMatchObject({ skip: 0, take: 20 }); + expect(res.json().meta.page).toBe(1); + } + ); + + it( + '200 — clamps negative page to 1', + async () => { + prismaMock.cardView.count.mockResolvedValue(0); + prismaMock.cardView.findMany.mockResolvedValue([]); + + const res = await app.inject({ + method: 'GET', + url: '/api/analytics/views?page=-5', + headers: authHeader(), + }); + + expect(res.statusCode).toBe(200); + expect( + prismaMock.cardView.findMany.mock.calls[0][0] + ).toMatchObject({ skip: 0, take: 20 }); + expect(res.json().meta.page).toBe(1); + } + ); + it( '401 — rejects unauthenticated request', async () => { diff --git a/apps/backend/src/routes/analytics.ts b/apps/backend/src/routes/analytics.ts index 7f79f8b..4522a49 100644 --- a/apps/backend/src/routes/analytics.ts +++ b/apps/backend/src/routes/analytics.ts @@ -108,7 +108,7 @@ export async function analyticsRoutes( _reply: FastifyReply ) => { const userId = (request.user as any).id; - const page = parseInt(request.query.page || '1', 10); + const page = Math.max(1, parseInt(request.query.page || '1', 10) || 1); const limit = 20; const skip = (page - 1) * limit;