Support API versioning

This commit is contained in:
Peng Wang
2019-11-11 10:48:51 -05:00
committed by Nik Bougalis
parent 79e9085dd1
commit 2aa11fa41d
16 changed files with 714 additions and 53 deletions

View File

@@ -61,6 +61,7 @@
#include <ripple/protocol/STParsedJSON.h>
#include <ripple/protocol/Protocol.h>
#include <ripple/resource/Fees.h>
#include <ripple/rpc/impl/RPCHelpers.h>
#include <ripple/beast/asio/io_latency_probe.h>
#include <ripple/beast/core/LexicalCast.h>
@@ -1583,7 +1584,8 @@ bool ApplicationImp::setup()
Resource::Charge loadType = Resource::feeReferenceRPC;
Resource::Consumer c;
RPC::Context context { journal ("RPCHandler"), jvCommand, *this,
loadType, getOPs (), getLedgerMaster (), c, Role::ADMIN};
loadType, getOPs (), getLedgerMaster(), c, Role::ADMIN,
RPC::ApiMaximumSupportedVersion};
Json::Value jvResult;
RPC::doCommand (context, jvResult);

View File

@@ -36,6 +36,7 @@
#include <ripple/protocol/SystemParameters.h>
#include <ripple/protocol/UintTypes.h>
#include <ripple/rpc/ServerHandler.h>
#include <ripple/rpc/impl/RPCHelpers.h>
#include <ripple/beast/core/LexicalCast.h>
#include <boost/algorithm/string/predicate.hpp>
@@ -1360,6 +1361,20 @@ rpcCmdLineToJson (std::vector<std::string> const& args,
jvRequest = rpParser.parseCommand (args[0], jvRpcParams, true);
auto insert_api_version = [](Json::Value & jr){
if( jr.isObject() &&
!jr.isMember(jss::error) &&
!jr.isMember(jss::api_version))
{
jr[jss::api_version] = RPC::ApiMaximumSupportedVersion;
}
};
if(jvRequest.isObject())
insert_api_version(jvRequest);
else if(jvRequest.isArray())
std::for_each(jvRequest.begin(), jvRequest.end(), insert_api_version);
JLOG (j.trace()) << "RPC Request: " << jvRequest << std::endl;
return jvRequest;
}

View File

@@ -123,6 +123,8 @@ JSS ( alternatives ); // out: PathRequest, RipplePathFind
JSS ( amendment_blocked ); // out: NetworkOPs
JSS ( amendments ); // in: AccountObjects, out: NetworkOPs
JSS ( amount ); // out: AccountChannels
JSS ( api_version); // in: many, out: Version
JSS ( api_version_low); // out: Version
JSS ( applied ); // out: SubmitTransaction
JSS ( asks ); // out: Subscribe
JSS ( assets ); // out: GatewayBalances
@@ -265,6 +267,8 @@ JSS ( index ); // in: LedgerEntry, DownloadShard
// field
JSS ( info ); // out: ServerInfo, ConsensusInfo, FetchInfo
JSS ( internal_command ); // in: Internal
JSS ( invalid_API_version ); // out: Many, when a request has an invalid
// version
JSS ( io_latency_ms ); // out: NetworkOPs
JSS ( ip ); // in: Connect, out: OverlayImpl
JSS ( issuer ); // in: RipplePathFind, Subscribe,

View File

@@ -55,6 +55,7 @@ struct Context
LedgerMaster& ledgerMaster;
Resource::Consumer& consumer;
Role role;
unsigned int apiVersion;
std::shared_ptr<JobQueue::Coro> coro {};
InfoSub::pointer infoSub {};
Headers headers {};

View File

@@ -33,7 +33,7 @@ struct Context;
/** Execute an RPC command and store the results in a Json::Value. */
Status doCommand (RPC::Context&, Json::Value&);
Role roleRequired (std::string const& method );
Role roleRequired (unsigned int version, std::string const& method );
} // RPC
} // ripple

View File

@@ -20,6 +20,7 @@
#include <ripple/rpc/impl/Handler.h>
#include <ripple/rpc/handlers/Handlers.h>
#include <ripple/rpc/handlers/Version.h>
#include <ripple/rpc/impl/RPCHelpers.h>
namespace ripple {
namespace RPC {
@@ -129,16 +130,21 @@ class HandlerTable {
explicit
HandlerTable (const Handler(&entries)[N])
{
for (std::size_t i = 0; i < N; ++i)
for(auto v = RPC::ApiMinimumSupportedVersion; v <= RPC::ApiMaximumSupportedVersion; ++v)
{
auto const& entry = entries[i];
assert (table_.find(entry.name_) == table_.end());
table_[entry.name_] = entry;
}
for (std::size_t i = 0; i < N; ++i)
{
auto & innerTable = table_[versionToIndex(v)];
auto const& entry = entries[i];
assert (innerTable.find(entry.name_) == innerTable.end());
innerTable[entry.name_] = entry;
}
// This is where the new-style handlers are added.
addHandler<LedgerHandler>();
addHandler<VersionHandler>();
// This is where the new-style handlers are added.
// This is also where different versions of handlers are added.
addHandler<LedgerHandler>(v);
addHandler<VersionHandler>(v);
}
}
public:
@@ -148,29 +154,38 @@ class HandlerTable {
return handlerTable;
}
Handler const* getHandler(std::string name) const
Handler const* getHandler(unsigned version, std::string name) const
{
auto i = table_.find(name);
return i == table_.end() ? nullptr : &i->second;
if(version > RPC::ApiMaximumSupportedVersion || version < RPC::ApiMinimumSupportedVersion)
return nullptr;
auto & innerTable = table_[versionToIndex(version)];
auto i = innerTable.find(name);
return i == innerTable.end() ? nullptr : &i->second;
}
std::vector<char const*>
getHandlerNames() const
{
std::vector<char const*> ret;
ret.reserve(table_.size());
for (auto const& i : table_)
ret.push_back(i.second.name_);
return ret;
std::unordered_set<char const*> name_set;
for ( int index = 0; index < table_.size(); ++index)
{
for(auto const& h : table_[index])
{
name_set.insert(h.second.name_);
}
}
return std::vector<char const*>(name_set.begin(), name_set.end());
}
private:
std::map<std::string, Handler> table_;
std::array<std::map<std::string, Handler>, APINumberVersionSupported> table_;
template <class HandlerImpl>
void addHandler()
void addHandler(unsigned version)
{
assert (table_.find(HandlerImpl::name()) == table_.end());
assert (version >= RPC::ApiMinimumSupportedVersion && version <= RPC::ApiMaximumSupportedVersion);
auto & innerTable = table_[versionToIndex(version)];
assert (innerTable.find(HandlerImpl::name()) == innerTable.end());
Handler h;
h.name_ = HandlerImpl::name();
@@ -178,15 +193,20 @@ class HandlerTable {
h.role_ = HandlerImpl::role();
h.condition_ = HandlerImpl::condition();
table_[HandlerImpl::name()] = h;
innerTable[HandlerImpl::name()] = h;
}
inline unsigned versionToIndex(unsigned version) const
{
return version - RPC::ApiMinimumSupportedVersion;
}
};
} // namespace
Handler const* getHandler(std::string const& name)
Handler const* getHandler(unsigned version, std::string const& name)
{
return HandlerTable::instance().getHandler(name);
return HandlerTable::instance().getHandler(version, name);
}
std::vector<char const*>

View File

@@ -51,7 +51,7 @@ struct Handler
RPC::Condition condition_;
};
Handler const* getHandler (std::string const&);
Handler const* getHandler (unsigned int version, std::string const&);
/** Return a Json::objectValue with a single entry. */
template <class Value>

View File

@@ -141,7 +141,7 @@ error_code_i fillHandler (Context& context,
JLOG (context.j.trace()) << "COMMAND:" << strCommand;
JLOG (context.j.trace()) << "REQUEST:" << context.params;
auto handler = getHandler(strCommand);
auto handler = getHandler(context.apiVersion, strCommand);
if (!handler)
return rpcUNKNOWN_COMMAND;
@@ -296,9 +296,9 @@ Status doCommand (
return rpcUNKNOWN_COMMAND;
}
Role roleRequired (std::string const& method)
Role roleRequired (unsigned int version, std::string const& method)
{
auto handler = RPC::getHandler(method);
auto handler = RPC::getHandler(version, method);
if (!handler)
return Role::FORBID;

View File

@@ -716,5 +716,24 @@ beast::SemanticVersion const firstVersion("1.0.0");
beast::SemanticVersion const goodVersion("1.0.0");
beast::SemanticVersion const lastVersion("1.0.0");
unsigned int getAPIVersionNumber(Json::Value const& jv)
{
static Json::Value const minVersion (RPC::ApiMinimumSupportedVersion);
static Json::Value const maxVersion (RPC::ApiMaximumSupportedVersion);
static Json::Value const invalidVersion (RPC::APIInvalidVersion);
Json::Value requestedVersion(RPC::APIVersionIfUnspecified);
if(jv.isObject())
{
requestedVersion = jv.get (jss::api_version, requestedVersion);
}
if( !(requestedVersion.isInt() || requestedVersion.isUInt()) ||
requestedVersion < minVersion || requestedVersion > maxVersion)
{
requestedVersion = invalidVersion;
}
return requestedVersion.asUInt();
}
} // RPC
} // ripple

View File

@@ -114,10 +114,42 @@ parseRippleLibSeed(Json::Value const& params);
std::pair<PublicKey, SecretKey>
keypairForSignature(Json::Value const& params, Json::Value& error);
/**
* API version numbers used in API version 1
*/
extern beast::SemanticVersion const firstVersion;
extern beast::SemanticVersion const goodVersion;
extern beast::SemanticVersion const lastVersion;
/**
* API version numbers used in later API versions
*
* Requests with a version number in the range
* [ApiMinimumSupportedVersion, ApiMaximumSupportedVersion]
* are supported.
*
* Network Requests without explicit version numbers use
* APIVersionIfUnspecified. APIVersionIfUnspecified is 1,
* because all the RPC requests with a version >= 2 must
* explicitly specify the version in the requests.
* Note that APIVersionIfUnspecified will be lower than
* ApiMinimumSupportedVersion when we stop supporting API
* version 1.
*
* Command line Requests use ApiMaximumSupportedVersion.
*/
constexpr unsigned int APIInvalidVersion = 0;
constexpr unsigned int APIVersionIfUnspecified = 1;
constexpr unsigned int ApiMinimumSupportedVersion = 1;
constexpr unsigned int ApiMaximumSupportedVersion = 1;
constexpr unsigned int APINumberVersionSupported = ApiMaximumSupportedVersion -
ApiMinimumSupportedVersion + 1;
static_assert (ApiMinimumSupportedVersion >= APIVersionIfUnspecified);
static_assert (ApiMaximumSupportedVersion >= ApiMinimumSupportedVersion);
template <class Object>
void
setVersion(Object& parent)
@@ -131,6 +163,20 @@ setVersion(Object& parent)
std::pair<RPC::Status, LedgerEntryType>
chooseLedgerEntryType(Json::Value const& params);
/**
* Retrieve the api version number from the json value
*
* Note that APIInvalidVersion will be returned if
* 1) the version number field has a wrong format
* 2) the version number retrieved is out of the supported range
* 3) the version number is unspecified and
* APIVersionIfUnspecified is out of the supported range
*
* @param value a Json value that may or may not specifies
* the api version number
* @return the api version number
*/
unsigned int getAPIVersionNumber(const Json::Value & value);
} // RPC
} // ripple

View File

@@ -28,6 +28,7 @@
#include <ripple/server/Server.h>
#include <ripple/server/impl/JSONRPCUtil.h>
#include <ripple/rpc/impl/ServerHandlerImp.h>
#include <ripple/rpc/impl/RPCHelpers.h>
#include <ripple/basics/contract.h>
#include <ripple/basics/Log.h>
#include <ripple/basics/make_SSLContext.h>
@@ -398,7 +399,9 @@ ServerHandlerImp::processSession(
Resource::Charge loadType = Resource::feeReferenceRPC;
try
{
if ((!jv.isMember(jss::command) && !jv.isMember(jss::method)) ||
auto apiVersion = RPC::getAPIVersionNumber(jv);
if (apiVersion == RPC::APIInvalidVersion ||
(!jv.isMember(jss::command) && !jv.isMember(jss::method)) ||
(jv.isMember(jss::command) && !jv[jss::command].isString()) ||
(jv.isMember(jss::method) && !jv[jss::method].isString()) ||
(jv.isMember(jss::command) && jv.isMember(jss::method) &&
@@ -406,7 +409,8 @@ ServerHandlerImp::processSession(
{
jr[jss::type] = jss::response;
jr[jss::status] = jss::error;
jr[jss::error] = jss::missingCommand;
jr[jss::error] = apiVersion == RPC::APIInvalidVersion ?
jss::invalid_API_version : jss::missingCommand;
jr[jss::request] = jv;
if (jv.isMember (jss::id))
jr[jss::id] = jv[jss::id];
@@ -414,12 +418,15 @@ ServerHandlerImp::processSession(
jr[jss::jsonrpc] = jv[jss::jsonrpc];
if (jv.isMember(jss::ripplerpc))
jr[jss::ripplerpc] = jv[jss::ripplerpc];
if (jv.isMember(jss::api_version))
jr[jss::api_version] = jv[jss::api_version];
is->getConsumer().charge(Resource::feeInvalidRPC);
return jr;
}
auto required = RPC::roleRequired(jv.isMember(jss::command) ?
auto required = RPC::roleRequired(apiVersion,
jv.isMember(jss::command) ?
jv[jss::command].asString() :
jv[jss::method].asString());
auto role = requestRole(
@@ -444,6 +451,7 @@ ServerHandlerImp::processSession(
app_.getLedgerMaster(),
is->getConsumer(),
role,
apiVersion,
coro,
is,
{is->user(), is->forwarded_for()}
@@ -500,6 +508,9 @@ ServerHandlerImp::processSession(
jr[jss::jsonrpc] = jv[jss::jsonrpc];
if (jv.isMember(jss::ripplerpc))
jr[jss::ripplerpc] = jv[jss::ripplerpc];
if (jv.isMember(jss::api_version))
jr[jss::api_version] = jv[jss::api_version];
jr[jss::type] = jss::response;
return jr;
}
@@ -545,6 +556,7 @@ make_json_error(Json::Int code, Json::Value&& message)
Json::Int constexpr method_not_found = -32601;
Json::Int constexpr server_overloaded = -32604;
Json::Int constexpr forbidden = -32605;
Json::Int constexpr wrong_version = -32606;
void
ServerHandlerImp::processRequest (Port const& port,
@@ -597,11 +609,40 @@ ServerHandlerImp::processRequest (Port const& port,
continue;
}
auto apiVersion = RPC::APIVersionIfUnspecified;
if (jsonRPC.isMember(jss::params) &&
jsonRPC[jss::params].isArray() &&
jsonRPC[jss::params].size() > 0 &&
jsonRPC[jss::params][0u].isObject())
{
apiVersion = RPC::getAPIVersionNumber(jsonRPC[jss::params][Json::UInt(0)]);
}
if ( apiVersion == RPC::APIVersionIfUnspecified && batch)
{
// for batch request, api_version may be at a different level
apiVersion = RPC::getAPIVersionNumber(jsonRPC);
}
if(apiVersion == RPC::APIInvalidVersion)
{
if (!batch)
{
HTTPReply (400, jss::invalid_API_version.c_str(), output, rpcJ);
return;
}
Json::Value r(Json::objectValue);
r[jss::request] = jsonRPC;
r[jss::error] = make_json_error(wrong_version, jss::invalid_API_version.c_str());
reply.append(r);
continue;
}
/* ------------------------------------------------------------------ */
auto role = Role::FORBID;
auto required = Role::FORBID;
if (jsonRPC.isMember(jss::method) && jsonRPC[jss::method].isString())
required = RPC::roleRequired(jsonRPC[jss::method].asString());
required = RPC::roleRequired(apiVersion, jsonRPC[jss::method].asString());
if (jsonRPC.isMember(jss::params) &&
jsonRPC[jss::params].isArray() &&
@@ -778,7 +819,7 @@ ServerHandlerImp::processRequest (Port const& port,
Resource::Charge loadType = Resource::feeReferenceRPC;
RPC::Context context {m_journal, params, app_, loadType, m_networkOPs,
app_.getLedgerMaster(), usage, role, coro, InfoSub::pointer(),
app_.getLedgerMaster(), usage, role, apiVersion, coro, InfoSub::pointer(),
{user, forwardedFor}};
Json::Value result;
RPC::doCommand (context, result);

View File

@@ -28,6 +28,7 @@
#include <ripple/resource/Fees.h>
#include <ripple/rpc/Context.h>
#include <ripple/rpc/impl/Tuning.h>
#include <ripple/rpc/impl/RPCHelpers.h>
#include <ripple/rpc/RPCHandler.h>
#include <test/jtx.h>
#include <ripple/beast/unit_test.h>
@@ -221,7 +222,8 @@ public:
Resource::Charge loadType = Resource::feeReferenceRPC;
Resource::Consumer c;
RPC::Context context { env.journal, {}, app, loadType,
app.getOPs(), app.getLedgerMaster(), c, Role::USER};
app.getOPs(), app.getLedgerMaster(), c, Role::USER,
RPC::APIVersionIfUnspecified};
Json::Value params = Json::objectValue;
params[jss::command] = "ripple_path_find";
@@ -320,7 +322,8 @@ public:
Resource::Charge loadType = Resource::feeReferenceRPC;
Resource::Consumer c;
RPC::Context context {env.journal, {}, app, loadType,
app.getOPs(), app.getLedgerMaster(), c, Role::USER};
app.getOPs(), app.getLedgerMaster(), c, Role::USER,
RPC::APIVersionIfUnspecified};
Json::Value result;
gate g;
// Test RPC::Tuning::max_src_cur source currencies.

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,200 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012-2017 Ripple Labs Inc.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#include <test/jtx.h>
#include <ripple/protocol/jss.h>
#include <ripple/rpc/impl/RPCHelpers.h>
namespace ripple {
class Version_test : public beast::unit_test::suite
{
void
testCorrectVersionNumber()
{
testcase ("right api_version: explicitly specified or filled by parser");
using namespace test::jtx;
Env env {*this};
auto isCorrectReply = [](Json::Value const & re) -> bool
{
if(re.isMember(jss::error))
return false;
return re.isMember(jss::version);
};
auto jrr = env.rpc("json", "version", "{\"api_version\": " +
std::to_string(RPC::ApiMaximumSupportedVersion)
+ "}") [jss::result];
BEAST_EXPECT(isCorrectReply(jrr));
jrr = env.rpc("version") [jss::result];
BEAST_EXPECT(isCorrectReply(jrr));
}
void
testWrongVersionNumber()
{
testcase ("wrong api_version: too low, too high, or wrong format");
using namespace test::jtx;
Env env {*this};
auto get_error_what = [](Json::Value const &re) -> std::string
{
if (re.isMember("error_what"))
if (re["error_what"].isString())
return re["error_what"].asString();
return {};
};
auto re = env.rpc("json", "version", "{\"api_version\": " +
std::to_string(RPC::ApiMinimumSupportedVersion - 1)
+ "}");
BEAST_EXPECT(get_error_what(re).find(jss::invalid_API_version.c_str()));
re = env.rpc("json", "version", "{\"api_version\": " +
std::to_string(RPC::ApiMaximumSupportedVersion + 1)
+ "}");
BEAST_EXPECT(get_error_what(re).find(jss::invalid_API_version.c_str()));
re = env.rpc("json", "version", "{\"api_version\": \"a\"}");
BEAST_EXPECT(get_error_what(re).find(jss::invalid_API_version.c_str()));
}
void testGetAPIVersionNumber()
{
testcase ("test getAPIVersionNumber function");
unsigned int versionIfUnspecified =
RPC::APIVersionIfUnspecified < RPC::ApiMinimumSupportedVersion ?
RPC::APIInvalidVersion : RPC::APIVersionIfUnspecified;
Json::Value j_array = Json::Value (Json::arrayValue);
Json::Value j_null = Json::Value (Json::nullValue);
BEAST_EXPECT(RPC::getAPIVersionNumber(j_array) == versionIfUnspecified);
BEAST_EXPECT(RPC::getAPIVersionNumber(j_null) == versionIfUnspecified);
Json::Value j_object = Json::Value (Json::objectValue);
BEAST_EXPECT(RPC::getAPIVersionNumber(j_object) == versionIfUnspecified);
j_object[jss::api_version] = RPC::APIVersionIfUnspecified;
BEAST_EXPECT(RPC::getAPIVersionNumber(j_object) == versionIfUnspecified);
j_object[jss::api_version] = RPC::ApiMinimumSupportedVersion;
BEAST_EXPECT(RPC::getAPIVersionNumber(j_object) == RPC::ApiMinimumSupportedVersion);
j_object[jss::api_version] = RPC::ApiMaximumSupportedVersion;
BEAST_EXPECT(RPC::getAPIVersionNumber(j_object) == RPC::ApiMaximumSupportedVersion);
j_object[jss::api_version] = RPC::ApiMinimumSupportedVersion - 1;
BEAST_EXPECT(RPC::getAPIVersionNumber(j_object) == RPC::APIInvalidVersion);
j_object[jss::api_version] = RPC::ApiMaximumSupportedVersion + 1;
BEAST_EXPECT(RPC::getAPIVersionNumber(j_object) == RPC::APIInvalidVersion);
j_object[jss::api_version] = RPC::APIInvalidVersion;
BEAST_EXPECT(RPC::getAPIVersionNumber(j_object) == RPC::APIInvalidVersion);
j_object[jss::api_version] = "a";
BEAST_EXPECT(RPC::getAPIVersionNumber(j_object) == RPC::APIInvalidVersion);
}
void
testBatch()
{
testcase ("batch, all good request");
using namespace test::jtx;
Env env {*this};
auto const without_api_verion = std::string ("{ ") +
"\"jsonrpc\": \"2.0\", "
"\"ripplerpc\": \"2.0\", "
"\"id\": 5, "
"\"method\": \"version\", "
"\"params\": {}}";
auto const with_api_verion = std::string ("{ ") +
"\"jsonrpc\": \"2.0\", "
"\"ripplerpc\": \"2.0\", "
"\"id\": 6, "
"\"method\": \"version\", "
"\"params\": { "
"\"api_version\": " + std::to_string(RPC::ApiMaximumSupportedVersion) +
"}}";
auto re = env.rpc("json2", '[' + without_api_verion + ", " +
with_api_verion + ']');
if( !BEAST_EXPECT( re.isArray() ))
return;
if( !BEAST_EXPECT( re.size() == 2 ))
return;
BEAST_EXPECT(re[0u].isMember(jss::result) &&
re[0u][jss::result].isMember(jss::version));
BEAST_EXPECT(re[1u].isMember(jss::result) &&
re[1u][jss::result].isMember(jss::version));
}
void
testBatchFail()
{
testcase ("batch, with a bad request");
using namespace test::jtx;
Env env {*this};
auto const without_api_verion = std::string ("{ ") +
"\"jsonrpc\": \"2.0\", "
"\"ripplerpc\": \"2.0\", "
"\"id\": 5, "
"\"method\": \"version\", "
"\"params\": {}}";
auto const with_wrong_api_verion = std::string ("{ ") +
"\"jsonrpc\": \"2.0\", "
"\"ripplerpc\": \"2.0\", "
"\"id\": 6, "
"\"method\": \"version\", "
"\"params\": { "
"\"api_version\": " +
std::to_string(RPC::ApiMaximumSupportedVersion + 1) +
"}}";
auto re = env.rpc("json2", '[' + without_api_verion + ", " +
with_wrong_api_verion + ']');
if( !BEAST_EXPECT( re.isArray() ))
return;
if( !BEAST_EXPECT( re.size() == 2 ))
return;
BEAST_EXPECT(re[0u].isMember(jss::result) &&
re[0u][jss::result].isMember(jss::version));
BEAST_EXPECT(re[1u].isMember(jss::error));
}
public:
void run() override
{
testCorrectVersionNumber();
testWrongVersionNumber();
testGetAPIVersionNumber();
testBatch();
testBatchFail();
}
};
BEAST_DEFINE_TESTSUITE(Version,rpc,ripple);
} // ripple

View File

@@ -53,3 +53,4 @@
#include <test/rpc/TransactionEntry_test.cpp>
#include <test/rpc/TransactionHistory_test.cpp>
#include <test/rpc/ValidatorRPC_test.cpp>
#include <test/rpc/Version_test.cpp>