From 5bc8f2e3e8b6eaaa980875c766ac84d93e3fa2b6 Mon Sep 17 00:00:00 2001 From: Mike Ellery Date: Wed, 29 Mar 2017 10:58:24 -0700 Subject: [PATCH] Add test for noripple_check (RIPD-1400): Add tests. Fix an error type returned in the handler. --- Builds/VisualStudio2015/RippleD.vcxproj | 4 + .../VisualStudio2015/RippleD.vcxproj.filters | 3 + src/ripple/rpc/handlers/NoRippleCheck.cpp | 4 +- src/test/rpc/NoRippleCheck_test.cpp | 334 ++++++++++++++++++ src/test/unity/rpc_test_unity.cpp | 1 + 5 files changed, 344 insertions(+), 2 deletions(-) create mode 100644 src/test/rpc/NoRippleCheck_test.cpp diff --git a/Builds/VisualStudio2015/RippleD.vcxproj b/Builds/VisualStudio2015/RippleD.vcxproj index 1ef62f4db..48c210644 100644 --- a/Builds/VisualStudio2015/RippleD.vcxproj +++ b/Builds/VisualStudio2015/RippleD.vcxproj @@ -4913,6 +4913,10 @@ True True + + True + True + True True diff --git a/Builds/VisualStudio2015/RippleD.vcxproj.filters b/Builds/VisualStudio2015/RippleD.vcxproj.filters index b6b237c0b..1a2f98838 100644 --- a/Builds/VisualStudio2015/RippleD.vcxproj.filters +++ b/Builds/VisualStudio2015/RippleD.vcxproj.filters @@ -5604,6 +5604,9 @@ test\rpc + + test\rpc + test\rpc diff --git a/src/ripple/rpc/handlers/NoRippleCheck.cpp b/src/ripple/rpc/handlers/NoRippleCheck.cpp index 04454f022..23e5eeeec 100644 --- a/src/ripple/rpc/handlers/NoRippleCheck.cpp +++ b/src/ripple/rpc/handlers/NoRippleCheck.cpp @@ -70,7 +70,7 @@ Json::Value doNoRippleCheck (RPC::Context& context) if (role == "gateway") roleGateway = true; else if (role != "user") - return RPC::invalid_field_message ("role"); + return RPC::invalid_field_error ("role"); } unsigned int limit; @@ -96,7 +96,7 @@ Json::Value doNoRippleCheck (RPC::Context& context) if (auto jv = RPC::accountFromString (accountID, strIdent)) { for (auto it (jv.begin ()); it != jv.end (); ++it) - result[it.memberName ()] = it.key (); + result[it.memberName ()] = *it; return result; } diff --git a/src/test/rpc/NoRippleCheck_test.cpp b/src/test/rpc/NoRippleCheck_test.cpp new file mode 100644 index 000000000..020f9aa25 --- /dev/null +++ b/src/test/rpc/NoRippleCheck_test.cpp @@ -0,0 +1,334 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 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 +#include +#include +#include +#include + +namespace ripple { + +class NoRippleCheck_test : public beast::unit_test::suite +{ + void + testBadInput () + { + testcase ("Bad input to noripple_check"); + + using namespace test::jtx; + Env env {*this}; + + auto const alice = Account {"alice"}; + env.fund (XRP(10000), alice); + env.close (); + + { // missing account field + auto const result = env.rpc ("json", "noripple_check", "{}") + [jss::result]; + BEAST_EXPECT (result[jss::error] == "invalidParams"); + BEAST_EXPECT (result[jss::error_message] == + "Missing field 'account'."); + } + + { // missing role field + Json::Value params; + params[jss::account] = alice.human(); + auto const result = env.rpc ("json", "noripple_check", + boost::lexical_cast(params)) [jss::result]; + BEAST_EXPECT (result[jss::error] == "invalidParams"); + BEAST_EXPECT (result[jss::error_message] == + "Missing field 'role'."); + } + + { // invalid role field + Json::Value params; + params[jss::account] = alice.human(); + params[jss::role] = "not_a_role"; + auto const result = env.rpc ("json", "noripple_check", + boost::lexical_cast(params)) [jss::result]; + BEAST_EXPECT (result[jss::error] == "invalidParams"); + BEAST_EXPECT (result[jss::error_message] == + "Invalid field 'role'."); + } + + { // invalid limit + Json::Value params; + params[jss::account] = alice.human(); + params[jss::role] = "user"; + params[jss::limit] = -1; + auto const result = env.rpc ("json", "noripple_check", + boost::lexical_cast(params)) [jss::result]; + BEAST_EXPECT (result[jss::error] == "invalidParams"); + BEAST_EXPECT (result[jss::error_message] == + "Invalid field 'limit', not unsigned integer."); + } + + { // invalid ledger (hash) + Json::Value params; + params[jss::account] = alice.human(); + params[jss::role] = "user"; + params[jss::ledger_hash] = 1; + auto const result = env.rpc ("json", "noripple_check", + boost::lexical_cast(params)) [jss::result]; + BEAST_EXPECT (result[jss::error] == "invalidParams"); + BEAST_EXPECT (result[jss::error_message] == + "ledgerHashNotString"); + } + + { // account not found + Json::Value params; + params[jss::account] = Account{"nobody"}.human(); + params[jss::role] = "user"; + params[jss::ledger] = "current"; + auto const result = env.rpc ("json", "noripple_check", + boost::lexical_cast(params)) [jss::result]; + BEAST_EXPECT (result[jss::error] == "actNotFound"); + BEAST_EXPECT (result[jss::error_message] == + "Account not found."); + } + + { // passing an account private key will cause + // parsing as a seed to fail + Json::Value params; + params[jss::account] = + toBase58 (TokenType::TOKEN_NODE_PRIVATE, alice.sk()); + params[jss::role] = "user"; + params[jss::ledger] = "current"; + auto const result = env.rpc ("json", "noripple_check", + boost::lexical_cast(params)) [jss::result]; + BEAST_EXPECT (result[jss::error] == "badSeed"); + BEAST_EXPECT (result[jss::error_message] == + "Disallowed seed."); + } + } + + void + testBasic (bool user, bool problems) + { + testcase << "Request noripple_check for " << + (user ? "user" : "gateway") << " role, expect" << + (problems ? "" : " no") << " problems"; + + using namespace test::jtx; + Env env {*this}; + + auto const gw = Account {"gw"}; + auto const alice = Account {"alice"}; + + env.fund (XRP(10000), gw, alice); + if ((user && problems) || (!user && !problems)) + { + env (fset (alice, asfDefaultRipple)); + env (trust (alice, gw["USD"](100))); + } + else + { + env (fclear (alice, asfDefaultRipple)); + env (trust (alice, gw["USD"](100), gw, tfSetNoRipple)); + } + env.close (); + + Json::Value params; + params[jss::account] = alice.human(); + params[jss::role] = (user ? "user" : "gateway"); + params[jss::ledger] = "current"; + auto result = env.rpc ("json", "noripple_check", + boost::lexical_cast(params)) [jss::result]; + + auto const pa = result["problems"]; + if (! BEAST_EXPECT (pa.isArray ())) + return; + + if (problems) + { + if (! BEAST_EXPECT (pa.size() == 2)) + return; + + if (user) + { + BEAST_EXPECT ( + boost::starts_with(pa[0u].asString(), + "You appear to have set")); + BEAST_EXPECT ( + boost::starts_with(pa[1u].asString(), + "You should probably set")); + } + else + { + BEAST_EXPECT ( + boost::starts_with(pa[0u].asString(), + "You should immediately set")); + BEAST_EXPECT( + boost::starts_with(pa[1u].asString(), + "You should clear")); + } + } + else + { + BEAST_EXPECT (pa.size() == 0); + } + + // now make a second request asking for the relevant transactions this + // time. + params[jss::transactions] = true; + result = env.rpc ("json", "noripple_check", + boost::lexical_cast(params)) [jss::result]; + if (! BEAST_EXPECT (result[jss::transactions].isArray ())) + return; + + auto const txs = result[jss::transactions]; + if (problems) + { + if (! BEAST_EXPECT (txs.size () == (user ? 1 : 2))) + return; + + if (! user) + { + BEAST_EXPECT (txs[0u][jss::Account] == alice.human()); + BEAST_EXPECT (txs[0u][jss::TransactionType] == "AccountSet"); + } + + BEAST_EXPECT ( + result[jss::transactions][txs.size()-1][jss::Account] == + alice.human()); + BEAST_EXPECT ( + result[jss::transactions][txs.size()-1][jss::TransactionType] == + "TrustSet"); + BEAST_EXPECT ( + result[jss::transactions][txs.size()-1][jss::LimitAmount] == + gw["USD"](100).value ().getJson (0)); + } + else + { + BEAST_EXPECT (txs.size () == 0); + } + } + +public: + void run () + { + testBadInput (); + for (auto user : {true, false}) + for (auto problem : {true, false}) + testBasic (user, problem); + } +}; + +class NoRippleCheckLimits_test : public beast::unit_test::suite +{ + void + testLimits(bool admin) + { + testcase << "Check limits in returned data, " << + (admin ? "admin" : "non-admin"); + + using namespace test::jtx; + + Env env {*this, admin ? envconfig () : envconfig(no_admin)}; + + auto const alice = Account {"alice"}; + env.fund (XRP (100000), alice); + env (fset (alice, asfDefaultRipple)); + env.close (); + + for (auto i = 0; i < ripple::RPC::Tuning::noRippleCheck.rmax + 5; ++i) + { + if (! admin) + { + // endpoint drop prevention. Non admin ports will drop requests + // if they are coming too fast, so we manipulate the resource + // manager here to reset the enpoint balance (for localhost) if + // we get too close to the drop limit. + using namespace ripple::Resource; + using namespace std::chrono; + using namespace beast::IP; + auto c = env.app().getResourceManager() + .newInboundEndpoint (Endpoint::from_string ("127.0.0.1")); + if (dropThreshold - c.balance() <= 20) + { + using clock_type = beast::abstract_clock ; + c.entry().local_balance = + DecayingSample + {steady_clock::now()}; + } + } + auto const gw = Account {"gw" + std::to_string(i)}; + env.fund (XRP (1000), gw); + env (trust (alice, gw["USD"](10))); + env.close(); + } + + // default limit value + Json::Value params; + params[jss::account] = alice.human(); + params[jss::role] = "user"; + params[jss::ledger] = "current"; + auto result = env.rpc ("json", "noripple_check", + boost::lexical_cast(params)) [jss::result]; + + BEAST_EXPECT (result["problems"].size() == 301); + + // one below minimum + params[jss::limit] = 9; + result = env.rpc ("json", "noripple_check", + boost::lexical_cast(params)) [jss::result]; + BEAST_EXPECT (result["problems"].size() == (admin ? 10 : 11)); + + // at minimum + params[jss::limit] = 10; + result = env.rpc ("json", "noripple_check", + boost::lexical_cast(params)) [jss::result]; + BEAST_EXPECT (result["problems"].size() == 11); + + // at max + params[jss::limit] = 400; + result = env.rpc ("json", "noripple_check", + boost::lexical_cast(params)) [jss::result]; + BEAST_EXPECT (result["problems"].size() == 401); + + // at max+1 + params[jss::limit] = 401; + result = env.rpc ("json", "noripple_check", + boost::lexical_cast(params)) [jss::result]; + BEAST_EXPECT (result["problems"].size() == (admin ? 402 : 401)); + } + +public: + void run () + { + for (auto admin : {true, false}) + testLimits (admin); + } +}; + +BEAST_DEFINE_TESTSUITE(NoRippleCheck, app, ripple); + +// These tests that deal with limit amounts are slow because of the +// offer/account setup, so making them manual -- the additional coverage provided +// by them is minimal + +BEAST_DEFINE_TESTSUITE_MANUAL(NoRippleCheckLimits, app, ripple); + +} // ripple + diff --git a/src/test/unity/rpc_test_unity.cpp b/src/test/unity/rpc_test_unity.cpp index 3c4bf492e..3c89559ad 100644 --- a/src/test/unity/rpc_test_unity.cpp +++ b/src/test/unity/rpc_test_unity.cpp @@ -34,6 +34,7 @@ #include #include #include +#include #include #include #include