Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion packages/xrpl/src/client/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -343,10 +343,11 @@ class Client extends EventEmitter<EventTypes> {
const request = {
...req,
account:
typeof req.account === 'string'
'account' in req && typeof req.account === 'string'
? ensureClassicAddress(req.account)
: undefined,
api_version: req.api_version ?? this.apiVersion,
strict: req.strict ?? false,
}
const response = await this.connection.request<R, T>(request)

Expand Down
3 changes: 2 additions & 1 deletion packages/xrpl/src/models/methods/baseMethod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { LedgerIndex } from '../common'
import type { Request } from '.'

export interface BaseRequest {
[x: string]: unknown
/**
* A unique value to identify this request. The response to this request uses
* the same id field. This way, even if responses arrive out of order, you
Expand All @@ -12,6 +11,8 @@ export interface BaseRequest {
id?: number | string
/** The name of the API method. */
command: string
/** Enable strict-mode. If omitted, use false. */
strict?: boolean
/** The API version to use. If omitted, use version 1. */
api_version?: number
}
Expand Down
7 changes: 6 additions & 1 deletion packages/xrpl/src/models/methods/pathFind.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,14 @@ import { Amount, Path } from '../common'

import { BaseRequest, BaseResponse } from './baseMethod'

export type Subcommand = 'create' | 'status' | 'close'

interface BasePathFindRequest extends BaseRequest {
command: 'path_find'
subcommand: string
/**
* Type of pathfinding request
*/
subcommand: Subcommand
}

/** Start sending pathfinding information. */
Expand Down
15 changes: 13 additions & 2 deletions packages/xrpl/src/models/methods/subscribe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { OfferCreate, Transaction } from '../transactions'
import { TransactionMetadata } from '../transactions/metadata'

import type { BaseRequest, BaseResponse } from './baseMethod'
import { ManifestRequest } from './manifest'
import type { ManifestRequest } from './manifest'

export interface SubscribeBook {
/**
Expand Down Expand Up @@ -454,6 +454,16 @@ export interface PathFindStream extends BaseStream {
}
}

/**
* The manifest stream sends messages when a new manifest has been processed or updated.
* The message contains the public key, either the master public key or ephemeral public key.
*
* @category Streams
*/
export interface ManifestStream extends BaseStream, ManifestRequest {
type: 'manifestReceived'
}

/**
* @category Streams
*/
Expand All @@ -465,6 +475,7 @@ export type Stream =
| PeerStatusStream
| OrderBookStream
| ConsensusStream
| ManifestStream

export type EventTypes =
| 'connected'
Expand Down Expand Up @@ -493,7 +504,7 @@ export type OnEventToListenerMap<T extends EventTypes> = T extends 'connected'
: T extends 'consensusPhase'
? (consensus: ConsensusStream) => void
: T extends 'manifestReceived'
? (manifest: ManifestRequest) => void
? (manifest: ManifestStream) => void
: T extends 'path_find'
? (path: PathFindStream) => void
: T extends 'error'
Expand Down
1 change: 1 addition & 0 deletions packages/xrpl/test/client/subscribe.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ describe('Client subscription', function () {
await new Promise<void>((resolve) => {
// @es-expect-error Seems like a valid method
testContext.client.on('manifestReceived', (path) => {
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- TODO: Refactor as this seems pointless
assert(path.type === 'manifestReceived')
resolve()
})
Expand Down
4 changes: 4 additions & 0 deletions packages/xrpl/test/integration/requests/ledger.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ describe('ledger', function () {
assert.typeOf(ledgerResponse.result.ledger_index, 'number')

const ledger = ledgerResponse.result.ledger as Ledger & {
// Add index signature to request to iterate through keys and asset types
[index: string]: unknown
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar to my comments in other test files, I don't see the need for this additional kv pair

accepted: boolean
hash: string
seqNum: string
Expand Down Expand Up @@ -118,6 +120,8 @@ describe('ledger', function () {
assert.typeOf(ledgerResponse.result.ledger_index, 'number')

const ledger = ledgerResponse.result.ledger as LedgerV1 & {
// Add index signature to request to iterate through keys and asset types
[index: string]: unknown
accepted: boolean
hash: string
seqNum: string
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ describe('ripple_path_find', function () {
const wallet2 = await generateFundedWallet(testContext.client)
const ripplePathFind: RipplePathFindRequest = {
command: 'ripple_path_find',
subcommand: 'create',
source_account: testContext.wallet.classicAddress,
destination_account: wallet2.classicAddress,
destination_amount: '100',
Expand Down
41 changes: 21 additions & 20 deletions packages/xrpl/test/integration/requests/serverInfo.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import { assert } from 'chai'
import omit from 'lodash/omit'

import { type ServerInfoRequest, type ServerInfoResponse } from '../../../src'
import {
StateAccountingFinal,
type ServerInfoRequest,
type ServerInfoResponse,
} from '../../../src'
import serverUrl from '../serverUrl'
import {
setupClient,
Expand All @@ -26,7 +30,9 @@ describe('server_info (rippled)', function () {
const request: ServerInfoRequest = {
command: 'server_info',
}

const response = await testContext.client.request(request)

const expected: ServerInfoResponse = {
id: 0,
result: {
Expand Down Expand Up @@ -138,29 +144,24 @@ describe('server_info (rippled)', function () {
for (const obj of response.result.info.load?.job_types ?? []) {
assert.equal(typeof obj.job_type, 'string')
}

// state_accounting
Object.keys(response.result.info.state_accounting).forEach(function (
key,
) {
assert.equal(
typeof response.result.info.state_accounting[key].duration_us,
'string',
)
assert.equal(
typeof response.result.info.state_accounting[key].transitions,
'string',
)
const state_accounting: StateAccountingFinal & {
[index: string]: unknown
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need this catch-all type here? I have found that we can iterate over the keys without using this additional key-value pair.

} = response.result.info.state_accounting
Object.keys(state_accounting).forEach(function (key) {
// @ts-expect-error -- non-iterative type
assert.equal(typeof state_accounting[key].duration_us, 'string')
// @ts-expect-error -- non-iterative type
assert.equal(typeof state_accounting[key].transitions, 'string')
})

// validated_ledger
assert.equal(typeof response.result.info.validated_ledger?.hash, 'string')
for (const key of Object.keys(
omit(response.result.info.validated_ledger, 'hash'),
)) {
assert.equal(
typeof response.result.info.validated_ledger?.[key],
'number',
)
const validated_ledger = response.result.info.validated_ledger
assert.equal(typeof validated_ledger?.hash, 'string')
for (const key of Object.keys(omit(validated_ledger, 'hash'))) {
// @ts-expect-error -- non-iterative type
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This type of @ts-expect-error statements can be removed.

assert.equal(typeof validated_ledger[key], 'number')
}
},
TIMEOUT,
Expand Down
3 changes: 3 additions & 0 deletions packages/xrpl/test/integration/requests/serverState.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,10 +135,12 @@ describe('server_state', function () {
key,
) {
assert.equal(
// @ts-expect-error -- non-iterative type
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need all these declarations?

typeof response.result.state.state_accounting[key].duration_us,
'string',
)
assert.equal(
// @ts-expect-error -- non-iterative type
typeof response.result.state.state_accounting[key].transitions,
'string',
)
Expand All @@ -153,6 +155,7 @@ describe('server_state', function () {
omit(response.result.state.validated_ledger, 'hash'),
)) {
assert.equal(
// @ts-expect-error -- non-iterative type
typeof response.result.state.validated_ledger?.[key],
'number',
)
Expand Down
74 changes: 74 additions & 0 deletions packages/xrpl/test/models/typeUtility.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { assert } from 'chai'

import type { BaseRequest } from '../../src/models'

// Helper type assertion function (only for TS, no runtime impact)
function assertType<T>(value: T): void {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since value is unused, we could perhaps use _ ?

// No runtime implementation needed
}

/**
* Type Utility Verification Testing.
*
* Providing ts verification testing for type utilities.
* Chai is not capable of properly type checking non-runtime assertions but this is the closest to simulate behavior.
* Intention is to prevent index signatures on any type models - breaks Omit<> utilty typing
*/
describe('BaseRequest', function () {
it(`pick utility`, function () {
// Define a value with the picked type
const picked: Pick<BaseRequest, 'command'> = { command: 'test' }

// Verify the property exists
assert.property(picked, 'command')
assert.typeOf(picked.command, 'string')

// Verify TypeScript enforces the picked type
assert.isUndefined((picked as any).id)
})

it(`omit utility`, function () {
type RequestMinusCommand = Omit<BaseRequest, 'command'>
const control: BaseRequest = { command: 'control' }
// Define a value with the omitted type
const omitted: RequestMinusCommand = {
id: 1,
api_version: 2,
}

// Verify command is not present
assert.notProperty(omitted, 'command')
assert(typeof control.command === 'string')

// Verify id properties remain
assert.property(omitted, 'id')
assert.typeOf(omitted.id, 'number')

// Verify api_version properties remain
assert.property(omitted, 'api_version')
assert.typeOf(omitted.api_version, 'number')

type HasCommand = 'command' extends keyof BaseRequest ? true : false
type HasNoCommand = 'command' extends keyof RequestMinusCommand
? false
: true

assertType<true>({} as HasCommand)
assertType<true>({} as HasNoCommand)
Comment on lines +56 to +57
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand the intent of these two tests. Are you testing that the empty type {} is compatible with both HasCommand and HasNoCommand? How is this useful?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, without an additional dependency, its kind tough the check correct type assertions that are not caught during runtime. Index signature removes type-checking of foreign keys and is not caught during runtime. Trying to write unit tests that are runtime-based is quite difficult for this scenerio.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, I understand. I see the value of the rest of the tests. I didn't understand the need for these two lines only.

})

it(`partial utility`, function () {
type OptionalRequest = Partial<BaseRequest>

// Verify Partial makes all properties optional
const empty: OptionalRequest = {}
assert.notProperty(empty, 'command')
assert.notProperty(empty, 'id')
assert.notProperty(empty, 'api_version')

// Verify optional nature at compile-time
// This should compile without error
const minimal: OptionalRequest = {}
assert.isUndefined(minimal.command)
})
})
Loading