diff --git a/packages/hoppscotch-backend/src/team-request/team-request.service.spec.ts b/packages/hoppscotch-backend/src/team-request/team-request.service.spec.ts index 04a22fa88..3814844d0 100644 --- a/packages/hoppscotch-backend/src/team-request/team-request.service.spec.ts +++ b/packages/hoppscotch-backend/src/team-request/team-request.service.spec.ts @@ -328,15 +328,74 @@ describe('getRequestsInCollection', () => { }); test('resolves with the correct info for the collection id and a valid cursor', async () => { + mockPrisma.teamRequest.findFirst.mockResolvedValue({ + orderIndex: dbTeamRequests[0].orderIndex, + } as DbTeamRequest); mockPrisma.teamRequest.findMany.mockResolvedValue([dbTeamRequests[1]]); - const response = teamRequestService.getRequestsInCollection( + const response = await teamRequestService.getRequestsInCollection( dbTeamRequests[1].collectionID, dbTeamRequests[0].id, 1, ); - expect(response).resolves.toEqual([teamRequests[1]]); + expect(response).toEqual([teamRequests[1]]); + }); + + test('paginates on the orderIndex of the cursor item, scoped to the collection', async () => { + mockPrisma.teamRequest.findFirst.mockResolvedValue({ + orderIndex: dbTeamRequests[0].orderIndex, + } as DbTeamRequest); + mockPrisma.teamRequest.findMany.mockResolvedValue([dbTeamRequests[1]]); + + await teamRequestService.getRequestsInCollection( + teamCollection.id, + dbTeamRequests[0].id, + 1, + ); + + expect(mockPrisma.teamRequest.findFirst).toHaveBeenCalledWith({ + where: { id: dbTeamRequests[0].id, collectionID: teamCollection.id }, + select: { orderIndex: true }, + }); + expect(mockPrisma.teamRequest.findMany).toHaveBeenCalledWith({ + take: 1, + where: { + collectionID: teamCollection.id, + orderIndex: { gt: dbTeamRequests[0].orderIndex }, + }, + orderBy: { orderIndex: 'asc' }, + }); + }); + + test('resolves with an empty array when cursor is provided but cursor item is not found', async () => { + mockPrisma.teamRequest.findFirst.mockResolvedValue(null); + + const result = await teamRequestService.getRequestsInCollection( + 'testcoll', + 'nonexistent-cursor', + 10, + ); + + expect(result).toEqual([]); + expect(mockPrisma.teamRequest.findMany).not.toHaveBeenCalled(); + }); + + test('does not look up a cursor item when no cursor is provided', async () => { + mockPrisma.teamRequest.findMany.mockResolvedValue(dbTeamRequests); + + await teamRequestService.getRequestsInCollection( + teamCollection.id, + null, + 10, + ); + + expect(mockPrisma.teamRequest.findFirst).not.toHaveBeenCalled(); + expect(mockPrisma.teamRequest.findMany).toHaveBeenCalledWith({ + take: 10, + where: { collectionID: teamCollection.id }, + orderBy: { orderIndex: 'asc' }, + }); }); }); diff --git a/packages/hoppscotch-backend/src/team-request/team-request.service.ts b/packages/hoppscotch-backend/src/team-request/team-request.service.ts index 13c0e90d4..0faf18bc5 100644 --- a/packages/hoppscotch-backend/src/team-request/team-request.service.ts +++ b/packages/hoppscotch-backend/src/team-request/team-request.service.ts @@ -226,8 +226,15 @@ export class TeamRequestService { /** * Fetch team requests by Collection ID + * + * Pagination is keyed on `orderIndex` (unique per collection, see the + * `TeamRequest_teamID_collectionID_orderIndex_key` constraint) instead of + * Prisma's `cursor` + `skip`, so a page never depends on the offset of the + * cursor row within the result set. + * * @param collectionID Collection ID to fetch requests in - * @param cursor Cursor for pagination + * @param cursor ID of the last request of the previous page. Must belong to + * `collectionID`; an unknown cursor resolves to an empty page * @param take Take number of requests * @returns */ @@ -236,20 +243,31 @@ export class TeamRequestService { cursor: string, take = 10, ) { + let whereClause: Prisma.TeamRequestWhereInput = { collectionID }; + + if (cursor) { + const cursorItem = await this.prisma.teamRequest.findFirst({ + where: { id: cursor, collectionID }, + select: { orderIndex: true }, + }); + + if (!cursorItem) return []; + + whereClause = { + collectionID, + orderIndex: { gt: cursorItem.orderIndex }, + }; + } + const dbTeamRequests = await this.prisma.teamRequest.findMany({ - cursor: cursor ? { id: cursor } : undefined, - take: take, - skip: cursor ? 1 : 0, - where: { - collectionID: collectionID, - }, + take, + where: whereClause, orderBy: { orderIndex: 'asc', }, }); - const teamRequests = dbTeamRequests.map((tr) => this.cast(tr)); - return teamRequests; + return dbTeamRequests.map((tr) => this.cast(tr)); } /**