-
Notifications
You must be signed in to change notification settings - Fork 8
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add unit tests for api-requests modules #44
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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, | ||
}); | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,31 +1,30 @@ | ||
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'])); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The example file is uses pre/post processing to only make a single API call and if you log the full |
||
const processedPayload = await preProcessApiSpecifications(payload); | ||
getLogger().debug('Performing API call', { aggregateApiCall: payload.aggregatedApiCall }); | ||
getLogger().debug('Performing API call', { processedPayload: processedPayload }); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This was a bug before - we should have logged the processed payloed. |
||
return node.api.performApiCall(processedPayload); | ||
}; | ||
|
||
export const makeTemplateRequests = async (signedApiUpdate: SignedApiUpdate): Promise<TemplateResponses> => { | ||
export const makeTemplateRequests = async (signedApiUpdate: SignedApiUpdate): Promise<TemplateResponse[]> => { | ||
const { | ||
config: { beacons, endpoints, templates, ois, apiCredentials }, | ||
apiLimiters, | ||
} = getState(); | ||
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]!; | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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, | ||
}); | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,15 +9,13 @@ 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'; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This broke the tests, since the |
||
import { initializeLogger } from './logger'; | ||
|
||
export async function main() { | ||
const config = await loadConfig(); | ||
initializeLogger(config); | ||
initializeState(config); | ||
|
||
initializeWallet(); | ||
initiateFetchingBeaconData(); | ||
initiateUpdatingSignedApi(); | ||
} | ||
|
This file was deleted.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tip: If you want to know what is actually logged, you can do:
which will forward the logs to console log. I used that a bit.