@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,183 @@
|
|
|
1
|
+
import FakeTimers from '@sinonjs/fake-timers'
|
|
2
|
+
import test from 'ava'
|
|
3
|
+
import axios, { AxiosError } from 'axios'
|
|
4
|
+
import { Server, WebSocket } from 'mock-socket'
|
|
5
|
+
import { AddressInfo } from 'net'
|
|
6
|
+
import { expose } from '../../src'
|
|
7
|
+
import { Adapter, AdapterEndpoint } from '../../src/adapter'
|
|
8
|
+
import { DEFAULT_SHARED_MS_BETWEEN_REQUESTS } from '../../src/rate-limiting'
|
|
9
|
+
import { DEFAULT_WS_TTL, WebSocketClassProvider, WebSocketTransport } from '../../src/transports'
|
|
10
|
+
import { ProviderResult } from '../../src/util'
|
|
11
|
+
import { InputParameters } from '../../src/validation'
|
|
12
|
+
import { MockCache } from '../util'
|
|
13
|
+
|
|
14
|
+
interface AdapterRequestParams {
|
|
15
|
+
base: string
|
|
16
|
+
quote: string
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const inputParameters: InputParameters = {
|
|
20
|
+
base: {
|
|
21
|
+
type: 'string',
|
|
22
|
+
required: true,
|
|
23
|
+
},
|
|
24
|
+
quote: {
|
|
25
|
+
type: 'string',
|
|
26
|
+
required: true,
|
|
27
|
+
},
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
interface ProviderMessage {
|
|
31
|
+
pair: string
|
|
32
|
+
value: number
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const URL = 'wss://test-ws.com/asd'
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Sets the mocked websocket instance in the provided provider class.
|
|
39
|
+
* We need this here, because the tests will connect using their instance of WebSocketClassProvider;
|
|
40
|
+
* fetching from this library to the \@chainlink/ea-bootstrap package would access _another_ instance
|
|
41
|
+
* of the same constructor. Although it should be a singleton, dependencies are different so that
|
|
42
|
+
* means that the static classes themselves are also different.
|
|
43
|
+
*
|
|
44
|
+
* @param provider - singleton WebSocketClassProvider
|
|
45
|
+
*/
|
|
46
|
+
export const mockWebSocketProvider = (provider: typeof WebSocketClassProvider): void => {
|
|
47
|
+
// Extend mock WebSocket class to bypass protocol headers error
|
|
48
|
+
class MockWebSocket extends WebSocket {
|
|
49
|
+
constructor(url: string, protocol: string | string[] | Record<string, string> | undefined) {
|
|
50
|
+
super(url, protocol instanceof Object ? undefined : protocol)
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Need to disable typing, the mock-socket impl does not implement the ws interface fully
|
|
55
|
+
provider.set(MockWebSocket as any) // eslint-disable-line @typescript-eslint/no-explicit-any
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export const websocketTransport = new WebSocketTransport({
|
|
59
|
+
url: URL,
|
|
60
|
+
handlers: {
|
|
61
|
+
async open() {
|
|
62
|
+
return
|
|
63
|
+
},
|
|
64
|
+
|
|
65
|
+
message(message: ProviderMessage): ProviderResult<AdapterRequestParams>[] {
|
|
66
|
+
const [base, quote] = message.pair.split('/')
|
|
67
|
+
return [
|
|
68
|
+
{
|
|
69
|
+
params: { base, quote },
|
|
70
|
+
value: message.value,
|
|
71
|
+
},
|
|
72
|
+
]
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
builders: {
|
|
76
|
+
subscribeMessage: (params: AdapterRequestParams) => ({
|
|
77
|
+
request: 'subscribe',
|
|
78
|
+
pair: `${params.base}/${params.quote}`,
|
|
79
|
+
}),
|
|
80
|
+
unsubscribeMessage: (params: AdapterRequestParams) => ({
|
|
81
|
+
request: 'unsubscribe',
|
|
82
|
+
pair: `${params.base}/${params.quote}`,
|
|
83
|
+
}),
|
|
84
|
+
},
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
export const webSocketEndpoint: AdapterEndpoint = {
|
|
88
|
+
name: 'test',
|
|
89
|
+
transport: websocketTransport,
|
|
90
|
+
inputParameters,
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const CACHE_MAX_AGE = 1000
|
|
94
|
+
|
|
95
|
+
const adapter: Adapter = {
|
|
96
|
+
name: 'test',
|
|
97
|
+
defaultEndpoint: 'test',
|
|
98
|
+
endpoints: [webSocketEndpoint],
|
|
99
|
+
envDefaultOverrides: {
|
|
100
|
+
CACHE_MAX_AGE,
|
|
101
|
+
},
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
test('connects to websocket, subscribes, gets message, unsubscribes', async (t) => {
|
|
105
|
+
const clock = FakeTimers.install()
|
|
106
|
+
|
|
107
|
+
// Disable retries to make the testing flow easier
|
|
108
|
+
process.env['CACHE_POLLING_MAX_RETRIES'] = '0'
|
|
109
|
+
|
|
110
|
+
const base = 'ETH'
|
|
111
|
+
const quote = 'DOGE'
|
|
112
|
+
const price = 251324
|
|
113
|
+
|
|
114
|
+
// Mock WS
|
|
115
|
+
mockWebSocketProvider(WebSocketClassProvider)
|
|
116
|
+
const mockWsServer = new Server(URL, { mock: false })
|
|
117
|
+
mockWsServer.on('connection', (socket) => {
|
|
118
|
+
let counter = 0
|
|
119
|
+
const parseMessage = () => {
|
|
120
|
+
if (counter++ === 0) {
|
|
121
|
+
socket.send(
|
|
122
|
+
JSON.stringify({
|
|
123
|
+
pair: `${base}/${quote}`,
|
|
124
|
+
value: price,
|
|
125
|
+
}),
|
|
126
|
+
)
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
socket.on('message', parseMessage)
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
// Create mocked cache so we can listen when values are set
|
|
133
|
+
// This is a more reliable method than expecting precise clock timings
|
|
134
|
+
const mockCache = new MockCache()
|
|
135
|
+
|
|
136
|
+
// Start up adapter
|
|
137
|
+
const server = await expose(adapter, {
|
|
138
|
+
cache: mockCache,
|
|
139
|
+
})
|
|
140
|
+
const address = `http://localhost:${(server?.address() as AddressInfo)?.port}`
|
|
141
|
+
|
|
142
|
+
const makeRequest = () =>
|
|
143
|
+
axios.post(address, {
|
|
144
|
+
data: {
|
|
145
|
+
base,
|
|
146
|
+
quote,
|
|
147
|
+
},
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
// Expect the first response to time out
|
|
151
|
+
// The polling behavior is tested in the cache tests, so this is easier here.
|
|
152
|
+
// Start the request:
|
|
153
|
+
const errorPromise: Promise<AxiosError | undefined> = t.throwsAsync(makeRequest)
|
|
154
|
+
// Advance enough time for the initial request async flow
|
|
155
|
+
clock.tickAsync(10)
|
|
156
|
+
// Wait for the failed cache get -> instant 504
|
|
157
|
+
const error = await errorPromise
|
|
158
|
+
t.is(error?.response?.status, 504)
|
|
159
|
+
|
|
160
|
+
// Advance clock so that the batch warmer executes once again and wait for the cache to be set
|
|
161
|
+
const cacheValueSetPromise = mockCache.waitForNextSet()
|
|
162
|
+
await clock.tickAsync(DEFAULT_SHARED_MS_BETWEEN_REQUESTS + 10)
|
|
163
|
+
await cacheValueSetPromise
|
|
164
|
+
|
|
165
|
+
// Second request should find the response in the cache
|
|
166
|
+
const response = await makeRequest()
|
|
167
|
+
|
|
168
|
+
t.is(response.status, 200)
|
|
169
|
+
t.deepEqual(response.data, {
|
|
170
|
+
data: null,
|
|
171
|
+
result: price,
|
|
172
|
+
statusCode: 200,
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
// Wait until the cache expires, and the subscription is out
|
|
176
|
+
await clock.tickAsync(Math.ceil(CACHE_MAX_AGE / DEFAULT_WS_TTL) * DEFAULT_WS_TTL + 1)
|
|
177
|
+
|
|
178
|
+
// Now that the cache is out and the subscription no longer there, this should time out
|
|
179
|
+
const error2: AxiosError | undefined = await t.throwsAsync(makeRequest)
|
|
180
|
+
t.is(error2?.response?.status, 504)
|
|
181
|
+
|
|
182
|
+
clock.uninstall()
|
|
183
|
+
})
|
package/test/util.ts
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { InstalledClock } from '@sinonjs/fake-timers'
|
|
2
|
+
import { AdapterDependencies } from '../src/adapter'
|
|
3
|
+
import { LocalCache } from '../src/cache'
|
|
4
|
+
import { SettingsMap } from '../src/config'
|
|
5
|
+
import { Transport } from '../src/transports'
|
|
6
|
+
import { AdapterRequest, AdapterResponse } from '../src/util'
|
|
7
|
+
|
|
8
|
+
export class NopTransport implements Transport<null, null, SettingsMap> {
|
|
9
|
+
async initialize(dependencies: AdapterDependencies): Promise<void> {
|
|
10
|
+
return
|
|
11
|
+
}
|
|
12
|
+
async hasBeenSetUp(): Promise<boolean> {
|
|
13
|
+
return false
|
|
14
|
+
}
|
|
15
|
+
async setup(req: AdapterRequest<unknown>): Promise<void | AdapterResponse<null>> {
|
|
16
|
+
return
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
type Resolve<T> = (value: T) => void
|
|
21
|
+
export const deferredPromise = <T>(): [Promise<T>, Resolve<T>] => {
|
|
22
|
+
let resolve!: Resolve<T>
|
|
23
|
+
const promise = new Promise<T>((r) => {
|
|
24
|
+
resolve = r
|
|
25
|
+
})
|
|
26
|
+
return [promise, resolve]
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export class MockCache extends LocalCache {
|
|
30
|
+
private awaitingPromiseResolve?: (value: unknown) => void
|
|
31
|
+
|
|
32
|
+
waitForNextSet() {
|
|
33
|
+
// eslint-disable-next-line no-promise-executor-return
|
|
34
|
+
return new Promise((resolve) => (this.awaitingPromiseResolve = resolve))
|
|
35
|
+
}
|
|
36
|
+
override async set(key: string, value: unknown, ttl: number): Promise<void> {
|
|
37
|
+
super.set(key, value, ttl)
|
|
38
|
+
if (this.awaitingPromiseResolve) {
|
|
39
|
+
this.awaitingPromiseResolve(value)
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export async function runPeriodicAsyncBackgroundExecution(
|
|
45
|
+
clock: InstalledClock,
|
|
46
|
+
{
|
|
47
|
+
interval,
|
|
48
|
+
times,
|
|
49
|
+
stepValidation,
|
|
50
|
+
}: {
|
|
51
|
+
interval: number
|
|
52
|
+
times: number
|
|
53
|
+
stepValidation: (iteration: number) => boolean
|
|
54
|
+
},
|
|
55
|
+
) {
|
|
56
|
+
for (let i = 0; i < times; i++) {
|
|
57
|
+
// Tick once for the interval
|
|
58
|
+
await clock.tickAsync(interval)
|
|
59
|
+
|
|
60
|
+
// Then use auxiliary method to ensure that the background process, if it
|
|
61
|
+
// spawns new timers, gets executed to completion
|
|
62
|
+
await runAllUntil(clock, () => stepValidation(i))
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async function runAllUntil(clock: InstalledClock, isComplete: () => boolean): Promise<void> {
|
|
67
|
+
while (!isComplete()) {
|
|
68
|
+
await clock.nextAsync()
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export async function runAllUntilTime(clock: InstalledClock, time: number): Promise<void> {
|
|
73
|
+
const targetTime = clock.now + time
|
|
74
|
+
while (clock.now < targetTime) {
|
|
75
|
+
await clock.nextAsync()
|
|
76
|
+
}
|
|
77
|
+
}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import untypedTest, { TestFn } from 'ava'
|
|
2
|
+
import axios, { AxiosError } from 'axios'
|
|
3
|
+
import { AddressInfo } from 'net'
|
|
4
|
+
import { expose } from '../src'
|
|
5
|
+
import { Adapter, AdapterEndpoint } from '../src/adapter'
|
|
6
|
+
import { AdapterResponse } from '../src/util'
|
|
7
|
+
import { NopTransport } from './util'
|
|
8
|
+
|
|
9
|
+
const test = untypedTest as TestFn<{
|
|
10
|
+
serverAddress: string
|
|
11
|
+
adapterEndpoint: AdapterEndpoint
|
|
12
|
+
}>
|
|
13
|
+
|
|
14
|
+
test.beforeEach(async (t) => {
|
|
15
|
+
const adapter: Adapter = {
|
|
16
|
+
name: 'test',
|
|
17
|
+
endpoints: [
|
|
18
|
+
{
|
|
19
|
+
name: 'test',
|
|
20
|
+
inputParameters: {},
|
|
21
|
+
transport: new (class extends NopTransport {
|
|
22
|
+
override async setup(): Promise<void | AdapterResponse<null>> {
|
|
23
|
+
return {
|
|
24
|
+
data: null,
|
|
25
|
+
statusCode: 200,
|
|
26
|
+
result: null,
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
})(),
|
|
30
|
+
},
|
|
31
|
+
],
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
t.context.adapterEndpoint = adapter.endpoints[0]
|
|
35
|
+
const server = await expose(adapter)
|
|
36
|
+
if (!server) {
|
|
37
|
+
throw 'Server did not start'
|
|
38
|
+
}
|
|
39
|
+
t.context.serverAddress = `http://localhost:${(server.address() as AddressInfo).port}`
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* NOTE: The tests here are run serially, because to avoid setting up and tearing down the adapter
|
|
44
|
+
* for each one, we're just modifying the inputParameters and sending a new request every time
|
|
45
|
+
*/
|
|
46
|
+
test.serial('any content-type other than application/json throws 400', async (t) => {
|
|
47
|
+
t.context.adapterEndpoint.inputParameters = {}
|
|
48
|
+
|
|
49
|
+
const error: AxiosError | undefined = await t.throwsAsync(() =>
|
|
50
|
+
axios.post(`${t.context.serverAddress}`, 'test string', {
|
|
51
|
+
headers: {
|
|
52
|
+
'Content-Type': 'text/plain',
|
|
53
|
+
},
|
|
54
|
+
}),
|
|
55
|
+
)
|
|
56
|
+
t.is(error?.response?.status, 400)
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
test.serial('no body in request throws 400', async (t) => {
|
|
60
|
+
t.context.adapterEndpoint.inputParameters = {}
|
|
61
|
+
|
|
62
|
+
const error: AxiosError | undefined = await t.throwsAsync(() =>
|
|
63
|
+
axios.post(`${t.context.serverAddress}`, '', {
|
|
64
|
+
headers: {
|
|
65
|
+
'Content-Type': 'application/json',
|
|
66
|
+
},
|
|
67
|
+
}),
|
|
68
|
+
)
|
|
69
|
+
t.is(error?.response?.status, 400)
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
test.serial('invalid endpoint name throws 404', async (t) => {
|
|
73
|
+
t.context.adapterEndpoint.inputParameters = {}
|
|
74
|
+
|
|
75
|
+
const error: AxiosError | undefined = await t.throwsAsync(() =>
|
|
76
|
+
axios.post(`${t.context.serverAddress}`, {
|
|
77
|
+
endpoint: 'random',
|
|
78
|
+
}),
|
|
79
|
+
)
|
|
80
|
+
t.is(error?.response?.status, 404)
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
test.serial('no endpoint without default throws 400', async (t) => {
|
|
84
|
+
t.context.adapterEndpoint.inputParameters = {}
|
|
85
|
+
|
|
86
|
+
const error: AxiosError | undefined = await t.throwsAsync(() =>
|
|
87
|
+
axios.post(`${t.context.serverAddress}`, {
|
|
88
|
+
data: {},
|
|
89
|
+
}),
|
|
90
|
+
)
|
|
91
|
+
t.is(error?.response?.status, 400)
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
test.serial('endpoint in data object', async (t) => {
|
|
95
|
+
t.context.adapterEndpoint.inputParameters = {}
|
|
96
|
+
|
|
97
|
+
const response = await axios.post(`${t.context.serverAddress}`, {
|
|
98
|
+
data: { endpoint: 'test' },
|
|
99
|
+
})
|
|
100
|
+
t.is(response.status, 200)
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
test.serial('no params returns 200', async (t) => {
|
|
104
|
+
t.context.adapterEndpoint.inputParameters = {}
|
|
105
|
+
|
|
106
|
+
const response = await axios.post(`${t.context.serverAddress}`, {
|
|
107
|
+
data: {},
|
|
108
|
+
endpoint: 'test',
|
|
109
|
+
})
|
|
110
|
+
t.is(response.status, 200)
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
test.serial('missing required param throws 400', async (t) => {
|
|
114
|
+
t.context.adapterEndpoint.inputParameters = {
|
|
115
|
+
base: {
|
|
116
|
+
type: 'string',
|
|
117
|
+
required: true,
|
|
118
|
+
},
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const error: AxiosError | undefined = await t.throwsAsync(() =>
|
|
122
|
+
axios.post(`${t.context.serverAddress}`, {
|
|
123
|
+
data: {},
|
|
124
|
+
}),
|
|
125
|
+
)
|
|
126
|
+
t.is(error?.response?.status, 400)
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
test.serial('wrongly typed param throws 400', async (t) => {
|
|
130
|
+
t.context.adapterEndpoint.inputParameters = {
|
|
131
|
+
base: {
|
|
132
|
+
type: 'string',
|
|
133
|
+
required: true,
|
|
134
|
+
},
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const error: AxiosError | undefined = await t.throwsAsync(() =>
|
|
138
|
+
axios.post(`${t.context.serverAddress}`, {
|
|
139
|
+
data: {
|
|
140
|
+
base: 123,
|
|
141
|
+
},
|
|
142
|
+
endpoint: 'test',
|
|
143
|
+
}),
|
|
144
|
+
)
|
|
145
|
+
t.is(error?.response?.status, 400)
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
test.serial('correctly typed param returns 200', async (t) => {
|
|
149
|
+
t.context.adapterEndpoint.inputParameters = {
|
|
150
|
+
base: {
|
|
151
|
+
type: 'string',
|
|
152
|
+
required: true,
|
|
153
|
+
},
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const response = await axios.post(`${t.context.serverAddress}`, {
|
|
157
|
+
data: {
|
|
158
|
+
base: 'asd',
|
|
159
|
+
},
|
|
160
|
+
endpoint: 'test',
|
|
161
|
+
})
|
|
162
|
+
t.is(response.status, 200)
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
test.serial('omitted optional param returns 200', async (t) => {
|
|
166
|
+
t.context.adapterEndpoint.inputParameters = {
|
|
167
|
+
base: {
|
|
168
|
+
type: 'string',
|
|
169
|
+
required: false,
|
|
170
|
+
},
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const response = await axios.post(`${t.context.serverAddress}`, {
|
|
174
|
+
data: {},
|
|
175
|
+
endpoint: 'test',
|
|
176
|
+
})
|
|
177
|
+
t.is(response.status, 200)
|
|
178
|
+
})
|
package/test.sh
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# for i in {1..100}; do
|
|
2
|
+
# if (( $i % 50 == 0 )); then
|
|
3
|
+
# echo "sent $i"
|
|
4
|
+
# fi
|
|
5
|
+
# curl -XPOST http://localhost:8080 -s \
|
|
6
|
+
# -H 'Content-Type: application/json' \
|
|
7
|
+
# -d '{"endpoint":"test", "data": {"base": "ethereum", "quote": "usd"}}' > /tmp/null &
|
|
8
|
+
# sleep 0.1
|
|
9
|
+
# done
|
|
10
|
+
|
|
11
|
+
# wait
|
|
12
|
+
|
|
13
|
+
# echo "done"
|
|
14
|
+
|
|
15
|
+
curl -XPOST http://localhost:8080 -s \
|
|
16
|
+
-H 'Content-Type: application/json' \
|
|
17
|
+
-d '{"endpoint":"batch", "data": {"base": "ethereum", "quote": "usd"}}' > /tmp/null &
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
wait
|
package/test2.sh
ADDED
package/tsconfig.json
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"outDir": "dist",
|
|
4
|
+
"rootDir": ".",
|
|
5
|
+
"target": "es2020",
|
|
6
|
+
"esModuleInterop": true,
|
|
7
|
+
"moduleResolution": "node",
|
|
8
|
+
"module": "CommonJS",
|
|
9
|
+
"resolveJsonModule": true,
|
|
10
|
+
|
|
11
|
+
// Type checking
|
|
12
|
+
"allowUnreachableCode": false,
|
|
13
|
+
"allowUnusedLabels": false,
|
|
14
|
+
"allowJs": true,
|
|
15
|
+
"alwaysStrict": true,
|
|
16
|
+
"noImplicitAny": true,
|
|
17
|
+
"noImplicitOverride": true,
|
|
18
|
+
"noImplicitReturns": false,
|
|
19
|
+
"noImplicitThis": true,
|
|
20
|
+
"noPropertyAccessFromIndexSignature": true,
|
|
21
|
+
"noUnusedLocals": true,
|
|
22
|
+
"strict": true,
|
|
23
|
+
"sourceMap": true,
|
|
24
|
+
// "declaration": true
|
|
25
|
+
},
|
|
26
|
+
"exclude": ["src/**/test/**/*"],
|
|
27
|
+
"include": ["./src/**/*"]
|
|
28
|
+
}
|
package/typedoc.json
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
const path = require('path')
|
|
2
|
+
const glob = require('glob')
|
|
3
|
+
|
|
4
|
+
module.exports = {
|
|
5
|
+
target: 'node',
|
|
6
|
+
// entry: './src/index.ts',
|
|
7
|
+
// entry: glob.sync('./src/**/*.test.{ts,tsx}').reduce((acc, file) => {
|
|
8
|
+
// acc[file.replace(/^\.\/src\//, '')] = file
|
|
9
|
+
// return acc
|
|
10
|
+
// }, {}),
|
|
11
|
+
mode: 'development', // This shouldn't be used in production, but is fine for now since the pkg is in active development.
|
|
12
|
+
devtool: 'inline-source-map', // This shouldn't be used in production, but is fine for now since the pkg is in active development.
|
|
13
|
+
module: {
|
|
14
|
+
rules: [
|
|
15
|
+
{
|
|
16
|
+
test: /\.tsx?$/,
|
|
17
|
+
use: 'ts-loader',
|
|
18
|
+
exclude: /node_modules/,
|
|
19
|
+
},
|
|
20
|
+
{ test: /\.json$/, type: 'json' },
|
|
21
|
+
// { test: /\.js$/, type: 'js' },
|
|
22
|
+
],
|
|
23
|
+
},
|
|
24
|
+
resolve: {
|
|
25
|
+
extensions: ['.tsx', '.ts', '.js', '.json'],
|
|
26
|
+
fallback: {
|
|
27
|
+
// buffer: require.resolve('buffer'),
|
|
28
|
+
// https: require.resolve('https-browserify'),
|
|
29
|
+
// assert: require.resolve('assert'),
|
|
30
|
+
// http: require.resolve('stream-http'),
|
|
31
|
+
// querystring: require.resolve('querystring-es3'),
|
|
32
|
+
// url: require.resolve('url'),
|
|
33
|
+
// stream: require.resolve('stream-browserify'),
|
|
34
|
+
// crypto: require.resolve('crypto-browserify'),
|
|
35
|
+
// path: require.resolve('path-browserify'),
|
|
36
|
+
// os: require.resolve('os-browserify/browser'),
|
|
37
|
+
// process: require.resolve('process/browser'),
|
|
38
|
+
// fs: false,
|
|
39
|
+
// tls: false,
|
|
40
|
+
long: false,
|
|
41
|
+
},
|
|
42
|
+
// alias: {
|
|
43
|
+
// 'node:async_hooks': 'async_hooks',
|
|
44
|
+
// },
|
|
45
|
+
},
|
|
46
|
+
output: {
|
|
47
|
+
// filename: 'bundle.js',
|
|
48
|
+
path: path.resolve(__dirname, 'dist'),
|
|
49
|
+
// filename: '[name].[contenthash].js',
|
|
50
|
+
filename: '[name].js',
|
|
51
|
+
},
|
|
52
|
+
optimization: {
|
|
53
|
+
splitChunks: {
|
|
54
|
+
chunks: 'all',
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
}
|