@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,787 @@
1
+ import sinon from "sinon";
2
+ import should from "should";
3
+
4
+ import { beforeEach, afterEach, describe, it } from "vitest";
5
+ import { v4 } from "uuid";
6
+ import { ExternalApiProvider } from "../../../../externalApiProvider.js";
7
+ import { ApiGroup } from "../../../../../common/external-apis/apiGroups.js";
8
+ import { AxiosAdapter } from "../../../../../common/adapters/axiosAdapter.js";
9
+ import { RobustExternalAPICallerService } from "../../../../robustExternalAPICallerService.js";
10
+
11
+ /**
12
+ * _performCallAttempt what aspects to test:
13
+ * + 1. Should reorder providers according to nice factor
14
+ * + 2. Should not touch the instance's providers list during the reordering
15
+ * + 3. Should call only first provider if it returns data
16
+ * + 4. Should call several providers until the data retrieved
17
+ * + 5. Should return null data if all providers returns null
18
+ * + 6.1 Should return shouldBeForceRetried=true if all providers declined by RPS (isRPSExceeded returns true)
19
+ * + 6.2 Should return shouldBeForceRetried=true if some providers fail and the rest ones declined by RPS (isRPSExceeded returns true) and count of requests declined by RPS is more than ceil of half of providers count
20
+ * + 7.1. Should return rpsFactor multiplied by rpsMultiplier if all providers declined by RPS (isRPSExceeded returns true)
21
+ * + 7.2. Should return rpsFactor multiplied by rpsMultiplier if some providers fail and the rest ones declined by RPS (isRPSExceeded returns true) and count of requests declined by RPS is more than ceil of half of providers count
22
+ * + 8. Should return the errors list caught when calling the providers and one of them fails
23
+ * + 9. Should return the errors list caught when calling the providers and all them fail
24
+ * + 9.1 Should return null data when all the providers fail
25
+ * + 10. Should not call the provider if it has RPS and rpsEnsurer says that this domain's RPS is exceeded
26
+ * + 11. Should correctly count the number of requests declined by RPS exceeding
27
+ * + 12. Should add cancelToken to axios params if it is not null
28
+ * + 13. Should add timeout to axios params if a provider has it
29
+ * + 14. Should do all sub-request if all of them except one don't fail
30
+ * + 15. Should call proper sub-request's query composer if there are composers for each sub-request
31
+ * + 16. Should pass correct endpoint with query returned by query composer as the first axios param
32
+ * 17. Should call composeBody if the http method is post/put/patch
33
+ * 18. Should pass body as the second axios parameter if httpMethod is post/put/patch and it is present
34
+ * 18. Should pass null as the second axios parameter if httpMethod is post/put/patch and the provider has no bodyComposer
35
+ * 19. Should make requests for each page if requests for previous pages succeed
36
+ * 20. Should make requests for all pages till not failing
37
+ * 21. Should actualize last called timestamp by calling rpsEnsuring method
38
+ * 22. Should pass axios config as the third parameter if http method is post/put/patch
39
+ * 23. Should pass axios config as the second parameter if the http method is not post/put/patch
40
+ * 24. Should notify stats collector that the service is called without an error
41
+ * 25. Should call query parameters changer for second and more pages
42
+ * 26. Should call dedicated query parameters changer for second and more pages if the request has sub-requests
43
+ * 27. Should pass proper params to query parameters changer for second and more page number
44
+ * 28. Should delay the request for second and more page number for the time corresponding to the RPS and rpsFactor if the provider has RPS
45
+ * 29. Should use corrected query string for second and more page as a first parameter for axios call
46
+ * 30. Should call axios with the http method of current provider
47
+ * 31. Should call axios with the http method of current provider's current sub-request
48
+ * 32. Should actualize the last called timestamp in rpsEnsurer when postponing the request for second and more page number
49
+ * 33. Should call checkWhetherResponseIsForLastPage if provider has this function
50
+ * 34. Should call checkWhetherResponseIsForLastPage with the correct params
51
+ * 35. Should call checkWhetherResponseIsForLastPage corresponding to sub-request if provider has sub-requests
52
+ * 36. Should return the paginated data as a single array if the request per page returns an array
53
+ * 37. Should return the joined sub-requests data as a single array if the request per sub-request returns an array
54
+ * 38. Should return the joined paginated sub-requests data as a single array if the request per sub-request returns an array and some sub-requests return an array per page
55
+ * 39. Should return the result of the request if the request has no sub-requests
56
+ * 40. Should punish the provider failed to retrieve data
57
+ * 41. Should punish the provider returned no data
58
+ * 42. Should call stats collector when the provider fails to retrieve data
59
+ * 43. Should call stats collector when the provider returns no data
60
+ * + 44. Should bind each query composer to provider
61
+ */
62
+ describe("RobustExternalAPICallerService", function () {
63
+ describe("#callExternalAPI>_performCallAttempt", function () {
64
+ const anyAxiosResponse = (method) => `${method}-result`;
65
+
66
+ const generateProviders = (
67
+ count,
68
+ endpoints = "any",
69
+ httpMethods = "any",
70
+ getDataByResponseFunctions = "any",
71
+ RPSes = null,
72
+ timeouts = null
73
+ ) => {
74
+ const providers = [];
75
+ const composeQueryStringStubs = [];
76
+ for (let i = 0; i < count; ++i) {
77
+ class TempProvider extends ExternalApiProvider {
78
+ constructor() {
79
+ super(
80
+ endpoints === "any" ? "any" : endpoints[i],
81
+ httpMethods === "any" ? "get" : httpMethods[i],
82
+ timeouts ? timeouts[i] : null,
83
+ RPSes === null
84
+ ? new ApiGroup(v4(), 10)
85
+ : new ApiGroup(v4(), RPSes[i])
86
+ );
87
+ }
88
+ getDataByResponse(
89
+ response,
90
+ params = [],
91
+ subRequestIndex = 0,
92
+ iterationsData = []
93
+ ) {
94
+ const func =
95
+ getDataByResponseFunctions === "any"
96
+ ? "any"
97
+ : Array.isArray(getDataByResponseFunctions[i])
98
+ ? getDataByResponseFunctions[i][
99
+ subRequestIndex
100
+ ]
101
+ : getDataByResponseFunctions[i];
102
+ return func();
103
+ }
104
+ }
105
+ const provider = new TempProvider();
106
+ providers.push(provider);
107
+ const composeQueryStringStub = sinon.stub(
108
+ provider,
109
+ "composeQueryString"
110
+ );
111
+ composeQueryStringStubs.push(composeQueryStringStub);
112
+ }
113
+ const isRpsExceededStubs = [];
114
+ providers.forEach((provider) => {
115
+ if (
116
+ provider.httpMethod === "post" ||
117
+ provider.httpMethod === "put" ||
118
+ provider.httpMethod === "patch"
119
+ ) {
120
+ provider.composeBody = () => {};
121
+ sinon.stub(provider, "composeBody");
122
+ }
123
+ const isRPSExceededStub = sinon.stub(provider, "isRpsExceeded");
124
+ isRPSExceededStub.returns(false);
125
+ isRpsExceededStubs.push(isRPSExceededStub);
126
+ });
127
+
128
+ return { providers, isRpsExceededStubs, composeQueryStringStubs };
129
+ };
130
+
131
+ const anyRPSes = (count) => new Array(count).fill(10);
132
+
133
+ let anyParameters = [];
134
+ let anyTimeout = 10000;
135
+ let anyCancelToken = "null";
136
+ let anyRPSFactor = 1;
137
+ let callStub;
138
+ beforeEach(function () {
139
+ callStub = sinon.stub(AxiosAdapter, "call");
140
+ callStub
141
+ .withArgs(
142
+ "get",
143
+ sinon.match.any,
144
+ sinon.match.any,
145
+ sinon.match.any
146
+ )
147
+ .resolves(anyAxiosResponse("get"));
148
+ callStub
149
+ .withArgs(
150
+ "post",
151
+ sinon.match.any,
152
+ sinon.match.any,
153
+ sinon.match.any
154
+ )
155
+ .resolves(anyAxiosResponse("post"));
156
+ callStub
157
+ .withArgs(
158
+ "patch",
159
+ sinon.match.any,
160
+ sinon.match.any,
161
+ sinon.match.any
162
+ )
163
+ .resolves(anyAxiosResponse("patch"));
164
+ callStub
165
+ .withArgs(
166
+ "delete",
167
+ sinon.match.any,
168
+ sinon.match.any,
169
+ sinon.match.any
170
+ )
171
+ .resolves(anyAxiosResponse("delete"));
172
+ callStub
173
+ .withArgs(
174
+ "put",
175
+ sinon.match.any,
176
+ sinon.match.any,
177
+ sinon.match.any
178
+ )
179
+ .resolves(anyAxiosResponse("put"));
180
+ });
181
+
182
+ afterEach(function () {
183
+ callStub.restore();
184
+ });
185
+
186
+ it("Should reorder providers according to nice factor", async function () {
187
+ const { providers } = generateProviders(
188
+ 3,
189
+ ["###1", "###2", "###3"],
190
+ ["get", "get", "get"],
191
+ [() => "any", () => null, () => null]
192
+ );
193
+ const retriever = new RobustExternalAPICallerService(
194
+ "any",
195
+ providers
196
+ );
197
+ [1, 5, 3].forEach(
198
+ (niceFactor, i) =>
199
+ (retriever.providers[i].niceFactor = niceFactor)
200
+ );
201
+
202
+ await retriever._performCallAttempt(
203
+ anyParameters,
204
+ anyTimeout,
205
+ anyCancelToken,
206
+ anyRPSFactor
207
+ );
208
+
209
+ callStub.args[0][0].should.be.equal("get");
210
+ callStub.args[0][1].should.startWith(
211
+ retriever.providers[1].endpoint
212
+ );
213
+ callStub.args[1][1].should.startWith(
214
+ retriever.providers[2].endpoint
215
+ );
216
+ callStub.args[2][1].should.startWith(
217
+ retriever.providers[0].endpoint
218
+ );
219
+ });
220
+
221
+ it("Should not touch the instance's providers list during the reordering", async function () {
222
+ const { providers } = generateProviders(
223
+ 3,
224
+ ["###1", "###2", "###3"],
225
+ ["get", "get", "get"],
226
+ [() => "any", () => null, () => null]
227
+ );
228
+ const retriever = new RobustExternalAPICallerService(
229
+ "any",
230
+ providers
231
+ );
232
+ [1, 5, 3].forEach(
233
+ (niceFactor, i) =>
234
+ (retriever.providers[i].niceFactor = niceFactor)
235
+ );
236
+
237
+ await retriever._performCallAttempt(
238
+ anyParameters,
239
+ anyTimeout,
240
+ anyCancelToken,
241
+ anyRPSFactor
242
+ );
243
+
244
+ retriever.providers[0].endpoint.should.be.equal(
245
+ providers[0].endpoint
246
+ );
247
+ retriever.providers[1].endpoint.should.be.equal(
248
+ providers[1].endpoint
249
+ );
250
+ retriever.providers[2].endpoint.should.be.equal(
251
+ providers[2].endpoint
252
+ );
253
+ });
254
+
255
+ it("Should call only first provider if it returns data", async function () {
256
+ const { providers } = generateProviders(
257
+ 3,
258
+ ["###1", "###2", "###3"],
259
+ ["get", "get", "get"],
260
+ [() => "any", () => "any", () => "any"]
261
+ );
262
+ const retriever = new RobustExternalAPICallerService(
263
+ "any",
264
+ providers
265
+ );
266
+
267
+ await retriever._performCallAttempt(
268
+ anyParameters,
269
+ anyTimeout,
270
+ anyCancelToken,
271
+ anyRPSFactor
272
+ );
273
+
274
+ callStub.callCount.should.be.equal(1);
275
+ callStub.args[0][0].should.be.equal("get");
276
+ callStub.args[0][1].should.startWith(
277
+ retriever.providers[0].endpoint
278
+ );
279
+ });
280
+
281
+ it("Should call several providers until the data retrieved", async function () {
282
+ const { providers } = generateProviders(
283
+ 3,
284
+ ["###1", "###2", "###3"],
285
+ ["get", "get", "get"],
286
+ [() => null, () => null, () => "any"]
287
+ );
288
+ const retriever = new RobustExternalAPICallerService(
289
+ "any",
290
+ providers
291
+ );
292
+
293
+ await retriever._performCallAttempt(
294
+ anyParameters,
295
+ anyTimeout,
296
+ anyCancelToken,
297
+ anyRPSFactor
298
+ );
299
+
300
+ callStub.callCount.should.be.equal(3);
301
+ callStub.args[0][0].should.be.equal("get");
302
+ callStub.args[1][0].should.be.equal("get");
303
+ callStub.args[2][0].should.be.equal("get");
304
+ callStub.args[0][1].should.startWith(
305
+ retriever.providers[0].endpoint
306
+ );
307
+ callStub.args[1][1].should.startWith(
308
+ retriever.providers[1].endpoint
309
+ );
310
+ callStub.args[2][1].should.startWith(
311
+ retriever.providers[2].endpoint
312
+ );
313
+ });
314
+
315
+ it("Should return null data if all providers returns null", async function () {
316
+ const { providers } = generateProviders(
317
+ 3,
318
+ ["###1", "###2", "###3"],
319
+ ["get", "get", "get"],
320
+ [() => null, () => null, () => null]
321
+ );
322
+ const retriever = new RobustExternalAPICallerService(
323
+ "any",
324
+ providers
325
+ );
326
+
327
+ const result = await retriever._performCallAttempt(
328
+ anyParameters,
329
+ anyTimeout,
330
+ anyCancelToken,
331
+ anyRPSFactor
332
+ );
333
+
334
+ (result.data === null).should.be.true();
335
+ });
336
+
337
+ it("Should return shouldBeForceRetried=true if providers have RPS and all providers declined by RPS (isRPSExceeded returns true)", async function () {
338
+ const { providers, isRpsExceededStubs } = generateProviders(
339
+ 3,
340
+ ["1", "2", "3"],
341
+ "any",
342
+ "any",
343
+ anyRPSes(3)
344
+ );
345
+ const retriever = new RobustExternalAPICallerService(
346
+ "any",
347
+ providers
348
+ );
349
+
350
+ isRpsExceededStubs.forEach((stub) => {
351
+ stub.resetBehavior();
352
+ stub.returns(true);
353
+ });
354
+
355
+ const result = await retriever._performCallAttempt(
356
+ anyParameters,
357
+ anyTimeout,
358
+ anyCancelToken,
359
+ anyRPSFactor
360
+ );
361
+
362
+ result.shouldBeForceRetried.should.be.true();
363
+ });
364
+
365
+ it("Should return shouldBeForceRetried=true if some providers fail and the rest ones declined by RPS (isRPSExceeded returns true) and count of requests declined by RPS is more than ceil of half of providers count", async function () {
366
+ const { providers, isRpsExceededStubs } = generateProviders(
367
+ 5,
368
+ ["1", "2", "3", "4", "5"],
369
+ "any",
370
+ [() => null, () => "any", () => null, () => "any", () => "any"],
371
+ anyRPSes(5)
372
+ );
373
+ const retriever = new RobustExternalAPICallerService(
374
+ "any",
375
+ providers
376
+ );
377
+
378
+ isRpsExceededStubs.forEach((stub, index) => {
379
+ if (index === 1 || index === 3 || index === 4) {
380
+ stub.resetBehavior();
381
+ stub.returns(true);
382
+ }
383
+ });
384
+
385
+ const result = await retriever._performCallAttempt(
386
+ anyParameters,
387
+ anyTimeout,
388
+ anyCancelToken,
389
+ anyRPSFactor
390
+ );
391
+
392
+ result.shouldBeForceRetried.should.be.true();
393
+ });
394
+
395
+ it("Should return rpsFactor multiplied by rpsMultiplier if providers have RPS and all providers declined by RPS (isRPSExceeded returns true)", async function () {
396
+ const { providers, isRpsExceededStubs } = generateProviders(
397
+ 3,
398
+ ["1", "2", "3"],
399
+ "any",
400
+ "any",
401
+ anyRPSes(3)
402
+ );
403
+ const retriever = new RobustExternalAPICallerService(
404
+ "any",
405
+ providers
406
+ );
407
+
408
+ isRpsExceededStubs.forEach((stub) => {
409
+ stub.resetBehavior();
410
+ stub.returns(true);
411
+ });
412
+
413
+ const rpsFactor = 1;
414
+
415
+ const result = await retriever._performCallAttempt(
416
+ anyParameters,
417
+ anyTimeout,
418
+ anyCancelToken,
419
+ anyRPSFactor
420
+ );
421
+
422
+ result.rpsFactor.should.be.equal(
423
+ rpsFactor * RobustExternalAPICallerService.rpsMultiplier
424
+ );
425
+ });
426
+
427
+ it("Should return rpsFactor multiplied by rpsMultiplier if some providers fail and the rest ones declined by RPS (isRPSExceeded returns true) and count of requests declined by RPS is more than ceil of half of providers count", async function () {
428
+ const { providers, isRpsExceededStubs } = generateProviders(
429
+ 5,
430
+ ["1", "2", "3", "4", "5"],
431
+ "any",
432
+ ["any", () => null, "any", () => null, () => null],
433
+ anyRPSes(5)
434
+ );
435
+ const retriever = new RobustExternalAPICallerService(
436
+ "any",
437
+ providers
438
+ );
439
+
440
+ isRpsExceededStubs.forEach((stub, index) => {
441
+ if (index === 1 || index === 3 || index === 4) {
442
+ stub.resetBehavior();
443
+ stub.returns(true);
444
+ }
445
+ });
446
+
447
+ const rpsFactor = 1;
448
+
449
+ const result = await retriever._performCallAttempt(
450
+ [],
451
+ anyTimeout,
452
+ null,
453
+ rpsFactor
454
+ );
455
+
456
+ result.rpsFactor.should.be.equal(
457
+ rpsFactor * RobustExternalAPICallerService.rpsMultiplier
458
+ );
459
+ });
460
+
461
+ it("Should return the errors list caught when calling the providers and one of them fails", async function () {
462
+ const { providers } = generateProviders(
463
+ 3,
464
+ ["1", "2", "3"],
465
+ ["get", "get", "get"],
466
+ [() => null, () => "any", () => "any"]
467
+ );
468
+ const retriever = new RobustExternalAPICallerService(
469
+ "any",
470
+ providers
471
+ );
472
+
473
+ const someError = new Error("E1");
474
+ callStub.resetBehavior();
475
+ callStub.onCall(0).resolves("any");
476
+ callStub.onCall(1).rejects(someError);
477
+ callStub.onCall(2).resolves("any");
478
+
479
+ const result = await retriever._performCallAttempt(
480
+ anyParameters,
481
+ anyTimeout,
482
+ anyCancelToken,
483
+ anyRPSFactor
484
+ );
485
+
486
+ result.errors.should.be.deepEqual([someError]);
487
+ });
488
+
489
+ it("Should return the errors list caught when calling the providers and all of them fail", async function () {
490
+ const { providers } = generateProviders(
491
+ 3,
492
+ ["1", "2", "3"],
493
+ ["get", "get", "get"],
494
+ [() => null, () => null, () => null]
495
+ );
496
+ const retriever = new RobustExternalAPICallerService(
497
+ "any",
498
+ providers
499
+ );
500
+
501
+ const someError1 = new Error("E1");
502
+ const someError2 = new Error("E2");
503
+ const someError3 = new Error("E3");
504
+ callStub.resetBehavior();
505
+ callStub.onCall(0).rejects(someError1);
506
+ callStub.onCall(1).rejects(someError2);
507
+ callStub.onCall(2).rejects(someError3);
508
+
509
+ const result = await retriever._performCallAttempt(
510
+ anyParameters,
511
+ anyTimeout,
512
+ anyCancelToken,
513
+ anyRPSFactor
514
+ );
515
+
516
+ result.errors.should.be.deepEqual([
517
+ someError1,
518
+ someError2,
519
+ someError3,
520
+ ]);
521
+ });
522
+
523
+ it("Should return null data when all the providers fail", async function () {
524
+ const { providers } = generateProviders(
525
+ 3,
526
+ ["1", "2", "3"],
527
+ ["get", "get", "get"],
528
+ [() => "any", () => "any", () => "any"]
529
+ );
530
+ const retriever = new RobustExternalAPICallerService(
531
+ "any",
532
+ providers
533
+ );
534
+
535
+ const someError = new Error("E1");
536
+ callStub.resetBehavior();
537
+ callStub.rejects(someError);
538
+
539
+ const result = await retriever._performCallAttempt(
540
+ anyParameters,
541
+ anyTimeout,
542
+ anyCancelToken,
543
+ anyRPSFactor
544
+ );
545
+
546
+ (result.data === null).should.be.true();
547
+ });
548
+
549
+ it("Should not call the provider if it has RPS and rpsEnsurer says that this domain's RPS is exceeded", async function () {
550
+ const { providers, isRpsExceededStubs } = generateProviders(
551
+ 3,
552
+ ["1", "2", "3"],
553
+ ["get", "get", "get"],
554
+ [() => null, () => null, () => null],
555
+ anyRPSes(3)
556
+ );
557
+ const retriever = new RobustExternalAPICallerService(
558
+ "any",
559
+ providers
560
+ );
561
+
562
+ isRpsExceededStubs.forEach((stub, index) => {
563
+ if (index === 0 || index === 2) {
564
+ stub.resetBehavior();
565
+ stub.returns(false);
566
+ } else if (index === 1) {
567
+ stub.resetBehavior();
568
+ stub.returns(true);
569
+ }
570
+ });
571
+
572
+ await retriever._performCallAttempt(
573
+ anyParameters,
574
+ anyTimeout,
575
+ anyCancelToken,
576
+ anyRPSFactor
577
+ );
578
+
579
+ (
580
+ callStub.args.find((params) =>
581
+ params[1].startsWith(providers[1].endpoint)
582
+ ) == null
583
+ ).should.be.true();
584
+ });
585
+
586
+ it("Should correctly count the number of requests declined by RPS exceeding", async function () {
587
+ const { providers, isRpsExceededStubs } = generateProviders(
588
+ 5,
589
+ ["1", "2", "3", "4", "5"],
590
+ ["get", "get", "get", "get", "get"],
591
+ [() => null, () => "any", () => "any", () => null, () => null],
592
+ anyRPSes(5)
593
+ );
594
+ const retriever = new RobustExternalAPICallerService(
595
+ "any",
596
+ providers
597
+ );
598
+
599
+ isRpsExceededStubs.forEach((stub, index) => {
600
+ if (index === 0 || index === 3 || index === 4) {
601
+ stub.resetBehavior();
602
+ stub.returns(false);
603
+ } else if (index === 1 || index === 2) {
604
+ stub.resetBehavior();
605
+ stub.returns(true);
606
+ }
607
+ });
608
+
609
+ const result = await retriever._performCallAttempt(
610
+ anyParameters,
611
+ anyTimeout,
612
+ anyCancelToken,
613
+ anyRPSFactor
614
+ );
615
+
616
+ result.shouldBeForceRetried.should.be.false();
617
+ });
618
+
619
+ it("Should add cancelToken to axios params if it is not null", async function () {
620
+ const { providers } = generateProviders(
621
+ 2,
622
+ ["1", "2"],
623
+ ["get", "get"],
624
+ [() => null, () => "any"]
625
+ );
626
+ const retriever = new RobustExternalAPICallerService(
627
+ "any",
628
+ providers
629
+ );
630
+
631
+ const someCancelToken = "someCC";
632
+ await retriever._performCallAttempt(
633
+ anyParameters,
634
+ anyTimeout,
635
+ someCancelToken,
636
+ anyRPSFactor
637
+ );
638
+
639
+ for (let i = 0; i < callStub.args.length; ++i) {
640
+ callStub.args[i][2]?.cancelToken.should.be.equal(
641
+ someCancelToken
642
+ );
643
+ }
644
+ });
645
+
646
+ it("Should add timeout to axios params if a provider has it", async function () {
647
+ const { providers } = generateProviders(
648
+ 2,
649
+ ["1", "2"],
650
+ ["get", "get"],
651
+ [() => null, () => "any"],
652
+ null,
653
+ [1001, 1002]
654
+ );
655
+
656
+ const retriever = new RobustExternalAPICallerService(
657
+ "any",
658
+ providers
659
+ );
660
+
661
+ await retriever._performCallAttempt(
662
+ anyParameters,
663
+ anyTimeout,
664
+ anyCancelToken,
665
+ anyRPSFactor
666
+ );
667
+
668
+ for (let i = 0; i < callStub.args.length; ++i) {
669
+ callStub.args[i][2]?.timeout.should.be.equal(
670
+ providers[i].timeout
671
+ );
672
+ }
673
+ });
674
+
675
+ it("Should do all sub-request if all of them except the last one don't throw error", async function () {
676
+ const { providers } = generateProviders(
677
+ 2,
678
+ ["1", "2"],
679
+ [["get", "get", "get"], "get"],
680
+ [() => "any", () => "any"]
681
+ );
682
+
683
+ const retriever = new RobustExternalAPICallerService(
684
+ "any",
685
+ providers
686
+ );
687
+
688
+ const someError = new Error("E1");
689
+ callStub.resetBehavior();
690
+ callStub.onFirstCall().resolves("any");
691
+ callStub.onSecondCall().resolves("any");
692
+ callStub.onThirdCall().rejects(someError);
693
+
694
+ await retriever._performCallAttempt(
695
+ anyParameters,
696
+ anyTimeout,
697
+ anyCancelToken,
698
+ anyRPSFactor
699
+ );
700
+
701
+ for (let i = 0; i < 3; ++i) {
702
+ callStub.args[i][1].should.startWith(providers[0].endpoint);
703
+ }
704
+ });
705
+
706
+ it("Should call query composer proper number of times if there are sub-request", async function () {
707
+ // const queryComposers = [[sinon.spy(), sinon.spy()], sinon.spy()];
708
+ const { providers, composeQueryStringStubs } = generateProviders(
709
+ 2,
710
+ ["1", "2"],
711
+ [["get", "get"], "get"],
712
+ [
713
+ [
714
+ () => "any",
715
+ function () {
716
+ throw new Error("any");
717
+ },
718
+ ],
719
+ () => "any",
720
+ ]
721
+ );
722
+
723
+ const retriever = new RobustExternalAPICallerService(
724
+ "any",
725
+ providers
726
+ );
727
+
728
+ await retriever._performCallAttempt(
729
+ anyParameters,
730
+ anyTimeout,
731
+ anyCancelToken,
732
+ anyRPSFactor
733
+ );
734
+
735
+ composeQueryStringStubs[0].callCount.should.be.equal(2);
736
+ composeQueryStringStubs[1].callCount.should.be.equal(1);
737
+ });
738
+
739
+ it("Should pass correct endpoint with query returned by query composer as the first axios param", async function () {
740
+ const { providers, composeQueryStringStubs } = generateProviders(
741
+ 2,
742
+ ["1", "2"],
743
+ [["get", "get"], "get"],
744
+ [
745
+ [
746
+ () => "any",
747
+ function () {
748
+ throw new Error("any");
749
+ },
750
+ ],
751
+ () => "any",
752
+ ]
753
+ );
754
+
755
+ const query1 = "?a=4&fgrh=4l4ao4";
756
+ const query2 = "a/s?j3jk=lkk0-3&h=oool230";
757
+ const query3 = "?jijiji2332=09o3i4r3&p1=-121120&hfwowfif=wqoi23";
758
+ composeQueryStringStubs[0].resetBehavior();
759
+ composeQueryStringStubs[0]
760
+ .withArgs(anyParameters, 0)
761
+ .returns(query1);
762
+ composeQueryStringStubs[0]
763
+ .withArgs(anyParameters, 1)
764
+ .returns(query2);
765
+ composeQueryStringStubs[1].resetBehavior();
766
+ composeQueryStringStubs[1]
767
+ .withArgs(anyParameters, 0)
768
+ .returns(query3);
769
+
770
+ const retriever = new RobustExternalAPICallerService(
771
+ "any",
772
+ providers
773
+ );
774
+
775
+ await retriever._performCallAttempt(
776
+ anyParameters,
777
+ anyTimeout,
778
+ anyCancelToken,
779
+ anyRPSFactor
780
+ );
781
+
782
+ callStub.args[0][1].should.be.equal(providers[0].endpoint + query1);
783
+ callStub.args[1][1].should.be.equal(providers[0].endpoint + query2);
784
+ callStub.args[2][1].should.be.equal(providers[1].endpoint + query3);
785
+ });
786
+ });
787
+ });