@chainlink/external-adapter-framework 0.0.6 → 0.0.7
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/dist/adapter.js +60 -0
- package/dist/background-executor.js +45 -0
- package/dist/cache/factory.js +57 -0
- package/dist/cache/index.js +163 -0
- package/dist/cache/local.js +83 -0
- package/dist/cache/metrics.js +114 -0
- package/dist/cache/redis.js +100 -0
- package/dist/config/index.js +364 -0
- package/dist/config/provider-limits.js +75 -0
- package/dist/examples/coingecko/batch-warming.js +52 -0
- package/dist/examples/coingecko/index.js +10 -0
- package/dist/examples/coingecko/rest.js +50 -0
- package/dist/examples/ncfx/config/index.js +15 -0
- package/dist/examples/ncfx/index.js +10 -0
- package/dist/examples/ncfx/websocket.js +72 -0
- package/dist/index.js +89 -0
- package/dist/metrics/constants.js +25 -0
- package/dist/metrics/index.js +76 -0
- package/dist/metrics/util.js +9 -0
- package/dist/rate-limiting/factory.js +33 -0
- package/dist/rate-limiting/index.js +36 -0
- package/dist/rate-limiting/metrics.js +32 -0
- package/dist/rate-limiting/nop-limiter.js +15 -0
- package/dist/rate-limiting/simple-counting.js +61 -0
- package/dist/src/adapter.js +112 -0
- package/dist/src/background-executor.js +45 -0
- package/dist/src/cache/factory.js +57 -0
- package/dist/src/cache/index.js +165 -0
- package/dist/src/cache/local.js +83 -0
- package/dist/src/cache/metrics.js +114 -0
- package/dist/src/cache/redis.js +100 -0
- package/dist/src/config/index.js +366 -0
- package/dist/src/config/provider-limits.js +75 -0
- package/dist/src/examples/bank-frick/accounts.js +191 -0
- package/dist/src/examples/bank-frick/config/index.js +45 -0
- package/dist/src/examples/bank-frick/index.js +14 -0
- package/dist/src/examples/bank-frick/util.js +39 -0
- package/dist/src/examples/coingecko/batch-warming.js +52 -0
- package/dist/src/examples/coingecko/index.js +10 -0
- package/dist/src/examples/coingecko/rest.js +50 -0
- package/dist/src/examples/ncfx/config/index.js +15 -0
- package/dist/src/examples/ncfx/index.js +10 -0
- package/dist/src/examples/ncfx/websocket.js +72 -0
- package/dist/src/index.js +89 -0
- package/dist/src/metrics/constants.js +25 -0
- package/dist/src/metrics/index.js +76 -0
- package/dist/src/metrics/util.js +9 -0
- package/dist/src/rate-limiting/background/fixed-frequency.js +37 -0
- package/dist/src/rate-limiting/index.js +63 -0
- package/dist/src/rate-limiting/metrics.js +32 -0
- package/dist/src/rate-limiting/request/simple-counting.js +62 -0
- package/dist/src/test.js +6 -0
- package/dist/src/transports/batch-warming.js +55 -0
- package/dist/src/transports/index.js +85 -0
- package/dist/src/transports/metrics.js +119 -0
- package/dist/src/transports/rest.js +93 -0
- package/dist/src/transports/util.js +85 -0
- package/dist/src/transports/websocket.js +175 -0
- package/dist/src/util/expiring-sorted-set.js +47 -0
- package/dist/src/util/index.js +35 -0
- package/dist/src/util/logger.js +62 -0
- package/dist/src/util/request.js +2 -0
- package/dist/src/validation/error.js +41 -0
- package/dist/src/validation/index.js +84 -0
- package/dist/src/validation/input-params.js +30 -0
- package/dist/src/validation/override-functions.js +40 -0
- package/dist/src/validation/preset-tokens.json +23 -0
- package/dist/src/validation/validator.js +303 -0
- package/dist/test.js +6 -0
- package/dist/transports/batch-warming.js +57 -0
- package/dist/transports/index.js +76 -0
- package/dist/transports/metrics.js +133 -0
- package/dist/transports/rest.js +91 -0
- package/dist/transports/util.js +85 -0
- package/dist/transports/websocket.js +171 -0
- package/dist/util/expiring-sorted-set.js +47 -0
- package/dist/util/index.js +35 -0
- package/dist/util/logger.js +62 -0
- package/dist/util/request.js +2 -0
- package/dist/validation/error.js +41 -0
- package/dist/validation/index.js +82 -0
- package/dist/validation/input-params.js +30 -0
- package/dist/validation/overrideFunctions.js +42 -0
- package/dist/validation/presetTokens.json +23 -0
- package/dist/validation/validator.js +303 -0
- package/package.json +6 -2
- package/.c8rc.json +0 -3
- package/.eslintignore +0 -9
- package/.eslintrc.js +0 -96
- package/.github/README.MD +0 -17
- package/.github/actions/setup/action.yaml +0 -13
- package/.github/workflows/main.yaml +0 -39
- package/.github/workflows/publish.yaml +0 -17
- package/.prettierignore +0 -13
- package/.yarnrc +0 -0
- package/docker-compose.yaml +0 -35
- package/src/adapter.ts +0 -236
- package/src/background-executor.ts +0 -53
- package/src/cache/factory.ts +0 -28
- package/src/cache/index.ts +0 -236
- package/src/cache/local.ts +0 -73
- package/src/cache/metrics.ts +0 -112
- package/src/cache/redis.ts +0 -93
- package/src/config/index.ts +0 -501
- package/src/config/provider-limits.ts +0 -130
- package/src/examples/coingecko/batch-warming.ts +0 -79
- package/src/examples/coingecko/index.ts +0 -9
- package/src/examples/coingecko/rest.ts +0 -77
- package/src/examples/ncfx/config/index.ts +0 -12
- package/src/examples/ncfx/index.ts +0 -9
- package/src/examples/ncfx/websocket.ts +0 -100
- package/src/index.ts +0 -106
- package/src/metrics/constants.ts +0 -23
- package/src/metrics/index.ts +0 -116
- package/src/metrics/util.ts +0 -11
- package/src/rate-limiting/background/fixed-frequency.ts +0 -47
- package/src/rate-limiting/index.ts +0 -100
- package/src/rate-limiting/metrics.ts +0 -18
- package/src/rate-limiting/request/simple-counting.ts +0 -76
- package/src/test.ts +0 -5
- package/src/transports/batch-warming.ts +0 -121
- package/src/transports/index.ts +0 -173
- package/src/transports/metrics.ts +0 -95
- package/src/transports/rest.ts +0 -161
- package/src/transports/util.ts +0 -63
- package/src/transports/websocket.ts +0 -238
- package/src/util/expiring-sorted-set.ts +0 -52
- package/src/util/index.ts +0 -20
- package/src/util/logger.ts +0 -69
- package/src/util/request.ts +0 -115
- package/src/validation/error.ts +0 -116
- package/src/validation/index.ts +0 -101
- package/src/validation/input-params.ts +0 -45
- package/src/validation/override-functions.ts +0 -44
- package/src/validation/preset-tokens.json +0 -23
- package/src/validation/validator.ts +0 -384
- package/test/adapter.test.ts +0 -27
- package/test/background-executor.test.ts +0 -109
- package/test/cache/cache-key.test.ts +0 -96
- package/test/cache/helper.ts +0 -101
- package/test/cache/local.test.ts +0 -54
- package/test/cache/redis.test.ts +0 -89
- package/test/correlation.test.ts +0 -114
- package/test/index.test.ts +0 -37
- package/test/metrics/feed-id.test.ts +0 -33
- package/test/metrics/helper.ts +0 -14
- package/test/metrics/labels.test.ts +0 -36
- package/test/metrics/metrics.test.ts +0 -267
- package/test/metrics/redis-metrics.test.ts +0 -113
- package/test/metrics/warmer-metrics.test.ts +0 -192
- package/test/metrics/ws-metrics.test.ts +0 -225
- package/test/rate-limit-config.test.ts +0 -243
- package/test/transports/batch.test.ts +0 -465
- package/test/transports/rest.test.ts +0 -242
- package/test/transports/websocket.test.ts +0 -183
- package/test/tsconfig.json +0 -5
- package/test/util.ts +0 -76
- package/test/validation.test.ts +0 -169
- package/test.sh +0 -20
- package/tsconfig.json +0 -24
- package/typedoc.json +0 -6
|
@@ -1,465 +0,0 @@
|
|
|
1
|
-
import FakeTimers, { InstalledClock } from '@sinonjs/fake-timers'
|
|
2
|
-
import untypedTest, { TestFn } from 'ava'
|
|
3
|
-
import axios, { AxiosError, AxiosRequestConfig, AxiosResponse } from 'axios'
|
|
4
|
-
import { AddressInfo } from 'net'
|
|
5
|
-
import nock from 'nock'
|
|
6
|
-
import { expose } from '../../src'
|
|
7
|
-
import { Adapter } from '../../src/adapter'
|
|
8
|
-
import { SettingsMap } from '../../src/config'
|
|
9
|
-
import { DEFAULT_SHARED_MS_BETWEEN_REQUESTS } from '../../src/rate-limiting'
|
|
10
|
-
import { AdapterContext, BatchWarmingTransport } from '../../src/transports'
|
|
11
|
-
import { ProviderResult } from '../../src/util'
|
|
12
|
-
import { MockCache, runAllUntilTime } from '../util'
|
|
13
|
-
|
|
14
|
-
const test = untypedTest as TestFn<{
|
|
15
|
-
clock: InstalledClock
|
|
16
|
-
}>
|
|
17
|
-
|
|
18
|
-
const URL = 'http://test-url.com'
|
|
19
|
-
const endpoint = '/price'
|
|
20
|
-
|
|
21
|
-
interface AdapterRequestParams {
|
|
22
|
-
from: string
|
|
23
|
-
to: string
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
interface ProviderRequestBody {
|
|
27
|
-
pairs: Array<{
|
|
28
|
-
base: string
|
|
29
|
-
quote: string
|
|
30
|
-
}>
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
interface ProviderResponseBody {
|
|
34
|
-
prices: Array<{
|
|
35
|
-
pair: string
|
|
36
|
-
price: number
|
|
37
|
-
}>
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
test.before(() => {
|
|
41
|
-
nock.disableNetConnect()
|
|
42
|
-
nock.enableNetConnect('localhost')
|
|
43
|
-
})
|
|
44
|
-
|
|
45
|
-
test.after(() => {
|
|
46
|
-
nock.restore()
|
|
47
|
-
})
|
|
48
|
-
|
|
49
|
-
test.beforeEach((t) => {
|
|
50
|
-
t.context.clock = FakeTimers.install({ shouldAdvanceTime: true, advanceTimeDelta: 100 })
|
|
51
|
-
})
|
|
52
|
-
|
|
53
|
-
test.afterEach((t) => {
|
|
54
|
-
t.context.clock.uninstall()
|
|
55
|
-
})
|
|
56
|
-
|
|
57
|
-
class MockBatchWarmingTransport extends BatchWarmingTransport<
|
|
58
|
-
AdapterRequestParams,
|
|
59
|
-
ProviderRequestBody,
|
|
60
|
-
ProviderResponseBody,
|
|
61
|
-
SettingsMap
|
|
62
|
-
> {
|
|
63
|
-
backgroundExecuteCalls = 0
|
|
64
|
-
|
|
65
|
-
constructor(private callSuper = false) {
|
|
66
|
-
super({
|
|
67
|
-
prepareRequest: (params: AdapterRequestParams[]): AxiosRequestConfig<ProviderRequestBody> => {
|
|
68
|
-
return {
|
|
69
|
-
baseURL: URL,
|
|
70
|
-
url: '/price',
|
|
71
|
-
method: 'POST',
|
|
72
|
-
data: {
|
|
73
|
-
pairs: params.map((p) => ({ base: p.from, quote: p.to })),
|
|
74
|
-
},
|
|
75
|
-
}
|
|
76
|
-
},
|
|
77
|
-
parseResponse: (
|
|
78
|
-
res: AxiosResponse<ProviderResponseBody>,
|
|
79
|
-
): ProviderResult<AdapterRequestParams>[] => {
|
|
80
|
-
return res.data.prices.map((p) => {
|
|
81
|
-
const [from, to] = p.pair.split('/')
|
|
82
|
-
return {
|
|
83
|
-
params: { from, to },
|
|
84
|
-
value: p.price,
|
|
85
|
-
}
|
|
86
|
-
})
|
|
87
|
-
},
|
|
88
|
-
})
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
override async backgroundExecute(context: AdapterContext<SettingsMap>): Promise<number> {
|
|
92
|
-
this.backgroundExecuteCalls++
|
|
93
|
-
if (this.callSuper) {
|
|
94
|
-
super.backgroundExecute(context)
|
|
95
|
-
}
|
|
96
|
-
return this.rateLimiter.msUntilNextExecution(context.adapterEndpoint.name)
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// Disable retries to make the testing flow easier
|
|
101
|
-
process.env['CACHE_POLLING_MAX_RETRIES'] = '0'
|
|
102
|
-
|
|
103
|
-
const from = 'ETH'
|
|
104
|
-
const to = 'USD'
|
|
105
|
-
const price = 1234
|
|
106
|
-
|
|
107
|
-
nock(URL)
|
|
108
|
-
.post(endpoint, {
|
|
109
|
-
pairs: [
|
|
110
|
-
{
|
|
111
|
-
base: from,
|
|
112
|
-
quote: to,
|
|
113
|
-
},
|
|
114
|
-
],
|
|
115
|
-
})
|
|
116
|
-
.reply(200, {
|
|
117
|
-
prices: [
|
|
118
|
-
{
|
|
119
|
-
pair: `${from}/${to}`,
|
|
120
|
-
price,
|
|
121
|
-
},
|
|
122
|
-
],
|
|
123
|
-
})
|
|
124
|
-
.persist()
|
|
125
|
-
|
|
126
|
-
const inputParameters = {
|
|
127
|
-
from: {
|
|
128
|
-
type: 'string',
|
|
129
|
-
required: true,
|
|
130
|
-
},
|
|
131
|
-
to: {
|
|
132
|
-
type: 'string',
|
|
133
|
-
required: true,
|
|
134
|
-
},
|
|
135
|
-
} as const
|
|
136
|
-
|
|
137
|
-
test.serial('sends request to DP and returns response', async (t) => {
|
|
138
|
-
const adapter: Adapter = {
|
|
139
|
-
name: 'test',
|
|
140
|
-
defaultEndpoint: 'test',
|
|
141
|
-
endpoints: [
|
|
142
|
-
{
|
|
143
|
-
name: 'test',
|
|
144
|
-
inputParameters,
|
|
145
|
-
transport: new MockBatchWarmingTransport(true),
|
|
146
|
-
},
|
|
147
|
-
],
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
// Create mocked cache so we can listen when values are set
|
|
151
|
-
// This is a more reliable method than expecting precise clock timings
|
|
152
|
-
const mockCache = new MockCache()
|
|
153
|
-
|
|
154
|
-
// Start the adapter
|
|
155
|
-
const server = await expose(adapter, {
|
|
156
|
-
cache: mockCache,
|
|
157
|
-
})
|
|
158
|
-
const address = `http://localhost:${(server?.address() as AddressInfo)?.port}`
|
|
159
|
-
|
|
160
|
-
const makeRequest = () =>
|
|
161
|
-
axios.post(address, {
|
|
162
|
-
data: {
|
|
163
|
-
from,
|
|
164
|
-
to,
|
|
165
|
-
},
|
|
166
|
-
})
|
|
167
|
-
|
|
168
|
-
// Expect the first response to time out
|
|
169
|
-
// The polling behavior is tested in the cache tests, so this is easier here.
|
|
170
|
-
// Start the request:
|
|
171
|
-
const errorPromise: Promise<AxiosError | undefined> = t.throwsAsync(makeRequest)
|
|
172
|
-
// Advance enough time for the initial request async flow
|
|
173
|
-
t.context.clock.tickAsync(10)
|
|
174
|
-
// Wait for the failed cache get -> instant 504
|
|
175
|
-
const error = await errorPromise
|
|
176
|
-
t.is(error?.response?.status, 504)
|
|
177
|
-
|
|
178
|
-
// Advance clock so that the batch warmer executes once again and wait for the cache to be set
|
|
179
|
-
const cacheValueSetPromise = mockCache.waitForNextSet()
|
|
180
|
-
await t.context.clock.tickAsync(DEFAULT_SHARED_MS_BETWEEN_REQUESTS + 10)
|
|
181
|
-
await cacheValueSetPromise
|
|
182
|
-
|
|
183
|
-
// Second request should find the response in the cache
|
|
184
|
-
const response = await makeRequest()
|
|
185
|
-
|
|
186
|
-
t.is(response.status, 200)
|
|
187
|
-
t.deepEqual(response.data, {
|
|
188
|
-
data: null,
|
|
189
|
-
result: price,
|
|
190
|
-
statusCode: 200,
|
|
191
|
-
})
|
|
192
|
-
})
|
|
193
|
-
|
|
194
|
-
test.serial(
|
|
195
|
-
'per minute rate limit of 4 with one batch transport results in a call every 15s',
|
|
196
|
-
async (t) => {
|
|
197
|
-
const rateLimit1m = 4
|
|
198
|
-
const transport = new MockBatchWarmingTransport()
|
|
199
|
-
|
|
200
|
-
const adapter: Adapter = {
|
|
201
|
-
name: 'test',
|
|
202
|
-
defaultEndpoint: 'test',
|
|
203
|
-
endpoints: [
|
|
204
|
-
{
|
|
205
|
-
name: 'test',
|
|
206
|
-
inputParameters,
|
|
207
|
-
transport: transport,
|
|
208
|
-
},
|
|
209
|
-
],
|
|
210
|
-
rateLimiting: {
|
|
211
|
-
tiers: {
|
|
212
|
-
default: {
|
|
213
|
-
rateLimit1m,
|
|
214
|
-
},
|
|
215
|
-
},
|
|
216
|
-
},
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
// Start the adapter
|
|
220
|
-
const server = await expose(adapter)
|
|
221
|
-
const address = `http://localhost:${(server?.address() as AddressInfo)?.port}`
|
|
222
|
-
|
|
223
|
-
const makeRequest = () =>
|
|
224
|
-
axios.post(address, {
|
|
225
|
-
data: {
|
|
226
|
-
from,
|
|
227
|
-
to,
|
|
228
|
-
},
|
|
229
|
-
})
|
|
230
|
-
|
|
231
|
-
// Expect the first response to time out
|
|
232
|
-
// The polling behavior is tested in the cache tests, so this is easier here.
|
|
233
|
-
// Start the request:
|
|
234
|
-
const errorPromise: Promise<AxiosError | undefined> = t.throwsAsync(makeRequest)
|
|
235
|
-
// Advance enough time for the initial request async flow
|
|
236
|
-
t.context.clock.tickAsync(10)
|
|
237
|
-
// Wait for the failed cache get -> instant 504
|
|
238
|
-
const error = await errorPromise
|
|
239
|
-
t.is(error?.response?.status, 504)
|
|
240
|
-
|
|
241
|
-
// Wait for the first background execute and check that it's been called
|
|
242
|
-
await t.context.clock.tickAsync(10)
|
|
243
|
-
t.is(transport.backgroundExecuteCalls, 1)
|
|
244
|
-
|
|
245
|
-
// Advance the clock a few minutes and check that the amount of calls is as expected
|
|
246
|
-
// +1 because of the previous first
|
|
247
|
-
await t.context.clock.tickAsync(5 * 60 * 1000) // 5m
|
|
248
|
-
t.is(transport.backgroundExecuteCalls, 5 * rateLimit1m + 1)
|
|
249
|
-
},
|
|
250
|
-
)
|
|
251
|
-
|
|
252
|
-
test.serial(
|
|
253
|
-
'per second limit of 1 with one batch transport results in a call every 1000ms',
|
|
254
|
-
async (t) => {
|
|
255
|
-
const rateLimit1s = 1
|
|
256
|
-
const transport = new MockBatchWarmingTransport()
|
|
257
|
-
|
|
258
|
-
const adapter: Adapter = {
|
|
259
|
-
name: 'test',
|
|
260
|
-
defaultEndpoint: 'test',
|
|
261
|
-
endpoints: [
|
|
262
|
-
{
|
|
263
|
-
name: 'test',
|
|
264
|
-
inputParameters,
|
|
265
|
-
transport: transport,
|
|
266
|
-
},
|
|
267
|
-
],
|
|
268
|
-
rateLimiting: {
|
|
269
|
-
tiers: {
|
|
270
|
-
default: {
|
|
271
|
-
rateLimit1s,
|
|
272
|
-
},
|
|
273
|
-
},
|
|
274
|
-
},
|
|
275
|
-
envDefaultOverrides: {
|
|
276
|
-
WARMUP_SUBSCRIPTION_TTL: 100000,
|
|
277
|
-
},
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
// Start the adapter
|
|
281
|
-
const server = await expose(adapter)
|
|
282
|
-
const address = `http://localhost:${(server?.address() as AddressInfo)?.port}`
|
|
283
|
-
|
|
284
|
-
const makeRequest = () =>
|
|
285
|
-
axios.post(address, {
|
|
286
|
-
data: {
|
|
287
|
-
from,
|
|
288
|
-
to,
|
|
289
|
-
},
|
|
290
|
-
})
|
|
291
|
-
|
|
292
|
-
// Expect the first response to time out
|
|
293
|
-
// The polling behavior is tested in the cache tests, so this is easier here.
|
|
294
|
-
// Start the request:
|
|
295
|
-
const errorPromise: Promise<AxiosError | undefined> = t.throwsAsync(makeRequest)
|
|
296
|
-
// Advance enough time for the initial request async flow
|
|
297
|
-
t.context.clock.tickAsync(10)
|
|
298
|
-
// Wait for the failed cache get -> instant 504
|
|
299
|
-
const error = await errorPromise
|
|
300
|
-
t.is(error?.response?.status, 504)
|
|
301
|
-
|
|
302
|
-
// Wait for the first background execute and check that it's been called
|
|
303
|
-
await t.context.clock.tickAsync(10)
|
|
304
|
-
t.is(transport.backgroundExecuteCalls, 1)
|
|
305
|
-
|
|
306
|
-
// Run for an entire minute and check that the values are as expected
|
|
307
|
-
await runAllUntilTime(t.context.clock, 59 * 1000)
|
|
308
|
-
|
|
309
|
-
t.is(transport.backgroundExecuteCalls, 60 * rateLimit1s + 1)
|
|
310
|
-
},
|
|
311
|
-
)
|
|
312
|
-
|
|
313
|
-
test.serial(
|
|
314
|
-
'per second limit of 1 with two batch transports results in a call every 2000ms for each',
|
|
315
|
-
async (t) => {
|
|
316
|
-
const rateLimit1s = 1
|
|
317
|
-
const transportA = new MockBatchWarmingTransport()
|
|
318
|
-
const transportB = new MockBatchWarmingTransport()
|
|
319
|
-
|
|
320
|
-
const adapter: Adapter = {
|
|
321
|
-
name: 'test',
|
|
322
|
-
endpoints: [
|
|
323
|
-
{
|
|
324
|
-
name: 'A',
|
|
325
|
-
inputParameters,
|
|
326
|
-
transport: transportA,
|
|
327
|
-
},
|
|
328
|
-
{
|
|
329
|
-
name: 'B',
|
|
330
|
-
inputParameters,
|
|
331
|
-
transport: transportB,
|
|
332
|
-
},
|
|
333
|
-
],
|
|
334
|
-
rateLimiting: {
|
|
335
|
-
tiers: {
|
|
336
|
-
default: {
|
|
337
|
-
rateLimit1s,
|
|
338
|
-
},
|
|
339
|
-
},
|
|
340
|
-
},
|
|
341
|
-
envDefaultOverrides: {
|
|
342
|
-
WARMUP_SUBSCRIPTION_TTL: 100000,
|
|
343
|
-
},
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
// Start the adapter
|
|
347
|
-
const server = await expose(adapter)
|
|
348
|
-
const address = `http://localhost:${(server?.address() as AddressInfo)?.port}`
|
|
349
|
-
|
|
350
|
-
const makeRequest = (endpointParam: string) =>
|
|
351
|
-
axios.post(address, {
|
|
352
|
-
data: {
|
|
353
|
-
from,
|
|
354
|
-
to,
|
|
355
|
-
},
|
|
356
|
-
endpoint: endpointParam,
|
|
357
|
-
})
|
|
358
|
-
|
|
359
|
-
// Expect the first response to time out
|
|
360
|
-
// The polling behavior is tested in the cache tests, so this is easier here.
|
|
361
|
-
// Start the request:
|
|
362
|
-
const errorPromiseA: Promise<AxiosError | undefined> = t.throwsAsync(() => makeRequest('A'))
|
|
363
|
-
// Advance enough time for the initial request async flow
|
|
364
|
-
t.context.clock.tickAsync(10)
|
|
365
|
-
// Wait for the failed cache get -> instant 504
|
|
366
|
-
const errorA = await errorPromiseA
|
|
367
|
-
t.is(errorA?.response?.status, 504)
|
|
368
|
-
|
|
369
|
-
// Do the same thing for transport B
|
|
370
|
-
const errorPromiseB: Promise<AxiosError | undefined> = t.throwsAsync(() => makeRequest('B'))
|
|
371
|
-
t.context.clock.tickAsync(10)
|
|
372
|
-
const errorB = await errorPromiseB
|
|
373
|
-
t.is(errorB?.response?.status, 504)
|
|
374
|
-
|
|
375
|
-
// Wait for the first background executes and check that they've been called
|
|
376
|
-
await t.context.clock.tickAsync(10)
|
|
377
|
-
t.is(transportA.backgroundExecuteCalls, 1)
|
|
378
|
-
t.is(transportB.backgroundExecuteCalls, 1)
|
|
379
|
-
|
|
380
|
-
// Run for a minute (59s actually, it'll start at 0 and go on regular intervals)
|
|
381
|
-
await runAllUntilTime(t.context.clock, 59 * 1000 + 10)
|
|
382
|
-
|
|
383
|
-
t.is(transportA.backgroundExecuteCalls, 30 * rateLimit1s + 1) // +1 for the first call
|
|
384
|
-
t.is(transportB.backgroundExecuteCalls, 30 * rateLimit1s)
|
|
385
|
-
},
|
|
386
|
-
)
|
|
387
|
-
|
|
388
|
-
test.serial(
|
|
389
|
-
'per second limit of 1 with two batch transports with different allocations results in correct time distribution',
|
|
390
|
-
async (t) => {
|
|
391
|
-
const rateLimit1s = 1
|
|
392
|
-
const transportA = new MockBatchWarmingTransport()
|
|
393
|
-
const transportB = new MockBatchWarmingTransport()
|
|
394
|
-
|
|
395
|
-
const adapter: Adapter = {
|
|
396
|
-
name: 'test',
|
|
397
|
-
endpoints: [
|
|
398
|
-
{
|
|
399
|
-
name: 'A',
|
|
400
|
-
inputParameters,
|
|
401
|
-
transport: transportA,
|
|
402
|
-
rateLimiting: {
|
|
403
|
-
allocationPercentage: 75,
|
|
404
|
-
},
|
|
405
|
-
},
|
|
406
|
-
{
|
|
407
|
-
// This one should be dynamically allocated
|
|
408
|
-
name: 'B',
|
|
409
|
-
inputParameters,
|
|
410
|
-
transport: transportB,
|
|
411
|
-
},
|
|
412
|
-
],
|
|
413
|
-
rateLimiting: {
|
|
414
|
-
tiers: {
|
|
415
|
-
default: {
|
|
416
|
-
rateLimit1s,
|
|
417
|
-
},
|
|
418
|
-
},
|
|
419
|
-
},
|
|
420
|
-
envDefaultOverrides: {
|
|
421
|
-
WARMUP_SUBSCRIPTION_TTL: 100000,
|
|
422
|
-
},
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
// Start the adapter
|
|
426
|
-
const server = await expose(adapter)
|
|
427
|
-
const address = `http://localhost:${(server?.address() as AddressInfo)?.port}`
|
|
428
|
-
|
|
429
|
-
const makeRequest = (endpointParam: string) =>
|
|
430
|
-
axios.post(address, {
|
|
431
|
-
data: {
|
|
432
|
-
from,
|
|
433
|
-
to,
|
|
434
|
-
},
|
|
435
|
-
endpoint: endpointParam,
|
|
436
|
-
})
|
|
437
|
-
|
|
438
|
-
// Expect the first response to time out
|
|
439
|
-
// The polling behavior is tested in the cache tests, so this is easier here.
|
|
440
|
-
// Start the request:
|
|
441
|
-
const errorPromiseA: Promise<AxiosError | undefined> = t.throwsAsync(() => makeRequest('A'))
|
|
442
|
-
// Advance enough time for the initial request async flow
|
|
443
|
-
t.context.clock.tickAsync(10)
|
|
444
|
-
// Wait for the failed cache get -> instant 504
|
|
445
|
-
const errorA = await errorPromiseA
|
|
446
|
-
t.is(errorA?.response?.status, 504)
|
|
447
|
-
|
|
448
|
-
// Do the same thing for transport B
|
|
449
|
-
const errorPromiseB: Promise<AxiosError | undefined> = t.throwsAsync(() => makeRequest('B'))
|
|
450
|
-
t.context.clock.tickAsync(10)
|
|
451
|
-
const errorB = await errorPromiseB
|
|
452
|
-
t.is(errorB?.response?.status, 504)
|
|
453
|
-
|
|
454
|
-
// Wait for the first background executes and check that they've been called
|
|
455
|
-
await t.context.clock.tickAsync(10)
|
|
456
|
-
t.is(transportA.backgroundExecuteCalls, 1)
|
|
457
|
-
t.is(transportB.backgroundExecuteCalls, 1)
|
|
458
|
-
|
|
459
|
-
// Run for a minute (59s actually, it'll start at 0 and go on regular intervals)
|
|
460
|
-
await runAllUntilTime(t.context.clock, 59 * 1000 + 10)
|
|
461
|
-
|
|
462
|
-
t.is(transportA.backgroundExecuteCalls, 45 * rateLimit1s + 1) // +1 for the first call
|
|
463
|
-
t.is(transportB.backgroundExecuteCalls, 15 * rateLimit1s)
|
|
464
|
-
},
|
|
465
|
-
)
|
|
@@ -1,242 +0,0 @@
|
|
|
1
|
-
import untypedTest, { TestFn } from 'ava'
|
|
2
|
-
import axios, { AxiosError, AxiosRequestConfig, AxiosResponse } from 'axios'
|
|
3
|
-
import { AddressInfo } from 'net'
|
|
4
|
-
import nock from 'nock'
|
|
5
|
-
import { expose } from '../../src'
|
|
6
|
-
import { Adapter, AdapterEndpoint } from '../../src/adapter'
|
|
7
|
-
import { EmptySettings } from '../../src/config'
|
|
8
|
-
import { RestTransport } from '../../src/transports'
|
|
9
|
-
import { AdapterRequest, AdapterResponse, sleep } from '../../src/util'
|
|
10
|
-
import { deferredPromise } from '../util'
|
|
11
|
-
|
|
12
|
-
const test = untypedTest as TestFn<{
|
|
13
|
-
serverAddress: string
|
|
14
|
-
}>
|
|
15
|
-
|
|
16
|
-
const URL = 'http://test-url.com'
|
|
17
|
-
|
|
18
|
-
test.before(() => {
|
|
19
|
-
nock.disableNetConnect()
|
|
20
|
-
nock.enableNetConnect('localhost')
|
|
21
|
-
})
|
|
22
|
-
|
|
23
|
-
test.after(() => {
|
|
24
|
-
nock.restore()
|
|
25
|
-
})
|
|
26
|
-
|
|
27
|
-
interface AdapterRequestParams {
|
|
28
|
-
from: string
|
|
29
|
-
to: string
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
interface ProviderRequestBody {
|
|
33
|
-
base: string
|
|
34
|
-
quote: string
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
interface ProviderResponseBody {
|
|
38
|
-
price: number
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
const endpoint = '/price'
|
|
42
|
-
|
|
43
|
-
const createAdapterEndpoint = (): AdapterEndpoint => {
|
|
44
|
-
const restEndpointTransport = new RestTransport<
|
|
45
|
-
AdapterRequestParams,
|
|
46
|
-
ProviderRequestBody,
|
|
47
|
-
ProviderResponseBody,
|
|
48
|
-
EmptySettings
|
|
49
|
-
>({
|
|
50
|
-
prepareRequest: (
|
|
51
|
-
req: AdapterRequest<AdapterRequestParams>,
|
|
52
|
-
): AxiosRequestConfig<ProviderRequestBody> => {
|
|
53
|
-
return {
|
|
54
|
-
baseURL: URL,
|
|
55
|
-
url: endpoint,
|
|
56
|
-
method: 'GET',
|
|
57
|
-
params: {
|
|
58
|
-
base: req.requestContext.data.from,
|
|
59
|
-
quote: req.requestContext.data.to,
|
|
60
|
-
},
|
|
61
|
-
}
|
|
62
|
-
},
|
|
63
|
-
parseResponse: (
|
|
64
|
-
req: AdapterRequest<AdapterRequestParams>,
|
|
65
|
-
res: AxiosResponse<ProviderResponseBody>,
|
|
66
|
-
): AdapterResponse<ProviderResponseBody> => {
|
|
67
|
-
return {
|
|
68
|
-
data: res.data,
|
|
69
|
-
statusCode: 200,
|
|
70
|
-
result: res.data.price,
|
|
71
|
-
}
|
|
72
|
-
},
|
|
73
|
-
options: {
|
|
74
|
-
coalescing: true,
|
|
75
|
-
},
|
|
76
|
-
})
|
|
77
|
-
|
|
78
|
-
return {
|
|
79
|
-
name: 'test',
|
|
80
|
-
inputParameters: {
|
|
81
|
-
from: {
|
|
82
|
-
type: 'string',
|
|
83
|
-
required: true,
|
|
84
|
-
},
|
|
85
|
-
to: {
|
|
86
|
-
type: 'string',
|
|
87
|
-
required: true,
|
|
88
|
-
},
|
|
89
|
-
},
|
|
90
|
-
transport: restEndpointTransport,
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
const from = 'ETH'
|
|
95
|
-
const to = 'USD'
|
|
96
|
-
const price = 1234
|
|
97
|
-
|
|
98
|
-
test('sends request to DP and returns response', async (t) => {
|
|
99
|
-
const adapter: Adapter = {
|
|
100
|
-
name: 'test',
|
|
101
|
-
defaultEndpoint: 'test',
|
|
102
|
-
endpoints: [createAdapterEndpoint()],
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
const server = await expose(adapter)
|
|
106
|
-
const address = `http://localhost:${(server?.address() as AddressInfo)?.port}`
|
|
107
|
-
|
|
108
|
-
nock(URL)
|
|
109
|
-
.get(endpoint)
|
|
110
|
-
.query({
|
|
111
|
-
base: from,
|
|
112
|
-
quote: to,
|
|
113
|
-
})
|
|
114
|
-
.reply(200, {
|
|
115
|
-
price,
|
|
116
|
-
})
|
|
117
|
-
|
|
118
|
-
const response = await axios.post(address, {
|
|
119
|
-
data: {
|
|
120
|
-
from,
|
|
121
|
-
to,
|
|
122
|
-
},
|
|
123
|
-
})
|
|
124
|
-
|
|
125
|
-
t.is(response.status, 200)
|
|
126
|
-
t.deepEqual(response.data, {
|
|
127
|
-
data: { price },
|
|
128
|
-
result: price,
|
|
129
|
-
statusCode: 200,
|
|
130
|
-
})
|
|
131
|
-
})
|
|
132
|
-
|
|
133
|
-
test('identical request to EA is coalesced and returned from cache', async (t) => {
|
|
134
|
-
const adapter: Adapter = {
|
|
135
|
-
name: 'test',
|
|
136
|
-
defaultEndpoint: 'test',
|
|
137
|
-
endpoints: [createAdapterEndpoint()],
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
const server = await expose(adapter)
|
|
141
|
-
const address = `http://localhost:${(server?.address() as AddressInfo)?.port}`
|
|
142
|
-
|
|
143
|
-
const [promise, resolve] = deferredPromise<ProviderResponseBody>()
|
|
144
|
-
nock(URL)
|
|
145
|
-
.get(endpoint)
|
|
146
|
-
.query({
|
|
147
|
-
base: from,
|
|
148
|
-
quote: to,
|
|
149
|
-
})
|
|
150
|
-
.reply(200, () => promise)
|
|
151
|
-
|
|
152
|
-
// Send first request, that will be blocked until we resolve the promise
|
|
153
|
-
const request1 = axios.post(address, {
|
|
154
|
-
data: {
|
|
155
|
-
from,
|
|
156
|
-
to,
|
|
157
|
-
},
|
|
158
|
-
})
|
|
159
|
-
await sleep(1) // To ensure all inner ops move along
|
|
160
|
-
|
|
161
|
-
// Send second request, and wait for response.
|
|
162
|
-
// This one will be coalesced with the first one, and made to wait until the cache is filled.
|
|
163
|
-
// We know it's not going to the DP twice, because Nock would block it.
|
|
164
|
-
const request2 = axios.post(address, {
|
|
165
|
-
data: {
|
|
166
|
-
from,
|
|
167
|
-
to,
|
|
168
|
-
},
|
|
169
|
-
})
|
|
170
|
-
await sleep(1) // To ensure all inner ops move along
|
|
171
|
-
|
|
172
|
-
// Resolve the promise
|
|
173
|
-
resolve({
|
|
174
|
-
price,
|
|
175
|
-
})
|
|
176
|
-
|
|
177
|
-
const responses = await Promise.all([request1, request2])
|
|
178
|
-
|
|
179
|
-
for (const response of responses) {
|
|
180
|
-
t.is(response.status, 200)
|
|
181
|
-
t.deepEqual(response.data, {
|
|
182
|
-
data: { price },
|
|
183
|
-
result: price,
|
|
184
|
-
statusCode: 200,
|
|
185
|
-
})
|
|
186
|
-
}
|
|
187
|
-
})
|
|
188
|
-
|
|
189
|
-
test('rate limits (per second) incoming request (without retries)', async (t) => {
|
|
190
|
-
const adapter: Adapter = {
|
|
191
|
-
name: 'test',
|
|
192
|
-
defaultEndpoint: 'test',
|
|
193
|
-
endpoints: [createAdapterEndpoint()],
|
|
194
|
-
rateLimiting: {
|
|
195
|
-
tiers: {
|
|
196
|
-
base: {
|
|
197
|
-
rateLimit1m: 1,
|
|
198
|
-
},
|
|
199
|
-
},
|
|
200
|
-
},
|
|
201
|
-
envDefaultOverrides: {
|
|
202
|
-
REST_TRANSPORT_MAX_RATE_LIMIT_RETRIES: 0,
|
|
203
|
-
},
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
const server = await expose(adapter)
|
|
207
|
-
const address = `http://localhost:${(server?.address() as AddressInfo)?.port}`
|
|
208
|
-
|
|
209
|
-
const payloads = [
|
|
210
|
-
{
|
|
211
|
-
base: 'ETH',
|
|
212
|
-
quote: 'BTC',
|
|
213
|
-
},
|
|
214
|
-
{
|
|
215
|
-
base: 'ETH',
|
|
216
|
-
quote: 'USD',
|
|
217
|
-
},
|
|
218
|
-
]
|
|
219
|
-
|
|
220
|
-
payloads.forEach((p) => {
|
|
221
|
-
nock(URL)
|
|
222
|
-
.get(endpoint)
|
|
223
|
-
.query(p)
|
|
224
|
-
.reply(200, {
|
|
225
|
-
price,
|
|
226
|
-
})
|
|
227
|
-
.persist()
|
|
228
|
-
})
|
|
229
|
-
|
|
230
|
-
const makeRequest = ({ base, quote }: { base: string; quote: string }) =>
|
|
231
|
-
axios.post(address, {
|
|
232
|
-
data: {
|
|
233
|
-
from: base,
|
|
234
|
-
to: quote,
|
|
235
|
-
},
|
|
236
|
-
})
|
|
237
|
-
|
|
238
|
-
const error: AxiosError | undefined = await t.throwsAsync(() =>
|
|
239
|
-
Promise.all(payloads.map((p) => makeRequest(p))),
|
|
240
|
-
)
|
|
241
|
-
t.is(error?.response?.status, 504)
|
|
242
|
-
})
|