@rabbitio/ui-kit 1.0.0-beta.41 → 1.0.0-beta.43

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.
Files changed (112) hide show
  1. package/.gitlab-ci.yml +29 -0
  2. package/.husky/commit-msg +8 -0
  3. package/.husky/pre-push +1 -0
  4. package/README.md +13 -4
  5. package/coverage/base.css +224 -0
  6. package/coverage/block-navigation.js +87 -0
  7. package/coverage/clover.xml +6516 -0
  8. package/coverage/coverage-final.json +43 -0
  9. package/coverage/favicon.png +0 -0
  10. package/coverage/index.html +416 -0
  11. package/coverage/prettify.css +1 -0
  12. package/coverage/prettify.js +2 -0
  13. package/coverage/rabbit-ui-kit/index.html +116 -0
  14. package/coverage/rabbit-ui-kit/index.js.html +88 -0
  15. package/coverage/rabbit-ui-kit/src/common/adapters/axiosAdapter.js.html +190 -0
  16. package/coverage/rabbit-ui-kit/src/common/adapters/index.html +116 -0
  17. package/coverage/rabbit-ui-kit/src/common/amountUtils.js.html +1393 -0
  18. package/coverage/rabbit-ui-kit/src/common/errorUtils.js.html +211 -0
  19. package/coverage/rabbit-ui-kit/src/common/external-apis/apiGroups.js.html +250 -0
  20. package/coverage/rabbit-ui-kit/src/common/external-apis/index.html +131 -0
  21. package/coverage/rabbit-ui-kit/src/common/external-apis/ipAddressProviders.js.html +499 -0
  22. package/coverage/rabbit-ui-kit/src/common/fiatCurrenciesService.js.html +568 -0
  23. package/coverage/rabbit-ui-kit/src/common/index.html +146 -0
  24. package/coverage/rabbit-ui-kit/src/common/models/blockchain.js.html +115 -0
  25. package/coverage/rabbit-ui-kit/src/common/models/coin.js.html +556 -0
  26. package/coverage/rabbit-ui-kit/src/common/models/index.html +146 -0
  27. package/coverage/rabbit-ui-kit/src/common/models/protocol.js.html +100 -0
  28. package/coverage/rabbit-ui-kit/src/common/utils/cache.js.html +889 -0
  29. package/coverage/rabbit-ui-kit/src/common/utils/emailAPI.js.html +139 -0
  30. package/coverage/rabbit-ui-kit/src/common/utils/index.html +161 -0
  31. package/coverage/rabbit-ui-kit/src/common/utils/logging/index.html +131 -0
  32. package/coverage/rabbit-ui-kit/src/common/utils/logging/logger.js.html +229 -0
  33. package/coverage/rabbit-ui-kit/src/common/utils/logging/logsStorage.js.html +268 -0
  34. package/coverage/rabbit-ui-kit/src/common/utils/postponeExecution.js.html +118 -0
  35. package/coverage/rabbit-ui-kit/src/common/utils/safeStringify.js.html +235 -0
  36. package/coverage/rabbit-ui-kit/src/components/atoms/AssetIcon/AssetIcon.jsx.html +250 -0
  37. package/coverage/rabbit-ui-kit/src/components/atoms/AssetIcon/index.html +116 -0
  38. package/coverage/rabbit-ui-kit/src/components/atoms/LoadingDots/LoadingDots.jsx.html +256 -0
  39. package/coverage/rabbit-ui-kit/src/components/atoms/LoadingDots/index.html +116 -0
  40. package/coverage/rabbit-ui-kit/src/components/atoms/SupportChat/SupportChat.jsx.html +229 -0
  41. package/coverage/rabbit-ui-kit/src/components/atoms/SupportChat/index.html +116 -0
  42. package/coverage/rabbit-ui-kit/src/components/atoms/buttons/Button/Button.jsx.html +802 -0
  43. package/coverage/rabbit-ui-kit/src/components/atoms/buttons/Button/index.html +116 -0
  44. package/coverage/rabbit-ui-kit/src/components/hooks/index.html +131 -0
  45. package/coverage/rabbit-ui-kit/src/components/hooks/useCallHandlingErrors.js.html +163 -0
  46. package/coverage/rabbit-ui-kit/src/components/hooks/useReferredState.js.html +157 -0
  47. package/coverage/rabbit-ui-kit/src/components/utils/index.html +146 -0
  48. package/coverage/rabbit-ui-kit/src/components/utils/inputValueProviders.js.html +259 -0
  49. package/coverage/rabbit-ui-kit/src/components/utils/uiUtils.js.html +127 -0
  50. package/coverage/rabbit-ui-kit/src/components/utils/urlQueryUtils.js.html +346 -0
  51. package/coverage/rabbit-ui-kit/src/index.html +116 -0
  52. package/coverage/rabbit-ui-kit/src/index.js.html +250 -0
  53. package/coverage/rabbit-ui-kit/src/robustExteranlApiCallerService/cacheAndConcurrentRequestsResolver.js.html +1762 -0
  54. package/coverage/rabbit-ui-kit/src/robustExteranlApiCallerService/cachedRobustExternalApiCallerService.js.html +649 -0
  55. package/coverage/rabbit-ui-kit/src/robustExteranlApiCallerService/cancelProcessing.js.html +172 -0
  56. package/coverage/rabbit-ui-kit/src/robustExteranlApiCallerService/concurrentCalculationsMetadataHolder.js.html +394 -0
  57. package/coverage/rabbit-ui-kit/src/robustExteranlApiCallerService/externalApiProvider.js.html +553 -0
  58. package/coverage/rabbit-ui-kit/src/robustExteranlApiCallerService/externalServicesStatsCollector.js.html +331 -0
  59. package/coverage/rabbit-ui-kit/src/robustExteranlApiCallerService/index.html +206 -0
  60. package/coverage/rabbit-ui-kit/src/robustExteranlApiCallerService/robustExternalAPICallerService.js.html +1249 -0
  61. package/coverage/rabbit-ui-kit/src/swaps-lib/external-apis/index.html +131 -0
  62. package/coverage/rabbit-ui-kit/src/swaps-lib/external-apis/swapProvider.js.html +727 -0
  63. package/coverage/rabbit-ui-kit/src/swaps-lib/external-apis/swapspaceSwapProvider.js.html +2899 -0
  64. package/coverage/rabbit-ui-kit/src/swaps-lib/models/baseSwapCreationInfo.js.html +214 -0
  65. package/coverage/rabbit-ui-kit/src/swaps-lib/models/existingSwap.js.html +304 -0
  66. package/coverage/rabbit-ui-kit/src/swaps-lib/models/existingSwapWithFiatData.js.html +487 -0
  67. package/coverage/rabbit-ui-kit/src/swaps-lib/models/index.html +146 -0
  68. package/coverage/rabbit-ui-kit/src/swaps-lib/services/index.html +116 -0
  69. package/coverage/rabbit-ui-kit/src/swaps-lib/services/publicSwapService.js.html +2191 -0
  70. package/coverage/rabbit-ui-kit/src/swaps-lib/utils/index.html +116 -0
  71. package/coverage/rabbit-ui-kit/src/swaps-lib/utils/swapUtils.js.html +742 -0
  72. package/coverage/rabbit-ui-kit/stories/atoms/LoadingDots.stories.jsx.html +226 -0
  73. package/coverage/rabbit-ui-kit/stories/atoms/buttons/Button.stories.jsx.html +946 -0
  74. package/coverage/rabbit-ui-kit/stories/atoms/buttons/index.html +116 -0
  75. package/coverage/rabbit-ui-kit/stories/atoms/index.html +116 -0
  76. package/coverage/sort-arrow-sprite.png +0 -0
  77. package/coverage/sorter.js +196 -0
  78. package/dist/index.cjs +1706 -1498
  79. package/dist/index.cjs.map +1 -1
  80. package/dist/index.modern.js +817 -666
  81. package/dist/index.modern.js.map +1 -1
  82. package/dist/index.module.js +1704 -1498
  83. package/dist/index.module.js.map +1 -1
  84. package/dist/index.umd.js +1664 -1456
  85. package/dist/index.umd.js.map +1 -1
  86. package/package.json +11 -3
  87. package/src/common/amountUtils.js +4 -2
  88. package/src/common/external-apis/ipAddressProviders.js +138 -0
  89. package/src/common/tests/integration/external-apis/ipAddressProviders/getClientIpAddress.test.js +14 -0
  90. package/src/common/utils/cache.js +4 -4
  91. package/src/components/tests/utils/inputValueProviders/provideFormatOfFloatValueByInputString.test.js +139 -0
  92. package/src/components/tests/utils/urlQueryUtils/getQueryParameterValues.test.js +71 -0
  93. package/src/components/tests/utils/urlQueryUtils/saveQueryParameterAndValues.test.js +144 -0
  94. package/src/components/utils/inputValueProviders.js +58 -0
  95. package/src/index.js +2 -0
  96. package/src/robustExteranlApiCallerService/robustExternalAPICallerService.js +4 -2
  97. package/src/robustExteranlApiCallerService/tests/robustExternalAPICallerService/robustExternalAPICallerService/callExternalAPI/_performCallAttempt.test.js +787 -0
  98. package/src/robustExteranlApiCallerService/tests/robustExternalAPICallerService/robustExternalAPICallerService/callExternalAPI/callExternalAPI.test.js +745 -0
  99. package/src/robustExteranlApiCallerService/tests/robustExternalAPICallerService/robustExternalAPICallerService/constructor.test.js +31 -0
  100. package/src/swaps-lib/external-apis/swapProvider.js +17 -4
  101. package/src/swaps-lib/external-apis/swapspaceSwapProvider.js +91 -30
  102. package/src/swaps-lib/models/baseSwapCreationInfo.js +4 -1
  103. package/src/swaps-lib/models/existingSwap.js +3 -0
  104. package/src/swaps-lib/models/existingSwapWithFiatData.js +4 -0
  105. package/src/swaps-lib/services/publicSwapService.js +32 -4
  106. package/src/swaps-lib/test/external-apis/swapspaceSwapProvider/_fetchSupportedCurrenciesIfNeeded.test.js +506 -0
  107. package/src/swaps-lib/test/external-apis/swapspaceSwapProvider/createSwap.test.js +1311 -0
  108. package/src/swaps-lib/test/external-apis/swapspaceSwapProvider/getAllSupportedCurrencies.test.js +76 -0
  109. package/src/swaps-lib/test/external-apis/swapspaceSwapProvider/getDepositCurrencies.test.js +82 -0
  110. package/src/swaps-lib/test/external-apis/swapspaceSwapProvider/getSwapInfo.test.js +1892 -0
  111. package/src/swaps-lib/test/external-apis/swapspaceSwapProvider/getWithdrawalCurrencies.test.js +111 -0
  112. package/src/swaps-lib/test/utils/swapUtils/safeHandleRequestsLimitExceeding.test.js +88 -0
@@ -0,0 +1,745 @@
1
+ import sinon from "sinon";
2
+ import should from "should";
3
+
4
+ import { beforeEach, afterEach, describe, it } from "vitest";
5
+
6
+ import { v4 } from "uuid";
7
+ import { ExternalApiProvider } from "../../../../externalApiProvider.js";
8
+ import { ApiGroup } from "../../../../../common/external-apis/apiGroups.js";
9
+ import { RobustExternalAPICallerService } from "../../../../robustExternalAPICallerService.js";
10
+
11
+ describe("RobustExternalAPICallerService", function () {
12
+ describe("#callExternalAPI", function () {
13
+ const generateProviders = (
14
+ count,
15
+ endpoints = "any",
16
+ httpMethods = "any",
17
+ getDataByResponseFunctions = "any",
18
+ RPSes = null,
19
+ timeouts = null
20
+ ) => {
21
+ const providers = [];
22
+ const composeQueryStringStubs = [];
23
+ for (let i = 0; i < count; ++i) {
24
+ class TempProvider extends ExternalApiProvider {
25
+ constructor() {
26
+ super(
27
+ endpoints === "any" ? "any" : endpoints[i],
28
+ httpMethods === "any" ? "get" : httpMethods[i],
29
+ timeouts ? timeouts[i] : null,
30
+ RPSes === null
31
+ ? new ApiGroup(v4(), 10)
32
+ : new ApiGroup(v4(), RPSes[i])
33
+ );
34
+ }
35
+ getDataByResponse(
36
+ response,
37
+ params = [],
38
+ subRequestIndex = 0,
39
+ iterationsData = []
40
+ ) {
41
+ const func =
42
+ getDataByResponseFunctions === "any"
43
+ ? "any"
44
+ : Array.isArray(getDataByResponseFunctions[i])
45
+ ? getDataByResponseFunctions[i][
46
+ subRequestIndex
47
+ ]
48
+ : getDataByResponseFunctions[i];
49
+ return func();
50
+ }
51
+ }
52
+ const provider = new TempProvider();
53
+ providers.push(provider);
54
+ const composeQueryStringStub = sinon.stub(
55
+ provider,
56
+ "composeQueryString"
57
+ );
58
+ composeQueryStringStubs.push(composeQueryStringStub);
59
+ }
60
+ const isRpsExceededStubs = [];
61
+ providers.forEach((provider) => {
62
+ if (
63
+ provider.httpMethod === "post" ||
64
+ provider.httpMethod === "put" ||
65
+ provider.httpMethod === "patch"
66
+ ) {
67
+ provider.composeBody = () => {};
68
+ sinon.stub(provider, "composeBody");
69
+ }
70
+ const isRPSExceededStub = sinon.stub(provider, "isRpsExceeded");
71
+ isRPSExceededStub.returns(false);
72
+ isRpsExceededStubs.push(isRPSExceededStub);
73
+ });
74
+
75
+ return { providers, isRpsExceededStubs, composeQueryStringStubs };
76
+ };
77
+
78
+ const anyTimeout = 1000;
79
+ const anyData = "any data";
80
+
81
+ let setTimeoutBackup = null;
82
+ beforeEach(function () {
83
+ setTimeoutBackup = global.setTimeout;
84
+ });
85
+
86
+ afterEach(function () {
87
+ global.setTimeout = setTimeoutBackup;
88
+ });
89
+
90
+ it("Should perform n+1 attempts when first n requests return null and n+1's returns not-null data and n is less than max_attempts_count", async function () {
91
+ const retrieverService = new RobustExternalAPICallerService(
92
+ "testService",
93
+ generateProviders(4).providers,
94
+ // eslint-disable-next-line no-console
95
+ console.log
96
+ );
97
+ const attemptsCount = 6;
98
+ const failingAttemptsCount = Math.floor(attemptsCount * 0.6);
99
+ const _performCallAttemptStub = sinon.stub(
100
+ retrieverService,
101
+ "_performCallAttempt"
102
+ );
103
+ for (let i = 0; i < failingAttemptsCount; ++i) {
104
+ _performCallAttemptStub.onCall(i).resolves({
105
+ data: null,
106
+ shouldBeForceRetried: false,
107
+ errors: [],
108
+ rpsFactor: RobustExternalAPICallerService.defaultRPSFactor,
109
+ });
110
+ }
111
+ _performCallAttemptStub.onCall(failingAttemptsCount).resolves({
112
+ data: "some data",
113
+ shouldBeForceRetried: false,
114
+ errors: [],
115
+ rpsFactor: RobustExternalAPICallerService.defaultRPSFactor,
116
+ });
117
+
118
+ await retrieverService.callExternalAPI(
119
+ [],
120
+ anyTimeout,
121
+ null,
122
+ attemptsCount
123
+ );
124
+
125
+ _performCallAttemptStub.callCount.should.be.equal(
126
+ failingAttemptsCount + 1
127
+ );
128
+ });
129
+
130
+ it("Should perform max_attempts_count due to null data and shouldBeForceRetried is false for the last call", async function () {
131
+ const retrieverService = new RobustExternalAPICallerService(
132
+ "testService",
133
+ generateProviders(4).providers,
134
+ // eslint-disable-next-line no-console
135
+ console.log
136
+ );
137
+ const attemptsCount = 5;
138
+ const _performCallAttemptStub = sinon.stub(
139
+ retrieverService,
140
+ "_performCallAttempt"
141
+ );
142
+ for (let i = 0; i < attemptsCount; ++i) {
143
+ _performCallAttemptStub.onCall(i).resolves({
144
+ data: null,
145
+ shouldBeForceRetried: false,
146
+ errors: [],
147
+ rpsFactor: RobustExternalAPICallerService.defaultRPSFactor,
148
+ });
149
+ }
150
+
151
+ try {
152
+ await retrieverService.callExternalAPI(
153
+ [],
154
+ anyTimeout,
155
+ null,
156
+ attemptsCount
157
+ );
158
+ } catch (e) {
159
+ // We expect an error here
160
+ }
161
+
162
+ _performCallAttemptStub.callCount.should.be.equal(attemptsCount);
163
+ });
164
+
165
+ it("Should perform max_attempts_count + n requests when all attempts return null data and shouldBeForceRetried is true for max_attempts_count-1 request and for n more requests", async function () {
166
+ const retrieverService = new RobustExternalAPICallerService(
167
+ "testService",
168
+ generateProviders(4).providers,
169
+ // eslint-disable-next-line no-console
170
+ console.log
171
+ );
172
+ const attemptsCount = 5;
173
+ const countOfAdditionalRequestsDueToForceRetryNeed = 3;
174
+ const _performCallAttemptStub = sinon.stub(
175
+ retrieverService,
176
+ "_performCallAttempt"
177
+ );
178
+ for (
179
+ let i = 0;
180
+ i <
181
+ attemptsCount + countOfAdditionalRequestsDueToForceRetryNeed;
182
+ ++i
183
+ ) {
184
+ _performCallAttemptStub.onCall(i).resolves({
185
+ data: null,
186
+ shouldBeForceRetried:
187
+ i >= attemptsCount - 1 &&
188
+ i !==
189
+ attemptsCount +
190
+ countOfAdditionalRequestsDueToForceRetryNeed -
191
+ 1,
192
+ errors: [],
193
+ rpsFactor: RobustExternalAPICallerService.defaultRPSFactor,
194
+ });
195
+ }
196
+
197
+ try {
198
+ await retrieverService.callExternalAPI(
199
+ [],
200
+ anyTimeout,
201
+ null,
202
+ attemptsCount
203
+ );
204
+ } catch (e) {
205
+ // We expect an error here
206
+ }
207
+
208
+ _performCallAttemptStub.callCount.should.be.equal(
209
+ attemptsCount + countOfAdditionalRequestsDueToForceRetryNeed
210
+ );
211
+ });
212
+
213
+ it("Should return null if all requests return null if doNotFailForNowData is true", async function () {
214
+ const retrieverService = new RobustExternalAPICallerService(
215
+ "testService",
216
+ generateProviders(4).providers,
217
+ // eslint-disable-next-line no-console
218
+ console.log
219
+ );
220
+ const attemptsCount = 5;
221
+ const _performCallAttemptStub = sinon.stub(
222
+ retrieverService,
223
+ "_performCallAttempt"
224
+ );
225
+ for (let i = 0; i < attemptsCount; ++i) {
226
+ _performCallAttemptStub.onCall(i).resolves({
227
+ data: null,
228
+ shouldBeForceRetried: false,
229
+ errors: [],
230
+ rpsFactor: RobustExternalAPICallerService.defaultRPSFactor,
231
+ });
232
+ }
233
+
234
+ const result = await retrieverService.callExternalAPI(
235
+ [],
236
+ anyTimeout,
237
+ null,
238
+ attemptsCount,
239
+ true
240
+ );
241
+
242
+ (result == null).should.be.true();
243
+ });
244
+
245
+ it("Should throw an error if all requests return null if doNotFailForNowData is false", async function () {
246
+ const retrieverService = new RobustExternalAPICallerService(
247
+ "testService",
248
+ generateProviders(4).providers,
249
+ // eslint-disable-next-line no-console
250
+ console.log
251
+ );
252
+ const attemptsCount = 5;
253
+ const _performCallAttemptStub = sinon.stub(
254
+ retrieverService,
255
+ "_performCallAttempt"
256
+ );
257
+ for (let i = 0; i < attemptsCount; ++i) {
258
+ _performCallAttemptStub.onCall(i).resolves({
259
+ data: null,
260
+ shouldBeForceRetried: false,
261
+ errors: [],
262
+ rpsFactor: RobustExternalAPICallerService.defaultRPSFactor,
263
+ });
264
+ }
265
+
266
+ try {
267
+ await retrieverService.callExternalAPI(
268
+ [],
269
+ anyTimeout,
270
+ null,
271
+ attemptsCount
272
+ );
273
+ } catch (e) {
274
+ e.message.should.match(
275
+ /.*Failed to retrieve data. It means all attempts have been failed.*/
276
+ );
277
+ }
278
+ });
279
+
280
+ it("Should use rpsFactor = 1 by default (as a parameter of the first call to _performCallAttempt)", async function () {
281
+ const retrieverService = new RobustExternalAPICallerService(
282
+ "testService",
283
+ generateProviders(4).providers,
284
+ // eslint-disable-next-line no-console
285
+ console.log
286
+ );
287
+ const attemptsCount = 1;
288
+ const _performCallAttemptStub = sinon.stub(
289
+ retrieverService,
290
+ "_performCallAttempt"
291
+ );
292
+ _performCallAttemptStub.onCall(0).resolves({
293
+ data: anyData,
294
+ shouldBeForceRetried: false,
295
+ errors: [],
296
+ rpsFactor: RobustExternalAPICallerService.defaultRPSFactor,
297
+ });
298
+
299
+ await retrieverService.callExternalAPI(
300
+ [],
301
+ anyTimeout,
302
+ null,
303
+ attemptsCount
304
+ );
305
+
306
+ _performCallAttemptStub.args[0][3].should.be.equal(1);
307
+ });
308
+
309
+ it("Should call _performCallAttempt without the delay when there is a first attempt", async function () {
310
+ const retrieverService = new RobustExternalAPICallerService(
311
+ "testService",
312
+ generateProviders(4).providers,
313
+ // eslint-disable-next-line no-console
314
+ console.log
315
+ );
316
+
317
+ global.setTimeout = sinon.stub();
318
+
319
+ const attemptsCount = 1;
320
+ const _performCallAttemptStub = sinon.stub(
321
+ retrieverService,
322
+ "_performCallAttempt"
323
+ );
324
+ _performCallAttemptStub.onCall(0).resolves({
325
+ data: anyData,
326
+ shouldBeForceRetried: false,
327
+ errors: [],
328
+ rpsFactor: RobustExternalAPICallerService.defaultRPSFactor,
329
+ });
330
+
331
+ await retrieverService.callExternalAPI(
332
+ [],
333
+ anyTimeout,
334
+ null,
335
+ attemptsCount
336
+ );
337
+
338
+ setTimeout.callCount.should.be.equal(0);
339
+ });
340
+
341
+ it("Should call _performCallAttempt with delay n-1 times when there are n attempts performed", async function () {
342
+ const retrieverService = new RobustExternalAPICallerService(
343
+ "testService",
344
+ generateProviders(4).providers,
345
+ // eslint-disable-next-line no-console
346
+ console.log
347
+ );
348
+
349
+ sinon.spy(global, "setTimeout");
350
+
351
+ const attemptsCount = 4;
352
+ const failingAttemptsCount = 2;
353
+ const _performCallAttemptStub = sinon.stub(
354
+ retrieverService,
355
+ "_performCallAttempt"
356
+ );
357
+ for (let i = 0; i <= failingAttemptsCount; ++i) {
358
+ _performCallAttemptStub.onCall(i).resolves({
359
+ data: i === failingAttemptsCount ? anyData : null,
360
+ shouldBeForceRetried: false,
361
+ errors: [],
362
+ rpsFactor: RobustExternalAPICallerService.defaultRPSFactor,
363
+ });
364
+ }
365
+
366
+ await retrieverService.callExternalAPI(
367
+ [],
368
+ anyTimeout,
369
+ null,
370
+ attemptsCount
371
+ );
372
+
373
+ setTimeout.callCount.should.be.equal(failingAttemptsCount);
374
+ });
375
+
376
+ it("Should call _performCallAttempt with delay corresponding to the max rps and default rpsFactor", async function () {
377
+ const RPSes = [10, 20, 4, 15];
378
+ const retrieverService = new RobustExternalAPICallerService(
379
+ "testService",
380
+ generateProviders(4, "any", "any", "any", RPSes).providers,
381
+ // eslint-disable-next-line no-console
382
+ console.log
383
+ );
384
+
385
+ sinon.spy(global, "setTimeout");
386
+
387
+ const attemptsCount = 5;
388
+ const failingAttemptsCount = 3;
389
+ const _performCallAttemptStub = sinon.stub(
390
+ retrieverService,
391
+ "_performCallAttempt"
392
+ );
393
+ for (let i = 0; i <= failingAttemptsCount; ++i) {
394
+ _performCallAttemptStub.onCall(i).resolves({
395
+ data: i === failingAttemptsCount ? anyData : null,
396
+ shouldBeForceRetried: false,
397
+ errors: [],
398
+ rpsFactor: RobustExternalAPICallerService.defaultRPSFactor,
399
+ });
400
+ }
401
+
402
+ await retrieverService.callExternalAPI(
403
+ [],
404
+ anyTimeout,
405
+ null,
406
+ attemptsCount
407
+ );
408
+
409
+ for (let j = 0; j < failingAttemptsCount; ++j) {
410
+ setTimeout.args[j][1].should.be.equal(
411
+ 1000 /
412
+ (Math.max(...RPSes) /
413
+ RobustExternalAPICallerService.defaultRPSFactor)
414
+ );
415
+ }
416
+ });
417
+
418
+ it("Should call _performCallAttempt with delay corresponding to the max rps and increasing rpsFactor", async function () {
419
+ const RPSes = [1, 11, 39, 0.1];
420
+ const retrieverService = new RobustExternalAPICallerService(
421
+ "testService",
422
+ generateProviders(4, "any", "any", "any", RPSes).providers,
423
+ // eslint-disable-next-line no-console
424
+ console.log
425
+ );
426
+
427
+ sinon.spy(global, "setTimeout");
428
+
429
+ const attemptsCount = 5;
430
+ const failingAttemptsCount = 3;
431
+ const _performCallAttemptStub = sinon.stub(
432
+ retrieverService,
433
+ "_performCallAttempt"
434
+ );
435
+ const rpsFactors = new Array(4)
436
+ .fill(RobustExternalAPICallerService.defaultRPSFactor)
437
+ .map(
438
+ (factor, index) =>
439
+ factor *
440
+ Math.pow(
441
+ RobustExternalAPICallerService.rpsMultiplier,
442
+ index
443
+ )
444
+ );
445
+
446
+ for (let i = 0; i <= failingAttemptsCount; ++i) {
447
+ _performCallAttemptStub.onCall(i).resolves({
448
+ data: i === failingAttemptsCount ? anyData : null,
449
+ shouldBeForceRetried: false,
450
+ errors: [],
451
+ rpsFactor: rpsFactors[i],
452
+ });
453
+ }
454
+
455
+ await retrieverService.callExternalAPI(
456
+ [],
457
+ anyTimeout,
458
+ null,
459
+ attemptsCount
460
+ );
461
+
462
+ for (let j = 0; j < failingAttemptsCount; ++j) {
463
+ setTimeout.args[j][1].should.be.equal(
464
+ 1000 / (Math.max(...RPSes) / rpsFactors[j]),
465
+ `Attempt ${j} failed`
466
+ );
467
+ }
468
+ });
469
+
470
+ it("Should log the errors array returned by _performCallAttempt", async function () {
471
+ const retrieverService = new RobustExternalAPICallerService(
472
+ "testService",
473
+ generateProviders(4).providers
474
+ );
475
+
476
+ const spy = sinon.spy(retrieverService, "_logger");
477
+
478
+ const attemptsCount = 3;
479
+ const failingAttemptsCount = 2;
480
+ const _performCallAttemptStub = sinon.stub(
481
+ retrieverService,
482
+ "_performCallAttempt"
483
+ );
484
+ for (let i = 0; i <= failingAttemptsCount; ++i) {
485
+ _performCallAttemptStub.onCall(i).resolves({
486
+ data: i === failingAttemptsCount ? anyData : null,
487
+ shouldBeForceRetried: false,
488
+ errors:
489
+ i !== failingAttemptsCount
490
+ ? [`aaa${i}${i}${i}`, `bbb${i}${i}${i}`]
491
+ : [],
492
+ rpsFactor: RobustExternalAPICallerService.defaultRPSFactor,
493
+ });
494
+ }
495
+
496
+ await retrieverService.callExternalAPI(
497
+ [],
498
+ anyTimeout,
499
+ null,
500
+ attemptsCount,
501
+ true
502
+ );
503
+
504
+ for (let j = 0; j < failingAttemptsCount; ++j) {
505
+ spy.args[j][0].message
506
+ .includes(`aaa${j}${j}${j}`)
507
+ .should.be.true();
508
+ spy.args[j][0].message
509
+ .includes(`bbb${j}${j}${j}`)
510
+ .should.be.true();
511
+ }
512
+ });
513
+
514
+ it("Should log an unexpected error occurred during the _performCallAttempt call", async function () {
515
+ const retrieverService = new RobustExternalAPICallerService(
516
+ "testService",
517
+ generateProviders(4).providers
518
+ );
519
+
520
+ const spy = sinon.spy(retrieverService, "_logger");
521
+
522
+ const someError = new Error("Failed ### in tests");
523
+ const attemptsCount = 1;
524
+ const _performCallAttemptStub = sinon.stub(
525
+ retrieverService,
526
+ "_performCallAttempt"
527
+ );
528
+ _performCallAttemptStub.onCall(0).rejects(someError);
529
+
530
+ await retrieverService.callExternalAPI(
531
+ [],
532
+ anyTimeout,
533
+ null,
534
+ attemptsCount,
535
+ true
536
+ );
537
+ spy.args[0][0].should.be.equal(someError);
538
+ });
539
+
540
+ it("Should pass correct parameters to _performCallAttempt - case default rpsFactor", async function () {
541
+ const retrieverService = new RobustExternalAPICallerService(
542
+ "testService",
543
+ generateProviders(4).providers
544
+ );
545
+
546
+ const attemptsCount = 2;
547
+ const _performCallAttemptStub = sinon.stub(
548
+ retrieverService,
549
+ "_performCallAttempt"
550
+ );
551
+ for (let i = 0; i < attemptsCount; ++i) {
552
+ _performCallAttemptStub.onCall(i).resolves({
553
+ data: null,
554
+ shouldBeForceRetried: false,
555
+ errors: [],
556
+ rpsFactor: RobustExternalAPICallerService.defaultRPSFactor,
557
+ });
558
+ }
559
+
560
+ const someParamsValues = [1, "aaa", { c: "1w2" }];
561
+ const someCancelToken = "some token";
562
+ await retrieverService.callExternalAPI(
563
+ someParamsValues,
564
+ anyTimeout,
565
+ someCancelToken,
566
+ attemptsCount,
567
+ true
568
+ );
569
+
570
+ for (let i = 0; i < attemptsCount; ++i) {
571
+ _performCallAttemptStub.args[i].should.be.deepEqual([
572
+ someParamsValues,
573
+ anyTimeout,
574
+ someCancelToken,
575
+ RobustExternalAPICallerService.defaultRPSFactor,
576
+ true,
577
+ ]);
578
+ }
579
+ });
580
+
581
+ it("Should pass correct parameters to _performCallAttempt - case increasing rpsFactor", async function () {
582
+ const retrieverService = new RobustExternalAPICallerService(
583
+ "testService",
584
+ generateProviders(4).providers
585
+ );
586
+
587
+ const attemptsCount = 5;
588
+ const failingAttemptsCount = 3;
589
+ const _performCallAttemptStub = sinon.stub(
590
+ retrieverService,
591
+ "_performCallAttempt"
592
+ );
593
+ const rpsFactors = new Array(failingAttemptsCount + 1)
594
+ .fill(RobustExternalAPICallerService.defaultRPSFactor)
595
+ .map(
596
+ (factor, index) =>
597
+ factor *
598
+ Math.pow(
599
+ RobustExternalAPICallerService.rpsMultiplier,
600
+ index
601
+ )
602
+ );
603
+
604
+ for (let i = 0; i <= failingAttemptsCount; ++i) {
605
+ _performCallAttemptStub.onCall(i).resolves({
606
+ data: i === failingAttemptsCount ? anyData : null,
607
+ shouldBeForceRetried: false,
608
+ errors: [],
609
+ rpsFactor: rpsFactors[i],
610
+ });
611
+ }
612
+ const someParamsValues = [1, "aaa", { c: "1w2" }];
613
+ const someCancelToken = "some token";
614
+ await retrieverService.callExternalAPI(
615
+ someParamsValues,
616
+ anyTimeout,
617
+ someCancelToken,
618
+ attemptsCount,
619
+ true
620
+ );
621
+
622
+ for (let i = 0; i < failingAttemptsCount + 1; ++i) {
623
+ _performCallAttemptStub.args[i].should.be.deepEqual(
624
+ [
625
+ someParamsValues,
626
+ anyTimeout,
627
+ someCancelToken,
628
+ i === 0
629
+ ? RobustExternalAPICallerService.defaultRPSFactor
630
+ : rpsFactors[i - 1],
631
+ true,
632
+ ],
633
+ `Attempt ${i} check`
634
+ );
635
+ }
636
+ });
637
+
638
+ it("Should log the 'not enough attempts' error when after all attempts data is null and doNotFailForNowData is true", async function () {
639
+ const retrieverService = new RobustExternalAPICallerService(
640
+ "testService",
641
+ generateProviders(4).providers
642
+ );
643
+
644
+ const spy = sinon.spy(retrieverService, "_logger");
645
+
646
+ const attemptsCount = 3;
647
+ const _performCallAttemptStub = sinon.stub(
648
+ retrieverService,
649
+ "_performCallAttempt"
650
+ );
651
+ for (let i = 0; i < attemptsCount; ++i) {
652
+ _performCallAttemptStub.onCall(i).resolves({
653
+ data: null,
654
+ shouldBeForceRetried: false,
655
+ errors: [],
656
+ rpsFactor: RobustExternalAPICallerService.defaultRPSFactor,
657
+ });
658
+ }
659
+
660
+ await retrieverService.callExternalAPI(
661
+ [],
662
+ anyTimeout,
663
+ null,
664
+ attemptsCount,
665
+ true
666
+ );
667
+
668
+ spy.args[0][0].message.should.be.equal(
669
+ "Failed to retrieve data. It means all attempts have been failed. DEV: add more attempts to this data retrieval"
670
+ );
671
+ });
672
+
673
+ it("Should throw the 'not enough attempts' error when after all attempts data is null and doNotFailForNowData is false", async function () {
674
+ const retrieverService = new RobustExternalAPICallerService(
675
+ "testService",
676
+ generateProviders(4).providers
677
+ );
678
+
679
+ const spy = sinon.spy(retrieverService, "_logger");
680
+
681
+ const attemptsCount = 3;
682
+ const _performCallAttemptStub = sinon.stub(
683
+ retrieverService,
684
+ "_performCallAttempt"
685
+ );
686
+ for (let i = 0; i < attemptsCount; ++i) {
687
+ _performCallAttemptStub.onCall(i).resolves({
688
+ data: null,
689
+ shouldBeForceRetried: false,
690
+ errors: [],
691
+ rpsFactor: RobustExternalAPICallerService.defaultRPSFactor,
692
+ });
693
+ }
694
+
695
+ let error = null;
696
+ try {
697
+ await retrieverService.callExternalAPI(
698
+ [],
699
+ anyTimeout,
700
+ null,
701
+ attemptsCount
702
+ );
703
+ } catch (e) {
704
+ error = e;
705
+ }
706
+
707
+ (error == null).should.be.false();
708
+ error.message.should.be.match(
709
+ /.*Failed to retrieve data. It means all attempts have been failed. DEV: add more attempts to this data retrieval/
710
+ );
711
+ });
712
+
713
+ it("Should return the not null data returned by the _performCallAttempt", async function () {
714
+ const retrieverService = new RobustExternalAPICallerService(
715
+ "testService",
716
+ generateProviders(4).providers
717
+ );
718
+
719
+ const attemptsCount = 4;
720
+ const failingAttemptsCount = 2;
721
+ const _performCallAttemptStub = sinon.stub(
722
+ retrieverService,
723
+ "_performCallAttempt"
724
+ );
725
+ for (let i = 0; i <= failingAttemptsCount; ++i) {
726
+ _performCallAttemptStub.onCall(i).resolves({
727
+ data: i === failingAttemptsCount ? anyData : null,
728
+ shouldBeForceRetried: false,
729
+ errors: [],
730
+ rpsFactor: RobustExternalAPICallerService.defaultRPSFactor,
731
+ });
732
+ }
733
+
734
+ const result = await retrieverService.callExternalAPI(
735
+ [],
736
+ anyTimeout,
737
+ null,
738
+ attemptsCount,
739
+ true
740
+ );
741
+
742
+ result.should.be.deepEqual(anyData);
743
+ });
744
+ });
745
+ });