Change prepare* methods to reject Promise on error (#984)

* Reject Promise on error, update docs, and add tests:
  * preparePayment
  * prepareTrustline
  * prepareOrder
  * prepareOrderCancellation
  * prepareSettings
  * prepareEscrowCreation
  * prepareEscrowExecution
  * prepareCheckCreate
  * prepareCheckCash
  * prepareCheckCancel
  * preparePaymentChannelCreate
  * preparePaymentChannelFund
  * preparePaymentChannelClaim

Note that we can't update mocha to ^5.2.0 because it causes testing to hang indefinitely; this needs to be investigated.
This commit is contained in:
Elliot Lee
2019-01-29 15:22:18 -08:00
committed by GitHub
parent dc148bf954
commit 2445004333
17 changed files with 491 additions and 115 deletions

View File

@@ -519,23 +519,170 @@ describe('RippleAPI', function () {
})
});
it('preparePayment - XRP to XRP no partial', function () {
assert.throws(() => {
this.api.preparePayment(address, requests.preparePayment.wrongPartial);
}, /XRP to XRP payments cannot be partial payments/);
});
describe('errors', function () {
it('preparePayment - address must match payment.source.address', function (
) {
assert.throws(() => {
this.api.preparePayment(address, requests.preparePayment.wrongAddress);
}, /address must match payment.source.address/);
});
const senderAddress = 'r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59';
const recipientAddress = 'rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo';
it('preparePayment - wrong amount', function () {
assert.throws(() => {
this.api.preparePayment(address, requests.preparePayment.wrongAmount);
}, this.api.errors.ValidationError);
it('rejects promise and does not throw when payment object is invalid', function (done) {
const payment = {
source: {
address: senderAddress,
amount: { // instead of `maxAmount`
value: '1000',
currency: 'drops'
}
},
destination: {
address: recipientAddress,
amount: {
value: '1000',
currency: 'drops'
}
}
}
// Cannot use `assert.rejects` because then the test passes (with UnhandledPromiseRejectionWarning) even when it should not.
// See https://github.com/mochajs/mocha/issues/3097
try {
this.api.preparePayment(senderAddress, payment).then(prepared => {
done(new Error('Expected method to reject. Prepared transaction: ' + JSON.stringify(prepared)));
}).catch(err => {
assert.strictEqual(err.name, 'ValidationError');
assert.strictEqual(err.message, 'payment must specify either (source.maxAmount and destination.amount) or (source.amount and destination.minAmount)');
done();
}).catch(done); // Finish test with assertion failure immediately instead of waiting for timeout.
} catch (err) {
done(new Error('Expected method to reject, but method threw. Thrown: ' + err));
};
});
it('rejects promise and does not throw when field is missing', function (done) {
const payment = {
source: {
address: senderAddress
// `maxAmount` missing
},
destination: {
address: recipientAddress,
amount: {
value: '1000',
currency: 'drops'
}
}
}
// Cannot use `assert.rejects` because then the test passes (with UnhandledPromiseRejectionWarning) even when it should not.
// See https://github.com/mochajs/mocha/issues/3097
try {
this.api.preparePayment(senderAddress, payment).then(prepared => {
done(new Error('Expected method to reject. Prepared transaction: ' + JSON.stringify(prepared)));
}).catch(err => {
assert.strictEqual(err.name, 'ValidationError');
assert.strictEqual(err.message, 'instance.payment.source is not exactly one from <sourceExactAdjustment>,<maxAdjustment>');
done();
}).catch(done); // Finish test with assertion failure immediately instead of waiting for timeout.
} catch (err) {
done(new Error('Expected method to reject, but method threw. Thrown: ' + err));
};
});
it('rejects promise and does not throw when fee exceeds maxFeeXRP', function (done) {
const payment = {
source: {
address: senderAddress,
maxAmount: {
value: '1000',
currency: 'drops'
}
},
destination: {
address: recipientAddress,
amount: {
value: '1000',
currency: 'drops'
}
}
}
// Cannot use `assert.rejects` because then the test passes (with UnhandledPromiseRejectionWarning) even when it should not.
// See https://github.com/mochajs/mocha/issues/3097
try {
this.api.preparePayment(senderAddress, payment, {
fee: '3' // XRP
}).then(prepared => {
done(new Error('Expected method to reject. Prepared transaction: ' + JSON.stringify(prepared)));
}).catch(err => {
assert.strictEqual(err.name, 'ValidationError');
assert.strictEqual(err.message, 'Fee of 3 XRP exceeds max of 2 XRP. To use this fee, increase `maxFeeXRP` in the RippleAPI constructor.');
done();
}).catch(done); // Finish test with assertion failure immediately instead of waiting for timeout.
} catch (err) {
done(new Error('Expected method to reject, but method threw. Thrown: ' + err));
};
});
it('preparePayment - XRP to XRP no partial', function (done) {
try {
// Cannot return promise because we want/expect it to reject.
this.api.preparePayment(address, requests.preparePayment.wrongPartial).then(prepared => {
done(new Error('Expected method to reject. Prepared transaction: ' + JSON.stringify(prepared)));
}).catch(err => {
assert.strictEqual(err.name, 'ValidationError');
assert.strictEqual(err.message, 'XRP to XRP payments cannot be partial payments');
done();
}).catch(done); // Finish test with assertion failure immediately instead of waiting for timeout.
} catch (err) {
done(new Error('Expected method to reject, but method threw. Thrown: ' + err));
};
});
it('preparePayment - address must match payment.source.address', function (done) {
try {
// Cannot return promise because we want/expect it to reject.
this.api.preparePayment(address, requests.preparePayment.wrongAddress).then(prepared => {
done(new Error('Expected method to reject. Prepared transaction: ' + JSON.stringify(prepared)));
}).catch(err => {
assert.strictEqual(err.name, 'ValidationError');
assert.strictEqual(err.message, 'address must match payment.source.address');
done();
}).catch(done); // Finish test with assertion failure immediately instead of waiting for timeout.
} catch (err) {
done(new Error('Expected method to reject, but method threw. Thrown: ' + err));
};
});
it('preparePayment - wrong amount', function (done) {
try {
// Cannot return promise because we want/expect it to reject.
this.api.preparePayment(address, requests.preparePayment.wrongAmount).then(prepared => {
done(new Error('Expected method to reject. Prepared transaction: ' + JSON.stringify(prepared)));
}).catch(err => {
assert.strictEqual(err.name, 'ValidationError');
assert.strictEqual(err.message, 'payment must specify either (source.maxAmount and destination.amount) or (source.amount and destination.minAmount)');
done();
}).catch(done); // Finish test with assertion failure immediately instead of waiting for timeout.
} catch (err) {
done(new Error('Expected method to reject, but method threw. Thrown: ' + err));
};
});
it('preparePayment - throws when fee exceeds 2 XRP', function (done) {
const localInstructions = _.defaults({
fee: '2.1'
}, instructions);
try {
// Cannot return promise because we want/expect it to reject.
this.api.preparePayment(
address, requests.preparePayment.normal, localInstructions).then(prepared => {
done(new Error('Expected method to reject. Prepared transaction: ' + JSON.stringify(prepared)));
}).catch(err => {
assert.strictEqual(err.name, 'ValidationError');
assert.strictEqual(err.message, 'Fee of 2.1 XRP exceeds max of 2 XRP. To use this fee, increase `maxFeeXRP` in the RippleAPI constructor.');
done();
}).catch(done); // Finish test with assertion failure immediately instead of waiting for timeout.
} catch (err) {
done(new Error('Expected method to reject, but method threw. Thrown: ' + err));
};
});
});
it('preparePayment with all options specified', function () {
@@ -565,17 +712,6 @@ describe('RippleAPI', function () {
responses.preparePayment.minAmount, 'prepare'));
});
it('preparePayment - throws when fee exceeds 2 XRP', function () {
const localInstructions = _.defaults({
fee: '2.1'
}, instructions);
assert.throws(() => {
this.api.preparePayment(
address, requests.preparePayment.normal, localInstructions)
}, /Fee of 2\.1 XRP exceeds max of 2 XRP\. To use this fee, increase `maxFeeXRP` in the RippleAPI constructor\./)
});
it('preparePayment - caps fee at 2 XRP by default', function () {
this.api._feeCushion = 1000000;
@@ -633,6 +769,22 @@ describe('RippleAPI', function () {
_.partial(checkResult, responses.prepareOrder.sell, 'prepare'));
});
it('prepareOrder - invalid', function (done) {
const request = requests.prepareOrder.sell;
delete request.direction; // Make invalid
try {
this.api.prepareOrder(address, request, instructions).then(prepared => {
done(new Error('Expected method to reject. Prepared transaction: ' + JSON.stringify(prepared)));
}).catch(err => {
assert.strictEqual(err.name, 'ValidationError');
assert.strictEqual(err.message, 'instance.order requires property "direction"');
done();
}).catch(done); // Finish test with assertion failure immediately instead of waiting for timeout.
} catch (err) {
done(new Error('Expected method to reject, but method threw. Thrown: ' + err));
};
});
it('prepareOrderCancellation', function () {
const request = requests.prepareOrderCancellation.simple;
return this.api.prepareOrderCancellation(address, request, instructions)
@@ -656,6 +808,22 @@ describe('RippleAPI', function () {
'prepare'));
});
it('prepareOrderCancellation - invalid', function (done) {
const request = requests.prepareOrderCancellation.withMemos;
delete request.orderSequence; // Make invalid
try {
this.api.prepareOrderCancellation(address, request).then(prepared => {
done(new Error('Expected method to reject. Prepared transaction: ' + JSON.stringify(prepared)));
}).catch(err => {
assert.strictEqual(err.name, 'ValidationError');
assert.strictEqual(err.message, 'instance.orderCancellation requires property "orderSequence"');
done();
}).catch(done); // Finish test with assertion failure immediately instead of waiting for timeout.
} catch (err) {
done(new Error('Expected method to reject, but method threw. Thrown: ' + err));
};
});
it('prepareTrustline - simple', function () {
return this.api.prepareTrustline(
address, requests.prepareTrustline.simple, instructions).then(
@@ -674,6 +842,23 @@ describe('RippleAPI', function () {
_.partial(checkResult, responses.prepareTrustline.complex, 'prepare'));
});
it('prepareTrustline - invalid', function (done) {
const trustline = requests.prepareTrustline.complex;
delete trustline.limit; // Make invalid
try {
this.api.prepareTrustline(
address, trustline, instructions).then(prepared => {
done(new Error('Expected method to reject. Prepared transaction: ' + JSON.stringify(prepared)));
}).catch(err => {
assert.strictEqual(err.name, 'ValidationError');
assert.strictEqual(err.message, 'instance.trustline requires property "limit"');
done();
}).catch(done); // Finish test with assertion failure immediately instead of waiting for timeout.
} catch (err) {
done(new Error('Expected method to reject, but method threw. Thrown: ' + err));
};
});
it('prepareSettings', function () {
return this.api.prepareSettings(
address, requests.prepareSettings.domain, instructions).then(
@@ -756,18 +941,34 @@ describe('RippleAPI', function () {
'prepare'));
});
it('prepareSettings - signers no threshold', function () {
it('prepareSettings - signers no threshold', function (done) {
const settings = requests.prepareSettings.signers.noThreshold;
assert.throws(() => {
this.api.prepareSettings(address, settings, instructions);
}, this.api.errors.ValidationError);
try {
this.api.prepareSettings(address, settings, instructions).then(prepared => {
done(new Error('Expected method to reject. Prepared transaction: ' + JSON.stringify(prepared)));
}).catch(err => {
assert.strictEqual(err.name, 'ValidationError');
assert.strictEqual(err.message, 'instance.settings.signers requires property "threshold"');
done();
}).catch(done); // Finish test with assertion failure immediately instead of waiting for timeout.
} catch (err) {
done(new Error('Expected method to reject, but method threw. Thrown: ' + err));
};
});
it('prepareSettings - signers no weights', function () {
it('prepareSettings - signers no weights', function (done) {
const settings = requests.prepareSettings.signers.noWeights;
assert.throws(() => {
this.api.prepareSettings(address, settings, instructions);
}, this.api.errors.ValidationError);
try {
this.api.prepareSettings(address, settings, instructions).then(prepared => {
done(new Error('Expected method to reject. Prepared transaction: ' + JSON.stringify(prepared)));
}).catch(err => {
assert.strictEqual(err.name, 'ValidationError');
assert.strictEqual(err.message, 'instance.settings.signers requires property "weights"');
done();
}).catch(done); // Finish test with assertion failure immediately instead of waiting for timeout.
} catch (err) {
done(new Error('Expected method to reject, but method threw. Thrown: ' + err));
};
});
it('prepareSettings - fee for multisign', function () {
@@ -780,6 +981,30 @@ describe('RippleAPI', function () {
'prepare'));
});
it('prepareSettings - invalid', function (done) {
// domain must be a string
const settings = Object.assign({},
requests.prepareSettings.domain,
{domain: 123});
const localInstructions = _.defaults({
signersCount: 4
}, instructions);
try {
this.api.prepareSettings(
address, settings, localInstructions).then(prepared => {
done(new Error('Expected method to reject. Prepared transaction: ' + JSON.stringify(prepared)));
}).catch(err => {
assert.strictEqual(err.name, 'ValidationError');
assert.strictEqual(err.message, 'instance.settings.domain is not of a type(s) string');
done();
}).catch(done); // Finish test with assertion failure immediately instead of waiting for timeout.
} catch (err) {
done(new Error('Expected method to reject, but method threw. Thrown: ' + err));
};
});
it('prepareEscrowCreation', function () {
const localInstructions = _.defaults({
maxFee: '0.000012'
@@ -798,6 +1023,23 @@ describe('RippleAPI', function () {
'prepare'));
});
it('prepareEscrowCreation - invalid', function (done) {
const escrow = Object.assign({}, requests.prepareEscrowCreation.full);
delete escrow.amount; // Make invalid
try {
this.api.prepareEscrowCreation(
address, escrow).then(prepared => {
done(new Error('Expected method to reject. Prepared transaction: ' + JSON.stringify(prepared)));
}).catch(err => {
assert.strictEqual(err.name, 'ValidationError');
assert.strictEqual(err.message, 'instance.escrowCreation requires property "amount"');
done();
}).catch(done); // Finish test with assertion failure immediately instead of waiting for timeout.
} catch (err) {
done(new Error('Expected method to reject, but method threw. Thrown: ' + err));
};
});
it('prepareEscrowExecution', function () {
return this.api.prepareEscrowExecution(
address,
@@ -816,18 +1058,34 @@ describe('RippleAPI', function () {
'prepare'));
});
it('prepareEscrowExecution - no condition', function () {
assert.throws(() => {
it('prepareEscrowExecution - no condition', function (done) {
try {
this.api.prepareEscrowExecution(address,
requests.prepareEscrowExecution.noCondition, instructions);
}, /"condition" and "fulfillment" fields on EscrowFinish must only be specified together./);
requests.prepareEscrowExecution.noCondition, instructions).then(prepared => {
done(new Error('Expected method to reject. Prepared transaction: ' + JSON.stringify(prepared)));
}).catch(err => {
assert.strictEqual(err.name, 'ValidationError');
assert.strictEqual(err.message, '"condition" and "fulfillment" fields on EscrowFinish must only be specified together.');
done();
}).catch(done); // Finish test with assertion failure immediately instead of waiting for timeout.
} catch (err) {
done(new Error('Expected method to reject, but method threw. Thrown: ' + err));
};
});
it('prepareEscrowExecution - no fulfillment', function () {
assert.throws(() => {
it('prepareEscrowExecution - no fulfillment', function (done) {
try {
this.api.prepareEscrowExecution(address,
requests.prepareEscrowExecution.noFulfillment, instructions);
}, /"condition" and "fulfillment" fields on EscrowFinish must only be specified together./);
requests.prepareEscrowExecution.noFulfillment, instructions).then(prepared => {
done(new Error('Expected method to reject. Prepared transaction: ' + JSON.stringify(prepared)));
}).catch(err => {
assert.strictEqual(err.name, 'ValidationError');
assert.strictEqual(err.message, '"condition" and "fulfillment" fields on EscrowFinish must only be specified together.');
done();
}).catch(done); // Finish test with assertion failure immediately instead of waiting for timeout.
} catch (err) {
done(new Error('Expected method to reject, but method threw. Thrown: ' + err));
};
});
it('prepareEscrowCancellation', function () {
@@ -1382,22 +1640,34 @@ describe('RippleAPI', function () {
'prepare'));
});
it('throws on preparePaymentChannelClaim with renew and close', function () {
assert.throws(() => {
it('rejects Promise on preparePaymentChannelClaim with renew and close', function (done) {
try {
this.api.preparePaymentChannelClaim(
address, requests.preparePaymentChannelClaim.full).then(
_.partial(checkResult, responses.preparePaymentChannelClaim.full,
'prepare'));
}, this.api.errors.ValidationError);
address, requests.preparePaymentChannelClaim.full).then(prepared => {
done(new Error('Expected method to reject. Prepared transaction: ' + JSON.stringify(prepared)));
}).catch(err => {
assert.strictEqual(err.name, 'ValidationError');
assert.strictEqual(err.message, '"renew" and "close" flags on PaymentChannelClaim are mutually exclusive');
done();
}).catch(done); // Finish test with assertion failure immediately instead of waiting for timeout.
} catch (err) {
done(new Error('Expected method to reject, but method threw. Thrown: ' + err));
};
});
it('throws on preparePaymentChannelClaim with no signature', function () {
assert.throws(() => {
it('rejects Promise on preparePaymentChannelClaim with no signature', function (done) {
try {
this.api.preparePaymentChannelClaim(
address, requests.preparePaymentChannelClaim.noSignature).then(
_.partial(checkResult, responses.preparePaymentChannelClaim.noSignature,
'prepare'));
}, this.api.errors.ValidationError);
address, requests.preparePaymentChannelClaim.noSignature).then(prepared => {
done(new Error('Expected method to reject. Prepared transaction: ' + JSON.stringify(prepared)));
}).catch(err => {
assert.strictEqual(err.name, 'ValidationError');
assert.strictEqual(err.message, '"signature" and "publicKey" fields on PaymentChannelClaim must only be specified together.');
done();
}).catch(done); // Finish test with assertion failure immediately instead of waiting for timeout.
} catch (err) {
done(new Error('Expected method to reject, but method threw. Thrown: ' + err));
};
});
it('sign', function () {