@mimik/request-retry 4.0.9 → 4.0.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +20 -33
- package/index.js +41 -40
- package/lib/common.js +0 -4
- package/lib/rp-axios-wrapper.js +0 -1
- package/lib/validate.js +26 -23
- package/package.json +17 -13
- package/.husky/pre-commit +0 -2
- package/.husky/pre-push +0 -2
- package/eslint.config.js +0 -70
- package/manual-test/httpMock.js +0 -19
- package/manual-test/man.js +0 -26
- package/manual-test/retryMockMan.js +0 -69
- package/manual-test/testEnv.js +0 -25
- package/manual-test/testRace.js +0 -35
- package/manual-test/testRetry.js +0 -64
- package/test/retryMock.js +0 -74
- package/test/rpRetry.spec.js +0 -432
- package/test/testEnv.js +0 -26
package/README.md
CHANGED
|
@@ -9,41 +9,17 @@ import rp from '@mimik/request-retry';
|
|
|
9
9
|
```
|
|
10
10
|
<a name="module_request-retry..rpRetry"></a>
|
|
11
11
|
|
|
12
|
-
### request-retry~rpRetry(options) ⇒ <code>Promise.<
|
|
12
|
+
### request-retry~rpRetry(options) ⇒ <code>Promise.<\*></code>
|
|
13
13
|
Make a request with retries.
|
|
14
14
|
|
|
15
15
|
**Kind**: inner method of [<code>request-retry</code>](#module_request-retry)
|
|
16
|
-
**Returns**: <code>Promise.<
|
|
16
|
+
**Returns**: <code>Promise.<\*></code> - A bluebird promise (supports cancellation).
|
|
17
17
|
**Category**: async
|
|
18
18
|
**Throws**:
|
|
19
19
|
|
|
20
20
|
- <code>Error</code> An error produced by `getRichError`, wrapping an
|
|
21
21
|
[Axios](https://www.npmjs.com/package/axios) error or a `TimeoutError`.
|
|
22
22
|
|
|
23
|
-
The following properties may be added to the `options` object under the `retry` property:
|
|
24
|
-
- retries `{int}`: Maximum number of retries, independent of the `retryStrategy`. Default: `2`.
|
|
25
|
-
If the value is `< 0` it is set to `0`; if `> 15` it is set to `15`.
|
|
26
|
-
- delay `{int}`: Delay between retries in milliseconds when no `delayStrategy` is provided. Default: `1000`.
|
|
27
|
-
If the value is `< 20` it is set to `20`; if `> 30000` it is set to `30000`.
|
|
28
|
-
- delayStrategy `{function}`: A function that returns the delay (in milliseconds) to wait before the next retry.
|
|
29
|
-
Signature: `(nbRetry, err, options, correlationId)`. Must return a number `> 0` and `< 30000`; otherwise `delay` is used.
|
|
30
|
-
- logLevel `{object}`: Controls logging behavior:
|
|
31
|
-
- *response*: Log level for responses. Invalid values fall back to `silly`.
|
|
32
|
-
- *error*: Log level for errors. Invalid values fall back to `silly`.
|
|
33
|
-
- *request*: Log level for requests. Invalid values fall back to `silly`.
|
|
34
|
-
- *responseDetails*: Detail level when displaying the response: `count`, `type`, or `full`. Default: `type`
|
|
35
|
-
(invalid values fall back to `type`).
|
|
36
|
-
- *responseName*: Label associated with the response. Default: `response`
|
|
37
|
-
(non-string or empty values fall back to `response`).
|
|
38
|
-
- timeout `{int}`: Request timeout (in seconds) applied to the initial request and all retries.
|
|
39
|
-
If reached, a `TimeoutError` is thrown. Default: `50`. Values `< 10` are set to `10`; values `> 120` are set to `120`.
|
|
40
|
-
- retryStrategy `{function}`: A function that decides whether to retry. Signature:
|
|
41
|
-
`(nbRetry, err, options, correlationId)`. Must return a boolean; otherwise it is ignored.
|
|
42
|
-
|
|
43
|
-
The following properties may be added to the `options` object under the `metrics` property:
|
|
44
|
-
- HTTPRequestDuration `{function}`: A `prom-client` metric function to measure request duration.
|
|
45
|
-
- url `{string}`: Optional URL label for the metric. If omitted, the request URL is used.
|
|
46
|
-
|
|
47
23
|
The **default** `retryStrategy` is:
|
|
48
24
|
|
|
49
25
|
```javascript
|
|
@@ -55,10 +31,6 @@ defaultRetry = (...args) => {
|
|
|
55
31
|
};
|
|
56
32
|
```
|
|
57
33
|
|
|
58
|
-
If `logLevel` is empty, requests and responses are logged at `info`, the response is logged with full details
|
|
59
|
-
under the `response` property, and request errors generate a `warn`. If logLevel is provided but incomplete,
|
|
60
|
-
only the provided keys are active; missing keys disable that logging.
|
|
61
|
-
|
|
62
34
|
If not already set, the `User-Agent` header is set to:
|
|
63
35
|
`mimik-{serverType}/{serverVersion}/{serverId} {architecture} {node}`.
|
|
64
36
|
|
|
@@ -70,7 +42,22 @@ To ease migration from `request-promise`, the following adjustments are applied
|
|
|
70
42
|
**Requires**: <code>module:@mimik/sumologic-winston-logger</code>, <code>module:@mimik/response-helper</code>
|
|
71
43
|
**Fulfil**: <code>object</code> - The Axios response when `resolveWithFullResponse` is `true`; otherwise `response.data`.
|
|
72
44
|
|
|
73
|
-
| Param | Type | Description |
|
|
74
|
-
| --- | --- | --- |
|
|
75
|
-
| options | <code>object</code> | Options for the request (similar to [axios](https://www.npmjs.com/package/axios)). The `validateStatus` option is disabled: an error is thrown for status codes outside **[200, 300)**. An additional option, `resolveWithFullResponse`, controls the return shape: when `true`, the full Axios response object is returned; when `false` or omitted, only `response.data` is returned. |
|
|
45
|
+
| Param | Type | Default | Description |
|
|
46
|
+
| --- | --- | --- | --- |
|
|
47
|
+
| options | <code>object</code> | | Options for the request (similar to [axios](https://www.npmjs.com/package/axios)). The `validateStatus` option is disabled: an error is thrown for status codes outside **[200, 300)**. An additional option, `resolveWithFullResponse`, controls the return shape: when `true`, the full Axios response object is returned; when `false` or omitted, only `response.data` is returned. |
|
|
48
|
+
| [options.retry] | <code>object</code> | | Retry configuration. |
|
|
49
|
+
| [options.retry.retries] | <code>number</code> | <code>2</code> | Maximum number of retries, independent of the `retryStrategy`. If the value is `< 0`, it is set to `0`; if `> 15`, it is set to `15`. |
|
|
50
|
+
| [options.retry.delay] | <code>number</code> | <code>1000</code> | Delay between retries in milliseconds when no `delayStrategy` is provided. If the value is `< 20`, it is set to `20`; if `> 30000`, it is set to `30000`. |
|
|
51
|
+
| [options.retry.delayStrategy] | <code>function</code> | | A function that returns the delay (in milliseconds) to wait before the next retry. Signature: `(nbRetry, err, options, correlationId)`. Must return a number `> 0` and `< 30000`; otherwise `delay` is used. |
|
|
52
|
+
| [options.retry.retryStrategy] | <code>function</code> | | A function that decides whether to retry. Signature: `(nbRetry, err, options, correlationId)`. Must return a boolean; otherwise it is ignored. |
|
|
53
|
+
| [options.retry.timeout] | <code>number</code> | <code>50</code> | Request timeout (in seconds) applied to the initial request and all retries. If reached, a `TimeoutError` is thrown. Values `< 10` are set to `10`; values `> 120` are set to `120`. |
|
|
54
|
+
| [options.retry.logLevel] | <code>object</code> | | Controls logging behavior. When omitted, requests and responses are logged at `info` with full details, and errors at `warn`. When provided but incomplete, only the provided keys are active; missing keys disable that logging. Non-object values are ignored and defaults apply. |
|
|
55
|
+
| [options.retry.logLevel.response] | <code>string</code> | | Log level for responses. Invalid values fall back to `silly`. |
|
|
56
|
+
| [options.retry.logLevel.error] | <code>string</code> | | Log level for errors. Invalid values fall back to `silly`. |
|
|
57
|
+
| [options.retry.logLevel.request] | <code>string</code> | | Log level for requests. Invalid values fall back to `silly`. |
|
|
58
|
+
| [options.retry.logLevel.responseDetails] | <code>string</code> | <code>"'type'"</code> | Detail level for the response: `count`, `type`, or `full`. Invalid values fall back to `type`. When `logLevel` is omitted entirely, the default is `full`. |
|
|
59
|
+
| [options.retry.logLevel.responseName] | <code>string</code> | <code>"'response'"</code> | Label associated with the response. Non-string or empty values fall back to `response`. |
|
|
60
|
+
| [options.metrics] | <code>object</code> | | Metrics configuration. |
|
|
61
|
+
| [options.metrics.HTTPRequestDuration] | <code>function</code> | | A `prom-client` metric function to measure request duration. |
|
|
62
|
+
| [options.metrics.url] | <code>string</code> | | Optional URL label for the metric. If omitted, the request URL is used. |
|
|
76
63
|
|
package/index.js
CHANGED
|
@@ -26,7 +26,7 @@ const DEFAULT_LOGLEVEL_ERROR = 'warn';
|
|
|
26
26
|
const DEFAULT_LOGLEVEL_REQUEST = 'info';
|
|
27
27
|
|
|
28
28
|
const MILLI = 1000;
|
|
29
|
-
const
|
|
29
|
+
const NS_PER_MS = 1000000;
|
|
30
30
|
|
|
31
31
|
Promise.config({ cancellation: true });
|
|
32
32
|
|
|
@@ -49,35 +49,36 @@ Promise.config({ cancellation: true });
|
|
|
49
49
|
* The `validateStatus` option is disabled: an error is thrown for status codes outside **[200, 300)**.
|
|
50
50
|
* An additional option, `resolveWithFullResponse`, controls the return shape:
|
|
51
51
|
* when `true`, the full Axios response object is returned; when `false` or omitted, only `response.data` is returned.
|
|
52
|
-
* @
|
|
52
|
+
* @param {object} [options.retry] - Retry configuration.
|
|
53
|
+
* @param {number} [options.retry.retries=2] - Maximum number of retries, independent of the `retryStrategy`.
|
|
54
|
+
* If the value is `< 0`, it is set to `0`; if `> 15`, it is set to `15`.
|
|
55
|
+
* @param {number} [options.retry.delay=1000] - Delay between retries in milliseconds when no `delayStrategy` is provided.
|
|
56
|
+
* If the value is `< 20`, it is set to `20`; if `> 30000`, it is set to `30000`.
|
|
57
|
+
* @param {Function} [options.retry.delayStrategy] - A function that returns the delay (in milliseconds) to wait before the next retry.
|
|
58
|
+
* Signature: `(nbRetry, err, options, correlationId)`. Must return a number `> 0` and `< 30000`; otherwise `delay` is used.
|
|
59
|
+
* @param {Function} [options.retry.retryStrategy] - A function that decides whether to retry. Signature:
|
|
60
|
+
* `(nbRetry, err, options, correlationId)`. Must return a boolean; otherwise it is ignored.
|
|
61
|
+
* @param {number} [options.retry.timeout=50] - Request timeout (in seconds) applied to the initial request and all retries.
|
|
62
|
+
* If reached, a `TimeoutError` is thrown. Values `< 10` are set to `10`; values `> 120` are set to `120`.
|
|
63
|
+
* @param {object} [options.retry.logLevel] - Controls logging behavior.
|
|
64
|
+
* When omitted, requests and responses are logged at `info` with full details, and errors at `warn`.
|
|
65
|
+
* When provided but incomplete, only the provided keys are active; missing keys disable that logging.
|
|
66
|
+
* Non-object values are ignored and defaults apply.
|
|
67
|
+
* @param {string} [options.retry.logLevel.response] - Log level for responses. Invalid values fall back to `silly`.
|
|
68
|
+
* @param {string} [options.retry.logLevel.error] - Log level for errors. Invalid values fall back to `silly`.
|
|
69
|
+
* @param {string} [options.retry.logLevel.request] - Log level for requests. Invalid values fall back to `silly`.
|
|
70
|
+
* @param {string} [options.retry.logLevel.responseDetails='type'] - Detail level for the response: `count`, `type`, or `full`.
|
|
71
|
+
* Invalid values fall back to `type`. When `logLevel` is omitted entirely, the default is `full`.
|
|
72
|
+
* @param {string} [options.retry.logLevel.responseName='response'] - Label associated with the response.
|
|
73
|
+
* Non-string or empty values fall back to `response`.
|
|
74
|
+
* @param {object} [options.metrics] - Metrics configuration.
|
|
75
|
+
* @param {Function} [options.metrics.HTTPRequestDuration] - A `prom-client` metric function to measure request duration.
|
|
76
|
+
* @param {string} [options.metrics.url] - Optional URL label for the metric. If omitted, the request URL is used.
|
|
77
|
+
* @return {Promise<*>} A bluebird promise (supports cancellation).
|
|
53
78
|
* @fulfil {object} - The Axios response when `resolveWithFullResponse` is `true`; otherwise `response.data`.
|
|
54
79
|
* @throws {Error} An error produced by `getRichError`, wrapping an
|
|
55
80
|
* [Axios](https://www.npmjs.com/package/axios) error or a `TimeoutError`.
|
|
56
81
|
*
|
|
57
|
-
* The following properties may be added to the `options` object under the `retry` property:
|
|
58
|
-
* - retries `{int}`: Maximum number of retries, independent of the `retryStrategy`. Default: `2`.
|
|
59
|
-
* If the value is `< 0` it is set to `0`; if `> 15` it is set to `15`.
|
|
60
|
-
* - delay `{int}`: Delay between retries in milliseconds when no `delayStrategy` is provided. Default: `1000`.
|
|
61
|
-
* If the value is `< 20` it is set to `20`; if `> 30000` it is set to `30000`.
|
|
62
|
-
* - delayStrategy `{function}`: A function that returns the delay (in milliseconds) to wait before the next retry.
|
|
63
|
-
* Signature: `(nbRetry, err, options, correlationId)`. Must return a number `> 0` and `< 30000`; otherwise `delay` is used.
|
|
64
|
-
* - logLevel `{object}`: Controls logging behavior:
|
|
65
|
-
* - *response*: Log level for responses. Invalid values fall back to `silly`.
|
|
66
|
-
* - *error*: Log level for errors. Invalid values fall back to `silly`.
|
|
67
|
-
* - *request*: Log level for requests. Invalid values fall back to `silly`.
|
|
68
|
-
* - *responseDetails*: Detail level when displaying the response: `count`, `type`, or `full`. Default: `type`
|
|
69
|
-
* (invalid values fall back to `type`).
|
|
70
|
-
* - *responseName*: Label associated with the response. Default: `response`
|
|
71
|
-
* (non-string or empty values fall back to `response`).
|
|
72
|
-
* - timeout `{int}`: Request timeout (in seconds) applied to the initial request and all retries.
|
|
73
|
-
* If reached, a `TimeoutError` is thrown. Default: `50`. Values `< 10` are set to `10`; values `> 120` are set to `120`.
|
|
74
|
-
* - retryStrategy `{function}`: A function that decides whether to retry. Signature:
|
|
75
|
-
* `(nbRetry, err, options, correlationId)`. Must return a boolean; otherwise it is ignored.
|
|
76
|
-
*
|
|
77
|
-
* The following properties may be added to the `options` object under the `metrics` property:
|
|
78
|
-
* - HTTPRequestDuration `{function}`: A `prom-client` metric function to measure request duration.
|
|
79
|
-
* - url `{string}`: Optional URL label for the metric. If omitted, the request URL is used.
|
|
80
|
-
*
|
|
81
82
|
* The **default** `retryStrategy` is:
|
|
82
83
|
*
|
|
83
84
|
* ```javascript
|
|
@@ -89,10 +90,6 @@ Promise.config({ cancellation: true });
|
|
|
89
90
|
* };
|
|
90
91
|
* ```
|
|
91
92
|
*
|
|
92
|
-
* If `logLevel` is empty, requests and responses are logged at `info`, the response is logged with full details
|
|
93
|
-
* under the `response` property, and request errors generate a `warn`. If logLevel is provided but incomplete,
|
|
94
|
-
* only the provided keys are active; missing keys disable that logging.
|
|
95
|
-
*
|
|
96
93
|
* If not already set, the `User-Agent` header is set to:
|
|
97
94
|
* `mimik-{serverType}/{serverVersion}/{serverId} {architecture} {node}`.
|
|
98
95
|
*
|
|
@@ -103,20 +100,21 @@ Promise.config({ cancellation: true });
|
|
|
103
100
|
*/
|
|
104
101
|
export const rpRetry = (origOptions) => {
|
|
105
102
|
const startHrTime = process.hrtime.bigint();
|
|
106
|
-
const options = origOptions;
|
|
103
|
+
const options = { ...origOptions };
|
|
107
104
|
const { metrics } = options;
|
|
108
|
-
|
|
105
|
+
delete options.metrics;
|
|
106
|
+
const enteredUrl = options.uri || options.url || '';
|
|
109
107
|
const correlationId = (options.headers && options.headers['x-correlation-id']) ? options.headers['x-correlation-id'] : getCorrelationId();
|
|
110
108
|
const errors = [];
|
|
111
109
|
const delayPromises = [];
|
|
112
110
|
const criteria = validateOptions(options, correlationId);
|
|
113
111
|
const { logLevel } = criteria;
|
|
114
|
-
const
|
|
112
|
+
const hasLogLevel = Object.keys(logLevel).length > 0;
|
|
115
113
|
let nbRetries = 0;
|
|
116
114
|
let mainTimeout;
|
|
117
115
|
const measure = (statusCode) => {
|
|
118
116
|
if (metrics && metrics.HTTPRequestDuration) {
|
|
119
|
-
const elapsedTimeInMs = Number(process.hrtime.bigint() - startHrTime) /
|
|
117
|
+
const elapsedTimeInMs = Number(process.hrtime.bigint() - startHrTime) / NS_PER_MS;
|
|
120
118
|
|
|
121
119
|
metrics.HTTPRequestDuration
|
|
122
120
|
.labels('rpRetry', options.method, metrics.url || enteredUrl, enteredUrl.includes('?'), statusCode)
|
|
@@ -127,7 +125,6 @@ export const rpRetry = (origOptions) => {
|
|
|
127
125
|
if (!options.headers) options.headers = { 'user-agent': setUserAgent() };
|
|
128
126
|
else if (!options.headers['user-agent']) options.headers['user-agent'] = setUserAgent();
|
|
129
127
|
const optionsInfo = { ...options };
|
|
130
|
-
delete optionsInfo.metrics;
|
|
131
128
|
|
|
132
129
|
const retryProcess = nbRetry => rp(options)
|
|
133
130
|
.then((response) => {
|
|
@@ -137,7 +134,7 @@ export const rpRetry = (origOptions) => {
|
|
|
137
134
|
info[logLevel.responseName] = setResponse(response, logLevel.responseDetails);
|
|
138
135
|
logger[logLevel.response]('request response', info, correlationId);
|
|
139
136
|
}
|
|
140
|
-
else if (
|
|
137
|
+
else if (!hasLogLevel) {
|
|
141
138
|
info[DEFAULT_RESPONSE_NAME] = setResponse(response, DEFAULT_LOGLEVEL_DETAILS);
|
|
142
139
|
logger[DEFAULT_LOGLEVEL_RESPONSE]('request response', info, correlationId);
|
|
143
140
|
}
|
|
@@ -150,7 +147,10 @@ export const rpRetry = (origOptions) => {
|
|
|
150
147
|
const delayPromise = Promise.delay(calculateDelay(options, nbRetry, criteria, err, correlationId));
|
|
151
148
|
|
|
152
149
|
delayPromises.unshift(delayPromise);
|
|
153
|
-
return delayPromise.then(() =>
|
|
150
|
+
return delayPromise.then(() => {
|
|
151
|
+
delayPromises.pop();
|
|
152
|
+
return retryProcess(nbRetry + 1);
|
|
153
|
+
});
|
|
154
154
|
}
|
|
155
155
|
const error = err;
|
|
156
156
|
|
|
@@ -166,12 +166,13 @@ export const rpRetry = (origOptions) => {
|
|
|
166
166
|
'request error response',
|
|
167
167
|
{ options: optionsInfo },
|
|
168
168
|
error,
|
|
169
|
-
logLevel.error || (
|
|
169
|
+
logLevel.error || (hasLogLevel ? undefined : DEFAULT_LOGLEVEL_ERROR),
|
|
170
170
|
correlationId,
|
|
171
171
|
);
|
|
172
172
|
});
|
|
173
173
|
|
|
174
174
|
const retryPromise = retryProcess(0);
|
|
175
|
+
// bluebird Promise — .cancel() requires cancellation: true (line 31)
|
|
175
176
|
const mainTimeoutPromise = new Promise((resolve, reject) => {
|
|
176
177
|
mainTimeout = setTimeout(() => {
|
|
177
178
|
delayPromises.forEach(delayPromise => delayPromise.cancel(`retry timeout delay, ${criteria.timeout}, ${nbRetries}`));
|
|
@@ -186,7 +187,7 @@ export const rpRetry = (origOptions) => {
|
|
|
186
187
|
'request error response',
|
|
187
188
|
{ options: optionsInfo },
|
|
188
189
|
error,
|
|
189
|
-
logLevel.error || (
|
|
190
|
+
logLevel.error || (hasLogLevel ? undefined : DEFAULT_LOGLEVEL_ERROR),
|
|
190
191
|
correlationId,
|
|
191
192
|
);
|
|
192
193
|
reject(error);
|
|
@@ -194,7 +195,7 @@ export const rpRetry = (origOptions) => {
|
|
|
194
195
|
});
|
|
195
196
|
|
|
196
197
|
if (logLevel.request) logger[logLevel.request]('making a request', { options: optionsInfo, criteria }, correlationId);
|
|
197
|
-
else if (
|
|
198
|
+
else if (!hasLogLevel) logger[DEFAULT_LOGLEVEL_REQUEST]('making a request', { options: optionsInfo, criteria }, correlationId);
|
|
198
199
|
return Promise.race([retryPromise, mainTimeoutPromise])
|
|
199
200
|
.then((result) => {
|
|
200
201
|
mainTimeoutPromise.cancel('main timeout');
|
package/lib/common.js
CHANGED
package/lib/rp-axios-wrapper.js
CHANGED
|
@@ -4,7 +4,6 @@ import { getRichError } from '@mimik/response-helper';
|
|
|
4
4
|
const rp = (origOptions) => {
|
|
5
5
|
const options = { ...origOptions }; // we will not do deep manipulation
|
|
6
6
|
|
|
7
|
-
if (options.metrics) delete options.metrics; // metrics has circular references
|
|
8
7
|
if (options.uri) options.url = options.uri;
|
|
9
8
|
delete options.uri;
|
|
10
9
|
delete options.validateStatus;
|
package/lib/validate.js
CHANGED
|
@@ -2,9 +2,9 @@ import { TOO_MANY_REQUESTS_ERROR } from './common.js';
|
|
|
2
2
|
import logger from '@mimik/sumologic-winston-logger';
|
|
3
3
|
|
|
4
4
|
const DETAILS = ['full', 'type', 'count'];
|
|
5
|
-
const [, TYPE
|
|
5
|
+
const [, TYPE] = DETAILS;
|
|
6
6
|
const RESPONSE = 'response';
|
|
7
|
-
const
|
|
7
|
+
const DEFAULT_LOGLEVEL = 'silly';
|
|
8
8
|
const DEFAULT_RETRIES = 2;
|
|
9
9
|
const MIN_RETRIES = 0;
|
|
10
10
|
const MAX_RETRIES = 15;
|
|
@@ -47,7 +47,7 @@ const validateOptions = (options, correlationId) => {
|
|
|
47
47
|
timeout,
|
|
48
48
|
} = options.retry;
|
|
49
49
|
const validateLogLevel = (val, name) => {
|
|
50
|
-
if (
|
|
50
|
+
if (val[name] === undefined || val[name] === null) return null;
|
|
51
51
|
if (!logger.LEVELS.includes(val[name])) {
|
|
52
52
|
logger.warn(`invalid logLevel.${name}, reset to default`, { name, value: val[name], default: DEFAULT_LOGLEVEL }, correlationId);
|
|
53
53
|
return DEFAULT_LOGLEVEL;
|
|
@@ -78,7 +78,7 @@ const validateOptions = (options, correlationId) => {
|
|
|
78
78
|
return val;
|
|
79
79
|
};
|
|
80
80
|
const validateResponseName = (val, name, defaultValue) => {
|
|
81
|
-
if (
|
|
81
|
+
if (val === undefined || val === null) return defaultValue;
|
|
82
82
|
if (typeof val !== 'string' || val.length === 0 || val.trim().length === 0) {
|
|
83
83
|
logger.warn(`invalid logLevel.${name}, reset to default`, { name, value: val, defaultValue }, correlationId);
|
|
84
84
|
return defaultValue;
|
|
@@ -91,25 +91,28 @@ const validateOptions = (options, correlationId) => {
|
|
|
91
91
|
delay = validateValue(delay, 'delay', DEFAULT_DELAY, MIN_DELAY, MAX_DELAY);
|
|
92
92
|
retries = validateValue(retries, 'retries', DEFAULT_RETRIES, MIN_RETRIES, MAX_RETRIES);
|
|
93
93
|
if (typeof delayStrategy !== 'function') {
|
|
94
|
-
if (delayStrategy) {
|
|
95
|
-
|
|
96
|
-
delayStrategy = setDelay;
|
|
97
|
-
}
|
|
98
|
-
else delayStrategy = setDelay;
|
|
94
|
+
if (delayStrategy) logger.warn('invalid delayStrategy, using delay', { strategyType: typeof delayStrategy, delay, default: DEFAULT_DELAY }, correlationId);
|
|
95
|
+
delayStrategy = setDelay;
|
|
99
96
|
}
|
|
100
97
|
if (typeof retryStrategy !== 'function') {
|
|
101
98
|
if (retryStrategy) {
|
|
102
|
-
logger.warn('invalid retryStrategy, ignoring', { strategyType: typeof retryStrategy, using: unknownRetry }, correlationId);
|
|
99
|
+
logger.warn('invalid retryStrategy, ignoring', { strategyType: typeof retryStrategy, using: 'unknownRetry (always retry)' }, correlationId);
|
|
103
100
|
retryStrategy = unknownRetry;
|
|
104
101
|
}
|
|
105
102
|
else retryStrategy = defaultRetry;
|
|
106
103
|
}
|
|
107
|
-
if (logLevel) {
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
104
|
+
if (logLevel && typeof logLevel === 'object' && !Array.isArray(logLevel)) {
|
|
105
|
+
const validated = {};
|
|
106
|
+
const response = validateLogLevel(logLevel, 'response');
|
|
107
|
+
const error = validateLogLevel(logLevel, 'error');
|
|
108
|
+
const request = validateLogLevel(logLevel, 'request');
|
|
109
|
+
|
|
110
|
+
if (response) validated.response = response;
|
|
111
|
+
if (error) validated.error = error;
|
|
112
|
+
if (request) validated.request = request;
|
|
113
|
+
validated.responseDetails = validateResponseDetails(logLevel.responseDetails, 'responseDetails', TYPE);
|
|
114
|
+
validated.responseName = validateResponseName(logLevel.responseName, 'responseName', RESPONSE);
|
|
115
|
+
logLevel = validated;
|
|
113
116
|
}
|
|
114
117
|
else {
|
|
115
118
|
logLevel = {};
|
|
@@ -146,20 +149,20 @@ const calculateDelay = (options, nbRetry, criteria, err, correlationId) => {
|
|
|
146
149
|
};
|
|
147
150
|
|
|
148
151
|
const isRetry = (options, nbRetry, criteria, err, correlationId) => {
|
|
149
|
-
let
|
|
152
|
+
let shouldRetry;
|
|
150
153
|
|
|
151
154
|
try {
|
|
152
|
-
|
|
155
|
+
shouldRetry = criteria.retryStrategy(nbRetry, err, options, correlationId);
|
|
153
156
|
}
|
|
154
157
|
catch (error) {
|
|
155
158
|
logger.warn('bad retry strategy', { error }, correlationId);
|
|
156
|
-
|
|
159
|
+
shouldRetry = true;
|
|
157
160
|
}
|
|
158
|
-
if (typeof
|
|
159
|
-
logger.warn('bad retry strategy', { error: `return of retryStrategy is a ${typeof
|
|
160
|
-
|
|
161
|
+
if (typeof shouldRetry !== 'boolean') {
|
|
162
|
+
logger.warn('bad retry strategy', { error: `return of retryStrategy is a ${typeof shouldRetry}` }, correlationId);
|
|
163
|
+
shouldRetry = true;
|
|
161
164
|
}
|
|
162
|
-
return
|
|
165
|
+
return shouldRetry;
|
|
163
166
|
};
|
|
164
167
|
|
|
165
168
|
const setResponse = (response, details) => {
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mimik/request-retry",
|
|
3
|
-
"version": "4.0.
|
|
3
|
+
"version": "4.0.11",
|
|
4
4
|
"description": "Request retry wrapping axios",
|
|
5
5
|
"main": "./index.js",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"scripts": {
|
|
8
|
-
"lint": "eslint . --no-error-on-unmatched-pattern",
|
|
9
8
|
"docs": "jsdoc2md index.js > README.md",
|
|
9
|
+
"lint": "eslint . --no-error-on-unmatched-pattern",
|
|
10
10
|
"test": "mocha --reporter mochawesome --bail --check-leaks --exit test/",
|
|
11
11
|
"test-ci": "c8 --reporter=lcov --reporter=text npm test",
|
|
12
12
|
"prepublishOnly": "npm run docs && npm run lint && npm run test-ci",
|
|
@@ -18,32 +18,36 @@
|
|
|
18
18
|
],
|
|
19
19
|
"author": "mimik technology inc <support@mimik.com> (https://developer.mimik.com/)",
|
|
20
20
|
"license": "MIT",
|
|
21
|
+
"engines": {
|
|
22
|
+
"node": ">=24.0.0"
|
|
23
|
+
},
|
|
21
24
|
"repository": {
|
|
22
25
|
"type": "git",
|
|
23
26
|
"url": "https://bitbucket.org/mimiktech/request-retry"
|
|
24
27
|
},
|
|
25
28
|
"dependencies": {
|
|
26
|
-
"@mimik/request-helper": "^2.0.
|
|
27
|
-
"@mimik/response-helper": "^4.0.
|
|
28
|
-
"@mimik/sumologic-winston-logger": "^2.
|
|
29
|
-
"axios": "1.13.
|
|
29
|
+
"@mimik/request-helper": "^2.0.6",
|
|
30
|
+
"@mimik/response-helper": "^4.0.11",
|
|
31
|
+
"@mimik/sumologic-winston-logger": "^2.2.2",
|
|
32
|
+
"axios": "1.13.6",
|
|
30
33
|
"bluebird": "3.7.2"
|
|
31
34
|
},
|
|
32
35
|
"devDependencies": {
|
|
33
|
-
"@eslint/js": "9.39.
|
|
34
|
-
"@mimik/eslint-plugin-document-env": "^2.0.
|
|
35
|
-
"@
|
|
36
|
+
"@eslint/js": "9.39.4",
|
|
37
|
+
"@mimik/eslint-plugin-document-env": "^2.0.9",
|
|
38
|
+
"@mimik/eslint-plugin-logger": "1.0.3",
|
|
39
|
+
"@stylistic/eslint-plugin": "5.10.0",
|
|
36
40
|
"body-parser": "2.2.2",
|
|
37
|
-
"c8": "
|
|
41
|
+
"c8": "11.0.0",
|
|
38
42
|
"chai": "6.2.2",
|
|
39
|
-
"eslint": "9.39.
|
|
43
|
+
"eslint": "9.39.4",
|
|
40
44
|
"eslint-plugin-import": "2.32.0",
|
|
41
45
|
"express": "5.2.1",
|
|
42
|
-
"globals": "17.
|
|
46
|
+
"globals": "17.4.0",
|
|
43
47
|
"husky": "9.1.7",
|
|
44
48
|
"jsdoc-to-markdown": "9.1.3",
|
|
45
49
|
"mocha": "11.7.5",
|
|
46
50
|
"mochawesome": "7.1.4",
|
|
47
|
-
"sinon": "21.0.
|
|
51
|
+
"sinon": "21.0.3"
|
|
48
52
|
}
|
|
49
53
|
}
|
package/.husky/pre-commit
DELETED
package/.husky/pre-push
DELETED
package/eslint.config.js
DELETED
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
import globals from 'globals';
|
|
2
|
-
import importPlugin from 'eslint-plugin-import';
|
|
3
|
-
import js from '@eslint/js';
|
|
4
|
-
import processDoc from '@mimik/eslint-plugin-document-env';
|
|
5
|
-
import stylistic from '@stylistic/eslint-plugin';
|
|
6
|
-
|
|
7
|
-
const MAX_LENGTH_LINE = 180;
|
|
8
|
-
const MAX_FUNCTION_PARAMETERS = 6;
|
|
9
|
-
const MAX_LINES_IN_FILES = 600;
|
|
10
|
-
const MAX_LINES_IN_FUNCTION = 150;
|
|
11
|
-
const MAX_STATEMENTS_IN_FUNCTION = 45;
|
|
12
|
-
const MIN_KEYS_IN_OBJECT = 10;
|
|
13
|
-
const MAX_COMPLEXITY = 30;
|
|
14
|
-
const ECMA_VERSION = 'latest';
|
|
15
|
-
const MAX_DEPTH = 6;
|
|
16
|
-
const ALLOWED_CONSTANTS = [0, 1, -1];
|
|
17
|
-
|
|
18
|
-
export default [
|
|
19
|
-
{
|
|
20
|
-
ignores: ['coverage/**', 'mochawesome-report/**', 'node_modules/**', 'dist/**'],
|
|
21
|
-
},
|
|
22
|
-
importPlugin.flatConfigs.recommended,
|
|
23
|
-
stylistic.configs.recommended,
|
|
24
|
-
js.configs.all,
|
|
25
|
-
{
|
|
26
|
-
plugins: {
|
|
27
|
-
processDoc,
|
|
28
|
-
},
|
|
29
|
-
languageOptions: {
|
|
30
|
-
ecmaVersion: ECMA_VERSION,
|
|
31
|
-
globals: {
|
|
32
|
-
...globals.mocha,
|
|
33
|
-
...globals.nodeBuiltin,
|
|
34
|
-
},
|
|
35
|
-
sourceType: 'module',
|
|
36
|
-
},
|
|
37
|
-
rules: {
|
|
38
|
-
'@stylistic/brace-style': ['warn', 'stroustrup', { allowSingleLine: true }],
|
|
39
|
-
'@stylistic/line-comment-position': ['off'],
|
|
40
|
-
'@stylistic/max-len': ['warn', MAX_LENGTH_LINE, { ignoreComments: true, ignoreStrings: true, ignoreRegExpLiterals: true }],
|
|
41
|
-
'@stylistic/quotes': ['warn', 'single'],
|
|
42
|
-
'@stylistic/semi': ['error', 'always'],
|
|
43
|
-
'capitalized-comments': ['off'],
|
|
44
|
-
'complexity': ['error', MAX_COMPLEXITY],
|
|
45
|
-
'curly': ['off'],
|
|
46
|
-
'id-length': ['error', { exceptions: ['x', 'y', 'z', 'i', 'j', 'k'] }],
|
|
47
|
-
'import/no-extraneous-dependencies': ['error', { devDependencies: true }],
|
|
48
|
-
'import/no-unresolved': ['error', { amd: true, caseSensitiveStrict: true, commonjs: true }],
|
|
49
|
-
'init-declarations': ['off'],
|
|
50
|
-
'linebreak-style': ['off'],
|
|
51
|
-
'max-depth': ['error', MAX_DEPTH],
|
|
52
|
-
'max-len': ['off'],
|
|
53
|
-
'max-lines': ['warn', { max: MAX_LINES_IN_FILES, skipComments: true, skipBlankLines: true }],
|
|
54
|
-
'max-lines-per-function': ['warn', { max: MAX_LINES_IN_FUNCTION, skipComments: true, skipBlankLines: true }],
|
|
55
|
-
'max-params': ['error', MAX_FUNCTION_PARAMETERS],
|
|
56
|
-
'max-statements': ['warn', MAX_STATEMENTS_IN_FUNCTION],
|
|
57
|
-
'no-confusing-arrow': ['off'],
|
|
58
|
-
'no-inline-comments': ['off'],
|
|
59
|
-
'no-magic-numbers': ['error', { ignore: ALLOWED_CONSTANTS, enforceConst: true, detectObjects: false }],
|
|
60
|
-
'no-process-env': ['error'],
|
|
61
|
-
'no-ternary': ['off'],
|
|
62
|
-
'no-undefined': ['off'],
|
|
63
|
-
'one-var': ['error', 'never'],
|
|
64
|
-
'processDoc/validate-document-env': ['error'],
|
|
65
|
-
'quotes': ['off'],
|
|
66
|
-
'sort-imports': ['error', { allowSeparatedGroups: true }],
|
|
67
|
-
'sort-keys': ['error', 'asc', { caseSensitive: true, minKeys: MIN_KEYS_IN_OBJECT, natural: false, allowLineSeparatedGroups: true }],
|
|
68
|
-
},
|
|
69
|
-
},
|
|
70
|
-
];
|
package/manual-test/httpMock.js
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
/* eslint-disable no-console */
|
|
2
|
-
import { OK } from '../lib/common.js';
|
|
3
|
-
import bodyParser from 'body-parser';
|
|
4
|
-
import express from 'express';
|
|
5
|
-
|
|
6
|
-
const PORT = 9000;
|
|
7
|
-
|
|
8
|
-
const app = express();
|
|
9
|
-
|
|
10
|
-
app.use(bodyParser.json());
|
|
11
|
-
app.get('/success', (req, res) => {
|
|
12
|
-
res.statusCode = OK;
|
|
13
|
-
|
|
14
|
-
res.send({ statusCode: OK });
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
app.listen(PORT, () => {
|
|
18
|
-
console.log('----->', `retry mock at ${PORT}`);
|
|
19
|
-
});
|
package/manual-test/man.js
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
/* eslint-disable no-console */
|
|
2
|
-
import './testEnv.js';
|
|
3
|
-
import { rpRetry } from '../index.js';
|
|
4
|
-
|
|
5
|
-
const DELAY = 1000;
|
|
6
|
-
const RETRY_TIMEOUT = 10;
|
|
7
|
-
const RETRY_RETRIES = 2;
|
|
8
|
-
|
|
9
|
-
const correlationId = `--request-retry-test--/${new Date().toISOString()}`;
|
|
10
|
-
|
|
11
|
-
const options = {
|
|
12
|
-
method: 'GET',
|
|
13
|
-
headers: {
|
|
14
|
-
'x-correlation-id': correlationId,
|
|
15
|
-
},
|
|
16
|
-
url: 'http://localhost:9080/test/retry',
|
|
17
|
-
json: true,
|
|
18
|
-
retry: {},
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
options.retry.delayStrategy = () => DELAY;
|
|
22
|
-
options.retry.timeout = RETRY_TIMEOUT;
|
|
23
|
-
options.retry.retries = RETRY_RETRIES;
|
|
24
|
-
|
|
25
|
-
rpRetry(options)
|
|
26
|
-
.catch(err => console.log(err));
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
/* eslint-disable no-console */
|
|
2
|
-
import { CREATED, OK, SYSTEM_ERROR } from '../lib/common.js';
|
|
3
|
-
import bodyParser from 'body-parser';
|
|
4
|
-
import express from 'express';
|
|
5
|
-
import { setTimeout } from 'node:timers';
|
|
6
|
-
|
|
7
|
-
const PORT = 9080;
|
|
8
|
-
const GET_NB_RETRY = 400;
|
|
9
|
-
const GET_TIMEOUT = 4000;
|
|
10
|
-
|
|
11
|
-
const app = express();
|
|
12
|
-
|
|
13
|
-
const config = {
|
|
14
|
-
port: PORT,
|
|
15
|
-
path: '/retry',
|
|
16
|
-
base: '/test',
|
|
17
|
-
get: {
|
|
18
|
-
nbRetry: GET_NB_RETRY,
|
|
19
|
-
success: OK,
|
|
20
|
-
error: SYSTEM_ERROR,
|
|
21
|
-
timeout: GET_TIMEOUT,
|
|
22
|
-
},
|
|
23
|
-
post: {
|
|
24
|
-
success: CREATED,
|
|
25
|
-
},
|
|
26
|
-
};
|
|
27
|
-
let nbRequest = 0;
|
|
28
|
-
let time = 0;
|
|
29
|
-
let message;
|
|
30
|
-
|
|
31
|
-
app.use(bodyParser.json());
|
|
32
|
-
app.get(`${config.base}${config.path}`, (req, res) => {
|
|
33
|
-
if (nbRequest === 0) message = 'Received first request';
|
|
34
|
-
else {
|
|
35
|
-
const timeLapse = Date.now() - time;
|
|
36
|
-
|
|
37
|
-
message = `Received ${nbRequest} retry with timelapse ${timeLapse}`;
|
|
38
|
-
}
|
|
39
|
-
console.log('----->', message);
|
|
40
|
-
time = Date.now();
|
|
41
|
-
|
|
42
|
-
if (nbRequest === config.get.nbRetry) {
|
|
43
|
-
res.statusCode = config.get.success;
|
|
44
|
-
res.send({ statusCode: config.get.success });
|
|
45
|
-
return;
|
|
46
|
-
}
|
|
47
|
-
nbRequest += 1;
|
|
48
|
-
setTimeout(() => {
|
|
49
|
-
res.statusCode = config.get.error;
|
|
50
|
-
res.send({ statusCode: config.get.error });
|
|
51
|
-
}, config.get.timeout);
|
|
52
|
-
});
|
|
53
|
-
app.post(`${config.base}${config.path}`, (req, res) => {
|
|
54
|
-
console.log('----->', 'Received a POST request');
|
|
55
|
-
res.statusCode = config.post.success;
|
|
56
|
-
res.send({ statusCode: config.post.success });
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
app.post('/logs/:id', (req, res) => {
|
|
60
|
-
console.log('--->', 'Received a log');
|
|
61
|
-
console.log('---> id:', req.params.id);
|
|
62
|
-
console.log('---> body:', req.body);
|
|
63
|
-
res.statusCode = config.post.success;
|
|
64
|
-
res.send({ statusCode: config.post.success });
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
app.listen(config.port, () => {
|
|
68
|
-
console.log('----->', `retry mock at ${config.port}`);
|
|
69
|
-
});
|
package/manual-test/testEnv.js
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
/* eslint no-process-env: "off" */
|
|
2
|
-
import process from 'node:process';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* The following environment variables are set for the test:
|
|
6
|
-
*
|
|
7
|
-
* | Env variable name | Description | Default | Comments |
|
|
8
|
-
* | ----------------- | ----------- | ------- | -------- |
|
|
9
|
-
* | SUMO_LOGIC_ENDPOINT | endpoint to use to log on sumologic | null
|
|
10
|
-
* | SUMO_LOGIC_COLLECTOR_CODE | code to use to log on sumologic | null
|
|
11
|
-
* | NO_STACK | flag to have a stack associated with the log | yes
|
|
12
|
-
* | LOG_LEVEL | log level to log | error
|
|
13
|
-
* | CONSOLE_LEVEL | log level to display | debug
|
|
14
|
-
* | LOG_MODE | log mode | none
|
|
15
|
-
*/
|
|
16
|
-
/*
|
|
17
|
-
*process.env.SUMO_LOGIC_ENDPOINT = 'http://localhost:9080/logs/';
|
|
18
|
-
*process.env.SUMO_LOGIC_COLLECTOR_CODE = '1234';
|
|
19
|
-
*/
|
|
20
|
-
process.env.SUMO_LOGIC_ENDPOINT = null;
|
|
21
|
-
process.env.SUMO_LOGIC_COLLECTOR_CODE = null;
|
|
22
|
-
process.env.NO_STACK = 'yes';
|
|
23
|
-
process.env.LOG_LEVEL = 'debug';
|
|
24
|
-
process.env.CONSOLE_LEVEL = 'debug';
|
|
25
|
-
process.env.LOG_MODE = 'none';
|
package/manual-test/testRace.js
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
/* eslint-disable no-console */
|
|
2
|
-
import { clearTimeout, setTimeout } from 'node:timers';
|
|
3
|
-
|
|
4
|
-
const MAX_INCR = 100000000;
|
|
5
|
-
const MAIN_TIMEOUT = 1; // in millisecond
|
|
6
|
-
|
|
7
|
-
const doSomething = () => {
|
|
8
|
-
let add = 0;
|
|
9
|
-
|
|
10
|
-
for (let i = 0; i < MAX_INCR; i += 1) {
|
|
11
|
-
add += 1;
|
|
12
|
-
console.log(add);
|
|
13
|
-
}
|
|
14
|
-
return add;
|
|
15
|
-
};
|
|
16
|
-
const timeout = MAIN_TIMEOUT;
|
|
17
|
-
|
|
18
|
-
const race = () => {
|
|
19
|
-
let result;
|
|
20
|
-
const mainTimeout = setTimeout(() => {
|
|
21
|
-
throw new Error('timeout');
|
|
22
|
-
}, timeout);
|
|
23
|
-
|
|
24
|
-
try {
|
|
25
|
-
result = doSomething();
|
|
26
|
-
}
|
|
27
|
-
catch (err) {
|
|
28
|
-
console.log(err);
|
|
29
|
-
result = 0;
|
|
30
|
-
}
|
|
31
|
-
clearTimeout(mainTimeout);
|
|
32
|
-
return result;
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
console.log(race());
|
package/manual-test/testRetry.js
DELETED
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
/* eslint-disable no-console */
|
|
2
|
-
import '../test/testEnv.js';
|
|
3
|
-
import { rpRetry } from '../index.js';
|
|
4
|
-
|
|
5
|
-
const TIMEOUT = 20000; // in millisecond
|
|
6
|
-
|
|
7
|
-
const options = {
|
|
8
|
-
method: 'GET',
|
|
9
|
-
url: 'http://api.swaggerhub.com/apis/mimik/Boxes/1.2.1', // 'http://www.google.com', // 'http://blah.com', // 'http://localhost:9070/test/retry', // 'http://www.google.com', // 'http://localhost:8080/mIT/v1/admin/healthCheck1', // 'http://blah.com'
|
|
10
|
-
headers: {
|
|
11
|
-
'x-correlation-id': '---test-request-retry---',
|
|
12
|
-
},
|
|
13
|
-
json: true,
|
|
14
|
-
timeout: TIMEOUT,
|
|
15
|
-
/*
|
|
16
|
-
* retry: {
|
|
17
|
-
* all default: maxRetry: 2, retryDelay: 1000ms, maxTimeout: 50s, logLevel: 3 silly, type response, retryDelayStrategy: setDelay, retryStrategy: defaultRetry
|
|
18
|
-
* retries: -1, // => 0
|
|
19
|
-
* retries: 20, // => 15
|
|
20
|
-
* retries: 'joe', // => 2
|
|
21
|
-
* retries: 0, // OK
|
|
22
|
-
* delay: 0, // => 20ms
|
|
23
|
-
* delay: 100000, // => 30000ms
|
|
24
|
-
* delay: 'joe', // => 1000ms
|
|
25
|
-
* delay: 1, // => 20ms
|
|
26
|
-
* delayStrategy: 'joe', // => setDelay returning retryDelay
|
|
27
|
-
* delayStrategy: function badDelay() { return 'joe' }, // => no change then bad delay strategy => retryDelay
|
|
28
|
-
* delayStrategy: function userDelay(...args) { return (Math.pow(2, args[0]) * 100) + Math.floor(Math.random() * 50) }, // OK
|
|
29
|
-
* retryStrategy: 'joe', // => unknownRetry returning false
|
|
30
|
-
* retryStrategy: function badRetry() { return 'joe' }, // => no change then bad retry strategy => false
|
|
31
|
-
* retryStrategy: function userRetry(...args) { // OK
|
|
32
|
-
* const { statusCode } = args[1]; // err
|
|
33
|
-
*
|
|
34
|
-
* return statusCode && (Math.floor(statusCode/100) !== 5 || statusCode !== 429); // retry if there is no statusCode or if there is 500 range statusCode or 429
|
|
35
|
-
* },
|
|
36
|
-
* retryStrategy: function invalidRetry(...args) { // defaultRetry
|
|
37
|
-
* const { statusCode } = args[34]; // null value
|
|
38
|
-
*
|
|
39
|
-
* return true // no retry
|
|
40
|
-
* },
|
|
41
|
-
* timeout: 0, // => 10s
|
|
42
|
-
* timeout: 150, // => 120s
|
|
43
|
-
* timeout: 'joe', // => 50s
|
|
44
|
-
* logLevel: {
|
|
45
|
-
* response: 'test', // silly with warning
|
|
46
|
-
* error: 1234, // => silly with warning
|
|
47
|
-
* request: null, // => silly no warning
|
|
48
|
-
* responseDetails: 'test', // => type with warning
|
|
49
|
-
* responseName: '', // => response with warning
|
|
50
|
-
* },
|
|
51
|
-
* logLevel: {
|
|
52
|
-
* response: 'info', // ok all the other value to default no warning
|
|
53
|
-
* },
|
|
54
|
-
* }
|
|
55
|
-
*/
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
const sDate = Date.now();
|
|
59
|
-
|
|
60
|
-
rpRetry(options)
|
|
61
|
-
.then(response => console.log('success', { response: typeof response }, Date.now() - sDate))
|
|
62
|
-
.catch((err) => {
|
|
63
|
-
console.log('failure', { error: err, info: err.info }, Date.now() - sDate);
|
|
64
|
-
});
|
package/test/retryMock.js
DELETED
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
/* eslint-disable no-console */
|
|
2
|
-
import { CREATED, OK, SYSTEM_ERROR } from '../lib/common.js';
|
|
3
|
-
import bodyParser from 'body-parser';
|
|
4
|
-
import express from 'express';
|
|
5
|
-
import process from 'node:process';
|
|
6
|
-
import { setTimeout } from 'node:timers';
|
|
7
|
-
|
|
8
|
-
const TIMEOUT = 3000;
|
|
9
|
-
const EXIT_OK = 0;
|
|
10
|
-
const PORT = 9070;
|
|
11
|
-
const GET_NB_RETRY = 400;
|
|
12
|
-
|
|
13
|
-
const app = express();
|
|
14
|
-
const config = {
|
|
15
|
-
port: PORT,
|
|
16
|
-
path: '/retry',
|
|
17
|
-
base: '/test',
|
|
18
|
-
get: {
|
|
19
|
-
nbRetry: GET_NB_RETRY,
|
|
20
|
-
success: OK,
|
|
21
|
-
error: SYSTEM_ERROR,
|
|
22
|
-
},
|
|
23
|
-
post: {
|
|
24
|
-
success: CREATED,
|
|
25
|
-
},
|
|
26
|
-
};
|
|
27
|
-
let nbRequest = 0;
|
|
28
|
-
let time = 0;
|
|
29
|
-
let message;
|
|
30
|
-
|
|
31
|
-
app.use(bodyParser.json());
|
|
32
|
-
app.get(`${config.base}${config.path}`, (req, res) => {
|
|
33
|
-
if (nbRequest === 0) message = 'Received first request';
|
|
34
|
-
else {
|
|
35
|
-
const timeLapse = Date.now() - time;
|
|
36
|
-
|
|
37
|
-
message = `Received ${nbRequest} retry with timelapse ${timeLapse}`;
|
|
38
|
-
}
|
|
39
|
-
console.log('----->', message);
|
|
40
|
-
time = Date.now();
|
|
41
|
-
|
|
42
|
-
if (nbRequest === config.get.nbRetry) {
|
|
43
|
-
res.statusCode = config.get.success;
|
|
44
|
-
res.send({ statusCode: config.get.success });
|
|
45
|
-
return;
|
|
46
|
-
}
|
|
47
|
-
nbRequest += 1;
|
|
48
|
-
res.statusCode = config.get.error;
|
|
49
|
-
|
|
50
|
-
res.send({ statusCode: config.get.error });
|
|
51
|
-
});
|
|
52
|
-
app.post(`${config.base}${config.path}`, (req, res) => {
|
|
53
|
-
console.log('----->', 'Received a POST request');
|
|
54
|
-
res.statusCode = config.post.success;
|
|
55
|
-
res.send({ statusCode: config.post.success });
|
|
56
|
-
});
|
|
57
|
-
app.get('/stop', (req, res) => {
|
|
58
|
-
console.log('----->', 'Received a stop');
|
|
59
|
-
res.statusCode = config.get.success;
|
|
60
|
-
res.send({ statusCode: config.get.success });
|
|
61
|
-
setTimeout(() => {
|
|
62
|
-
process.exit(EXIT_OK);
|
|
63
|
-
}, TIMEOUT);
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
const listen = () => {
|
|
67
|
-
app.listen(config.port, () => {
|
|
68
|
-
console.log('----->', `retry mock at ${config.port}`);
|
|
69
|
-
});
|
|
70
|
-
};
|
|
71
|
-
|
|
72
|
-
export {
|
|
73
|
-
listen,
|
|
74
|
-
};
|
package/test/rpRetry.spec.js
DELETED
|
@@ -1,432 +0,0 @@
|
|
|
1
|
-
/* eslint-disable max-lines-per-function */
|
|
2
|
-
import './testEnv.js';
|
|
3
|
-
import { afterEach, before, beforeEach, describe, it } from 'mocha';
|
|
4
|
-
import { assert, spy } from 'sinon';
|
|
5
|
-
import { expect } from 'chai';
|
|
6
|
-
import { getCorrelationId } from '@mimik/request-helper';
|
|
7
|
-
import { listen } from './retryMock.js';
|
|
8
|
-
import logger from '@mimik/sumologic-winston-logger';
|
|
9
|
-
import { rpRetry } from '../index.js';
|
|
10
|
-
|
|
11
|
-
const TIMEOUT = 50000;
|
|
12
|
-
const RETRY_DELAY = 10000;
|
|
13
|
-
const OUT_OF_SCOPE_RETRY_DELAY = 100000;
|
|
14
|
-
const IN_SCOPE_RETRY_DELAY = 20000;
|
|
15
|
-
const RETRY_TIMEOUT = 10;
|
|
16
|
-
const OUT_OF_SCOPE_RETRY_TIMEOUT = 150;
|
|
17
|
-
const IN_SCOPE_RETRY_TIMEOUT = 50;
|
|
18
|
-
const RETRY_RETRIES = 2;
|
|
19
|
-
const OUT_OF_SCOPE_RETRIES = 20;
|
|
20
|
-
const IN_SCOPE_RETRIES = 10;
|
|
21
|
-
const DELAY_STRATEGY_RESPONSE_1 = -20;
|
|
22
|
-
const DELAY_STRATEGY_RESPONSE_2 = 50;
|
|
23
|
-
const DELAY_STRATEGY_RESPONSE_3 = 100;
|
|
24
|
-
const CALL_ARG_MESSAGE = 0;
|
|
25
|
-
const FIRST_CALL = 0;
|
|
26
|
-
/*
|
|
27
|
-
* const DEFAULT_NO_MESSAGE = 'no message';
|
|
28
|
-
* const NO_ERROR = 'not an error object';
|
|
29
|
-
*/
|
|
30
|
-
|
|
31
|
-
describe('request-retry Unit Tests', () => {
|
|
32
|
-
const correlationId = getCorrelationId('--request-retry-test--');
|
|
33
|
-
// let loggerSpyError;
|
|
34
|
-
let loggerSpyWarn;
|
|
35
|
-
let loggerSpyInfo;
|
|
36
|
-
|
|
37
|
-
before(() => {
|
|
38
|
-
listen();
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
describe('rpRetry(options) options validations prerun', () => {
|
|
42
|
-
const options = {
|
|
43
|
-
method: 'POST',
|
|
44
|
-
headers: {
|
|
45
|
-
'x-correlation-id': correlationId,
|
|
46
|
-
},
|
|
47
|
-
url: 'http://localhost:9070/test/retry',
|
|
48
|
-
json: true,
|
|
49
|
-
retry: {},
|
|
50
|
-
};
|
|
51
|
-
beforeEach(() => {
|
|
52
|
-
loggerSpyWarn = spy(logger, 'warn');
|
|
53
|
-
});
|
|
54
|
-
afterEach(() => {
|
|
55
|
-
options.retry = {};
|
|
56
|
-
loggerSpyWarn.restore();
|
|
57
|
-
});
|
|
58
|
-
it('should not generate warning: null retry, using default', () => {
|
|
59
|
-
options.retry = null;
|
|
60
|
-
return rpRetry(options).then(() => {
|
|
61
|
-
assert.notCalled(loggerSpyWarn);
|
|
62
|
-
});
|
|
63
|
-
});
|
|
64
|
-
it('should not generate warning: empty retry, using default', () => rpRetry(options).then(() => {
|
|
65
|
-
assert.notCalled(loggerSpyWarn);
|
|
66
|
-
}));
|
|
67
|
-
it('should generate warning: out of scope retries: -1 becoming min', () => {
|
|
68
|
-
options.retry.retries = -1;
|
|
69
|
-
return rpRetry(options).then(() => {
|
|
70
|
-
assert.calledOnce(loggerSpyWarn);
|
|
71
|
-
const callArgs = loggerSpyWarn.getCall(FIRST_CALL).args;
|
|
72
|
-
expect(callArgs[CALL_ARG_MESSAGE]).to.be.a('string');
|
|
73
|
-
expect(callArgs[CALL_ARG_MESSAGE]).to.equal('out of scope retries, reset to min');
|
|
74
|
-
});
|
|
75
|
-
});
|
|
76
|
-
it('should generate warning: out of scope retries: 20 becoming max', () => {
|
|
77
|
-
options.retry.retries = OUT_OF_SCOPE_RETRIES;
|
|
78
|
-
return rpRetry(options).then(() => {
|
|
79
|
-
assert.calledOnce(loggerSpyWarn);
|
|
80
|
-
const callArgs = loggerSpyWarn.getCall(FIRST_CALL).args;
|
|
81
|
-
expect(callArgs[CALL_ARG_MESSAGE]).to.be.a('string');
|
|
82
|
-
expect(callArgs[CALL_ARG_MESSAGE]).to.equal('out of scope retries, reset to max');
|
|
83
|
-
});
|
|
84
|
-
});
|
|
85
|
-
it('should not generate warning: in scope retries', () => {
|
|
86
|
-
options.retry.retries = IN_SCOPE_RETRIES;
|
|
87
|
-
return rpRetry(options).then(() => {
|
|
88
|
-
assert.notCalled(loggerSpyWarn);
|
|
89
|
-
});
|
|
90
|
-
});
|
|
91
|
-
it('should generate warning: invalid retries: "notANumber" becoming default', () => {
|
|
92
|
-
options.retry.retries = 'notANumber';
|
|
93
|
-
return rpRetry(options).then(() => {
|
|
94
|
-
assert.calledOnce(loggerSpyWarn);
|
|
95
|
-
const callArgs = loggerSpyWarn.getCall(FIRST_CALL).args;
|
|
96
|
-
expect(callArgs[CALL_ARG_MESSAGE]).to.be.a('string');
|
|
97
|
-
expect(callArgs[CALL_ARG_MESSAGE]).to.equal('invalid retries, reset to default');
|
|
98
|
-
});
|
|
99
|
-
});
|
|
100
|
-
it('should generate warning: out of scope delay: 0 becoming min', () => {
|
|
101
|
-
options.retry.delay = 0;
|
|
102
|
-
return rpRetry(options).then(() => {
|
|
103
|
-
assert.calledOnce(loggerSpyWarn);
|
|
104
|
-
const callArgs = loggerSpyWarn.getCall(FIRST_CALL).args;
|
|
105
|
-
expect(callArgs[CALL_ARG_MESSAGE]).to.be.a('string');
|
|
106
|
-
expect(callArgs[CALL_ARG_MESSAGE]).to.equal('out of scope delay, reset to min');
|
|
107
|
-
});
|
|
108
|
-
});
|
|
109
|
-
it('should generate warning: out of scope delay: 100000 becoming max', () => {
|
|
110
|
-
options.retry.delay = OUT_OF_SCOPE_RETRY_DELAY;
|
|
111
|
-
return rpRetry(options).then(() => {
|
|
112
|
-
assert.calledOnce(loggerSpyWarn);
|
|
113
|
-
const callArgs = loggerSpyWarn.getCall(FIRST_CALL).args;
|
|
114
|
-
expect(callArgs[CALL_ARG_MESSAGE]).to.be.a('string');
|
|
115
|
-
expect(callArgs[CALL_ARG_MESSAGE]).to.equal('out of scope delay, reset to max');
|
|
116
|
-
});
|
|
117
|
-
});
|
|
118
|
-
it('should generate warning: invalid delay: "notANumber" becoming default', () => {
|
|
119
|
-
options.retry.delay = 'notANumber';
|
|
120
|
-
return rpRetry(options).then(() => {
|
|
121
|
-
assert.calledOnce(loggerSpyWarn);
|
|
122
|
-
const callArgs = loggerSpyWarn.getCall(FIRST_CALL).args;
|
|
123
|
-
expect(callArgs[CALL_ARG_MESSAGE]).to.be.a('string');
|
|
124
|
-
expect(callArgs[CALL_ARG_MESSAGE]).to.equal('invalid delay, reset to default');
|
|
125
|
-
});
|
|
126
|
-
});
|
|
127
|
-
it('should not generate warning: in scope delay', () => {
|
|
128
|
-
options.retry.delay = IN_SCOPE_RETRY_DELAY;
|
|
129
|
-
return rpRetry(options).then(() => {
|
|
130
|
-
assert.notCalled(loggerSpyWarn);
|
|
131
|
-
});
|
|
132
|
-
});
|
|
133
|
-
it('should generate warning: out of scope timeout: 0 becoming min', () => {
|
|
134
|
-
options.retry.timeout = 0;
|
|
135
|
-
return rpRetry(options).then(() => {
|
|
136
|
-
assert.calledOnce(loggerSpyWarn);
|
|
137
|
-
const callArgs = loggerSpyWarn.getCall(FIRST_CALL).args;
|
|
138
|
-
expect(callArgs[CALL_ARG_MESSAGE]).to.be.a('string');
|
|
139
|
-
expect(callArgs[CALL_ARG_MESSAGE]).to.equal('out of scope timeout, reset to min');
|
|
140
|
-
});
|
|
141
|
-
});
|
|
142
|
-
it('should generate warning: out of scope timeout: 150 becoming max', () => {
|
|
143
|
-
options.retry.timeout = OUT_OF_SCOPE_RETRY_TIMEOUT;
|
|
144
|
-
return rpRetry(options).then(() => {
|
|
145
|
-
assert.calledOnce(loggerSpyWarn);
|
|
146
|
-
const callArgs = loggerSpyWarn.getCall(FIRST_CALL).args;
|
|
147
|
-
expect(callArgs[CALL_ARG_MESSAGE]).to.be.a('string');
|
|
148
|
-
expect(callArgs[CALL_ARG_MESSAGE]).to.equal('out of scope timeout, reset to max');
|
|
149
|
-
});
|
|
150
|
-
});
|
|
151
|
-
it('should generate warning: invalid timeout: "notANumber" becoming default', () => {
|
|
152
|
-
options.retry.timeout = 'notANumber';
|
|
153
|
-
return rpRetry(options).then(() => {
|
|
154
|
-
assert.calledOnce(loggerSpyWarn);
|
|
155
|
-
const callArgs = loggerSpyWarn.getCall(FIRST_CALL).args;
|
|
156
|
-
expect(callArgs[CALL_ARG_MESSAGE]).to.be.a('string');
|
|
157
|
-
expect(callArgs[CALL_ARG_MESSAGE]).to.equal('invalid timeout, reset to default');
|
|
158
|
-
});
|
|
159
|
-
});
|
|
160
|
-
it('should not generate warning: in scope timeout', () => {
|
|
161
|
-
options.retry.timeout = IN_SCOPE_RETRY_TIMEOUT;
|
|
162
|
-
return rpRetry(options).then(() => {
|
|
163
|
-
assert.notCalled(loggerSpyWarn);
|
|
164
|
-
});
|
|
165
|
-
});
|
|
166
|
-
it('should generate warning: invalid retryStrategy: "notAFunction" becoming default function', () => {
|
|
167
|
-
options.retry.retryStrategy = 'notAFunction';
|
|
168
|
-
return rpRetry(options).then(() => {
|
|
169
|
-
assert.calledOnce(loggerSpyWarn);
|
|
170
|
-
const callArgs = loggerSpyWarn.getCall(FIRST_CALL).args;
|
|
171
|
-
expect(callArgs[CALL_ARG_MESSAGE]).to.be.a('string');
|
|
172
|
-
expect(callArgs[CALL_ARG_MESSAGE]).to.equal('invalid retryStrategy, ignoring');
|
|
173
|
-
});
|
|
174
|
-
});
|
|
175
|
-
it('should generate warning: invalid delayStrategy: "notAFunction" becoming delay or default', () => {
|
|
176
|
-
options.retry.delayStrategy = 'notAFunction';
|
|
177
|
-
return rpRetry(options).then(() => {
|
|
178
|
-
assert.calledOnce(loggerSpyWarn);
|
|
179
|
-
const callArgs = loggerSpyWarn.getCall(FIRST_CALL).args;
|
|
180
|
-
expect(callArgs[CALL_ARG_MESSAGE]).to.be.a('string');
|
|
181
|
-
expect(callArgs[CALL_ARG_MESSAGE]).to.equal('invalid delayStrategy, using delay');
|
|
182
|
-
});
|
|
183
|
-
});
|
|
184
|
-
it('should generate multiple warnings', () => {
|
|
185
|
-
options.retry.delayStrategy = 'notAFunction';
|
|
186
|
-
options.retry.retries = 'notANumber';
|
|
187
|
-
return rpRetry(options).then(() => {
|
|
188
|
-
assert.calledTwice(loggerSpyWarn);
|
|
189
|
-
});
|
|
190
|
-
});
|
|
191
|
-
it('should generate warning: invalid logLevel.request, reset to default', () => {
|
|
192
|
-
options.retry.logLevel = {
|
|
193
|
-
request: 'unknownLevel',
|
|
194
|
-
};
|
|
195
|
-
return rpRetry(options).then(() => {
|
|
196
|
-
assert.calledOnce(loggerSpyWarn);
|
|
197
|
-
const callArgs = loggerSpyWarn.getCall(FIRST_CALL).args;
|
|
198
|
-
expect(callArgs[CALL_ARG_MESSAGE]).to.be.a('string');
|
|
199
|
-
expect(callArgs[CALL_ARG_MESSAGE]).to.equal('invalid logLevel.request, reset to default');
|
|
200
|
-
});
|
|
201
|
-
});
|
|
202
|
-
it('should not generate warning: correct logLevel (info)', () => {
|
|
203
|
-
loggerSpyInfo = spy(logger, 'info');
|
|
204
|
-
options.retry.logLevel = {
|
|
205
|
-
response: 'info',
|
|
206
|
-
};
|
|
207
|
-
return rpRetry(options).then(() => {
|
|
208
|
-
assert.notCalled(loggerSpyWarn);
|
|
209
|
-
assert.calledOnce(loggerSpyInfo);
|
|
210
|
-
loggerSpyInfo.restore();
|
|
211
|
-
});
|
|
212
|
-
});
|
|
213
|
-
it('should generate warning: invalid logLevel.responseDetails, reset to default', () => {
|
|
214
|
-
options.retry.logLevel = {
|
|
215
|
-
responseDetails: 'unknownDetails',
|
|
216
|
-
};
|
|
217
|
-
return rpRetry(options).then(() => {
|
|
218
|
-
assert.calledOnce(loggerSpyWarn);
|
|
219
|
-
const callArgs = loggerSpyWarn.getCall(FIRST_CALL).args;
|
|
220
|
-
expect(callArgs[CALL_ARG_MESSAGE]).to.be.a('string');
|
|
221
|
-
expect(callArgs[CALL_ARG_MESSAGE]).to.equal('invalid logLevel.responseDetails, reset to default');
|
|
222
|
-
});
|
|
223
|
-
});
|
|
224
|
-
it('should not generate warning: correct logLevelResponseName', () => {
|
|
225
|
-
options.retry.logLevel = {
|
|
226
|
-
responseName: 'aName',
|
|
227
|
-
};
|
|
228
|
-
return rpRetry(options).then(() => {
|
|
229
|
-
assert.notCalled(loggerSpyWarn);
|
|
230
|
-
});
|
|
231
|
-
});
|
|
232
|
-
it('should generate warning: invalid logLevel.responseName (""), reset to default', () => {
|
|
233
|
-
options.retry.logLevel = {
|
|
234
|
-
responseName: '',
|
|
235
|
-
};
|
|
236
|
-
return rpRetry(options).then(() => {
|
|
237
|
-
assert.calledOnce(loggerSpyWarn);
|
|
238
|
-
const callArgs = loggerSpyWarn.getCall(FIRST_CALL).args;
|
|
239
|
-
expect(callArgs[CALL_ARG_MESSAGE]).to.be.a('string');
|
|
240
|
-
expect(callArgs[CALL_ARG_MESSAGE]).to.equal('invalid logLevel.responseName, reset to default');
|
|
241
|
-
});
|
|
242
|
-
});
|
|
243
|
-
it('should generate warning: invalid logLevel.responseName (" "), reset to default', () => {
|
|
244
|
-
options.retry.logLevel = {
|
|
245
|
-
responseName: ' ',
|
|
246
|
-
};
|
|
247
|
-
return rpRetry(options).then(() => {
|
|
248
|
-
assert.calledOnce(loggerSpyWarn);
|
|
249
|
-
const callArgs = loggerSpyWarn.getCall(FIRST_CALL).args;
|
|
250
|
-
expect(callArgs[CALL_ARG_MESSAGE]).to.be.a('string');
|
|
251
|
-
expect(callArgs[CALL_ARG_MESSAGE]).to.equal('invalid logLevel.responseName, reset to default');
|
|
252
|
-
});
|
|
253
|
-
});
|
|
254
|
-
});
|
|
255
|
-
describe('rpRetry(options) request mapping and response details', () => {
|
|
256
|
-
it('should succeed with body mapped to axios data', () => rpRetry({
|
|
257
|
-
method: 'POST',
|
|
258
|
-
headers: { 'x-correlation-id': correlationId },
|
|
259
|
-
url: 'http://localhost:9070/test/retry',
|
|
260
|
-
body: { test: 'data' },
|
|
261
|
-
retry: {},
|
|
262
|
-
}).then((response) => {
|
|
263
|
-
expect(response).to.have.property('statusCode');
|
|
264
|
-
}));
|
|
265
|
-
it('should succeed with json object mapped to axios data', () => rpRetry({
|
|
266
|
-
method: 'POST',
|
|
267
|
-
headers: { 'x-correlation-id': correlationId },
|
|
268
|
-
url: 'http://localhost:9070/test/retry',
|
|
269
|
-
json: { test: 'data' },
|
|
270
|
-
retry: {},
|
|
271
|
-
}).then((response) => {
|
|
272
|
-
expect(response).to.have.property('statusCode');
|
|
273
|
-
}));
|
|
274
|
-
it('should succeed with qs mapped to axios params', () => rpRetry({
|
|
275
|
-
method: 'POST',
|
|
276
|
-
headers: { 'x-correlation-id': correlationId },
|
|
277
|
-
url: 'http://localhost:9070/test/retry',
|
|
278
|
-
json: true,
|
|
279
|
-
qs: { key: 'value' },
|
|
280
|
-
retry: {},
|
|
281
|
-
}).then((response) => {
|
|
282
|
-
expect(response).to.have.property('statusCode');
|
|
283
|
-
}));
|
|
284
|
-
it('should throw a System error on network failure', () => rpRetry({
|
|
285
|
-
method: 'GET',
|
|
286
|
-
headers: { 'x-correlation-id': correlationId },
|
|
287
|
-
url: 'http://localhost:1/unreachable',
|
|
288
|
-
retry: { retries: 0, timeout: 10 },
|
|
289
|
-
}).catch((err) => {
|
|
290
|
-
expect(err.name).to.be.a('string');
|
|
291
|
-
}));
|
|
292
|
-
it('should log response with count details', () => {
|
|
293
|
-
const loggerSpySilly = spy(logger, 'silly');
|
|
294
|
-
|
|
295
|
-
return rpRetry({
|
|
296
|
-
method: 'POST',
|
|
297
|
-
headers: { 'x-correlation-id': correlationId },
|
|
298
|
-
url: 'http://localhost:9070/test/retry',
|
|
299
|
-
json: true,
|
|
300
|
-
retry: {
|
|
301
|
-
logLevel: {
|
|
302
|
-
response: 'silly',
|
|
303
|
-
responseDetails: 'count',
|
|
304
|
-
},
|
|
305
|
-
},
|
|
306
|
-
}).then(() => {
|
|
307
|
-
assert.calledOnce(loggerSpySilly);
|
|
308
|
-
loggerSpySilly.restore();
|
|
309
|
-
});
|
|
310
|
-
});
|
|
311
|
-
});
|
|
312
|
-
describe('rpRetry(options) options validation run', function Test() {
|
|
313
|
-
this.timeout(TIMEOUT);
|
|
314
|
-
const options = {
|
|
315
|
-
method: 'GET',
|
|
316
|
-
headers: {
|
|
317
|
-
'x-correlation-id': correlationId,
|
|
318
|
-
},
|
|
319
|
-
url: 'http://localhost:9070/test/retry',
|
|
320
|
-
json: true,
|
|
321
|
-
retry: {},
|
|
322
|
-
};
|
|
323
|
-
beforeEach(() => {
|
|
324
|
-
loggerSpyWarn = spy(logger, 'warn');
|
|
325
|
-
});
|
|
326
|
-
afterEach(() => {
|
|
327
|
-
options.retry = {};
|
|
328
|
-
loggerSpyWarn.restore();
|
|
329
|
-
});
|
|
330
|
-
it('should generate warning: bad retry strategy (throwing an error), ignoring and using default retries', () => {
|
|
331
|
-
options.retry.retryStrategy = () => {
|
|
332
|
-
throw new Error('this is a test');
|
|
333
|
-
};
|
|
334
|
-
options.retry.retries = RETRY_RETRIES;
|
|
335
|
-
return rpRetry(options)
|
|
336
|
-
.catch(() => {
|
|
337
|
-
assert.called(loggerSpyWarn);
|
|
338
|
-
const callArgs = loggerSpyWarn.getCall(FIRST_CALL).args;
|
|
339
|
-
expect(callArgs[CALL_ARG_MESSAGE]).to.be.a('string');
|
|
340
|
-
expect(callArgs[CALL_ARG_MESSAGE]).to.equal('bad retry strategy');
|
|
341
|
-
});
|
|
342
|
-
});
|
|
343
|
-
it('should generate warning: bad retry strategy (not returning a boolean), ignoring and using default retries', () => {
|
|
344
|
-
options.retry.retryStrategy = () => 'notABoolean';
|
|
345
|
-
options.retry.retries = RETRY_RETRIES;
|
|
346
|
-
return rpRetry(options)
|
|
347
|
-
.catch(() => {
|
|
348
|
-
assert.called(loggerSpyWarn);
|
|
349
|
-
const callArgs = loggerSpyWarn.getCall(FIRST_CALL).args;
|
|
350
|
-
expect(callArgs[CALL_ARG_MESSAGE]).to.be.a('string');
|
|
351
|
-
expect(callArgs[CALL_ARG_MESSAGE]).to.equal('bad retry strategy');
|
|
352
|
-
});
|
|
353
|
-
});
|
|
354
|
-
it('should not generate warning for retry strategy, but warn on request error', () => {
|
|
355
|
-
options.retry.retryStrategy = () => false;
|
|
356
|
-
options.retry.retries = RETRY_RETRIES;
|
|
357
|
-
return rpRetry(options)
|
|
358
|
-
.catch(() => {
|
|
359
|
-
assert.called(loggerSpyWarn);
|
|
360
|
-
});
|
|
361
|
-
});
|
|
362
|
-
it('should generate warning: bad delay strategy (throwing an error), ignoring and using default delay', () => {
|
|
363
|
-
options.retry.delayStrategy = () => {
|
|
364
|
-
throw new Error('this is a test');
|
|
365
|
-
};
|
|
366
|
-
options.retry.retries = RETRY_RETRIES;
|
|
367
|
-
return rpRetry(options)
|
|
368
|
-
.catch(() => {
|
|
369
|
-
assert.called(loggerSpyWarn);
|
|
370
|
-
const callArgs = loggerSpyWarn.getCall(FIRST_CALL).args;
|
|
371
|
-
expect(callArgs[CALL_ARG_MESSAGE]).to.be.a('string');
|
|
372
|
-
expect(callArgs[CALL_ARG_MESSAGE]).to.equal('bad delay strategy');
|
|
373
|
-
});
|
|
374
|
-
});
|
|
375
|
-
it('should generate warning: bad delay strategy (not returning a number), ignoring and using default delay', () => {
|
|
376
|
-
options.retry.delayStrategy = () => 'notANumber';
|
|
377
|
-
options.retry.retries = RETRY_RETRIES;
|
|
378
|
-
return rpRetry(options)
|
|
379
|
-
.catch(() => {
|
|
380
|
-
assert.called(loggerSpyWarn);
|
|
381
|
-
const callArgs = loggerSpyWarn.getCall(FIRST_CALL).args;
|
|
382
|
-
expect(callArgs[CALL_ARG_MESSAGE]).to.be.a('string');
|
|
383
|
-
expect(callArgs[CALL_ARG_MESSAGE]).to.equal('bad delay strategy');
|
|
384
|
-
});
|
|
385
|
-
});
|
|
386
|
-
it('should generate warning: bad delay strategy (returning an out of scope number), ignoring and using default delay', () => {
|
|
387
|
-
options.retry.delayStrategy = () => DELAY_STRATEGY_RESPONSE_1;
|
|
388
|
-
options.retry.retries = RETRY_RETRIES;
|
|
389
|
-
return rpRetry(options)
|
|
390
|
-
.catch(() => {
|
|
391
|
-
assert.called(loggerSpyWarn);
|
|
392
|
-
const callArgs = loggerSpyWarn.getCall(FIRST_CALL).args;
|
|
393
|
-
expect(callArgs[CALL_ARG_MESSAGE]).to.be.a('string');
|
|
394
|
-
expect(callArgs[CALL_ARG_MESSAGE]).to.equal('calculated delay out of scope: using delay or default');
|
|
395
|
-
});
|
|
396
|
-
});
|
|
397
|
-
it('should not generate warning for delay strategy, but warn on request error', () => {
|
|
398
|
-
options.retry.delayStrategy = () => DELAY_STRATEGY_RESPONSE_2;
|
|
399
|
-
options.retry.retries = RETRY_RETRIES;
|
|
400
|
-
return rpRetry(options)
|
|
401
|
-
.catch(() => {
|
|
402
|
-
assert.called(loggerSpyWarn);
|
|
403
|
-
});
|
|
404
|
-
});
|
|
405
|
-
it('should generate a timeout error', () => {
|
|
406
|
-
options.retry.delayStrategy = () => RETRY_DELAY;
|
|
407
|
-
options.retry.timeout = RETRY_TIMEOUT;
|
|
408
|
-
options.retry.retries = RETRY_RETRIES;
|
|
409
|
-
return rpRetry(options)
|
|
410
|
-
.catch((err) => {
|
|
411
|
-
expect(err.name).to.equal('System');
|
|
412
|
-
expect(err.cause.name).to.equal('TimeoutError');
|
|
413
|
-
// assert.called(loggerSpyWarn);
|
|
414
|
-
});
|
|
415
|
-
});
|
|
416
|
-
it('should stop mock server', () => rpRetry({
|
|
417
|
-
method: 'GET',
|
|
418
|
-
headers: {
|
|
419
|
-
'x-correlation-id': correlationId,
|
|
420
|
-
},
|
|
421
|
-
url: 'http://localhost:9070/stop',
|
|
422
|
-
retry: {
|
|
423
|
-
delayStrategy: () => DELAY_STRATEGY_RESPONSE_3,
|
|
424
|
-
retries: 1,
|
|
425
|
-
},
|
|
426
|
-
})
|
|
427
|
-
.catch((err) => {
|
|
428
|
-
expect(err.name).to.equal('System');
|
|
429
|
-
expect(err.cause.name).to.equal('TimeoutError');
|
|
430
|
-
}));
|
|
431
|
-
});
|
|
432
|
-
});
|
package/test/testEnv.js
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
/* eslint no-process-env: "off" */
|
|
2
|
-
import process from 'node:process';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* The following environment variables are set for the test:
|
|
6
|
-
*
|
|
7
|
-
* | Env variable name | Description | Default | Comments |
|
|
8
|
-
* | ----------------- | ----------- | ------- | -------- |
|
|
9
|
-
* | SUMO_LOGIC_ENDPOINT | endpoint to use to log on sumologic | null
|
|
10
|
-
* | SUMO_LOGIC_COLLECTOR_CODE | code to use to log on sumologic | null
|
|
11
|
-
* | NO_STACK | flag to have a stack associated with the log | yes
|
|
12
|
-
* | LOG_LEVEL | log level to log | error
|
|
13
|
-
* | CONSOLE_LEVEL | log level to display | debug
|
|
14
|
-
* | LOG_MODE | log mode | none
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
/*
|
|
18
|
-
* process.env.SUMO_LOGIC_ENDPOINT = 'http://localhost:9080/logs/';
|
|
19
|
-
* process.env.SUMO_LOGIC_COLLECTOR_CODE = '1234';
|
|
20
|
-
*/
|
|
21
|
-
process.env.SUMO_LOGIC_ENDPOINT = null;
|
|
22
|
-
process.env.SUMO_LOGIC_COLLECTOR_CODE = null;
|
|
23
|
-
process.env.NO_STACK = 'yes';
|
|
24
|
-
process.env.LOG_LEVEL = 'debug';
|
|
25
|
-
process.env.CONSOLE_LEVEL = 'debug';
|
|
26
|
-
process.env.LOG_MODE = 'none';
|