Skip to content

Commit 65aae95

Browse files
committed
Add more SeamPaginator tests
1 parent aa2b37b commit 65aae95

File tree

2 files changed

+100
-19
lines changed

2 files changed

+100
-19
lines changed

src/lib/seam/connect/seam-paginator.ts

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ interface SeamPaginatorParent {
1010
interface Pagination {
1111
readonly hasNextPage: boolean
1212
readonly nextPageCursor: string | null
13+
readonly nextPageUrl: string | null
1314
}
1415

1516
export class SeamPaginator<
@@ -24,21 +25,37 @@ export class SeamPaginator<
2425
parent: SeamPaginatorParent,
2526
request: SeamHttpRequest<TResponse, TResponseKey>,
2627
) {
28+
if (request.responseKey == null) {
29+
throw new Error(
30+
`The ${request.pathname} endpoint does not support pagination`,
31+
)
32+
}
2733
this.#parent = parent
2834
this.#request = request
2935
}
3036

31-
async first(): Promise<
37+
async firstPage(): Promise<
3238
[EnsureReadonlyArray<TResponse[TResponseKey]>, Pagination]
3339
> {
34-
return await this.fetch()
40+
return await this.#fetch()
3541
}
3642

37-
async fetch(
38-
nextPageCursor?: Pagination['nextPageCursor'],
43+
async nextPage(
44+
nextPageCursor: Pagination['nextPageCursor'],
3945
): Promise<[EnsureReadonlyArray<TResponse[TResponseKey]>, Pagination]> {
46+
if (nextPageCursor == null) {
47+
throw new Error('Cannot get the next page with a null nextPageCursor')
48+
}
49+
50+
return await this.#fetch(nextPageCursor)
51+
}
52+
53+
readonly #fetch = async (
54+
nextPageCursor?: Pagination['nextPageCursor'],
55+
): Promise<[EnsureReadonlyArray<TResponse[TResponseKey]>, Pagination]> => {
4056
const responseKey = this.#request.responseKey
41-
if (typeof responseKey !== 'string') {
57+
58+
if (responseKey == null) {
4259
throw new Error('Cannot paginate a response without a responseKey')
4360
}
4461

@@ -58,6 +75,7 @@ export class SeamPaginator<
5875
: {
5976
hasNextPage: false,
6077
nextPageCursor: null,
78+
nextPageUrl: null,
6179
}
6280
if (!Array.isArray(data)) {
6381
throw new Error('Expected an array response')
@@ -70,10 +88,10 @@ export class SeamPaginator<
7088

7189
async toArray(): Promise<EnsureReadonlyArray<TResponse[TResponseKey]>> {
7290
const items = [] as EnsureMutableArray<TResponse[TResponseKey]>
73-
let [current, pagination] = await this.first()
91+
let [current, pagination] = await this.firstPage()
7492
items.push(...current)
7593
while (pagination.hasNextPage) {
76-
;[current, pagination] = await this.fetch(pagination.nextPageCursor)
94+
;[current, pagination] = await this.nextPage(pagination.nextPageCursor)
7795
items.push(...current)
7896
}
7997
return items as EnsureReadonlyArray<TResponse[TResponseKey]>
@@ -82,12 +100,12 @@ export class SeamPaginator<
82100
async *flatten(): AsyncGenerator<
83101
EnsureReadonlyArray<TResponse[TResponseKey]>
84102
> {
85-
let [current, pagination] = await this.first()
103+
let [current, pagination] = await this.firstPage()
86104
for (const item of current) {
87105
yield item
88106
}
89107
while (pagination.hasNextPage) {
90-
;[current, pagination] = await this.fetch(pagination.nextPageCursor)
108+
;[current, pagination] = await this.#fetch(pagination.nextPageCursor)
91109
for (const item of current) {
92110
yield item
93111
}
@@ -97,10 +115,10 @@ export class SeamPaginator<
97115
async *[Symbol.asyncIterator](): AsyncGenerator<
98116
EnsureReadonlyArray<TResponse[TResponseKey]>
99117
> {
100-
let [current, pagination] = await this.first()
118+
let [current, pagination] = await this.firstPage()
101119
yield current
102120
while (pagination.hasNextPage) {
103-
;[current, pagination] = await this.fetch(pagination.nextPageCursor)
121+
;[current, pagination] = await this.nextPage(pagination.nextPageCursor)
104122
yield current
105123
}
106124
}

test/seam/connect/seam-paginator.test.ts

Lines changed: 71 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,35 +10,98 @@ test('SeamPaginator: creates a SeamPaginator', async (t) => {
1010
t.true(pages instanceof SeamPaginator)
1111
})
1212

13-
test('SeamPaginator: fetches an array of devices', async (t) => {
13+
test('SeamPaginator: cannot paginate a request with an empty response', async (t) => {
1414
const { seed, endpoint } = await getTestServer(t)
1515
const seam = SeamHttp.fromApiKey(seed.seam_apikey1_token, { endpoint })
16-
const pages = seam.createPaginator(seam.devices.list())
1716

17+
// @ts-expect-error Testing validation
18+
t.throws(() => seam.createPaginator(seam.devices.update()), {
19+
message: /does not support pagination/,
20+
})
21+
})
22+
23+
// TODO: Validate the request supports pagination by extending SeamHttpRequest with this knowledge via codegen.
24+
test.failing(
25+
'SeamPaginator: cannot paginate an request that does not return pagination data',
26+
async (t) => {
27+
const { seed, endpoint } = await getTestServer(t)
28+
const seam = SeamHttp.fromApiKey(seed.seam_apikey1_token, { endpoint })
29+
30+
t.throws(() => seam.createPaginator(seam.workspaces.list()), {
31+
message: /does not support pagination/,
32+
})
33+
},
34+
)
35+
36+
test('SeamPaginator: firstPage returns the first page', async (t) => {
37+
const { seed, endpoint } = await getTestServer(t)
38+
const seam = SeamHttp.fromApiKey(seed.seam_apikey1_token, { endpoint })
39+
const pages = seam.createPaginator(seam.devices.list({ limit: 2 }))
40+
const [devices, pagination] = await pages.firstPage()
41+
t.is(devices.length, 2)
42+
t.true(pagination.hasNextPage)
43+
t.truthy(pagination.nextPageCursor)
44+
t.truthy(pagination.nextPageUrl)
45+
})
46+
47+
test('SeamPaginator: nextPage returns the next page', async (t) => {
48+
const { seed, endpoint } = await getTestServer(t)
49+
const seam = SeamHttp.fromApiKey(seed.seam_apikey1_token, { endpoint })
50+
const pages = seam.createPaginator(seam.devices.list({ limit: 2 }))
51+
const [devices, { hasNextPage, nextPageCursor }] = await pages.firstPage()
52+
t.is(devices.length, 2)
53+
t.true(hasNextPage)
54+
const [moreDevices] = await pages.nextPage(nextPageCursor)
55+
t.is(moreDevices.length, 2)
56+
})
57+
58+
test('SeamPaginator: nextPage requires the nextPageCursor', async (t) => {
59+
const { seed, endpoint } = await getTestServer(t)
60+
const seam = SeamHttp.fromApiKey(seed.seam_apikey1_token, { endpoint })
61+
const pages = seam.createPaginator(seam.devices.list({ limit: 2 }))
62+
await t.throwsAsync(async () => await pages.nextPage(null), {
63+
message: /nextPageCursor/,
64+
})
65+
})
66+
67+
test('SeamPaginator: toArray returns an array of devices', async (t) => {
68+
const { seed, endpoint } = await getTestServer(t)
69+
const seam = SeamHttp.fromApiKey(seed.seam_apikey1_token, { endpoint })
70+
const allDevices = await seam.devices.list()
71+
const pages = seam.createPaginator(seam.devices.list({ limit: 1 }))
1872
const devices = await pages.toArray()
1973
t.true(devices.length > 1)
74+
t.is(devices.length, allDevices.length)
2075
})
2176

22-
test('SeamPaginator: flattens an array of devices', async (t) => {
77+
test('SeamPaginator: flatten allows iteration over all devices', async (t) => {
2378
const { seed, endpoint } = await getTestServer(t)
2479
const seam = SeamHttp.fromApiKey(seed.seam_apikey1_token, { endpoint })
25-
const pages = seam.createPaginator(seam.devices.list())
80+
const allDevices = await seam.devices.list()
81+
const pages = seam.createPaginator(seam.devices.list({ limit: 1 }))
2682

2783
const devices = []
2884
for await (const device of pages.flatten()) {
2985
devices.push(device)
3086
}
3187
t.true(devices.length > 1)
88+
t.is(devices.length, allDevices.length)
3289
})
3390

34-
test('SeamPaginator: Fetches an array of pages', async (t) => {
91+
test('SeamPaginator: instance allows iteration over all pages', async (t) => {
3592
const { seed, endpoint } = await getTestServer(t)
3693
const seam = SeamHttp.fromApiKey(seed.seam_apikey1_token, { endpoint })
37-
const pages = seam.createPaginator(seam.devices.list())
94+
const allDevices = await seam.devices.list()
95+
const pages = seam.createPaginator(seam.devices.list({ limit: 1 }))
3896

3997
const devices = []
98+
const allPages = []
4099
for await (const page of pages) {
41-
devices.push(page)
100+
t.is(page.length, 1)
101+
allPages.push(page)
102+
devices.push(...page)
42103
}
43-
t.true(devices.length > 0)
104+
t.true(allPages.length > 1)
105+
t.true(devices.length > 1)
106+
t.is(devices.length, allDevices.length)
44107
})

0 commit comments

Comments
 (0)