Skip to content

Commit

Permalink
fix(gatsby-source-contentful): Improve network error handling (#30257) (
Browse files Browse the repository at this point in the history
#30617)

Co-authored-by: Ward Peeters <ward@coding-tech.com>
(cherry picked from commit c1ac5e4)

Co-authored-by: Benedikt Rötsch <axe312ger@users.noreply.github.com>
  • Loading branch information
GatsbyJS Bot and axe312ger committed Apr 2, 2021
1 parent 0eac672 commit b3315a0
Show file tree
Hide file tree
Showing 6 changed files with 361 additions and 59 deletions.
6 changes: 6 additions & 0 deletions packages/gatsby-source-contentful/README.md
Expand Up @@ -181,6 +181,12 @@ Number of entries to retrieve from Contentful at a time. Due to some technical l

Number of workers to use when downloading Contentful assets. Due to technical limitations, opening too many concurrent requests can cause stalled downloads. If you encounter this issue you can set this param to a lower number than 50, e.g 25.

**`contentfulClientConfig`** [object][optional] [default: `{}`]

Additional config which will get passed to [Contentfuls JS SDK](https://github.com/contentful/contentful.js#configuration).

Use this with caution, you might override values this plugin does set for you to connect to Contentful.

## Notes on Contentful Content Models

There are currently some things to keep in mind when building your content models at Contentful.
Expand Down
3 changes: 2 additions & 1 deletion packages/gatsby-source-contentful/package.json
Expand Up @@ -31,7 +31,8 @@
"@babel/cli": "^7.12.1",
"@babel/core": "^7.12.3",
"babel-preset-gatsby-package": "^0.12.0",
"cross-env": "^7.0.3"
"cross-env": "^7.0.3",
"nock": "^13.0.6"
},
"homepage": "https://github.com/gatsbyjs/gatsby/tree/master/packages/gatsby-source-contentful#readme",
"keywords": [
Expand Down
@@ -0,0 +1,219 @@
/**
* @jest-environment node
*/

import nock from "nock"
import fetchData from "../fetch"
import { createPluginConfig } from "../plugin-options"

nock.disableNetConnect()

const host = `localhost`
const options = {
spaceId: `12345`,
accessToken: `67890`,
host,
contentfulClientConfig: {
retryLimit: 2,
},
}
const baseURI = `https://${host}`

const start = jest.fn()
const end = jest.fn()
const mockActivity = {
start,
end,
tick: jest.fn(),
done: end,
}

const reporter = {
info: jest.fn(),
verbose: jest.fn(),
panic: jest.fn(e => {
throw e
}),
activityTimer: jest.fn(() => mockActivity),
createProgress: jest.fn(() => mockActivity),
}

const pluginConfig = createPluginConfig(options)

describe(`fetch-retry`, () => {
afterEach(() => {
nock.cleanAll()
reporter.verbose.mockClear()
reporter.panic.mockClear()
})

test(`request retries when network timeout happens`, async () => {
const scope = nock(baseURI)
// Space
.get(`/spaces/${options.spaceId}/`)
.reply(200, { items: [] })
// Locales
.get(`/spaces/${options.spaceId}/environments/master/locales`)
.reply(200, { items: [{ code: `en`, default: true }] })
// Sync
.get(
`/spaces/${options.spaceId}/environments/master/sync?initial=true&limit=100`
)
.times(1)
.replyWithError({ code: `ETIMEDOUT` })
.get(
`/spaces/${options.spaceId}/environments/master/sync?initial=true&limit=100`
)
.reply(200, { items: [] })
// Content types
.get(
`/spaces/${options.spaceId}/environments/master/content_types?skip=0&limit=100&order=sys.createdAt`
)
.reply(200, { items: [] })

await fetchData({ pluginConfig, reporter })

expect(reporter.panic).not.toBeCalled()
expect(scope.isDone()).toBeTruthy()
})

test(`request should fail after to many retries`, async () => {
// Due to the retries, this can take up to 10 seconds
jest.setTimeout(10000)

const scope = nock(baseURI)
// Space
.get(`/spaces/${options.spaceId}/`)
.reply(200, { items: [] })
// Locales
.get(`/spaces/${options.spaceId}/environments/master/locales`)
.reply(200, { items: [{ code: `en`, default: true }] })
// Sync
.get(
`/spaces/${options.spaceId}/environments/master/sync?initial=true&limit=100`
)
.times(3)
.reply(
500,
{
sys: {
type: `Error`,
id: `MockedContentfulError`,
},
message: `Mocked message of Contentful error`,
},
{ [`x-contentful-request-id`]: `123abc` }
)

try {
await fetchData({ pluginConfig, reporter })
jest.fail()
} catch (e) {
const msg = expect(e.context.sourceMessage)
msg.toEqual(
expect.stringContaining(
`Fetching contentful data failed: 500 MockedContentfulError`
)
)
msg.toEqual(expect.stringContaining(`Request ID: 123abc`))
msg.toEqual(
expect.stringContaining(`The request was sent with 3 attempts`)
)
}
expect(reporter.panic).toBeCalled()
expect(scope.isDone()).toBeTruthy()
})
})

describe(`fetch-network-errors`, () => {
test(`catches plain network error`, async () => {
const scope = nock(baseURI)
// Space
.get(`/spaces/${options.spaceId}/`)
.replyWithError({ code: `ECONNRESET` })
try {
await fetchData({
pluginConfig: createPluginConfig({
...options,
contentfulClientConfig: { retryOnError: false },
}),
reporter,
})
jest.fail()
} catch (e) {
expect(e.context.sourceMessage).toEqual(
expect.stringContaining(
`Accessing your Contentful space failed: ECONNRESET`
)
)
}

expect(reporter.panic).toBeCalled()
expect(scope.isDone()).toBeTruthy()
})

test(`catches error with response string`, async () => {
const scope = nock(baseURI)
// Space
.get(`/spaces/${options.spaceId}/`)
.reply(502, `Bad Gateway`)

try {
await fetchData({
pluginConfig: createPluginConfig({
...options,
contentfulClientConfig: { retryOnError: false },
}),
reporter,
})
jest.fail()
} catch (e) {
expect(e.context.sourceMessage).toEqual(
expect.stringContaining(
`Accessing your Contentful space failed: Bad Gateway`
)
)
}

expect(reporter.panic).toBeCalled()
expect(scope.isDone()).toBeTruthy()
})

test(`catches error with response object`, async () => {
const scope = nock(baseURI)
// Space
.get(`/spaces/${options.spaceId}/`)
.reply(429, {
sys: {
type: `Error`,
id: `MockedContentfulError`,
},
message: `Mocked message of Contentful error`,
requestId: `123abc`,
})

try {
await fetchData({
pluginConfig: createPluginConfig({
...options,
contentfulClientConfig: { retryOnError: false },
}),
reporter,
})
jest.fail()
} catch (e) {
const msg = expect(e.context.sourceMessage)

msg.toEqual(
expect.stringContaining(
`Accessing your Contentful space failed: MockedContentfulError`
)
)
msg.toEqual(expect.stringContaining(`Mocked message of Contentful error`))
msg.toEqual(expect.stringContaining(`Request ID: 123abc`))
}

expect(reporter.panic).toBeCalled()
expect(scope.isDone()).toBeTruthy()
})
})

0 comments on commit b3315a0

Please sign in to comment.