@mimik/request-retry 3.0.2 → 4.0.0
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/eslint.config.js +63 -0
- package/index.js +43 -26
- package/lib/rp-axios-wrapper.js +4 -5
- package/lib/validate.js +29 -19
- package/manual-test/httpMock.js +6 -4
- package/manual-test/man.js +6 -4
- package/manual-test/retryMockMan.js +8 -4
- package/manual-test/testEnv.js +5 -3
- package/manual-test/testRace.js +15 -4
- package/manual-test/testRetry.js +45 -42
- package/package.json +22 -25
- package/test/retryMock.js +14 -7
- package/test/rpRetry.spec.js +133 -120
- package/test/testEnv.js +5 -2
- package/.eslintrc +0 -43
package/eslint.config.js
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import importPlugin from 'eslint-plugin-import';
|
|
2
|
+
import js from '@eslint/js';
|
|
3
|
+
import processDoc from '@mimik/eslint-plugin-document-env';
|
|
4
|
+
import stylistic from '@stylistic/eslint-plugin';
|
|
5
|
+
|
|
6
|
+
const MAX_LENGTH_LINE = 180;
|
|
7
|
+
const MAX_FUNCTION_PARAMETERS = 6;
|
|
8
|
+
const MAX_LINES_IN_FILES = 600;
|
|
9
|
+
const MAX_LINES_IN_FUNCTION = 150;
|
|
10
|
+
const MAX_STATEMENTS_IN_FUNCTION = 45;
|
|
11
|
+
const MIN_KEYS_IN_OBJECT = 10;
|
|
12
|
+
const MAX_COMPLEXITY = 30;
|
|
13
|
+
|
|
14
|
+
export default [
|
|
15
|
+
{
|
|
16
|
+
ignores: ['mochawesome-report/**', 'node_modules/**', 'dist/**'],
|
|
17
|
+
},
|
|
18
|
+
importPlugin.flatConfigs.recommended,
|
|
19
|
+
stylistic.configs['recommended-flat'],
|
|
20
|
+
js.configs.all,
|
|
21
|
+
{
|
|
22
|
+
plugins: {
|
|
23
|
+
processDoc,
|
|
24
|
+
},
|
|
25
|
+
languageOptions: {
|
|
26
|
+
ecmaVersion: 2022,
|
|
27
|
+
globals: {
|
|
28
|
+
console: 'readonly',
|
|
29
|
+
describe: 'readonly',
|
|
30
|
+
it: 'readonly',
|
|
31
|
+
require: 'readonly',
|
|
32
|
+
},
|
|
33
|
+
sourceType: 'module',
|
|
34
|
+
},
|
|
35
|
+
rules: {
|
|
36
|
+
'@stylistic/brace-style': ['warn', 'stroustrup', { allowSingleLine: true }],
|
|
37
|
+
'@stylistic/line-comment-position': ['off'],
|
|
38
|
+
'@stylistic/semi': ['error', 'always'],
|
|
39
|
+
'capitalized-comments': ['off'],
|
|
40
|
+
'complexity': ['error', MAX_COMPLEXITY],
|
|
41
|
+
'curly': ['off'],
|
|
42
|
+
'id-length': ['error', { exceptions: ['x', 'y', 'z', 'i', 'j', 'k'] }],
|
|
43
|
+
'import/no-extraneous-dependencies': ['error', { devDependencies: true }],
|
|
44
|
+
'import/no-unresolved': ['error', { amd: true, caseSensitiveStrict: true, commonjs: true }],
|
|
45
|
+
'init-declarations': ['off'],
|
|
46
|
+
'linebreak-style': ['off'],
|
|
47
|
+
'max-len': ['warn', MAX_LENGTH_LINE, { ignoreComments: true }],
|
|
48
|
+
'max-lines': ['warn', { max: MAX_LINES_IN_FILES, skipComments: true }],
|
|
49
|
+
'max-lines-per-function': ['warn', { max: MAX_LINES_IN_FUNCTION, skipComments: true }],
|
|
50
|
+
'max-params': ['error', MAX_FUNCTION_PARAMETERS],
|
|
51
|
+
'max-statements': ['warn', MAX_STATEMENTS_IN_FUNCTION],
|
|
52
|
+
'no-confusing-arrow': ['off'], // arrow isnt confusing
|
|
53
|
+
'no-inline-comments': ['off'],
|
|
54
|
+
'no-process-env': ['error'],
|
|
55
|
+
'no-ternary': ['off'],
|
|
56
|
+
'no-undefined': ['off'],
|
|
57
|
+
'one-var': ['error', 'never'],
|
|
58
|
+
'processDoc/validate-document-env': ['error'],
|
|
59
|
+
'quotes': ['warn', 'single'],
|
|
60
|
+
'sort-keys': ['error', 'asc', { caseSensitive: true, minKeys: MIN_KEYS_IN_OBJECT, natural: false }],
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
];
|
package/index.js
CHANGED
|
@@ -1,15 +1,22 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
const logger = require('@mimik/sumologic-winston-logger');
|
|
4
|
-
const { getRichError } = require('@mimik/response-helper');
|
|
5
|
-
const { getCorrelationId, setUserAgent } = require('@mimik/request-helper');
|
|
6
|
-
const {
|
|
7
|
-
validateOptions,
|
|
1
|
+
import {
|
|
8
2
|
calculateDelay,
|
|
9
3
|
isRetry,
|
|
10
4
|
setResponse,
|
|
11
|
-
|
|
12
|
-
|
|
5
|
+
validateOptions,
|
|
6
|
+
} from './lib/validate.js';
|
|
7
|
+
import {
|
|
8
|
+
clearTimeout,
|
|
9
|
+
setTimeout,
|
|
10
|
+
} from 'timers';
|
|
11
|
+
import {
|
|
12
|
+
getCorrelationId,
|
|
13
|
+
setUserAgent,
|
|
14
|
+
} from '@mimik/request-helper';
|
|
15
|
+
import Promise from 'bluebird';
|
|
16
|
+
import { getRichError } from '@mimik/response-helper';
|
|
17
|
+
import logger from '@mimik/sumologic-winston-logger';
|
|
18
|
+
import process from 'process';
|
|
19
|
+
import { rp } from './lib/rp-axios-wrapper.js';
|
|
13
20
|
|
|
14
21
|
const DEFAULT_RESPONSE_NAME = 'response';
|
|
15
22
|
const DEFAULT_LOGLEVEL_DETAILS = 'full';
|
|
@@ -17,6 +24,16 @@ const DEFAULT_LOGLEVEL_RESPONSE = 'info';
|
|
|
17
24
|
const DEFAULT_LOGLEVEL_ERROR = 'warn';
|
|
18
25
|
const DEFAULT_LOGLEVEL_REQUEST = 'info';
|
|
19
26
|
|
|
27
|
+
const MILLI_SECONDS = 0;
|
|
28
|
+
const MILLI_NANO_SECONDS = 1;
|
|
29
|
+
const INCR = 1;
|
|
30
|
+
const MILLI = 1000;
|
|
31
|
+
const NANO = 1e6;
|
|
32
|
+
const EMPTY = 0;
|
|
33
|
+
const NONE = 0;
|
|
34
|
+
const OK_RESPONSE = 200;
|
|
35
|
+
const SYSTEM_ERROR = 500;
|
|
36
|
+
|
|
20
37
|
Promise.config({ cancellation: true });
|
|
21
38
|
|
|
22
39
|
/**
|
|
@@ -82,12 +99,12 @@ const rpRetry = (origOptions) => {
|
|
|
82
99
|
const criteria = validateOptions(options, correlationId);
|
|
83
100
|
const { logLevel } = criteria;
|
|
84
101
|
const logLevelLength = Object.keys(logLevel).length;
|
|
85
|
-
let nbRetries =
|
|
102
|
+
let nbRetries = NONE;
|
|
86
103
|
let mainTimeout;
|
|
87
104
|
const measure = (statusCode) => {
|
|
88
105
|
if (metrics && metrics.HTTPRequestDuration) {
|
|
89
106
|
const elapsedHrTime = process.hrtime(startHrTime);
|
|
90
|
-
const elapsedTimeInMs = elapsedHrTime[
|
|
107
|
+
const elapsedTimeInMs = elapsedHrTime[MILLI_SECONDS] * MILLI + elapsedHrTime[MILLI_NANO_SECONDS] / NANO;
|
|
91
108
|
|
|
92
109
|
metrics.HTTPRequestDuration
|
|
93
110
|
.labels('rpRetry', options.method, metrics.url || enteredUrl, enteredUrl.includes('?'), statusCode)
|
|
@@ -100,7 +117,7 @@ const rpRetry = (origOptions) => {
|
|
|
100
117
|
const optionsInfo = { ...options };
|
|
101
118
|
delete optionsInfo.metrics;
|
|
102
119
|
|
|
103
|
-
const retryProcess =
|
|
120
|
+
const retryProcess = nbRetry => rp(options)
|
|
104
121
|
.then((response) => {
|
|
105
122
|
const info = { options: optionsInfo, nbRetries, errors };
|
|
106
123
|
|
|
@@ -108,7 +125,7 @@ const rpRetry = (origOptions) => {
|
|
|
108
125
|
info[logLevel.responseName] = setResponse(response, logLevel.responseDetails);
|
|
109
126
|
logger[logLevel.response]('request response', info, correlationId);
|
|
110
127
|
}
|
|
111
|
-
else if (logLevelLength ===
|
|
128
|
+
else if (logLevelLength === EMPTY) {
|
|
112
129
|
info[DEFAULT_RESPONSE_NAME] = setResponse(response, DEFAULT_LOGLEVEL_DETAILS);
|
|
113
130
|
logger[DEFAULT_LOGLEVEL_RESPONSE]('request response', info, correlationId);
|
|
114
131
|
}
|
|
@@ -116,16 +133,16 @@ const rpRetry = (origOptions) => {
|
|
|
116
133
|
})
|
|
117
134
|
.catch((err) => {
|
|
118
135
|
if ((nbRetry < criteria.retries) && isRetry(options, nbRetry, criteria, err, correlationId)) {
|
|
119
|
-
nbRetries = nbRetry +
|
|
136
|
+
nbRetries = nbRetry + INCR;
|
|
120
137
|
errors.push(err);
|
|
121
138
|
const delayPromise = Promise.delay(calculateDelay(options, nbRetry, criteria, err, correlationId));
|
|
122
139
|
|
|
123
140
|
delayPromises.unshift(delayPromise);
|
|
124
|
-
return delayPromise.then(() => retryProcess(nbRetry +
|
|
141
|
+
return delayPromise.then(() => retryProcess(nbRetry + INCR));
|
|
125
142
|
}
|
|
126
143
|
const error = err;
|
|
127
144
|
|
|
128
|
-
if (nbRetries !==
|
|
145
|
+
if (nbRetries !== NONE) {
|
|
129
146
|
if (!error.info) error.info = {};
|
|
130
147
|
error.info.retry = {
|
|
131
148
|
nbRetries,
|
|
@@ -137,18 +154,18 @@ const rpRetry = (origOptions) => {
|
|
|
137
154
|
'request error response',
|
|
138
155
|
{ options: optionsInfo },
|
|
139
156
|
error,
|
|
140
|
-
logLevel.error || ((logLevelLength ===
|
|
157
|
+
logLevel.error || ((logLevelLength === EMPTY) ? DEFAULT_LOGLEVEL_ERROR : undefined),
|
|
141
158
|
correlationId,
|
|
142
159
|
);
|
|
143
160
|
});
|
|
144
161
|
|
|
145
|
-
const retryPromise = retryProcess(
|
|
162
|
+
const retryPromise = retryProcess(NONE);
|
|
146
163
|
const mainTimeoutPromise = new Promise((resolve, reject) => {
|
|
147
164
|
mainTimeout = setTimeout(() => {
|
|
148
|
-
delayPromises.forEach(
|
|
165
|
+
delayPromises.forEach(delayPromise => delayPromise.cancel(`retry timeout delay, ${criteria.timeout}, ${nbRetries}`));
|
|
149
166
|
let error = new Error('retry timeout');
|
|
150
167
|
|
|
151
|
-
if (nbRetries !==
|
|
168
|
+
if (nbRetries !== NONE) {
|
|
152
169
|
error.info = { retry: { nbRetries, errors } };
|
|
153
170
|
}
|
|
154
171
|
error.name = 'TimeoutError';
|
|
@@ -157,30 +174,30 @@ const rpRetry = (origOptions) => {
|
|
|
157
174
|
'request error response',
|
|
158
175
|
{ options: optionsInfo },
|
|
159
176
|
error,
|
|
160
|
-
logLevel.error || ((logLevelLength ===
|
|
177
|
+
logLevel.error || ((logLevelLength === EMPTY) ? DEFAULT_LOGLEVEL_ERROR : undefined),
|
|
161
178
|
correlationId,
|
|
162
179
|
);
|
|
163
180
|
reject(error);
|
|
164
|
-
}, criteria.timeout *
|
|
181
|
+
}, criteria.timeout * MILLI);
|
|
165
182
|
});
|
|
166
183
|
|
|
167
184
|
if (logLevel.request) logger[logLevel.request]('making a request', { options: optionsInfo, criteria }, correlationId);
|
|
168
|
-
else if (logLevelLength ===
|
|
185
|
+
else if (logLevelLength === EMPTY) logger[DEFAULT_LOGLEVEL_REQUEST]('making a request', { options: optionsInfo, criteria }, correlationId);
|
|
169
186
|
return Promise.race([retryPromise, mainTimeoutPromise])
|
|
170
187
|
.then((result) => {
|
|
171
188
|
mainTimeoutPromise.cancel('main timeout');
|
|
172
189
|
clearTimeout(mainTimeout);
|
|
173
|
-
measure(
|
|
190
|
+
measure(OK_RESPONSE);
|
|
174
191
|
return result;
|
|
175
192
|
})
|
|
176
193
|
.catch((err) => {
|
|
177
194
|
mainTimeoutPromise.cancel(`main timeout error: ${err.message}`);
|
|
178
195
|
clearTimeout(mainTimeout);
|
|
179
|
-
measure(err.statusCode ||
|
|
196
|
+
measure(err.statusCode || SYSTEM_ERROR);
|
|
180
197
|
throw err;
|
|
181
198
|
});
|
|
182
199
|
};
|
|
183
200
|
|
|
184
|
-
|
|
201
|
+
export {
|
|
185
202
|
rpRetry,
|
|
186
203
|
};
|
package/lib/rp-axios-wrapper.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
const { getRichError } = require('@mimik/response-helper');
|
|
1
|
+
import axios from 'axios';
|
|
2
|
+
import clone from 'lodash.clone';
|
|
3
|
+
import { getRichError } from '@mimik/response-helper';
|
|
5
4
|
|
|
6
5
|
const rp = (origOptions) => {
|
|
7
6
|
const options = clone(origOptions);
|
|
@@ -43,6 +42,6 @@ const rp = (origOptions) => {
|
|
|
43
42
|
});
|
|
44
43
|
};
|
|
45
44
|
|
|
46
|
-
|
|
45
|
+
export {
|
|
47
46
|
rp,
|
|
48
47
|
};
|
package/lib/validate.js
CHANGED
|
@@ -1,23 +1,29 @@
|
|
|
1
|
-
|
|
1
|
+
import logger from '@mimik/sumologic-winston-logger';
|
|
2
2
|
|
|
3
3
|
const DETAILS = ['full', 'type', 'count'];
|
|
4
|
-
const TYPE = DETAILS
|
|
4
|
+
const [, TYPE/* type */] = DETAILS;
|
|
5
5
|
const RESPONSE = 'response';
|
|
6
|
-
const DEFAULT_LOGLEVEL = logger.LEVELS
|
|
6
|
+
const [, , , , , DEFAULT_LOGLEVEL/* 'silly' */] = logger.LEVELS;
|
|
7
7
|
const DEFAULT_RETRIES = 2;
|
|
8
8
|
const MIN_RETRIES = 0;
|
|
9
9
|
const MAX_RETRIES = 15;
|
|
10
10
|
const DEFAULT_DELAY = 1000; // in ms
|
|
11
11
|
const MIN_DELAY = 20; // in ms
|
|
12
12
|
const MAX_DELAY = 30000; // in ms
|
|
13
|
+
const NO_DELAY = 0;
|
|
13
14
|
const DEFAULT_TIMEOUT = 50; // in seconds
|
|
14
15
|
const MIN_TIMEOUT = 10; // in seconds
|
|
15
16
|
const MAX_TIMEOUT = 120; // in seconds
|
|
17
|
+
const TOO_MANY_REQUESTS_ERROR = 429;
|
|
18
|
+
const EMPTY = 0;
|
|
19
|
+
const SYSTEM_RANGE = 5;
|
|
20
|
+
const SYSTEM_RANGE_EXTRACT = 100;
|
|
21
|
+
const ARG_ERROR = 1;
|
|
16
22
|
|
|
17
23
|
const defaultRetry = (...args) => {
|
|
18
|
-
const { statusCode } = args[
|
|
24
|
+
const { statusCode } = args[ARG_ERROR]; // err
|
|
19
25
|
|
|
20
|
-
return (statusCode && (Math.floor(statusCode /
|
|
26
|
+
return (statusCode && (Math.floor(statusCode / SYSTEM_RANGE_EXTRACT) === SYSTEM_RANGE || statusCode === TOO_MANY_REQUESTS_ERROR)) || (!statusCode && !args[ARG_ERROR].message.includes('Invalid URI'));
|
|
21
27
|
};
|
|
22
28
|
const unknownRetry = () => true;
|
|
23
29
|
const defaultDelay = () => DEFAULT_DELAY;
|
|
@@ -74,7 +80,7 @@ const validateOptions = (options, correlationId) => {
|
|
|
74
80
|
};
|
|
75
81
|
const validateResponseName = (val, name, defaultValue) => {
|
|
76
82
|
if (!val && val !== '') return defaultValue;
|
|
77
|
-
if (typeof val !== 'string' || val.length ===
|
|
83
|
+
if (typeof val !== 'string' || val.length === EMPTY || val.trim().length === EMPTY) {
|
|
78
84
|
logger.warn(`invalid logLevel.${name}, reset to default`, { name, value: val, defaultValue }, correlationId);
|
|
79
85
|
return defaultValue;
|
|
80
86
|
}
|
|
@@ -86,29 +92,29 @@ const validateOptions = (options, correlationId) => {
|
|
|
86
92
|
delay = validateValue(delay, 'delay', DEFAULT_DELAY, MIN_DELAY, MAX_DELAY);
|
|
87
93
|
retries = validateValue(retries, 'retries', DEFAULT_RETRIES, MIN_RETRIES, MAX_RETRIES);
|
|
88
94
|
if (typeof delayStrategy !== 'function') {
|
|
89
|
-
if (
|
|
90
|
-
else {
|
|
95
|
+
if (delayStrategy) {
|
|
91
96
|
logger.warn('invalid delayStrategy, using delay', { strategyType: typeof delayStrategy, delay, default: DEFAULT_DELAY }, correlationId);
|
|
92
97
|
delayStrategy = setDelay;
|
|
93
98
|
}
|
|
99
|
+
else delayStrategy = setDelay;
|
|
94
100
|
}
|
|
95
101
|
if (typeof retryStrategy !== 'function') {
|
|
96
|
-
if (
|
|
97
|
-
else {
|
|
102
|
+
if (retryStrategy) {
|
|
98
103
|
logger.warn('invalid retryStrategy, ignoring', { strategyType: typeof retryStrategy, using: unknownRetry }, correlationId);
|
|
99
104
|
retryStrategy = unknownRetry;
|
|
100
105
|
}
|
|
106
|
+
else retryStrategy = defaultRetry;
|
|
101
107
|
}
|
|
102
|
-
if (
|
|
103
|
-
logLevel = {};
|
|
104
|
-
}
|
|
105
|
-
else {
|
|
108
|
+
if (logLevel) {
|
|
106
109
|
logLevel.response = validateLogLevel(logLevel, 'response');
|
|
107
110
|
logLevel.error = validateLogLevel(logLevel, 'error');
|
|
108
111
|
logLevel.request = validateLogLevel(logLevel, 'request');
|
|
109
112
|
logLevel.responseDetails = validateResponseDetails(logLevel.responseDetails, 'responseDetails', TYPE);
|
|
110
113
|
logLevel.responseName = validateResponseName(logLevel.responseName, 'responseName', RESPONSE);
|
|
111
114
|
}
|
|
115
|
+
else {
|
|
116
|
+
logLevel = {};
|
|
117
|
+
}
|
|
112
118
|
return {
|
|
113
119
|
delay,
|
|
114
120
|
delayStrategy,
|
|
@@ -122,7 +128,9 @@ const validateOptions = (options, correlationId) => {
|
|
|
122
128
|
const calculateDelay = (options, nbRetry, criteria, err, correlationId) => {
|
|
123
129
|
let calcDelay;
|
|
124
130
|
|
|
125
|
-
try {
|
|
131
|
+
try {
|
|
132
|
+
calcDelay = criteria.delayStrategy(nbRetry, err, options, correlationId);
|
|
133
|
+
}
|
|
126
134
|
catch (error) {
|
|
127
135
|
logger.warn('bad delay strategy', { error }, correlationId);
|
|
128
136
|
calcDelay = criteria.delay;
|
|
@@ -131,7 +139,7 @@ const calculateDelay = (options, nbRetry, criteria, err, correlationId) => {
|
|
|
131
139
|
logger.warn('bad delay strategy', { error: `return of delayStrategy is a ${typeof calcDelay}` }, correlationId);
|
|
132
140
|
calcDelay = criteria.delay;
|
|
133
141
|
}
|
|
134
|
-
else if (calcDelay > MAX_DELAY || calcDelay <=
|
|
142
|
+
else if (calcDelay > MAX_DELAY || calcDelay <= NO_DELAY) {
|
|
135
143
|
logger.warn('calculated delay out of scope: using delay or default', { calcDelay, delay: criteria.delay, default: DEFAULT_DELAY }, correlationId);
|
|
136
144
|
calcDelay = criteria.delay;
|
|
137
145
|
}
|
|
@@ -141,7 +149,9 @@ const calculateDelay = (options, nbRetry, criteria, err, correlationId) => {
|
|
|
141
149
|
const isRetry = (options, nbRetry, criteria, err, correlationId) => {
|
|
142
150
|
let iRetry;
|
|
143
151
|
|
|
144
|
-
try {
|
|
152
|
+
try {
|
|
153
|
+
iRetry = criteria.retryStrategy(nbRetry, err, options, correlationId);
|
|
154
|
+
}
|
|
145
155
|
catch (error) {
|
|
146
156
|
logger.warn('bad retry strategy', { error }, correlationId);
|
|
147
157
|
iRetry = true;
|
|
@@ -165,9 +175,9 @@ const setResponse = (response, details) => {
|
|
|
165
175
|
return response;
|
|
166
176
|
};
|
|
167
177
|
|
|
168
|
-
|
|
169
|
-
validateOptions,
|
|
178
|
+
export {
|
|
170
179
|
calculateDelay,
|
|
171
180
|
isRetry,
|
|
172
181
|
setResponse,
|
|
182
|
+
validateOptions,
|
|
173
183
|
};
|
package/manual-test/httpMock.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
/* eslint-disable no-console */
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
import bodyParser from 'body-parser';
|
|
3
|
+
import express from 'express';
|
|
4
|
+
|
|
5
|
+
const PORT = 9000;
|
|
4
6
|
|
|
5
7
|
const app = express();
|
|
6
8
|
|
|
@@ -11,6 +13,6 @@ app.get('/success', (req, res) => {
|
|
|
11
13
|
res.send({ statusCode: 200 });
|
|
12
14
|
});
|
|
13
15
|
|
|
14
|
-
app.listen(
|
|
15
|
-
console.log('----->', `retry mock at ${
|
|
16
|
+
app.listen(PORT, () => {
|
|
17
|
+
console.log('----->', `retry mock at ${PORT}`);
|
|
16
18
|
});
|
package/manual-test/man.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
/* eslint-disable no-console */
|
|
2
|
-
|
|
2
|
+
import './testEnv';
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
import { rpRetry } from '../index.js';
|
|
5
|
+
|
|
6
|
+
const DELAY = 1000;
|
|
5
7
|
|
|
6
8
|
const correlationId = `--request-retry-test--/${new Date().toISOString()}`;
|
|
7
9
|
|
|
@@ -15,9 +17,9 @@ const options = {
|
|
|
15
17
|
retry: {},
|
|
16
18
|
};
|
|
17
19
|
|
|
18
|
-
options.retry.delayStrategy = () =>
|
|
20
|
+
options.retry.delayStrategy = () => DELAY;
|
|
19
21
|
options.retry.timeout = 10;
|
|
20
22
|
options.retry.retries = 2;
|
|
21
23
|
|
|
22
24
|
rpRetry(options)
|
|
23
|
-
.catch(
|
|
25
|
+
.catch(err => console.log(err));
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
/* eslint-disable no-console */
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
import bodyParser from 'body-parser';
|
|
3
|
+
import express from 'express';
|
|
4
|
+
import { setTimeout } from 'timers';
|
|
5
|
+
|
|
6
|
+
const NONE = 0;
|
|
7
|
+
const INCR = 1;
|
|
4
8
|
|
|
5
9
|
const app = express();
|
|
6
10
|
const config = {
|
|
@@ -23,7 +27,7 @@ let message;
|
|
|
23
27
|
|
|
24
28
|
app.use(bodyParser.json());
|
|
25
29
|
app.get(`${config.base}${config.path}`, (req, res) => {
|
|
26
|
-
if (nbRequest ===
|
|
30
|
+
if (nbRequest === NONE) message = 'Recieved first request';
|
|
27
31
|
else {
|
|
28
32
|
const timeLap = Date.now() - time;
|
|
29
33
|
|
|
@@ -37,7 +41,7 @@ app.get(`${config.base}${config.path}`, (req, res) => {
|
|
|
37
41
|
res.send({ statusCode: config.get.success });
|
|
38
42
|
return;
|
|
39
43
|
}
|
|
40
|
-
nbRequest +=
|
|
44
|
+
nbRequest += INCR;
|
|
41
45
|
setTimeout(() => {
|
|
42
46
|
res.statusCode = config.get.error;
|
|
43
47
|
res.send({ statusCode: config.get.error });
|
package/manual-test/testEnv.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
/* eslint no-process-env: "off" */
|
|
2
|
+
import process from 'process';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* The following environment variables are set for the test:
|
|
@@ -12,9 +13,10 @@
|
|
|
12
13
|
* | CONSOLE_LEVEL | log level to diplay | debug
|
|
13
14
|
* | LOG_MODE | log mode | none
|
|
14
15
|
*/
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
/*
|
|
17
|
+
*process.env.SUMO_LOGIC_ENDPOINT = 'http://localhost:9080/logs/';
|
|
18
|
+
*process.env.SUMO_LOGIC_COLLECTOR_CODE = '1234';
|
|
19
|
+
*/
|
|
18
20
|
process.env.SUMO_LOGIC_ENDPOINT = null;
|
|
19
21
|
process.env.SUMO_LOGIC_COLLECTOR_CODE = null;
|
|
20
22
|
process.env.NO_STACK = 'yes';
|
package/manual-test/testRace.js
CHANGED
|
@@ -1,9 +1,18 @@
|
|
|
1
1
|
/* eslint-disable no-console */
|
|
2
|
+
import {
|
|
3
|
+
clearTimeout,
|
|
4
|
+
setTimeout,
|
|
5
|
+
} from 'timers';
|
|
6
|
+
|
|
7
|
+
const INCR = 1;
|
|
8
|
+
const MAX_INCR = 100000000;
|
|
9
|
+
const NO_RESULT = 0;
|
|
10
|
+
|
|
2
11
|
const doSomething = () => {
|
|
3
12
|
let add = 0;
|
|
4
13
|
|
|
5
|
-
for (let i = 0; i <
|
|
6
|
-
add +=
|
|
14
|
+
for (let i = 0; i < MAX_INCR; i += INCR) {
|
|
15
|
+
add += INCR;
|
|
7
16
|
console.log(add);
|
|
8
17
|
}
|
|
9
18
|
return add;
|
|
@@ -16,10 +25,12 @@ const race = () => {
|
|
|
16
25
|
throw new Error('timeout');
|
|
17
26
|
}, timeout);
|
|
18
27
|
|
|
19
|
-
try {
|
|
28
|
+
try {
|
|
29
|
+
result = doSomething();
|
|
30
|
+
}
|
|
20
31
|
catch (err) {
|
|
21
32
|
console.log(err);
|
|
22
|
-
result =
|
|
33
|
+
result = NO_RESULT;
|
|
23
34
|
}
|
|
24
35
|
clearTimeout(mainTimeout);
|
|
25
36
|
return result;
|
package/manual-test/testRetry.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/* eslint-disable no-console */
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
import '../test/testEnv.js';
|
|
3
|
+
|
|
4
|
+
import { rpRetry } from '../index.js';
|
|
4
5
|
|
|
5
6
|
const options = {
|
|
6
7
|
method: 'GET',
|
|
@@ -10,51 +11,53 @@ const options = {
|
|
|
10
11
|
},
|
|
11
12
|
json: true,
|
|
12
13
|
timeout: 20000,
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
14
|
+
/*
|
|
15
|
+
* retry: {
|
|
16
|
+
* all default: maxRetry: 2, retryDelay: 1000ms, maxTimeout: 50s, logLevel: 3 silly, type response, retryDelayStrategy: setDelay, retryStrategy: defaultRetry
|
|
17
|
+
* retries: -1, // => 0
|
|
18
|
+
* retries: 20, // => 15
|
|
19
|
+
* retries: 'joe', // => 2
|
|
20
|
+
* retries: 0, // OK
|
|
21
|
+
* delay: 0, // => 20ms
|
|
22
|
+
* delay: 100000, // => 30000ms
|
|
23
|
+
* delay: 'joe', // => 1000ms
|
|
24
|
+
* delay: 1, // => 20ms
|
|
25
|
+
* delayStrategy: 'joe', // => setDelay returning retryDelay
|
|
26
|
+
* delayStrategy: function badDelay() { return 'joe' }, // => no change then bad delay strategy => retryDelay
|
|
27
|
+
* delayStrategy: function userDelay(...args) { return (Math.pow(2, args[0]) * 100) + Math.floor(Math.random() * 50) }, // OK
|
|
28
|
+
* retryStrategy: 'joe', // => unknowRetry returning false
|
|
29
|
+
* retryStrategy: function badRetry() { return 'joe' }, // => no change then bad retry strategy => false
|
|
30
|
+
* retryStrategy: function userRetry(...args) { // OK
|
|
31
|
+
* const { statusCode } = args[1]; // err
|
|
32
|
+
*
|
|
33
|
+
* return statusCode && (Math.floor(statusCode/100) !== 5 || statusCode !== 429); // retry if there is no statusCode or if there is 500 range statucCode or 429
|
|
34
|
+
* },
|
|
35
|
+
* retryStrategy: function invalidRetry(...args) { // defaultRetry
|
|
36
|
+
* const { statusCode } = args[34]; // null value
|
|
37
|
+
*
|
|
38
|
+
* return true // no retry
|
|
39
|
+
* },
|
|
40
|
+
* timeout: 0, // => 10s
|
|
41
|
+
* timeout: 150, // => 120s
|
|
42
|
+
* timeout: 'joe', // => 50s
|
|
43
|
+
* logLevel: {
|
|
44
|
+
* response: 'test', // silly with warning
|
|
45
|
+
* error: 1234, // => silly with warning
|
|
46
|
+
* request: null, // => silly no warning
|
|
47
|
+
* responseDetails: 'test', // => type with warning
|
|
48
|
+
* responseName: '', // => response with warning
|
|
49
|
+
* },
|
|
50
|
+
* logLevel: {
|
|
51
|
+
* response: 'info', // ok all the other value to default no warning
|
|
52
|
+
* },
|
|
53
|
+
* }
|
|
54
|
+
*/
|
|
52
55
|
};
|
|
53
56
|
|
|
54
57
|
const sDate = Date.now();
|
|
55
58
|
|
|
56
59
|
rpRetry(options)
|
|
57
|
-
.then(
|
|
60
|
+
.then(response => console.log('success', { response: typeof response }, Date.now() - sDate))
|
|
58
61
|
.catch((err) => {
|
|
59
62
|
console.log('failure', { error: err, info: err.info }, Date.now() - sDate);
|
|
60
63
|
});
|
package/package.json
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mimik/request-retry",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.0.0",
|
|
4
4
|
"description": "Request retry wrapping axios",
|
|
5
|
-
"main": "index.js",
|
|
5
|
+
"main": "./index.js",
|
|
6
|
+
"type": "module",
|
|
6
7
|
"scripts": {
|
|
7
|
-
"lint": "eslint --
|
|
8
|
+
"lint": "eslint . --no-error-on-unmatched-pattern",
|
|
8
9
|
"docs": "jsdoc2md index.js > README.md",
|
|
9
10
|
"test": "mocha --reporter mochawesome --bail --check-leaks test/",
|
|
10
|
-
"test-ci": "
|
|
11
|
+
"test-ci": "c8 --reporter=lcov --reporter=text npm test --exit",
|
|
11
12
|
"prepublishOnly": "npm run docs && npm run lint && npm run test-ci",
|
|
12
|
-
"commit-ready": "npm run docs && npm run lint && npm run test-ci"
|
|
13
|
-
"prepare": "husky install"
|
|
13
|
+
"commit-ready": "npm run docs && npm run lint && npm run test-ci"
|
|
14
14
|
},
|
|
15
15
|
"husky": {
|
|
16
16
|
"hooks": {
|
|
@@ -29,31 +29,28 @@
|
|
|
29
29
|
"url": "https://bitbucket.org/mimiktech/request-retry"
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@mimik/request-helper": "^
|
|
33
|
-
"@mimik/response-helper": "^
|
|
34
|
-
"@mimik/sumologic-winston-logger": "^
|
|
35
|
-
"axios": "1.
|
|
32
|
+
"@mimik/request-helper": "^2.0.1",
|
|
33
|
+
"@mimik/response-helper": "^4.0.0",
|
|
34
|
+
"@mimik/sumologic-winston-logger": "^2.0.2",
|
|
35
|
+
"axios": "1.8.4",
|
|
36
36
|
"bluebird": "3.7.2",
|
|
37
37
|
"lodash.clone": "4.5.0"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
|
-
"@
|
|
41
|
-
"@mimik/eslint-plugin-document-env": "^
|
|
42
|
-
"
|
|
40
|
+
"@eslint/js": "9.23.0",
|
|
41
|
+
"@mimik/eslint-plugin-document-env": "^2.0.5",
|
|
42
|
+
"@stylistic/eslint-plugin": "4.2.0",
|
|
43
|
+
"acorn": "8.14.1",
|
|
43
44
|
"body-parser": "1.20.3",
|
|
44
|
-
"
|
|
45
|
-
"
|
|
46
|
-
"eslint
|
|
45
|
+
"c8": "10.1.3",
|
|
46
|
+
"chai": "5.2.0",
|
|
47
|
+
"eslint": "9.23.0",
|
|
47
48
|
"eslint-plugin-import": "2.31.0",
|
|
48
|
-
"
|
|
49
|
-
"
|
|
50
|
-
"
|
|
51
|
-
"
|
|
52
|
-
"husky": "9.1.6",
|
|
53
|
-
"jsdoc-to-markdown": "9.0.2",
|
|
54
|
-
"mocha": "10.7.3",
|
|
49
|
+
"express": "4.21.2",
|
|
50
|
+
"husky": "9.1.7",
|
|
51
|
+
"jsdoc-to-markdown": "9.1.1",
|
|
52
|
+
"mocha": "11.1.0",
|
|
55
53
|
"mochawesome": "7.1.3",
|
|
56
|
-
"
|
|
57
|
-
"sinon": "19.0.2"
|
|
54
|
+
"sinon": "20.0.0"
|
|
58
55
|
}
|
|
59
56
|
}
|
package/test/retryMock.js
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
/* eslint-disable no-console */
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
import bodyParser from 'body-parser';
|
|
3
|
+
import express from 'express';
|
|
4
|
+
import process from 'process';
|
|
5
|
+
import { setTimeout } from 'timers';
|
|
6
|
+
|
|
7
|
+
const NONE = 0;
|
|
8
|
+
const INCR = 1;
|
|
9
|
+
const TIMEOUT = 3000;
|
|
10
|
+
const EXIT_NORMAL = 0;
|
|
4
11
|
|
|
5
12
|
const app = express();
|
|
6
13
|
const config = {
|
|
@@ -22,7 +29,7 @@ let message;
|
|
|
22
29
|
|
|
23
30
|
app.use(bodyParser.json());
|
|
24
31
|
app.get(`${config.base}${config.path}`, (req, res) => {
|
|
25
|
-
if (nbRequest ===
|
|
32
|
+
if (nbRequest === NONE) message = 'Recieved first request';
|
|
26
33
|
else {
|
|
27
34
|
const timeLap = Date.now() - time;
|
|
28
35
|
|
|
@@ -36,7 +43,7 @@ app.get(`${config.base}${config.path}`, (req, res) => {
|
|
|
36
43
|
res.send({ statusCode: config.get.success });
|
|
37
44
|
return;
|
|
38
45
|
}
|
|
39
|
-
nbRequest +=
|
|
46
|
+
nbRequest += INCR;
|
|
40
47
|
res.statusCode = config.get.error;
|
|
41
48
|
|
|
42
49
|
res.send({ statusCode: config.get.error });
|
|
@@ -51,8 +58,8 @@ app.get('/stop', (req, res) => {
|
|
|
51
58
|
res.statusCode = config.get.success;
|
|
52
59
|
res.send({ statusCode: config.get.success });
|
|
53
60
|
setTimeout(() => {
|
|
54
|
-
process.exit(
|
|
55
|
-
},
|
|
61
|
+
process.exit(EXIT_NORMAL);
|
|
62
|
+
}, TIMEOUT);
|
|
56
63
|
});
|
|
57
64
|
|
|
58
65
|
const listen = () => {
|
|
@@ -61,6 +68,6 @@ const listen = () => {
|
|
|
61
68
|
});
|
|
62
69
|
};
|
|
63
70
|
|
|
64
|
-
|
|
71
|
+
export {
|
|
65
72
|
listen,
|
|
66
73
|
};
|
package/test/rpRetry.spec.js
CHANGED
|
@@ -1,27 +1,36 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
import './testEnv.js';
|
|
2
|
+
import { afterEach, before, beforeEach, describe, it } from 'mocha';
|
|
3
|
+
import { assert, spy } from 'sinon';
|
|
4
|
+
import { expect, should } from 'chai';
|
|
5
|
+
import { getCorrelationId } from '@mimik/request-helper';
|
|
6
|
+
import { listen } from './retryMock.js';
|
|
7
|
+
import logger from '@mimik/sumologic-winston-logger';
|
|
8
|
+
import { rpRetry } from '../index.js';
|
|
5
9
|
|
|
6
|
-
const
|
|
7
|
-
const
|
|
10
|
+
const TIMEOUT = 50000;
|
|
11
|
+
const RETRY_DELAY = 10000;
|
|
12
|
+
const RETRY_TIMEOUT = 10;
|
|
13
|
+
const RETRY_RETRIES = 2;
|
|
14
|
+
const DELAY_STRATEGY_RESPONSE_1 = -20;
|
|
15
|
+
const DELAY_STRATEGY_RESPONSE_2 = 50;
|
|
16
|
+
const DELAY_STRATEGY_RESPONSE_3 = 100;
|
|
17
|
+
const CALL_ARG_MESSAGE = 0;
|
|
18
|
+
const FIRST_CALL = 0;
|
|
8
19
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
// const DEFAULT_NO_MESSAGE = 'no message';
|
|
16
|
-
// const NO_ERROR = 'not an error object';
|
|
20
|
+
should();
|
|
21
|
+
/*
|
|
22
|
+
* const DEFAULT_NO_MESSAGE = 'no message';
|
|
23
|
+
* const NO_ERROR = 'not an error object';
|
|
24
|
+
*/
|
|
17
25
|
|
|
18
26
|
describe('request-retry Unit Tests', () => {
|
|
19
27
|
const correlationId = getCorrelationId('--request-retry-test--');
|
|
20
28
|
// let loggerSpyError;
|
|
21
29
|
let loggerSpyWarn;
|
|
22
30
|
let loggerSpyInfo;
|
|
31
|
+
|
|
23
32
|
before(() => {
|
|
24
|
-
|
|
33
|
+
listen();
|
|
25
34
|
});
|
|
26
35
|
|
|
27
36
|
describe('rpRetry(options) options validations prerun', () => {
|
|
@@ -35,7 +44,7 @@ describe('request-retry Unit Tests', () => {
|
|
|
35
44
|
retry: {},
|
|
36
45
|
};
|
|
37
46
|
beforeEach(() => {
|
|
38
|
-
loggerSpyWarn =
|
|
47
|
+
loggerSpyWarn = spy(logger, 'warn');
|
|
39
48
|
});
|
|
40
49
|
afterEach(() => {
|
|
41
50
|
options.retry = {};
|
|
@@ -44,134 +53,134 @@ describe('request-retry Unit Tests', () => {
|
|
|
44
53
|
it('should not generate warning: null retry, using default', () => {
|
|
45
54
|
options.retry = null;
|
|
46
55
|
return rpRetry(options).then(() => {
|
|
47
|
-
|
|
56
|
+
assert.notCalled(loggerSpyWarn);
|
|
48
57
|
});
|
|
49
58
|
});
|
|
50
59
|
it('should not generate warning: empty retry, using default', () => rpRetry(options).then(() => {
|
|
51
|
-
|
|
60
|
+
assert.notCalled(loggerSpyWarn);
|
|
52
61
|
}));
|
|
53
62
|
it('should generate warning: out of scope retries: -1 becoming min', () => {
|
|
54
63
|
options.retry.retries = -1;
|
|
55
64
|
return rpRetry(options).then(() => {
|
|
56
|
-
|
|
57
|
-
const callArgs = loggerSpyWarn.getCall(
|
|
58
|
-
expect(callArgs[
|
|
59
|
-
expect(callArgs[
|
|
65
|
+
assert.calledOnce(loggerSpyWarn);
|
|
66
|
+
const callArgs = loggerSpyWarn.getCall(FIRST_CALL).args;
|
|
67
|
+
expect(callArgs[CALL_ARG_MESSAGE]).to.be.a('string');
|
|
68
|
+
expect(callArgs[CALL_ARG_MESSAGE]).to.equal('out of scope retries, reset to min');
|
|
60
69
|
});
|
|
61
70
|
});
|
|
62
71
|
it('should generate warning: out of scope retries: 20 becoming max', () => {
|
|
63
72
|
options.retry.retries = 20;
|
|
64
73
|
return rpRetry(options).then(() => {
|
|
65
|
-
|
|
66
|
-
const callArgs = loggerSpyWarn.getCall(
|
|
67
|
-
expect(callArgs[
|
|
68
|
-
expect(callArgs[
|
|
74
|
+
assert.calledOnce(loggerSpyWarn);
|
|
75
|
+
const callArgs = loggerSpyWarn.getCall(FIRST_CALL).args;
|
|
76
|
+
expect(callArgs[CALL_ARG_MESSAGE]).to.be.a('string');
|
|
77
|
+
expect(callArgs[CALL_ARG_MESSAGE]).to.equal('out of scope retries, reset to max');
|
|
69
78
|
});
|
|
70
79
|
});
|
|
71
80
|
it('should not generate warning: in scope retries', () => {
|
|
72
81
|
options.retry.retries = 10;
|
|
73
82
|
return rpRetry(options).then(() => {
|
|
74
|
-
|
|
83
|
+
assert.notCalled(loggerSpyWarn);
|
|
75
84
|
});
|
|
76
85
|
});
|
|
77
86
|
it('should generate warning: invalid retries: "notANumber" becoming default', () => {
|
|
78
87
|
options.retry.retries = 'notANumber';
|
|
79
88
|
return rpRetry(options).then(() => {
|
|
80
|
-
|
|
81
|
-
const callArgs = loggerSpyWarn.getCall(
|
|
82
|
-
expect(callArgs[
|
|
83
|
-
expect(callArgs[
|
|
89
|
+
assert.calledOnce(loggerSpyWarn);
|
|
90
|
+
const callArgs = loggerSpyWarn.getCall(FIRST_CALL).args;
|
|
91
|
+
expect(callArgs[CALL_ARG_MESSAGE]).to.be.a('string');
|
|
92
|
+
expect(callArgs[CALL_ARG_MESSAGE]).to.equal('invalid retries, reset to default');
|
|
84
93
|
});
|
|
85
94
|
});
|
|
86
95
|
it('should generate warning: out of scope delay: 0 becoming min', () => {
|
|
87
96
|
options.retry.delay = 0;
|
|
88
97
|
return rpRetry(options).then(() => {
|
|
89
|
-
|
|
90
|
-
const callArgs = loggerSpyWarn.getCall(
|
|
91
|
-
expect(callArgs[
|
|
92
|
-
expect(callArgs[
|
|
98
|
+
assert.calledOnce(loggerSpyWarn);
|
|
99
|
+
const callArgs = loggerSpyWarn.getCall(FIRST_CALL).args;
|
|
100
|
+
expect(callArgs[CALL_ARG_MESSAGE]).to.be.a('string');
|
|
101
|
+
expect(callArgs[CALL_ARG_MESSAGE]).to.equal('out of scope delay, reset to min');
|
|
93
102
|
});
|
|
94
103
|
});
|
|
95
104
|
it('should generate warning: out of scope delay: 100000 becoming max', () => {
|
|
96
105
|
options.retry.delay = 100000;
|
|
97
106
|
return rpRetry(options).then(() => {
|
|
98
|
-
|
|
99
|
-
const callArgs = loggerSpyWarn.getCall(
|
|
100
|
-
expect(callArgs[
|
|
101
|
-
expect(callArgs[
|
|
107
|
+
assert.calledOnce(loggerSpyWarn);
|
|
108
|
+
const callArgs = loggerSpyWarn.getCall(FIRST_CALL).args;
|
|
109
|
+
expect(callArgs[CALL_ARG_MESSAGE]).to.be.a('string');
|
|
110
|
+
expect(callArgs[CALL_ARG_MESSAGE]).to.equal('out of scope delay, reset to max');
|
|
102
111
|
});
|
|
103
112
|
});
|
|
104
113
|
it('should generate warning: invalid delay: "notANumber" becoming default', () => {
|
|
105
114
|
options.retry.delay = 'notANumber';
|
|
106
115
|
return rpRetry(options).then(() => {
|
|
107
|
-
|
|
108
|
-
const callArgs = loggerSpyWarn.getCall(
|
|
109
|
-
expect(callArgs[
|
|
110
|
-
expect(callArgs[
|
|
116
|
+
assert.calledOnce(loggerSpyWarn);
|
|
117
|
+
const callArgs = loggerSpyWarn.getCall(FIRST_CALL).args;
|
|
118
|
+
expect(callArgs[CALL_ARG_MESSAGE]).to.be.a('string');
|
|
119
|
+
expect(callArgs[CALL_ARG_MESSAGE]).to.equal('invalid delay, reset to default');
|
|
111
120
|
});
|
|
112
121
|
});
|
|
113
122
|
it('should not generate warning: in scope delay', () => {
|
|
114
123
|
options.retry.delay = 20000;
|
|
115
124
|
return rpRetry(options).then(() => {
|
|
116
|
-
|
|
125
|
+
assert.notCalled(loggerSpyWarn);
|
|
117
126
|
});
|
|
118
127
|
});
|
|
119
128
|
it('should generate warning: out of scope timeout: 0 becoming min', () => {
|
|
120
129
|
options.retry.timeout = 0;
|
|
121
130
|
return rpRetry(options).then(() => {
|
|
122
|
-
|
|
123
|
-
const callArgs = loggerSpyWarn.getCall(
|
|
124
|
-
expect(callArgs[
|
|
125
|
-
expect(callArgs[
|
|
131
|
+
assert.calledOnce(loggerSpyWarn);
|
|
132
|
+
const callArgs = loggerSpyWarn.getCall(FIRST_CALL).args;
|
|
133
|
+
expect(callArgs[CALL_ARG_MESSAGE]).to.be.a('string');
|
|
134
|
+
expect(callArgs[CALL_ARG_MESSAGE]).to.equal('out of scope timeout, reset to min');
|
|
126
135
|
});
|
|
127
136
|
});
|
|
128
137
|
it('should generate warning: out of scope timeout: 150 becoming max', () => {
|
|
129
138
|
options.retry.timeout = 150;
|
|
130
139
|
return rpRetry(options).then(() => {
|
|
131
|
-
|
|
132
|
-
const callArgs = loggerSpyWarn.getCall(
|
|
133
|
-
expect(callArgs[
|
|
134
|
-
expect(callArgs[
|
|
140
|
+
assert.calledOnce(loggerSpyWarn);
|
|
141
|
+
const callArgs = loggerSpyWarn.getCall(FIRST_CALL).args;
|
|
142
|
+
expect(callArgs[CALL_ARG_MESSAGE]).to.be.a('string');
|
|
143
|
+
expect(callArgs[CALL_ARG_MESSAGE]).to.equal('out of scope timeout, reset to max');
|
|
135
144
|
});
|
|
136
145
|
});
|
|
137
146
|
it('should generate warning: invalid timeout: "notANumber" becoming default', () => {
|
|
138
147
|
options.retry.timeout = 'notANumber';
|
|
139
148
|
return rpRetry(options).then(() => {
|
|
140
|
-
|
|
141
|
-
const callArgs = loggerSpyWarn.getCall(
|
|
142
|
-
expect(callArgs[
|
|
143
|
-
expect(callArgs[
|
|
149
|
+
assert.calledOnce(loggerSpyWarn);
|
|
150
|
+
const callArgs = loggerSpyWarn.getCall(FIRST_CALL).args;
|
|
151
|
+
expect(callArgs[CALL_ARG_MESSAGE]).to.be.a('string');
|
|
152
|
+
expect(callArgs[CALL_ARG_MESSAGE]).to.equal('invalid timeout, reset to default');
|
|
144
153
|
});
|
|
145
154
|
});
|
|
146
155
|
it('should not generate warning: in scope timeout', () => {
|
|
147
156
|
options.retry.timeout = 50;
|
|
148
157
|
return rpRetry(options).then(() => {
|
|
149
|
-
|
|
158
|
+
assert.notCalled(loggerSpyWarn);
|
|
150
159
|
});
|
|
151
160
|
});
|
|
152
161
|
it('should generate warning: invalid retryStrategy: "notANumber" becoming default function', () => {
|
|
153
162
|
options.retry.retryStrategy = 'notAFunction';
|
|
154
163
|
return rpRetry(options).then(() => {
|
|
155
|
-
|
|
156
|
-
const callArgs = loggerSpyWarn.getCall(
|
|
157
|
-
expect(callArgs[
|
|
158
|
-
expect(callArgs[
|
|
164
|
+
assert.calledOnce(loggerSpyWarn);
|
|
165
|
+
const callArgs = loggerSpyWarn.getCall(FIRST_CALL).args;
|
|
166
|
+
expect(callArgs[CALL_ARG_MESSAGE]).to.be.a('string');
|
|
167
|
+
expect(callArgs[CALL_ARG_MESSAGE]).to.equal('invalid retryStrategy, ignoring');
|
|
159
168
|
});
|
|
160
169
|
});
|
|
161
170
|
it('should generate warning: invalid delayStrategy: "notANumber" becoming delay or default', () => {
|
|
162
171
|
options.retry.delayStrategy = 'notAFunction';
|
|
163
172
|
return rpRetry(options).then(() => {
|
|
164
|
-
|
|
165
|
-
const callArgs = loggerSpyWarn.getCall(
|
|
166
|
-
expect(callArgs[
|
|
167
|
-
expect(callArgs[
|
|
173
|
+
assert.calledOnce(loggerSpyWarn);
|
|
174
|
+
const callArgs = loggerSpyWarn.getCall(FIRST_CALL).args;
|
|
175
|
+
expect(callArgs[CALL_ARG_MESSAGE]).to.be.a('string');
|
|
176
|
+
expect(callArgs[CALL_ARG_MESSAGE]).to.equal('invalid delayStrategy, using delay');
|
|
168
177
|
});
|
|
169
178
|
});
|
|
170
179
|
it('should generate multiple warnings', () => {
|
|
171
180
|
options.retry.delayStrategy = 'notAFunction';
|
|
172
181
|
options.retry.retries = 'notANumber';
|
|
173
182
|
return rpRetry(options).then(() => {
|
|
174
|
-
|
|
183
|
+
assert.calledTwice(loggerSpyWarn);
|
|
175
184
|
});
|
|
176
185
|
});
|
|
177
186
|
it('should generate warning: invalid logLevel.request, reset to default', () => {
|
|
@@ -179,20 +188,20 @@ describe('request-retry Unit Tests', () => {
|
|
|
179
188
|
request: 'unknownLevel',
|
|
180
189
|
};
|
|
181
190
|
return rpRetry(options).then(() => {
|
|
182
|
-
|
|
183
|
-
const callArgs = loggerSpyWarn.getCall(
|
|
184
|
-
expect(callArgs[
|
|
185
|
-
expect(callArgs[
|
|
191
|
+
assert.calledOnce(loggerSpyWarn);
|
|
192
|
+
const callArgs = loggerSpyWarn.getCall(FIRST_CALL).args;
|
|
193
|
+
expect(callArgs[CALL_ARG_MESSAGE]).to.be.a('string');
|
|
194
|
+
expect(callArgs[CALL_ARG_MESSAGE]).to.equal('invalid logLevel.request, reset to default');
|
|
186
195
|
});
|
|
187
196
|
});
|
|
188
197
|
it('should not generate warning: correct logLevel (info)', () => {
|
|
189
|
-
loggerSpyInfo =
|
|
198
|
+
loggerSpyInfo = spy(logger, 'info');
|
|
190
199
|
options.retry.logLevel = {
|
|
191
200
|
response: 'info',
|
|
192
201
|
};
|
|
193
202
|
return rpRetry(options).then(() => {
|
|
194
|
-
|
|
195
|
-
|
|
203
|
+
assert.notCalled(loggerSpyWarn);
|
|
204
|
+
assert.calledOnce(loggerSpyInfo);
|
|
196
205
|
loggerSpyInfo.restore();
|
|
197
206
|
});
|
|
198
207
|
});
|
|
@@ -201,10 +210,10 @@ describe('request-retry Unit Tests', () => {
|
|
|
201
210
|
responseDetails: 'unknownDetails',
|
|
202
211
|
};
|
|
203
212
|
return rpRetry(options).then(() => {
|
|
204
|
-
|
|
205
|
-
const callArgs = loggerSpyWarn.getCall(
|
|
206
|
-
expect(callArgs[
|
|
207
|
-
expect(callArgs[
|
|
213
|
+
assert.calledOnce(loggerSpyWarn);
|
|
214
|
+
const callArgs = loggerSpyWarn.getCall(FIRST_CALL).args;
|
|
215
|
+
expect(callArgs[CALL_ARG_MESSAGE]).to.be.a('string');
|
|
216
|
+
expect(callArgs[CALL_ARG_MESSAGE]).to.equal('invalid logLevel.responseDetails, reset to default');
|
|
208
217
|
});
|
|
209
218
|
});
|
|
210
219
|
it('should not generate warning: correct logLevelResponseName', () => {
|
|
@@ -212,7 +221,7 @@ describe('request-retry Unit Tests', () => {
|
|
|
212
221
|
responseName: 'aName',
|
|
213
222
|
};
|
|
214
223
|
return rpRetry(options).then(() => {
|
|
215
|
-
|
|
224
|
+
assert.notCalled(loggerSpyWarn);
|
|
216
225
|
});
|
|
217
226
|
});
|
|
218
227
|
it('should generate warning: invalid logLevel.responseName (""), reset to default', () => {
|
|
@@ -220,10 +229,10 @@ describe('request-retry Unit Tests', () => {
|
|
|
220
229
|
responseName: '',
|
|
221
230
|
};
|
|
222
231
|
return rpRetry(options).then(() => {
|
|
223
|
-
|
|
224
|
-
const callArgs = loggerSpyWarn.getCall(
|
|
225
|
-
expect(callArgs[
|
|
226
|
-
expect(callArgs[
|
|
232
|
+
assert.calledOnce(loggerSpyWarn);
|
|
233
|
+
const callArgs = loggerSpyWarn.getCall(FIRST_CALL).args;
|
|
234
|
+
expect(callArgs[CALL_ARG_MESSAGE]).to.be.a('string');
|
|
235
|
+
expect(callArgs[CALL_ARG_MESSAGE]).to.equal('invalid logLevel.responseName, reset to default');
|
|
227
236
|
});
|
|
228
237
|
});
|
|
229
238
|
it('should generate warning: invalid logLevel.responseName (" ""), reset to default', () => {
|
|
@@ -231,15 +240,15 @@ describe('request-retry Unit Tests', () => {
|
|
|
231
240
|
responseName: ' ',
|
|
232
241
|
};
|
|
233
242
|
return rpRetry(options).then(() => {
|
|
234
|
-
|
|
235
|
-
const callArgs = loggerSpyWarn.getCall(
|
|
236
|
-
expect(callArgs[
|
|
237
|
-
expect(callArgs[
|
|
243
|
+
assert.calledOnce(loggerSpyWarn);
|
|
244
|
+
const callArgs = loggerSpyWarn.getCall(FIRST_CALL).args;
|
|
245
|
+
expect(callArgs[CALL_ARG_MESSAGE]).to.be.a('string');
|
|
246
|
+
expect(callArgs[CALL_ARG_MESSAGE]).to.equal('invalid logLevel.responseName, reset to default');
|
|
238
247
|
});
|
|
239
248
|
});
|
|
240
249
|
});
|
|
241
|
-
describe('rpRetry(options) options validation run', function
|
|
242
|
-
this.timeout(
|
|
250
|
+
describe('rpRetry(options) options validation run', function Test() {
|
|
251
|
+
this.timeout(TIMEOUT);
|
|
243
252
|
const options = {
|
|
244
253
|
method: 'GET',
|
|
245
254
|
headers: {
|
|
@@ -250,21 +259,23 @@ describe('request-retry Unit Tests', () => {
|
|
|
250
259
|
retry: {},
|
|
251
260
|
};
|
|
252
261
|
beforeEach(() => {
|
|
253
|
-
loggerSpyWarn =
|
|
262
|
+
loggerSpyWarn = spy(logger, 'warn');
|
|
254
263
|
});
|
|
255
264
|
afterEach(() => {
|
|
256
265
|
options.retry = {};
|
|
257
266
|
loggerSpyWarn.restore();
|
|
258
267
|
});
|
|
259
268
|
it('should generate warning: bad retry strategy (throwing an error), ignoring and using default retries', () => {
|
|
260
|
-
options.retry.retryStrategy = () => {
|
|
269
|
+
options.retry.retryStrategy = () => {
|
|
270
|
+
throw new Error('this is a test');
|
|
271
|
+
};
|
|
261
272
|
options.retry.retries = 2;
|
|
262
273
|
return rpRetry(options)
|
|
263
274
|
.catch(() => {
|
|
264
|
-
|
|
265
|
-
const callArgs = loggerSpyWarn.getCall(
|
|
266
|
-
expect(callArgs[
|
|
267
|
-
expect(callArgs[
|
|
275
|
+
assert.called(loggerSpyWarn);
|
|
276
|
+
const callArgs = loggerSpyWarn.getCall(FIRST_CALL).args;
|
|
277
|
+
expect(callArgs[CALL_ARG_MESSAGE]).to.be.a('string');
|
|
278
|
+
expect(callArgs[CALL_ARG_MESSAGE]).to.equal('bad retry strategy');
|
|
268
279
|
});
|
|
269
280
|
});
|
|
270
281
|
it('should generate warning: bad retry strategy (not returning a boolean), ignoring and using default retries', () => {
|
|
@@ -272,10 +283,10 @@ describe('request-retry Unit Tests', () => {
|
|
|
272
283
|
options.retry.retries = 2;
|
|
273
284
|
return rpRetry(options)
|
|
274
285
|
.catch(() => {
|
|
275
|
-
|
|
276
|
-
const callArgs = loggerSpyWarn.getCall(
|
|
277
|
-
expect(callArgs[
|
|
278
|
-
expect(callArgs[
|
|
286
|
+
assert.called(loggerSpyWarn);
|
|
287
|
+
const callArgs = loggerSpyWarn.getCall(FIRST_CALL).args;
|
|
288
|
+
expect(callArgs[CALL_ARG_MESSAGE]).to.be.a('string');
|
|
289
|
+
expect(callArgs[CALL_ARG_MESSAGE]).to.equal('bad retry strategy');
|
|
279
290
|
});
|
|
280
291
|
});
|
|
281
292
|
it('should not generate warning: retry strategy returning a boolean', () => {
|
|
@@ -283,18 +294,20 @@ describe('request-retry Unit Tests', () => {
|
|
|
283
294
|
options.retry.retries = 2;
|
|
284
295
|
return rpRetry(options)
|
|
285
296
|
.catch(() => {
|
|
286
|
-
|
|
297
|
+
assert.called(loggerSpyWarn);
|
|
287
298
|
});
|
|
288
299
|
});
|
|
289
300
|
it('should generate warning: bad delay strategy (throwing an error), ignoring and using default delay', () => {
|
|
290
|
-
options.retry.delayStrategy = () => {
|
|
301
|
+
options.retry.delayStrategy = () => {
|
|
302
|
+
throw new Error('this is a test');
|
|
303
|
+
};
|
|
291
304
|
options.retry.retries = 2;
|
|
292
305
|
return rpRetry(options)
|
|
293
306
|
.catch(() => {
|
|
294
|
-
|
|
295
|
-
const callArgs = loggerSpyWarn.getCall(
|
|
296
|
-
expect(callArgs[
|
|
297
|
-
expect(callArgs[
|
|
307
|
+
assert.called(loggerSpyWarn);
|
|
308
|
+
const callArgs = loggerSpyWarn.getCall(FIRST_CALL).args;
|
|
309
|
+
expect(callArgs[CALL_ARG_MESSAGE]).to.be.a('string');
|
|
310
|
+
expect(callArgs[CALL_ARG_MESSAGE]).to.equal('bad delay strategy');
|
|
298
311
|
});
|
|
299
312
|
});
|
|
300
313
|
it('should generate warning: bad delay strategy (not returning a number), ignoring and using default delay', () => {
|
|
@@ -302,40 +315,40 @@ describe('request-retry Unit Tests', () => {
|
|
|
302
315
|
options.retry.retries = 2;
|
|
303
316
|
return rpRetry(options)
|
|
304
317
|
.catch(() => {
|
|
305
|
-
|
|
306
|
-
const callArgs = loggerSpyWarn.getCall(
|
|
307
|
-
expect(callArgs[
|
|
308
|
-
expect(callArgs[
|
|
318
|
+
assert.called(loggerSpyWarn);
|
|
319
|
+
const callArgs = loggerSpyWarn.getCall(FIRST_CALL).args;
|
|
320
|
+
expect(callArgs[CALL_ARG_MESSAGE]).to.be.a('string');
|
|
321
|
+
expect(callArgs[CALL_ARG_MESSAGE]).to.equal('bad delay strategy');
|
|
309
322
|
});
|
|
310
323
|
});
|
|
311
324
|
it('should generate warning: bad delay strategy (returning an out of scope number), ignoring and using default delay', () => {
|
|
312
|
-
options.retry.delayStrategy = () =>
|
|
325
|
+
options.retry.delayStrategy = () => DELAY_STRATEGY_RESPONSE_1;
|
|
313
326
|
options.retry.retries = 2;
|
|
314
327
|
return rpRetry(options)
|
|
315
328
|
.catch(() => {
|
|
316
|
-
|
|
317
|
-
const callArgs = loggerSpyWarn.getCall(
|
|
318
|
-
expect(callArgs[
|
|
319
|
-
expect(callArgs[
|
|
329
|
+
assert.called(loggerSpyWarn);
|
|
330
|
+
const callArgs = loggerSpyWarn.getCall(FIRST_CALL).args;
|
|
331
|
+
expect(callArgs[CALL_ARG_MESSAGE]).to.be.a('string');
|
|
332
|
+
expect(callArgs[CALL_ARG_MESSAGE]).to.equal('calculated delay out of scope: using delay or default');
|
|
320
333
|
});
|
|
321
334
|
});
|
|
322
335
|
it('should not generate warning: delay strategy returning an in scope number', () => {
|
|
323
|
-
options.retry.delayStrategy = () =>
|
|
336
|
+
options.retry.delayStrategy = () => DELAY_STRATEGY_RESPONSE_2;
|
|
324
337
|
options.retry.retries = 2;
|
|
325
338
|
return rpRetry(options)
|
|
326
339
|
.catch(() => {
|
|
327
|
-
|
|
340
|
+
assert.called(loggerSpyWarn);
|
|
328
341
|
});
|
|
329
342
|
});
|
|
330
343
|
it('should generate a timeout error', () => {
|
|
331
|
-
options.retry.delayStrategy = () =>
|
|
332
|
-
options.retry.timeout =
|
|
333
|
-
options.retry.retries =
|
|
344
|
+
options.retry.delayStrategy = () => RETRY_DELAY;
|
|
345
|
+
options.retry.timeout = RETRY_TIMEOUT;
|
|
346
|
+
options.retry.retries = RETRY_RETRIES;
|
|
334
347
|
return rpRetry(options)
|
|
335
348
|
.catch((err) => {
|
|
336
349
|
expect(err.name).to.equal('System');
|
|
337
350
|
expect(err.cause.name).to.equal('TimeoutError');
|
|
338
|
-
//
|
|
351
|
+
// assert.called(loggerSpyWarn);
|
|
339
352
|
});
|
|
340
353
|
});
|
|
341
354
|
it('should stop mock server', () => rpRetry({
|
|
@@ -345,7 +358,7 @@ describe('request-retry Unit Tests', () => {
|
|
|
345
358
|
},
|
|
346
359
|
url: 'http://localhost:9070/stop',
|
|
347
360
|
retry: {
|
|
348
|
-
delayStrategy: () =>
|
|
361
|
+
delayStrategy: () => DELAY_STRATEGY_RESPONSE_3,
|
|
349
362
|
retries: 1,
|
|
350
363
|
},
|
|
351
364
|
})
|
package/test/testEnv.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
/* eslint no-process-env: "off" */
|
|
2
|
+
import process from 'process';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* The following environment variables are set for the test:
|
|
@@ -13,8 +14,10 @@
|
|
|
13
14
|
* | LOG_MODE | log mode | none
|
|
14
15
|
*/
|
|
15
16
|
|
|
16
|
-
|
|
17
|
-
|
|
17
|
+
/*
|
|
18
|
+
* process.env.SUMO_LOGIC_ENDPOINT = 'http://localhost:9080/logs/';
|
|
19
|
+
* process.env.SUMO_LOGIC_COLLECTOR_CODE = '1234';
|
|
20
|
+
*/
|
|
18
21
|
process.env.SUMO_LOGIC_ENDPOINT = null;
|
|
19
22
|
process.env.SUMO_LOGIC_COLLECTOR_CODE = null;
|
|
20
23
|
process.env.NO_STACK = 'yes';
|
package/.eslintrc
DELETED
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"plugins": [
|
|
3
|
-
"@mimik/document-env",
|
|
4
|
-
"@mimik/dependencies"
|
|
5
|
-
],
|
|
6
|
-
"env": {
|
|
7
|
-
"node": true
|
|
8
|
-
},
|
|
9
|
-
"parserOptions": {
|
|
10
|
-
"ecmaVersion": 2020
|
|
11
|
-
},
|
|
12
|
-
"extends": "airbnb",
|
|
13
|
-
"rules": {
|
|
14
|
-
"import/no-extraneous-dependencies": ["error", {"devDependencies": true}],
|
|
15
|
-
"import/no-unresolved": ["error", { "amd": true, "commonjs": true, "caseSensitiveStrict": true }],
|
|
16
|
-
"brace-style": [1, "stroustrup", {"allowSingleLine": true}],
|
|
17
|
-
"no-confusing-arrow": [0], // arrow isnt confusing
|
|
18
|
-
"max-len": [1, 180, { "ignoreComments": true }],
|
|
19
|
-
"linebreak-style": 0,
|
|
20
|
-
"quotes": [1, "single"],
|
|
21
|
-
"semi": [1, "always"],
|
|
22
|
-
"no-process-env": ["error"],
|
|
23
|
-
"@mimik/document-env/validate-document-env": 2,
|
|
24
|
-
"@mimik/dependencies/case-sensitive": 2,
|
|
25
|
-
"@mimik/dependencies/no-cycles": 2,
|
|
26
|
-
"@mimik/dependencies/require-json-ext": 2
|
|
27
|
-
},
|
|
28
|
-
"settings":{
|
|
29
|
-
"react": {
|
|
30
|
-
"version": "detect"
|
|
31
|
-
}
|
|
32
|
-
},
|
|
33
|
-
"globals": {
|
|
34
|
-
"module": true,
|
|
35
|
-
"require": true,
|
|
36
|
-
"const": false,
|
|
37
|
-
"it": false,
|
|
38
|
-
"describe": false,
|
|
39
|
-
"before": true,
|
|
40
|
-
"after": true,
|
|
41
|
-
"JSON": true
|
|
42
|
-
}
|
|
43
|
-
}
|