diff --git a/src/list.test.ts b/src/list.test.ts index ddc4519..aedf6cb 100644 --- a/src/list.test.ts +++ b/src/list.test.ts @@ -762,4 +762,58 @@ describe('list', () => { expect(directories).toEqual([]) expect(mockStore.fulfilled).toBeTruthy() }) + + test('Handles missing content automatic pagination', async () => { + const mockStore = new MockFetch().get({ + headers: { authorization: `Bearer ${edgeToken}` }, + response: new Response('', { status: 404 }), + url: `${edgeURL}/${siteID}/site:${storeName}?prefix=group%2F`, + }) + + globalThis.fetch = mockStore.fetch + + const store = getStore({ + edgeURL, + name: storeName, + token: edgeToken, + siteID, + }) + + const { blobs } = await store.list({ + prefix: 'group/', + }) + + expect(blobs).toEqual([]) + expect(mockStore.fulfilled).toBeTruthy() + }) + + test('Handles missing content manual pagination', async () => { + const mockStore = new MockFetch().get({ + headers: { authorization: `Bearer ${edgeToken}` }, + response: new Response('', { status: 404 }), + url: `${edgeURL}/${siteID}/site:${storeName}`, + }) + + globalThis.fetch = mockStore.fetch + + const store = getStore({ + edgeURL, + name: storeName, + token: edgeToken, + siteID, + }) + const result: ListResult = { + blobs: [], + directories: [], + } + + for await (const entry of store.list({ paginate: true })) { + result.blobs.push(...entry.blobs) + result.directories.push(...entry.directories) + } + + expect(result.blobs).toEqual([]) + expect(result.directories).toEqual([]) + expect(mockStore.fulfilled).toBeTruthy() + }) }) diff --git a/src/store.ts b/src/store.ts index f6c99cb..8fb9a0c 100644 --- a/src/store.ts +++ b/src/store.ts @@ -409,21 +409,34 @@ export class Store { parameters: nextParameters, storeName, }) - const page = (await res.json()) as ListResponse - if (page.next_cursor) { - currentCursor = page.next_cursor - } else { - done = true + let blobs: ListResponseBlob[] = [] + let directories: string[] = [] + + if (![200, 204, 404].includes(res.status)) { + throw new BlobsInternalError(res) } - const blobs = (page.blobs ?? []).map(Store.formatListResultBlob).filter(Boolean) as ListResponseBlob[] + if (res.status === 404) { + done = true + } else { + const page = (await res.json()) as ListResponse + + if (page.next_cursor) { + currentCursor = page.next_cursor + } else { + done = true + } + + blobs = (page.blobs ?? []).map(Store.formatListResultBlob).filter(Boolean) as ListResponseBlob[] + directories = page.directories ?? [] + } return { done: false, value: { blobs, - directories: page.directories ?? [], + directories, }, } }, diff --git a/src/store_list.test.ts b/src/store_list.test.ts index 256eedc..990afa9 100644 --- a/src/store_list.test.ts +++ b/src/store_list.test.ts @@ -136,6 +136,45 @@ describe('listStores', () => { }) }) + test('Handles missing content for auto pagination', async () => { + const mockStore = new MockFetch().get({ + headers: { authorization: `Bearer ${apiToken}` }, + response: new Response('', { status: 404 }), + url: `https://api.netlify.com/api/v1/blobs/${siteID}?prefix=site%3A`, + }) + + globalThis.fetch = mockStore.fetch + + const { stores } = await listStores({ + token: apiToken, + siteID, + }) + + expect(stores).toStrictEqual([]) + expect(mockStore.fulfilled).toBeTruthy() + }) + + test('Handles missing content with manual pagination', async () => { + const mockStore = new MockFetch().get({ + headers: { authorization: `Bearer ${apiToken}` }, + response: new Response('', { status: 404 }), + url: `https://api.netlify.com/api/v1/blobs/${siteID}?prefix=site%3A`, + }) + + globalThis.fetch = mockStore.fetch + + const result: ListStoresResponse = { + stores: [], + } + + for await (const entry of listStores({ token: apiToken, siteID, paginate: true })) { + result.stores.push(...entry.stores) + } + + expect(result.stores).toStrictEqual([]) + expect(mockStore.fulfilled).toBeTruthy() + }) + describe('With edge credentials', () => { test('Lists site stores', async () => { const mockStore = new MockFetch().get({ diff --git a/src/store_list.ts b/src/store_list.ts index 2bbd70f..c582c45 100644 --- a/src/store_list.ts +++ b/src/store_list.ts @@ -56,6 +56,11 @@ const getListIterator = (client: Client, prefix: string): AsyncIterable