/*! * @license * Copyright Squiz Australia Pty Ltd. All Rights Reserved. */ import { faker } from '@faker-js/faker/locale/en'; import { QUERY } from '../../../constants/QueryParams'; import { BadRequestError } from '../../../errors/BadRequestError'; import { InternalServerError } from '../../../errors/InternalServerError'; import { PAGINATION_DIRECTION_TYPE_AFTER, PAGINATION_DIRECTION_TYPE_BEFORE } from '../model/PaginationTypes'; import { decodeTokenString, encodeTokenString, getFormattedResponse, getPaginationInfo, getPaginationLinks, validateTokenHasCorrectFormat, } from './PaginationUtils'; import { PaginationLinks } from '../model'; const mockToken = 'eyJwayI6eyJTIjoiZm9vIn0sInNrIjp7IlMiOiJiYXIifX0='; const mockPrimaryKey = { pk: { S: 'foo' }, sk: { S: 'bar' }, }; describe('PaginationUtils', () => { describe('decodeTokenString', () => { it('should decode a valid token string', () => { const result = decodeTokenString(mockToken); expect(result).toStrictEqual(mockPrimaryKey); }); it('should throw a BadRequestError if decode fails', () => { const mockBadToken = ''; expect(() => { decodeTokenString(mockBadToken); }).toThrow(BadRequestError); }); }); describe('encodeTokenString', () => { it('should encode a valid key', () => { const result = encodeTokenString(mockPrimaryKey); expect(result).toStrictEqual(mockToken); }); it('should throw a InternalServerError if encode fails', () => { const mockBadKey = undefined; expect(() => { encodeTokenString(mockBadKey); }).toThrow(InternalServerError); }); }); describe('getPaginationInfo', () => { let mockPageQuery: Record; let mockPageSize: number; let mockRequestUrl: string; beforeEach(() => { mockPageSize = faker.helpers.arrayElement([20, 50, 100]); mockRequestUrl = faker.internet.url(); mockPageQuery = { after: mockToken, size: mockPageSize, }; }); it('should handle page[after] and page[size] params', () => { const result = getPaginationInfo(mockRequestUrl, mockPageQuery); expect(result).toStrictEqual({ direction: PAGINATION_DIRECTION_TYPE_AFTER, limit: mockPageSize, requestUrl: mockRequestUrl, token: mockToken, }); }); it('should handle page[before] and page[size] params', () => { mockPageQuery.after = undefined; mockPageQuery.before = mockToken; const result = getPaginationInfo(mockRequestUrl, mockPageQuery); expect(result).toStrictEqual({ direction: PAGINATION_DIRECTION_TYPE_BEFORE, limit: mockPageSize, requestUrl: mockRequestUrl, token: mockToken, }); }); it('should fallback to default limit if page[size] not defined', () => { mockPageQuery.size = undefined; const result = getPaginationInfo(mockRequestUrl, mockPageQuery); expect(result.limit).toBe(100); }); describe('Error handling', () => { it('should throw a BadRequestError if both page[before] and page[after] are present in the request', () => { mockPageQuery.before = mockToken; expect(() => { getPaginationInfo(mockRequestUrl, mockPageQuery); }).toThrow(BadRequestError); }); }); }); describe('getPaginationLinks', () => { let mockRequestUrl: string; let mockNextToken; let mockPrevToken; let mockTokens: PaginationLinks; let mockPageAfterParam: string; let mockPageBeforeParam: string; beforeEach(() => { mockRequestUrl = faker.internet.url(); mockNextToken = 'mockNextToken'; mockPrevToken = 'mockPrevToken'; mockTokens = { next: mockNextToken, prev: mockPrevToken, }; mockPageAfterParam = `${QUERY.PAGE.AFTER}=${mockNextToken}`; mockPageBeforeParam = `${QUERY.PAGE.BEFORE}=${mockPrevToken}`; }); it('should handle a request URL with no other parameters', () => { const result = getPaginationLinks(`${mockRequestUrl}/api`, mockTokens); expect(result).toStrictEqual({ next: `${mockRequestUrl}?${mockPageAfterParam}`, prev: `${mockRequestUrl}?${mockPageBeforeParam}`, }); }); it('should handle a request URL with other query parameters', () => { const mockSizeParam = `${QUERY.PAGE.SIZE}=50`; const result = getPaginationLinks(`${mockRequestUrl}/api?${mockSizeParam}`, mockTokens); expect(result).toStrictEqual({ next: `${mockRequestUrl}?${mockSizeParam}&${mockPageAfterParam}`, prev: `${mockRequestUrl}?${mockSizeParam}&${mockPageBeforeParam}`, }); }); it('should replace page[before] and/or page[after] request params with new tokens', () => { const mockRequestAfterBeforeParams = `${QUERY.PAGE.AFTER}=mockRequestAfter&${QUERY.PAGE.BEFORE}=mockRequestBefore`; const result = getPaginationLinks(`${mockRequestUrl}/api?${mockRequestAfterBeforeParams}`, mockTokens); expect(result).toStrictEqual({ next: `${mockRequestUrl}?${mockPageAfterParam}`, prev: `${mockRequestUrl}?${mockPageBeforeParam}`, }); }); it('should handle null tokens', () => { mockTokens = { next: null, prev: null, }; const mockQueryParam = `${QUERY.PAGE.SIZE}=50`; const result1 = getPaginationLinks(`${mockRequestUrl}/api?${mockQueryParam}`, mockTokens); expect(result1).toStrictEqual({ next: null, prev: null, }); const result2 = getPaginationLinks(`${mockRequestUrl}/api`, mockTokens); expect(result2).toStrictEqual({ next: null, prev: null, }); }); }); describe('validateTokenHasCorrectFormat', () => { it('should not throw error for a valid token', () => { expect(() => { validateTokenHasCorrectFormat(mockToken); }).not.toThrow(); }); it('should not throw error for a `null` token', () => { expect(() => { validateTokenHasCorrectFormat(null); }).not.toThrow(BadRequestError); }); it('should throw a BadRequestError for an invalid token', () => { const badToken1 = 'eyJzayI6InNrIn0='; // missing pk const badToken2 = 'eyJwayI6InBrIn0='; // missing sk expect(() => { validateTokenHasCorrectFormat(badToken1); }).toThrow(BadRequestError); expect(() => { validateTokenHasCorrectFormat(badToken2); }).toThrow(BadRequestError); }); }); describe('getFormattedResponse', () => { let mockNextToken; let mockPrevToken; let mockTokens: PaginationLinks; let mockLength; let mockData: object; beforeEach(() => { mockNextToken = 'mockNextToken'; mockPrevToken = 'mockPrevToken'; mockTokens = { next: mockNextToken, prev: mockPrevToken, }; mockLength = faker.number.int({ min: 1, max: 5 }); mockData = Array.from({ length: mockLength }).reduce((acc: Record, _curr) => { return { ...acc, [faker.lorem.word()]: faker.lorem.words(), }; }, {}); }); it('should add links to the final formatted response', (): void => { const response = getFormattedResponse({ links: mockTokens, data: [mockData], }); expect(response.data[0]).toBe(mockData); expect(response.links).toBe(mockTokens); }); it('should not add links to the final formatted response if not present', (): void => { const response = getFormattedResponse({ links: { next: null, prev: null, }, data: [mockData], }); expect(response.data[0]).toBe(mockData); expect(response).not.toHaveProperty('links'); }); }); });