mirror of
https://github.com/XRPLF/rippled.git
synced 2025-11-19 10:35:50 +00:00
Add Ticket Tests (RIPD-1328):
Migrate ticket-test.js to cpp. Add coverage for Change/Cancel ticket handlers.
This commit is contained in:
committed by
Nik Bougalis
parent
35504f1723
commit
7e39d645b9
@@ -4402,6 +4402,10 @@
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\src\test\app\Ticket_test.cpp">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\src\test\app\Transaction_ordering_test.cpp">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
|
||||
|
||||
@@ -5205,6 +5205,9 @@
|
||||
<ClCompile Include="..\..\src\test\app\Taker_test.cpp">
|
||||
<Filter>test\app</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\src\test\app\Ticket_test.cpp">
|
||||
<Filter>test\app</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\src\test\app\Transaction_ordering_test.cpp">
|
||||
<Filter>test\app</Filter>
|
||||
</ClCompile>
|
||||
|
||||
@@ -46,6 +46,16 @@ create (Account const& account,
|
||||
|
||||
} // detail
|
||||
|
||||
Json::Value
|
||||
cancel(Account const& account, std::string const & ticketId)
|
||||
{
|
||||
Json::Value jv;
|
||||
jv[jss::TransactionType] = "TicketCancel";
|
||||
jv[jss::Account] = account.human();
|
||||
jv["TicketID"] = ticketId;
|
||||
return jv;
|
||||
}
|
||||
|
||||
} // ticket
|
||||
|
||||
} // jtx
|
||||
|
||||
@@ -97,8 +97,8 @@ create (Account const& account,
|
||||
}
|
||||
|
||||
/** Cancel a ticket */
|
||||
//Json::Value
|
||||
//cancel (Account const& account,
|
||||
Json::Value
|
||||
cancel(Account const& account, std::string const & ticketId);
|
||||
|
||||
} // ticket
|
||||
|
||||
|
||||
403
src/test/app/Ticket_test.cpp
Normal file
403
src/test/app/Ticket_test.cpp
Normal file
@@ -0,0 +1,403 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2012-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/test/jtx.h>
|
||||
#include <ripple/protocol/JsonFields.h>
|
||||
#include <ripple/protocol/Feature.h>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
class Ticket_test : public beast::unit_test::suite
|
||||
{
|
||||
static auto constexpr idOne =
|
||||
"00000000000000000000000000000000"
|
||||
"00000000000000000000000000000001";
|
||||
|
||||
/// @brief validate metadata for a create/cancel ticket transaction and
|
||||
/// return the 3 or 4 nodes that make-up the metadata (AffectedNodes)
|
||||
///
|
||||
/// @param env current jtx env (meta will be extracted from it)
|
||||
///
|
||||
/// @param other_target flag to indicate whether a Target different
|
||||
/// from the Account was specified for the ticket (when created)
|
||||
///
|
||||
/// @param expiration flag to indicate a cancellation with expiration which
|
||||
/// causes two of the affected nodes to be swapped (in order).
|
||||
///
|
||||
/// @retval std::array size 4 of json object values representing
|
||||
/// each meta node entry. When the transaction was a cancel with differing
|
||||
/// target and account, there will be 4 complete items, otherwise the last
|
||||
/// entry will be an empty object
|
||||
auto
|
||||
checkTicketMeta(
|
||||
test::jtx::Env& env,
|
||||
bool other_target = false,
|
||||
bool expiration = false)
|
||||
{
|
||||
using namespace std::string_literals;
|
||||
auto const& tx = env.tx ()->getJson (0);
|
||||
bool is_cancel = tx[jss::TransactionType] == "TicketCancel";
|
||||
|
||||
auto const& jvm = env.meta ()->getJson (0);
|
||||
std::array<Json::Value, 4> retval;
|
||||
|
||||
// these are the affected nodes that we expect for
|
||||
// a few different scenarios.
|
||||
// tuple is index, field name, and label (LedgerEntryType)
|
||||
std::vector<
|
||||
std::tuple<std::size_t, std::string, std::string>
|
||||
> expected_nodes;
|
||||
|
||||
if (is_cancel && other_target)
|
||||
{
|
||||
expected_nodes = {
|
||||
std::make_tuple(0, sfModifiedNode.fieldName, "AccountRoot"s),
|
||||
std::make_tuple(
|
||||
expiration ? 2: 1, sfModifiedNode.fieldName, "AccountRoot"s),
|
||||
std::make_tuple(
|
||||
expiration ? 1: 2, sfDeletedNode.fieldName, "Ticket"s),
|
||||
std::make_tuple(3, sfDeletedNode.fieldName, "DirectoryNode"s)
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
expected_nodes = {
|
||||
std::make_tuple(0, sfModifiedNode.fieldName, "AccountRoot"s),
|
||||
std::make_tuple(1,
|
||||
is_cancel ?
|
||||
sfDeletedNode.fieldName : sfCreatedNode.fieldName,
|
||||
"Ticket"s),
|
||||
std::make_tuple(2,
|
||||
is_cancel ?
|
||||
sfDeletedNode.fieldName : sfCreatedNode.fieldName,
|
||||
"DirectoryNode"s)
|
||||
};
|
||||
}
|
||||
|
||||
BEAST_EXPECT(jvm.isMember (sfAffectedNodes.fieldName));
|
||||
BEAST_EXPECT(jvm[sfAffectedNodes.fieldName].isArray());
|
||||
BEAST_EXPECT(
|
||||
jvm[sfAffectedNodes.fieldName].size() == expected_nodes.size());
|
||||
|
||||
// verify the actual metadata against the expected
|
||||
for (auto const& it : expected_nodes)
|
||||
{
|
||||
auto const& idx = std::get<0>(it);
|
||||
auto const& field = std::get<1>(it);
|
||||
auto const& type = std::get<2>(it);
|
||||
BEAST_EXPECT(jvm[sfAffectedNodes.fieldName][idx].isMember(field));
|
||||
retval[idx] = jvm[sfAffectedNodes.fieldName][idx][field];
|
||||
BEAST_EXPECT(retval[idx][sfLedgerEntryType.fieldName] == type);
|
||||
}
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
void testTicketNotEnabled ()
|
||||
{
|
||||
testcase ("Feature Not Enabled");
|
||||
|
||||
using namespace test::jtx;
|
||||
Env env {*this};
|
||||
|
||||
env (ticket::create (env.master), ter(temDISABLED));
|
||||
env (ticket::cancel (env.master, idOne), ter (temDISABLED));
|
||||
}
|
||||
|
||||
void testTicketCancelNonexistent ()
|
||||
{
|
||||
testcase ("Cancel Nonexistent");
|
||||
|
||||
using namespace test::jtx;
|
||||
Env env {*this, features (featureTickets)};
|
||||
env (ticket::cancel (env.master, idOne), ter (tecNO_ENTRY));
|
||||
}
|
||||
|
||||
void testTicketCreatePreflightFail ()
|
||||
{
|
||||
testcase ("Create/Cancel Ticket with Bad Fee, Fail Preflight");
|
||||
|
||||
using namespace test::jtx;
|
||||
Env env {*this, features (featureTickets)};
|
||||
|
||||
env (ticket::create (env.master), fee (XRP (-1)), ter (temBAD_FEE));
|
||||
env (ticket::cancel (env.master, idOne), fee (XRP (-1)), ter (temBAD_FEE));
|
||||
}
|
||||
|
||||
void testTicketCreateNonexistent ()
|
||||
{
|
||||
testcase ("Create Tickets with Nonexistent Accounts");
|
||||
|
||||
using namespace test::jtx;
|
||||
Env env {*this, features (featureTickets)};
|
||||
Account alice {"alice"};
|
||||
env.memoize (alice);
|
||||
|
||||
env (ticket::create (env.master, alice), ter(tecNO_TARGET));
|
||||
|
||||
env (ticket::create (alice, env.master),
|
||||
json (jss::Sequence, 1),
|
||||
ter (terNO_ACCOUNT));
|
||||
}
|
||||
|
||||
void testTicketToSelf ()
|
||||
{
|
||||
testcase ("Create Tickets with Same Account and Target");
|
||||
|
||||
using namespace test::jtx;
|
||||
Env env {*this, features (featureTickets)};
|
||||
|
||||
env (ticket::create (env.master, env.master));
|
||||
auto cr = checkTicketMeta (env);
|
||||
auto const& jticket = cr[1];
|
||||
|
||||
BEAST_EXPECT(jticket[sfLedgerIndex.fieldName] ==
|
||||
"7F58A0AE17775BA3404D55D406DD1C2E91EADD7AF3F03A26877BCE764CCB75E3");
|
||||
BEAST_EXPECT(jticket[sfNewFields.fieldName][jss::Account] ==
|
||||
env.master.human());
|
||||
BEAST_EXPECT(jticket[sfNewFields.fieldName][jss::Sequence] == 1);
|
||||
//verify that there's no `Target` saved in the ticket
|
||||
BEAST_EXPECT(! jticket[sfNewFields.fieldName].
|
||||
isMember(sfTarget.fieldName));
|
||||
}
|
||||
|
||||
void testTicketCancelByCreator ()
|
||||
{
|
||||
testcase ("Create Ticket and Then Cancel by Creator");
|
||||
|
||||
using namespace test::jtx;
|
||||
Env env {*this, features (featureTickets)};
|
||||
|
||||
// create and verify
|
||||
env (ticket::create (env.master));
|
||||
auto cr = checkTicketMeta (env);
|
||||
auto const& jacct = cr[0];
|
||||
auto const& jticket = cr[1];
|
||||
BEAST_EXPECT(
|
||||
jacct[sfPreviousFields.fieldName][sfOwnerCount.fieldName] == 0);
|
||||
BEAST_EXPECT(
|
||||
jacct[sfFinalFields.fieldName][sfOwnerCount.fieldName] == 1);
|
||||
BEAST_EXPECT(jticket[sfNewFields.fieldName][jss::Sequence] ==
|
||||
jacct[sfPreviousFields.fieldName][jss::Sequence]);
|
||||
BEAST_EXPECT(jticket[sfLedgerIndex.fieldName] ==
|
||||
"7F58A0AE17775BA3404D55D406DD1C2E91EADD7AF3F03A26877BCE764CCB75E3");
|
||||
BEAST_EXPECT(jticket[sfNewFields.fieldName][jss::Account] ==
|
||||
env.master.human());
|
||||
|
||||
// cancel
|
||||
env (ticket::cancel(env.master, jticket[sfLedgerIndex.fieldName].asString()));
|
||||
auto crd = checkTicketMeta (env);
|
||||
auto const& jacctd = crd[0];
|
||||
BEAST_EXPECT(jacctd[sfFinalFields.fieldName][jss::Sequence] == 3);
|
||||
BEAST_EXPECT(
|
||||
jacctd[sfFinalFields.fieldName][sfOwnerCount.fieldName] == 0);
|
||||
}
|
||||
|
||||
void testTicketInsufficientReserve ()
|
||||
{
|
||||
testcase ("Create Ticket Insufficient Reserve");
|
||||
|
||||
using namespace test::jtx;
|
||||
Env env {*this, features (featureTickets)};
|
||||
Account alice {"alice"};
|
||||
|
||||
env.fund (env.current ()->fees ().accountReserve (0), alice);
|
||||
env.close ();
|
||||
|
||||
env (ticket::create (alice), ter (tecINSUFFICIENT_RESERVE));
|
||||
}
|
||||
|
||||
void testTicketCancelByTarget ()
|
||||
{
|
||||
testcase ("Create Ticket and Then Cancel by Target");
|
||||
|
||||
using namespace test::jtx;
|
||||
Env env {*this, features (featureTickets)};
|
||||
Account alice {"alice"};
|
||||
|
||||
env.fund (XRP (10000), alice);
|
||||
env.close ();
|
||||
|
||||
// create and verify
|
||||
env (ticket::create (env.master, alice));
|
||||
auto cr = checkTicketMeta (env, true);
|
||||
auto const& jacct = cr[0];
|
||||
auto const& jticket = cr[1];
|
||||
BEAST_EXPECT(
|
||||
jacct[sfFinalFields.fieldName][sfOwnerCount.fieldName] == 1);
|
||||
BEAST_EXPECT(jticket[sfLedgerEntryType.fieldName] == "Ticket");
|
||||
BEAST_EXPECT(jticket[sfLedgerIndex.fieldName] ==
|
||||
"C231BA31A0E13A4D524A75F990CE0D6890B800FF1AE75E51A2D33559547AC1A2");
|
||||
BEAST_EXPECT(jticket[sfNewFields.fieldName][jss::Account] ==
|
||||
env.master.human());
|
||||
BEAST_EXPECT(jticket[sfNewFields.fieldName][sfTarget.fieldName] ==
|
||||
alice.human());
|
||||
BEAST_EXPECT(jticket[sfNewFields.fieldName][jss::Sequence] == 2);
|
||||
|
||||
// cancel using the target account
|
||||
env (ticket::cancel(alice, jticket[sfLedgerIndex.fieldName].asString()));
|
||||
auto crd = checkTicketMeta (env, true);
|
||||
auto const& jacctd = crd[0];
|
||||
auto const& jdir = crd[2];
|
||||
BEAST_EXPECT(
|
||||
jacctd[sfFinalFields.fieldName][sfOwnerCount.fieldName] == 0);
|
||||
BEAST_EXPECT(jdir[sfLedgerIndex.fieldName] ==
|
||||
jticket[sfLedgerIndex.fieldName]);
|
||||
BEAST_EXPECT(jdir[sfFinalFields.fieldName][jss::Account] ==
|
||||
env.master.human());
|
||||
BEAST_EXPECT(jdir[sfFinalFields.fieldName][sfTarget.fieldName] ==
|
||||
alice.human());
|
||||
BEAST_EXPECT(jdir[sfFinalFields.fieldName][jss::Flags] == 0);
|
||||
BEAST_EXPECT(jdir[sfFinalFields.fieldName][sfOwnerNode.fieldName] ==
|
||||
"0000000000000000");
|
||||
BEAST_EXPECT(jdir[sfFinalFields.fieldName][jss::Sequence] == 2);
|
||||
}
|
||||
|
||||
void testTicketWithExpiration ()
|
||||
{
|
||||
testcase ("Create Ticket with Future Expiration");
|
||||
|
||||
using namespace test::jtx;
|
||||
Env env {*this, features (featureTickets)};
|
||||
|
||||
// create and verify
|
||||
uint32_t expire =
|
||||
(env.timeKeeper ().closeTime () + 60s)
|
||||
.time_since_epoch ().count ();
|
||||
env (ticket::create (env.master, expire));
|
||||
auto cr = checkTicketMeta (env);
|
||||
auto const& jacct = cr[0];
|
||||
auto const& jticket = cr[1];
|
||||
BEAST_EXPECT(
|
||||
jacct[sfPreviousFields.fieldName][sfOwnerCount.fieldName] == 0);
|
||||
BEAST_EXPECT(
|
||||
jacct[sfFinalFields.fieldName][sfOwnerCount.fieldName] == 1);
|
||||
BEAST_EXPECT(jticket[sfNewFields.fieldName][jss::Sequence] ==
|
||||
jacct[sfPreviousFields.fieldName][jss::Sequence]);
|
||||
BEAST_EXPECT(
|
||||
jticket[sfNewFields.fieldName][sfExpiration.fieldName] == expire);
|
||||
}
|
||||
|
||||
void testTicketZeroExpiration ()
|
||||
{
|
||||
testcase ("Create Ticket with Zero Expiration");
|
||||
|
||||
using namespace test::jtx;
|
||||
Env env {*this, features (featureTickets)};
|
||||
|
||||
// create and verify
|
||||
env (ticket::create (env.master, 0u), ter (temBAD_EXPIRATION));
|
||||
}
|
||||
|
||||
void testTicketWithPastExpiration ()
|
||||
{
|
||||
testcase ("Create Ticket with Past Expiration");
|
||||
|
||||
using namespace test::jtx;
|
||||
Env env {*this, features (featureTickets)};
|
||||
|
||||
env.timeKeeper ().adjustCloseTime (days {2});
|
||||
env.close ();
|
||||
|
||||
// create and verify
|
||||
uint32_t expire = 60;
|
||||
env (ticket::create (env.master, expire));
|
||||
// in the case of past expiration, we only get
|
||||
// one meta node entry returned
|
||||
auto const& jvm = env.meta ()->getJson (0);
|
||||
BEAST_EXPECT(jvm.isMember(sfAffectedNodes.fieldName));
|
||||
BEAST_EXPECT(jvm[sfAffectedNodes.fieldName].isArray());
|
||||
BEAST_EXPECT(jvm[sfAffectedNodes.fieldName].size() == 1);
|
||||
BEAST_EXPECT(jvm[sfAffectedNodes.fieldName][0u].
|
||||
isMember(sfModifiedNode.fieldName));
|
||||
auto const& jacct =
|
||||
jvm[sfAffectedNodes.fieldName][0u][sfModifiedNode.fieldName];
|
||||
BEAST_EXPECT(
|
||||
jacct[sfLedgerEntryType.fieldName] == "AccountRoot");
|
||||
BEAST_EXPECT(jacct[sfFinalFields.fieldName][jss::Account] ==
|
||||
env.master.human());
|
||||
}
|
||||
|
||||
void testTicketAllowExpiration ()
|
||||
{
|
||||
testcase ("Create Ticket and Allow to Expire");
|
||||
|
||||
using namespace test::jtx;
|
||||
Env env {*this, features (featureTickets)};
|
||||
|
||||
// create and verify
|
||||
uint32_t expire =
|
||||
(env.timeKeeper ().closeTime () + std::chrono::hours {3})
|
||||
.time_since_epoch().count();
|
||||
env (ticket::create (env.master, expire));
|
||||
auto cr = checkTicketMeta (env);
|
||||
auto const& jacct = cr[0];
|
||||
auto const& jticket = cr[1];
|
||||
BEAST_EXPECT(
|
||||
jacct[sfPreviousFields.fieldName][sfOwnerCount.fieldName] == 0);
|
||||
BEAST_EXPECT(
|
||||
jacct[sfFinalFields.fieldName][sfOwnerCount.fieldName] == 1);
|
||||
BEAST_EXPECT(
|
||||
jticket[sfNewFields.fieldName][sfExpiration.fieldName] == expire);
|
||||
BEAST_EXPECT(jticket[sfLedgerIndex.fieldName] ==
|
||||
"7F58A0AE17775BA3404D55D406DD1C2E91EADD7AF3F03A26877BCE764CCB75E3");
|
||||
|
||||
Account alice {"alice"};
|
||||
env.fund (XRP (10000), alice);
|
||||
env.close ();
|
||||
|
||||
// now try to cancel with alice account, which should not work
|
||||
auto jv = ticket::cancel(alice, jticket[sfLedgerIndex.fieldName].asString());
|
||||
env (jv, ter (tecNO_PERMISSION));
|
||||
|
||||
// advance the ledger time to as to trigger expiration
|
||||
env.timeKeeper ().adjustCloseTime (days {3});
|
||||
env.close ();
|
||||
|
||||
// now try again - the cancel succeeds because ticket has expired
|
||||
env (jv);
|
||||
auto crd = checkTicketMeta (env, true, true);
|
||||
auto const& jticketd = crd[1];
|
||||
BEAST_EXPECT(
|
||||
jticketd[sfFinalFields.fieldName][sfExpiration.fieldName] == expire);
|
||||
}
|
||||
|
||||
public:
|
||||
void run ()
|
||||
{
|
||||
testTicketNotEnabled ();
|
||||
testTicketCancelNonexistent ();
|
||||
testTicketCreatePreflightFail ();
|
||||
testTicketCreateNonexistent ();
|
||||
testTicketToSelf ();
|
||||
testTicketCancelByCreator ();
|
||||
testTicketInsufficientReserve ();
|
||||
testTicketCancelByTarget ();
|
||||
testTicketWithExpiration ();
|
||||
testTicketZeroExpiration ();
|
||||
testTicketWithPastExpiration ();
|
||||
testTicketAllowExpiration ();
|
||||
}
|
||||
};
|
||||
|
||||
BEAST_DEFINE_TESTSUITE (Ticket, tx, ripple);
|
||||
|
||||
} // ripple
|
||||
|
||||
@@ -41,3 +41,4 @@
|
||||
#include <test/app/TxQ_test.cpp>
|
||||
#include <test/app/ValidatorList_test.cpp>
|
||||
#include <test/app/SetTrust_test.cpp>
|
||||
#include <test/app/Ticket_test.cpp>
|
||||
|
||||
Reference in New Issue
Block a user