test: subscription standalone integration tests (#1690)

Add tests for pathFind and other subscriptions that can be tested with a standalone node.
This commit is contained in:
Jackson Mills
2021-10-12 14:57:46 -07:00
committed by GitHub
parent 1a8bbfa43e
commit ede1500afa
6 changed files with 292 additions and 8 deletions

View File

@@ -1,4 +1,5 @@
import type { Amount, Currency, Path, StreamType } from '../common'
import Offer from '../ledger/offer'
import { OfferCreate, Transaction } from '../transactions'
import TransactionMetadata from '../transactions/metadata'
@@ -71,9 +72,12 @@ export interface SubscribeRequest extends BaseRequest {
* @category Responses
*/
export interface SubscribeResponse extends BaseResponse {
result: Record<string, never> | Stream
// eslint-disable-next-line @typescript-eslint/ban-types -- actually should be an empty object
result: {} | LedgerStreamResponse | BooksSnapshot
}
type BooksSnapshot = Offer[]
interface BaseStream {
type: string
}
@@ -124,6 +128,46 @@ export interface LedgerStream extends BaseStream {
validated_ledgers?: string
}
/**
* This response mirrors the LedgerStream, except it does NOT include the 'type' nor 'txn_count' fields.
*/
// eslint-disable-next-line import/no-unused-modules -- Detailed enough to be worth exporting for end users.
export interface LedgerStreamResponse {
/**
* The reference transaction cost as of this ledger version, in drops of XRP.
* If this ledger version includes a SetFee pseudo-transaction the new.
* Transaction cost applies starting with the following ledger version.
*/
fee_base: number
/** The reference transaction cost in "fee units". */
fee_ref: number
/** The identifying hash of the ledger version that was closed. */
ledger_hash: string
/** The ledger index of the ledger that was closed. */
ledger_index: number
/** The time this ledger was closed, in seconds since the Ripple Epoch. */
ledger_time: number
/**
* The minimum reserve, in drops of XRP, that is required for an account. If
* this ledger version includes a SetFee pseudo-transaction the new base reserve
* applies starting with the following ledger version.
*/
reserve_base: number
/**
* The owner reserve for each object an account owns in the ledger, in drops
* of XRP. If the ledger includes a SetFee pseudo-transaction the new owner
* reserve applies after this ledger.
*/
reserve_inc: number
/**
* Range of ledgers that the server has available. This may be a disjoint
* sequence such as 24900901-24900984,24901116-24901158. This field is not
* returned if the server is not connected to the network, or if it is
* connected but has not yet obtained a ledger from the network.
*/
validated_ledgers?: string
}
/**
* The validations stream sends messages whenever it receives validation
* messages, also called validation votes, regardless of whether or not the
@@ -344,7 +388,9 @@ export interface PathFindStream extends BaseStream {
* Array of objects with suggested paths to take. If empty, then no paths
* were found connecting the source and destination accounts.
*/
alternatives: {
alternatives:
| []
| {
paths_computed: Path[]
source_amount: Amount
}

View File

@@ -197,7 +197,7 @@ async function processSuccessfulResponse(
wallet: walletToFund,
balance: await getUpdatedBalance(
client,
walletToFund.getClassicAddress(),
walletToFund.classicAddress,
startingBalance,
),
})

View File

@@ -34,6 +34,7 @@ export * from './requests/noRippleCheck'
export * from './requests/pathFind'
export * from './requests/ripplePathFind'
export * from './requests/submit'
export * from './requests/subscribe'
export * from './requests/tx'
export * from './requests/utility'

View File

@@ -1,11 +1,16 @@
import { assert } from 'chai'
import _ from 'lodash'
import { PathFindRequest, PathFindResponse } from 'xrpl-local'
import {
PathFindRequest,
PathFindResponse,
Client,
PathFindStream,
} from 'xrpl-local'
import serverUrl from '../serverUrl'
import { setupClient, suiteClientSetup, teardownClient } from '../setup'
import { generateFundedWallet } from '../utils'
import { generateFundedWallet, ledgerAccept, subscribeDone } from '../utils'
// how long before each test case times out
const TIMEOUT = 20000
@@ -44,4 +49,61 @@ describe('path_find', function () {
assert.deepEqual(response, expectedResponse)
})
/**
* For other stream style tests look at integration/requests/subscribe.ts
* Note: This test uses '.then' to avoid awaits in order to use 'done' style tests.
*/
it('path_find stream succeeds', function (done) {
generateFundedWallet(this.client)
.then((wallet2) => {
const pathFind: PathFindRequest = {
command: 'path_find',
subcommand: 'create',
source_account: this.wallet.getClassicAddress(),
destination_account: wallet2.getClassicAddress(),
destination_amount: '100',
}
this.client.request(pathFind).then((response) => {
const expectedResponse: PathFindResponse = {
id: response.id,
status: 'success',
type: 'response',
result: {
alternatives: response.result.alternatives,
destination_account: pathFind.destination_account,
destination_amount: pathFind.destination_amount,
source_account: pathFind.source_account,
full_reply: false,
id: response.id,
},
}
const expectedStreamResult: PathFindStream = {
type: 'path_find',
source_account: pathFind.source_account,
destination_account: pathFind.destination_account,
destination_amount: pathFind.destination_amount,
full_reply: true,
id: 10,
alternatives: [],
}
const client: Client = this.client
client.on('path_find', (path) => {
assert.equal(path.type, 'path_find')
assert.deepEqual(
_.omit(path, 'id'),
_.omit(expectedStreamResult, 'id'),
)
subscribeDone(this.client, done)
})
assert.deepEqual(response, expectedResponse)
})
})
.then(() => {
ledgerAccept(this.client)
})
})
})

View File

@@ -0,0 +1,170 @@
import { assert } from 'chai'
import _ from 'lodash'
import {
Client,
OfferCreate,
SubscribeRequest,
Wallet,
SubscribeResponse,
} from 'xrpl-local'
import { StreamType } from 'xrpl-local/models/common'
import serverUrl from '../serverUrl'
import { setupClient, suiteClientSetup, teardownClient } from '../setup'
import { ledgerAccept, subscribeDone, testTransaction } from '../utils'
// how long before each test case times out
const TIMEOUT = 20000
// Note: This test use '.then' to avoid awaits in order to use 'done' style tests.
// eslint-disable-next-line max-params -- Helps keep things well-typed
async function createTxHandlerTest(
client: Client,
wallet: Wallet,
done: Mocha.Done,
subscriptionStream: StreamType,
): Promise<void> {
const txStream = 'transaction'
client.on(txStream, (tx) => {
assert.equal(tx.type, txStream)
subscribeDone(client, done)
})
const request: SubscribeRequest = {
command: 'subscribe',
streams: [subscriptionStream],
accounts: [wallet.getClassicAddress()],
}
client.request(request).then((response) => {
assert.equal(response.status, 'success')
assert.equal(response.type, 'response')
assert.deepEqual(response.result, {})
})
}
describe('subscribe', function () {
this.timeout(TIMEOUT)
before(suiteClientSetup)
beforeEach(_.partial(setupClient, serverUrl))
afterEach(teardownClient)
/**
* Subscribe streams which are not testable with just a standalone node:
* (If that changes, please add tests for these streams).
*
* 'consensus'
* 'manifests'
* 'peer_status'
* 'validations'
* 'server'.
*/
it('Successfully Subscribes', async function () {
const response: SubscribeResponse = await this.client.request({
command: 'subscribe',
})
assert.deepEqual(response.result, {})
assert.equal(response.type, 'response')
})
it('Successfully Unsubscribes', async function () {
const response = await this.client.request({
command: 'unsubscribe',
})
assert.deepEqual(response.result, {})
assert.equal(response.type, 'response')
})
it('Emits transaction', function (done) {
const streamType = 'transactions'
createTxHandlerTest(this.client, this.wallet, done, streamType).then(() => {
// Trigger the event
const tx: OfferCreate = {
TransactionType: 'OfferCreate',
Account: this.wallet.getClassicAddress(),
TakerGets: '13100000',
TakerPays: {
currency: 'USD',
issuer: this.wallet.getClassicAddress(),
value: '10',
},
}
testTransaction(this.client, tx, this.wallet)
})
})
it('Emits transaction on transactions_proposed', function (done) {
createTxHandlerTest(
this.client,
this.wallet,
done,
'transactions_proposed',
).then(() => {
const tx: OfferCreate = {
TransactionType: 'OfferCreate',
Account: this.wallet.getClassicAddress(),
TakerGets: '13100000',
TakerPays: {
currency: 'USD',
issuer: this.wallet.getClassicAddress(),
value: '10',
},
}
// The transactions_proposed stream should trigger the transaction handler WITHOUT ledgerAccept
const client: Client = this.client
client.submit(this.wallet, tx)
})
})
// Note: This test use '.then' to avoid awaits in order to use 'done' style tests.
it('Emits ledger', function (done) {
const request: SubscribeRequest = {
command: 'subscribe',
streams: ['ledger'],
accounts: [this.wallet.getClassicAddress()],
}
this.client.request(request).then((response) => {
// Explicitly checking that there are only known fields in the return
const expectedResult = {
fee_base: response.result.fee_base,
fee_ref: response.result.fee_ref,
ledger_hash: response.result.ledger_hash,
ledger_index: response.result.ledger_index,
ledger_time: response.result.ledger_time,
reserve_base: response.result.reserve_base,
reserve_inc: response.result.reserve_inc,
validated_ledgers: response.result.validated_ledgers,
}
assert.equal(response.type, 'response')
assert.deepEqual(response.result, expectedResult)
const client: Client = this.client
client.on('ledgerClosed', (ledger) => {
// Fields that are expected to change between the initial test and now are updated
assert.deepEqual(ledger, {
...expectedResult,
type: 'ledgerClosed',
txn_count: ledger.txn_count,
ledger_hash: ledger.ledger_hash,
ledger_index: parseInt(expectedResult.ledger_index, 10) + 1,
ledger_time: ledger.ledger_time,
validated_ledgers: ledger.validated_ledgers,
})
subscribeDone(this.client, done)
})
// Trigger the event
ledgerAccept(this.client)
})
})
})

View File

@@ -14,6 +14,11 @@ export async function ledgerAccept(client: Client): Promise<void> {
await client.connection.request(request)
}
export function subscribeDone(client: Client, done: Mocha.Done): void {
client.removeAllListeners()
done()
}
export async function fundAccount(
client: Client,
wallet: Wallet,