Skip to content

Commit aa3635a

Browse files
committed
Add generate unit test for generated customBg transport
1 parent 2ed16d0 commit aa3635a

5 files changed

Lines changed: 400 additions & 26 deletions

File tree

scripts/generator-adapter/generators/app/index.ts

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,7 @@ export default class extends Generator.default {
221221
this.fs.copyTpl(
222222
this.templatePath(`test/integration/adapter.test.ts.ejs`),
223223
this.destinationPath(`${this.args[0]}/${this.props.adapterName}/test/integration/adapter.test.ts`),
224-
{ endpoints: httpEndpoints, transportName: 'rest', setBgExecuteMsEnv: false },
224+
{ endpoints: httpEndpoints, transportName: 'rest', setBgExecuteMsEnv: false, mockPostResponse: false },
225225
)
226226

227227
// Create http unit test files
@@ -270,9 +270,11 @@ export default class extends Generator.default {
270270
this.fs.copyTpl(
271271
this.templatePath(`test/integration/adapter.test.ts.ejs`),
272272
this.destinationPath(`${this.args[0]}/${this.props.adapterName}/test/integration/${fileName}`),
273-
{ endpoints: customFgEndpoints, transportName: 'customfg', setBgExecuteMsEnv: false },
273+
{ endpoints: customFgEndpoints, transportName: 'customfg', setBgExecuteMsEnv: false, mockPostResponse: false },
274274
)
275275
}
276+
277+
// Create adapter.test.ts or adapter-custombg.test.ts if there is at least one endpoint with customBgTransport
276278
if (customBgEndpoints.length) {
277279
let fileName = 'adapter.test.ts'
278280
if (httpEndpoints.length || wsEndpoints.length || customFgEndpoints.length) {
@@ -281,8 +283,18 @@ export default class extends Generator.default {
281283
this.fs.copyTpl(
282284
this.templatePath(`test/integration/adapter.test.ts.ejs`),
283285
this.destinationPath(`${this.args[0]}/${this.props.adapterName}/test/integration/${fileName}`),
284-
{ endpoints: customBgEndpoints, transportName: 'custombg', setBgExecuteMsEnv: true },
286+
{ endpoints: customBgEndpoints, transportName: 'custombg', setBgExecuteMsEnv: true, mockPostResponse: true },
285287
)
288+
289+
// Create customBg unit test files
290+
for (const { inputEndpointName, normalizedEndpointNameCap, inputTransports } of customBgEndpoints) {
291+
const transportFileBaseName = inputTransports.length > 1 ? `${inputEndpointName}-custombg` : inputEndpointName
292+
this.fs.copyTpl(
293+
this.templatePath(`test/unit/custombg.test.ts.ejs`),
294+
this.destinationPath(`${this.args[0]}/${this.props.adapterName}/test/unit/${transportFileBaseName}.test.ts`),
295+
{ inputEndpointName, normalizedEndpointNameCap, transportFileBaseName },
296+
)
297+
}
286298
}
287299

288300
// Copy test fixtures
@@ -291,7 +303,8 @@ export default class extends Generator.default {
291303
this.destinationPath(`${this.args[0]}/${this.props.adapterName}/test/integration/fixtures.ts`),
292304
{
293305
includeWsFixtures: wsEndpoints.length > 0,
294-
includeHttpFixtures: httpEndpoints.length > 0 || customBgEndpoints.length > 0 || customFgEndpoints.length > 0,
306+
includeHttpFixtures: httpEndpoints.length > 0 || customFgEndpoints.length > 0,
307+
includeCustomBgFixtures: customBgEndpoints.length > 0
295308
},
296309
)
297310

Lines changed: 72 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { TransportDependencies } from '@chainlink/external-adapter-framework/transports'
2+
import { calculateHttpRequestKey } from '@chainlink/external-adapter-framework/cache'
23
import { ResponseCache } from '@chainlink/external-adapter-framework/cache/response'
34
import { Requester } from '@chainlink/external-adapter-framework/util/requester'
45
import {
@@ -9,34 +10,48 @@ import { EndpointContext } from '@chainlink/external-adapter-framework/adapter'
910
import { BaseEndpointTypes, inputParameters } from '../endpoint/<%= inputEndpointName %>'
1011
import { AdapterError } from '@chainlink/external-adapter-framework/validation/error'
1112

12-
const logger = makeLogger('CustomTransport')
13+
const logger = makeLogger('<%= normalizedEndpointNameCap %>Transport')
1314

1415
type RequestParams = typeof inputParameters.validated
1516

17+
type PriceResponse = {
18+
[symbol: string]: {
19+
price: number
20+
}
21+
}
22+
1623
<% if (includeComments) { -%>
17-
// CustomTransport extends base types from endpoint and adds additional, Provider-specific types (if needed).
24+
// <%= normalizedEndpointNameCap %>Transport extends base types from endpoint and adds additional, Provider-specific types (if needed).
1825
<% } -%>
19-
export type CustomTransportTypes = BaseEndpointTypes & {
26+
export type <%= normalizedEndpointNameCap %>TransportTypes = BaseEndpointTypes & {
2027
Provider: {
2128
RequestBody: never
2229
ResponseBody: any
2330
}
2431
}
2532
<% if (includeComments) { -%>
26-
// CustomTransport is used to perform custom data fetching and processing from a Provider. The framework provides built-in transports to
27-
// fetch data from a Provider using several protocols, including `http`, `websocket`, and `sse`. Use CustomTransport when the Provider uses
33+
// <%= normalizedEndpointNameCap %>Transport is used to perform custom data fetching and processing from a Provider. The framework provides built-in transports to
34+
// fetch data from a Provider using several protocols, including `http`, `websocket`, and `sse`. Use <%= normalizedEndpointNameCap %>Transport when the Provider uses
2835
// different protocol, or you need custom functionality that built-in transports don't support. For example, custom, multistep authentication
2936
// for requests, paginated requests, on-chain data retrieval using third party libraries, and so on.
3037
<% } -%>
31-
export class CustomTransport extends SubscriptionTransport<CustomTransportTypes> {
38+
export class <%= normalizedEndpointNameCap %>Transport extends SubscriptionTransport<<%= normalizedEndpointNameCap %>TransportTypes> {
3239
<% if (includeComments) { -%>
3340
// name of the transport, used for logging
3441
<% } -%>
3542
name!: string
43+
<% if (includeComments) { -%>
44+
// name of the endpoint, used to calculate the request key
45+
<% } -%>
46+
endpointName!: string
47+
<% if (includeComments) { -%>
48+
// instance of adapter config
49+
<% } -%>
50+
config!: BaseEndpointTypes['Settings']
3651
<% if (includeComments) { -%>
3752
// cache instance for caching responses from provider
3853
<% } -%>
39-
responseCache!: ResponseCache<CustomTransportTypes>
54+
responseCache!: ResponseCache<<%= normalizedEndpointNameCap %>TransportTypes>
4055
<% if (includeComments) { -%>
4156
// instance of Requester to be used for data fetching. Use this instance to perform http calls
4257
<% } -%>
@@ -46,22 +61,24 @@ export class CustomTransport extends SubscriptionTransport<CustomTransportTypes>
4661
// REQUIRED. Transport will be automatically initialized by the framework using this method. It will be called with transport
4762
// dependencies, adapter settings, endpoint name, and transport name as arguments. Use this method to initialize transport state
4863
<% } -%>
49-
async initialize(dependencies: TransportDependencies<CustomTransportTypes>, adapterSettings: CustomTransportTypes['Settings'], endpointName: string, transportName: string): Promise<void> {
64+
async initialize(dependencies: TransportDependencies<<%= normalizedEndpointNameCap %>TransportTypes>, adapterSettings: <%= normalizedEndpointNameCap %>TransportTypes['Settings'], endpointName: string, transportName: string): Promise<void> {
5065
await super.initialize(dependencies, adapterSettings, endpointName, transportName)
66+
this.config = adapterSettings
67+
this.endpointName = endpointName
5168
this.requester = dependencies.requester
5269
}
5370
<% if (includeComments) { -%>
5471
// 'backgroundHandler' is called on each background execution iteration. It receives endpoint context as first argument
5572
// and an array of all the entries in the subscription set as second argument. Use this method to handle the incoming
5673
// request, process it and save it in the cache.
5774
<% } -%>
58-
async backgroundHandler(context: EndpointContext<CustomTransportTypes>, entries: RequestParams[]) {
75+
async backgroundHandler(context: EndpointContext<<%= normalizedEndpointNameCap %>TransportTypes>, entries: RequestParams[]) {
5976
await Promise.all(entries.map(async (param) => this.handleRequest(param)))
6077
await sleep(context.adapterSettings.BACKGROUND_EXECUTE_MS)
6178
}
6279

6380
async handleRequest(param: RequestParams) {
64-
let response: AdapterResponse<CustomTransportTypes['Response']>
81+
let response: AdapterResponse<<%= normalizedEndpointNameCap %>TransportTypes['Response']>
6582
try {
6683
response = await this._handleRequest(param)
6784
} catch (e) {
@@ -81,19 +98,56 @@ export class CustomTransport extends SubscriptionTransport<CustomTransportTypes>
8198
}
8299

83100
async _handleRequest(
84-
_: RequestParams,
85-
): Promise<AdapterResponse<CustomTransportTypes['Response']>> {
101+
params: RequestParams,
102+
): Promise<AdapterResponse<<%= normalizedEndpointNameCap %>TransportTypes['Response']>> {
86103

87-
const providerDataRequestedUnixMs = Date.now()
104+
const providerDataRequestedUnixMs = Date.now()
88105

89-
// custom transport logic
106+
// custom transport logic
107+
108+
const requestConfig = {
109+
method: 'POST',
110+
baseURL: this.config.API_ENDPOINT,
111+
url: '/cryptocurrency/price',
112+
headers: {
113+
'X_API_KEY': this.config.API_KEY,
114+
},
115+
data: {
116+
symbol: params.base.toUpperCase(),
117+
convert: params.quote.toUpperCase(),
118+
},
119+
}
120+
121+
const response = await this.requester.request<PriceResponse>(
122+
calculateHttpRequestKey<BaseEndpointTypes>({
123+
context: {
124+
adapterSettings: this.config,
125+
inputParameters,
126+
endpointName: this.endpointName,
127+
},
128+
data: requestConfig.data,
129+
transportName: this.name,
130+
}),
131+
requestConfig,
132+
)
133+
134+
const data = response.response.data
135+
const baseSymbol = params.base.toUpperCase()
136+
const result = data[baseSymbol]?.price
137+
138+
if (result === undefined) {
139+
throw new AdapterError({
140+
statusCode: 502,
141+
message: `The data provider didn't return any value for ${params.base}/${params.quote}`,
142+
})
143+
}
90144

91145
return {
92146
data: {
93-
result: 2000,
147+
result,
94148
},
95149
statusCode: 200,
96-
result: 2000,
150+
result,
97151
timestamps: {
98152
providerDataRequestedUnixMs,
99153
providerDataReceivedUnixMs: Date.now(),
@@ -102,9 +156,9 @@ export class CustomTransport extends SubscriptionTransport<CustomTransportTypes>
102156
}
103157
}
104158

105-
getSubscriptionTtlFromConfig(adapterSettings: CustomTransportTypes['Settings']): number {
159+
getSubscriptionTtlFromConfig(adapterSettings: <%= normalizedEndpointNameCap %>TransportTypes['Settings']): number {
106160
return adapterSettings.WARMUP_SUBSCRIPTION_TTL
107161
}
108162
}
109163

110-
export const customSubscriptionTransport = new CustomTransport()
164+
export const customSubscriptionTransport = new <%= normalizedEndpointNameCap %>Transport()

scripts/generator-adapter/generators/app/templates/test/integration/adapter.test.ts.ejs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,11 @@ import {
33
setEnvVariables,
44
} from '@chainlink/external-adapter-framework/util/testing-utils'
55
import * as nock from 'nock'
6+
<% if (mockPostResponse) { %>
7+
import { mockPostResponseSuccess } from './fixtures'
8+
<% } else { %>
69
import { mockResponseSuccess } from './fixtures'
10+
<% } %>
711

812
describe('execute', () => {
913
let spy: jest.SpyInstance
@@ -41,11 +45,15 @@ describe('execute', () => {
4145
endpoint: '<%= endpoints[i].inputEndpointName %>',
4246
transport: '<%= transportName %>'
4347
}
48+
<% if (mockPostResponse) { %>
49+
mockPostResponseSuccess()
50+
<% } else { %>
4451
mockResponseSuccess()
52+
<% } %>
4553
const response = await testAdapter.request(data)
46-
expect(response.statusCode).toBe(200)
4754
expect(response.json()).toMatchSnapshot()
55+
expect(response.statusCode).toBe(200)
4856
})
4957
})
50-
<% } %>
58+
<% } %>
5159
})

scripts/generator-adapter/generators/app/templates/test/integration/fixtures.ts.ejs

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<% if (includeHttpFixtures) { %>import nock from 'nock'<% } %>
1+
<% if (includeHttpFixtures || includeCustomBgFixtures) { %>import nock from 'nock'<% } %>
22
<% if (includeWsFixtures) { %>import { MockWebsocketServer } from '@chainlink/external-adapter-framework/util/testing-utils'<% } %>
33
<% if (includeHttpFixtures) { %>
44
export const mockResponseSuccess = (): nock.Scope =>
@@ -22,6 +22,27 @@ export const mockResponseSuccess = (): nock.Scope =>
2222
])
2323
.persist()
2424
<% } %>
25+
<% if (includeCustomBgFixtures) { %>
26+
export const mockPostResponseSuccess = (): nock.Scope =>
27+
nock('https://dataproviderapi.com', {
28+
encodedQueryParams: true,
29+
})
30+
.post('/cryptocurrency/price', {
31+
symbol: 'ETH',
32+
convert: 'USD',
33+
})
34+
.reply(200, () => ({ ETH: { price: 10000 } }), [
35+
'Content-Type',
36+
'application/json',
37+
'Connection',
38+
'close',
39+
'Vary',
40+
'Accept-Encoding',
41+
'Vary',
42+
'Origin',
43+
])
44+
.persist()
45+
<% } %>
2546
<% if (includeWsFixtures) { %>
2647
export const mockWebsocketServer = (URL: string): MockWebsocketServer => {
2748
const mockWsServer = new MockWebsocketServer(URL, { mock: false })
@@ -41,4 +62,4 @@ export const mockWebsocketServer = (URL: string): MockWebsocketServer => {
4162
4263
return mockWsServer
4364
}
44-
<% } %>
65+
<% } %>

0 commit comments

Comments
 (0)