2023-03-20 17:50:52 +01:00
/* eslint-disable jest/require-top-level-describe, jest/no-export */
2023-04-14 18:21:29 +02:00
import {
ProviderType ,
withMockedCommunications ,
withNetworkClient ,
} from './helpers' ;
type TestsForRpcMethodThatCheckForBlockHashInResponseOptions = {
providerType : ProviderType ;
numberOfParameters : number ;
} ;
2023-03-20 17:50:52 +01:00
/ * *
* Defines tests which exercise the behavior exhibited by an RPC method that
* use ` blockHash ` in the response data to determine whether the response is
* cacheable .
*
* @param method - The name of the RPC method under test .
* @param additionalArgs - Additional arguments .
* @param additionalArgs . numberOfParameters - The number of parameters supported
* by the method under test .
* @param additionalArgs . providerType - The type of provider being tested ;
* either ` infura ` or ` custom ` ( default : "infura" ) .
* /
export function testsForRpcMethodsThatCheckForBlockHashInResponse (
2023-04-14 18:21:29 +02:00
method : string ,
{
numberOfParameters ,
providerType ,
} : TestsForRpcMethodThatCheckForBlockHashInResponseOptions ,
2023-03-20 17:50:52 +01:00
) {
if ( providerType !== 'infura' && providerType !== 'custom' ) {
throw new Error (
` providerType must be either "infura" or "custom", was " ${ providerType } " instead ` ,
) ;
}
it ( 'does not hit the RPC endpoint more than once for identical requests and it has a valid blockHash' , async ( ) = > {
const requests = [ { method } , { method } ] ;
const mockResult = { blockHash : '0x1' } ;
await withMockedCommunications ( { providerType } , async ( comms ) = > {
// The first time a block-cacheable request is made, the latest block
// number is retrieved through the block tracker first. It doesn't
// matter what this is — it's just used as a cache key.
comms . mockNextBlockTrackerRequest ( ) ;
comms . mockRpcCall ( {
request : requests [ 0 ] ,
response : { result : mockResult } ,
} ) ;
const results = await withNetworkClient (
{ providerType } ,
( { makeRpcCallsInSeries } ) = > makeRpcCallsInSeries ( requests ) ,
) ;
expect ( results ) . toStrictEqual ( [ mockResult , mockResult ] ) ;
} ) ;
} ) ;
it ( 'hits the RPC endpoint and does not reuse the result of a previous request if the latest block number was updated since' , async ( ) = > {
const requests = [ { method } , { method } ] ;
const mockResults = [ { blockHash : '0x100' } , { blockHash : '0x200' } ] ;
await withMockedCommunications ( { providerType } , async ( comms ) = > {
// Note that we have to mock these requests in a specific order. The
// first block tracker request occurs because of the first RPC
// request. The second block tracker request, however, does not occur
// because of the second RPC request, but rather because we call
// `clock.runAll()` below.
comms . mockNextBlockTrackerRequest ( { blockNumber : '0x1' } ) ;
comms . mockRpcCall ( {
request : requests [ 0 ] ,
response : { result : mockResults [ 0 ] } ,
} ) ;
comms . mockNextBlockTrackerRequest ( { blockNumber : '0x2' } ) ;
comms . mockRpcCall ( {
request : requests [ 1 ] ,
response : { result : mockResults [ 1 ] } ,
} ) ;
const results = await withNetworkClient (
{ providerType } ,
async ( client ) = > {
const firstResult = await client . makeRpcCall ( requests [ 0 ] ) ;
// Proceed to the next iteration of the block tracker so that a new
// block is fetched and the current block is updated.
client . clock . runAll ( ) ;
const secondResult = await client . makeRpcCall ( requests [ 1 ] ) ;
return [ firstResult , secondResult ] ;
} ,
) ;
expect ( results ) . toStrictEqual ( mockResults ) ;
} ) ;
} ) ;
it ( 'does not reuse the result of a previous request if result.blockHash was null' , async ( ) = > {
const requests = [ { method } , { method } ] ;
const mockResults = [
{ blockHash : null , extra : 'some value' } ,
{ blockHash : '0x100' , extra : 'some other value' } ,
] ;
await withMockedCommunications ( { providerType } , async ( comms ) = > {
// The first time a block-cacheable request is made, the latest block
// number is retrieved through the block tracker first. It doesn't
// matter what this is — it's just used as a cache key.
comms . mockNextBlockTrackerRequest ( ) ;
comms . mockRpcCall ( {
request : requests [ 0 ] ,
response : { result : mockResults [ 0 ] } ,
} ) ;
comms . mockRpcCall ( {
request : requests [ 1 ] ,
response : { result : mockResults [ 1 ] } ,
} ) ;
const results = await withNetworkClient (
{ providerType } ,
( { makeRpcCallsInSeries } ) = > makeRpcCallsInSeries ( requests ) ,
) ;
expect ( results ) . toStrictEqual ( mockResults ) ;
} ) ;
} ) ;
it ( 'does not reuse the result of a previous request if result.blockHash was undefined' , async ( ) = > {
const requests = [ { method } , { method } ] ;
const mockResults = [
{ extra : 'some value' } ,
{ blockHash : '0x100' , extra : 'some other value' } ,
] ;
await withMockedCommunications ( { providerType } , async ( comms ) = > {
// The first time a block-cacheable request is made, the latest block
// number is retrieved through the block tracker first. It doesn't
// matter what this is — it's just used as a cache key.
comms . mockNextBlockTrackerRequest ( ) ;
comms . mockRpcCall ( {
request : requests [ 0 ] ,
response : { result : mockResults [ 0 ] } ,
} ) ;
comms . mockRpcCall ( {
request : requests [ 1 ] ,
response : { result : mockResults [ 1 ] } ,
} ) ;
const results = await withNetworkClient (
{ providerType } ,
( { makeRpcCallsInSeries } ) = > makeRpcCallsInSeries ( requests ) ,
) ;
expect ( results ) . toStrictEqual ( mockResults ) ;
} ) ;
} ) ;
it ( 'does not reuse the result of a previous request if result.blockHash was "0x0000000000000000000000000000000000000000000000000000000000000000"' , async ( ) = > {
const requests = [ { method } , { method } ] ;
const mockResults = [
{
blockHash :
'0x0000000000000000000000000000000000000000000000000000000000000000' ,
extra : 'some value' ,
} ,
{ blockHash : '0x100' , extra : 'some other value' } ,
] ;
await withMockedCommunications ( { providerType } , async ( comms ) = > {
// The first time a block-cacheable request is made, the latest block
// number is retrieved through the block tracker first. It doesn't
// matter what this is — it's just used as a cache key.
comms . mockNextBlockTrackerRequest ( ) ;
comms . mockRpcCall ( {
request : requests [ 0 ] ,
response : { result : mockResults [ 0 ] } ,
} ) ;
comms . mockRpcCall ( {
request : requests [ 1 ] ,
response : { result : mockResults [ 1 ] } ,
} ) ;
const results = await withNetworkClient (
{ providerType } ,
( { makeRpcCallsInSeries } ) = > makeRpcCallsInSeries ( requests ) ,
) ;
expect ( results ) . toStrictEqual ( mockResults ) ;
} ) ;
} ) ;
for ( const emptyValue of [ null , undefined , '\u003cnil\u003e' ] ) {
it ( ` does not retry an empty response of " ${ emptyValue } " ` , async ( ) = > {
const request = { method } ;
const mockResult = emptyValue ;
await withMockedCommunications ( { providerType } , async ( comms ) = > {
// The first time a block-cacheable request is made, the latest block
// number is retrieved through the block tracker first. It doesn't
// matter what this is — it's just used as a cache key.
comms . mockNextBlockTrackerRequest ( ) ;
comms . mockRpcCall ( {
request ,
response : { result : mockResult } ,
} ) ;
const result = await withNetworkClient (
{ providerType } ,
( { makeRpcCall } ) = > makeRpcCall ( request ) ,
) ;
expect ( result ) . toStrictEqual ( mockResult ) ;
} ) ;
} ) ;
it ( ` does not reuse the result of a previous request if it was " ${ emptyValue } " ` , async ( ) = > {
const requests = [ { method } , { method } ] ;
const mockResults = [ emptyValue , { blockHash : '0x100' } ] ;
await withMockedCommunications ( { providerType } , async ( comms ) = > {
// The first time a block-cacheable request is made, the latest block
// number is retrieved through the block tracker first. It doesn't
// matter what this is — it's just used as a cache key.
comms . mockNextBlockTrackerRequest ( ) ;
comms . mockRpcCall ( {
request : requests [ 0 ] ,
response : { result : mockResults [ 0 ] } ,
} ) ;
comms . mockRpcCall ( {
request : requests [ 1 ] ,
response : { result : mockResults [ 1 ] } ,
} ) ;
const results = await withNetworkClient (
{ providerType } ,
( { makeRpcCallsInSeries } ) = > makeRpcCallsInSeries ( requests ) ,
) ;
expect ( results ) . toStrictEqual ( mockResults ) ;
} ) ;
} ) ;
}
for ( const paramIndex of [ . . . Array ( numberOfParameters ) . keys ( ) ] ) {
it ( ` does not reuse the result of a previous request with a valid blockHash if parameter at index " ${ paramIndex } " differs ` , async ( ) = > {
const firstMockParams = [
. . . new Array ( numberOfParameters ) . fill ( 'some value' ) ,
] ;
const secondMockParams = firstMockParams . slice ( ) ;
secondMockParams [ paramIndex ] = 'another value' ;
const requests = [
{
method ,
params : firstMockParams ,
} ,
{ method , params : secondMockParams } ,
] ;
const mockResults = [ { blockHash : '0x100' } , { blockHash : '0x200' } ] ;
await withMockedCommunications ( { providerType } , async ( comms ) = > {
// The first time a block-cacheable request is made, the latest block
// number is retrieved through the block tracker first. It doesn't
// matter what this is — it's just used as a cache key.
comms . mockNextBlockTrackerRequest ( ) ;
comms . mockRpcCall ( {
request : requests [ 0 ] ,
response : { result : mockResults [ 0 ] } ,
} ) ;
comms . mockRpcCall ( {
request : requests [ 1 ] ,
response : { result : mockResults [ 1 ] } ,
} ) ;
const results = await withNetworkClient (
{ providerType } ,
( { makeRpcCallsInSeries } ) = > makeRpcCallsInSeries ( requests ) ,
) ;
expect ( results ) . toStrictEqual ( [ mockResults [ 0 ] , mockResults [ 1 ] ] ) ;
} ) ;
} ) ;
}
}