From 3294f89f194b32e09266467f578eb3fcdebcea02 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 26 Mar 2024 15:26:43 +0000 Subject: [PATCH 1/2] 0.33.7 (#252) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index fb19a9be..a7d2503c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@chainlink/external-adapter-framework", - "version": "0.33.6", + "version": "0.33.7", "main": "dist/index.js", "license": "MIT", "dependencies": { From 4cf52e051fbefcca79ab09a3dcd06fb54b29f27f Mon Sep 17 00:00:00 2001 From: karen-stepanyan <91897037+karen-stepanyan@users.noreply.github.com> Date: Thu, 11 Apr 2024 18:10:08 +0400 Subject: [PATCH 2/2] Added support for transport overrides (#253) * add support for transport overrides * lint * change transport override keyword --- src/adapter/endpoint.ts | 10 +- src/util/types.ts | 5 +- src/validation/index.ts | 2 +- test/transports/routing.test.ts | 203 ++++++++++++++++++++++++++++---- 4 files changed, 194 insertions(+), 26 deletions(-) diff --git a/src/adapter/endpoint.ts b/src/adapter/endpoint.ts index 7eed1d7d..af880b5b 100644 --- a/src/adapter/endpoint.ts +++ b/src/adapter/endpoint.ts @@ -170,13 +170,19 @@ export class AdapterEndpoint implements AdapterEndpo } /** - * Default routing strategy. Will try and use the transport input parameter if present in the request body. + * Default routing strategy. Will try and use the transport override if present + * or transport input parameter in the request body. * * @param req - The current adapter request - * @returns the transport param if present + * @returns the transport param or override if present */ private defaultRouter(req: AdapterRequest>) { const rawRequestBody = req.body as unknown as { data: AdapterRequestData } + const requestOverrides = rawRequestBody.data?.overrides?.[this.adapterName.toLowerCase()] + // Transport override + if (requestOverrides?.['transport']) { + return requestOverrides['transport'] + } return rawRequestBody.data?.transport } } diff --git a/src/util/types.ts b/src/util/types.ts index e7e1a0f6..f88bc633 100644 --- a/src/util/types.ts +++ b/src/util/types.ts @@ -33,7 +33,7 @@ export type AdapterRequestContext = { /** Name of the endpoint this payload should be directed to */ endpointName: string - /** Name of the endpoint this payload should be directed to */ + /** Name of the transport this payload should be directed to */ transportName: string /** Precalculated cache key used to get and set corresponding values from the cache and subscription sets */ @@ -101,11 +101,12 @@ export interface AdapterMetricsMeta { } /** - * Map of overrides objects (symbol -\> symbol) per adapter name + * Map of overrides objects (symbol -\> symbol and request transport) per adapter name */ export type Overrides = { [adapterName: string]: { [symbol: string]: string + transport: string } } diff --git a/src/validation/index.ts b/src/validation/index.ts index a2e584ab..0ff9ba7e 100644 --- a/src/validation/index.ts +++ b/src/validation/index.ts @@ -125,7 +125,7 @@ export const validatorMiddleware: AdapterMiddlewareBuilder = req.requestContext.cacheKey = `${cachePrefix}${cacheKey}` } else { - const transportName = endpoint.getTransportNameForRequest(req, adapter.config.settings) + const transportName = req.requestContext.transportName req.requestContext.cacheKey = calculateCacheKey({ data: req.requestContext.data, adapterName: adapter.name, diff --git a/test/transports/routing.test.ts b/test/transports/routing.test.ts index e884b490..df54f7de 100644 --- a/test/transports/routing.test.ts +++ b/test/transports/routing.test.ts @@ -2,7 +2,7 @@ import untypedTest, { TestFn } from 'ava' import axios, { AxiosRequestConfig, AxiosResponse } from 'axios' import MockAdapter from 'axios-mock-adapter' import { Server } from 'mock-socket' -import { Adapter, AdapterEndpoint, EndpointContext } from '../../src/adapter' +import { Adapter, AdapterEndpoint } from '../../src/adapter' import { AdapterConfig, SettingsDefinitionFromConfig, @@ -18,6 +18,8 @@ import { } from '../../src/transports' import { InputParameters } from '../../src/validation' import { TestAdapter, mockWebSocketProvider } from '../../src/util/testing-utils' +import { AdapterRequest } from '../../src/util' +import { TypeFromDefinition } from '../../src/validation/input-params' const test = untypedTest as TestFn<{ testAdapter: TestAdapter> @@ -88,7 +90,7 @@ const inputParameters = new InputParameters({ }) class MockWebSocketTransport extends WebSocketTransport { - public backgroundExecuteCalls = 0 + public registerRequestCalls = 0 constructor() { super({ @@ -123,9 +125,12 @@ class MockWebSocketTransport extends WebSocketTransport { }) } - override async backgroundExecute(context: EndpointContext): Promise { - this.backgroundExecuteCalls++ - return super.backgroundExecute(context) + override async registerRequest( + req: AdapterRequest>, + _: WebSocketTypes['Settings'], + ): Promise { + this.registerRequestCalls++ + return super.registerRequest(req, _) } } @@ -157,9 +162,11 @@ type HttpTypes = BaseEndpointTypes & { } class MockHttpTransport extends HttpTransport { - backgroundExecuteCalls = 0 + // Since backgroundExecute always runs for all compatible transports, regardless of the requests, + // we check for registered requests + registerRequestCalls = 0 - constructor(private callSuper = false) { + constructor() { super({ prepareRequests: (params) => { return { @@ -186,11 +193,12 @@ class MockHttpTransport extends HttpTransport { }) } - override async backgroundExecute(context: EndpointContext): Promise { - this.backgroundExecuteCalls++ - if (this.callSuper) { - super.backgroundExecute(context) - } + override async registerRequest( + req: AdapterRequest>, + _: HttpTypes['Settings'], + ): Promise { + this.registerRequestCalls++ + return super.registerRequest(req, _) } } @@ -204,7 +212,7 @@ type SSETypes = BaseEndpointTypes & { } class MockSseTransport extends SseTransport { - public backgroundExecuteCalls = 0 + public registerRequestCalls = 0 constructor() { super({ @@ -251,9 +259,12 @@ class MockSseTransport extends SseTransport { }) } - override async backgroundExecute(context: EndpointContext): Promise { - this.backgroundExecuteCalls++ - return super.backgroundExecute(context) + override async registerRequest( + req: AdapterRequest>, + _: SSETypes['Settings'], + ): Promise { + this.registerRequestCalls++ + return super.registerRequest(req, _) } } @@ -299,6 +310,15 @@ test.beforeEach(async (t) => { } }) +test.afterEach(() => { + const batchTransport = transports.get('batch') as unknown as { registerRequestCalls: number } + const wsTransport = transports.get('websocket') as unknown as { registerRequestCalls: number } + const sseTransport = transports.get('sse') as unknown as { registerRequestCalls: number } + batchTransport.registerRequestCalls = 0 + wsTransport.registerRequestCalls = 0 + sseTransport.registerRequestCalls = 0 +}) + test.serial('endpoint routing errors on invalid transport', async (t) => { t.is( Object.keys(transports).find((s) => s === 'INVALID'), @@ -340,7 +360,7 @@ test.serial('endpoint routing can route to HttpTransport', async (t) => { t.is(error.statusCode, 504) const internalTransport = transports.get('batch') as unknown as MockHttpTransport - t.assert(internalTransport.backgroundExecuteCalls > 0) + t.assert(internalTransport.registerRequestCalls > 0) }) test.serial('endpoint routing can route to WebSocket transport', async (t) => { @@ -351,7 +371,7 @@ test.serial('endpoint routing can route to WebSocket transport', async (t) => { }) t.is(error?.statusCode, 504) const internalTransport = transports.get('websocket') as unknown as MockWebSocketTransport - t.assert(internalTransport.backgroundExecuteCalls > 0) + t.assert(internalTransport.registerRequestCalls > 0) }) test.serial('endpoint routing can route to SSE transport', async (t) => { @@ -377,7 +397,7 @@ test.serial('endpoint routing can route to SSE transport', async (t) => { t.is(error.statusCode, 504) const internalTransport = transports.get('sse') as unknown as MockSseTransport - t.assert(internalTransport.backgroundExecuteCalls > 0) + t.assert(internalTransport.registerRequestCalls > 0) }) test.serial('custom router is applied to get valid transport to route to', async (t) => { @@ -439,7 +459,7 @@ test.serial('custom router is applied to get valid transport to route to', async t.is(error.statusCode, 504) const internalTransport = transports.get('batch') as unknown as MockHttpTransport - t.assert(internalTransport.backgroundExecuteCalls > 0) + t.assert(internalTransport.registerRequestCalls > 0) }) test.serial('custom router returns invalid transport and request fails', async (t) => { @@ -624,7 +644,7 @@ test.serial('missing transport in input params with default succeeds', async (t) t.is(error.statusCode, 504) const internalTransport = transports.get('batch') as unknown as MockHttpTransport - t.assert(internalTransport.backgroundExecuteCalls > 0) + t.assert(internalTransport.registerRequestCalls > 0) }) test.serial('transport creation fails if transport names are not acceptable', async (t) => { @@ -658,3 +678,144 @@ test.serial('transports with same name throws error', async (t) => { { message: 'Transport with name "websocket" is already registered in this map' }, ) }) + +test.serial('transport override routes to correct Transport', async (t) => { + axiosMock + .onPost(`${restUrl}/price`, { + pairs: [ + { + base: from, + quote: to, + }, + ], + }) + .reply(200, { + prices: [ + { + pair: `${from}/${to}`, + price, + }, + ], + }) + + const error = await t.context.testAdapter.request({ + from, + to, + transport: 'websocket', + overrides: { + test: { + transport: 'batch', + }, + }, + }) + + t.is(error.statusCode, 504) + const internalTransport = transports.get('batch') as unknown as MockHttpTransport + t.assert(internalTransport.registerRequestCalls > 0) +}) + +test.serial('invalid transport override is skipped', async (t) => { + axiosMock + .onPost(`${restUrl}/price`, { + pairs: [ + { + base: from, + quote: to, + }, + ], + }) + .reply(200, { + prices: [ + { + pair: `${from}/${to}`, + price, + }, + ], + }) + + const error = await t.context.testAdapter.request({ + from, + to, + transport: 'websocket', + overrides: { + // Invalid adapter name + XXXX: { + transport: 'batch', + }, + }, + }) + + t.is(error.statusCode, 504) + const internalTransport = transports.get('websocket') as unknown as MockHttpTransport + t.assert(internalTransport.registerRequestCalls > 0) +}) + +test.serial( + 'transport and transport override are ignored when custom router returns a value', + async (t) => { + const endpoint = new AdapterEndpoint({ + inputParameters, + name: 'price', // /price + transportRoutes: transports, + customRouter: () => 'batch', + }) + + const customConfig = new AdapterConfig(settings, { + envDefaultOverrides: { + LOG_LEVEL: 'debug', + METRICS_ENABLED: false, + CACHE_POLLING_SLEEP_MS: 10, + CACHE_POLLING_MAX_RETRIES: 0, + }, + }) + + const adapter = new Adapter({ + name: 'TEST', + defaultEndpoint: 'price', + config: customConfig, + endpoints: [endpoint], + rateLimiting: { + tiers: { + default: { + rateLimit1s: 5, + }, + }, + }, + }) + + const testAdapter = await TestAdapter.start(adapter, t.context) + + axiosMock + .onPost(`${restUrl}/price`, { + pairs: [ + { + base: from, + quote: to, + }, + ], + }) + .reply(200, { + prices: [ + { + pair: `${from}/${to}`, + price, + }, + ], + }) + + const error = await testAdapter.request({ + from, + to, + transport: 'sse', + overrides: { + test: { + transport: 'websocket', + }, + }, + }) + t.is(error.statusCode, 504) + + const internalTransport = transports.get('batch') as unknown as MockHttpTransport + t.assert(internalTransport.registerRequestCalls > 0) + }, +)