fix: resolves TODOs in ./src (#1623)

fix: resolves TODOs in ./src
This commit is contained in:
Nathan Nichols
2021-10-15 17:18:22 -05:00
committed by GitHub
parent b71147416c
commit 6e57b4e2ee
32 changed files with 136 additions and 610 deletions

View File

@@ -1,51 +0,0 @@
'use strict';
const fs = require('fs');
const path = require('path');
const execSync = require('child_process').execSync;
const ejs = require('ejs');
const renderFromPaths =
require('json-schema-to-markdown-table').renderFromPaths;
const ROOT = path.dirname(path.normalize(__dirname));
function strip(string) {
return string.replace(/^\s+|\s+$/g, '');
}
function importFile(relativePath) {
const absolutePath = path.join(ROOT, relativePath);
return strip(fs.readFileSync(absolutePath).toString('utf-8'));
}
function renderFixture(fixtureRelativePath) {
const json = importFile(path.join('test', 'fixtures', fixtureRelativePath));
return '\n```json\n' + json + '\n```\n';
}
function renderSchema(schemaRelativePath) {
const schemasPath = path.join(ROOT, 'src', 'common', 'schemas');
const schemaPath = path.join(schemasPath, schemaRelativePath);
return renderFromPaths(schemaPath, schemasPath);
}
function main() {
const locals = {
importFile: importFile,
renderFixture: renderFixture,
renderSchema: renderSchema
};
const indexPath = path.join(ROOT, 'docs', 'src', 'index.md.ejs');
ejs.renderFile(indexPath, locals, function(error, output) {
if (error) {
console.error(error);
process.exit(1);
} else {
const outputPath = path.join(ROOT, 'docs', 'index.md');
fs.writeFileSync(outputPath, output);
execSync('npm run doctoc', {cwd: ROOT});
process.exit(0);
}
});
}
main();

View File

@@ -1,18 +0,0 @@
#!/bin/bash
function checkEOL {
local changedFiles=$(git --no-pager diff --name-only -M100% --diff-filter=AM --relative $(git merge-base FETCH_HEAD origin/HEAD) FETCH_HEAD)
local result=0
for name in $changedFiles; do
grep -c -U -q $'\r' $name
if [ $? -eq 0 ]; then
echo "windows eol found in $name" >&2
result=1
fi
done
if [ $result -eq 1 ]; then
false
fi
}
checkEOL

View File

@@ -1,98 +0,0 @@
#!/bin/bash -ex
NODE_INDEX="$1"
TOTAL_NODES="$2"
function checkEOL {
./scripts/checkeol.sh
}
lint() {
echo "tslint $(node_modules/.bin/tslint --version)"
npm run lint
}
unittest() {
# test "src"
# TODO: replace/upgrade mocha-junit-reporter
#mocha test --reporter mocha-junit-reporter --reporter-options mochaFile=$CIRCLE_TEST_REPORTS/test-results.xml
npm test --coverage
#npm run coveralls
# test compiled version in "dist/npm"
$(npm bin)/babel -D --optional runtime --ignore "**/node_modules/**" -d test-compiled/ test/
echo "--reporter spec --timeout 5000 --slow 500" > test-compiled/mocha.opts
mkdir -p test-compiled/node_modules
ln -nfs ../../dist/npm test-compiled/node_modules/xrpl-local
mocha --opts test-compiled/mocha.opts test-compiled
#compile tests for browser testing
#gulp build-min build-tests
#node --harmony test-compiled/mocked-server.js > /dev/null &
#echo "Running tests in PhantomJS"
#mocha-phantomjs test/localRunner.html
#echo "Running tests using minified version in PhantomJS"
#mocha-phantomjs test/localRunnerMin.html
#echo "Running tests in SauceLabs"
#http-server &
#npm run sauce
#pkill -f mocked-server.js
#pkill -f http-server
rm -rf test-compiled
}
integrationtest() {
mocha test/integration/integration.js
# run integration tests in PhantomJS
#gulp build-tests build-min
#echo "Running integragtion tests in PhantomJS"
#mocha-phantomjs test/localIntegrationRunner.html
}
doctest() {
mv docs/index.md docs/index.md.save
npm run docgen
mv docs/index.md docs/index.md.test
mv docs/index.md.save docs/index.md
cmp docs/index.md docs/index.md.test
rm docs/index.md.test
}
oneNode() {
checkEOL
doctest
lint
unittest
integrationtest
}
twoNodes() {
case "$NODE_INDEX" in
0) doctest; lint; integrationtest;;
1) checkEOL; unittest;;
*) echo "ERROR: invalid usage"; exit 2;;
esac
}
threeNodes() {
case "$NODE_INDEX" in
0) doctest; lint; integrationtest;;
1) checkEOL;;
2) unittest;;
*) echo "ERROR: invalid usage"; exit 2;;
esac
}
case "$TOTAL_NODES" in
"") oneNode;;
1) oneNode;;
2) twoNodes;;
3) threeNodes;;
*) echo "ERROR: invalid usage"; exit 2;;
esac

View File

@@ -1,18 +0,0 @@
echo "PUBLISH"
function exit_on_error {
res=$?
[[ ${res:-99} -eq 0 ]] || exit $res
}
rm -rf build
npm install
gulp
npm test
exit_on_error
echo ""
echo "publish to npm"
npm publish
exit_on_error

View File

@@ -1,18 +0,0 @@
echo "PUBLISH RELEASE CANDIDATE"
function exit_on_error {
res=$?
[[ ${res:-99} -eq 0 ]] || exit $res
}
rm -rf build
npm install
gulp
npm test
exit_on_error
echo ""
echo "publish rc to npm"
npm publish --tag beta
exit_on_error

View File

@@ -250,7 +250,7 @@ export class Connection extends EventEmitter {
this.ws = createWebSocket(this.url, this.config)
if (this.ws == null) {
throw new Error('Connect: created null websocket')
throw new XrplError('Connect: created null websocket')
}
this.ws.on('error', (error) => this.onConnectionFailed(error))
@@ -422,7 +422,7 @@ export class Connection extends EventEmitter {
*/
private async onceOpen(connectionTimeoutID: NodeJS.Timeout): Promise<void> {
if (this.ws == null) {
throw new Error('onceOpen: ws is null')
throw new XrplError('onceOpen: ws is null')
}
// Once the connection completes successfully, remove all old listeners
@@ -436,7 +436,7 @@ export class Connection extends EventEmitter {
// Handle a closed connection: reconnect if it was unexpected
this.ws.once('close', (code, reason) => {
if (this.ws == null) {
throw new Error('onceClose: ws is null')
throw new XrplError('onceClose: ws is null')
}
this.clearHeartbeatInterval()

View File

@@ -3,8 +3,7 @@
import * as assert from 'assert'
import { EventEmitter } from 'events'
import { ValidationError, XrplError } from '../errors'
import * as errors from '../errors'
import { NotFoundError, ValidationError, XrplError } from '../errors'
import {
Request,
Response,
@@ -85,18 +84,19 @@ import {
UnsubscribeResponse,
} from '../models/methods'
import { BaseRequest, BaseResponse } from '../models/methods/baseMethod'
import autofill from '../sugar/autofill'
import { getBalances, getXrpBalance } from '../sugar/balances'
import getFee from '../sugar/fee'
import getLedgerIndex from '../sugar/ledgerIndex'
import getOrderbook from '../sugar/orderbook'
import {
autofill,
ensureClassicAddress,
getLedgerIndex,
getOrderbook,
getFee,
getBalances,
getXrpBalance,
submit,
submitSigned,
submitReliable,
submitSignedReliable,
} from '../sugar/submit'
import { ensureClassicAddress } from '../sugar/utils'
} from '../sugar'
import fundWallet from '../wallet/fundWallet'
import {
@@ -394,7 +394,7 @@ class Client extends EventEmitter {
>(req: T, resp: U): Promise<U> {
if (!resp.result.marker) {
return Promise.reject(
new errors.NotFoundError('response does not have a next page'),
new NotFoundError('response does not have a next page'),
)
}
const nextPageRequest = { ...req, marker: resp.result.marker }

View File

@@ -33,7 +33,7 @@ export default class RequestManager {
public cancel(id: string | number): void {
const promise = this.promisesAwaitingResponse.get(id)
if (promise == null) {
throw new Error(`No existing promise with id ${id}`)
throw new XrplError(`No existing promise with id ${id}`)
}
clearTimeout(promise.timer)
this.deletePromise(id)
@@ -49,7 +49,7 @@ export default class RequestManager {
public resolve(id: string | number, response: Response): void {
const promise = this.promisesAwaitingResponse.get(id)
if (promise == null) {
throw new Error(`No existing promise with id ${id}`)
throw new XrplError(`No existing promise with id ${id}`)
}
clearTimeout(promise.timer)
promise.resolve(response)
@@ -66,7 +66,7 @@ export default class RequestManager {
public reject(id: string | number, error: Error): void {
const promise = this.promisesAwaitingResponse.get(id)
if (promise == null) {
throw new Error(`No existing promise with id ${id}`)
throw new XrplError(`No existing promise with id ${id}`)
}
clearTimeout(promise.timer)
// TODO: figure out how to have a better stack trace for an error

View File

@@ -1,8 +1,6 @@
/* eslint-disable max-classes-per-file -- Errors can be defined in the same file */
import { inspect } from 'util'
// TODO: replace all `new Error`s with `new XrplError`s
/**
* Base Error class for xrpl.js. All Errors thrown by xrpl.js should throw
* XrplErrors.

View File

@@ -34,10 +34,12 @@ export interface Signer {
}
export interface Memo {
Memo: {
MemoData?: string
MemoType?: string
MemoFormat?: string
}
}
export type StreamType =
| 'consensus'

View File

@@ -1,4 +1,4 @@
import { BaseRequest, BaseResponse } from './baseMethod'
import type { BaseRequest, BaseResponse } from './baseMethod'
/**
* The ping command returns an acknowledgement, so that clients can test the
@@ -17,7 +17,5 @@ export interface PingRequest extends BaseRequest {
* @category Responses
*/
export interface PingResponse extends BaseResponse {
// TODO: figure out if there's a better way to type this
// eslint-disable-next-line @typescript-eslint/ban-types -- actually should be an empty object
result: {}
result: { role?: string; unlimited?: boolean }
}

View File

@@ -1,5 +1,5 @@
import type { Amount, Currency, Path, StreamType } from '../common'
import Offer from '../ledger/offer'
import { Offer } from '../ledger'
import { OfferCreate, Transaction } from '../transactions'
import TransactionMetadata from '../transactions/metadata'
@@ -66,18 +66,17 @@ export interface SubscribeRequest extends BaseRequest {
url_password?: string
}
type BooksSnapshot = Offer[]
/**
* Response expected from a {@link SubscribeRequest}.
*
* @category Responses
*/
export interface SubscribeResponse extends BaseResponse {
// eslint-disable-next-line @typescript-eslint/ban-types -- actually should be an empty object
result: {} | LedgerStreamResponse | BooksSnapshot
result: Record<string, never> | LedgerStreamResponse | BooksSnapshot
}
type BooksSnapshot = Offer[]
interface BaseStream {
type: string
}

View File

@@ -1,6 +1,6 @@
import { Currency, StreamType } from '../common'
import { BaseRequest, BaseResponse } from './baseMethod'
import type { BaseRequest, BaseResponse } from './baseMethod'
interface Book {
taker_gets: Currency

View File

@@ -144,11 +144,10 @@ export interface BaseTransaction {
* validated or rejected.
*/
LastLedgerSequence?: number
// TODO: Make Memo match the format of Signer (By including the Memo: wrapper inside the Interface)
/**
* Additional arbitrary information used to identify this transaction.
*/
Memos?: Array<{ Memo: Memo }>
Memos?: Memo[]
/**
* Array of objects that represent a multi-signature which authorizes this
* transaction.

View File

@@ -1,6 +1,7 @@
import BigNumber from 'bignumber.js'
import type { Client } from '..'
import { XrplError } from '../errors'
const NUM_DECIMAL_PLACES = 6
const BASE_10 = 10
@@ -25,7 +26,7 @@ export default async function getFee(
const baseFee = serverInfo.validated_ledger?.base_fee_xrp
if (baseFee == null) {
throw new Error('getFee: Could not get base_fee_xrp from server_info')
throw new XrplError('getFee: Could not get base_fee_xrp from server_info')
}
const baseFeeXrp = new BigNumber(baseFee)

13
src/sugar/index.ts Normal file
View File

@@ -0,0 +1,13 @@
export { default as autofill } from './autofill'
export { getBalances, getXrpBalance } from './balances'
export { default as getFee } from './fee'
export { default as getLedgerIndex } from './ledgerIndex'
export { default as getOrderbook } from './orderbook'
export * from './submit'
export * from './utils'

View File

@@ -1,76 +0,0 @@
import { classicAddressToXAddress } from 'ripple-address-codec'
import keypairs from 'ripple-keypairs'
import ECDSA from '../ecdsa'
import { UnexpectedError } from '../errors'
export interface GeneratedAddress {
xAddress: string
classicAddress?: string
secret: string
}
interface GenerateAddressOptions {
// The entropy to use to generate the seed.
entropy?: Uint8Array | number[]
// The digital signature algorithm to generate an address for. Can be `ecdsa-secp256k1` (default) or `ed25519`.
algorithm?: ECDSA
/*
* Specifies whether the address is intended for use on a test network such as Testnet or Devnet.
* If `true`, the address should only be used for testing, and will start with `T`.
* If `false` (default), the address should only be used on mainnet, and will start with `X`.
*/
test?: boolean
// If `true`, return the classic address, in addition to the X-address.
includeClassicAddress?: boolean
}
/**
* TODO: Move this function to be a static function of the Wallet Class.
* TODO: Doc this function.
*
* @param options - Options for generating X-Address.
* @returns A generated address.
* @throws When cannot generate an address.
*/
function generateXAddress(
options: GenerateAddressOptions = {},
): GeneratedAddress {
try {
const generateSeedOptions: {
entropy?: Uint8Array
algorithm?: ECDSA
} = {
algorithm: options.algorithm,
}
if (options.entropy) {
generateSeedOptions.entropy = Uint8Array.from(options.entropy)
}
const secret = keypairs.generateSeed(generateSeedOptions)
const keypair = keypairs.deriveKeypair(secret)
const classicAddress = keypairs.deriveAddress(keypair.publicKey)
const returnValue: GeneratedAddress = {
xAddress: classicAddressToXAddress(
classicAddress,
false,
options.test ?? false,
),
secret,
}
if (options.includeClassicAddress) {
returnValue.classicAddress = classicAddress
}
return returnValue
} catch (error) {
if (error instanceof Error) {
throw new UnexpectedError(error.message)
}
throw error
}
}
export { generateXAddress }

View File

@@ -6,7 +6,7 @@
import BigNumber from 'bignumber.js'
import { decode, encode } from 'ripple-binary-codec'
import { ValidationError } from '../../errors'
import { ValidationError, XrplError } from '../../errors'
import type { Ledger } from '../../models/ledger'
import { LedgerEntry } from '../../models/ledger'
import { Transaction } from '../../models/transactions'
@@ -61,7 +61,7 @@ function addLengthPrefix(hex: string): string {
]) + hex
)
}
throw new Error('Variable integer overflow.')
throw new XrplError('Variable integer overflow.')
}
/**
@@ -76,7 +76,6 @@ export function hashSignedTx(tx: Transaction | string): string {
let txObject: Transaction
if (typeof tx === 'string') {
txBlob = tx
// TODO: type ripple-binary-codec with Transaction
/* eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- Required until updated in binary codec. */
txObject = decode(tx) as unknown as Transaction
} else {

View File

@@ -1,3 +1,4 @@
import { XrplError } from '../../../errors'
import hashPrefix from '../hashPrefix'
import sha512Half from '../sha512Half'
@@ -54,7 +55,7 @@ class InnerNode extends Node {
} else if (existingNode instanceof Leaf) {
if (existingNode.tag === tag) {
// Collision
throw new Error(
throw new XrplError(
'Tried to add a node to a SHAMap that was already in there.',
)
} else {
@@ -79,7 +80,7 @@ class InnerNode extends Node {
*/
public setNode(slot: number, node: Node): void {
if (slot < 0 || slot > SLOT_MAX) {
throw new Error('Invalid slot: slot must be between 0-15.')
throw new XrplError('Invalid slot: slot must be between 0-15.')
}
this.leaves[slot] = node
this.empty = false
@@ -94,7 +95,7 @@ class InnerNode extends Node {
*/
public getNode(slot: number): Node | undefined {
if (slot < 0 || slot > SLOT_MAX) {
throw new Error('Invalid slot: slot must be between 0-15.')
throw new XrplError('Invalid slot: slot must be between 0-15.')
}
return this.leaves[slot]
}

View File

@@ -1,3 +1,4 @@
import { XrplError } from '../../../errors'
import hashPrefix from '../hashPrefix'
import sha512Half from '../sha512Half'
@@ -30,13 +31,13 @@ class Leaf extends Node {
/**
* Add item to Leaf.
*
* @param _tag - Index of the Node.
* @param _node - Node to insert.
* @param tag - Index of the Node.
* @param node - Node to insert.
* @throws When called, because LeafNodes cannot addItem.
*/
// eslint-disable-next-line class-methods-use-this -- no `this` needed here
public addItem(_tag: string, _node: Node): void {
throw new Error('Cannot call addItem on a LeafNode')
public addItem(tag: string, node: Node): void {
throw new XrplError('Cannot call addItem on a LeafNode')
this.addItem(tag, node)
}
/**
@@ -60,7 +61,7 @@ class Leaf extends Node {
return sha512Half(txNodePrefix + this.data + this.tag)
}
default:
throw new Error('Tried to hash a SHAMap node of unknown type.')
throw new XrplError('Tried to hash a SHAMap node of unknown type.')
}
}
}

View File

@@ -19,7 +19,6 @@ import { Response } from '../models/methods'
import getBalanceChanges from './balanceChanges'
import { deriveKeypair, deriveXAddress } from './derive'
import { generateXAddress } from './generateAddress'
import {
hashSignedTx,
hashTx,
@@ -124,7 +123,6 @@ export {
isValidSecret,
isValidAddress,
hashes,
generateXAddress,
deriveKeypair,
deriveXAddress,
signPaymentChannelClaim,

View File

@@ -5,12 +5,15 @@ import { isValidClassicAddress } from 'ripple-address-codec'
import type { Client } from '..'
import { RippledError, XRPLFaucetError } from '../errors'
import { GeneratedAddress } from '../utils/generateAddress'
import Wallet from '.'
interface FaucetWallet {
account: GeneratedAddress
account: {
xAddress: string
classicAddress?: string
secret: string
}
amount: number
balance: number
}

View File

@@ -11,8 +11,7 @@ import { sign as signWithKeypair, verify } from 'ripple-keypairs'
import { ValidationError } from '../errors'
import { Signer } from '../models/common'
import { Transaction } from '../models/transactions'
import { validateBaseTransaction } from '../models/transactions/common'
import { Transaction, validate } from '../models/transactions'
import Wallet from '.'
@@ -37,11 +36,9 @@ function multisign(transactions: Array<Transaction | string>): string {
/*
* This will throw a more clear error for JS users if any of the supplied transactions has incorrect formatting
* TODO: Replace this with validate() (The general validation function for all Transactions)
* also make validate accept '| Transaction' to avoid type casting here.
*/
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- validate does not accept Transaction type
validateBaseTransaction(tx as unknown as Record<string, unknown>)
validate(tx as unknown as Record<string, unknown>)
if (tx.Signers == null || tx.Signers.length === 0) {
throw new ValidationError(
"For multisigning all transactions must include a Signers field containing an array of signatures. You may have forgotten to pass the 'forMultisign' parameter when signing.",
@@ -162,7 +159,9 @@ function addressToBigNumber(address: string): BigNumber {
function getDecodedTransaction(txOrBlob: Transaction | string): Transaction {
if (typeof txOrBlob === 'object') {
return txOrBlob
// We need this to handle X-addresses in multisigning
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- We are casting here to get strong typing
return decode(encode(txOrBlob)) as unknown as Transaction
}
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- We are casting here to get strong typing

View File

@@ -1,8 +1,9 @@
import BigNumber from 'bignumber.js'
import { assert } from 'chai'
import { BookOffersRequest, ValidationError } from 'xrpl-local'
import { OfferFlags } from 'xrpl-local/models/ledger/offer'
import { BookOffersRequest } from 'xrpl-local'
import { ValidationError, XrplError } from 'xrpl-local/errors'
import { OfferFlags } from 'xrpl-local/models/ledger'
import requests from '../fixtures/requests'
import responses from '../fixtures/responses'
@@ -52,7 +53,7 @@ function normalRippledResponse(
) {
return rippled.book_offers.fabric.requestBookOffersAsksResponse(request)
}
throw new Error('unexpected end')
throw new XrplError('unexpected end')
}
function xrpRippledResponse(

View File

@@ -255,7 +255,7 @@ describe('Connection', function () {
// overload websocket send on open when _ws exists
this.client.connection.ws.send = function (_0, _1, _2): void {
// recent ws throws this error instead of calling back
throw new Error('WebSocket is not open: readyState 0 (CONNECTING)')
throw new XrplError('WebSocket is not open: readyState 0 (CONNECTING)')
}
const request = { command: 'subscribe', streams: ['ledger'] }
this.client.connection.request(request)
@@ -340,20 +340,22 @@ describe('Connection', function () {
if (connectsCount === num) {
if (disconnectsCount !== num) {
done(
new Error(
new XrplError(
`disconnectsCount must be equal to ${num}(got ${disconnectsCount} instead)`,
),
)
} else if (reconnectsCount !== num) {
done(
new Error(
new XrplError(
`reconnectsCount must be equal to ${num} (got ${reconnectsCount} instead)`,
),
)
// eslint-disable-next-line no-negated-condition -- Necessary
} else if (code !== 1006) {
done(
new Error(`disconnect must send code 1006 (got ${code} instead)`),
new XrplError(
`disconnect must send code 1006 (got ${code} instead)`,
),
)
} else {
done()
@@ -399,14 +401,14 @@ describe('Connection', function () {
this.timeout(5000)
// fail on reconnect/connection
this.client.connection.reconnect = async (): Promise<void> => {
throw new Error('error on reconnect')
throw new XrplError('error on reconnect')
}
// Hook up a listener for the reconnect error event
this.client.on('error', (error, message) => {
if (error === 'reconnect' && message === 'error on reconnect') {
return done()
}
return done(new Error('Expected error on reconnect'))
return done(new XrplError('Expected error on reconnect'))
})
// Trigger a heartbeat
this.client.connection.heartbeat()
@@ -422,7 +424,7 @@ describe('Connection', function () {
it('should emit disconnected event with code 1006 (CLOSE_ABNORMAL)', function (done) {
this.client.connection.once('error', (error) => {
done(new Error(`should not throw error, got ${String(error)}`))
done(new XrplError(`should not throw error, got ${String(error)}`))
})
this.client.connection.once('disconnected', (code) => {
assert.strictEqual(code, 1006)
@@ -597,14 +599,14 @@ describe('Connection', function () {
it('should try to reconnect on empty subscribe response on reconnect', function (done) {
this.timeout(23000)
this.client.on('error', (error) => {
done(error || new Error('Should not emit error.'))
done(error || new XrplError('Should not emit error.'))
})
let disconnectedCount = 0
this.client.on('connected', () => {
done(
disconnectedCount === 1
? undefined
: new Error('Wrong number of disconnects'),
: new XrplError('Wrong number of disconnects'),
)
})
this.client.on('disconnected', () => {
@@ -622,7 +624,7 @@ describe('Connection', function () {
.request({
command: 'test_garbage',
})
.then(() => new Error('Should not have succeeded'))
.then(() => new XrplError('Should not have succeeded'))
.catch(done())
})

View File

@@ -19,12 +19,11 @@ describe('Utility method integration tests', function () {
const response = await (this.client as Client).request({
command: 'ping',
})
const expected = {
id: 0,
const expected: unknown = {
result: { role: 'admin', unlimited: true },
type: 'response',
}
assert.deepEqual(_.omit(response, 'id'), _.omit(expected, 'id'))
assert.deepEqual(_.omit(response, 'id'), expected)
})
it('random', async function () {

View File

@@ -3,6 +3,7 @@ import _ from 'lodash'
import { Server as WebSocketServer } from 'ws'
import type { Request } from '../src'
import { XrplError } from '../src/errors'
import type {
BaseResponse,
ErrorResponse,
@@ -15,7 +16,7 @@ function createResponse(
response: Record<string, unknown>,
): string {
if (!('type' in response) && !('error' in response)) {
throw new Error(
throw new XrplError(
`Bad response format. Must contain \`type\` or \`error\`. ${JSON.stringify(
response,
)}`,
@@ -65,10 +66,10 @@ export default function createMockRippled(port: number): MockedWebSocketServer {
try {
request = JSON.parse(requestJSON)
if (request.id == null) {
throw new Error(`Request has no id: ${requestJSON}`)
throw new XrplError(`Request has no id: ${requestJSON}`)
}
if (request.command == null) {
throw new Error(`Request has no command: ${requestJSON}`)
throw new XrplError(`Request has no id: ${requestJSON}`)
}
if (request.command === 'ping') {
ping(conn, request)
@@ -77,7 +78,7 @@ export default function createMockRippled(port: number): MockedWebSocketServer {
} else if (request.command in mock.responses) {
conn.send(createResponse(request, mock.getResponse(request)))
} else {
throw new Error(
throw new XrplError(
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions -- We know it's there
`No event handler registered in mock rippled for ${request.command}`,
)
@@ -117,14 +118,14 @@ export default function createMockRippled(port: number): MockedWebSocketServer {
| ((r: Request) => Response | ErrorResponse),
): void {
if (typeof command !== 'string') {
throw new Error('command is not a string')
throw new XrplError('command is not a string')
}
if (
typeof response === 'object' &&
!('type' in response) &&
!('error' in response)
) {
throw new Error(
throw new XrplError(
`Bad response format. Must contain \`type\` or \`error\`. ${JSON.stringify(
response,
)}`,
@@ -135,7 +136,7 @@ export default function createMockRippled(port: number): MockedWebSocketServer {
mock.getResponse = (request: Request): Record<string, unknown> => {
if (!(request.command in mock.responses)) {
throw new Error(`No handler for ${request.command}`)
throw new XrplError(`No handler for ${request.command}`)
}
const functionOrObject = mock.responses[request.command]
if (typeof functionOrObject === 'function') {

View File

@@ -40,6 +40,35 @@ describe('Payment', function () {
assert.doesNotThrow(() => validate(paymentTransaction))
})
it(`Verifies memos correctly`, function () {
paymentTransaction.Memos = [
{
Memo: {
MemoData: '32324324',
},
},
]
assert.doesNotThrow(() => validate(paymentTransaction))
})
it(`Verifies memos correctly`, function () {
paymentTransaction.Memos = [
{
Memo: {
MemoData: '32324324',
MemoType: 121221,
},
},
]
assert.throws(
() => validate(paymentTransaction),
ValidationError,
'BaseTransaction: invalid Memos',
)
})
it(`throws when Amount is missing`, function () {
delete paymentTransaction.Amount
assert.throws(

View File

@@ -4,6 +4,8 @@ import path from 'path'
import { Client } from 'xrpl-local'
import { XrplError } from '../src/errors'
/**
* Client Test Runner.
*
@@ -27,10 +29,10 @@ describe('Client', function () {
)
for (const methodName of allPublicMethods) {
if (!allTestedMethods.has(methodName)) {
// TODO: Once migration is complete, remove `.skip()` so that missing tests are reported as failures.
// eslint-disable-next-line mocha/no-skipped-tests -- See above TODO
/** TODO: Remove the skip, rename methods. */
// eslint-disable-next-line mocha/no-skipped-tests -- skip these tests for now.
it.skip(`${methodName} - no test suite found`, function () {
throw new Error(
throw new XrplError(
`Test file not found! Create file "test/client/${methodName}.ts".`,
)
})

View File

@@ -10,17 +10,15 @@ const HEX_ZERO =
/**
* Generates data to hash for testing.
*
* @param v - TODO: fill in.
* @param int - TODO: fill in.
* @returns TODO: fill in.
*/
// eslint-disable-next-line id-length -- TODO: figure out what this variable means
function intToVuc(v: number): string {
function intToVuc(int: number): string {
let ret = ''
// eslint-disable-next-line id-length -- TODO: figure out what this variable means
for (let i = 0; i < 32; i++) {
for (let it = 0; it < 32; it++) {
ret += '0'
ret += v.toString(16).toUpperCase()
ret += int.toString(16).toUpperCase()
}
return ret
}

View File

@@ -1,5 +1,5 @@
/* eslint-disable @typescript-eslint/no-explicit-any -- Necessary for these methods TODO: further cleanup */
/* eslint-disable @typescript-eslint/explicit-module-boundary-types -- Necessary for these methods TODO: further cleanup */
/* eslint-disable @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- required for
assertions. */
import net from 'net'
import { assert } from 'chai'
@@ -31,8 +31,8 @@ export function assertResultMatch(
if (expected.txJSON) {
assert(response.txJSON)
assert.deepEqual(
JSON.parse(response.txJSON as string),
JSON.parse(expected.txJSON as string),
JSON.parse(response.txJSON),
JSON.parse(expected.txJSON),
'checkResult: txJSON must match',
)
}
@@ -58,7 +58,7 @@ export function assertResultMatch(
* @param message - Expected error message/substring of the error message.
*/
export async function assertRejects(
promise: PromiseLike<any>,
promise: PromiseLike<unknown>,
instanceOf: any,
message?: string | RegExp,
): Promise<void> {

View File

@@ -1,238 +0,0 @@
import { assert } from 'chai'
import { UnexpectedError } from 'xrpl-local'
import ECDSA from 'xrpl-local/ecdsa'
import { generateXAddress } from 'xrpl-local/utils/generateAddress'
import responses from '../fixtures/responses'
describe('generateAddress', function () {
it('generateAddress', function () {
assert.deepEqual(
generateXAddress({ entropy: new Array(16).fill(0) }),
// THEN we get the expected return value
responses.generateXAddress,
)
})
it('generateAddress invalid entropy', function () {
assert.throws(() => {
/*
* GIVEN entropy of 1 byte
* WHEN generating an address
*/
generateXAddress({ entropy: new Array(1).fill(0) })
/*
* THEN an UnexpectedError is thrown
* because 16 bytes of entropy are required
*/
}, UnexpectedError)
})
it('generateAddress with no options object', function () {
// GIVEN no options
// WHEN generating an address
const account = generateXAddress()
// THEN we get an object with an xAddress starting with 'x' and a secret starting with 's'
assert(account.xAddress.startsWith('X'), 'Address must start with `X`')
assert(account.secret.startsWith('s'), 'Secret must start with `s`')
})
it('generateAddress with empty options object', function () {
// GIVEN an empty options object
const options = {}
// WHEN generating an address
const account = generateXAddress(options)
// THEN we get an object with an xAddress starting with 'x' and a secret starting with 's'
assert(account.xAddress.startsWith('X'), 'Address must start with `X`')
assert(account.secret.startsWith('s'), 'Secret must start with `s`')
})
it('generateAddress with algorithm `ecdsa-secp256k1`', function () {
// GIVEN we want to use 'ecdsa-secp256k1'
const options = {
algorithm: ECDSA.secp256k1,
includeClassicAddress: true,
}
// WHEN generating an address
const account = generateXAddress(options)
// THEN we get an object with an address starting with 'r' and a secret starting with 's' (not 'sEd')
assert(
account.classicAddress?.startsWith('r'),
'Address must start with `r`',
)
assert.deepEqual(
account.secret.slice(0, 1),
's',
`Secret ${account.secret} must start with 's'`,
)
assert.notStrictEqual(
account.secret.slice(0, 3),
'sEd',
`secp256k1 secret ${account.secret} must not start with 'sEd'`,
)
})
it('generateAddress with algorithm `ed25519`', function () {
// GIVEN we want to use 'ed25519'
const options = {
algorithm: ECDSA.ed25519,
includeClassicAddress: true,
}
// WHEN generating an address
const account = generateXAddress(options)
// THEN we get an object with an address starting with 'r' and a secret starting with 'sEd'
assert(
account.classicAddress?.startsWith('r'),
'Address must start with `r`',
)
assert.deepEqual(
account.secret.slice(0, 3),
'sEd',
`Ed25519 secret ${account.secret} must start with 'sEd'`,
)
})
it('generateAddress with algorithm `ecdsa-secp256k1` and given entropy', function () {
// GIVEN we want to use 'ecdsa-secp256k1' with entropy of zero
const options = {
algorithm: ECDSA.secp256k1,
entropy: new Array(16).fill(0),
}
// WHEN generating an address
const account = generateXAddress(options)
// THEN we get the expected return value
assert.deepEqual(account, responses.generateXAddress)
})
it('generateAddress with algorithm `ed25519` and given entropy', function () {
// GIVEN we want to use 'ed25519' with entropy of zero
const options = {
algorithm: ECDSA.ed25519,
entropy: new Array(16).fill(0),
}
// WHEN generating an address
const account = generateXAddress(options)
// THEN we get the expected return value
assert.deepEqual(account, {
// generateAddress return value always includes xAddress to encourage X-address adoption
xAddress: 'X7xq1YJ4xmLSGGLhuakFQB9CebWYthQkgsvFC4LGFH871HB',
secret: 'sEdSJHS4oiAdz7w2X2ni1gFiqtbJHqE',
})
})
it('generateAddress with algorithm `ecdsa-secp256k1` and given entropy; include classic address', function () {
// GIVEN we want to use 'ecdsa-secp256k1' with entropy of zero
const options = {
algorithm: ECDSA.secp256k1,
entropy: new Array(16).fill(0),
includeClassicAddress: true,
}
// WHEN generating an address
const account = generateXAddress(options)
// THEN we get the expected return value
assert.deepEqual(account, responses.generateAddress)
})
it('generateAddress with algorithm `ed25519` and given entropy; include classic address', function () {
// GIVEN we want to use 'ed25519' with entropy of zero
const options = {
algorithm: ECDSA.ed25519,
entropy: new Array(16).fill(0),
includeClassicAddress: true,
}
// WHEN generating an address
const account = generateXAddress(options)
// THEN we get the expected return value
assert.deepEqual(account, {
// generateAddress return value always includes xAddress to encourage X-address adoption
xAddress: 'X7xq1YJ4xmLSGGLhuakFQB9CebWYthQkgsvFC4LGFH871HB',
secret: 'sEdSJHS4oiAdz7w2X2ni1gFiqtbJHqE',
classicAddress: 'r9zRhGr7b6xPekLvT6wP4qNdWMryaumZS7',
})
})
it('generateAddress with algorithm `ecdsa-secp256k1` and given entropy; include classic address; for test network use', function () {
// GIVEN we want to use 'ecdsa-secp256k1' with entropy of zero
const options = {
algorithm: ECDSA.secp256k1,
entropy: new Array(16).fill(0),
includeClassicAddress: true,
test: true,
}
// WHEN generating an address
const account = generateXAddress(options)
// THEN we get the expected return value
const response = {
// generateAddress return value always includes xAddress to encourage X-address adoption
...responses.generateAddress,
xAddress: 'TVG3TcCD58BD6MZqsNuTihdrhZwR8SzvYS8U87zvHsAcNw4',
}
assert.deepEqual(account, response)
})
it('generateAddress with algorithm `ed25519` and given entropy; include classic address; for test network use', function () {
// GIVEN we want to use 'ed25519' with entropy of zero
const options = {
algorithm: ECDSA.ed25519,
entropy: new Array(16).fill(0),
includeClassicAddress: true,
test: true,
}
// WHEN generating an address
const account = generateXAddress(options)
// THEN we get the expected return value
assert.deepEqual(account, {
// generateAddress return value always includes xAddress to encourage X-address adoption
xAddress: 'T7t4HeTMF5tT68agwuVbJwu23ssMPeh8dDtGysZoQiij1oo',
secret: 'sEdSJHS4oiAdz7w2X2ni1gFiqtbJHqE',
classicAddress: 'r9zRhGr7b6xPekLvT6wP4qNdWMryaumZS7',
})
})
it('generateAddress for test network use', function () {
// GIVEN we want an address for test network use
const options = { test: true }
// WHEN generating an address
const account = generateXAddress(options)
// THEN we get an object with xAddress starting with 'T' and a secret starting with 's'
// generateAddress return value always includes xAddress to encourage X-address adoption
assert.deepEqual(
account.xAddress.slice(0, 1),
'T',
'Test addresses start with T',
)
assert.deepEqual(
account.secret.slice(0, 1),
's',
`Secret ${account.secret} must start with 's'`,
)
})
})