Skip to content

Commit 3e52e2c

Browse files
authored
Merge pull request #141 from contentstack/fix/DX-3529-retryLimit-retryDelay
Enhance retry logic with configurable limit and delay and add tests
2 parents f0cbc10 + 5a3ef3f commit 3e52e2c

File tree

5 files changed

+175
-18
lines changed

5 files changed

+175
-18
lines changed

.talismanrc

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,15 @@ fileignoreconfig:
33
ignore_detectors:
44
- filecontent
55
- filename: package-lock.json
6-
checksum: 52799bf1f9a1c387a74baeecac6c1c08f22bb8abdd2a1f0e689d8ed374b47635
6+
checksum: 61066aedc29ef5bd8904d1ee2384dad828e8f9aab1a6b0360797ec7926e7f8dd
77
- filename: .husky/pre-commit
88
checksum: 5baabd7d2c391648163f9371f0e5e9484f8fb90fa2284cfc378732ec3192c193
99
- filename: test/request.spec.ts
1010
checksum: 87afd3bb570fd52437404cbe69a39311ad8a8c73bca9d075ecf88652fd3e13f6
1111
- filename: src/lib/request.ts
1212
checksum: 86d761c4f50fcf377e52c98e0c4db6f06be955790fc5a0f2ba8fe32a88c60825
13+
- filename: src/lib/retryPolicy/delivery-sdk-handlers.ts
14+
checksum: 08ccd6342b3adbeb7b85309a034b4df4b2ad905a0cc2a3778ab483b61ba41b9e
15+
- filename: test/retryPolicy/delivery-sdk-handlers.spec.ts
16+
checksum: 6d22d7482aa6dccba5554ae497e5b0c3572357a5cead6f4822ee4428edc12207
1317
version: ""

package-lock.json

Lines changed: 6 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@contentstack/core",
3-
"version": "1.3.1",
3+
"version": "1.3.2",
44
"type": "commonjs",
55
"main": "./dist/cjs/src/index.js",
66
"types": "./dist/cjs/src/index.d.ts",
@@ -20,12 +20,12 @@
2020
"husky-check": "npx husky install && chmod +x .husky/pre-commit"
2121
},
2222
"dependencies": {
23-
"axios": "^1.11.0",
23+
"axios": "^1.12.2",
2424
"axios-mock-adapter": "^2.1.0",
25+
"husky": "^9.1.7",
2526
"lodash": "^4.17.21",
2627
"qs": "^6.14.0",
27-
"tslib": "^2.8.1",
28-
"husky": "^9.1.7"
28+
"tslib": "^2.8.1"
2929
},
3030
"files": [
3131
"dist/*",

src/lib/retryPolicy/delivery-sdk-handlers.ts

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,17 @@ export const retryResponseErrorHandler = (error: any, config: any, axiosInstance
8383
}
8484
error.config.retryCount = retryCount;
8585

86-
return axiosInstance(error.config);
86+
// Apply configured delay for retries
87+
return new Promise((resolve, reject) => {
88+
setTimeout(async () => {
89+
try {
90+
const retryResponse = await axiosInstance(error.config);
91+
resolve(retryResponse);
92+
} catch (retryError) {
93+
reject(retryError);
94+
}
95+
}, config.retryDelay || 300); // Use configured delay with fallback
96+
});
8797
}
8898
}
8999

@@ -99,17 +109,22 @@ export const retryResponseErrorHandler = (error: any, config: any, axiosInstance
99109
}
100110
};
101111
const retry = (error: any, config: any, retryCount: number, retryDelay: number, axiosInstance: AxiosInstance) => {
102-
let delayTime: number = retryDelay;
103112
if (retryCount > config.retryLimit) {
104113
return Promise.reject(error);
105114
}
106115

107-
delayTime = config.retryDelay;
116+
// Use the passed retryDelay parameter first, then config.retryDelay, then default
117+
const delayTime = retryDelay || config.retryDelay || 300;
108118
error.config.retryCount = retryCount;
109119

110-
return new Promise(function (resolve) {
111-
return setTimeout(function () {
112-
return resolve(axiosInstance(error.request));
120+
return new Promise(function (resolve, reject) {
121+
return setTimeout(async function () {
122+
try {
123+
const retryResponse = await axiosInstance(error.config);
124+
resolve(retryResponse);
125+
} catch (retryError) {
126+
reject(retryError);
127+
}
113128
}, delayTime);
114129
});
115130
};

test/retryPolicy/delivery-sdk-handlers.spec.ts

Lines changed: 139 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,14 @@ describe('retryResponseErrorHandler', () => {
154154
});
155155
it('should call the retry function if retryCondition is passed', async () => {
156156
const error = {
157-
config: { retryOnError: true, retryCount: 4 },
157+
config: {
158+
retryOnError: true,
159+
retryCount: 4,
160+
method: 'post',
161+
url: '/retryURL',
162+
data: { key: 'value' },
163+
headers: { 'Content-Type': 'application/json' }
164+
},
158165
response: {
159166
status: 200,
160167
statusText: 'Success Response but retry needed',
@@ -231,6 +238,103 @@ describe('retryResponseErrorHandler', () => {
231238
expect(response.status).toBe(200);
232239
});
233240

241+
it('should use configured retryDelay for 429 status retries', async () => {
242+
const error = {
243+
config: { retryOnError: true, retryCount: 1 },
244+
response: {
245+
status: 429,
246+
statusText: 'Rate limit exceeded',
247+
headers: {},
248+
data: {
249+
error_message: 'Rate limit exceeded',
250+
error_code: 429,
251+
errors: null,
252+
},
253+
},
254+
};
255+
const config = { retryLimit: 3, retryDelay: 500 };
256+
const client = axios.create();
257+
258+
mock.onAny().reply(200, { success: true });
259+
260+
jest.useFakeTimers();
261+
const startTime = Date.now();
262+
263+
const responsePromise = retryResponseErrorHandler(error, config, client);
264+
265+
// Fast-forward time by the configured delay
266+
jest.advanceTimersByTime(500);
267+
268+
const response = (await responsePromise) as AxiosResponse;
269+
expect(response.status).toBe(200);
270+
271+
jest.useRealTimers();
272+
});
273+
274+
it('should use configured retryDelay for 401 status retries', async () => {
275+
const error = {
276+
config: { retryOnError: true, retryCount: 1 },
277+
response: {
278+
status: 401,
279+
statusText: 'Unauthorized',
280+
headers: {},
281+
data: {
282+
error_message: 'Unauthorized',
283+
error_code: 401,
284+
errors: null,
285+
},
286+
},
287+
};
288+
const config = { retryLimit: 3, retryDelay: 250 };
289+
const client = axios.create();
290+
291+
mock.onAny().reply(200, { success: true });
292+
293+
jest.useFakeTimers();
294+
295+
const responsePromise = retryResponseErrorHandler(error, config, client);
296+
297+
// Fast-forward time by the configured delay
298+
jest.advanceTimersByTime(250);
299+
300+
const response = (await responsePromise) as AxiosResponse;
301+
expect(response.status).toBe(200);
302+
303+
jest.useRealTimers();
304+
});
305+
306+
it('should use default retryDelay (300ms) when not configured for 429 retries', async () => {
307+
const error = {
308+
config: { retryOnError: true, retryCount: 1 },
309+
response: {
310+
status: 429,
311+
statusText: 'Rate limit exceeded',
312+
headers: {},
313+
data: {
314+
error_message: 'Rate limit exceeded',
315+
error_code: 429,
316+
errors: null,
317+
},
318+
},
319+
};
320+
const config = { retryLimit: 3 }; // No retryDelay specified
321+
const client = axios.create();
322+
323+
mock.onAny().reply(200, { success: true });
324+
325+
jest.useFakeTimers();
326+
327+
const responsePromise = retryResponseErrorHandler(error, config, client);
328+
329+
// Fast-forward time by the default delay (300ms)
330+
jest.advanceTimersByTime(300);
331+
332+
const response = (await responsePromise) as AxiosResponse;
333+
expect(response.status).toBe(200);
334+
335+
jest.useRealTimers();
336+
});
337+
234338
it('should retry when retryCondition is true', async () => {
235339
const error = {
236340
config: { retryOnError: true, retryCount: 1 },
@@ -256,6 +360,40 @@ describe('retryResponseErrorHandler', () => {
256360
expect(retryCondition).toHaveBeenCalledWith(error);
257361
});
258362

363+
it('should use configured retryDelay when retryCondition triggers retry', async () => {
364+
const error = {
365+
config: { retryOnError: true, retryCount: 1 },
366+
response: {
367+
status: 500,
368+
statusText: 'Internal Server Error',
369+
headers: {},
370+
data: {
371+
error_message: 'Internal Server Error',
372+
error_code: 500,
373+
errors: null,
374+
},
375+
},
376+
};
377+
const retryCondition = jest.fn().mockReturnValue(true);
378+
const config = { retryLimit: 3, retryCondition, retryDelay: 750 };
379+
const client = axios.create();
380+
381+
mock.onAny().reply(200, { success: true });
382+
383+
jest.useFakeTimers();
384+
385+
const responsePromise = retryResponseErrorHandler(error, config, client);
386+
387+
// Fast-forward time by the configured delay
388+
jest.advanceTimersByTime(750);
389+
390+
const response = (await responsePromise) as AxiosResponse;
391+
expect(response.status).toBe(200);
392+
expect(retryCondition).toHaveBeenCalledWith(error);
393+
394+
jest.useRealTimers();
395+
});
396+
259397
it('should retry with delay when x-ratelimit-remaining is 0 and retry-after header is present', async () => {
260398
const error = {
261399
config: { retryOnError: true, retryCount: 1 },

0 commit comments

Comments
 (0)