Skip to content

Commit

Permalink
fix: if the purge api call fails, include the api response body in th…
Browse files Browse the repository at this point in the history
…e thrown error's message (#571)
  • Loading branch information
JakeChampion authored Mar 4, 2025
1 parent bb1fb1c commit e01516d
Show file tree
Hide file tree
Showing 3 changed files with 69 additions and 13 deletions.
1 change: 1 addition & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ module.exports = {
extends: '@netlify/eslint-config-node',
rules: {
'max-statements': 'off',
'max-lines': 'off',
},
overrides: [
...overrides,
Expand Down
50 changes: 46 additions & 4 deletions src/lib/purge_cache.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,11 @@ test('Calls the purge API endpoint and returns `undefined` if the operation was
expect(mockAPI.fulfilled).toBeTruthy()
})

test('Throws if the API response does not have a successful status code', async () => {
test('Throws an error if the API response does not have a successful status code, using the response body as part of the error message', async () => {
if (!hasFetchAPI) {
console.warn('Skipping test requires the fetch API')

return
}

const mockSiteID = '123456789'
Expand All @@ -77,7 +79,7 @@ test('Throws if the API response does not have a successful status code', async
},
headers: { Authorization: `Bearer ${mockToken}` },
method: 'post',
response: new Response(null, { status: 500 }),
response: new Response('site not found', { status: 404 }),
url: `https://api.netlify.com/api/v1/purge`,
})
// eslint-disable-next-line unicorn/consistent-function-scoping
Expand All @@ -90,14 +92,54 @@ test('Throws if the API response does not have a successful status code', async
try {
await invokeLambda(myFunction)

throw new Error('Invocation should have failed')
expect.fail('Invocation should have failed')
} catch (error) {
expect((error as NodeJS.ErrnoException).message).toBe(
'Cache purge API call returned an unexpected status code: 500',
'Cache purge API call was unsuccessful.\nStatus: 404\nBody: site not found',
)
}
})

test('Throws if the API response does not have a successful status code, does not include the response body if it is not text', async () => {
if (!hasFetchAPI) {
console.warn('Skipping test requires the fetch API')

return
}

const mockSiteID = '123456789'
const mockToken = '1q2w3e4r5t6y7u8i9o0p'

process.env.NETLIFY_PURGE_API_TOKEN = mockToken
process.env.SITE_ID = mockSiteID

const mockAPI = new MockFetch().post({
body: (payload: string) => {
const data = JSON.parse(payload)

expect(data.site_id).toBe(mockSiteID)
},
headers: { Authorization: `Bearer ${mockToken}` },
method: 'post',
response: new Response(null, { status: 500 }),
url: `https://api.netlify.com/api/v1/purge`,
})
// eslint-disable-next-line unicorn/consistent-function-scoping
const myFunction = async () => {
await purgeCache()
}

globalThis.fetch = mockAPI.fetcher

try {
await invokeLambda(myFunction)

throw new Error('Invocation should have failed')
} catch (error) {
expect((error as NodeJS.ErrnoException).message).toBe('Cache purge API call was unsuccessful.\nStatus: 500')
}
})

test('Ignores purgeCache if in local dev with no token or site', async () => {
if (!hasFetchAPI) {
console.warn('Skipping test requires the fetch API')
Expand Down
31 changes: 22 additions & 9 deletions src/lib/purge_cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,14 @@ export const purgeCache = async (options: PurgeCacheOptions = {}) => {
)
}

const { siteID } = options as PurgeCacheOptionsWithSiteID
const { siteSlug } = options as PurgeCacheOptionsWithSiteSlug
const { domain } = options as PurgeCacheOptionsWithDomain

if ((siteID && siteSlug) || (siteID && domain) || (siteSlug && domain)) {
throw new Error('Can only pass one of either "siteID", "siteSlug", or "domain"')
}

const payload: PurgeAPIPayload = {
cache_tags: options.tags,
deploy_alias: options.deployAlias,
Expand All @@ -50,22 +58,20 @@ export const purgeCache = async (options: PurgeCacheOptions = {}) => {
return
}

if ('siteSlug' in options) {
payload.site_slug = options.siteSlug
} else if ('domain' in options) {
payload.domain = options.domain
if (siteSlug) {
payload.site_slug = siteSlug
} else if (domain) {
payload.domain = domain
} else {
// The `siteID` from `options` takes precedence over the one from the
// environment.
const siteID = options.siteID || env.SITE_ID
payload.site_id = siteID || env.SITE_ID

if (!siteID) {
if (!payload.site_id) {
throw new Error(
'The Netlify site ID was not found in the execution environment. Please supply it manually using the `siteID` property.',
)
}

payload.site_id = siteID
}

if (!token) {
Expand All @@ -91,6 +97,13 @@ export const purgeCache = async (options: PurgeCacheOptions = {}) => {
})

if (!response.ok) {
throw new Error(`Cache purge API call returned an unexpected status code: ${response.status}`)
let text
try {
text = await response.text()
} catch {}
if (text) {
throw new Error(`Cache purge API call was unsuccessful.\nStatus: ${response.status}\nBody: ${text}`)
}
throw new Error(`Cache purge API call was unsuccessful.\nStatus: ${response.status}`)
}
}

0 comments on commit e01516d

Please sign in to comment.