/*! * @license * Copyright Squiz Australia Pty Ltd. All Rights Reserved. */ import { AttributeValue } from '@aws-sdk/client-dynamodb'; import { faker } from '@faker-js/faker/locale/en'; import { EntityType, ENTITY_TYPES } from '../../../constants/Repository.constants'; import { PaginationInfo } from '../model/PaginationInfo'; import { PAGINATION_DIRECTION_TYPE_AFTER, PAGINATION_DIRECTION_TYPE_BEFORE } from '../model/PaginationTypes'; import { encodeTokenString } from '../utils/PaginationUtils'; import { PaginationService } from './PaginationService'; describe('PaginationService', (): void => { let paginationService: PaginationService; const mockConvertToDynamoDbItem = jest.fn(); beforeAll((): void => { paginationService = new PaginationService(); }); describe('addPaginationLinksToResponse', (): void => { let mockType: EntityType; let mockData; let mockPaginationInfo: PaginationInfo; let mockExclusiveStartKey: Record; const mockLastEvaluatedKey: string | null = null; beforeEach(() => { mockType = ENTITY_TYPES.job; mockPaginationInfo = { direction: PAGINATION_DIRECTION_TYPE_AFTER, limit: 20, requestUrl: 'mockUrl', token: 'mockToken', }; mockExclusiveStartKey = { pk: { S: 'mockPk' }, sk: { S: 'mockSk' }, }; }); it('should return a full page of items including links', (): void => { mockData = Array.from({ length: faker.number.int({ min: 1, max: mockPaginationInfo.limit - 1 }) }).map( (_, i) => ({ type: mockType, pk: `mockPk${i}`, sk: `mockSk${i}`, }), ); mockConvertToDynamoDbItem .mockImplementationOnce(() => { return mockExclusiveStartKey; }) .mockImplementationOnce(() => { return mockExclusiveStartKey; }); const response = paginationService.addPaginationLinksToResponse( mockData, mockPaginationInfo, mockPaginationInfo.token, mockConvertToDynamoDbItem, ); expect(mockConvertToDynamoDbItem).toBeCalledTimes(1); expect(response.data).toStrictEqual(mockData); expect(response.links).toBeTruthy(); expect(response.links.prev).toContain(encodeURIComponent(encodeTokenString(mockExclusiveStartKey))); expect(response.links.next).toContain(mockPaginationInfo.token); }); it('should return a page of items with `null` links when the data is less than the limit', (): void => { mockData = Array.from({ length: faker.number.int({ min: 1, max: mockPaginationInfo.limit - 1, }), }).map((_, i) => ({ type: mockType, i })); mockConvertToDynamoDbItem.mockImplementationOnce(() => mockExclusiveStartKey); const response = paginationService.addPaginationLinksToResponse( mockData, { ...mockPaginationInfo, token: null }, null, mockConvertToDynamoDbItem, ); expect(mockConvertToDynamoDbItem).not.toBeCalled(); expect(response.data).toStrictEqual(mockData); expect(response.links).toBeTruthy(); expect(response.links.prev).toBeNull(); expect(response.links.next).toBeNull(); }); it('should return a page of items with non-null previous links when the data is less than the limit but request included a token', (): void => { mockData = Array.from({ length: faker.number.int({ min: 1, max: mockPaginationInfo.limit - 1, }), }).map((_, i) => ({ type: mockType, i })); mockConvertToDynamoDbItem.mockImplementationOnce(() => mockExclusiveStartKey); const response = paginationService.addPaginationLinksToResponse( mockData, mockPaginationInfo, mockLastEvaluatedKey, mockConvertToDynamoDbItem, ); expect(mockConvertToDynamoDbItem).toBeCalledTimes(1); expect(response.data).toStrictEqual(mockData); expect(response.links).toBeTruthy(); expect(response.links.prev).toBeTruthy(); expect(response.links.next).toBeNull(); }); it('should reverse the data appropriately when paginating in reverse', (): void => { mockData = Array.from({ length: faker.number.int({ min: 1, max: mockPaginationInfo.limit - 1, }), }).map((_, i) => ({ type: mockType, i })); mockConvertToDynamoDbItem.mockImplementationOnce(() => mockExclusiveStartKey); const response = paginationService.addPaginationLinksToResponse( mockData, { ...mockPaginationInfo, direction: PAGINATION_DIRECTION_TYPE_BEFORE }, mockLastEvaluatedKey, mockConvertToDynamoDbItem, ); expect(mockConvertToDynamoDbItem).toBeCalledTimes(1); expect(response.data).toStrictEqual(mockData.reverse()); expect(response.links).toBeTruthy(); }); it('should only return the limited number of items even if DB contains more', (): void => { mockData = Array.from({ length: faker.number.int({ min: mockPaginationInfo.limit + 1, max: mockPaginationInfo.limit * 2, }), }).map((_, i) => ({ type: mockType, i })); mockConvertToDynamoDbItem .mockImplementationOnce(() => mockExclusiveStartKey) .mockImplementationOnce(() => mockExclusiveStartKey); const response = paginationService.addPaginationLinksToResponse( mockData, mockPaginationInfo, mockLastEvaluatedKey, mockConvertToDynamoDbItem, ); expect(mockConvertToDynamoDbItem).toBeCalledTimes(2); expect(response.data).toHaveLength(mockPaginationInfo.limit); expect(response.links).toBeTruthy(); }); }); });