@mimik/request-retry 4.0.9 → 4.0.10

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 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.&lt;object&gt;</code>
12
+ ### request-retry~rpRetry(options) ⇒ <code>Promise.&lt;\*&gt;</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.&lt;object&gt;</code> - A bluebird promise (supports cancellation).
16
+ **Returns**: <code>Promise.&lt;\*&gt;</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>&quot;&#x27;type&#x27;&quot;</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>&quot;&#x27;response&#x27;&quot;</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 NANO_TO_MS = 1000000;
29
+ const NS_PER_MS = 1000000;
30
30
 
31
31
  Promise.config({ cancellation: true });
32
32
 
@@ -49,34 +49,35 @@ 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
- * @return {Promise<object>} A bluebird promise (supports cancellation).
53
- * @fulfil {object} - The Axios response when `resolveWithFullResponse` is `true`; otherwise `response.data`.
54
- * @throws {Error} An error produced by `getRichError`, wrapping an
55
- * [Axios](https://www.npmjs.com/package/axios) error or a `TimeoutError`.
56
- *
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`.
52
+ * @param {object} [options.retry] - Retry configuration.
53
+ * @param {number} [options.retry.retries=2] - Maximum number of retries, independent of the `retryStrategy`.
59
54
  * 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`.
55
+ * @param {number} [options.retry.delay=1000] - Delay between retries in milliseconds when no `delayStrategy` is provided.
61
56
  * 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.
57
+ * @param {Function} [options.retry.delayStrategy] - A function that returns the delay (in milliseconds) to wait before the next retry.
63
58
  * 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:
59
+ * @param {Function} [options.retry.retryStrategy] - A function that decides whether to retry. Signature:
75
60
  * `(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.
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).
78
+ * @fulfil {object} - The Axios response when `resolveWithFullResponse` is `true`; otherwise `response.data`.
79
+ * @throws {Error} An error produced by `getRichError`, wrapping an
80
+ * [Axios](https://www.npmjs.com/package/axios) error or a `TimeoutError`.
80
81
  *
81
82
  * The **default** `retryStrategy` is:
82
83
  *
@@ -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
- const enteredUrl = options.uri || options.url;
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 logLevelLength = Object.keys(logLevel).length;
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) / NANO_TO_MS;
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 (logLevelLength === 0) {
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(() => retryProcess(nbRetry + 1));
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 || ((logLevelLength === 0) ? DEFAULT_LOGLEVEL_ERROR : undefined),
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 || ((logLevelLength === 0) ? DEFAULT_LOGLEVEL_ERROR : undefined),
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 (logLevelLength === 0) logger[DEFAULT_LOGLEVEL_REQUEST]('making a request', { options: optionsInfo, criteria }, correlationId);
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
@@ -1,13 +1,9 @@
1
- const PARAMETER_ERROR = 400;
2
1
  const TOO_MANY_REQUESTS_ERROR = 429;
3
2
  const SYSTEM_ERROR = 500;
4
3
  const OK = 200;
5
- const CREATED = 201;
6
4
 
7
5
  export {
8
- PARAMETER_ERROR,
9
6
  TOO_MANY_REQUESTS_ERROR,
10
7
  SYSTEM_ERROR,
11
8
  OK,
12
- CREATED,
13
9
  };
@@ -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/* type */] = DETAILS;
5
+ const [, TYPE] = DETAILS;
6
6
  const RESPONSE = 'response';
7
- const [, , , , , DEFAULT_LOGLEVEL/* 'silly' */] = logger.LEVELS;
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 (!val[name]) return null;
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 (!val && val !== '') return defaultValue;
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
- logger.warn('invalid delayStrategy, using delay', { strategyType: typeof delayStrategy, delay, default: DEFAULT_DELAY }, correlationId);
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
- logLevel.response = validateLogLevel(logLevel, 'response');
109
- logLevel.error = validateLogLevel(logLevel, 'error');
110
- logLevel.request = validateLogLevel(logLevel, 'request');
111
- logLevel.responseDetails = validateResponseDetails(logLevel.responseDetails, 'responseDetails', TYPE);
112
- logLevel.responseName = validateResponseName(logLevel.responseName, 'responseName', RESPONSE);
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 iRetry;
152
+ let shouldRetry;
150
153
 
151
154
  try {
152
- iRetry = criteria.retryStrategy(nbRetry, err, options, correlationId);
155
+ shouldRetry = criteria.retryStrategy(nbRetry, err, options, correlationId);
153
156
  }
154
157
  catch (error) {
155
158
  logger.warn('bad retry strategy', { error }, correlationId);
156
- iRetry = true;
159
+ shouldRetry = true;
157
160
  }
158
- if (typeof iRetry !== 'boolean') {
159
- logger.warn('bad retry strategy', { error: `return of retryStrategy is a ${typeof iRetry}` }, correlationId);
160
- iRetry = true;
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 iRetry;
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.9",
3
+ "version": "4.0.10",
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.5",
27
- "@mimik/response-helper": "^4.0.10",
28
- "@mimik/sumologic-winston-logger": "^2.1.12",
29
- "axios": "1.13.5",
29
+ "@mimik/request-helper": "^2.0.6",
30
+ "@mimik/response-helper": "^4.0.11",
31
+ "@mimik/sumologic-winston-logger": "^2.2.1",
32
+ "axios": "1.13.6",
30
33
  "bluebird": "3.7.2"
31
34
  },
32
35
  "devDependencies": {
33
- "@eslint/js": "9.39.2",
34
- "@mimik/eslint-plugin-document-env": "^2.0.8",
35
- "@stylistic/eslint-plugin": "5.9.0",
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": "10.1.3",
41
+ "c8": "11.0.0",
38
42
  "chai": "6.2.2",
39
- "eslint": "9.39.2",
43
+ "eslint": "9.39.4",
40
44
  "eslint-plugin-import": "2.32.0",
41
45
  "express": "5.2.1",
42
- "globals": "17.3.0",
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.1"
51
+ "sinon": "21.0.2"
48
52
  }
49
53
  }
package/.husky/pre-commit DELETED
@@ -1,2 +0,0 @@
1
- #!/bin/sh
2
- npm run commit-ready
package/.husky/pre-push DELETED
@@ -1,2 +0,0 @@
1
- #!/bin/sh
2
- npm run test
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
- ];
@@ -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
- });
@@ -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
- });
@@ -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';
@@ -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());
@@ -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
- };
@@ -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';