From 7b82051bdbc5574106be30cd3ceddcf752edd2ce Mon Sep 17 00:00:00 2001 From: Mike Ellery Date: Fri, 27 Jan 2017 15:05:46 -0800 Subject: [PATCH] Add test for feature RPC (RIPD-1391): Create unit test for feature RPC method. Add client_error field to env RPC requests to provide information about parsing errors. --- Builds/VisualStudio2015/RippleD.vcxproj | 4 + .../VisualStudio2015/RippleD.vcxproj.filters | 3 + src/test/jtx/impl/Env.cpp | 12 +- src/test/rpc/Feature_test.cpp | 283 ++++++++++++++++++ src/test/unity/rpc_test_unity.cpp | 1 + 5 files changed, 302 insertions(+), 1 deletion(-) create mode 100644 src/test/rpc/Feature_test.cpp diff --git a/Builds/VisualStudio2015/RippleD.vcxproj b/Builds/VisualStudio2015/RippleD.vcxproj index a465874930..43674ff017 100644 --- a/Builds/VisualStudio2015/RippleD.vcxproj +++ b/Builds/VisualStudio2015/RippleD.vcxproj @@ -4863,6 +4863,10 @@ True True + + True + True + True True diff --git a/Builds/VisualStudio2015/RippleD.vcxproj.filters b/Builds/VisualStudio2015/RippleD.vcxproj.filters index 42399cfd1c..4d8af37aee 100644 --- a/Builds/VisualStudio2015/RippleD.vcxproj.filters +++ b/Builds/VisualStudio2015/RippleD.vcxproj.filters @@ -5565,6 +5565,9 @@ test\rpc + + test\rpc + test\rpc diff --git a/src/test/jtx/impl/Env.cpp b/src/test/jtx/impl/Env.cpp index fff1ac2b58..4a7564e620 100644 --- a/src/test/jtx/impl/Env.cpp +++ b/src/test/jtx/impl/Env.cpp @@ -517,13 +517,23 @@ Env::do_rpc(std::vector const& args) jv[jss::ripplerpc] = "2.0"; jv[jss::id] = 5; } - auto response = client().invoke(jv[jss::method].asString(), jv[jss::params][0U]); + auto response = client().invoke( + jv[jss::method].asString(), + jv[jss::params][0U]); + if (jv.isMember(jss::jsonrpc)) { response[jss::jsonrpc] = jv[jss::jsonrpc]; response[jss::ripplerpc] = jv[jss::ripplerpc]; response[jss::id] = jv[jss::id]; } + + if (jv[jss::params][0u].isMember(jss::error) && + (! response.isMember(jss::error))) + { + response["client_error"] = jv[jss::params][0u]; + } + return response; } diff --git a/src/test/rpc/Feature_test.cpp b/src/test/rpc/Feature_test.cpp new file mode 100644 index 0000000000..a375adf877 --- /dev/null +++ b/src/test/rpc/Feature_test.cpp @@ -0,0 +1,283 @@ +//------------------------------------------------------------------------------ +/* + 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 +#include +#include +#include +#include + +namespace ripple { + +class Feature_test : public beast::unit_test::suite +{ + void + testNoParams() + { + testcase ("No Params, None Enabled"); + + using namespace test::jtx; + Env env {*this}; + + auto jrr = env.rpc("feature") [jss::result]; + if(! BEAST_EXPECT(jrr.isMember(jss::features))) + return; + for(auto const& feature : jrr[jss::features]) + { + if(! BEAST_EXPECT(feature.isMember(jss::name))) + return; + //default config - so all should be disabled, not vetoed, and supported + BEAST_EXPECTS(! feature[jss::enabled].asBool(), + feature[jss::name].asString() + " enabled"); + BEAST_EXPECTS(! feature[jss::vetoed].asBool(), + feature[jss::name].asString() + " vetoed"); + BEAST_EXPECTS(feature[jss::supported].asBool(), + feature[jss::name].asString() + " supported"); + } + } + + void + testSingleFeature() + { + testcase ("Feature Param"); + + using namespace test::jtx; + Env env {*this}; + + auto jrr = env.rpc("feature", "CryptoConditions") [jss::result]; + BEAST_EXPECTS(jrr[jss::status] == jss::success, "status"); + jrr.removeMember(jss::status); + BEAST_EXPECT(jrr.size() == 1); + auto feature = *(jrr.begin()); + + BEAST_EXPECTS(feature[jss::name] == "CryptoConditions", "name"); + BEAST_EXPECTS(! feature[jss::enabled].asBool(), "enabled"); + BEAST_EXPECTS(! feature[jss::vetoed].asBool(), "vetoed"); + BEAST_EXPECTS(feature[jss::supported].asBool(), "supported"); + + // feature names are case-sensitive - expect error here + jrr = env.rpc("feature", "cryptoconditions") [jss::result]; + BEAST_EXPECT(jrr[jss::error] == "badFeature"); + BEAST_EXPECT(jrr[jss::error_message] == "Feature unknown or invalid."); + } + + void + testInvalidFeature() + { + testcase ("Invalid Feature"); + + using namespace test::jtx; + Env env {*this}; + + auto jrr = env.rpc("feature", "AllTheThings") [jss::result]; + BEAST_EXPECT(jrr[jss::error] == "badFeature"); + BEAST_EXPECT(jrr[jss::error_message] == "Feature unknown or invalid."); + } + + void + testNonAdmin() + { + testcase ("Feature Without Admin"); + + using namespace test::jtx; + Env env {*this, envconfig([](std::unique_ptr cfg) { + (*cfg)["port_rpc"].set("admin",""); + (*cfg)["port_ws"].set("admin",""); + return cfg; + })}; + + auto jrr = env.rpc("feature") [jss::result]; + // The current HTTP/S ServerHandler returns an HTTP 403 error code here + // rather than a noPermission JSON error. The JSONRPCClient just eats that + // error and returns an null result. + BEAST_EXPECT(jrr.isNull()); + } + + void + testSomeEnabled() + { + testcase ("No Params, Some Enabled"); + + using namespace test::jtx; + Env env {*this, + features(featureEscrow), + features(featureCryptoConditions)}; + // The amendment table has to be modified + // since that is what feature RPC actually checks + env.app().getAmendmentTable().enable(featureEscrow); + env.app().getAmendmentTable().enable(featureCryptoConditions); + + auto jrr = env.rpc("feature") [jss::result]; + if(! BEAST_EXPECT(jrr.isMember(jss::features))) + return; + for(auto it = jrr[jss::features].begin(); + it != jrr[jss::features].end(); ++it) + { + uint256 id; + id.SetHexExact(it.key().asString().c_str()); + if(! BEAST_EXPECT((*it).isMember(jss::name))) + return; + bool expectEnabled = env.app().getAmendmentTable().isEnabled(id); + bool expectSupported = env.app().getAmendmentTable().isSupported(id); + BEAST_EXPECTS((*it)[jss::enabled].asBool() == expectEnabled, + (*it)[jss::name].asString() + " enabled"); + BEAST_EXPECTS(! (*it)[jss::vetoed].asBool(), + (*it)[jss::name].asString() + " vetoed"); + BEAST_EXPECTS((*it)[jss::supported].asBool() == expectSupported, + (*it)[jss::name].asString() + " supported"); + } + } + + void + testWithMajorities() + { + testcase ("With Majorities"); + + using namespace test::jtx; + Env env {*this, envconfig(validator, "")}; + + auto jrr = env.rpc("feature") [jss::result]; + if(! BEAST_EXPECT(jrr.isMember(jss::features))) + return; + + // at this point, there are no majorities so no fields related to + // amendment voting + for(auto const& feature : jrr[jss::features]) + { + if(! BEAST_EXPECT(feature.isMember(jss::name))) + return; + BEAST_EXPECTS(! feature.isMember(jss::majority), + feature[jss::name].asString() + " majority"); + BEAST_EXPECTS(! feature.isMember(jss::count), + feature[jss::name].asString() + " count"); + BEAST_EXPECTS(! feature.isMember(jss::threshold), + feature[jss::name].asString() + " threshold"); + BEAST_EXPECTS(! feature.isMember(jss::validations), + feature[jss::name].asString() + " validations"); + BEAST_EXPECTS(! feature.isMember(jss::vote), + feature[jss::name].asString() + " vote"); + } + + auto majorities = getMajorityAmendments (*env.closed()); + if(! BEAST_EXPECT(majorities.empty())) + return; + + // close ledgers until the amendments show up. + for (auto i = 0; i <= 256; ++i) + { + env.close(); + majorities = getMajorityAmendments (*env.closed()); + if (! majorities.empty()) + break; + } + + // There should be at least 5 amendments. Don't do exact comparison + // to avoid maintenance as more amendments are added in the future. + BEAST_EXPECT(majorities.size() >= 5); + + jrr = env.rpc("feature") [jss::result]; + if(! BEAST_EXPECT(jrr.isMember(jss::features))) + return; + for(auto const& feature : jrr[jss::features]) + { + if(! BEAST_EXPECT(feature.isMember(jss::name))) + return; + BEAST_EXPECTS(feature.isMember(jss::majority), + feature[jss::name].asString() + " majority"); + BEAST_EXPECTS(feature.isMember(jss::count), + feature[jss::name].asString() + " count"); + BEAST_EXPECTS(feature.isMember(jss::threshold), + feature[jss::name].asString() + " threshold"); + BEAST_EXPECTS(feature.isMember(jss::validations), + feature[jss::name].asString() + " validations"); + BEAST_EXPECTS(feature.isMember(jss::vote), + feature[jss::name].asString() + " vote"); + BEAST_EXPECT(feature[jss::vote] == 256); + BEAST_EXPECT(feature[jss::majority] == 2740); + } + + } + + void + testVeto() + { + testcase ("Veto"); + + using namespace test::jtx; + Env env {*this, + features(featureCryptoConditions)}; + // The amendment table has to be modified + // since that is what feature RPC actually checks + env.app().getAmendmentTable().enable(featureCryptoConditions); + + auto jrr = env.rpc("feature", "CryptoConditions") [jss::result]; + if(! BEAST_EXPECTS(jrr[jss::status] == jss::success, "status")) + return; + jrr.removeMember(jss::status); + if(! BEAST_EXPECT(jrr.size() == 1)) + return; + auto feature = *(jrr.begin()); + BEAST_EXPECTS(feature[jss::name] == "CryptoConditions", "name"); + BEAST_EXPECTS(! feature[jss::vetoed].asBool(), "vetoed"); + + jrr = env.rpc("feature", "CryptoConditions", "reject") [jss::result]; + if(! BEAST_EXPECTS(jrr[jss::status] == jss::success, "status")) + return; + jrr.removeMember(jss::status); + if(! BEAST_EXPECT(jrr.size() == 1)) + return; + feature = *(jrr.begin()); + BEAST_EXPECTS(feature[jss::name] == "CryptoConditions", "name"); + BEAST_EXPECTS(feature[jss::vetoed].asBool(), "vetoed"); + + jrr = env.rpc("feature", "CryptoConditions", "accept") [jss::result]; + if(! BEAST_EXPECTS(jrr[jss::status] == jss::success, "status")) + return; + jrr.removeMember(jss::status); + if(! BEAST_EXPECT(jrr.size() == 1)) + return; + feature = *(jrr.begin()); + BEAST_EXPECTS(feature[jss::name] == "CryptoConditions", "name"); + BEAST_EXPECTS(! feature[jss::vetoed].asBool(), "vetoed"); + + // anything other than accept or reject is an error + jrr = env.rpc("feature", "CryptoConditions", "maybe"); + if(! BEAST_EXPECT(jrr.isMember("client_error"))) + return; + BEAST_EXPECT(jrr["client_error"][jss::error] == "invalidParams"); + BEAST_EXPECT(jrr["client_error"][jss::error_message] == "Invalid parameters."); + } + +public: + + void run() override + { + testNoParams(); + testSingleFeature(); + testInvalidFeature(); + testNonAdmin(); + testSomeEnabled(); + testWithMajorities(); + testVeto(); + } +}; + +BEAST_DEFINE_TESTSUITE(Feature,rpc,ripple); + +} // ripple diff --git a/src/test/unity/rpc_test_unity.cpp b/src/test/unity/rpc_test_unity.cpp index 2ab89c9d1b..cd154b9862 100644 --- a/src/test/unity/rpc_test_unity.cpp +++ b/src/test/unity/rpc_test_unity.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include