From 9a9dbaf2ece4d48c479fc8e3a1f3db39122a4f8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emanuel=20Tesa=C5=99?= Date: Wed, 27 Sep 2023 12:14:03 +0200 Subject: [PATCH] Add unit tests for api-requests modules (#44) --- .../src/api-requests/data-provider.test.ts | 38 +++ .../src/api-requests/data-provider.ts | 15 +- .../src/api-requests/signed-api.test.ts | 110 +++++++++ .../src/api-requests/signed-api.ts | 47 ++-- packages/data-pusher/src/index.ts | 2 - packages/data-pusher/src/state.ts | 3 +- packages/data-pusher/src/update-signed-api.ts | 6 +- .../data-pusher/src/validation/schema.test.ts | 2 +- packages/data-pusher/src/validation/schema.ts | 8 + packages/data-pusher/src/wallets.ts | 10 - packages/data-pusher/test/fixtures.ts | 228 ++++++++++++++++++ 11 files changed, 426 insertions(+), 43 deletions(-) create mode 100644 packages/data-pusher/src/api-requests/data-provider.test.ts create mode 100644 packages/data-pusher/src/api-requests/signed-api.test.ts delete mode 100644 packages/data-pusher/src/wallets.ts create mode 100644 packages/data-pusher/test/fixtures.ts diff --git a/packages/data-pusher/src/api-requests/data-provider.test.ts b/packages/data-pusher/src/api-requests/data-provider.test.ts new file mode 100644 index 00000000..59be1761 --- /dev/null +++ b/packages/data-pusher/src/api-requests/data-provider.test.ts @@ -0,0 +1,38 @@ +import { api as nodeApiModule } from '@api3/airnode-node'; +import { makeTemplateRequests } from './data-provider'; +import * as stateModule from '../state'; +import * as loggerModule from '../logger'; +import { + config, + createMockedLogger, + nodaryTemplateRequestErrorResponse, + nodaryTemplateRequestResponseData, + nodaryTemplateResponses, +} from '../../test/fixtures'; + +describe(makeTemplateRequests.name, () => { + it('makes a single template request for multiple beacons', async () => { + const state = stateModule.getInitialState(config); + jest.spyOn(stateModule, 'getState').mockReturnValue(state); + const logger = createMockedLogger(); + jest.spyOn(loggerModule, 'getLogger').mockReturnValue(logger); + jest.spyOn(nodeApiModule, 'performApiCall').mockResolvedValue([[], nodaryTemplateRequestResponseData]); + + const response = await makeTemplateRequests(config.triggers.signedApiUpdates[0]!); + + expect(response).toEqual(nodaryTemplateResponses); + }); + + it('handles request failure', async () => { + const state = stateModule.getInitialState(config); + jest.spyOn(stateModule, 'getState').mockReturnValue(state); + const logger = createMockedLogger(); + jest.spyOn(loggerModule, 'getLogger').mockReturnValue(logger); + jest.spyOn(nodeApiModule, 'performApiCall').mockRejectedValue(nodaryTemplateRequestErrorResponse); + + await expect(makeTemplateRequests(config.triggers.signedApiUpdates[0]!)).rejects.toEqual({ + errorMessage: 'Invalid API key', + success: false, + }); + }); +}); diff --git a/packages/data-pusher/src/api-requests/data-provider.ts b/packages/data-pusher/src/api-requests/data-provider.ts index 6927f945..2af71701 100644 --- a/packages/data-pusher/src/api-requests/data-provider.ts +++ b/packages/data-pusher/src/api-requests/data-provider.ts @@ -1,22 +1,21 @@ import * as abi from '@api3/airnode-abi'; import * as node from '@api3/airnode-node'; -import { isNil } from 'lodash'; +import { isNil, pick } from 'lodash'; import { getState } from '../state'; import { preProcessApiSpecifications } from '../unexported-airnode-features/api-specification-processing'; import { SignedApiUpdate, TemplateId } from '../validation/schema'; import { getLogger } from '../logger'; -type TemplateResponse = [TemplateId, node.HttpGatewayApiCallSuccessResponse]; -type TemplateResponses = TemplateResponse[]; +export type TemplateResponse = [TemplateId, node.HttpGatewayApiCallSuccessResponse]; export const callApi = async (payload: node.ApiCallPayload) => { - getLogger().debug('Preprocessing API call payload', { aggregateApiCall: payload.aggregatedApiCall }); + getLogger().debug('Preprocessing API call payload', pick(payload.aggregatedApiCall, ['endpointName', 'oisTitle'])); const processedPayload = await preProcessApiSpecifications(payload); - getLogger().debug('Performing API call', { aggregateApiCall: payload.aggregatedApiCall }); + getLogger().debug('Performing API call', { processedPayload: processedPayload }); return node.api.performApiCall(processedPayload); }; -export const makeTemplateRequests = async (signedApiUpdate: SignedApiUpdate): Promise => { +export const makeTemplateRequests = async (signedApiUpdate: SignedApiUpdate): Promise => { const { config: { beacons, endpoints, templates, ois, apiCredentials }, apiLimiters, @@ -24,8 +23,8 @@ export const makeTemplateRequests = async (signedApiUpdate: SignedApiUpdate): Pr getLogger().debug('Making template requests', signedApiUpdate); const { beaconIds } = signedApiUpdate; - // Because each beacon have same operation, just take first one as operational template - // See the function validateTriggerReferences in validation.ts + // Because each beacon has the same operation, just take first one as operational template. See validation.ts for + // details. const operationTemplateId = beacons[beaconIds[0]!]!.templateId; const operationTemplate = templates[operationTemplateId]!; diff --git a/packages/data-pusher/src/api-requests/signed-api.test.ts b/packages/data-pusher/src/api-requests/signed-api.test.ts new file mode 100644 index 00000000..9751b6e7 --- /dev/null +++ b/packages/data-pusher/src/api-requests/signed-api.test.ts @@ -0,0 +1,110 @@ +import axios from 'axios'; +import { ZodError } from 'zod'; +import { postSignedApiData, signTemplateResponses } from './signed-api'; +import { + config, + createMockedLogger, + signedApiResponse, + nodarySignedTemplateResponses, + nodaryTemplateResponses, +} from '../../test/fixtures'; +import * as loggerModule from '../logger'; +import * as stateModule from '../state'; + +describe(signTemplateResponses.name, () => { + it('signs template responses', async () => { + const state = stateModule.getInitialState(config); + jest.spyOn(stateModule, 'getState').mockReturnValue(state); + const logger = createMockedLogger(); + jest.spyOn(loggerModule, 'getLogger').mockReturnValue(logger); + jest.useFakeTimers().setSystemTime(new Date('2023-01-20')); + + const signedTemplateResponses = await signTemplateResponses(nodaryTemplateResponses); + + expect(signedTemplateResponses).toEqual(nodarySignedTemplateResponses); + }); + + afterEach(() => { + jest.useRealTimers(); + }); +}); + +describe(postSignedApiData.name, () => { + it('posts data to central api', async () => { + const state = stateModule.getInitialState(config); + // Assumes the template responses are for unique template IDs (which is true in the test fixtures). + state.templateValues = Object.fromEntries( + nodarySignedTemplateResponses.map(([templateId, signedData]) => { + const dataQueue = new stateModule.DelayedSignedDataQueue(); + dataQueue.put(signedData); + return [templateId, dataQueue]; + }) + ); + jest.spyOn(stateModule, 'getState').mockReturnValue(state); + const logger = createMockedLogger(); + jest.spyOn(loggerModule, 'getLogger').mockReturnValue(logger); + jest.spyOn(axios, 'post').mockResolvedValue(signedApiResponse); + + const response = await postSignedApiData(config.triggers.signedApiUpdates[0]!); + + expect(response).toEqual({ count: 3, success: true }); + }); + + it('handles invalid response from signed API', async () => { + const state = stateModule.getInitialState(config); + // Assumes the template responses are for unique template IDs (which is true in the test fixtures). + state.templateValues = Object.fromEntries( + nodarySignedTemplateResponses.map(([templateId, signedData]) => { + const dataQueue = new stateModule.DelayedSignedDataQueue(); + dataQueue.put(signedData); + return [templateId, dataQueue]; + }) + ); + jest.spyOn(stateModule, 'getState').mockReturnValue(state); + const logger = createMockedLogger(); + jest.spyOn(loggerModule, 'getLogger').mockReturnValue(logger); + jest.spyOn(axios, 'post').mockResolvedValue({ youHaveNotThoughAboutThisDidYou: 'yes-I-did' }); + + const response = await postSignedApiData(config.triggers.signedApiUpdates[0]!); + + expect(response).toEqual({ success: false }); + expect(logger.warn).toHaveBeenCalledWith('Failed to parse response from the signed API.', { + errors: new ZodError([ + { + code: 'invalid_type', + expected: 'object', + received: 'undefined', + path: [], + message: 'Required', + }, + ]), + signedApiName: 'localhost', + updateDelay: 5, + }); + }); + + it('handles request failure', async () => { + const state = stateModule.getInitialState(config); + // Assumes the template responses are for unique template IDs (which is true in the test fixtures). + state.templateValues = Object.fromEntries( + nodarySignedTemplateResponses.map(([templateId, signedData]) => { + const dataQueue = new stateModule.DelayedSignedDataQueue(); + dataQueue.put(signedData); + return [templateId, dataQueue]; + }) + ); + jest.spyOn(stateModule, 'getState').mockReturnValue(state); + const logger = createMockedLogger(); + jest.spyOn(loggerModule, 'getLogger').mockReturnValue(logger); + jest.spyOn(axios, 'post').mockRejectedValue('simulated-network-error'); + + const response = await postSignedApiData(config.triggers.signedApiUpdates[0]!); + + expect(response).toEqual({ success: false }); + expect(logger.warn).toHaveBeenCalledWith('Failed to make update signed API request.', { + axiosResponse: undefined, + signedApiName: 'localhost', + updateDelay: 5, + }); + }); +}); diff --git a/packages/data-pusher/src/api-requests/signed-api.ts b/packages/data-pusher/src/api-requests/signed-api.ts index dd07ab59..f0db8d15 100644 --- a/packages/data-pusher/src/api-requests/signed-api.ts +++ b/packages/data-pusher/src/api-requests/signed-api.ts @@ -1,28 +1,26 @@ -import * as node from '@api3/airnode-node'; import { go } from '@api3/promise-utils'; import axios, { AxiosError } from 'axios'; import { isEmpty, isNil } from 'lodash'; import { ethers } from 'ethers'; +import { TemplateResponse } from './data-provider'; import { getLogger } from '../logger'; import { getState } from '../state'; import { SignedApiNameUpdateDelayGroup } from '../update-signed-api'; -import { SignedApiPayload, SignedData, TemplateId } from '../validation/schema'; +import { SignedApiPayload, SignedData, TemplateId, signedApiResponseSchema } from '../validation/schema'; import { signWithTemplateId } from '../utils'; -type TemplateResponse = [TemplateId, node.HttpGatewayApiCallSuccessResponse]; -type TemplateResponses = TemplateResponse[]; -type SignedResponse = [TemplateId, SignedData]; +export type SignedResponse = [TemplateId, SignedData]; export const postSignedApiData = async (group: SignedApiNameUpdateDelayGroup) => { const { config: { beacons, signedApis }, templateValues, } = getState(); - const { providerName, beaconIds, updateDelay } = group; - const logContext = { providerName, updateDelay }; + const { signedApiName, beaconIds, updateDelay } = group; + const logContext = { signedApiName, updateDelay }; getLogger().debug('Posting signed API data.', { group, ...logContext }); - const provider = signedApis.find((a) => a.name === providerName)!; + const provider = signedApis.find((a) => a.name === signedApiName)!; const batchPayloadOrNull = beaconIds.map((beaconId): SignedApiPayload | null => { const { templateId, airnode } = beacons[beaconId]!; @@ -34,10 +32,11 @@ export const postSignedApiData = async (group: SignedApiNameUpdateDelayGroup) => const batchPayload = batchPayloadOrNull.filter((payload): payload is SignedApiPayload => !isNil(payload)); if (isEmpty(batchPayload)) { - getLogger().debug('No batch payload found to post skipping.', logContext); - return; + getLogger().debug('No batch payload found to post. Skipping.', logContext); + return { success: true, count: 0 }; } - const goRes = await go, AxiosError>(async () => { + const goAxiosRequest = await go, AxiosError>(async () => { + getLogger().debug('Posting batch payload.', { ...logContext, batchPayload }); const axiosResponse = await axios.post(provider.url, batchPayload, { headers: { 'Content-Type': 'application/json', @@ -46,19 +45,31 @@ export const postSignedApiData = async (group: SignedApiNameUpdateDelayGroup) => return axiosResponse.data; }); - - if (!goRes.success) { + if (!goAxiosRequest.success) { getLogger().warn( - `Failed to post payload to update signed API.`, + `Failed to make update signed API request.`, // See: https://axios-http.com/docs/handling_errors - { ...logContext, axiosResponse: goRes.error.response } + { ...logContext, axiosResponse: goAxiosRequest.error.response } ); - return; + return { success: false }; } - getLogger().info(`Pushed signed data updates to the pool.`, { ...logContext, count: goRes.data.count }); + + getLogger().debug('Parsing response from the signed API.', { ...logContext, axiosResponse: goAxiosRequest.data }); + const parsedResponse = signedApiResponseSchema.safeParse(goAxiosRequest.data); + if (!parsedResponse.success) { + getLogger().warn('Failed to parse response from the signed API.', { + ...logContext, + errors: parsedResponse.error, + }); + return { success: false }; + } + + const count = parsedResponse.data.count; + getLogger().info(`Pushed signed data updates to the signed API.`, { ...logContext, count }); + return { success: true, count }; }; -export const signTemplateResponses = async (templateResponses: TemplateResponses) => { +export const signTemplateResponses = async (templateResponses: TemplateResponse[]) => { getLogger().debug('Signing template responses', { templateResponses }); const signPromises = templateResponses.map(async ([templateId, response]) => { diff --git a/packages/data-pusher/src/index.ts b/packages/data-pusher/src/index.ts index fed9f334..9bb32af8 100644 --- a/packages/data-pusher/src/index.ts +++ b/packages/data-pusher/src/index.ts @@ -9,7 +9,6 @@ import { loadConfig } from './validation/config'; import { initiateFetchingBeaconData } from './fetch-beacon-data'; import { initiateUpdatingSignedApi } from './update-signed-api'; import { initializeState } from './state'; -import { initializeWallet } from './wallets'; import { initializeLogger } from './logger'; export async function main() { @@ -17,7 +16,6 @@ export async function main() { initializeLogger(config); initializeState(config); - initializeWallet(); initiateFetchingBeaconData(); initiateUpdatingSignedApi(); } diff --git a/packages/data-pusher/src/state.ts b/packages/data-pusher/src/state.ts index 47971bfd..f82d5c26 100644 --- a/packages/data-pusher/src/state.ts +++ b/packages/data-pusher/src/state.ts @@ -1,4 +1,5 @@ import Bottleneck from 'bottleneck'; +import { ethers } from 'ethers'; import { Config, SignedData, TemplateId } from './validation/schema'; import { DIRECT_GATEWAY_MAX_CONCURRENCY_DEFAULT, DIRECT_GATEWAY_MIN_TIME_DEFAULT_MS } from './constants'; import { deriveEndpointId, getRandomId } from './utils'; @@ -76,7 +77,7 @@ export const getInitialState = (config: Config) => { config, templateValues: buildTemplateStorages(config), apiLimiters: buildApiLimiters(config), - walletPrivateKey: '', + walletPrivateKey: ethers.Wallet.fromMnemonic(config.walletMnemonic).privateKey, sponsorWalletsPrivateKey: {}, }; }; diff --git a/packages/data-pusher/src/update-signed-api.ts b/packages/data-pusher/src/update-signed-api.ts index d6ef53e2..e8ef71e2 100644 --- a/packages/data-pusher/src/update-signed-api.ts +++ b/packages/data-pusher/src/update-signed-api.ts @@ -10,7 +10,7 @@ import { postSignedApiData } from './api-requests/signed-api'; type SignedApiUpdateDelayBeaconIdsMap = Record>; export type SignedApiNameUpdateDelayGroup = { - providerName: string; + signedApiName: string; beaconIds: BeaconId[]; updateDelay: number; }; @@ -38,9 +38,9 @@ export const initiateUpdatingSignedApi = async () => { const signedApiUpdateDelayGroups: SignedApiNameUpdateDelayGroup[] = Object.entries( signedApiUpdateDelayBeaconIdsMap - ).flatMap(([providerName, updateDelayBeaconIds]) => + ).flatMap(([signedApiName, updateDelayBeaconIds]) => Object.entries(updateDelayBeaconIds).map(([updateDelay, beaconIds]) => ({ - providerName, + signedApiName, updateDelay: parseInt(updateDelay), beaconIds, })) diff --git a/packages/data-pusher/src/validation/schema.test.ts b/packages/data-pusher/src/validation/schema.test.ts index 9608afb7..1b516b86 100644 --- a/packages/data-pusher/src/validation/schema.test.ts +++ b/packages/data-pusher/src/validation/schema.test.ts @@ -5,5 +5,5 @@ import { configSchema } from './schema'; it('validates example config', async () => { const config = JSON.parse(readFileSync(join(__dirname, '../../config/pusher.example.json'), 'utf8')); - await expect(configSchema.parseAsync(config)).resolves.not.toThrow(); + await expect(configSchema.parseAsync(config)).resolves.toEqual(expect.any(Object)); }); diff --git a/packages/data-pusher/src/validation/schema.ts b/packages/data-pusher/src/validation/schema.ts index 5f4d1d9f..aac9dc07 100644 --- a/packages/data-pusher/src/validation/schema.ts +++ b/packages/data-pusher/src/validation/schema.ts @@ -360,3 +360,11 @@ export type RateLimitingConfig = z.infer; export type ApisCredentials = z.infer; export const secretsSchema = z.record(z.string()); + +export const signedApiResponseSchema = z + .object({ + count: z.number(), + }) + .strict(); + +export type SignedApiResponse = z.infer; diff --git a/packages/data-pusher/src/wallets.ts b/packages/data-pusher/src/wallets.ts deleted file mode 100644 index 49b09610..00000000 --- a/packages/data-pusher/src/wallets.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { ethers } from 'ethers'; -import { getState, setState } from './state'; - -export const initializeWallet = () => { - const state = getState(); - - const walletPrivateKey = ethers.Wallet.fromMnemonic(state.config.walletMnemonic).privateKey; - - setState({ ...state, walletPrivateKey }); -}; diff --git a/packages/data-pusher/test/fixtures.ts b/packages/data-pusher/test/fixtures.ts new file mode 100644 index 00000000..88c3bf00 --- /dev/null +++ b/packages/data-pusher/test/fixtures.ts @@ -0,0 +1,228 @@ +import { PerformApiCallSuccess } from '@api3/airnode-node/dist/src/api'; +import { ApiCallErrorResponse } from '@api3/airnode-node'; +import { Logger } from 'signed-api/common'; +import { AxiosResponse } from 'axios'; +import { Config } from '../src/validation/schema'; +import { SignedResponse } from '../src/api-requests/signed-api'; +import { TemplateResponse } from '../src/api-requests/data-provider'; + +export const createMockedLogger = (): Logger => { + return { + debug: jest.fn(), + warn: jest.fn(), + error: jest.fn(), + info: jest.fn(), + child: jest.fn(), + }; +}; + +export const config: Config = { + walletMnemonic: 'diamond result history offer forest diagram crop armed stumble orchard stage glance', + logger: { + type: 'pretty', + styling: 'on', + minLevel: 'debug', + }, + rateLimiting: { maxDirectGatewayConcurrency: 25, minDirectGatewayTime: 10 }, + beacons: { + '0xebba8507d616ed80766292d200a3598fdba656d9938cecc392765d4a284a69a4': { + airnode: '0xbF3137b0a7574563a23a8fC8badC6537F98197CC', + templateId: '0xcc35bd1800c06c12856a87311dd95bfcbb3add875844021d59a929d79f3c99bd', + }, + '0x6f6acbdadaaf116c89faf0e8de1d0c7c2352b01cce7be0eb9deb126ceaefa6ba': { + airnode: '0xbF3137b0a7574563a23a8fC8badC6537F98197CC', + templateId: '0x086130c54864b2129f8ac6d8d7ab819fa8181bbe676e35047b1bca4c31d51c66', + }, + '0x7944f22b40cc691a003e35db4810b41543a83781d94f706b5c0b6980e0a06ed7': { + airnode: '0xbF3137b0a7574563a23a8fC8badC6537F98197CC', + templateId: '0x1d65c1f1e127a41cebd2339f823d0290322c63f3044380cbac105db8e522ebb9', + }, + }, + templates: { + '0xcc35bd1800c06c12856a87311dd95bfcbb3add875844021d59a929d79f3c99bd': { + endpointId: '0x3528e42b017a5fbf9d2993a2df04efc3ed474357575065a111b054ddf9de2acc', + parameters: + '0x31730000000000000000000000000000000000000000000000000000000000006e616d65000000000000000000000000000000000000000000000000000000005754492f55534400000000000000000000000000000000000000000000000000', + }, + '0x086130c54864b2129f8ac6d8d7ab819fa8181bbe676e35047b1bca4c31d51c66': { + endpointId: '0x3528e42b017a5fbf9d2993a2df04efc3ed474357575065a111b054ddf9de2acc', + parameters: + '0x31730000000000000000000000000000000000000000000000000000000000006e616d65000000000000000000000000000000000000000000000000000000005841472f55534400000000000000000000000000000000000000000000000000', + }, + '0x1d65c1f1e127a41cebd2339f823d0290322c63f3044380cbac105db8e522ebb9': { + endpointId: '0x3528e42b017a5fbf9d2993a2df04efc3ed474357575065a111b054ddf9de2acc', + parameters: + '0x31730000000000000000000000000000000000000000000000000000000000006e616d65000000000000000000000000000000000000000000000000000000005841552f55534400000000000000000000000000000000000000000000000000', + }, + }, + endpoints: { + '0x3528e42b017a5fbf9d2993a2df04efc3ed474357575065a111b054ddf9de2acc': { + endpointName: 'feed', + oisTitle: 'Nodary', + }, + }, + triggers: { + signedApiUpdates: [ + { + signedApiName: 'localhost', + beaconIds: [ + '0xebba8507d616ed80766292d200a3598fdba656d9938cecc392765d4a284a69a4', + '0x6f6acbdadaaf116c89faf0e8de1d0c7c2352b01cce7be0eb9deb126ceaefa6ba', + '0x7944f22b40cc691a003e35db4810b41543a83781d94f706b5c0b6980e0a06ed7', + ], + fetchInterval: 5, + updateDelay: 5, + }, + ], + }, + signedApis: [ + { + name: 'localhost', + url: 'http://localhost:8090', + }, + ], + ois: [ + { + oisFormat: '2.1.0', + title: 'Nodary', + version: '0.2.0', + apiSpecifications: { + components: { + securitySchemes: { + NodarySecurityScheme1ApiKey: { in: 'header', name: 'x-nodary-api-key', type: 'apiKey' }, + }, + }, + paths: { + '/feed/latest': { get: { parameters: [{ in: 'query', name: 'name' }] } }, + '/feed/latestV2': { get: { parameters: [{ in: 'query', name: 'names' }] } }, + }, + servers: [{ url: 'https://api.nodary.io' }], + security: { NodarySecurityScheme1ApiKey: [] }, + }, + endpoints: [ + { + fixedOperationParameters: [], + name: 'feed', + operation: { method: 'get', path: '/feed/latestV2' }, + parameters: [{ name: 'name', operationParameter: { in: 'query', name: 'names' } }], + reservedParameters: [ + { name: '_type', fixed: 'int256' }, + { name: '_times', fixed: '1000000000000000000' }, + ], + preProcessingSpecifications: [ + { + environment: 'Node', + value: 'const output = {};', + timeoutMs: 5000, + }, + ], + postProcessingSpecifications: [ + { + environment: 'Node', + value: 'const output = input[endpointParameters.name].value;', + timeoutMs: 5000, + }, + ], + }, + ], + }, + ], + apiCredentials: [ + { + oisTitle: 'Nodary', + securitySchemeName: 'NodarySecurityScheme1ApiKey', + securitySchemeValue: 'invalid-api-key', + }, + ], +}; + +export const nodaryTemplateRequestResponseData: PerformApiCallSuccess = { + data: { + 'WTI/USD': { value: 89.06, timestamp: 1695727965885, category: 'commodity' }, + 'XAG/USD': { value: 23.01525, timestamp: 1695728005891, category: 'commodity' }, + 'XAU/USD': { value: 1912.425, timestamp: 1695728005891, category: 'commodity' }, + }, +}; + +export const nodaryTemplateRequestErrorResponse: ApiCallErrorResponse = { + errorMessage: 'Invalid API key', + success: false, +}; + +export const nodaryTemplateResponses: TemplateResponse[] = [ + [ + '0xcc35bd1800c06c12856a87311dd95bfcbb3add875844021d59a929d79f3c99bd', + { + data: { + encodedValue: '0x000000000000000000000000000000000000000000000004d3f4ae23d04a0000', + rawValue: 89.06, + values: ['89060000000000000000'], + }, + success: true, + }, + ], + [ + '0x086130c54864b2129f8ac6d8d7ab819fa8181bbe676e35047b1bca4c31d51c66', + { + data: { + encodedValue: '0x0000000000000000000000000000000000000000000000013f6697ef5acf2000', + rawValue: 23.01525, + values: ['23015250000000000000'], + }, + success: true, + }, + ], + [ + '0x1d65c1f1e127a41cebd2339f823d0290322c63f3044380cbac105db8e522ebb9', + { + data: { + encodedValue: '0x000000000000000000000000000000000000000000000067ac3a7509c06a8000', + rawValue: 1912.425, + values: ['1912425000000000000000'], + }, + success: true, + }, + ], +]; + +export const nodarySignedTemplateResponses: SignedResponse[] = [ + [ + '0xcc35bd1800c06c12856a87311dd95bfcbb3add875844021d59a929d79f3c99bd', + { + encodedValue: '0x000000000000000000000000000000000000000000000004d3f4ae23d04a0000', + signature: + '0xaa5f77b3141527b67903699c77f2fd66e1cdcdb71c7d586addc4e5f6b0a5ca25537495389753795b6c23240a45bb5a1295a9c2aa526385702c54863a0f94f45d1c', + timestamp: '1674172800', // 2023-01-20 + }, + ], + [ + '0x086130c54864b2129f8ac6d8d7ab819fa8181bbe676e35047b1bca4c31d51c66', + { + encodedValue: '0x0000000000000000000000000000000000000000000000013f6697ef5acf2000', + signature: + '0x99878ea2e49e238d11f3c81b5232779b2a87c71e6007b35fccef107b5a2ebab23accd2d2db4d7de25014a513645cb91fdc959487496299750f069bdfabc714da1b', + timestamp: '1674172800', // 2023-01-20 + }, + ], + [ + '0x1d65c1f1e127a41cebd2339f823d0290322c63f3044380cbac105db8e522ebb9', + { + encodedValue: '0x000000000000000000000000000000000000000000000067ac3a7509c06a8000', + signature: + '0x9f48048354e4716077bb0c9201ae0c59f8ae29c754f272f9ed376cd22535526a5df1d3fb6397b4e7391b6317deb1e223f32f94f9c66b3f3fc1af154ad0429f201c', + timestamp: '1674172800', // 2023-01-20 + }, + ], +]; + +// Axios parses the response body to JSON and automatically fills other request properties which are not needed for +// testing. +export const signedApiResponse: Partial = { + status: 201, + headers: { + 'content-type': 'application/json', + 'access-control-allow-origin': '*', + 'access-control-allow-methods': '*', + }, + data: { count: 3 }, +};