forked from AubaidFarrukh/smart-retry
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathutils.test.ts
More file actions
148 lines (127 loc) · 5.46 KB
/
Copy pathutils.test.ts
File metadata and controls
148 lines (127 loc) · 5.46 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
import {
generateId,
calculateDelay,
defaultShouldRetry,
sleep,
getErrorMessage,
getStatusCode,
} from '../src/utils';
describe('Utils', () => {
describe('generateId', () => {
it('should generate a valid UUID', () => {
const id = generateId();
expect(id).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/);
});
it('should generate unique IDs', () => {
const id1 = generateId();
const id2 = generateId();
expect(id1).not.toBe(id2);
});
});
describe('calculateDelay', () => {
it('should calculate exponential backoff correctly', () => {
expect(calculateDelay(1000, 1, 'exponential')).toBe(1000);
expect(calculateDelay(1000, 2, 'exponential')).toBe(2000);
expect(calculateDelay(1000, 3, 'exponential')).toBe(4000);
expect(calculateDelay(1000, 4, 'exponential')).toBe(8000);
});
it('should calculate linear backoff correctly', () => {
expect(calculateDelay(1000, 1, 'linear')).toBe(1000);
expect(calculateDelay(1000, 2, 'linear')).toBe(2000);
expect(calculateDelay(1000, 3, 'linear')).toBe(3000);
expect(calculateDelay(1000, 4, 'linear')).toBe(4000);
});
it('should return fixed delay for none backoff', () => {
expect(calculateDelay(1000, 1, 'none')).toBe(1000);
expect(calculateDelay(1000, 2, 'none')).toBe(1000);
expect(calculateDelay(1000, 3, 'none')).toBe(1000);
});
});
describe('defaultShouldRetry', () => {
it('should retry on network errors', () => {
expect(defaultShouldRetry({ code: 'ECONNREFUSED' })).toBe(true);
expect(defaultShouldRetry({ code: 'ETIMEDOUT' })).toBe(true);
expect(defaultShouldRetry({ code: 'ENOTFOUND' })).toBe(true);
expect(defaultShouldRetry({ code: 'ECONNRESET' })).toBe(true);
});
it('should retry on 5xx errors', () => {
expect(defaultShouldRetry({ response: { status: 500 } })).toBe(true);
expect(defaultShouldRetry({ response: { status: 502 } })).toBe(true);
expect(defaultShouldRetry({ response: { status: 503 } })).toBe(true);
expect(defaultShouldRetry({ response: { status: 504 } })).toBe(true);
});
it('should retry on 408 and 429', () => {
expect(defaultShouldRetry({ response: { status: 408 } })).toBe(true);
expect(defaultShouldRetry({ response: { status: 429 } })).toBe(true);
});
it('should not retry on 4xx errors (except 408, 429)', () => {
expect(defaultShouldRetry({ response: { status: 400 } })).toBe(false);
expect(defaultShouldRetry({ response: { status: 401 } })).toBe(false);
expect(defaultShouldRetry({ response: { status: 403 } })).toBe(false);
expect(defaultShouldRetry({ response: { status: 404 } })).toBe(false);
});
it('should not retry on null/undefined', () => {
expect(defaultShouldRetry(null)).toBe(false);
expect(defaultShouldRetry(undefined)).toBe(false);
});
it('should not retry on errors without a status or known network code', () => {
expect(defaultShouldRetry(new TypeError('Cannot read properties of undefined'))).toBe(false);
expect(defaultShouldRetry({ message: 'boom' })).toBe(false);
});
it('should not retry POST/PATCH by default, even on a retryable status', () => {
expect(defaultShouldRetry({ response: { status: 503 }, config: { method: 'post' } })).toBe(
false
);
expect(defaultShouldRetry({ response: { status: 503 }, config: { method: 'PATCH' } })).toBe(
false
);
expect(defaultShouldRetry({ code: 'ECONNREFUSED', config: { method: 'post' } })).toBe(false);
});
it('should retry POST/PATCH when allowNonIdempotent is true', () => {
expect(
defaultShouldRetry({ response: { status: 503 }, config: { method: 'post' } }, true)
).toBe(true);
});
it('should still retry GET/PUT/DELETE/HEAD/OPTIONS by default', () => {
for (const method of ['get', 'PUT', 'delete', 'HEAD', 'options']) {
expect(defaultShouldRetry({ response: { status: 503 }, config: { method } })).toBe(true);
}
});
it('should retry when no method info is present (generic smartRetry usage)', () => {
expect(defaultShouldRetry({ response: { status: 503 } })).toBe(true);
});
});
describe('sleep', () => {
it('should delay for specified milliseconds', async () => {
const start = Date.now();
await sleep(100);
const duration = Date.now() - start;
expect(duration).toBeGreaterThanOrEqual(95);
expect(duration).toBeLessThan(150);
});
});
describe('getErrorMessage', () => {
it('should extract message from response statusText', () => {
const error = { response: { statusText: 'Not Found' } };
expect(getErrorMessage(error)).toBe('Not Found');
});
it('should extract message from error.message', () => {
const error = new Error('Connection timeout');
expect(getErrorMessage(error)).toBe('Connection timeout');
});
it('should convert to string for unknown errors', () => {
expect(getErrorMessage('string error')).toBe('string error');
expect(getErrorMessage(123)).toBe('123');
});
});
describe('getStatusCode', () => {
it('should extract status code from error', () => {
const error = { response: { status: 404 } };
expect(getStatusCode(error)).toBe(404);
});
it('should return undefined for non-HTTP errors', () => {
const error = new Error('Network error');
expect(getStatusCode(error)).toBeUndefined();
});
});
});