From 63570807494669c4e77283d4e094499f41908c44 Mon Sep 17 00:00:00 2001 From: "ea-generator[bot]" Date: Tue, 24 Feb 2026 03:01:34 +0000 Subject: [PATCH] feat(opdata-5278): generated EA --- .pnp.cjs | 24 ++- package.json | 5 + packages/sources/accountable/CHANGELOG.md | 0 packages/sources/accountable/README.md | 3 + packages/sources/accountable/package.json | 42 +++++ .../sources/accountable/src/config/index.ts | 15 ++ .../accountable/src/config/overrides.json | 3 + .../sources/accountable/src/endpoint/index.ts | 1 + .../accountable/src/endpoint/reserves.ts | 32 ++++ packages/sources/accountable/src/index.ts | 21 +++ .../accountable/src/transport/reserves.ts | 90 +++++++++++ .../sources/accountable/test-payload.json | 7 + .../__snapshots__/adapter.test.ts.snap | 76 +++++++++ .../test/integration/adapter.test.ts | 114 ++++++++++++++ .../accountable/test/integration/fixtures.ts | 82 ++++++++++ .../test/unit/transport/reserves.test.ts | 146 ++++++++++++++++++ packages/sources/accountable/tsconfig.json | 9 ++ .../sources/accountable/tsconfig.test.json | 7 + packages/tsconfig.json | 3 + packages/tsconfig.test.json | 3 + yarn.lock | 18 +++ 21 files changed, 700 insertions(+), 1 deletion(-) create mode 100644 packages/sources/accountable/CHANGELOG.md create mode 100644 packages/sources/accountable/README.md create mode 100644 packages/sources/accountable/package.json create mode 100644 packages/sources/accountable/src/config/index.ts create mode 100644 packages/sources/accountable/src/config/overrides.json create mode 100644 packages/sources/accountable/src/endpoint/index.ts create mode 100644 packages/sources/accountable/src/endpoint/reserves.ts create mode 100644 packages/sources/accountable/src/index.ts create mode 100644 packages/sources/accountable/src/transport/reserves.ts create mode 100644 packages/sources/accountable/test-payload.json create mode 100644 packages/sources/accountable/test/integration/__snapshots__/adapter.test.ts.snap create mode 100644 packages/sources/accountable/test/integration/adapter.test.ts create mode 100644 packages/sources/accountable/test/integration/fixtures.ts create mode 100644 packages/sources/accountable/test/unit/transport/reserves.test.ts create mode 100644 packages/sources/accountable/tsconfig.json create mode 100755 packages/sources/accountable/tsconfig.test.json diff --git a/.pnp.cjs b/.pnp.cjs index eef0fd263a..8843cd1830 100644 --- a/.pnp.cjs +++ b/.pnp.cjs @@ -154,6 +154,10 @@ const RAW_RUNTIME_STATE = "name": "@chainlink/readme-test-adapter",\ "reference": "workspace:packages/scripts/src/generate-readme/test/integration/readme-test-adapter"\ },\ + {\ + "name": "@chainlink/accountable-adapter",\ + "reference": "workspace:packages/sources/accountable"\ + },\ {\ "name": "@chainlink/ada-balance-adapter",\ "reference": "workspace:packages/sources/ada-balance"\ @@ -770,6 +774,7 @@ const RAW_RUNTIME_STATE = "enableTopLevelFallback": true,\ "ignorePatternData": "(^(?:\\\\.yarn\\\\/sdks(?:\\\\/(?!\\\\.{1,2}(?:\\\\/|$))(?:(?:(?!(?:^|\\\\/)\\\\.{1,2}(?:\\\\/|$)).)*?)|$))$)",\ "fallbackExclusionList": [\ + ["@chainlink/accountable-adapter", ["workspace:packages/sources/accountable"]],\ ["@chainlink/ada-balance-adapter", ["workspace:packages/sources/ada-balance"]],\ ["@chainlink/aleno-adapter", ["workspace:packages/sources/aleno"]],\ ["@chainlink/allium-state-adapter", ["workspace:packages/sources/allium-state"]],\ @@ -4724,6 +4729,23 @@ const RAW_RUNTIME_STATE = "linkType": "HARD"\ }]\ ]],\ + ["@chainlink/accountable-adapter", [\ + ["workspace:packages/sources/accountable", {\ + "packageLocation": "./packages/sources/accountable/",\ + "packageDependencies": [\ + ["@chainlink/accountable-adapter", "workspace:packages/sources/accountable"],\ + ["@chainlink/external-adapter-framework", "npm:2.11.4"],\ + ["@sinonjs/fake-timers", "npm:9.1.2"],\ + ["@types/jest", "npm:29.5.14"],\ + ["@types/node", "npm:22.14.1"],\ + ["@types/sinonjs__fake-timers", "npm:8.1.5"],\ + ["nock", "npm:13.5.6"],\ + ["tslib", "npm:2.4.1"],\ + ["typescript", "patch:typescript@npm%3A5.8.3#optional!builtin::version=5.8.3&hash=5786d5"]\ + ],\ + "linkType": "SOFT"\ + }]\ + ]],\ ["@chainlink/ada-balance-adapter", [\ ["workspace:packages/sources/ada-balance", {\ "packageLocation": "./packages/sources/ada-balance/",\ @@ -6153,7 +6175,7 @@ const RAW_RUNTIME_STATE = "linkType": "HARD"\ }],\ ["npm:2.9.0", {\ - "packageLocation": "./.yarn/cache/@chainlink-external-adapter-framework-npm-2.9.0-664e8a533b-36152824af.zip/node_modules/@chainlink/external-adapter-framework/",\ + "packageLocation": "./.yarn/unplugged/@chainlink-external-adapter-framework-npm-2.9.0-664e8a533b/node_modules/@chainlink/external-adapter-framework/",\ "packageDependencies": [\ ["@chainlink/external-adapter-framework", "npm:2.9.0"],\ ["ajv", "npm:8.17.1"],\ diff --git a/package.json b/package.json index ed3d28f320..cdd6f919ca 100644 --- a/package.json +++ b/package.json @@ -77,5 +77,10 @@ "resolutions": { "ethereum-cryptography@^1.1.2": "patch:ethereum-cryptography@npm%3A1.1.2#./.yarn/patches/ethereum-cryptography-npm-1.1.2-c16cfd7e8a.patch", "ethereum-cryptography@^1.0.3": "patch:ethereum-cryptography@npm%3A1.1.2#./.yarn/patches/ethereum-cryptography-npm-1.1.2-c16cfd7e8a.patch" + }, + "dependenciesMeta": { + "@chainlink/external-adapter-framework@2.9.0": { + "unplugged": true + } } } diff --git a/packages/sources/accountable/CHANGELOG.md b/packages/sources/accountable/CHANGELOG.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/sources/accountable/README.md b/packages/sources/accountable/README.md new file mode 100644 index 0000000000..91ab1d4455 --- /dev/null +++ b/packages/sources/accountable/README.md @@ -0,0 +1,3 @@ +# Chainlink External Adapter for example-adapter + +This README will be generated automatically when code is merged to `main`. If you would like to generate a preview of the README, please run `yarn generate:readme example-adapter`. diff --git a/packages/sources/accountable/package.json b/packages/sources/accountable/package.json new file mode 100644 index 0000000000..c7ca6ce899 --- /dev/null +++ b/packages/sources/accountable/package.json @@ -0,0 +1,42 @@ +{ + "name": "@chainlink/accountable-adapter", + "version": "0.0.0", + "description": "Chainlink accountable adapter.", + "keywords": [ + "Chainlink", + "LINK", + "blockchain", + "oracle", + "accountable" + ], + "main": "dist/index.js", + "types": "dist/index.d.ts", + "files": [ + "dist" + ], + "repository": { + "url": "https://github.com/smartcontractkit/external-adapters-js", + "type": "git" + }, + "license": "MIT", + "scripts": { + "clean": "rm -rf dist && rm -f tsconfig.tsbuildinfo", + "prepack": "yarn build", + "build": "tsc -b", + "server": "node -e 'require(\"./index.js\").server()'", + "server:dist": "node -e 'require(\"./dist/index.js\").server()'", + "start": "yarn server:dist" + }, + "devDependencies": { + "@sinonjs/fake-timers": "9.1.2", + "@types/jest": "^29.5.14", + "@types/node": "22.14.1", + "@types/sinonjs__fake-timers": "8.1.5", + "nock": "13.5.6", + "typescript": "5.8.3" + }, + "dependencies": { + "@chainlink/external-adapter-framework": "2.11.4", + "tslib": "2.4.1" + } +} diff --git a/packages/sources/accountable/src/config/index.ts b/packages/sources/accountable/src/config/index.ts new file mode 100644 index 0000000000..f8b44b8a4d --- /dev/null +++ b/packages/sources/accountable/src/config/index.ts @@ -0,0 +1,15 @@ +import { AdapterConfig } from '@chainlink/external-adapter-framework/config' + +export const config = new AdapterConfig({ + ACCOUNTABLE_BEARER_TOKEN: { + description: 'Bearer token for Accountable API authentication', + type: 'string', + required: true, + sensitive: true, + }, + API_ENDPOINT: { + description: 'API endpoint for Accountable', + type: 'string', + default: 'https://dvn.accountable.capital/v1', + }, +}) diff --git a/packages/sources/accountable/src/config/overrides.json b/packages/sources/accountable/src/config/overrides.json new file mode 100644 index 0000000000..8842b435bd --- /dev/null +++ b/packages/sources/accountable/src/config/overrides.json @@ -0,0 +1,3 @@ +{ + "example-adapter": {} +} diff --git a/packages/sources/accountable/src/endpoint/index.ts b/packages/sources/accountable/src/endpoint/index.ts new file mode 100644 index 0000000000..913cc2b9f2 --- /dev/null +++ b/packages/sources/accountable/src/endpoint/index.ts @@ -0,0 +1 @@ +export { endpoint as reserves } from './reserves' diff --git a/packages/sources/accountable/src/endpoint/reserves.ts b/packages/sources/accountable/src/endpoint/reserves.ts new file mode 100644 index 0000000000..fee8642227 --- /dev/null +++ b/packages/sources/accountable/src/endpoint/reserves.ts @@ -0,0 +1,32 @@ +import { AdapterEndpoint } from '@chainlink/external-adapter-framework/adapter' +import { SingleNumberResultResponse } from '@chainlink/external-adapter-framework/util' +import { InputParameters } from '@chainlink/external-adapter-framework/validation' +import { config } from '../config' +import { httpTransport } from '../transport/reserves' + +export const inputParameters = new InputParameters( + { + client: { + required: true, + type: 'string', + description: 'The client identifier (e.g. syrupusdt, syrupusdc)', + }, + }, + [ + { + client: 'syrupusdc', + }, + ], +) + +export type BaseEndpointTypes = { + Parameters: typeof inputParameters.definition + Response: SingleNumberResultResponse + Settings: typeof config.settings +} + +export const endpoint = new AdapterEndpoint({ + name: 'reserves', + transport: httpTransport, + inputParameters, +}) diff --git a/packages/sources/accountable/src/index.ts b/packages/sources/accountable/src/index.ts new file mode 100644 index 0000000000..2f6d186540 --- /dev/null +++ b/packages/sources/accountable/src/index.ts @@ -0,0 +1,21 @@ +import { expose, ServerInstance } from '@chainlink/external-adapter-framework' +import { Adapter } from '@chainlink/external-adapter-framework/adapter' +import { config } from './config' +import { reserves } from './endpoint' + +export const adapter = new Adapter({ + defaultEndpoint: reserves.name, + name: 'ACCOUNTABLE', + config, + endpoints: [reserves], + rateLimiting: { + tiers: { + default: { + rateLimit1s: 10, + note: 'Setting rate limit based on API spec of 10 requests per second', + }, + }, + }, +}) + +export const server = (): Promise => expose(adapter) diff --git a/packages/sources/accountable/src/transport/reserves.ts b/packages/sources/accountable/src/transport/reserves.ts new file mode 100644 index 0000000000..ca0b86460b --- /dev/null +++ b/packages/sources/accountable/src/transport/reserves.ts @@ -0,0 +1,90 @@ +import { HttpTransport } from '@chainlink/external-adapter-framework/transports' +import { BaseEndpointTypes } from '../endpoint/reserves' + +export interface ResponseSchema { + client: string + totalReserve: number + totalSupply: number + underlyingAssets: { + name: string + value: number + }[] + collateralization: number +} + +export type HttpTransportTypes = BaseEndpointTypes & { + Provider: { + RequestBody: never + ResponseBody: ResponseSchema + } +} + +export interface RequestParams { + client: string +} + +export interface AdapterConfig { + API_ENDPOINT: string + ACCOUNTABLE_BEARER_TOKEN: string +} + +/** + * Builds a single request configuration for a given parameter + */ +export const buildRequestConfig = (param: RequestParams, config: AdapterConfig) => { + return { + params: [param], + request: { + baseURL: config.API_ENDPOINT, + url: '/reserves', + headers: { + accept: 'application/json', + Authorization: `Bearer ${config.ACCOUNTABLE_BEARER_TOKEN}`, + }, + params: { + client: param.client, + }, + }, + } +} + +/** + * Builds an error response for a parameter when no data is returned + */ +export const buildErrorResponse = (param: RequestParams) => { + return { + params: param, + response: { + errorMessage: `The data provider didn't return any value for client: ${param.client}`, + statusCode: 502, + }, + } +} + +/** + * Builds a success response from the API data + */ +export const buildSuccessResponse = (param: RequestParams, totalReserve: number) => { + return { + params: param, + response: { + result: totalReserve, + data: { + result: totalReserve, + }, + }, + } +} + +export const httpTransport = new HttpTransport({ + prepareRequests: (params, config) => { + return params.map((param) => buildRequestConfig(param, config)) + }, + parseResponse: (params, response) => { + if (!response.data) { + return params.map((param) => buildErrorResponse(param)) + } + + return params.map((param) => buildSuccessResponse(param, response.data.totalReserve)) + }, +}) diff --git a/packages/sources/accountable/test-payload.json b/packages/sources/accountable/test-payload.json new file mode 100644 index 0000000000..07fa4e0c19 --- /dev/null +++ b/packages/sources/accountable/test-payload.json @@ -0,0 +1,7 @@ +{ + "requests": [ + { + "client": "syrupusdc" + } + ] +} diff --git a/packages/sources/accountable/test/integration/__snapshots__/adapter.test.ts.snap b/packages/sources/accountable/test/integration/__snapshots__/adapter.test.ts.snap new file mode 100644 index 0000000000..c338d2b755 --- /dev/null +++ b/packages/sources/accountable/test/integration/__snapshots__/adapter.test.ts.snap @@ -0,0 +1,76 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`execute reserves endpoint happy path should return success for syrupusdc client 1`] = ` +{ + "data": { + "result": 39869034.71, + }, + "result": 39869034.71, + "statusCode": 200, + "timestamps": { + "providerDataReceivedUnixMs": 978347471111, + "providerDataRequestedUnixMs": 978347471111, + }, +} +`; + +exports[`execute reserves endpoint happy path should return success for syrupusdt client 1`] = ` +{ + "data": { + "result": 15000000.5, + }, + "result": 15000000.5, + "statusCode": 200, + "timestamps": { + "providerDataReceivedUnixMs": 978347471111, + "providerDataRequestedUnixMs": 978347471111, + }, +} +`; + +exports[`execute reserves endpoint happy path should use reserves as default endpoint 1`] = ` +{ + "data": { + "result": 39869034.71, + }, + "result": 39869034.71, + "statusCode": 200, + "timestamps": { + "providerDataReceivedUnixMs": 978347471111, + "providerDataRequestedUnixMs": 978347471111, + }, +} +`; + +exports[`execute reserves endpoint upstream failures should handle empty data response from upstream 1`] = ` +{ + "errorMessage": "The data provider didn't return any value for client: invalidclient", + "statusCode": 502, + "timestamps": { + "providerDataReceivedUnixMs": 978347471111, + "providerDataRequestedUnixMs": 978347471111, + }, +} +`; + +exports[`execute reserves endpoint validation errors should fail on empty request body 1`] = ` +{ + "error": { + "message": "[Param: client] param is required but no value was provided", + "name": "AdapterError", + }, + "status": "errored", + "statusCode": 400, +} +`; + +exports[`execute reserves endpoint validation errors should fail when client parameter is missing 1`] = ` +{ + "error": { + "message": "[Param: client] param is required but no value was provided", + "name": "AdapterError", + }, + "status": "errored", + "statusCode": 400, +} +`; diff --git a/packages/sources/accountable/test/integration/adapter.test.ts b/packages/sources/accountable/test/integration/adapter.test.ts new file mode 100644 index 0000000000..407003e5b3 --- /dev/null +++ b/packages/sources/accountable/test/integration/adapter.test.ts @@ -0,0 +1,114 @@ +import { + TestAdapter, + setEnvVariables, +} from '@chainlink/external-adapter-framework/util/testing-utils' +import * as nock from 'nock' +import { + mockResponseEmptyData, + mockResponseSuccess, + mockResponseSuccessSyrupUsdt, +} from './fixtures' + +describe('execute', () => { + let spy: jest.SpyInstance + let testAdapter: TestAdapter + let oldEnv: NodeJS.ProcessEnv + + beforeAll(async () => { + oldEnv = JSON.parse(JSON.stringify(process.env)) + process.env.ACCOUNTABLE_BEARER_TOKEN = + process.env.ACCOUNTABLE_BEARER_TOKEN ?? 'fake-bearer-token' + + const mockDate = new Date('2001-01-01T11:11:11.111Z') + spy = jest.spyOn(Date, 'now').mockReturnValue(mockDate.getTime()) + + const adapter = (await import('./../../src')).adapter + adapter.rateLimiting = undefined + testAdapter = await TestAdapter.startWithMockedCache(adapter, { + testAdapter: {} as TestAdapter, + }) + }) + + afterEach(() => { + nock.cleanAll() + const keys = testAdapter.mockCache?.cache.keys() + if (keys) { + for (const key of keys) { + testAdapter.mockCache?.delete(key) + } + } + }) + + afterAll(async () => { + setEnvVariables(oldEnv) + await testAdapter.api.close() + nock.restore() + nock.cleanAll() + spy.mockRestore() + }) + + describe('reserves endpoint', () => { + describe('happy path', () => { + it('should return success for syrupusdc client', async () => { + const data = { + client: 'syrupusdc', + endpoint: 'reserves', + } + mockResponseSuccess() + const response = await testAdapter.request(data) + expect(response.statusCode).toBe(200) + expect(response.json()).toMatchSnapshot() + }) + + it('should return success for syrupusdt client', async () => { + const data = { + client: 'syrupusdt', + endpoint: 'reserves', + } + mockResponseSuccessSyrupUsdt() + const response = await testAdapter.request(data) + expect(response.statusCode).toBe(200) + expect(response.json()).toMatchSnapshot() + }) + + it('should use reserves as default endpoint', async () => { + const data = { + client: 'syrupusdc', + } + mockResponseSuccess() + const response = await testAdapter.request(data) + expect(response.statusCode).toBe(200) + expect(response.json()).toMatchSnapshot() + }) + }) + + describe('validation errors', () => { + it('should fail on empty request body', async () => { + const response = await testAdapter.request({}) + expect(response.statusCode).toBe(400) + expect(response.json()).toMatchSnapshot() + }) + + it('should fail when client parameter is missing', async () => { + const response = await testAdapter.request({ + endpoint: 'reserves', + }) + expect(response.statusCode).toBe(400) + expect(response.json()).toMatchSnapshot() + }) + }) + + describe('upstream failures', () => { + it('should handle empty data response from upstream', async () => { + mockResponseEmptyData() + const data = { + client: 'invalidclient', + endpoint: 'reserves', + } + const response = await testAdapter.request(data) + expect(response.statusCode).toBe(502) + expect(response.json()).toMatchSnapshot() + }) + }) + }) +}) diff --git a/packages/sources/accountable/test/integration/fixtures.ts b/packages/sources/accountable/test/integration/fixtures.ts new file mode 100644 index 0000000000..adbe152d06 --- /dev/null +++ b/packages/sources/accountable/test/integration/fixtures.ts @@ -0,0 +1,82 @@ +import nock from 'nock' + +export const mockResponseSuccess = (): nock.Scope => + nock('https://dvn.accountable.capital', { + encodedQueryParams: true, + }) + .get('/v1/reserves') + .query({ + client: 'syrupusdc', + }) + .reply( + 200, + () => ({ + client: 'syrupUSDC', + totalReserve: 39869034.71, + totalSupply: 39676944.15, + underlyingAssets: [ + { name: 'Copper', value: 37695400.26754955 }, + { name: 'Fireblocks', value: 2135159.12458195 }, + { name: 'Insurance Fund', value: 612614.08 }, + { name: 'Ethereum Chain', value: 35942.9508146177 }, + { name: 'Binance', value: 2524.0707870999145 }, + { name: 'BNB Smart Chain', value: 8.29771446758096 }, + ], + collateralization: 1.004841, + }), + [ + 'Content-Type', + 'application/json', + 'Connection', + 'close', + 'Vary', + 'Accept-Encoding', + 'Vary', + 'Origin', + ], + ) + .persist() + +export const mockResponseSuccessSyrupUsdt = (): nock.Scope => + nock('https://dvn.accountable.capital', { + encodedQueryParams: true, + }) + .get('/v1/reserves') + .query({ + client: 'syrupusdt', + }) + .reply( + 200, + () => ({ + client: 'syrupUSDT', + totalReserve: 15000000.5, + totalSupply: 14500000.25, + underlyingAssets: [ + { name: 'Copper', value: 12000000.5 }, + { name: 'Fireblocks', value: 3000000.0 }, + ], + collateralization: 1.034482, + }), + [ + 'Content-Type', + 'application/json', + 'Connection', + 'close', + 'Vary', + 'Accept-Encoding', + 'Vary', + 'Origin', + ], + ) + .persist() + +export const mockResponseEmptyData = (): nock.Scope => + nock('https://dvn.accountable.capital', { + encodedQueryParams: true, + }) + .get('/v1/reserves') + .query({ + client: 'invalidclient', + }) + .reply(200, null, ['Content-Type', 'application/json']) + .persist() diff --git a/packages/sources/accountable/test/unit/transport/reserves.test.ts b/packages/sources/accountable/test/unit/transport/reserves.test.ts new file mode 100644 index 0000000000..a45eea89ef --- /dev/null +++ b/packages/sources/accountable/test/unit/transport/reserves.test.ts @@ -0,0 +1,146 @@ +import { + AdapterConfig, + buildErrorResponse, + buildRequestConfig, + buildSuccessResponse, + RequestParams, +} from '../../../src/transport/reserves' + +describe('reserves transport', () => { + describe('buildRequestConfig', () => { + const mockConfig: AdapterConfig = { + API_ENDPOINT: 'https://api.example.com/v1', + ACCOUNTABLE_BEARER_TOKEN: 'test-bearer-token', + } + + it('returns correct baseURL from config', () => { + const param: RequestParams = { client: 'syrupusdc' } + const result = buildRequestConfig(param, mockConfig) + + expect(result.request.baseURL).toBe('https://api.example.com/v1') + }) + + it('returns correct url path', () => { + const param: RequestParams = { client: 'syrupusdc' } + const result = buildRequestConfig(param, mockConfig) + + expect(result.request.url).toBe('/reserves') + }) + + it('includes accept header as application/json', () => { + const param: RequestParams = { client: 'syrupusdc' } + const result = buildRequestConfig(param, mockConfig) + + expect(result.request.headers.accept).toBe('application/json') + }) + + it('includes Authorization header with Bearer token from config', () => { + const param: RequestParams = { client: 'syrupusdc' } + const result = buildRequestConfig(param, mockConfig) + + expect(result.request.headers.Authorization).toBe('Bearer test-bearer-token') + }) + + it('includes client in request params', () => { + const param: RequestParams = { client: 'syrupusdc' } + const result = buildRequestConfig(param, mockConfig) + + expect(result.request.params.client).toBe('syrupusdc') + }) + + it('wraps input param in params array', () => { + const param: RequestParams = { client: 'syrupusdt' } + const result = buildRequestConfig(param, mockConfig) + + expect(result.params).toEqual([{ client: 'syrupusdt' }]) + }) + + it('handles different client values', () => { + const param: RequestParams = { client: 'customclient' } + const result = buildRequestConfig(param, mockConfig) + + expect(result.request.params.client).toBe('customclient') + expect(result.params[0].client).toBe('customclient') + }) + }) + + describe('buildErrorResponse', () => { + it('returns params unchanged', () => { + const param: RequestParams = { client: 'syrupusdc' } + const result = buildErrorResponse(param) + + expect(result.params).toEqual({ client: 'syrupusdc' }) + }) + + it('returns statusCode 502', () => { + const param: RequestParams = { client: 'syrupusdc' } + const result = buildErrorResponse(param) + + expect(result.response.statusCode).toBe(502) + }) + + it('includes client name in error message', () => { + const param: RequestParams = { client: 'syrupusdc' } + const result = buildErrorResponse(param) + + expect(result.response.errorMessage).toBe( + "The data provider didn't return any value for client: syrupusdc", + ) + }) + + it('handles different client values in error message', () => { + const param: RequestParams = { client: 'invalidclient' } + const result = buildErrorResponse(param) + + expect(result.response.errorMessage).toContain('invalidclient') + }) + }) + + describe('buildSuccessResponse', () => { + it('returns params unchanged', () => { + const param: RequestParams = { client: 'syrupusdc' } + const result = buildSuccessResponse(param, 39869034.71) + + expect(result.params).toEqual({ client: 'syrupusdc' }) + }) + + it('returns totalReserve as result', () => { + const param: RequestParams = { client: 'syrupusdc' } + const result = buildSuccessResponse(param, 39869034.71) + + expect(result.response.result).toBe(39869034.71) + }) + + it('returns totalReserve in data.result', () => { + const param: RequestParams = { client: 'syrupusdc' } + const result = buildSuccessResponse(param, 39869034.71) + + expect(result.response.data.result).toBe(39869034.71) + }) + + it('handles zero totalReserve', () => { + const param: RequestParams = { client: 'zeroclient' } + const result = buildSuccessResponse(param, 0) + + expect(result.response.result).toBe(0) + expect(result.response.data.result).toBe(0) + }) + + it('handles large totalReserve values', () => { + const param: RequestParams = { client: 'largeclient' } + const largeValue = 999999999999.99 + const result = buildSuccessResponse(param, largeValue) + + expect(result.response.result).toBe(largeValue) + expect(result.response.data.result).toBe(largeValue) + }) + + it('handles negative totalReserve values', () => { + const param: RequestParams = { client: 'negativeclient' } + const result = buildSuccessResponse(param, -100.5) + + expect(result.response.result).toBe(-100.5) + expect(result.response.data.result).toBe(-100.5) + }) + }) +}) diff --git a/packages/sources/accountable/tsconfig.json b/packages/sources/accountable/tsconfig.json new file mode 100644 index 0000000000..f59363fd76 --- /dev/null +++ b/packages/sources/accountable/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "dist", + "rootDir": "src" + }, + "include": ["src/**/*", "src/**/*.json"], + "exclude": ["dist", "**/*.spec.ts", "**/*.test.ts"] +} diff --git a/packages/sources/accountable/tsconfig.test.json b/packages/sources/accountable/tsconfig.test.json new file mode 100755 index 0000000000..e3de28cb5c --- /dev/null +++ b/packages/sources/accountable/tsconfig.test.json @@ -0,0 +1,7 @@ +{ + "extends": "../../tsconfig.base.json", + "include": ["src/**/*", "**/test", "src/**/*.json"], + "compilerOptions": { + "noEmit": true + } +} diff --git a/packages/tsconfig.json b/packages/tsconfig.json index fdfd8590b0..b5f0a1cff3 100644 --- a/packages/tsconfig.json +++ b/packages/tsconfig.json @@ -98,6 +98,9 @@ { "path": "./scripts" }, + { + "path": "./sources/accountable" + }, { "path": "./sources/ada-balance" }, diff --git a/packages/tsconfig.test.json b/packages/tsconfig.test.json index 34b47d0d3a..b51610ab10 100644 --- a/packages/tsconfig.test.json +++ b/packages/tsconfig.test.json @@ -98,6 +98,9 @@ { "path": "./scripts/tsconfig.test.json" }, + { + "path": "./sources/accountable/tsconfig.test.json" + }, { "path": "./sources/ada-balance/tsconfig.test.json" }, diff --git a/yarn.lock b/yarn.lock index 91d0f297f8..094cb86b4c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2262,6 +2262,21 @@ __metadata: languageName: node linkType: hard +"@chainlink/accountable-adapter@workspace:packages/sources/accountable": + version: 0.0.0-use.local + resolution: "@chainlink/accountable-adapter@workspace:packages/sources/accountable" + dependencies: + "@chainlink/external-adapter-framework": "npm:2.11.4" + "@sinonjs/fake-timers": "npm:9.1.2" + "@types/jest": "npm:^29.5.14" + "@types/node": "npm:22.14.1" + "@types/sinonjs__fake-timers": "npm:8.1.5" + nock: "npm:13.5.6" + tslib: "npm:2.4.1" + typescript: "npm:5.8.3" + languageName: unknown + linkType: soft + "@chainlink/ada-balance-adapter@workspace:*, @chainlink/ada-balance-adapter@workspace:packages/sources/ada-balance": version: 0.0.0-use.local resolution: "@chainlink/ada-balance-adapter@workspace:packages/sources/ada-balance" @@ -3613,6 +3628,9 @@ __metadata: ts-node: "npm:10.9.2" typescript: "npm:5.8.3" yo: "npm:4.3.1" + dependenciesMeta: + "@chainlink/external-adapter-framework@2.9.0": + unplugged: true languageName: unknown linkType: soft