Add tests for subscribe/unsubscribe error cases:

Fixes: RIPD-1417

Fix incorrect error case messages. Fix crash in NetworkOps instance when
exiting with remaining RPC subscriptions. Add code to remove URL
subscription when requested.
This commit is contained in:
Mike Ellery
2017-06-19 14:31:22 -07:00
committed by seelabs
parent e0168b98d7
commit ac1ab720c4
5 changed files with 367 additions and 58 deletions

View File

@@ -217,6 +217,10 @@ public:
~NetworkOPsImp() override
{
jobCounter_.join();
// this clear() is necessary to ensure the shared_ptrs in this map get
// destroyed NOW because the objects in this map invoke methods on this
// class when they are destroyed
mRpcSubMap.clear();
}
public:
@@ -477,6 +481,7 @@ public:
InfoSub::pointer findRpcSub (std::string const& strUrl) override;
InfoSub::pointer addRpcSub (
std::string const& strUrl, InfoSub::ref) override;
bool tryRemoveRpcSub (std::string const& strUrl) override;
//--------------------------------------------------------------------------
//
@@ -554,13 +559,20 @@ private:
subRpcMapType mRpcSubMap;
SubMapType mSubLedger; // Accepted ledgers.
SubMapType mSubManifests; // Received validator manifests.
SubMapType mSubServer; // When server changes connectivity state.
SubMapType mSubTransactions; // All accepted transactions.
SubMapType mSubRTTransactions; // All proposed and accepted transactions.
SubMapType mSubValidations; // Received validations.
SubMapType mSubPeerStatus; // peer status changes
enum SubTypes
{
sLedger, // Accepted ledgers.
sManifests, // Received validator manifests.
sServer, // When server changes connectivity state.
sTransactions, // All accepted transactions.
sRTTransactions, // All proposed and accepted transactions.
sValidations, // Received validations.
sPeerStatus, // Peer status changes.
sLastEntry = sPeerStatus // as this name implies, any new entry must
// be ADDED ABOVE this one
};
std::array<SubMapType, SubTypes::sLastEntry+1> mStreamMaps;
ServerFeeSummary mLastFeeSummary;
@@ -1593,7 +1605,7 @@ void NetworkOPsImp::pubManifest (Manifest const& mo)
// VFALCO consider std::shared_mutex
ScopedLockType sl (mSubLock);
if (!mSubManifests.empty ())
if (!mStreamMaps[sManifests].empty ())
{
Json::Value jvObj (Json::objectValue);
@@ -1606,7 +1618,8 @@ void NetworkOPsImp::pubManifest (Manifest const& mo)
jvObj [jss::signature] = strHex (mo.getSignature ());
jvObj [jss::master_signature] = strHex (mo.getMasterSignature ());
for (auto i = mSubManifests.begin (); i != mSubManifests.end (); )
for (auto i = mStreamMaps[sManifests].begin ();
i != mStreamMaps[sManifests].end (); )
{
if (auto p = i->second.lock())
{
@@ -1615,7 +1628,7 @@ void NetworkOPsImp::pubManifest (Manifest const& mo)
}
else
{
i = mSubManifests.erase (i);
i = mStreamMaps[sManifests].erase (i);
}
}
}
@@ -1661,7 +1674,7 @@ void NetworkOPsImp::pubServer ()
//
ScopedLockType sl (mSubLock);
if (!mSubServer.empty ())
if (!mStreamMaps[sServer].empty ())
{
Json::Value jvObj (Json::objectValue);
@@ -1704,7 +1717,8 @@ void NetworkOPsImp::pubServer ()
mLastFeeSummary = f;
for (auto i = mSubServer.begin (); i != mSubServer.end (); )
for (auto i = mStreamMaps[sServer].begin ();
i != mStreamMaps[sServer].end (); )
{
InfoSub::pointer p = i->second.lock ();
@@ -1718,7 +1732,7 @@ void NetworkOPsImp::pubServer ()
}
else
{
i = mSubServer.erase (i);
i = mStreamMaps[sServer].erase (i);
}
}
}
@@ -1730,7 +1744,7 @@ void NetworkOPsImp::pubValidation (STValidation::ref val)
// VFALCO consider std::shared_mutex
ScopedLockType sl (mSubLock);
if (!mSubValidations.empty ())
if (!mStreamMaps[sValidations].empty ())
{
Json::Value jvObj (Json::objectValue);
@@ -1769,7 +1783,8 @@ void NetworkOPsImp::pubValidation (STValidation::ref val)
if (auto const reserveInc = (*val)[~sfReserveIncrement])
jvObj [jss::reserve_inc] = *reserveInc;
for (auto i = mSubValidations.begin (); i != mSubValidations.end (); )
for (auto i = mStreamMaps[sValidations].begin ();
i != mStreamMaps[sValidations].end (); )
{
if (auto p = i->second.lock())
{
@@ -1778,7 +1793,7 @@ void NetworkOPsImp::pubValidation (STValidation::ref val)
}
else
{
i = mSubValidations.erase (i);
i = mStreamMaps[sValidations].erase (i);
}
}
}
@@ -1789,13 +1804,14 @@ void NetworkOPsImp::pubPeerStatus (
{
ScopedLockType sl (mSubLock);
if (!mSubPeerStatus.empty ())
if (!mStreamMaps[sPeerStatus].empty ())
{
Json::Value jvObj (func());
jvObj [jss::type] = "peerStatusChange";
for (auto i = mSubPeerStatus.begin (); i != mSubPeerStatus.end (); )
for (auto i = mStreamMaps[sPeerStatus].begin ();
i != mStreamMaps[sPeerStatus].end (); )
{
InfoSub::pointer p = i->second.lock ();
@@ -1806,7 +1822,7 @@ void NetworkOPsImp::pubPeerStatus (
}
else
{
i = mSubValidations.erase (i);
i = mStreamMaps[sPeerStatus].erase (i);
}
}
}
@@ -2361,8 +2377,8 @@ void NetworkOPsImp::pubProposedTransaction (
{
ScopedLockType sl (mSubLock);
auto it = mSubRTTransactions.begin ();
while (it != mSubRTTransactions.end ())
auto it = mStreamMaps[sRTTransactions].begin ();
while (it != mStreamMaps[sRTTransactions].end ())
{
InfoSub::pointer p = it->second.lock ();
@@ -2373,7 +2389,7 @@ void NetworkOPsImp::pubProposedTransaction (
}
else
{
it = mSubRTTransactions.erase (it);
it = mStreamMaps[sRTTransactions].erase (it);
}
}
}
@@ -2402,7 +2418,7 @@ void NetworkOPsImp::pubLedger (
{
ScopedLockType sl (mSubLock);
if (!mSubLedger.empty ())
if (!mStreamMaps[sLedger].empty ())
{
Json::Value jvObj (Json::objectValue);
@@ -2426,8 +2442,8 @@ void NetworkOPsImp::pubLedger (
= app_.getLedgerMaster ().getCompleteLedgers ();
}
auto it = mSubLedger.begin ();
while (it != mSubLedger.end ())
auto it = mStreamMaps[sLedger].begin ();
while (it != mStreamMaps[sLedger].end ())
{
InfoSub::pointer p = it->second.lock ();
if (p)
@@ -2436,7 +2452,7 @@ void NetworkOPsImp::pubLedger (
++it;
}
else
it = mSubLedger.erase (it);
it = mStreamMaps[sLedger].erase (it);
}
}
}
@@ -2529,8 +2545,8 @@ void NetworkOPsImp::pubValidatedTransaction (
{
ScopedLockType sl (mSubLock);
auto it = mSubTransactions.begin ();
while (it != mSubTransactions.end ())
auto it = mStreamMaps[sTransactions].begin ();
while (it != mStreamMaps[sTransactions].end ())
{
InfoSub::pointer p = it->second.lock ();
@@ -2540,12 +2556,12 @@ void NetworkOPsImp::pubValidatedTransaction (
++it;
}
else
it = mSubTransactions.erase (it);
it = mStreamMaps[sTransactions].erase (it);
}
it = mSubRTTransactions.begin ();
it = mStreamMaps[sRTTransactions].begin ();
while (it != mSubRTTransactions.end ())
while (it != mStreamMaps[sRTTransactions].end ())
{
InfoSub::pointer p = it->second.lock ();
@@ -2555,7 +2571,7 @@ void NetworkOPsImp::pubValidatedTransaction (
++it;
}
else
it = mSubRTTransactions.erase (it);
it = mStreamMaps[sRTTransactions].erase (it);
}
}
app_.getOrderBookDB ().processTxn (alAccepted, alTx, jvObj);
@@ -2782,28 +2798,30 @@ bool NetworkOPsImp::subLedger (InfoSub::ref isrListener, Json::Value& jvResult)
}
ScopedLockType sl (mSubLock);
return mSubLedger.emplace (isrListener->getSeq (), isrListener).second;
return mStreamMaps[sLedger].emplace (
isrListener->getSeq (), isrListener).second;
}
// <-- bool: true=erased, false=was not there
bool NetworkOPsImp::unsubLedger (std::uint64_t uSeq)
{
ScopedLockType sl (mSubLock);
return mSubLedger.erase (uSeq);
return mStreamMaps[sLedger].erase (uSeq);
}
// <-- bool: true=added, false=already there
bool NetworkOPsImp::subManifests (InfoSub::ref isrListener)
{
ScopedLockType sl (mSubLock);
return mSubManifests.emplace (isrListener->getSeq (), isrListener).second;
return mStreamMaps[sManifests].emplace (
isrListener->getSeq (), isrListener).second;
}
// <-- bool: true=erased, false=was not there
bool NetworkOPsImp::unsubManifests (std::uint64_t uSeq)
{
ScopedLockType sl (mSubLock);
return mSubManifests.erase (uSeq);
return mStreamMaps[sManifests].erase (uSeq);
}
// <-- bool: true=added, false=already there
@@ -2832,21 +2850,22 @@ bool NetworkOPsImp::subServer (InfoSub::ref isrListener, Json::Value& jvResult,
app_.nodeIdentity().first);
ScopedLockType sl (mSubLock);
return mSubServer.emplace (isrListener->getSeq (), isrListener).second;
return mStreamMaps[sServer].emplace (
isrListener->getSeq (), isrListener).second;
}
// <-- bool: true=erased, false=was not there
bool NetworkOPsImp::unsubServer (std::uint64_t uSeq)
{
ScopedLockType sl (mSubLock);
return mSubServer.erase (uSeq);
return mStreamMaps[sServer].erase (uSeq);
}
// <-- bool: true=added, false=already there
bool NetworkOPsImp::subTransactions (InfoSub::ref isrListener)
{
ScopedLockType sl (mSubLock);
return mSubTransactions.emplace (
return mStreamMaps[sTransactions].emplace (
isrListener->getSeq (), isrListener).second;
}
@@ -2854,14 +2873,14 @@ bool NetworkOPsImp::subTransactions (InfoSub::ref isrListener)
bool NetworkOPsImp::unsubTransactions (std::uint64_t uSeq)
{
ScopedLockType sl (mSubLock);
return mSubTransactions.erase (uSeq);
return mStreamMaps[sTransactions].erase (uSeq);
}
// <-- bool: true=added, false=already there
bool NetworkOPsImp::subRTTransactions (InfoSub::ref isrListener)
{
ScopedLockType sl (mSubLock);
return mSubRTTransactions.emplace (
return mStreamMaps[sRTTransactions].emplace (
isrListener->getSeq (), isrListener).second;
}
@@ -2869,35 +2888,37 @@ bool NetworkOPsImp::subRTTransactions (InfoSub::ref isrListener)
bool NetworkOPsImp::unsubRTTransactions (std::uint64_t uSeq)
{
ScopedLockType sl (mSubLock);
return mSubRTTransactions.erase (uSeq);
return mStreamMaps[sRTTransactions].erase (uSeq);
}
// <-- bool: true=added, false=already there
bool NetworkOPsImp::subValidations (InfoSub::ref isrListener)
{
ScopedLockType sl (mSubLock);
return mSubValidations.emplace (isrListener->getSeq (), isrListener).second;
return mStreamMaps[sValidations].emplace (
isrListener->getSeq (), isrListener).second;
}
// <-- bool: true=erased, false=was not there
bool NetworkOPsImp::unsubValidations (std::uint64_t uSeq)
{
ScopedLockType sl (mSubLock);
return mSubValidations.erase (uSeq);
return mStreamMaps[sValidations].erase (uSeq);
}
// <-- bool: true=added, false=already there
bool NetworkOPsImp::subPeerStatus (InfoSub::ref isrListener)
{
ScopedLockType sl (mSubLock);
return mSubPeerStatus.emplace (isrListener->getSeq (), isrListener).second;
return mStreamMaps[sPeerStatus].emplace (
isrListener->getSeq (), isrListener).second;
}
// <-- bool: true=erased, false=was not there
bool NetworkOPsImp::unsubPeerStatus (std::uint64_t uSeq)
{
ScopedLockType sl (mSubLock);
return mSubPeerStatus.erase (uSeq);
return mStreamMaps[sPeerStatus].erase (uSeq);
}
InfoSub::pointer NetworkOPsImp::findRpcSub (std::string const& strUrl)
@@ -2922,6 +2943,25 @@ InfoSub::pointer NetworkOPsImp::addRpcSub (
return rspEntry;
}
bool NetworkOPsImp::tryRemoveRpcSub (std::string const& strUrl)
{
ScopedLockType sl (mSubLock);
auto pInfo = findRpcSub(strUrl);
if (!pInfo)
return false;
// check to see if any of the stream maps still hold a weak reference to
// this entry before removing
for (SubMapType const& map : mStreamMaps)
{
if (map.find(pInfo->getSeq()) != map.end())
return false;
}
mRpcSubMap.erase(strUrl);
return true;
}
#ifndef USE_NEW_BOOK_PAGE
// NIKB FIXME this should be looked at. There's no reason why this shouldn't

View File

@@ -115,6 +115,7 @@ public:
//
virtual pointer findRpcSub (std::string const& strUrl) = 0;
virtual pointer addRpcSub (std::string const& strUrl, ref rspEntry) = 0;
virtual bool tryRemoveRpcSub (std::string const& strUrl) = 0;
};
public:

View File

@@ -70,12 +70,18 @@ Json::Value doSubscribe (RPC::Context& context)
{
JLOG (context.j.debug())
<< "doSubscribe: building: " << strUrl;
auto rspSub = make_RPCSub (context.app.getOPs (),
context.app.getIOService (), context.app.getJobQueue (),
strUrl, strUsername, strPassword, context.app.logs ());
ispSub = context.netOps.addRpcSub (
strUrl, std::dynamic_pointer_cast<InfoSub> (rspSub));
try
{
auto rspSub = make_RPCSub (context.app.getOPs (),
context.app.getIOService (), context.app.getJobQueue (),
strUrl, strUsername, strPassword, context.app.logs ());
ispSub = context.netOps.addRpcSub (
strUrl, std::dynamic_pointer_cast<InfoSub> (rspSub));
}
catch (std::runtime_error& ex)
{
return RPC::make_param_error (ex.what());
}
}
else
{
@@ -222,8 +228,8 @@ Json::Value doSubscribe (RPC::Context& context)
if (! taker_gets.isMember (jss::currency) || !to_currency (
book.out.currency, taker_gets[jss::currency].asString ()))
{
JLOG (context.j.info()) << "Bad taker_pays currency.";
return rpcError (rpcSRC_CUR_MALFORMED);
JLOG (context.j.info()) << "Bad taker_gets currency.";
return rpcError (rpcDST_AMT_MALFORMED);
}
// Parse optional issuer.

View File

@@ -29,13 +29,12 @@
namespace ripple {
// FIXME: This leaks RPCSub objects for JSON-RPC. Shouldn't matter for anyone
// sane.
Json::Value doUnsubscribe (RPC::Context& context)
{
InfoSub::pointer ispSub;
Json::Value jvResult (Json::objectValue);
bool removeUrl {false};
if (! context.infoSub && ! context.params.isMember(jss::url))
{
@@ -52,6 +51,7 @@ Json::Value doUnsubscribe (RPC::Context& context)
ispSub = context.netOps.findRpcSub (strUrl);
if (! ispSub)
return jvResult;
removeUrl = true;
}
else
{
@@ -177,9 +177,9 @@ Json::Value doUnsubscribe (RPC::Context& context)
|| !to_currency (book.out.currency,
taker_gets[jss::currency].asString ()))
{
JLOG (context.j.info()) << "Bad taker_pays currency.";
JLOG (context.j.info()) << "Bad taker_gets currency.";
return rpcError (rpcSRC_CUR_MALFORMED);
return rpcError (rpcDST_AMT_MALFORMED);
}
// Parse optional issuer.
else if (((taker_gets.isMember (jss::issuer))
@@ -213,6 +213,11 @@ Json::Value doUnsubscribe (RPC::Context& context)
}
}
if (removeUrl)
{
context.netOps.tryRemoveRpcSub(context.params[jss::url].asString ());
}
return jvResult;
}

View File

@@ -21,6 +21,7 @@
#include <ripple/core/ConfigSections.h>
#include <ripple/protocol/JsonFields.h>
#include <test/jtx/WSClient.h>
#include <test/jtx/envconfig.h>
#include <test/jtx.h>
#include <ripple/beast/unit_test.h>
@@ -382,6 +383,259 @@ public:
BEAST_EXPECT(jv[jss::status] == "success");
}
void
testSubByUrl()
{
using namespace jtx;
testcase("Subscribe by url");
Env env {*this};
Json::Value jv;
jv[jss::url] = "http://localhost/events";
jv[jss::url_username] = "admin";
jv[jss::url_password] = "password";
jv[jss::streams] = Json::arrayValue;
jv[jss::streams][0u] = "validations";
auto jr = env.rpc("json", "subscribe", to_string(jv)) [jss::result];
BEAST_EXPECT(jr[jss::status] == "success");
jv[jss::streams][0u] = "ledger";
jr = env.rpc("json", "subscribe", to_string(jv)) [jss::result];
BEAST_EXPECT(jr[jss::status] == "success");
jr = env.rpc("json", "unsubscribe", to_string(jv)) [jss::result];
BEAST_EXPECT(jr[jss::status] == "success");
jv[jss::streams][0u] = "validations";
jr = env.rpc("json", "unsubscribe", to_string(jv)) [jss::result];
BEAST_EXPECT(jr[jss::status] == "success");
}
void
testSubErrors(bool subscribe)
{
using namespace jtx;
auto const method = subscribe ? "subscribe" : "unsubscribe";
testcase << "Error cases for " << method;
Env env {*this};
auto wsc = makeWSClient(env.app().config());
{
auto jr = env.rpc("json", method, "{}") [jss::result];
BEAST_EXPECT(jr[jss::error] == "invalidParams");
BEAST_EXPECT(jr[jss::error_message] == "Invalid parameters.");
}
{
Json::Value jv;
jv[jss::url] = "not-a-url";
jv[jss::username] = "admin";
jv[jss::password] = "password";
auto jr = env.rpc("json", method, to_string(jv)) [jss::result];
if (subscribe)
{
BEAST_EXPECT(jr[jss::error] == "invalidParams");
BEAST_EXPECT(jr[jss::error_message] == "Failed to parse url.");
}
// else TODO: why isn't this an error for unsubscribe ?
// (findRpcSub returns null)
}
{
Json::Value jv;
jv[jss::url] = "ftp://scheme.not.supported.tld";
auto jr = env.rpc("json", method, to_string(jv)) [jss::result];
if (subscribe)
{
BEAST_EXPECT(jr[jss::error] == "invalidParams");
BEAST_EXPECT(jr[jss::error_message] == "Only http and https is supported.");
}
}
{
Env env_nonadmin {*this, no_admin(envconfig(port_increment, 2))};
Json::Value jv;
jv[jss::url] = "no-url";
auto jr = env_nonadmin.rpc("json", method, to_string(jv)) [jss::result];
BEAST_EXPECT(jr[jss::error] == "noPermission");
BEAST_EXPECT(jr[jss::error_message] == "You don't have permission for this command.");
}
for (auto const& f : {jss::accounts_proposed, jss::accounts})
{
{
Json::Value jv;
jv[f] = "";
auto jr = wsc->invoke(method, jv) [jss::result];
BEAST_EXPECT(jr[jss::error] == "invalidParams");
BEAST_EXPECT(jr[jss::error_message] == "Invalid parameters.");
}
{
Json::Value jv;
jv[f] = Json::arrayValue;
auto jr = wsc->invoke(method, jv) [jss::result];
BEAST_EXPECT(jr[jss::error] == "actMalformed");
BEAST_EXPECT(jr[jss::error_message] == "Account malformed.");
}
}
{
Json::Value jv;
jv[jss::books] = "";
auto jr = wsc->invoke(method, jv) [jss::result];
BEAST_EXPECT(jr[jss::error] == "invalidParams");
BEAST_EXPECT(jr[jss::error_message] == "Invalid parameters.");
}
{
Json::Value jv;
jv[jss::books] = Json::arrayValue;
jv[jss::books][0u] = 1;
auto jr = wsc->invoke(method, jv) [jss::result];
BEAST_EXPECT(jr[jss::error] == "invalidParams");
BEAST_EXPECT(jr[jss::error_message] == "Invalid parameters.");
}
{
Json::Value jv;
jv[jss::books] = Json::arrayValue;
jv[jss::books][0u] = Json::objectValue;
jv[jss::books][0u][jss::taker_gets] = Json::objectValue;
jv[jss::books][0u][jss::taker_pays] = Json::objectValue;
auto jr = wsc->invoke(method, jv) [jss::result];
BEAST_EXPECT(jr[jss::error] == "srcCurMalformed");
BEAST_EXPECT(jr[jss::error_message] == "Source currency is malformed.");
}
{
Json::Value jv;
jv[jss::books] = Json::arrayValue;
jv[jss::books][0u] = Json::objectValue;
jv[jss::books][0u][jss::taker_gets] = Json::objectValue;
jv[jss::books][0u][jss::taker_pays] = Json::objectValue;
jv[jss::books][0u][jss::taker_pays][jss::currency] = "ZZZZ";
auto jr = wsc->invoke(method, jv) [jss::result];
BEAST_EXPECT(jr[jss::error] == "srcCurMalformed");
BEAST_EXPECT(jr[jss::error_message] == "Source currency is malformed.");
}
{
Json::Value jv;
jv[jss::books] = Json::arrayValue;
jv[jss::books][0u] = Json::objectValue;
jv[jss::books][0u][jss::taker_gets] = Json::objectValue;
jv[jss::books][0u][jss::taker_pays] = Json::objectValue;
jv[jss::books][0u][jss::taker_pays][jss::currency] = "USD";
jv[jss::books][0u][jss::taker_pays][jss::issuer] = 1;
auto jr = wsc->invoke(method, jv) [jss::result];
BEAST_EXPECT(jr[jss::error] == "srcIsrMalformed");
BEAST_EXPECT(jr[jss::error_message] == "Source issuer is malformed.");
}
{
Json::Value jv;
jv[jss::books] = Json::arrayValue;
jv[jss::books][0u] = Json::objectValue;
jv[jss::books][0u][jss::taker_gets] = Json::objectValue;
jv[jss::books][0u][jss::taker_pays] = Json::objectValue;
jv[jss::books][0u][jss::taker_pays][jss::currency] = "USD";
jv[jss::books][0u][jss::taker_pays][jss::issuer] = Account{"gateway"}.human() + "DEAD";
auto jr = wsc->invoke(method, jv) [jss::result];
BEAST_EXPECT(jr[jss::error] == "srcIsrMalformed");
BEAST_EXPECT(jr[jss::error_message] == "Source issuer is malformed.");
}
{
Json::Value jv;
jv[jss::books] = Json::arrayValue;
jv[jss::books][0u] = Json::objectValue;
jv[jss::books][0u][jss::taker_pays] = Account{"gateway"}["USD"](1).value().getJson(1);
jv[jss::books][0u][jss::taker_gets] = Json::objectValue;
auto jr = wsc->invoke(method, jv) [jss::result];
// NOTE: this error is slightly incongruous with the
// equivalent source currency error
BEAST_EXPECT(jr[jss::error] == "dstAmtMalformed");
BEAST_EXPECT(jr[jss::error_message] == "Destination amount/currency/issuer is malformed.");
}
{
Json::Value jv;
jv[jss::books] = Json::arrayValue;
jv[jss::books][0u] = Json::objectValue;
jv[jss::books][0u][jss::taker_pays] = Account{"gateway"}["USD"](1).value().getJson(1);
jv[jss::books][0u][jss::taker_gets][jss::currency] = "ZZZZ";
auto jr = wsc->invoke(method, jv) [jss::result];
// NOTE: this error is slightly incongruous with the
// equivalent source currency error
BEAST_EXPECT(jr[jss::error] == "dstAmtMalformed");
BEAST_EXPECT(jr[jss::error_message] == "Destination amount/currency/issuer is malformed.");
}
{
Json::Value jv;
jv[jss::books] = Json::arrayValue;
jv[jss::books][0u] = Json::objectValue;
jv[jss::books][0u][jss::taker_pays] = Account{"gateway"}["USD"](1).value().getJson(1);
jv[jss::books][0u][jss::taker_gets][jss::currency] = "USD";
jv[jss::books][0u][jss::taker_gets][jss::issuer] = 1;
auto jr = wsc->invoke(method, jv) [jss::result];
BEAST_EXPECT(jr[jss::error] == "dstIsrMalformed");
BEAST_EXPECT(jr[jss::error_message] == "Destination issuer is malformed.");
}
{
Json::Value jv;
jv[jss::books] = Json::arrayValue;
jv[jss::books][0u] = Json::objectValue;
jv[jss::books][0u][jss::taker_pays] = Account{"gateway"}["USD"](1).value().getJson(1);
jv[jss::books][0u][jss::taker_gets][jss::currency] = "USD";
jv[jss::books][0u][jss::taker_gets][jss::issuer] = Account{"gateway"}.human() + "DEAD";
auto jr = wsc->invoke(method, jv) [jss::result];
BEAST_EXPECT(jr[jss::error] == "dstIsrMalformed");
BEAST_EXPECT(jr[jss::error_message] == "Destination issuer is malformed.");
}
{
Json::Value jv;
jv[jss::books] = Json::arrayValue;
jv[jss::books][0u] = Json::objectValue;
jv[jss::books][0u][jss::taker_pays] = Account{"gateway"}["USD"](1).value().getJson(1);
jv[jss::books][0u][jss::taker_gets] = Account{"gateway"}["USD"](1).value().getJson(1);
auto jr = wsc->invoke(method, jv) [jss::result];
BEAST_EXPECT(jr[jss::error] == "badMarket");
BEAST_EXPECT(jr[jss::error_message] == "No such market.");
}
{
Json::Value jv;
jv[jss::streams] = "";
auto jr = wsc->invoke(method, jv) [jss::result];
BEAST_EXPECT(jr[jss::error] == "invalidParams");
BEAST_EXPECT(jr[jss::error_message] == "Invalid parameters.");
}
{
Json::Value jv;
jv[jss::streams] = Json::arrayValue;
jv[jss::streams][0u] = 1;
auto jr = wsc->invoke(method, jv) [jss::result];
BEAST_EXPECT(jr[jss::error] == "malformedStream");
BEAST_EXPECT(jr[jss::error_message] == "Stream malformed.");
}
{
Json::Value jv;
jv[jss::streams] = Json::arrayValue;
jv[jss::streams][0u] = "not_a_stream";
auto jr = wsc->invoke(method, jv) [jss::result];
BEAST_EXPECT(jr[jss::error] == "malformedStream");
BEAST_EXPECT(jr[jss::error_message] == "Stream malformed.");
}
}
void run() override
{
testServer();
@@ -389,6 +643,9 @@ public:
testTransactions();
testManifests();
testValidations();
testSubErrors(true);
testSubErrors(false);
testSubByUrl();
}
};