Skip to content

Commit

Permalink
FAI-6462: retry http 409 (edit conflict) for graphql endpoint (#148)
Browse files Browse the repository at this point in the history
  • Loading branch information
ted-faros committed Jun 7, 2023
1 parent 386e87a commit 478d439
Show file tree
Hide file tree
Showing 2 changed files with 62 additions and 10 deletions.
8 changes: 7 additions & 1 deletion src/axios.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import axios, {AxiosError, AxiosInstance, AxiosRequestConfig} from 'axios';
import axiosRetry, {IAxiosRetryConfig, isRetryableError} from 'axios-retry';
import isRetryAllowed from 'is-retry-allowed';
import {isNil} from 'lodash';
import {Logger} from 'pino';

import {wrapApiError} from './errors';
Expand Down Expand Up @@ -43,8 +44,13 @@ export function makeAxiosInstanceWithRetry(
return makeAxiosInstance(config, {
retries,
retryCondition: (error) => {
const isGraphQLEndpoint = !isNil(error.config.url)
&& error.config.url.endsWith('graphql')
&& error.config.method === 'post';
// Timeouts should be retryable
return isNetworkError(error) || isRetryableError(error);
// 409 is an edit conflict error, which is retryable for GraphQL endpoints
return isNetworkError(error) || isRetryableError(error)
|| isGraphQLEndpoint && error.response?.status === 409;
},
retryDelay: (retryNumber, error) => {
if (logger) {
Expand Down
64 changes: 55 additions & 9 deletions test/axios.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,18 @@ describe('axios', () => {
transport: {target: 'pino-pretty', options: {levelFirst: true}},
});

function mockPostCode(url: string, code: number) {
return nock(apiUrl)
.post(url)
.reply(code)
.post(url)
.reply(code)
.post(url)
.reply(code)
.post(url)
.reply(200, {tenantId: '1'});
}

test('get resource with retry', async () => {
const mock = nock(apiUrl)
.get('/hi')
Expand All @@ -33,16 +45,22 @@ describe('axios', () => {
mock.done();
});

test('get graphql with 409 should fail', async () => {
const mock = nock(apiUrl).get('/graphs/foo/graphql').reply(409);
const client = sut.makeAxiosInstanceWithRetry(
{baseURL: apiUrl},
undefined,
3,
100
);
await expect(client.get('/graphs/foo/graphql')).rejects.toThrowError(
'Request failed with status code 409'
);
mock.done();
});

test('post resource with retry', async () => {
const mock = nock(apiUrl)
.post('/hi')
.reply(502)
.post('/hi')
.reply(502)
.post('/hi')
.reply(502)
.post('/hi')
.reply(200, {tenantId: '1'});
const mock = mockPostCode('/hi', 502);
const client = sut.makeAxiosInstanceWithRetry(
{baseURL: apiUrl},
undefined,
Expand All @@ -55,6 +73,34 @@ describe('axios', () => {
mock.done();
});

test('post graphql with 409 should retry', async () => {
const mock = mockPostCode('/graphs/foo/graphql', 409);
const client = sut.makeAxiosInstanceWithRetry(
{baseURL: apiUrl},
undefined,
3,
100
);
const res = await client.post('/graphs/foo/graphql');
expect(res.status).toBe(200);
expect(res.data).toStrictEqual({tenantId: '1'});
mock.done();
});

test('post non-graphql with 409 should fail', async () => {
const mock = nock(apiUrl).post('/hi').reply(409);
const client = sut.makeAxiosInstanceWithRetry(
{baseURL: apiUrl},
undefined,
3,
100
);
await expect(client.post('/hi')).rejects.toThrowError(
'Request failed with status code 409'
);
mock.done();
});

test('give up after a retry', async () => {
const mock = nock(apiUrl).get('/hi').reply(502).get('/hi').reply(404);
const client = sut.makeAxiosInstanceWithRetry(
Expand Down

0 comments on commit 478d439

Please sign in to comment.