@chainlink/external-adapter-framework 0.0.14 → 0.0.15
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/.c8rc.json +3 -0
- package/.eslintignore +10 -0
- package/.eslintrc.js +96 -0
- package/.github/README.MD +42 -0
- package/.github/actions/setup/action.yaml +13 -0
- package/.github/workflows/label.yaml +39 -0
- package/.github/workflows/main.yaml +39 -0
- package/.github/workflows/publish.yaml +17 -0
- package/.prettierignore +13 -0
- package/.yarnrc +0 -0
- package/README.md +103 -0
- package/dist/examples/coingecko/test/e2e/adapter.test.ts.js +82953 -0
- package/dist/examples/coingecko/test/integration/adapter.test.ts.js +91672 -0
- package/dist/main.js +72703 -0
- package/docker-compose.yaml +35 -0
- package/env.sh +54 -0
- package/env2.sh +55 -0
- package/jest.config.js +5 -0
- package/package.json +14 -3
- package/publish.sh +0 -0
- package/src/adapter.ts +263 -0
- package/src/background-executor.ts +52 -0
- package/src/cache/factory.ts +26 -0
- package/src/cache/index.ts +258 -0
- package/src/cache/local.ts +73 -0
- package/src/cache/metrics.ts +112 -0
- package/src/cache/redis.ts +93 -0
- package/src/config/index.ts +517 -0
- package/src/config/provider-limits.ts +127 -0
- package/src/examples/bank-frick/README.MD +10 -0
- package/src/examples/bank-frick/accounts.ts +246 -0
- package/src/examples/bank-frick/config/index.ts +53 -0
- package/src/examples/bank-frick/index.ts +13 -0
- package/src/examples/bank-frick/types.d.ts +38 -0
- package/src/examples/bank-frick/util.ts +55 -0
- package/src/examples/coingecko/src/config/index.ts +12 -0
- package/src/examples/coingecko/src/config/overrides.json +10826 -0
- package/src/examples/coingecko/src/cryptoUtils.ts +88 -0
- package/src/examples/coingecko/src/endpoint/coins.ts +54 -0
- package/src/examples/coingecko/src/endpoint/crypto-marketcap.ts +66 -0
- package/src/examples/coingecko/src/endpoint/crypto-volume.ts +66 -0
- package/src/examples/coingecko/src/endpoint/crypto.ts +63 -0
- package/src/examples/coingecko/src/endpoint/dominance.ts +40 -0
- package/src/examples/coingecko/src/endpoint/global-marketcap.ts +40 -0
- package/src/examples/coingecko/src/endpoint/index.ts +6 -0
- package/src/examples/coingecko/src/globalUtils.ts +78 -0
- package/src/examples/coingecko/src/index.ts +17 -0
- package/src/examples/coingecko/test/e2e/adapter.test.ts +278 -0
- package/src/examples/coingecko/test/integration/__snapshots__/adapter.test.ts.snap +15 -0
- package/src/examples/coingecko/test/integration/adapter.test.ts +281 -0
- package/src/examples/coingecko/test/integration/capturedRequests.json +1 -0
- package/src/examples/coingecko/test/integration/fixtures.ts +42 -0
- package/src/examples/coingecko-old/batch-warming.ts +79 -0
- package/src/examples/coingecko-old/index.ts +9 -0
- package/src/examples/coingecko-old/rest.ts +77 -0
- package/src/examples/ncfx/config/index.ts +12 -0
- package/src/examples/ncfx/index.ts +9 -0
- package/src/examples/ncfx/websocket.ts +99 -0
- package/src/index.ts +149 -0
- package/src/metrics/constants.ts +23 -0
- package/src/metrics/index.ts +115 -0
- package/src/metrics/util.ts +18 -0
- package/src/rate-limiting/background/fixed-frequency.ts +45 -0
- package/src/rate-limiting/index.ts +100 -0
- package/src/rate-limiting/metrics.ts +18 -0
- package/src/rate-limiting/request/simple-counting.ts +76 -0
- package/src/transports/batch-warming.ts +127 -0
- package/src/transports/index.ts +152 -0
- package/src/transports/metrics.ts +95 -0
- package/src/transports/rest.ts +168 -0
- package/src/transports/util.ts +63 -0
- package/src/transports/websocket.ts +245 -0
- package/src/util/index.ts +23 -0
- package/src/util/logger.ts +69 -0
- package/src/util/recordRequests.ts +47 -0
- package/src/util/request.ts +117 -0
- package/src/util/subscription-set/expiring-sorted-set.ts +54 -0
- package/src/util/subscription-set/subscription-set.ts +35 -0
- package/src/util/test-payload-loader.ts +87 -0
- package/src/validation/error.ts +116 -0
- package/src/validation/index.ts +110 -0
- package/src/validation/input-params.ts +45 -0
- package/src/validation/override-functions.ts +44 -0
- package/src/validation/overrideFunctions.ts +44 -0
- package/src/validation/preset-tokens.json +23 -0
- package/src/validation/validator.ts +384 -0
- package/test/adapter.test.ts +27 -0
- package/test/background-executor.test.ts +108 -0
- package/test/cache/cache-key.test.ts +114 -0
- package/test/cache/helper.ts +100 -0
- package/test/cache/local.test.ts +54 -0
- package/test/cache/redis.test.ts +89 -0
- package/test/correlation.test.ts +114 -0
- package/test/index.test.ts +37 -0
- package/test/metrics/feed-id.test.ts +38 -0
- package/test/metrics/helper.ts +14 -0
- package/test/metrics/labels.test.ts +36 -0
- package/test/metrics/metrics.test.ts +267 -0
- package/test/metrics/redis-metrics.test.ts +113 -0
- package/test/metrics/warmer-metrics.test.ts +193 -0
- package/test/metrics/ws-metrics.test.ts +225 -0
- package/test/rate-limit-config.test.ts +242 -0
- package/test/smoke/smoke.test.ts +166 -0
- package/test/smoke/test-payload-fail.json +3 -0
- package/test/smoke/test-payload.js +22 -0
- package/test/smoke/test-payload.json +7 -0
- package/test/transports/batch.test.ts +466 -0
- package/test/transports/rest.test.ts +242 -0
- package/test/transports/websocket.test.ts +183 -0
- package/test/tsconfig.json +5 -0
- package/test/util.ts +77 -0
- package/test/validation.test.ts +178 -0
- package/test.sh +20 -0
- package/test2.sh +2 -0
- package/tsconfig.json +28 -0
- package/typedoc.json +6 -0
- package/webpack.config.js +57 -0
- package/yarn-error.log +3778 -0
- package/adapter.d.ts +0 -107
- package/adapter.js +0 -115
- package/background-executor.d.ts +0 -11
- package/background-executor.js +0 -45
- package/cache/factory.d.ts +0 -6
- package/cache/factory.js +0 -55
- package/cache/index.d.ts +0 -94
- package/cache/index.js +0 -173
- package/cache/local.d.ts +0 -23
- package/cache/local.js +0 -83
- package/cache/metrics.d.ts +0 -27
- package/cache/metrics.js +0 -120
- package/cache/redis.d.ts +0 -16
- package/cache/redis.js +0 -100
- package/chainlink-external-adapter-framework-0.0.6.tgz +0 -0
- package/config/index.d.ts +0 -209
- package/config/index.js +0 -380
- package/config/provider-limits.d.ts +0 -31
- package/config/provider-limits.js +0 -79
- package/examples/bank-frick/accounts.d.ts +0 -39
- package/examples/bank-frick/accounts.js +0 -191
- package/examples/bank-frick/config/index.d.ts +0 -4
- package/examples/bank-frick/config/index.js +0 -54
- package/examples/bank-frick/index.d.ts +0 -2
- package/examples/bank-frick/index.js +0 -14
- package/examples/bank-frick/util.d.ts +0 -4
- package/examples/bank-frick/util.js +0 -39
- package/examples/coingecko/batch-warming.d.ts +0 -2
- package/examples/coingecko/batch-warming.js +0 -52
- package/examples/coingecko/index.d.ts +0 -2
- package/examples/coingecko/index.js +0 -10
- package/examples/coingecko/rest.d.ts +0 -2
- package/examples/coingecko/rest.js +0 -50
- package/examples/ncfx/config/index.d.ts +0 -12
- package/examples/ncfx/config/index.js +0 -15
- package/examples/ncfx/index.d.ts +0 -2
- package/examples/ncfx/index.js +0 -10
- package/examples/ncfx/websocket.d.ts +0 -36
- package/examples/ncfx/websocket.js +0 -72
- package/index.d.ts +0 -11
- package/index.js +0 -133
- package/metrics/constants.d.ts +0 -16
- package/metrics/constants.js +0 -25
- package/metrics/index.d.ts +0 -15
- package/metrics/index.js +0 -122
- package/metrics/util.d.ts +0 -7
- package/metrics/util.js +0 -9
- package/package/adapter.d.ts +0 -88
- package/package/adapter.js +0 -112
- package/package/background-executor.d.ts +0 -11
- package/package/background-executor.js +0 -45
- package/package/cache/factory.d.ts +0 -6
- package/package/cache/factory.js +0 -57
- package/package/cache/index.d.ts +0 -90
- package/package/cache/index.js +0 -169
- package/package/cache/local.d.ts +0 -23
- package/package/cache/local.js +0 -83
- package/package/cache/metrics.d.ts +0 -27
- package/package/cache/metrics.js +0 -120
- package/package/cache/redis.d.ts +0 -16
- package/package/cache/redis.js +0 -100
- package/package/config/index.d.ts +0 -195
- package/package/config/index.js +0 -365
- package/package/config/provider-limits.d.ts +0 -31
- package/package/config/provider-limits.js +0 -76
- package/package/examples/coingecko/batch-warming.d.ts +0 -2
- package/package/examples/coingecko/batch-warming.js +0 -52
- package/package/examples/coingecko/index.d.ts +0 -2
- package/package/examples/coingecko/index.js +0 -10
- package/package/examples/coingecko/rest.d.ts +0 -2
- package/package/examples/coingecko/rest.js +0 -50
- package/package/examples/ncfx/config/index.d.ts +0 -12
- package/package/examples/ncfx/config/index.js +0 -15
- package/package/examples/ncfx/index.d.ts +0 -2
- package/package/examples/ncfx/index.js +0 -10
- package/package/examples/ncfx/websocket.d.ts +0 -36
- package/package/examples/ncfx/websocket.js +0 -72
- package/package/index.d.ts +0 -12
- package/package/index.js +0 -92
- package/package/metrics/constants.d.ts +0 -16
- package/package/metrics/constants.js +0 -25
- package/package/metrics/index.d.ts +0 -15
- package/package/metrics/index.js +0 -123
- package/package/metrics/util.d.ts +0 -3
- package/package/metrics/util.js +0 -9
- package/package/package.json +0 -72
- package/package/rate-limiting/background/fixed-frequency.d.ts +0 -10
- package/package/rate-limiting/background/fixed-frequency.js +0 -37
- package/package/rate-limiting/index.d.ts +0 -54
- package/package/rate-limiting/index.js +0 -63
- package/package/rate-limiting/metrics.d.ts +0 -3
- package/package/rate-limiting/metrics.js +0 -44
- package/package/rate-limiting/request/simple-counting.d.ts +0 -20
- package/package/rate-limiting/request/simple-counting.js +0 -62
- package/package/test.d.ts +0 -1
- package/package/test.js +0 -6
- package/package/transports/batch-warming.d.ts +0 -34
- package/package/transports/batch-warming.js +0 -101
- package/package/transports/index.d.ts +0 -87
- package/package/transports/index.js +0 -87
- package/package/transports/metrics.d.ts +0 -21
- package/package/transports/metrics.js +0 -105
- package/package/transports/rest.d.ts +0 -43
- package/package/transports/rest.js +0 -129
- package/package/transports/util.d.ts +0 -8
- package/package/transports/util.js +0 -85
- package/package/transports/websocket.d.ts +0 -80
- package/package/transports/websocket.js +0 -169
- package/package/util/expiring-sorted-set.d.ts +0 -21
- package/package/util/expiring-sorted-set.js +0 -47
- package/package/util/index.d.ts +0 -11
- package/package/util/index.js +0 -35
- package/package/util/logger.d.ts +0 -42
- package/package/util/logger.js +0 -62
- package/package/util/request.d.ts +0 -55
- package/package/util/request.js +0 -2
- package/package/validation/error.d.ts +0 -50
- package/package/validation/error.js +0 -79
- package/package/validation/index.d.ts +0 -5
- package/package/validation/index.js +0 -86
- package/package/validation/input-params.d.ts +0 -15
- package/package/validation/input-params.js +0 -30
- package/package/validation/override-functions.d.ts +0 -3
- package/package/validation/override-functions.js +0 -40
- package/package/validation/preset-tokens.json +0 -23
- package/package/validation/validator.d.ts +0 -47
- package/package/validation/validator.js +0 -303
- package/rate-limiting/background/fixed-frequency.d.ts +0 -10
- package/rate-limiting/background/fixed-frequency.js +0 -35
- package/rate-limiting/index.d.ts +0 -54
- package/rate-limiting/index.js +0 -63
- package/rate-limiting/metrics.d.ts +0 -3
- package/rate-limiting/metrics.js +0 -44
- package/rate-limiting/request/simple-counting.d.ts +0 -20
- package/rate-limiting/request/simple-counting.js +0 -62
- package/test.d.ts +0 -1
- package/test.js +0 -6
- package/transports/batch-warming.d.ts +0 -35
- package/transports/batch-warming.js +0 -101
- package/transports/index.d.ts +0 -70
- package/transports/index.js +0 -87
- package/transports/metrics.d.ts +0 -21
- package/transports/metrics.js +0 -105
- package/transports/rest.d.ts +0 -44
- package/transports/rest.js +0 -131
- package/transports/util.d.ts +0 -8
- package/transports/util.js +0 -85
- package/transports/websocket.d.ts +0 -81
- package/transports/websocket.js +0 -168
- package/util/expiring-sorted-set.d.ts +0 -21
- package/util/expiring-sorted-set.js +0 -47
- package/util/index.d.ts +0 -12
- package/util/index.js +0 -35
- package/util/logger.d.ts +0 -42
- package/util/logger.js +0 -62
- package/util/request.d.ts +0 -57
- package/util/request.js +0 -2
- package/util/subscription-set/expiring-sorted-set.d.ts +0 -22
- package/util/subscription-set/expiring-sorted-set.js +0 -47
- package/util/subscription-set/subscription-set.d.ts +0 -18
- package/util/subscription-set/subscription-set.js +0 -19
- package/util/test-payload-loader.d.ts +0 -25
- package/util/test-payload-loader.js +0 -83
- package/validation/error.d.ts +0 -50
- package/validation/error.js +0 -79
- package/validation/index.d.ts +0 -5
- package/validation/index.js +0 -91
- package/validation/input-params.d.ts +0 -15
- package/validation/input-params.js +0 -30
- package/validation/override-functions.d.ts +0 -3
- package/validation/override-functions.js +0 -40
- package/validation/preset-tokens.json +0 -23
- package/validation/validator.d.ts +0 -47
- package/validation/validator.js +0 -303
|
@@ -0,0 +1,267 @@
|
|
|
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
|
+
})
|
|
@@ -0,0 +1,113 @@
|
|
|
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, AdapterDependencies, AdapterEndpoint } from '../../src/adapter'
|
|
7
|
+
import { Cache, LocalCache, RedisCache } from '../../src/cache'
|
|
8
|
+
import { SettingsMap } from '../../src/config'
|
|
9
|
+
import { 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
|
+
})
|
|
@@ -0,0 +1,193 @@
|
|
|
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
|
+
params: AdapterRequestParams[],
|
|
100
|
+
res: AxiosResponse<ProviderResponseBody>,
|
|
101
|
+
): ProviderResult<AdapterRequestParams>[] => {
|
|
102
|
+
return res.data.prices.map((p) => {
|
|
103
|
+
const [from, to] = p.pair.split('/')
|
|
104
|
+
return {
|
|
105
|
+
params: { from, to },
|
|
106
|
+
value: p.price,
|
|
107
|
+
}
|
|
108
|
+
})
|
|
109
|
+
},
|
|
110
|
+
})
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const from = 'ETH'
|
|
115
|
+
const to = 'USD'
|
|
116
|
+
const price = 1234
|
|
117
|
+
|
|
118
|
+
nock(URL)
|
|
119
|
+
.post(endpoint, {
|
|
120
|
+
pairs: [
|
|
121
|
+
{
|
|
122
|
+
base: from,
|
|
123
|
+
quote: to,
|
|
124
|
+
},
|
|
125
|
+
],
|
|
126
|
+
})
|
|
127
|
+
.reply(200, {
|
|
128
|
+
prices: [
|
|
129
|
+
{
|
|
130
|
+
pair: `${from}/${to}`,
|
|
131
|
+
price,
|
|
132
|
+
},
|
|
133
|
+
],
|
|
134
|
+
})
|
|
135
|
+
.persist()
|
|
136
|
+
|
|
137
|
+
const inputParameters = {
|
|
138
|
+
from: {
|
|
139
|
+
type: 'string',
|
|
140
|
+
required: true,
|
|
141
|
+
},
|
|
142
|
+
to: {
|
|
143
|
+
type: 'string',
|
|
144
|
+
required: true,
|
|
145
|
+
},
|
|
146
|
+
} as const
|
|
147
|
+
|
|
148
|
+
test.serial('Test cache warmer active metric', async (t) => {
|
|
149
|
+
const makeRequest = () =>
|
|
150
|
+
axios.post(t.context.serverAddress, {
|
|
151
|
+
data: {
|
|
152
|
+
from,
|
|
153
|
+
to,
|
|
154
|
+
},
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
// Expect the first response to time out
|
|
158
|
+
// The polling behavior is tested in the cache tests, so this is easier here.
|
|
159
|
+
// Start the request:
|
|
160
|
+
const errorPromise: Promise<AxiosError | undefined> = t.throwsAsync(makeRequest)
|
|
161
|
+
// Advance enough time for the initial request async flow
|
|
162
|
+
clock.tickAsync(10)
|
|
163
|
+
// Wait for the failed cache get -> instant 504
|
|
164
|
+
const error = await errorPromise
|
|
165
|
+
t.is(error?.response?.status, 504)
|
|
166
|
+
|
|
167
|
+
// Advance clock so that the batch warmer executes once again and wait for the cache to be set
|
|
168
|
+
const cacheValueSetPromise = t.context.cache.waitForNextSet()
|
|
169
|
+
await clock.tickAsync(DEFAULT_SHARED_MS_BETWEEN_REQUESTS + 10)
|
|
170
|
+
await cacheValueSetPromise
|
|
171
|
+
|
|
172
|
+
// Second request should find the response in the cache
|
|
173
|
+
let response = await makeRequest()
|
|
174
|
+
|
|
175
|
+
t.is(response.status, 200)
|
|
176
|
+
const metricsAddress = `http://localhost:${process.env['METRICS_PORT']}/metrics`
|
|
177
|
+
response = await axios.get(metricsAddress)
|
|
178
|
+
let metricsMap = parsePromMetrics(response.data)
|
|
179
|
+
let expectedLabel = '{isBatched="true",app_name="test",app_version="undefined"}'
|
|
180
|
+
t.is(metricsMap.get(`cache_warmer_get_count${expectedLabel}`), 1)
|
|
181
|
+
|
|
182
|
+
// Wait until the cache expires, and the subscription is out
|
|
183
|
+
await clock.tickAsync(90001)
|
|
184
|
+
|
|
185
|
+
// Now that the cache is out and the subscription no longer there, this should time out
|
|
186
|
+
const error2: AxiosError | undefined = await t.throwsAsync(makeRequest)
|
|
187
|
+
t.is(error2?.response?.status, 504)
|
|
188
|
+
|
|
189
|
+
response = await axios.get(metricsAddress)
|
|
190
|
+
metricsMap = parsePromMetrics(response.data)
|
|
191
|
+
expectedLabel = '{isBatched="true",app_name="test",app_version="undefined"}'
|
|
192
|
+
t.is(metricsMap.get(`cache_warmer_get_count${expectedLabel}`), 0)
|
|
193
|
+
})
|