From 64f2576fc829df379cdc31921e99b7e8f55b05f0 Mon Sep 17 00:00:00 2001 From: Will Date: Fri, 8 Jul 2016 11:53:21 -0400 Subject: [PATCH] Add jtx cpp test for account_objects RPC (RIPD-1230) port of js test, account_objects-test.js - bob account setup and rpc invoke - error tests; no account, non-existant account, bad seed, validation - combined unstepped testcase then stepped with limit/marker --- Builds/VisualStudio2015/RippleD.vcxproj | 4 + .../VisualStudio2015/RippleD.vcxproj.filters | 3 + src/ripple/rpc/tests/AccountObjects_test.cpp | 337 ++++++++++++++++++ src/ripple/unity/rpcx.cpp | 1 + 4 files changed, 345 insertions(+) create mode 100644 src/ripple/rpc/tests/AccountObjects_test.cpp diff --git a/Builds/VisualStudio2015/RippleD.vcxproj b/Builds/VisualStudio2015/RippleD.vcxproj index 8b1931dcd..5d4327bea 100644 --- a/Builds/VisualStudio2015/RippleD.vcxproj +++ b/Builds/VisualStudio2015/RippleD.vcxproj @@ -3353,6 +3353,10 @@ True True + + True + True + True True diff --git a/Builds/VisualStudio2015/RippleD.vcxproj.filters b/Builds/VisualStudio2015/RippleD.vcxproj.filters index ed2b6b2f9..47f32ae1f 100644 --- a/Builds/VisualStudio2015/RippleD.vcxproj.filters +++ b/Builds/VisualStudio2015/RippleD.vcxproj.filters @@ -3813,6 +3813,9 @@ ripple\rpc\tests + + ripple\rpc\tests + ripple\rpc\tests diff --git a/src/ripple/rpc/tests/AccountObjects_test.cpp b/src/ripple/rpc/tests/AccountObjects_test.cpp new file mode 100644 index 000000000..2a492e2cb --- /dev/null +++ b/src/ripple/rpc/tests/AccountObjects_test.cpp @@ -0,0 +1,337 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2016 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 + +namespace ripple { +namespace test { + +static char const* bobs_account_objects[] = { +R"json( +{ + "Balance": { + "currency": "USD", + "issuer": "rrrrrrrrrrrrrrrrrrrrBZbvji", + "value": "-1000" + }, + "Flags": 131072, + "HighLimit": { + "currency": "USD", + "issuer": "rPMh7Pi9ct699iZUTWaytJUoHcJ7cgyziK", + "value": "1000" + }, + "HighNode": "0000000000000000", + "LedgerEntryType": "RippleState", + "LowLimit": { + "currency": "USD", + "issuer": "r32rQHyesiTtdWFU7UJVtff4nCR5SHCbJW", + "value": "0" + }, + "LowNode": "0000000000000000", + "index": + "D89BC239086183EB9458C396E643795C1134963E6550E682A190A5F021766D43" +})json" +, +R"json( +{ + "Balance": { + "currency": "USD", + "issuer": "rrrrrrrrrrrrrrrrrrrrBZbvji", + "value": "-1000" + }, + "Flags": 131072, + "HighLimit": { + "currency": "USD", + "issuer": "rPMh7Pi9ct699iZUTWaytJUoHcJ7cgyziK", + "value": "1000" + }, + "HighNode": "0000000000000000", + "LedgerEntryType": "RippleState", + "LowLimit": { + "currency": "USD", + "issuer": "r9cZvwKU3zzuZK9JFovGg1JC5n7QiqNL8L", + "value": "0" + }, + "LowNode": "0000000000000000", + "index": + "D13183BCFFC9AAC9F96AEBB5F66E4A652AD1F5D10273AEB615478302BEBFD4A4" +})json" +, +R"json( +{ + "Account": "rPMh7Pi9ct699iZUTWaytJUoHcJ7cgyziK", + "BookDirectory": + "50AD0A9E54D2B381288D535EB724E4275FFBF41580D28A925D038D7EA4C68000", + "BookNode": "0000000000000000", + "Flags": 65536, + "LedgerEntryType": "Offer", + "OwnerNode": "0000000000000000", + "Sequence": 4, + "TakerGets": { + "currency": "USD", + "issuer": "rPMh7Pi9ct699iZUTWaytJUoHcJ7cgyziK", + "value": "1" + }, + "TakerPays": "100000000", + "index": + "A984D036A0E562433A8377CA57D1A1E056E58C0D04818F8DFD3A1AA3F217DD82" +})json" +, +R"json( +{ + "Account": "rPMh7Pi9ct699iZUTWaytJUoHcJ7cgyziK", + "BookDirectory": + "B025997A323F5C3E03DDF1334471F5984ABDE31C59D463525D038D7EA4C68000", + "BookNode": "0000000000000000", + "Flags": 65536, + "LedgerEntryType": "Offer", + "OwnerNode": "0000000000000000", + "Sequence": 5, + "TakerGets": { + "currency": "USD", + "issuer" : "r32rQHyesiTtdWFU7UJVtff4nCR5SHCbJW", + "value" : "1" + }, + "TakerPays" : "100000000", + "index" : + "CAFE32332D752387B01083B60CC63069BA4A969C9730836929F841450F6A718E" +} +)json" +}; + +class AccountObjects_test : public beast::unit_test::suite +{ +public: + void testErrors() + { + testcase("error cases"); + + using namespace jtx; + Env env(*this); + + // test error on no account + { + auto resp = env.rpc("account_objects"); + BEAST_EXPECT( resp[jss::result][jss::error_message] == + "Missing field 'account'."); + } + // test error on malformed account string. + { + Json::Value params; + params[jss::account] = "n94JNrQYkDrpt62bbSR7nVEhdyAvcJXRAsjEkFYyqRkh9SUTYEqV"; + auto resp = env.rpc("json", "account_objects", to_string(params)); + BEAST_EXPECT( resp[jss::result][jss::error_message] == "Disallowed seed."); + } + // test error on account that's not in the ledger. + { + Json::Value params; + params[jss::account] = Account{ "bogie" }.human(); + auto resp = env.rpc("json", "account_objects", to_string(params)); + BEAST_EXPECT( resp[jss::result][jss::error_message] == "Account not found."); + } + Account const bob{ "bob" }; + // test error on large ledger_index. + { + Json::Value params; + params[jss::account] = bob.human(); + params[jss::ledger_index] = 10; + auto resp = env.rpc("json", "account_objects", to_string(params)); + BEAST_EXPECT( resp[jss::result][jss::error_message] == "ledgerNotFound"); + } + + env.fund(XRP(1000), bob); + // test error on type param not a string + { + Json::Value params; + params[jss::account] = bob.human(); + params[jss::type] = 10; + auto resp = env.rpc("json", "account_objects", to_string(params)); + BEAST_EXPECT( resp[jss::result][jss::error_message] == + "Invalid field 'type', not string."); + } + // test error on type param not a valid type + { + Json::Value params; + params[jss::account] = bob.human(); + params[jss::type] = "expedited"; + auto resp = env.rpc("json", "account_objects", to_string(params)); + BEAST_EXPECT( resp[jss::result][jss::error_message] == "Invalid field 'type'."); + } + // test error on limit -ve + { + Json::Value params; + params[jss::account] = bob.human(); + params[jss::limit] = -1; + auto resp = env.rpc("json", "account_objects", to_string(params)); + BEAST_EXPECT( resp[jss::result][jss::error_message] == + "Invalid field 'limit', not unsigned integer."); + } + // test errors on marker + { + Account const gw{ "G" }; + env.fund(XRP(1000), gw); + auto const USD = gw["USD"]; + env.trust(USD(1000), bob); + env(pay(gw, bob, XRP(1))); + env(offer(bob, XRP(100), bob["USD"](1)), txflags(tfPassive)); + + Json::Value params; + params[jss::account] = bob.human(); + params[jss::limit] = 1; + auto resp = env.rpc("json", "account_objects", to_string(params)); + + auto resume_marker = resp[jss::result][jss::marker]; + std::string mark = to_string(resume_marker); + params[jss::marker] = 10; + resp = env.rpc("json", "account_objects", to_string(params)); + BEAST_EXPECT( resp[jss::result][jss::error_message] == "Invalid field 'marker', not string."); + + params[jss::marker] = "This is a string with no comma"; + resp = env.rpc("json", "account_objects", to_string(params)); + BEAST_EXPECT( resp[jss::result][jss::error_message] == "Invalid field 'marker'."); + + params[jss::marker] = "This string has a comma, but is not hex"; + resp = env.rpc("json", "account_objects", to_string(params)); + BEAST_EXPECT( resp[jss::result][jss::error_message] == "Invalid field 'marker'."); + + params[jss::marker] = std::string(&mark[1U], 64); + resp = env.rpc("json", "account_objects", to_string(params)); + BEAST_EXPECT( resp[jss::result][jss::error_message] == "Invalid field 'marker'."); + + params[jss::marker] = std::string(&mark[1U], 65); + resp = env.rpc("json", "account_objects", to_string(params)); + BEAST_EXPECT( resp[jss::result][jss::error_message] == "Invalid field 'marker'."); + + params[jss::marker] = std::string(&mark[1U], 65) + "not hex"; + resp = env.rpc("json", "account_objects", to_string(params)); + BEAST_EXPECT( resp[jss::result][jss::error_message] == "Invalid field 'marker'."); + + // Should this be an error? + // A hex digit is absent from the end of marker. + // No account objects returned. + params[jss::marker] = std::string(&mark[1U], 128); + resp = env.rpc("json", "account_objects", to_string(params)); + BEAST_EXPECT( resp[jss::result][jss::account_objects].size() == 0); + } + } + + void testUnsteppedThenStepped() + { + testcase("unsteppedThenStepped"); + + using namespace jtx; + Env env(*this); + + Account const gw1{ "G1" }; + Account const gw2{ "G2" }; + Account const bob{ "bob" }; + + auto const USD1 = gw1["USD"]; + auto const USD2 = gw2["USD"]; + + env.fund(XRP(1000), gw1, gw2, bob); + env.trust(USD1(1000), bob); + env.trust(USD2(1000), bob); + + env(pay(gw1, bob, USD1(1000))); + env(pay(gw2, bob, USD2(1000))); + + env(offer(bob, XRP(100), bob["USD"](1)),txflags(tfPassive)); + env(offer(bob, XRP(100), USD1(1)), txflags(tfPassive)); + + Json::Value bobj[4]; + for (int i = 0; i < 4; ++i) + Json::Reader{}.parse(bobs_account_objects[i], bobj[i]); + + // test 'unstepped' + // i.e. request account objects without explicit limit/marker paging + { + Json::Value params; + params[jss::account] = bob.human(); + auto resp = env.rpc("json", "account_objects", to_string(params)); + BEAST_EXPECT( !resp.isMember(jss::marker)); + + BEAST_EXPECT( resp[jss::result][jss::account_objects].size() == 4); + for (int i = 0; i < 4; ++i) + { + auto& aobj = resp[jss::result][jss::account_objects][i]; + aobj.removeMember("PreviousTxnID"); + aobj.removeMember("PreviousTxnLgrSeq"); + + BEAST_EXPECT( aobj == bobj[i]); + } + } + // test request with type parameter as filter, unstepped + { + Json::Value params; + params[jss::account] = bob.human(); + params[jss::type] = "state"; + auto resp = env.rpc("json", "account_objects", to_string(params)); + BEAST_EXPECT( !resp.isMember(jss::marker)); + + BEAST_EXPECT( resp[jss::result][jss::account_objects].size() == 2); + for (int i = 0; i < 2; ++i) + { + auto& aobj = resp[jss::result][jss::account_objects][i]; + aobj.removeMember("PreviousTxnID"); + aobj.removeMember("PreviousTxnLgrSeq"); + + BEAST_EXPECT( aobj == bobj[i]); + } + } + // test stepped one-at-a-time with limit=1, resume from prev marker + { + Json::Value params; + params[jss::account] = bob.human(); + params[jss::limit] = 1; + for (int i = 0; i < 4; ++i) + { + auto resp = env.rpc("json", "account_objects", to_string(params)); + auto& aobjs = resp[jss::result][jss::account_objects]; + BEAST_EXPECT( aobjs.size() == 1); + auto& aobj = aobjs[0U]; + if (i < 3) BEAST_EXPECT( resp[jss::result][jss::limit] == 1); + + aobj.removeMember("PreviousTxnID"); + aobj.removeMember("PreviousTxnLgrSeq"); + BEAST_EXPECT( aobj == bobj[i]); + + auto resume_marker = resp[jss::result][jss::marker]; + params[jss::marker] = resume_marker; + } + } + } + + void run() override + { + testErrors(); + testUnsteppedThenStepped(); + } +}; + +BEAST_DEFINE_TESTSUITE(AccountObjects,app,ripple); + +} +} diff --git a/src/ripple/unity/rpcx.cpp b/src/ripple/unity/rpcx.cpp index 4972b9089..401848f4a 100644 --- a/src/ripple/unity/rpcx.cpp +++ b/src/ripple/unity/rpcx.cpp @@ -95,6 +95,7 @@ #include #include +#include #include #include #include