-
Notifications
You must be signed in to change notification settings - Fork 3.9k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(api-service,dashboard): Implement before after pagination
- Loading branch information
Showing
11 changed files
with
430 additions
and
156 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,76 +6,261 @@ const v2Prefix = '/v2'; | |
let session: UserSession; | ||
|
||
describe('List Subscriber Permutations', () => { | ||
it('should not return subscribers if not matching query', async () => { | ||
it('should not return subscribers if not matching search params', async () => { | ||
await createSubscriberAndValidate('XYZ'); | ||
await createSubscriberAndValidate('XYZ2'); | ||
const subscribers = await getAllAndValidate({ | ||
searchQuery: 'ABC', | ||
searchParams: { email: '[email protected]' }, | ||
expectedTotalResults: 0, | ||
expectedArraySize: 0, | ||
}); | ||
expect(subscribers).to.be.empty; | ||
}); | ||
|
||
it('should not return subscribers if offset is bigger than available subscribers', async () => { | ||
it('should return all results within range', async () => { | ||
const uuid = generateUUID(); | ||
await create10Subscribers(uuid); | ||
await getAllAndValidate({ | ||
searchQuery: uuid, | ||
offset: 11, | ||
limit: 15, | ||
expectedTotalResults: 10, | ||
expectedArraySize: 0, | ||
expectedArraySize: 10, | ||
}); | ||
}); | ||
|
||
it('should return all results within range', async () => { | ||
it('should return results without any search params', async () => { | ||
const uuid = generateUUID(); | ||
await create10Subscribers(uuid); | ||
await getAllAndValidate({ | ||
searchQuery: uuid, | ||
offset: 0, | ||
limit: 15, | ||
expectedTotalResults: 10, | ||
expectedArraySize: 10, | ||
}); | ||
}); | ||
|
||
it('should return results without query', async () => { | ||
it('should page subscribers without overlap using cursors', async () => { | ||
const uuid = generateUUID(); | ||
await create10Subscribers(uuid); | ||
await getAllAndValidate({ | ||
searchQuery: uuid, | ||
offset: 0, | ||
limit: 15, | ||
expectedTotalResults: 10, | ||
expectedArraySize: 10, | ||
|
||
const firstPage = await getListSubscribers({ | ||
limit: 5, | ||
}); | ||
|
||
const secondPage = await getListSubscribers({ | ||
after: firstPage.next, | ||
limit: 5, | ||
}); | ||
|
||
const idsDeduplicated = buildIdSet(firstPage.subscribers, secondPage.subscribers); | ||
expect(idsDeduplicated.size).to.be.equal(10); | ||
}); | ||
}); | ||
|
||
describe('List Subscriber Search Filters', () => { | ||
it('should find subscriber by email', async () => { | ||
const uuid = generateUUID(); | ||
await createSubscriberAndValidate(uuid); | ||
|
||
const subscribers = await getAllAndValidate({ | ||
searchParams: { email: `test-${uuid}@subscriber` }, | ||
expectedTotalResults: 1, | ||
expectedArraySize: 1, | ||
}); | ||
|
||
expect(subscribers[0].email).to.contain(uuid); | ||
}); | ||
|
||
it('should find subscriber by phone', async () => { | ||
const uuid = generateUUID(); | ||
await createSubscriberAndValidate(uuid); | ||
|
||
const subscribers = await getAllAndValidate({ | ||
searchParams: { phone: '1234567' }, | ||
expectedTotalResults: 1, | ||
expectedArraySize: 1, | ||
}); | ||
|
||
expect(subscribers[0].phone).to.equal('+1234567890'); | ||
}); | ||
|
||
it('should find subscriber by full name', async () => { | ||
const uuid = generateUUID(); | ||
await createSubscriberAndValidate(uuid); | ||
|
||
const subscribers = await getAllAndValidate({ | ||
searchParams: { name: `Test ${uuid} Subscriber` }, | ||
expectedTotalResults: 1, | ||
expectedArraySize: 1, | ||
}); | ||
|
||
expect(subscribers[0].firstName).to.equal(`Test ${uuid}`); | ||
expect(subscribers[0].lastName).to.equal('Subscriber'); | ||
}); | ||
|
||
it('should find subscriber by subscriberId', async () => { | ||
const uuid = generateUUID(); | ||
await createSubscriberAndValidate(uuid); | ||
|
||
const subscribers = await getAllAndValidate({ | ||
searchParams: { subscriberId: `test-subscriber-${uuid}` }, | ||
expectedTotalResults: 1, | ||
expectedArraySize: 1, | ||
}); | ||
|
||
expect(subscribers[0].subscriberId).to.equal(`test-subscriber-${uuid}`); | ||
}); | ||
}); | ||
|
||
it('should page subscribers without overlap', async () => { | ||
describe('List Subscriber Cursor Pagination', () => { | ||
it('should paginate forward using after cursor', async () => { | ||
const uuid = generateUUID(); | ||
await create10Subscribers(uuid); | ||
const listResponse1 = await getAllAndValidate({ | ||
searchQuery: uuid, | ||
offset: 0, | ||
|
||
const firstPage = await getListSubscribers({ | ||
limit: 5, | ||
expectedTotalResults: 10, | ||
expectedArraySize: 5, | ||
}); | ||
const listResponse2 = await getAllAndValidate({ | ||
searchQuery: uuid, | ||
offset: 5, | ||
|
||
const secondPage = await getListSubscribers({ | ||
after: firstPage.next, | ||
limit: 5, | ||
expectedTotalResults: 10, | ||
expectedArraySize: 5, | ||
}); | ||
const idsDeduplicated = buildIdSet(listResponse1, listResponse2); | ||
expect(idsDeduplicated.size).to.be.equal(10); | ||
|
||
expect(firstPage.subscribers).to.have.lengthOf(5); | ||
expect(secondPage.subscribers).to.have.lengthOf(5); | ||
expect(firstPage.next).to.exist; | ||
expect(secondPage.previous).to.exist; | ||
|
||
const idsDeduplicated = buildIdSet(firstPage.subscribers, secondPage.subscribers); | ||
expect(idsDeduplicated.size).to.equal(10); | ||
}); | ||
|
||
it('should paginate backward using before cursor', async () => { | ||
const uuid = generateUUID(); | ||
await create10Subscribers(uuid); | ||
|
||
const firstPage = await getListSubscribers({ | ||
limit: 5, | ||
}); | ||
|
||
const secondPage = await getListSubscribers({ | ||
after: firstPage.next, | ||
limit: 5, | ||
}); | ||
|
||
const previousPage = await getListSubscribers({ | ||
before: secondPage.previous, | ||
limit: 5, | ||
}); | ||
|
||
expect(previousPage.subscribers).to.have.lengthOf(5); | ||
expect(previousPage.next).to.exist; | ||
expect(previousPage.subscribers).to.deep.equal(firstPage.subscribers); | ||
}); | ||
|
||
it('should handle pagination with limit=1', async () => { | ||
const uuid = generateUUID(); | ||
await create10Subscribers(uuid); | ||
|
||
const firstPage = await getListSubscribers({ | ||
limit: 1, | ||
}); | ||
|
||
expect(firstPage.subscribers).to.have.lengthOf(1); | ||
expect(firstPage.next).to.exist; | ||
expect(firstPage.previous).to.not.exist; | ||
}); | ||
|
||
it('should return empty array when no more results after cursor', async () => { | ||
const uuid = generateUUID(); | ||
await create10Subscribers(uuid); | ||
|
||
const allResults = await getListSubscribers({ | ||
limit: 10, | ||
}); | ||
|
||
const nextPage = await getListSubscribers({ | ||
after: allResults.next, | ||
limit: 5, | ||
}); | ||
|
||
expect(nextPage.subscribers).to.have.lengthOf(0); | ||
expect(nextPage.next).to.not.exist; | ||
expect(nextPage.previous).to.exist; | ||
}); | ||
}); | ||
|
||
describe('List Subscriber Sorting', () => { | ||
it('should sort subscribers by createdAt in ascending order', async () => { | ||
const uuid = generateUUID(); | ||
await create10Subscribers(uuid); | ||
|
||
const response = await getListSubscribers({ | ||
sortBy: 'createdAt', | ||
sortDirection: 'asc', | ||
limit: 10, | ||
}); | ||
|
||
const timestamps = response.subscribers.map((sub) => new Date(sub.createdAt).getTime()); | ||
const sortedTimestamps = [...timestamps].sort((a, b) => a - b); | ||
expect(timestamps).to.deep.equal(sortedTimestamps); | ||
}); | ||
|
||
it('should sort subscribers by createdAt in descending order', async () => { | ||
const uuid = generateUUID(); | ||
await create10Subscribers(uuid); | ||
|
||
const response = await getListSubscribers({ | ||
sortBy: 'createdAt', | ||
sortDirection: 'desc', | ||
limit: 10, | ||
}); | ||
|
||
const timestamps = response.subscribers.map((sub) => new Date(sub.createdAt).getTime()); | ||
const sortedTimestamps = [...timestamps].sort((a, b) => b - a); | ||
expect(timestamps).to.deep.equal(sortedTimestamps); | ||
}); | ||
|
||
it('should sort subscribers by subscriberId', async () => { | ||
const uuid = generateUUID(); | ||
await create10Subscribers(uuid); | ||
|
||
const response = await getListSubscribers({ | ||
sortBy: 'subscriberId', | ||
sortDirection: 'asc', | ||
limit: 10, | ||
}); | ||
|
||
const ids = response.subscribers.map((sub) => sub.subscriberId); | ||
const sortedIds = [...ids].sort(); | ||
expect(ids).to.deep.equal(sortedIds); | ||
}); | ||
|
||
it('should maintain sort order across pages', async () => { | ||
const uuid = generateUUID(); | ||
await create10Subscribers(uuid); | ||
|
||
const firstPage = await getListSubscribers({ | ||
sortBy: 'createdAt', | ||
sortDirection: 'asc', | ||
limit: 5, | ||
}); | ||
|
||
const secondPage = await getListSubscribers({ | ||
sortBy: 'createdAt', | ||
sortDirection: 'asc', | ||
after: firstPage.next, | ||
limit: 5, | ||
}); | ||
|
||
const allTimestamps = [ | ||
...firstPage.subscribers.map((sub) => new Date(sub.createdAt).getTime()), | ||
...secondPage.subscribers.map((sub) => new Date(sub.createdAt).getTime()), | ||
]; | ||
|
||
const sortedTimestamps = [...allTimestamps].sort((a, b) => a - b); | ||
expect(allTimestamps).to.deep.equal(sortedTimestamps); | ||
}); | ||
}); | ||
|
||
// Helper functions | ||
async function createSubscriberAndValidate(nameSuffix: string = '') { | ||
const createSubscriberDto = { | ||
subscriberId: `test-subscriber-${nameSuffix}`, | ||
|
@@ -100,41 +285,48 @@ async function create10Subscribers(uuid: string) { | |
} | ||
} | ||
|
||
async function getListSubscribers(query: string, offset: number, limit: number) { | ||
const res = await session.testAgent.get(`${v2Prefix}/subscribers`).query({ | ||
query, | ||
page: Math.floor(offset / limit) + 1, | ||
limit, | ||
}); | ||
interface IListSubscribersQuery { | ||
email?: string; | ||
phone?: string; | ||
name?: string; | ||
subscriberId?: string; | ||
after?: string; | ||
before?: string; | ||
limit?: number; | ||
sortBy?: string; | ||
sortDirection?: 'asc' | 'desc'; | ||
} | ||
|
||
async function getListSubscribers(params: IListSubscribersQuery = {}) { | ||
const res = await session.testAgent.get(`${v2Prefix}/subscribers`).query(params); | ||
expect(res.status).to.equal(200); | ||
|
||
return res.body.data; | ||
} | ||
|
||
interface IAllAndValidate { | ||
msgPrefix?: string; | ||
searchQuery: string; | ||
offset?: number; | ||
searchParams?: IListSubscribersQuery; | ||
limit?: number; | ||
expectedTotalResults: number; | ||
expectedArraySize: number; | ||
} | ||
|
||
async function getAllAndValidate({ | ||
msgPrefix = '', | ||
searchQuery = '', | ||
offset = 0, | ||
limit = 50, | ||
searchParams = {}, | ||
limit = 15, | ||
expectedTotalResults, | ||
expectedArraySize, | ||
}: IAllAndValidate) { | ||
const listResponse = await getListSubscribers(searchQuery, offset, limit); | ||
const listResponse = await getListSubscribers({ | ||
...searchParams, | ||
limit, | ||
}); | ||
const summary = buildLogMsg( | ||
{ | ||
msgPrefix, | ||
searchQuery, | ||
offset, | ||
limit, | ||
searchParams, | ||
expectedTotalResults, | ||
expectedArraySize, | ||
}, | ||
|
@@ -143,16 +335,13 @@ async function getAllAndValidate({ | |
|
||
expect(listResponse.subscribers).to.be.an('array', summary); | ||
expect(listResponse.subscribers).lengthOf(expectedArraySize, `subscribers length ${summary}`); | ||
expect(listResponse.totalCount).to.be.equal(expectedTotalResults, `total Results don't match ${summary}`); | ||
|
||
return listResponse.subscribers; | ||
} | ||
|
||
function buildLogMsg(params: IAllAndValidate, listResponse: any): string { | ||
return `Log - msgPrefix: ${params.msgPrefix}, | ||
searchQuery: ${params.searchQuery}, | ||
offset: ${params.offset}, | ||
limit: ${params.limit}, | ||
searchParams: ${JSON.stringify(params.searchParams || 'Not specified', null, 2)}, | ||
expectedTotalResults: ${params.expectedTotalResults ?? 'Not specified'}, | ||
expectedArraySize: ${params.expectedArraySize ?? 'Not specified'} | ||
response: | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.