@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.
Files changed (161) hide show
  1. package/dist/adapter.js +60 -0
  2. package/dist/background-executor.js +45 -0
  3. package/dist/cache/factory.js +57 -0
  4. package/dist/cache/index.js +163 -0
  5. package/dist/cache/local.js +83 -0
  6. package/dist/cache/metrics.js +114 -0
  7. package/dist/cache/redis.js +100 -0
  8. package/dist/config/index.js +364 -0
  9. package/dist/config/provider-limits.js +75 -0
  10. package/dist/examples/coingecko/batch-warming.js +52 -0
  11. package/dist/examples/coingecko/index.js +10 -0
  12. package/dist/examples/coingecko/rest.js +50 -0
  13. package/dist/examples/ncfx/config/index.js +15 -0
  14. package/dist/examples/ncfx/index.js +10 -0
  15. package/dist/examples/ncfx/websocket.js +72 -0
  16. package/dist/index.js +89 -0
  17. package/dist/metrics/constants.js +25 -0
  18. package/dist/metrics/index.js +76 -0
  19. package/dist/metrics/util.js +9 -0
  20. package/dist/rate-limiting/factory.js +33 -0
  21. package/dist/rate-limiting/index.js +36 -0
  22. package/dist/rate-limiting/metrics.js +32 -0
  23. package/dist/rate-limiting/nop-limiter.js +15 -0
  24. package/dist/rate-limiting/simple-counting.js +61 -0
  25. package/dist/src/adapter.js +112 -0
  26. package/dist/src/background-executor.js +45 -0
  27. package/dist/src/cache/factory.js +57 -0
  28. package/dist/src/cache/index.js +165 -0
  29. package/dist/src/cache/local.js +83 -0
  30. package/dist/src/cache/metrics.js +114 -0
  31. package/dist/src/cache/redis.js +100 -0
  32. package/dist/src/config/index.js +366 -0
  33. package/dist/src/config/provider-limits.js +75 -0
  34. package/dist/src/examples/bank-frick/accounts.js +191 -0
  35. package/dist/src/examples/bank-frick/config/index.js +45 -0
  36. package/dist/src/examples/bank-frick/index.js +14 -0
  37. package/dist/src/examples/bank-frick/util.js +39 -0
  38. package/dist/src/examples/coingecko/batch-warming.js +52 -0
  39. package/dist/src/examples/coingecko/index.js +10 -0
  40. package/dist/src/examples/coingecko/rest.js +50 -0
  41. package/dist/src/examples/ncfx/config/index.js +15 -0
  42. package/dist/src/examples/ncfx/index.js +10 -0
  43. package/dist/src/examples/ncfx/websocket.js +72 -0
  44. package/dist/src/index.js +89 -0
  45. package/dist/src/metrics/constants.js +25 -0
  46. package/dist/src/metrics/index.js +76 -0
  47. package/dist/src/metrics/util.js +9 -0
  48. package/dist/src/rate-limiting/background/fixed-frequency.js +37 -0
  49. package/dist/src/rate-limiting/index.js +63 -0
  50. package/dist/src/rate-limiting/metrics.js +32 -0
  51. package/dist/src/rate-limiting/request/simple-counting.js +62 -0
  52. package/dist/src/test.js +6 -0
  53. package/dist/src/transports/batch-warming.js +55 -0
  54. package/dist/src/transports/index.js +85 -0
  55. package/dist/src/transports/metrics.js +119 -0
  56. package/dist/src/transports/rest.js +93 -0
  57. package/dist/src/transports/util.js +85 -0
  58. package/dist/src/transports/websocket.js +175 -0
  59. package/dist/src/util/expiring-sorted-set.js +47 -0
  60. package/dist/src/util/index.js +35 -0
  61. package/dist/src/util/logger.js +62 -0
  62. package/dist/src/util/request.js +2 -0
  63. package/dist/src/validation/error.js +41 -0
  64. package/dist/src/validation/index.js +84 -0
  65. package/dist/src/validation/input-params.js +30 -0
  66. package/dist/src/validation/override-functions.js +40 -0
  67. package/dist/src/validation/preset-tokens.json +23 -0
  68. package/dist/src/validation/validator.js +303 -0
  69. package/dist/test.js +6 -0
  70. package/dist/transports/batch-warming.js +57 -0
  71. package/dist/transports/index.js +76 -0
  72. package/dist/transports/metrics.js +133 -0
  73. package/dist/transports/rest.js +91 -0
  74. package/dist/transports/util.js +85 -0
  75. package/dist/transports/websocket.js +171 -0
  76. package/dist/util/expiring-sorted-set.js +47 -0
  77. package/dist/util/index.js +35 -0
  78. package/dist/util/logger.js +62 -0
  79. package/dist/util/request.js +2 -0
  80. package/dist/validation/error.js +41 -0
  81. package/dist/validation/index.js +82 -0
  82. package/dist/validation/input-params.js +30 -0
  83. package/dist/validation/overrideFunctions.js +42 -0
  84. package/dist/validation/presetTokens.json +23 -0
  85. package/dist/validation/validator.js +303 -0
  86. package/package.json +6 -2
  87. package/.c8rc.json +0 -3
  88. package/.eslintignore +0 -9
  89. package/.eslintrc.js +0 -96
  90. package/.github/README.MD +0 -17
  91. package/.github/actions/setup/action.yaml +0 -13
  92. package/.github/workflows/main.yaml +0 -39
  93. package/.github/workflows/publish.yaml +0 -17
  94. package/.prettierignore +0 -13
  95. package/.yarnrc +0 -0
  96. package/docker-compose.yaml +0 -35
  97. package/src/adapter.ts +0 -236
  98. package/src/background-executor.ts +0 -53
  99. package/src/cache/factory.ts +0 -28
  100. package/src/cache/index.ts +0 -236
  101. package/src/cache/local.ts +0 -73
  102. package/src/cache/metrics.ts +0 -112
  103. package/src/cache/redis.ts +0 -93
  104. package/src/config/index.ts +0 -501
  105. package/src/config/provider-limits.ts +0 -130
  106. package/src/examples/coingecko/batch-warming.ts +0 -79
  107. package/src/examples/coingecko/index.ts +0 -9
  108. package/src/examples/coingecko/rest.ts +0 -77
  109. package/src/examples/ncfx/config/index.ts +0 -12
  110. package/src/examples/ncfx/index.ts +0 -9
  111. package/src/examples/ncfx/websocket.ts +0 -100
  112. package/src/index.ts +0 -106
  113. package/src/metrics/constants.ts +0 -23
  114. package/src/metrics/index.ts +0 -116
  115. package/src/metrics/util.ts +0 -11
  116. package/src/rate-limiting/background/fixed-frequency.ts +0 -47
  117. package/src/rate-limiting/index.ts +0 -100
  118. package/src/rate-limiting/metrics.ts +0 -18
  119. package/src/rate-limiting/request/simple-counting.ts +0 -76
  120. package/src/test.ts +0 -5
  121. package/src/transports/batch-warming.ts +0 -121
  122. package/src/transports/index.ts +0 -173
  123. package/src/transports/metrics.ts +0 -95
  124. package/src/transports/rest.ts +0 -161
  125. package/src/transports/util.ts +0 -63
  126. package/src/transports/websocket.ts +0 -238
  127. package/src/util/expiring-sorted-set.ts +0 -52
  128. package/src/util/index.ts +0 -20
  129. package/src/util/logger.ts +0 -69
  130. package/src/util/request.ts +0 -115
  131. package/src/validation/error.ts +0 -116
  132. package/src/validation/index.ts +0 -101
  133. package/src/validation/input-params.ts +0 -45
  134. package/src/validation/override-functions.ts +0 -44
  135. package/src/validation/preset-tokens.json +0 -23
  136. package/src/validation/validator.ts +0 -384
  137. package/test/adapter.test.ts +0 -27
  138. package/test/background-executor.test.ts +0 -109
  139. package/test/cache/cache-key.test.ts +0 -96
  140. package/test/cache/helper.ts +0 -101
  141. package/test/cache/local.test.ts +0 -54
  142. package/test/cache/redis.test.ts +0 -89
  143. package/test/correlation.test.ts +0 -114
  144. package/test/index.test.ts +0 -37
  145. package/test/metrics/feed-id.test.ts +0 -33
  146. package/test/metrics/helper.ts +0 -14
  147. package/test/metrics/labels.test.ts +0 -36
  148. package/test/metrics/metrics.test.ts +0 -267
  149. package/test/metrics/redis-metrics.test.ts +0 -113
  150. package/test/metrics/warmer-metrics.test.ts +0 -192
  151. package/test/metrics/ws-metrics.test.ts +0 -225
  152. package/test/rate-limit-config.test.ts +0 -243
  153. package/test/transports/batch.test.ts +0 -465
  154. package/test/transports/rest.test.ts +0 -242
  155. package/test/transports/websocket.test.ts +0 -183
  156. package/test/tsconfig.json +0 -5
  157. package/test/util.ts +0 -76
  158. package/test/validation.test.ts +0 -169
  159. package/test.sh +0 -20
  160. package/tsconfig.json +0 -24
  161. package/typedoc.json +0 -6
@@ -1,267 +0,0 @@
1
- import untypedTest, { TestFn } from 'ava'
2
- import axios, { 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 } from '../../src/util'
10
- import { parsePromMetrics } from './helper'
11
-
12
- const test = untypedTest as TestFn<{
13
- serverAddress: string
14
- }>
15
-
16
- const URL = 'http://test-url.com'
17
-
18
- interface AdapterRequestParams {
19
- from: string
20
- to: string
21
- }
22
-
23
- interface ProviderRequestBody {
24
- base: string
25
- quote: string
26
- }
27
-
28
- interface ProviderResponseBody {
29
- price: number
30
- }
31
-
32
- const endpoint = '/price'
33
-
34
- const createAdapterEndpoint = (): AdapterEndpoint => {
35
- const restEndpointTransport = new RestTransport<
36
- AdapterRequestParams,
37
- ProviderRequestBody,
38
- ProviderResponseBody,
39
- EmptySettings
40
- >({
41
- prepareRequest: (
42
- req: AdapterRequest<AdapterRequestParams>,
43
- ): AxiosRequestConfig<ProviderRequestBody> => {
44
- return {
45
- baseURL: URL,
46
- url: endpoint,
47
- method: 'GET',
48
- params: {
49
- base: req.requestContext.data.from,
50
- quote: req.requestContext.data.to,
51
- },
52
- }
53
- },
54
- parseResponse: (
55
- req: AdapterRequest<AdapterRequestParams>,
56
- res: AxiosResponse<ProviderResponseBody>,
57
- ): AdapterResponse<ProviderResponseBody> => {
58
- return {
59
- data: res.data,
60
- statusCode: 200,
61
- result: res.data.price,
62
- }
63
- },
64
- options: {
65
- coalescing: true,
66
- },
67
- })
68
-
69
- return {
70
- name: 'test',
71
- inputParameters: {
72
- from: {
73
- type: 'string',
74
- required: true,
75
- },
76
- to: {
77
- type: 'string',
78
- required: true,
79
- },
80
- },
81
- transport: restEndpointTransport,
82
- }
83
- }
84
-
85
- const from = 'ETH'
86
- const to = 'USD'
87
- const price = 1234
88
-
89
- test.before(async (t) => {
90
- process.env['METRICS_ENABLED'] = 'true'
91
- process.env['METRICS_PORT'] = '9090'
92
- process.env['npm_package_version'] = '1'
93
- const adapter: Adapter = {
94
- name: 'test',
95
- defaultEndpoint: 'test',
96
- endpoints: [createAdapterEndpoint()],
97
- }
98
- const server = await expose(adapter)
99
- if (!server) {
100
- throw 'Server did not start'
101
- }
102
- t.context.serverAddress = `http://localhost:${(server.address() as AddressInfo).port}`
103
- })
104
-
105
- test.serial('Test http requests total metrics (data provider hit)', async (t) => {
106
- const metricsAddress = `http://localhost:${process.env['METRICS_PORT']}/metrics`
107
- nock(URL)
108
- .get(endpoint)
109
- .query({
110
- base: from,
111
- quote: to,
112
- })
113
- .reply(200, {
114
- price,
115
- })
116
-
117
- await axios.post(t.context.serverAddress, {
118
- data: {
119
- from,
120
- to,
121
- },
122
- })
123
-
124
- const response = await axios.get(metricsAddress)
125
- const metricsMap = parsePromMetrics(response.data)
126
- const expectedLabel =
127
- '{method="POST",feed_id="|from:eth|to:usd",status_code="200",type="dataProviderHit",provider_status_code="200",app_name="test",app_version="undefined"}'
128
- t.is(metricsMap.get(`http_requests_total${expectedLabel}`), 1)
129
- })
130
-
131
- test.serial('Test http request duration metrics', async (t) => {
132
- const metricsAddress = `http://localhost:${process.env['METRICS_PORT']}/metrics`
133
- const response = await axios.get(metricsAddress)
134
- const metricsMap = parsePromMetrics(response.data)
135
- const expectedLabel = '{app_name="test",app_version="undefined"}'
136
- const responseTime = metricsMap.get(`http_request_duration_seconds_sum${expectedLabel}`)
137
- if (responseTime !== undefined) {
138
- t.is(typeof responseTime === 'number', true)
139
- t.is(responseTime > 0, true)
140
- } else {
141
- t.fail('Response time did not record')
142
- }
143
- })
144
-
145
- test.serial('Test data provider requests metrics', async (t) => {
146
- const metricsAddress = `http://localhost:${process.env['METRICS_PORT']}/metrics`
147
- const response = await axios.get(metricsAddress)
148
- const metricsMap = parsePromMetrics(response.data)
149
- const expectedLabel =
150
- '{provider_status_code="200",method="GET",app_name="test",app_version="undefined"}'
151
- t.is(metricsMap.get(`data_provider_requests${expectedLabel}`), 1)
152
- })
153
-
154
- test.serial('Test data provider request duration metrics', async (t) => {
155
- const metricsAddress = `http://localhost:${process.env['METRICS_PORT']}/metrics`
156
- const response = await axios.get(metricsAddress)
157
- const metricsMap = parsePromMetrics(response.data)
158
- const expectedLabel = '{app_name="test",app_version="undefined"}'
159
- const responseTime = metricsMap.get(`data_provider_request_duration_seconds_sum${expectedLabel}`)
160
- if (responseTime !== undefined) {
161
- t.is(typeof responseTime === 'number', true)
162
- t.is(responseTime > 0, true)
163
- } else {
164
- t.fail('Response time did not record')
165
- }
166
- })
167
-
168
- test.serial('Test cache set count metrics', async (t) => {
169
- const metricsAddress = `http://localhost:${process.env['METRICS_PORT']}/metrics`
170
- const response = await axios.get(metricsAddress)
171
- const metricsMap = parsePromMetrics(response.data)
172
- const expectedLabel =
173
- '{participant_id="test-|from:eth|to:usd",feed_id="|from:eth|to:usd",cache_type="local",app_name="test",app_version="undefined"}'
174
- t.is(metricsMap.get(`cache_data_set_count${expectedLabel}`), 1)
175
- })
176
-
177
- test.serial('Test cache max age metrics', async (t) => {
178
- const metricsAddress = `http://localhost:${process.env['METRICS_PORT']}/metrics`
179
- const response = await axios.get(metricsAddress)
180
- const metricsMap = parsePromMetrics(response.data)
181
- const expectedLabel =
182
- '{participant_id="test-|from:eth|to:usd",feed_id="|from:eth|to:usd",cache_type="local",app_name="test",app_version="undefined"}'
183
- t.is(metricsMap.get(`cache_data_max_age${expectedLabel}`), 90000)
184
- })
185
-
186
- test.serial('Test cache set staleness metrics', async (t) => {
187
- const metricsAddress = `http://localhost:${process.env['METRICS_PORT']}/metrics`
188
- const response = await axios.get(metricsAddress)
189
- const metricsMap = parsePromMetrics(response.data)
190
- const expectedLabel =
191
- '{participant_id="test-|from:eth|to:usd",feed_id="|from:eth|to:usd",cache_type="local",app_name="test",app_version="undefined"}'
192
- const staleness = metricsMap.get(`cache_data_staleness_seconds${expectedLabel}`)
193
- if (staleness !== undefined) {
194
- t.is(typeof staleness === 'number', true)
195
- t.is(staleness === 0, true)
196
- } else {
197
- t.fail('Staleness was not retrieved')
198
- }
199
- })
200
-
201
- test.serial('Test credit spent metrics', async (t) => {
202
- const metricsAddress = `http://localhost:${process.env['METRICS_PORT']}/metrics`
203
- const response = await axios.get(metricsAddress)
204
- const metricsMap = parsePromMetrics(response.data)
205
- const expectedLabel =
206
- '{feed_id="|from:eth|to:usd",participant_id="test-|from:eth|to:usd",app_name="test",app_version="undefined"}'
207
- t.is(metricsMap.get(`rate_limit_credits_spent_total${expectedLabel}`), 1)
208
- })
209
-
210
- test.serial('Test http requests total metrics (cache hit)', async (t) => {
211
- const metricsAddress = `http://localhost:${process.env['METRICS_PORT']}/metrics`
212
- nock(URL)
213
- .get(endpoint)
214
- .query({
215
- base: from,
216
- quote: to,
217
- })
218
- .reply(200, {
219
- price,
220
- })
221
-
222
- await axios.post(t.context.serverAddress, {
223
- data: {
224
- from,
225
- to,
226
- },
227
- })
228
-
229
- const response = await axios.get(metricsAddress)
230
- const metricsMap = parsePromMetrics(response.data)
231
- const expectedLabel =
232
- '{method="POST",feed_id="|from:eth|to:usd",status_code="200",type="cacheHit",app_name="test",app_version="undefined"}'
233
- t.is(metricsMap.get(`http_requests_total${expectedLabel}`), 1)
234
- })
235
-
236
- test.serial('Test cache get count metrics', async (t) => {
237
- const metricsAddress = `http://localhost:${process.env['METRICS_PORT']}/metrics`
238
- const response = await axios.get(metricsAddress)
239
- const metricsMap = parsePromMetrics(response.data)
240
- const expectedLabel =
241
- '{participant_id="test-|from:eth|to:usd",feed_id="|from:eth|to:usd",cache_type="local",app_name="test",app_version="undefined"}'
242
- t.is(metricsMap.get(`cache_data_get_count${expectedLabel}`), 1)
243
- })
244
-
245
- test.serial('Test cache get value metrics', async (t) => {
246
- const metricsAddress = `http://localhost:${process.env['METRICS_PORT']}/metrics`
247
- const response = await axios.get(metricsAddress)
248
- const metricsMap = parsePromMetrics(response.data)
249
- const expectedLabel =
250
- '{participant_id="test-|from:eth|to:usd",feed_id="|from:eth|to:usd",cache_type="local",app_name="test",app_version="undefined"}'
251
- t.is(metricsMap.get(`cache_data_get_values${expectedLabel}`), 1234)
252
- })
253
-
254
- test.serial('Test cache get staleness metrics', async (t) => {
255
- const metricsAddress = `http://localhost:${process.env['METRICS_PORT']}/metrics`
256
- const response = await axios.get(metricsAddress)
257
- const metricsMap = parsePromMetrics(response.data)
258
- const expectedLabel =
259
- '{participant_id="test-|from:eth|to:usd",feed_id="|from:eth|to:usd",cache_type="local",app_name="test",app_version="undefined"}'
260
- const staleness = metricsMap.get(`cache_data_staleness_seconds${expectedLabel}`)
261
- if (staleness !== undefined) {
262
- t.is(typeof staleness === 'number', true)
263
- t.is(staleness > 0, true)
264
- } else {
265
- t.fail('Staleness was not retrieved')
266
- }
267
- })
@@ -1,113 +0,0 @@
1
- import untypedTest, { TestFn } from 'ava'
2
- import axios from 'axios'
3
- import Redis from 'ioredis'
4
- import { AddressInfo } from 'ws'
5
- import { expose } from '../../src'
6
- import { Adapter, AdapterEndpoint } from '../../src/adapter'
7
- import { Cache, LocalCache, RedisCache } from '../../src/cache'
8
- import { SettingsMap } from '../../src/config'
9
- import { AdapterDependencies, Transport } from '../../src/transports'
10
- import { BasicCacheSetterTransport } from '../cache/helper'
11
- import { NopTransport } from '../util'
12
- import { parsePromMetrics } from './helper'
13
-
14
- export const test = untypedTest as TestFn<{
15
- serverAddress: string
16
- cache: Cache
17
- adapterEndpoint: AdapterEndpoint
18
- }>
19
-
20
- class RedisMock {
21
- store = new LocalCache<string>()
22
-
23
- get(key: string) {
24
- return this.store.get(key)
25
- }
26
-
27
- del(key: string) {
28
- return this.store.delete(key)
29
- }
30
-
31
- set(key: string, value: string, px: 'PX', ttl: number) {
32
- return this.store.set(key, value, ttl)
33
- }
34
-
35
- multi() {
36
- return new CommandChainMock(this)
37
- }
38
- }
39
-
40
- class CommandChainMock {
41
- promises: Promise<unknown>[] = []
42
- constructor(private redisMock: RedisMock) {}
43
-
44
- set(key: string, value: string, px: 'PX', ttl: number) {
45
- this.promises.push(this.redisMock.set(key, value, px, ttl))
46
- return this
47
- }
48
-
49
- exec() {
50
- return Promise.all(this.promises)
51
- }
52
- }
53
-
54
- test.before(async (t) => {
55
- process.env['METRICS_ENABLED'] = 'true'
56
- process.env['METRICS_PORT'] = '9091'
57
- const adapter: Adapter = {
58
- name: 'test',
59
- defaultEndpoint: 'test',
60
- endpoints: [
61
- {
62
- name: 'test',
63
- inputParameters: {
64
- base: {
65
- type: 'string',
66
- required: true,
67
- },
68
- factor: {
69
- type: 'number',
70
- required: true,
71
- },
72
- },
73
- transport: new BasicCacheSetterTransport() as Transport<unknown, unknown, SettingsMap>,
74
- },
75
- {
76
- name: 'nowork',
77
- inputParameters: {},
78
- transport: new NopTransport(),
79
- },
80
- ],
81
- envDefaultOverrides: {
82
- CACHE_POLLING_SLEEP_MS: 10,
83
- CACHE_POLLING_MAX_RETRIES: 3,
84
- },
85
- }
86
-
87
- const cache = new RedisCache(new RedisMock() as unknown as Redis) // Fake redis
88
- const dependencies: Partial<AdapterDependencies> = {
89
- cache,
90
- }
91
-
92
- t.context.cache = cache
93
- const server = await expose(adapter, dependencies)
94
- if (!server) {
95
- throw 'Server did not start'
96
- }
97
- t.context.serverAddress = `http://localhost:${(server.address() as AddressInfo).port}`
98
- })
99
-
100
- test.serial('Test redis sent command metric', async (t) => {
101
- const data = {
102
- base: 'eth',
103
- factor: 123,
104
- }
105
-
106
- await axios.post(t.context.serverAddress, { data })
107
- const metricsAddress = `http://localhost:${process.env['METRICS_PORT']}/metrics`
108
- const response = await axios.get(metricsAddress)
109
- const metricsMap = parsePromMetrics(response.data)
110
- const expectedLabel =
111
- '{status="SUCCESS",function_name="exec",app_name="test",app_version="undefined"}'
112
- t.is(metricsMap.get(`redis_commands_sent_count${expectedLabel}`), 1)
113
- })
@@ -1,192 +0,0 @@
1
- import FakeTimers 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 { BatchWarmingTransport } from '../../src/transports'
11
- import { ProviderResult } from '../../src/util'
12
- import { MockCache } from '../util'
13
- import { parsePromMetrics } from './helper'
14
-
15
- const test = untypedTest as TestFn<{
16
- serverAddress: string
17
- cache: MockCache
18
- }>
19
-
20
- const URL = 'http://test-url.com'
21
- const endpoint = '/price'
22
-
23
- interface AdapterRequestParams {
24
- from: string
25
- to: string
26
- }
27
-
28
- interface ProviderRequestBody {
29
- pairs: Array<{
30
- base: string
31
- quote: string
32
- }>
33
- }
34
-
35
- interface ProviderResponseBody {
36
- prices: Array<{
37
- pair: string
38
- price: number
39
- }>
40
- }
41
- const clock = FakeTimers.install({ shouldAdvanceTime: true, advanceTimeDelta: 100 })
42
-
43
- test.before(async (t) => {
44
- nock.disableNetConnect()
45
- nock.enableNetConnect('localhost')
46
- process.env['METRICS_ENABLED'] = 'true'
47
- process.env['METRICS_PORT'] = '9092'
48
- // Disable retries to make the testing flow easier
49
- process.env['CACHE_POLLING_MAX_RETRIES'] = '0'
50
-
51
- const adapter: Adapter = {
52
- name: 'test',
53
- defaultEndpoint: 'test',
54
- endpoints: [
55
- {
56
- name: 'test',
57
- inputParameters,
58
- transport: new MockBatchWarmingTransport(),
59
- },
60
- ],
61
- }
62
-
63
- // Create mocked cache so we can listen when values are set
64
- // This is a more reliable method than expecting precise clock timings
65
- const mockCache = new MockCache()
66
-
67
- // Start the adapter
68
- const server = await expose(adapter, {
69
- cache: mockCache,
70
- })
71
- t.context.serverAddress = `http://localhost:${(server?.address() as AddressInfo)?.port}`
72
- t.context.cache = mockCache
73
- })
74
-
75
- test.after(() => {
76
- nock.restore()
77
- clock.uninstall()
78
- })
79
-
80
- class MockBatchWarmingTransport extends BatchWarmingTransport<
81
- AdapterRequestParams,
82
- ProviderRequestBody,
83
- ProviderResponseBody,
84
- SettingsMap
85
- > {
86
- constructor() {
87
- super({
88
- prepareRequest: (params: AdapterRequestParams[]): AxiosRequestConfig<ProviderRequestBody> => {
89
- return {
90
- baseURL: URL,
91
- url: '/price',
92
- method: 'POST',
93
- data: {
94
- pairs: params.map((p) => ({ base: p.from, quote: p.to })),
95
- },
96
- }
97
- },
98
- parseResponse: (
99
- res: AxiosResponse<ProviderResponseBody>,
100
- ): ProviderResult<AdapterRequestParams>[] => {
101
- return res.data.prices.map((p) => {
102
- const [from, to] = p.pair.split('/')
103
- return {
104
- params: { from, to },
105
- value: p.price,
106
- }
107
- })
108
- },
109
- })
110
- }
111
- }
112
-
113
- const from = 'ETH'
114
- const to = 'USD'
115
- const price = 1234
116
-
117
- nock(URL)
118
- .post(endpoint, {
119
- pairs: [
120
- {
121
- base: from,
122
- quote: to,
123
- },
124
- ],
125
- })
126
- .reply(200, {
127
- prices: [
128
- {
129
- pair: `${from}/${to}`,
130
- price,
131
- },
132
- ],
133
- })
134
- .persist()
135
-
136
- const inputParameters = {
137
- from: {
138
- type: 'string',
139
- required: true,
140
- },
141
- to: {
142
- type: 'string',
143
- required: true,
144
- },
145
- } as const
146
-
147
- test.serial('Test cache warmer active metric', async (t) => {
148
- const makeRequest = () =>
149
- axios.post(t.context.serverAddress, {
150
- data: {
151
- from,
152
- to,
153
- },
154
- })
155
-
156
- // Expect the first response to time out
157
- // The polling behavior is tested in the cache tests, so this is easier here.
158
- // Start the request:
159
- const errorPromise: Promise<AxiosError | undefined> = t.throwsAsync(makeRequest)
160
- // Advance enough time for the initial request async flow
161
- clock.tickAsync(10)
162
- // Wait for the failed cache get -> instant 504
163
- const error = await errorPromise
164
- t.is(error?.response?.status, 504)
165
-
166
- // Advance clock so that the batch warmer executes once again and wait for the cache to be set
167
- const cacheValueSetPromise = t.context.cache.waitForNextSet()
168
- await clock.tickAsync(DEFAULT_SHARED_MS_BETWEEN_REQUESTS + 10)
169
- await cacheValueSetPromise
170
-
171
- // Second request should find the response in the cache
172
- let response = await makeRequest()
173
-
174
- t.is(response.status, 200)
175
- const metricsAddress = `http://localhost:${process.env['METRICS_PORT']}/metrics`
176
- response = await axios.get(metricsAddress)
177
- let metricsMap = parsePromMetrics(response.data)
178
- let expectedLabel = '{isBatched="true",app_name="test",app_version="undefined"}'
179
- t.is(metricsMap.get(`cache_warmer_get_count${expectedLabel}`), 1)
180
-
181
- // Wait until the cache expires, and the subscription is out
182
- await clock.tickAsync(90001)
183
-
184
- // Now that the cache is out and the subscription no longer there, this should time out
185
- const error2: AxiosError | undefined = await t.throwsAsync(makeRequest)
186
- t.is(error2?.response?.status, 504)
187
-
188
- response = await axios.get(metricsAddress)
189
- metricsMap = parsePromMetrics(response.data)
190
- expectedLabel = '{isBatched="true",app_name="test",app_version="undefined"}'
191
- t.is(metricsMap.get(`cache_warmer_get_count${expectedLabel}`), 0)
192
- })