Add AccountLinesRPC unit tests.

This commit is contained in:
Scott Schurr
2016-02-03 12:02:37 -08:00
committed by Nik Bougalis
parent 41125a0a34
commit acaf91a2f7
7 changed files with 400 additions and 10 deletions

View File

@@ -3275,6 +3275,10 @@
</ClInclude>
<ClInclude Include="..\..\src\ripple\rpc\Status.h">
</ClInclude>
<ClCompile Include="..\..\src\ripple\rpc\tests\AccountLinesRPC.test.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\src\ripple\rpc\tests\JSONRPC.test.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>

View File

@@ -3765,6 +3765,9 @@
<ClInclude Include="..\..\src\ripple\rpc\Status.h">
<Filter>ripple\rpc</Filter>
</ClInclude>
<ClCompile Include="..\..\src\ripple\rpc\tests\AccountLinesRPC.test.cpp">
<Filter>ripple\rpc\tests</Filter>
</ClCompile>
<ClCompile Include="..\..\src\ripple\rpc\tests\JSONRPC.test.cpp">
<Filter>ripple\rpc\tests</Filter>
</ClCompile>

View File

@@ -97,13 +97,9 @@ Json::Value doAccountLines (RPC::Context& context)
std::string strIdent (params[jss::account].asString ());
AccountID accountID;
if (auto jv = RPC::accountFromString (accountID, strIdent))
{
for (auto it = jv.begin (); it != jv.end (); ++it)
result[it.memberName ()] = it.key ();
// Intentional assignment in if. Extra parentheses silence warning.
if ((result = RPC::accountFromString (accountID, strIdent)))
return result;
}
if (! ledger->exists(keylet::account (accountID)))
return rpcError (rpcACT_NOT_FOUND);
@@ -116,10 +112,8 @@ Json::Value doAccountLines (RPC::Context& context)
AccountID raPeerAccount;
if (hasPeer)
{
result[jss::peer] = context.app.accountIDCache().toBase58 (accountID);
result = RPC::accountFromString (raPeerAccount, strPeer);
if (result)
// Intentional assignment in if. Extra parentheses silence warning.
if ((result = RPC::accountFromString (raPeerAccount, strPeer)))
return result;
}

View File

@@ -0,0 +1,360 @@
//------------------------------------------------------------------------------
/*
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 <BeastConfig.h>
#include <ripple/protocol/ErrorCodes.h>
#include <ripple/protocol/JsonFields.h>
#include <ripple/protocol/TxFlags.h>
#include <ripple/test/jtx.h>
#include <beast/unit_test/suite.h>
namespace ripple {
namespace RPC {
class AccountLinesRPC_test : public beast::unit_test::suite
{
public:
void testAccountLines()
{
using namespace test::jtx;
Env env(*this);
{
// account_lines with no account.
auto const lines = env.rpc ("json", "account_lines", "{ }");
expect (lines[jss::result][jss::error_message] ==
RPC::missing_field_error(jss::account)[jss::error_message]);
}
{
// account_lines with a malformed account.
auto const lines = env.rpc ("json", "account_lines",
R"({"account": )"
R"("n9MJkEKHDhy5eTLuHUQeAAjo382frHNbFK4C8hcwN4nwM2SrLdBj"})");
expect (lines[jss::result][jss::error_message] ==
RPC::make_error(rpcBAD_SEED)[jss::error_message]);
}
Account const alice {"alice"};
{
// account_lines on an unfunded account.
auto const lines = env.rpc ("json", "account_lines",
R"({"account": ")" + alice.human() + R"("})");
expect (lines[jss::result][jss::error_message] ==
RPC::make_error(rpcACT_NOT_FOUND)[jss::error_message]);
}
env.fund(XRP(10000), alice);
env.close();
LedgerInfo const ledger3Info = env.closed()->info();
expect (ledger3Info.seq == 3);
{
// alice is funded but has no lines. An empty array is returned.
auto const lines = env.rpc ("json", "account_lines",
R"({"account": ")" + alice.human() + R"("})");
expect (lines[jss::result][jss::lines].isArray());
expect (lines[jss::result][jss::lines].size() == 0);
}
{
// Specify a ledger that doesn't exist.
auto const lines = env.rpc ("json", "account_lines",
R"({"account": ")" + alice.human() + R"(", )"
R"("ledger_index": "nonsense"})");
expect (lines[jss::result][jss::error_message] ==
"ledgerIndexMalformed");
}
{
// Specify a different ledger that doesn't exist.
auto const lines = env.rpc ("json", "account_lines",
R"({"account": ")" + alice.human() + R"(", )"
R"("ledger_index": 50000})");
expect (lines[jss::result][jss::error_message] ==
"ledgerNotFound");
}
// Create trust lines to share with alice.
Account const gw1 {"gw1"};
env.fund(XRP(10000), gw1);
std::vector<IOU> gw1Currencies;
for (char c = 0; c <= ('Z' - 'A'); ++c)
{
// gw1 currencies have names "YAA" -> "YAZ".
gw1Currencies.push_back(
gw1[std::string("YA") + static_cast<char>('A' + c)]);
IOU const& gw1Currency = gw1Currencies.back();
// Establish trust lines.
env(trust(alice, gw1Currency(100 + c)));
env(pay(gw1, alice, gw1Currency(50 + c)));
}
env.close();
LedgerInfo const ledger4Info = env.closed()->info();
expect (ledger4Info.seq == 4);
// Add another set of trust lines in another ledger so we can see
// differences in historic ledgers.
Account const gw2 {"gw2"};
env.fund(XRP(10000), gw2);
// gw2 requires authorization.
env(fset(gw2, asfRequireAuth));
env.close();
std::vector<IOU> gw2Currencies;
for (char c = 0; c <= ('Z' - 'A'); ++c)
{
// gw2 currencies have names "ZAA" -> "ZAZ".
gw2Currencies.push_back(
gw2[std::string("ZA") + static_cast<char>('A' + c)]);
IOU const& gw2Currency = gw2Currencies.back();
// Establish trust lines.
env(trust(alice, gw2Currency(200 + c)));
env(trust(gw2, gw2Currency(0), alice, tfSetfAuth));
env.close();
env(pay(gw2, alice, gw2Currency(100 + c)));
env.close();
// Set flags on gw2 trust lines so we can look for them.
env(trust(alice, gw2Currency(0), gw2, tfSetNoRipple | tfSetFreeze));
}
env.close();
LedgerInfo const ledger58Info = env.closed()->info();
expect (ledger58Info.seq == 58);
// A re-usable test for historic ledgers.
auto testAccountLinesHistory =
[this, &env](Account const& account, LedgerInfo const& info, int count)
{
// Get account_lines by ledger index.
auto const linesSeq = env.rpc ("json", "account_lines",
R"({"account": ")" + account.human() + R"(", )"
R"("ledger_index": )" + std::to_string(info.seq) + "}");
this->expect (linesSeq[jss::result][jss::lines].isArray());
this->expect (linesSeq[jss::result][jss::lines].size() == count);
// Get account_lines by ledger hash.
auto const linesHash = env.rpc ("json", "account_lines",
R"({"account": ")" + account.human() + R"(", )"
R"("ledger_hash": ")" + to_string(info.hash) + R"("})");
this->expect (linesHash[jss::result][jss::lines].isArray());
this->expect (linesHash[jss::result][jss::lines].size() == count);
};
// Alice should have no trust lines in ledger 3.
testAccountLinesHistory (alice, ledger3Info, 0);
// Alice should have 26 trust lines in ledger 4.
testAccountLinesHistory (alice, ledger4Info, 26);
// Alice should have 52 trust lines in ledger 58.
testAccountLinesHistory (alice, ledger58Info, 52);
{
// Surprisingly, it's valid to specify both index and hash, in
// which case the hash wins.
auto const lines = env.rpc ("json", "account_lines",
R"({"account": ")" + alice.human() + R"(", )"
R"("ledger_hash": ")" + to_string(ledger4Info.hash) + R"("})"
R"("ledger_index": )" + std::to_string(ledger58Info.seq) + "}");
this->expect (lines[jss::result][jss::lines].isArray());
this->expect (lines[jss::result][jss::lines].size() == 26);
}
{
// alice should have 52 trust lines in the current ledger.
auto const lines = env.rpc ("json", "account_lines",
R"({"account": ")" + alice.human() + R"("})");
expect (lines[jss::result][jss::lines].isArray());
expect (lines[jss::result][jss::lines].size() == 52);
}
{
// alice should have 26 trust lines with gw1.
auto const lines = env.rpc ("json", "account_lines",
R"({"account": ")" + alice.human() + R"(", )"
R"("peer": ")" + gw1.human() + R"("})");
expect (lines[jss::result][jss::lines].isArray());
expect (lines[jss::result][jss::lines].size() == 26);
}
{
// Use a malformed peer.
auto const lines = env.rpc ("json", "account_lines",
R"({"account": ")" + alice.human() + R"(", )"
R"("peer": )"
R"("n9MJkEKHDhy5eTLuHUQeAAjo382frHNbFK4C8hcwN4nwM2SrLdBj"})");
expect (lines[jss::result][jss::error_message] ==
RPC::make_error(rpcBAD_SEED)[jss::error_message]);
}
{
// A negative limit should fail.
auto const lines = env.rpc ("json", "account_lines",
R"({"account": ")" + alice.human() + R"(", )"
R"("limit": -1})");
expect (lines[jss::result][jss::error_message] ==
RPC::expected_field_message(jss::limit, "unsigned integer"));
}
{
// Limit the response to 1 trust line.
auto const linesA = env.rpc ("json", "account_lines",
R"({"account": ")" + alice.human() + R"(", )"
R"("limit": 1})");
expect (linesA[jss::result][jss::lines].isArray());
expect (linesA[jss::result][jss::lines].size() == 1);
// Pick up from where the marker left off. We should get 51.
auto marker = linesA[jss::result][jss::marker].asString();
auto const linesB = env.rpc ("json", "account_lines",
R"({"account": ")" + alice.human() + R"(", )"
R"("marker": ")" + marker + R"("})");
expect (linesB[jss::result][jss::lines].isArray());
expect (linesB[jss::result][jss::lines].size() == 51);
// Go again from where the marker left off, but set a limit of 3.
auto const linesC = env.rpc ("json", "account_lines",
R"({"account": ")" + alice.human() + R"(", )"
R"("limit": 3, )"
R"("marker": ")" + marker + R"("})");
expect (linesC[jss::result][jss::lines].isArray());
expect (linesC[jss::result][jss::lines].size() == 3);
// Mess with the marker so it becomes bad and check for the error.
marker[5] = marker[5] == '7' ? '8' : '7';
auto const linesD = env.rpc ("json", "account_lines",
R"({"account": ")" + alice.human() + R"(", )"
R"("marker": ")" + marker + R"("})");
expect (linesD[jss::result][jss::error_message] ==
RPC::make_error(rpcINVALID_PARAMS)[jss::error_message]);
}
{
// A non-string marker should also fail.
auto const lines = env.rpc ("json", "account_lines",
R"({"account": ")" + alice.human() + R"(", )"
R"("marker": true})");
expect (lines[jss::result][jss::error_message] ==
RPC::expected_field_message(jss::marker, "string"));
}
{
// Check that the flags we expect from alice to gw2 are present.
auto const lines = env.rpc ("json", "account_lines",
R"({"account": ")" + alice.human() + R"(", )"
R"("limit": 1, )"
R"("peer": ")" + gw2.human() + R"("})");
auto const& line = lines[jss::result][jss::lines][0u];
expect (line[jss::freeze].asBool() == true);
expect (line[jss::no_ripple].asBool() == true);
expect (line[jss::peer_authorized].asBool() == true);
}
{
// Check that the flags we expect from gw2 to alice are present.
auto const linesA = env.rpc ("json", "account_lines",
R"({"account": ")" + gw2.human() + R"(", )"
R"("limit": 1, )"
R"("peer": ")" + alice.human() + R"("})");
auto const& lineA = linesA[jss::result][jss::lines][0u];
expect (lineA[jss::freeze_peer].asBool() == true);
expect (lineA[jss::no_ripple_peer].asBool() == true);
expect (lineA[jss::authorized].asBool() == true);
// Continue from the returned marker to make sure that works.
expect (linesA[jss::result].isMember(jss::marker));
auto const marker = linesA[jss::result][jss::marker].asString();
auto const linesB = env.rpc ("json", "account_lines",
R"({"account": ")" + gw2.human() + R"(", )"
R"("limit": 25, )"
R"("marker": ")" + marker + R"(", )"
R"("peer": ")" + alice.human() + R"("})");
expect (linesB[jss::result][jss::lines].isArray());
expect (linesB[jss::result][jss::lines].size() == 25);
expect (! linesB[jss::result].isMember(jss::marker));
}
}
void testAccountLineDelete()
{
using namespace test::jtx;
Env env(*this);
// The goal here is to observe account_lines marker behavior if the
// entry pointed at by a returned marker is removed from the ledger.
//
// It isn't easy to explicitly delete a trust line, so we do so in a
// round-about fashion. It takes 4 actors:
// o Gateway gw1 issues USD
// o alice offers to buy 100 USD for 100 XRP.
// o becky offers to sell 100 USD for 100 XRP.
// There will now be an inferred trustline between alice and gw1.
// o alice pays her 100 USD to cheri.
// alice should now have no USD and no trustline to gw1.
Account const alice {"alice"};
Account const becky {"becky"};
Account const cheri {"cheri"};
Account const gw1 {"gw1"};
Account const gw2 {"gw2"};
env.fund(XRP(10000), alice, becky, cheri, gw1, gw2);
env.close();
auto const USD = gw1["USD"];
auto const EUR = gw2["EUR"];
env(trust(alice, EUR(200)));
env(trust(becky, USD(200)));
env(trust(cheri, USD(200)));
env.close();
// becky gets 100 USD from gw1.
env(pay(gw1, becky, USD(100)));
env.close();
// alice offers to buy 100 USD for 100 XRP.
env(offer(alice, USD(100), XRP(100)));
env.close();
// becky offers to buy 100 XRP for 100 USD.
env(offer(becky, XRP(100), USD(100)));
env.close();
// Get account_lines for alice. Limit at 1, so we get a marker.
auto const linesBeg = env.rpc ("json", "account_lines",
R"({"account": ")" + alice.human() + R"(", )"
R"("limit": 1})");
expect (linesBeg[jss::result][jss::lines][0u][jss::currency] == "EUR");
expect (linesBeg[jss::result].isMember(jss::marker));
// alice pays 100 USD to cheri.
env(pay(alice, cheri, USD(100)));
env.close();
// Since alice paid all her USD to cheri, alice should no longer
// have a trust line to gw1. So the old marker should now be invalid.
auto const linesEnd = env.rpc ("json", "account_lines",
R"({"account": ")" + alice.human() + R"(", )"
R"("marker": ")" +
linesBeg[jss::result][jss::marker].asString() + R"("})");
expect (linesEnd[jss::result][jss::error_message] ==
RPC::make_error(rpcINVALID_PARAMS)[jss::error_message]);
}
void run ()
{
testAccountLines();
testAccountLineDelete();
}
};
BEAST_DEFINE_TESTSUITE(AccountLinesRPC,app,ripple);
} // RPC
} // ripple

View File

@@ -43,6 +43,27 @@ trust (Account const& account,
return jv;
}
Json::Value
trust (Account const& account,
STAmount const& amount,
Account const& peer,
std::uint32_t flags)
{
if (isXRP(amount))
Throw<std::runtime_error> (
"trust() requires IOU");
Json::Value jv;
jv[jss::Account] = account.human();
{
auto& ja = jv[jss::LimitAmount] = amount.getJson(0);
ja[jss::issuer] = peer.human();
}
jv[jss::TransactionType] = "TrustSet";
jv[jss::Flags] = flags;
return jv;
}
} // jtx
} // test
} // ripple

View File

@@ -34,6 +34,13 @@ trust (Account const& account,
STAmount const& amount,
std::uint32_t flags=0);
/** Change flags on a trust line. */
Json::Value
trust (Account const& account,
STAmount const& amount,
Account const& peer,
std::uint32_t flags);
} // jtx
} // test
} // ripple

View File

@@ -99,6 +99,7 @@
#include <ripple/rpc/impl/TransactionSign.cpp>
#include <ripple/rpc/impl/RPCVersion.cpp>
#include <ripple/rpc/tests/AccountLinesRPC.test.cpp>
#include <ripple/rpc/tests/JSONRPC.test.cpp>
#include <ripple/rpc/tests/LedgerRequestRPC.test.cpp>
#include <ripple/rpc/tests/KeyGeneration.test.cpp>