Skip to content

Commit

Permalink
fix: 404ing list results should return empty iterators, not throw (#189)
Browse files Browse the repository at this point in the history
Co-authored-by: Sean Roberts <[email protected]>
  • Loading branch information
sean-roberts and Sean Roberts authored Aug 7, 2024
1 parent 769c60f commit 1da860d
Show file tree
Hide file tree
Showing 4 changed files with 118 additions and 7 deletions.
54 changes: 54 additions & 0 deletions src/list.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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('<not_found>', { 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('<not_found>', { 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()
})
})
27 changes: 20 additions & 7 deletions src/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
},
}
},
Expand Down
39 changes: 39 additions & 0 deletions src/store_list.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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('<not found>', { 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('<not found>', { 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({
Expand Down
5 changes: 5 additions & 0 deletions src/store_list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,11 @@ const getListIterator = (client: Client, prefix: string): AsyncIterable<ListStor
method: HTTPMethod.GET,
parameters: nextParameters,
})

if (res.status === 404) {
return { done: true, value: undefined }
}

const page = (await res.json()) as ListStoresResponse

if (page.next_cursor) {
Expand Down

0 comments on commit 1da860d

Please sign in to comment.