account_object supports nft page (#736)

Fix #696
This commit is contained in:
cyan317
2023-07-10 13:42:57 +01:00
committed by GitHub
parent 7b306f3ba0
commit 271323b0f4
16 changed files with 876 additions and 169 deletions

View File

@@ -17,13 +17,15 @@
*/
//==============================================================================
#include <ripple/basics/StringUtilities.h>
#include <backend/BackendInterface.h>
#include <backend/DBHelpers.h>
#include <log/Logger.h>
#include <rpc/Errors.h>
#include <rpc/RPCHelpers.h>
#include <util/Profiler.h>
#include <ripple/basics/StringUtilities.h>
#include <ripple/protocol/nftPageMask.h>
#include <boost/algorithm/string.hpp>
#include <boost/format.hpp>
@@ -405,6 +407,61 @@ getStartHint(ripple::SLE const& sle, ripple::AccountID const& accountID)
return sle.getFieldU64(ripple::sfOwnerNode);
}
// traverse account's nfts
// return Status if error occurs
// return [nextpage, count of nft already found] if success
std::variant<Status, AccountCursor>
traverseNFTObjects(
BackendInterface const& backend,
std::uint32_t sequence,
ripple::AccountID const& accountID,
ripple::uint256 nextPage,
std::uint32_t limit,
boost::asio::yield_context& yield,
std::function<void(ripple::SLE&&)> atOwnedNode)
{
auto const firstNFTPage = ripple::keylet::nftpage_min(accountID);
auto const lastNFTPage = ripple::keylet::nftpage_max(accountID);
// check if nextPage is valid
if (nextPage != beast::zero and firstNFTPage.key != (nextPage & ~ripple::nft::pageMask))
return Status{RippledError::rpcINVALID_PARAMS, "Invalid marker."};
// no marker, start from the last page
ripple::uint256 currentPage = nextPage == beast::zero ? lastNFTPage.key : nextPage;
// read the current page
auto page = backend.fetchLedgerObject(currentPage, sequence, yield);
if (!page)
{
if (nextPage == beast::zero) // no nft objects in lastNFTPage
return AccountCursor{beast::zero, 0};
else // marker is in the right range, but still invalid
return Status{RippledError::rpcINVALID_PARAMS, "Invalid marker."};
}
// the object exists and the key is in right range, must be nft page
ripple::SLE pageSLE{ripple::SLE{ripple::SerialIter{page->data(), page->size()}, currentPage}};
auto count = 0;
// traverse the nft page linked list until the start of the list or reach the limit
while (true)
{
auto const nftPreviousPage = pageSLE.getFieldH256(ripple::sfPreviousPageMin);
atOwnedNode(std::move(pageSLE));
count++;
if (count == limit or nftPreviousPage == beast::zero)
return AccountCursor{nftPreviousPage, count};
page = backend.fetchLedgerObject(nftPreviousPage, sequence, yield);
pageSLE = ripple::SLE{ripple::SerialIter{page->data(), page->size()}, nftPreviousPage};
}
return AccountCursor{beast::zero, 0};
}
std::variant<Status, AccountCursor>
traverseOwnedNodes(
BackendInterface const& backend,
@@ -413,53 +470,50 @@ traverseOwnedNodes(
std::uint32_t limit,
std::optional<std::string> jsonCursor,
boost::asio::yield_context& yield,
std::function<void(ripple::SLE&&)> atOwnedNode)
std::function<void(ripple::SLE&&)> atOwnedNode,
bool nftIncluded)
{
if (!backend.fetchLedgerObject(ripple::keylet::account(accountID).key, sequence, yield))
return Status{RippledError::rpcACT_NOT_FOUND};
auto const maybeCursor = parseAccountCursor(jsonCursor);
if (!maybeCursor)
return Status(ripple::rpcINVALID_PARAMS, "Malformed cursor");
if (!maybeCursor)
return Status{RippledError::rpcINVALID_PARAMS, "Malformed cursor."};
// the format is checked in RPC framework level
auto [hexCursor, startHint] = *maybeCursor;
return traverseOwnedNodes(
backend,
ripple::keylet::ownerDir(accountID),
hexCursor,
startHint,
sequence,
limit,
jsonCursor,
yield,
atOwnedNode);
}
auto const isNftMarkerNonZero = startHint == std::numeric_limits<uint32_t>::max() and hexCursor != beast::zero;
auto const isNftMarkerZero = startHint == std::numeric_limits<uint32_t>::max() and hexCursor == beast::zero;
// if we need to traverse nft objects and this is the first request -> traverse nft objects
// if we need to traverse nft objects and the marker is still in nft page -> traverse nft objects
// if we need to traverse nft objects and the marker is still in nft page but next page is zero -> owned nodes
// if we need to traverse nft objects and the marker is not in nft page -> traverse owned nodes
if (nftIncluded and (!jsonCursor or isNftMarkerNonZero))
{
auto const cursorMaybe = traverseNFTObjects(backend, sequence, accountID, hexCursor, limit, yield, atOwnedNode);
std::variant<Status, AccountCursor>
ngTraverseOwnedNodes(
BackendInterface const& backend,
ripple::AccountID const& accountID,
std::uint32_t sequence,
std::uint32_t limit,
std::optional<std::string> jsonCursor,
boost::asio::yield_context& yield,
std::function<void(ripple::SLE&&)> atOwnedNode)
{
auto const maybeCursor = parseAccountCursor(jsonCursor);
// the format is checked in RPC framework level
auto const [hexCursor, startHint] = *maybeCursor;
if (auto const status = std::get_if<Status>(&cursorMaybe))
return *status;
auto const [nextNFTPage, nftsCount] = std::get<AccountCursor>(cursorMaybe);
// if limit reach , we return the next page and max as marker
if (nftsCount >= limit)
return AccountCursor{nextNFTPage, std::numeric_limits<uint32_t>::max()};
// adjust limit ,continue traversing owned nodes
limit -= nftsCount;
hexCursor = beast::zero;
startHint = 0;
}
else if (nftIncluded and isNftMarkerZero)
{
// the last request happen to fetch all the nft, adjust marker to continue traversing owned nodes
hexCursor = beast::zero;
startHint = 0;
}
return traverseOwnedNodes(
backend,
ripple::keylet::ownerDir(accountID),
hexCursor,
startHint,
sequence,
limit,
jsonCursor,
yield,
atOwnedNode);
backend, ripple::keylet::ownerDir(accountID), hexCursor, startHint, sequence, limit, yield, atOwnedNode);
}
std::variant<Status, AccountCursor>
@@ -470,7 +524,6 @@ traverseOwnedNodes(
std::uint32_t const startHint,
std::uint32_t sequence,
std::uint32_t limit,
std::optional<std::string> jsonCursor,
boost::asio::yield_context& yield,
std::function<void(ripple::SLE&&)> atOwnedNode)
{
@@ -496,7 +549,7 @@ traverseOwnedNodes(
auto hintDir = backend.fetchLedgerObject(hintIndex.key, sequence, yield);
if (!hintDir)
return Status(ripple::rpcINVALID_PARAMS, "Invalid marker");
return Status(ripple::rpcINVALID_PARAMS, "Invalid marker.");
ripple::SerialIter it{hintDir->data(), hintDir->size()};
ripple::SLE sle{it, hintIndex.key};
@@ -505,7 +558,7 @@ traverseOwnedNodes(
std::find(std::begin(indexes), std::end(indexes), hexMarker) == std::end(indexes))
{
// the index specified by marker is not in the page specified by marker
return Status(ripple::rpcINVALID_PARAMS, "Invalid marker");
return Status(ripple::rpcINVALID_PARAMS, "Invalid marker.");
}
currentIndex = hintIndex;
@@ -515,7 +568,7 @@ traverseOwnedNodes(
auto const ownerDir = backend.fetchLedgerObject(currentIndex.key, sequence, yield);
if (!ownerDir)
return Status(ripple::rpcINVALID_PARAMS, "Owner directory not found");
return Status(ripple::rpcINVALID_PARAMS, "Owner directory not found.");
ripple::SerialIter it{ownerDir->data(), ownerDir->size()};
ripple::SLE sle{it, currentIndex.key};

View File

@@ -101,16 +101,6 @@ getLedgerInfoFromHashOrSeq(
std::optional<uint32_t> ledgerIndex,
uint32_t maxSeq);
std::variant<Status, AccountCursor>
traverseOwnedNodes(
BackendInterface const& backend,
ripple::AccountID const& accountID,
std::uint32_t sequence,
std::uint32_t limit,
std::optional<std::string> jsonCursor,
boost::asio::yield_context& yield,
std::function<void(ripple::SLE&&)> atOwnedNode);
std::variant<Status, AccountCursor>
traverseOwnedNodes(
BackendInterface const& backend,
@@ -119,21 +109,21 @@ traverseOwnedNodes(
std::uint32_t const startHint,
std::uint32_t sequence,
std::uint32_t limit,
std::optional<std::string> jsonCursor,
boost::asio::yield_context& yield,
std::function<void(ripple::SLE&&)> atOwnedNode);
// Remove the account check from traverseOwnedNodes
// Account check has been done by framework,remove it from internal function
std::variant<Status, AccountCursor>
ngTraverseOwnedNodes(
traverseOwnedNodes(
BackendInterface const& backend,
ripple::AccountID const& accountID,
std::uint32_t sequence,
std::uint32_t limit,
std::optional<std::string> jsonCursor,
boost::asio::yield_context& yield,
std::function<void(ripple::SLE&&)> atOwnedNode);
std::function<void(ripple::SLE&&)> atOwnedNode,
bool nftIncluded = false);
std::shared_ptr<ripple::SLE const>
read(

View File

@@ -160,7 +160,7 @@ CustomValidator AccountMarkerValidator =
if (!parseAccountCursor(value.as_string().c_str()))
{
// align with the current error message
return Error{Status{RippledError::rpcINVALID_PARAMS, "Malformed cursor"}};
return Error{Status{RippledError::rpcINVALID_PARAMS, "Malformed cursor."}};
}
return MaybeError{};

View File

@@ -87,7 +87,7 @@ AccountChannelsHandler::process(AccountChannelsHandler::Input input, Context con
return true;
};
auto const next = ngTraverseOwnedNodes(
auto const next = traverseOwnedNodes(
*sharedPtrBackend_, *accountID, lgrInfo.seq, input.limit, input.marker, ctx.yield, addToResponse);
if (auto status = std::get_if<Status>(&next))

View File

@@ -63,7 +63,7 @@ AccountCurrenciesHandler::process(AccountCurrenciesHandler::Input input, Context
};
// traverse all owned nodes, limit->max, marker->empty
ngTraverseOwnedNodes(
traverseOwnedNodes(
*sharedPtrBackend_,
*accountID,
lgrInfo.seq,

View File

@@ -129,7 +129,7 @@ AccountLinesHandler::process(AccountLinesHandler::Input input, Context const& ct
}
};
auto const next = ngTraverseOwnedNodes(
auto const next = traverseOwnedNodes(
*sharedPtrBackend_, *accountID, lgrInfo.seq, input.limit, input.marker, ctx.yield, addToResponse);
if (auto status = std::get_if<Status>(&next))

View File

@@ -21,7 +21,6 @@
namespace RPC {
// document does not mention nft_page, we still support it tho
std::unordered_map<std::string, ripple::LedgerEntryType> const AccountObjectsHandler::TYPESMAP{
{"state", ripple::ltRIPPLE_STATE},
{"ticket", ripple::ltTICKET},
@@ -93,8 +92,8 @@ AccountObjectsHandler::process(AccountObjectsHandler::Input input, Context const
return true;
};
auto const next = ngTraverseOwnedNodes(
*sharedPtrBackend_, *accountID, lgrInfo.seq, input.limit, input.marker, ctx.yield, addToResponse);
auto const next = traverseOwnedNodes(
*sharedPtrBackend_, *accountID, lgrInfo.seq, input.limit, input.marker, ctx.yield, addToResponse, true);
if (auto status = std::get_if<Status>(&next))
return Error{*status};

View File

@@ -70,7 +70,7 @@ AccountOffersHandler::process(AccountOffersHandler::Input input, Context const&
return true;
};
auto const next = ngTraverseOwnedNodes(
auto const next = traverseOwnedNodes(
*sharedPtrBackend_, *accountID, lgrInfo.seq, input.limit, input.marker, ctx.yield, addToResponse);
if (auto const status = std::get_if<Status>(&next))

View File

@@ -109,7 +109,7 @@ GatewayBalancesHandler::process(GatewayBalancesHandler::Input input, Context con
};
// traverse all owned nodes, limit->max, marker->empty
auto const ret = ngTraverseOwnedNodes(
auto const ret = traverseOwnedNodes(
*sharedPtrBackend_,
*accountID,
lgrInfo.seq,

View File

@@ -111,15 +111,7 @@ NFTOffersHandlerBase::iterateOfferDirectory(
}
auto result = traverseOwnedNodes(
*sharedPtrBackend_,
directory,
cursor,
startHint,
lgrInfo.seq,
reserve,
{},
yield,
[&offers](ripple::SLE&& offer) {
*sharedPtrBackend_, directory, cursor, startHint, lgrInfo.seq, reserve, yield, [&offers](ripple::SLE&& offer) {
if (offer.getType() == ripple::ltNFTOKEN_OFFER)
{
offers.push_back(std::move(offer));

View File

@@ -85,7 +85,7 @@ NoRippleCheckHandler::process(NoRippleCheckHandler::Input input, Context const&
auto limit = input.limit;
ngTraverseOwnedNodes(
traverseOwnedNodes(
*sharedPtrBackend_,
*accountID,
lgrInfo.seq,

View File

@@ -50,33 +50,8 @@ class RPCHelpersTest : public MockBackendTest, public SyncAsioContextTest
}
};
TEST_F(RPCHelpersTest, TraverseOwnedNodesNotAccount)
{
MockBackend* rawBackendPtr = static_cast<MockBackend*>(mockBackendPtr.get());
// fetch account object return emtpy
ON_CALL(*rawBackendPtr, doFetchLedgerObject).WillByDefault(Return(std::optional<Blob>{}));
EXPECT_CALL(*rawBackendPtr, doFetchLedgerObject).Times(1);
boost::asio::spawn(ctx, [this](boost::asio::yield_context yield) {
auto account = GetAccountIDWithString(ACCOUNT);
auto ret = traverseOwnedNodes(*mockBackendPtr, account, 9, 10, "", yield, [](auto) {
});
auto status = std::get_if<Status>(&ret);
EXPECT_TRUE(status != nullptr);
EXPECT_EQ(*status, RippledError::rpcACT_NOT_FOUND);
});
ctx.run();
}
TEST_F(RPCHelpersTest, TraverseOwnedNodesMarkerInvalidIndexNotHex)
{
MockBackend* rawBackendPtr = static_cast<MockBackend*>(mockBackendPtr.get());
// fetch account object return something
auto fake = Blob{'f', 'a', 'k', 'e'};
ON_CALL(*rawBackendPtr, doFetchLedgerObject).WillByDefault(Return(fake));
EXPECT_CALL(*rawBackendPtr, doFetchLedgerObject).Times(1);
boost::asio::spawn(ctx, [this](boost::asio::yield_context yield) {
auto account = GetAccountIDWithString(ACCOUNT);
auto ret = traverseOwnedNodes(*mockBackendPtr, account, 9, 10, "nothex,10", yield, [](auto) {
@@ -85,19 +60,13 @@ TEST_F(RPCHelpersTest, TraverseOwnedNodesMarkerInvalidIndexNotHex)
auto status = std::get_if<Status>(&ret);
EXPECT_TRUE(status != nullptr);
EXPECT_EQ(*status, ripple::rpcINVALID_PARAMS);
EXPECT_EQ(status->message, "Malformed cursor");
EXPECT_EQ(status->message, "Malformed cursor.");
});
ctx.run();
}
TEST_F(RPCHelpersTest, TraverseOwnedNodesMarkerInvalidPageNotInt)
{
MockBackend* rawBackendPtr = static_cast<MockBackend*>(mockBackendPtr.get());
// fetch account object return something
auto fake = Blob{'f', 'a', 'k', 'e'};
ON_CALL(*rawBackendPtr, doFetchLedgerObject).WillByDefault(Return(fake));
EXPECT_CALL(*rawBackendPtr, doFetchLedgerObject).Times(1);
boost::asio::spawn(ctx, [this](boost::asio::yield_context yield) {
auto account = GetAccountIDWithString(ACCOUNT);
auto ret = traverseOwnedNodes(*mockBackendPtr, account, 9, 10, "nothex,abc", yield, [](auto) {
@@ -106,7 +75,7 @@ TEST_F(RPCHelpersTest, TraverseOwnedNodesMarkerInvalidPageNotInt)
auto status = std::get_if<Status>(&ret);
EXPECT_TRUE(status != nullptr);
EXPECT_EQ(*status, ripple::rpcINVALID_PARAMS);
EXPECT_EQ(status->message, "Malformed cursor");
EXPECT_EQ(status->message, "Malformed cursor.");
});
ctx.run();
}
@@ -117,12 +86,8 @@ TEST_F(RPCHelpersTest, TraverseOwnedNodesNoInputMarker)
MockBackend* rawBackendPtr = static_cast<MockBackend*>(mockBackendPtr.get());
auto account = GetAccountIDWithString(ACCOUNT);
auto accountKk = ripple::keylet::account(account).key;
auto owneDirKk = ripple::keylet::ownerDir(account).key;
// fetch account object return something
auto fake = Blob{'f', 'a', 'k', 'e'};
ON_CALL(*rawBackendPtr, doFetchLedgerObject(accountKk, testing::_, testing::_)).WillByDefault(Return(fake));
EXPECT_CALL(*rawBackendPtr, doFetchLedgerObject).Times(2);
EXPECT_CALL(*rawBackendPtr, doFetchLedgerObject).Times(1);
// return owner index
ripple::STObject ownerDir = CreateOwnerDirLedgerObject({ripple::uint256{INDEX1}, ripple::uint256{INDEX2}}, INDEX1);
@@ -157,12 +122,8 @@ TEST_F(RPCHelpersTest, TraverseOwnedNodesNoInputMarkerReturnSamePageMarker)
MockBackend* rawBackendPtr = static_cast<MockBackend*>(mockBackendPtr.get());
auto account = GetAccountIDWithString(ACCOUNT);
auto accountKk = ripple::keylet::account(account).key;
auto owneDirKk = ripple::keylet::ownerDir(account).key;
// fetch account object return something
auto fake = Blob{'f', 'a', 'k', 'e'};
ON_CALL(*rawBackendPtr, doFetchLedgerObject(accountKk, testing::_, testing::_)).WillByDefault(Return(fake));
EXPECT_CALL(*rawBackendPtr, doFetchLedgerObject).Times(2);
EXPECT_CALL(*rawBackendPtr, doFetchLedgerObject).Times(1);
std::vector<Blob> bbs;
@@ -202,16 +163,12 @@ TEST_F(RPCHelpersTest, TraverseOwnedNodesNoInputMarkerReturnOtherPageMarker)
MockBackend* rawBackendPtr = static_cast<MockBackend*>(mockBackendPtr.get());
auto account = GetAccountIDWithString(ACCOUNT);
auto accountKk = ripple::keylet::account(account).key;
auto ownerDirKk = ripple::keylet::ownerDir(account).key;
constexpr static auto nextPage = 99;
constexpr static auto limit = 15;
auto ownerDir2Kk = ripple::keylet::page(ripple::keylet::ownerDir(account), nextPage).key;
// fetch account object return something
auto fake = Blob{'f', 'a', 'k', 'e'};
ON_CALL(*rawBackendPtr, doFetchLedgerObject(accountKk, testing::_, testing::_)).WillByDefault(Return(fake));
EXPECT_CALL(*rawBackendPtr, doFetchLedgerObject).Times(3);
EXPECT_CALL(*rawBackendPtr, doFetchLedgerObject).Times(2);
std::vector<Blob> bbs;
@@ -262,14 +219,10 @@ TEST_F(RPCHelpersTest, TraverseOwnedNodesWithMarkerReturnSamePageMarker)
MockBackend* rawBackendPtr = static_cast<MockBackend*>(mockBackendPtr.get());
auto account = GetAccountIDWithString(ACCOUNT);
auto accountKk = ripple::keylet::account(account).key;
auto ownerDir2Kk = ripple::keylet::page(ripple::keylet::ownerDir(account), 99).key;
constexpr static auto limit = 8;
constexpr static auto pageNum = 99;
// fetch account object return something
auto fake = Blob{'f', 'a', 'k', 'e'};
ON_CALL(*rawBackendPtr, doFetchLedgerObject(accountKk, testing::_, testing::_)).WillByDefault(Return(fake));
EXPECT_CALL(*rawBackendPtr, doFetchLedgerObject).Times(3);
EXPECT_CALL(*rawBackendPtr, doFetchLedgerObject).Times(2);
std::vector<Blob> bbs;
@@ -317,14 +270,10 @@ TEST_F(RPCHelpersTest, TraverseOwnedNodesWithUnexistingIndexMarker)
MockBackend* rawBackendPtr = static_cast<MockBackend*>(mockBackendPtr.get());
auto account = GetAccountIDWithString(ACCOUNT);
auto accountKk = ripple::keylet::account(account).key;
auto ownerDir2Kk = ripple::keylet::page(ripple::keylet::ownerDir(account), 99).key;
constexpr static auto limit = 8;
constexpr static auto pageNum = 99;
// fetch account object return something
auto fake = Blob{'f', 'a', 'k', 'e'};
ON_CALL(*rawBackendPtr, doFetchLedgerObject(accountKk, testing::_, testing::_)).WillByDefault(Return(fake));
EXPECT_CALL(*rawBackendPtr, doFetchLedgerObject).Times(2);
EXPECT_CALL(*rawBackendPtr, doFetchLedgerObject).Times(1);
int objectsCount = 10;
ripple::STObject channel1 = CreatePaymentChannelLedgerObject(ACCOUNT, ACCOUNT2, 100, 10, 32, TXNID, 28);
@@ -348,7 +297,7 @@ TEST_F(RPCHelpersTest, TraverseOwnedNodesWithUnexistingIndexMarker)
auto status = std::get_if<Status>(&ret);
EXPECT_TRUE(status != nullptr);
EXPECT_EQ(*status, ripple::rpcINVALID_PARAMS);
EXPECT_EQ(status->message, "Invalid marker");
EXPECT_EQ(status->message, "Invalid marker.");
});
ctx.run();
}

View File

@@ -137,7 +137,7 @@ TEST_F(RPCAccountHandlerTest, InvalidMarker)
auto const err = RPC::makeError(output.error());
EXPECT_EQ(err.at("error").as_string(), "invalidParams");
EXPECT_EQ(err.at("error_message").as_string(), "Malformed cursor");
EXPECT_EQ(err.at("error_message").as_string(), "Malformed cursor.");
});
runSpawn([&, this](auto& yield) {
auto const handler = AnyHandler{AccountChannelsHandler{mockBackendPtr}};

View File

@@ -142,7 +142,7 @@ TEST_F(RPCAccountLinesHandlerTest, InvalidMarker)
auto const err = RPC::makeError(output.error());
EXPECT_EQ(err.at("error").as_string(), "invalidParams");
EXPECT_EQ(err.at("error_message").as_string(), "Malformed cursor");
EXPECT_EQ(err.at("error_message").as_string(), "Malformed cursor.");
});
runSpawn([this](auto& yield) {
auto const handler = AnyHandler{AccountLinesHandler{mockBackendPtr}};

View File

@@ -24,6 +24,8 @@
#include <fmt/core.h>
#include <algorithm>
using namespace RPC;
namespace json = boost::json;
using namespace testing;
@@ -34,6 +36,7 @@ constexpr static auto ACCOUNT2 = "rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun";
constexpr static auto LEDGERHASH = "4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652";
constexpr static auto INDEX1 = "1B8590C01B0006EDFA9ED60296DD052DC5E90F99659B25014D08E1BC983515BC";
constexpr static auto TXNID = "E3FE6EA3D48F0C2B639448020EA4F03D4F4F8FFDB243A852A0F59177921B4879";
constexpr static auto TOKENID = "000827103B94ECBB7BF0A0A6ED62B3607801A27B65F4679F4AD1D4850000C0EA";
constexpr static auto MAXSEQ = 30;
constexpr static auto MINSEQ = 10;
@@ -122,7 +125,14 @@ generateTestValuesForParametersTest()
"MarkerInvalid",
R"({"account":"rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun", "marker":"xxxx"})",
"invalidParams",
"Malformed cursor"},
"Malformed cursor."},
AccountObjectsParamTestCaseBundle{
"NFTMarkerInvalid",
fmt::format(
R"({{"account":"rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun", "marker":"wronghex256,{}"}})",
std::numeric_limits<uint32_t>::max()),
"invalidParams",
"Malformed cursor."},
AccountObjectsParamTestCaseBundle{
"DeletionBlockersOnlyInvalidString",
R"({"account": "rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun", "deletion_blockers_only": "wrong"})",
@@ -261,7 +271,7 @@ TEST_F(RPCAccountObjectsHandlerTest, AccountNotExist)
});
}
TEST_F(RPCAccountObjectsHandlerTest, DefaultParameter)
TEST_F(RPCAccountObjectsHandlerTest, DefaultParameterNoNFTFound)
{
static auto constexpr expectedOut = R"({
"ledger_hash":"4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652",
@@ -302,14 +312,20 @@ TEST_F(RPCAccountObjectsHandlerTest, DefaultParameter)
EXPECT_CALL(*rawBackendPtr, fetchLedgerBySequence).Times(1);
ON_CALL(*rawBackendPtr, fetchLedgerBySequence).WillByDefault(Return(ledgerinfo));
auto const accountKk = ripple::keylet::account(GetAccountIDWithString(ACCOUNT)).key;
auto const account = GetAccountIDWithString(ACCOUNT);
auto const accountKk = ripple::keylet::account(account).key;
ON_CALL(*rawBackendPtr, doFetchLedgerObject(accountKk, MAXSEQ, _)).WillByDefault(Return(Blob{'f', 'a', 'k', 'e'}));
auto const ownerDir = CreateOwnerDirLedgerObject({ripple::uint256{INDEX1}}, INDEX1);
auto const ownerDirKk = ripple::keylet::ownerDir(GetAccountIDWithString(ACCOUNT)).key;
auto const ownerDirKk = ripple::keylet::ownerDir(account).key;
ON_CALL(*rawBackendPtr, doFetchLedgerObject(ownerDirKk, 30, _))
.WillByDefault(Return(ownerDir.getSerializer().peekData()));
EXPECT_CALL(*rawBackendPtr, doFetchLedgerObject).Times(2);
// nft null
auto const nftMaxKK = ripple::keylet::nftpage_max(account).key;
ON_CALL(*rawBackendPtr, doFetchLedgerObject(nftMaxKK, 30, _)).WillByDefault(Return(std::nullopt));
EXPECT_CALL(*rawBackendPtr, doFetchLedgerObject).Times(3);
std::vector<Blob> bbs;
auto const line1 =
CreateRippleStateLedgerObject(ACCOUNT, "USD", ISSUER, 100, ACCOUNT, 10, ACCOUNT2, 20, TXNID, 123, 0);
@@ -341,17 +357,23 @@ TEST_F(RPCAccountObjectsHandlerTest, Limit)
EXPECT_CALL(*rawBackendPtr, fetchLedgerBySequence).Times(1);
ON_CALL(*rawBackendPtr, fetchLedgerBySequence).WillByDefault(Return(ledgerinfo));
auto const accountKk = ripple::keylet::account(GetAccountIDWithString(ACCOUNT)).key;
auto const account = GetAccountIDWithString(ACCOUNT);
auto const accountKk = ripple::keylet::account(account).key;
ON_CALL(*rawBackendPtr, doFetchLedgerObject(accountKk, MAXSEQ, _)).WillByDefault(Return(Blob{'f', 'a', 'k', 'e'}));
static auto constexpr limit = 10;
auto count = limit * 2;
// put 20 items in owner dir, but only return 10
auto const ownerDir = CreateOwnerDirLedgerObject(std::vector(count, ripple::uint256{INDEX1}), INDEX1);
auto const ownerDirKk = ripple::keylet::ownerDir(GetAccountIDWithString(ACCOUNT)).key;
auto const ownerDirKk = ripple::keylet::ownerDir(account).key;
ON_CALL(*rawBackendPtr, doFetchLedgerObject(ownerDirKk, 30, _))
.WillByDefault(Return(ownerDir.getSerializer().peekData()));
EXPECT_CALL(*rawBackendPtr, doFetchLedgerObject).Times(2);
// nft null
auto const nftMaxKK = ripple::keylet::nftpage_max(account).key;
ON_CALL(*rawBackendPtr, doFetchLedgerObject(nftMaxKK, 30, _)).WillByDefault(Return(std::nullopt));
EXPECT_CALL(*rawBackendPtr, doFetchLedgerObject).Times(3);
std::vector<Blob> bbs;
while (count-- != 0)
@@ -430,7 +452,7 @@ TEST_F(RPCAccountObjectsHandlerTest, Marker)
});
}
TEST_F(RPCAccountObjectsHandlerTest, MultipleDir)
TEST_F(RPCAccountObjectsHandlerTest, MultipleDirNoNFT)
{
auto const rawBackendPtr = static_cast<MockBackend*>(mockBackendPtr.get());
mockBackendPtr->updateRange(MINSEQ); // min
@@ -439,7 +461,8 @@ TEST_F(RPCAccountObjectsHandlerTest, MultipleDir)
EXPECT_CALL(*rawBackendPtr, fetchLedgerBySequence).Times(1);
ON_CALL(*rawBackendPtr, fetchLedgerBySequence).WillByDefault(Return(ledgerinfo));
auto const accountKk = ripple::keylet::account(GetAccountIDWithString(ACCOUNT)).key;
auto const account = GetAccountIDWithString(ACCOUNT);
auto const accountKk = ripple::keylet::account(account).key;
ON_CALL(*rawBackendPtr, doFetchLedgerObject(accountKk, MAXSEQ, _)).WillByDefault(Return(Blob{'f', 'a', 'k', 'e'}));
static auto constexpr count = 10;
@@ -448,13 +471,18 @@ TEST_F(RPCAccountObjectsHandlerTest, MultipleDir)
auto ownerDir = CreateOwnerDirLedgerObject(std::vector(cc, ripple::uint256{INDEX1}), INDEX1);
// set next page
ownerDir.setFieldU64(ripple::sfIndexNext, nextpage);
auto const ownerDirKk = ripple::keylet::ownerDir(GetAccountIDWithString(ACCOUNT)).key;
auto const ownerDirKk = ripple::keylet::ownerDir(account).key;
auto const page1 = ripple::keylet::page(ownerDirKk, nextpage).key;
ON_CALL(*rawBackendPtr, doFetchLedgerObject(ownerDirKk, 30, _))
.WillByDefault(Return(ownerDir.getSerializer().peekData()));
ON_CALL(*rawBackendPtr, doFetchLedgerObject(page1, 30, _))
.WillByDefault(Return(ownerDir.getSerializer().peekData()));
EXPECT_CALL(*rawBackendPtr, doFetchLedgerObject).Times(3);
// nft null
auto const nftMaxKK = ripple::keylet::nftpage_max(account).key;
ON_CALL(*rawBackendPtr, doFetchLedgerObject(nftMaxKK, 30, _)).WillByDefault(Return(std::nullopt));
EXPECT_CALL(*rawBackendPtr, doFetchLedgerObject).Times(4);
std::vector<Blob> bbs;
// 10 items per page, 2 pages
@@ -494,14 +522,19 @@ TEST_F(RPCAccountObjectsHandlerTest, TypeFilter)
EXPECT_CALL(*rawBackendPtr, fetchLedgerBySequence).Times(1);
ON_CALL(*rawBackendPtr, fetchLedgerBySequence).WillByDefault(Return(ledgerinfo));
auto const accountKk = ripple::keylet::account(GetAccountIDWithString(ACCOUNT)).key;
auto const account = GetAccountIDWithString(ACCOUNT);
auto const accountKk = ripple::keylet::account(account).key;
ON_CALL(*rawBackendPtr, doFetchLedgerObject(accountKk, MAXSEQ, _)).WillByDefault(Return(Blob{'f', 'a', 'k', 'e'}));
auto const ownerDir = CreateOwnerDirLedgerObject({ripple::uint256{INDEX1}, ripple::uint256{INDEX1}}, INDEX1);
auto const ownerDirKk = ripple::keylet::ownerDir(GetAccountIDWithString(ACCOUNT)).key;
auto const ownerDirKk = ripple::keylet::ownerDir(account).key;
ON_CALL(*rawBackendPtr, doFetchLedgerObject(ownerDirKk, 30, _))
.WillByDefault(Return(ownerDir.getSerializer().peekData()));
EXPECT_CALL(*rawBackendPtr, doFetchLedgerObject).Times(2);
EXPECT_CALL(*rawBackendPtr, doFetchLedgerObject).Times(3);
// nft null
auto const nftMaxKK = ripple::keylet::nftpage_max(account).key;
ON_CALL(*rawBackendPtr, doFetchLedgerObject(nftMaxKK, 30, _)).WillByDefault(Return(std::nullopt));
std::vector<Blob> bbs;
// put 1 state and 1 offer
@@ -546,14 +579,20 @@ TEST_F(RPCAccountObjectsHandlerTest, TypeFilterReturnEmpty)
EXPECT_CALL(*rawBackendPtr, fetchLedgerBySequence).Times(1);
ON_CALL(*rawBackendPtr, fetchLedgerBySequence).WillByDefault(Return(ledgerinfo));
auto const accountKk = ripple::keylet::account(GetAccountIDWithString(ACCOUNT)).key;
auto const account = GetAccountIDWithString(ACCOUNT);
auto const accountKk = ripple::keylet::account(account).key;
ON_CALL(*rawBackendPtr, doFetchLedgerObject(accountKk, MAXSEQ, _)).WillByDefault(Return(Blob{'f', 'a', 'k', 'e'}));
auto const ownerDir = CreateOwnerDirLedgerObject({ripple::uint256{INDEX1}, ripple::uint256{INDEX1}}, INDEX1);
auto const ownerDirKk = ripple::keylet::ownerDir(GetAccountIDWithString(ACCOUNT)).key;
auto const ownerDirKk = ripple::keylet::ownerDir(account).key;
ON_CALL(*rawBackendPtr, doFetchLedgerObject(ownerDirKk, 30, _))
.WillByDefault(Return(ownerDir.getSerializer().peekData()));
EXPECT_CALL(*rawBackendPtr, doFetchLedgerObject).Times(2);
EXPECT_CALL(*rawBackendPtr, doFetchLedgerObject).Times(3);
// nft null
auto const nftMaxKK = ripple::keylet::nftpage_max(account).key;
ON_CALL(*rawBackendPtr, doFetchLedgerObject(nftMaxKK, 30, _)).WillByDefault(Return(std::nullopt));
std::vector<Blob> bbs;
auto const line1 =
CreateRippleStateLedgerObject(ACCOUNT, "USD", ISSUER, 100, ACCOUNT, 10, ACCOUNT2, 20, TXNID, 123, 0);
@@ -598,14 +637,21 @@ TEST_F(RPCAccountObjectsHandlerTest, DeletionBlockersOnlyFilter)
EXPECT_CALL(*rawBackendPtr, fetchLedgerBySequence).Times(1);
ON_CALL(*rawBackendPtr, fetchLedgerBySequence).WillByDefault(Return(ledgerinfo));
auto const accountKk = ripple::keylet::account(GetAccountIDWithString(ACCOUNT)).key;
auto const account = GetAccountIDWithString(ACCOUNT);
auto const accountKk = ripple::keylet::account(account).key;
ON_CALL(*rawBackendPtr, doFetchLedgerObject(accountKk, MAXSEQ, _)).WillByDefault(Return(Blob{'f', 'a', 'k', 'e'}));
auto const ownerDir = CreateOwnerDirLedgerObject({ripple::uint256{INDEX1}, ripple::uint256{INDEX1}}, INDEX1);
auto const ownerDirKk = ripple::keylet::ownerDir(GetAccountIDWithString(ACCOUNT)).key;
auto const ownerDirKk = ripple::keylet::ownerDir(account).key;
ON_CALL(*rawBackendPtr, doFetchLedgerObject(ownerDirKk, 30, _))
.WillByDefault(Return(ownerDir.getSerializer().peekData()));
EXPECT_CALL(*rawBackendPtr, doFetchLedgerObject).Times(2);
// nft null
auto const nftMaxKK = ripple::keylet::nftpage_max(account).key;
ON_CALL(*rawBackendPtr, doFetchLedgerObject(nftMaxKK, 30, _)).WillByDefault(Return(std::nullopt));
EXPECT_CALL(*rawBackendPtr, doFetchLedgerObject).Times(3);
auto const line =
CreateRippleStateLedgerObject(ACCOUNT, "USD", ISSUER, 100, ACCOUNT, 10, ACCOUNT2, 20, TXNID, 123, 0);
@@ -654,14 +700,19 @@ TEST_F(RPCAccountObjectsHandlerTest, DeletionBlockersOnlyFilterWithTypeFilter)
EXPECT_CALL(*rawBackendPtr, fetchLedgerBySequence).Times(1);
ON_CALL(*rawBackendPtr, fetchLedgerBySequence).WillByDefault(Return(ledgerinfo));
auto const accountKk = ripple::keylet::account(GetAccountIDWithString(ACCOUNT)).key;
auto const account = GetAccountIDWithString(ACCOUNT);
auto const accountKk = ripple::keylet::account(account).key;
ON_CALL(*rawBackendPtr, doFetchLedgerObject(accountKk, MAXSEQ, _)).WillByDefault(Return(Blob{'f', 'a', 'k', 'e'}));
auto const ownerDir = CreateOwnerDirLedgerObject({ripple::uint256{INDEX1}, ripple::uint256{INDEX1}}, INDEX1);
auto const ownerDirKk = ripple::keylet::ownerDir(GetAccountIDWithString(ACCOUNT)).key;
auto const ownerDirKk = ripple::keylet::ownerDir(account).key;
ON_CALL(*rawBackendPtr, doFetchLedgerObject(ownerDirKk, 30, _))
.WillByDefault(Return(ownerDir.getSerializer().peekData()));
EXPECT_CALL(*rawBackendPtr, doFetchLedgerObject).Times(2);
EXPECT_CALL(*rawBackendPtr, doFetchLedgerObject).Times(3);
// nft null
auto const nftMaxKK = ripple::keylet::nftpage_max(account).key;
ON_CALL(*rawBackendPtr, doFetchLedgerObject(nftMaxKK, 30, _)).WillByDefault(Return(std::nullopt));
auto const line =
CreateRippleStateLedgerObject(ACCOUNT, "USD", ISSUER, 100, ACCOUNT, 10, ACCOUNT2, 20, TXNID, 123, 0);
@@ -701,14 +752,20 @@ TEST_F(RPCAccountObjectsHandlerTest, DeletionBlockersOnlyFilterEmptyResult)
EXPECT_CALL(*rawBackendPtr, fetchLedgerBySequence).Times(1);
ON_CALL(*rawBackendPtr, fetchLedgerBySequence).WillByDefault(Return(ledgerinfo));
auto const accountKk = ripple::keylet::account(GetAccountIDWithString(ACCOUNT)).key;
auto const account = GetAccountIDWithString(ACCOUNT);
auto const accountKk = ripple::keylet::account(account).key;
ON_CALL(*rawBackendPtr, doFetchLedgerObject(accountKk, MAXSEQ, _)).WillByDefault(Return(Blob{'f', 'a', 'k', 'e'}));
auto const ownerDir = CreateOwnerDirLedgerObject({ripple::uint256{INDEX1}, ripple::uint256{INDEX1}}, INDEX1);
auto const ownerDirKk = ripple::keylet::ownerDir(GetAccountIDWithString(ACCOUNT)).key;
auto const ownerDirKk = ripple::keylet::ownerDir(account).key;
ON_CALL(*rawBackendPtr, doFetchLedgerObject(ownerDirKk, 30, _))
.WillByDefault(Return(ownerDir.getSerializer().peekData()));
EXPECT_CALL(*rawBackendPtr, doFetchLedgerObject).Times(2);
// nft null
auto const nftMaxKK = ripple::keylet::nftpage_max(account).key;
ON_CALL(*rawBackendPtr, doFetchLedgerObject(nftMaxKK, 30, _)).WillByDefault(Return(std::nullopt));
EXPECT_CALL(*rawBackendPtr, doFetchLedgerObject).Times(3);
auto const offer1 = CreateOfferLedgerObject(
ACCOUNT,
@@ -762,14 +819,18 @@ TEST_F(RPCAccountObjectsHandlerTest, DeletionBlockersOnlyFilterWithIncompatibleT
EXPECT_CALL(*rawBackendPtr, fetchLedgerBySequence).Times(1);
ON_CALL(*rawBackendPtr, fetchLedgerBySequence).WillByDefault(Return(ledgerinfo));
auto const accountKk = ripple::keylet::account(GetAccountIDWithString(ACCOUNT)).key;
auto const account = GetAccountIDWithString(ACCOUNT);
auto const accountKk = ripple::keylet::account(account).key;
ON_CALL(*rawBackendPtr, doFetchLedgerObject(accountKk, MAXSEQ, _)).WillByDefault(Return(Blob{'f', 'a', 'k', 'e'}));
auto const ownerDir = CreateOwnerDirLedgerObject({ripple::uint256{INDEX1}, ripple::uint256{INDEX1}}, INDEX1);
auto const ownerDirKk = ripple::keylet::ownerDir(GetAccountIDWithString(ACCOUNT)).key;
auto const ownerDirKk = ripple::keylet::ownerDir(account).key;
ON_CALL(*rawBackendPtr, doFetchLedgerObject(ownerDirKk, 30, _))
.WillByDefault(Return(ownerDir.getSerializer().peekData()));
EXPECT_CALL(*rawBackendPtr, doFetchLedgerObject).Times(2);
EXPECT_CALL(*rawBackendPtr, doFetchLedgerObject).Times(3);
// nft null
auto const nftMaxKK = ripple::keylet::nftpage_max(account).key;
ON_CALL(*rawBackendPtr, doFetchLedgerObject(nftMaxKK, 30, _)).WillByDefault(Return(std::nullopt));
auto const offer1 = CreateOfferLedgerObject(
ACCOUNT,
@@ -812,3 +873,666 @@ TEST_F(RPCAccountObjectsHandlerTest, DeletionBlockersOnlyFilterWithIncompatibleT
EXPECT_EQ(output->as_object().at("account_objects").as_array().size(), 0);
});
}
TEST_F(RPCAccountObjectsHandlerTest, NFTMixOtherObjects)
{
static auto constexpr expectedOut = R"({
"ledger_hash":"4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652",
"ledger_index":30,
"validated":true,
"account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
"limit": 200,
"account_objects":[
{
"Flags":0,
"LedgerEntryType":"NFTokenPage",
"NFTokens":[
{
"NFToken":{
"NFTokenID":"000827103B94ECBB7BF0A0A6ED62B3607801A27B65F4679F4AD1D4850000C0EA",
"URI":"7777772E6F6B2E636F6D"
}
}
],
"PreviousPageMin":"4B4E9C06F24296074F7BC48F92A97916C6DC5EA9659B25014D08E1BC983515BC",
"PreviousTxnID":"0000000000000000000000000000000000000000000000000000000000000000",
"PreviousTxnLgrSeq":0,
"index":"4B4E9C06F24296074F7BC48F92A97916C6DC5EA9FFFFFFFFFFFFFFFFFFFFFFFF"
},
{
"Flags":0,
"LedgerEntryType":"NFTokenPage",
"NFTokens":[
{
"NFToken":{
"NFTokenID":"000827103B94ECBB7BF0A0A6ED62B3607801A27B65F4679F4AD1D4850000C0EA",
"URI":"7777772E6F6B2E636F6D"
}
}
],
"PreviousTxnID":"0000000000000000000000000000000000000000000000000000000000000000",
"PreviousTxnLgrSeq":0,
"index":"4B4E9C06F24296074F7BC48F92A97916C6DC5EA9659B25014D08E1BC983515BC"
},
{
"Balance":{
"currency":"USD",
"issuer":"rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW",
"value":"100"
},
"Flags":0,
"HighLimit":{
"currency":"USD",
"issuer":"rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun",
"value":"20"
},
"LedgerEntryType":"RippleState",
"LowLimit":{
"currency":"USD",
"issuer":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
"value":"10"
},
"PreviousTxnID":"E3FE6EA3D48F0C2B639448020EA4F03D4F4F8FFDB243A852A0F59177921B4879",
"PreviousTxnLgrSeq":123,
"index":"1B8590C01B0006EDFA9ED60296DD052DC5E90F99659B25014D08E1BC983515BC"
}
]
})";
auto const rawBackendPtr = static_cast<MockBackend*>(mockBackendPtr.get());
mockBackendPtr->updateRange(MINSEQ); // min
mockBackendPtr->updateRange(MAXSEQ); // max
auto const ledgerinfo = CreateLedgerInfo(LEDGERHASH, MAXSEQ);
EXPECT_CALL(*rawBackendPtr, fetchLedgerBySequence).Times(1);
ON_CALL(*rawBackendPtr, fetchLedgerBySequence).WillByDefault(Return(ledgerinfo));
auto const account = GetAccountIDWithString(ACCOUNT);
auto const accountKk = ripple::keylet::account(account).key;
ON_CALL(*rawBackendPtr, doFetchLedgerObject(accountKk, MAXSEQ, _)).WillByDefault(Return(Blob{'f', 'a', 'k', 'e'}));
auto const ownerDir = CreateOwnerDirLedgerObject({ripple::uint256{INDEX1}}, INDEX1);
auto const ownerDirKk = ripple::keylet::ownerDir(account).key;
ON_CALL(*rawBackendPtr, doFetchLedgerObject(ownerDirKk, 30, _))
.WillByDefault(Return(ownerDir.getSerializer().peekData()));
// nft page 1
auto const nftMaxKK = ripple::keylet::nftpage_max(account).key;
auto const nftPage2KK = ripple::keylet::nftpage(ripple::keylet::nftpage_min(account), ripple::uint256{INDEX1}).key;
auto const nftpage1 =
CreateNFTTokenPage(std::vector{std::make_pair<std::string, std::string>(TOKENID, "www.ok.com")}, nftPage2KK);
ON_CALL(*rawBackendPtr, doFetchLedgerObject(nftMaxKK, 30, _))
.WillByDefault(Return(nftpage1.getSerializer().peekData()));
// nft page 2 , end
auto const nftpage2 =
CreateNFTTokenPage(std::vector{std::make_pair<std::string, std::string>(TOKENID, "www.ok.com")}, std::nullopt);
ON_CALL(*rawBackendPtr, doFetchLedgerObject(nftPage2KK, 30, _))
.WillByDefault(Return(nftpage2.getSerializer().peekData()));
EXPECT_CALL(*rawBackendPtr, doFetchLedgerObject).Times(4);
std::vector<Blob> bbs;
auto const line1 =
CreateRippleStateLedgerObject(ACCOUNT, "USD", ISSUER, 100, ACCOUNT, 10, ACCOUNT2, 20, TXNID, 123, 0);
bbs.push_back(line1.getSerializer().peekData());
ON_CALL(*rawBackendPtr, doFetchLedgerObjects).WillByDefault(Return(bbs));
EXPECT_CALL(*rawBackendPtr, doFetchLedgerObjects).Times(1);
auto const static input = boost::json::parse(fmt::format(
R"({{
"account":"{}"
}})",
ACCOUNT));
auto const handler = AnyHandler{AccountObjectsHandler{mockBackendPtr}};
runSpawn([&](auto& yield) {
auto const output = handler.process(input, Context{std::ref(yield)});
ASSERT_TRUE(output);
EXPECT_EQ(*output, json::parse(expectedOut));
});
}
TEST_F(RPCAccountObjectsHandlerTest, NFTReachLimitReturnMarker)
{
auto const rawBackendPtr = static_cast<MockBackend*>(mockBackendPtr.get());
mockBackendPtr->updateRange(MINSEQ); // min
mockBackendPtr->updateRange(MAXSEQ); // max
auto const ledgerinfo = CreateLedgerInfo(LEDGERHASH, MAXSEQ);
EXPECT_CALL(*rawBackendPtr, fetchLedgerBySequence).Times(1);
ON_CALL(*rawBackendPtr, fetchLedgerBySequence).WillByDefault(Return(ledgerinfo));
auto const account = GetAccountIDWithString(ACCOUNT);
auto const accountKk = ripple::keylet::account(account).key;
ON_CALL(*rawBackendPtr, doFetchLedgerObject(accountKk, MAXSEQ, _)).WillByDefault(Return(Blob{'f', 'a', 'k', 'e'}));
auto current = ripple::keylet::nftpage_max(account).key;
std::string first{INDEX1};
sort(first.begin(), first.end());
for (auto i = 0; i < 10; i++)
{
std::next_permutation(first.begin(), first.end());
auto previous =
ripple::keylet::nftpage(ripple::keylet::nftpage_min(account), ripple::uint256{first.c_str()}).key;
auto const nftpage =
CreateNFTTokenPage(std::vector{std::make_pair<std::string, std::string>(TOKENID, "www.ok.com")}, previous);
ON_CALL(*rawBackendPtr, doFetchLedgerObject(current, 30, _))
.WillByDefault(Return(nftpage.getSerializer().peekData()));
current = previous;
}
EXPECT_CALL(*rawBackendPtr, doFetchLedgerObject).Times(11);
auto const static input = boost::json::parse(fmt::format(
R"({{
"account":"{}",
"limit":{}
}})",
ACCOUNT,
10));
auto const handler = AnyHandler{AccountObjectsHandler{mockBackendPtr}};
runSpawn([&](auto& yield) {
auto const output = handler.process(input, Context{std::ref(yield)});
ASSERT_TRUE(output);
EXPECT_EQ(output.value().as_object().at("account_objects").as_array().size(), 10);
EXPECT_EQ(
output.value().as_object().at("marker").as_string(),
fmt::format("{},{}", ripple::strHex(current), std::numeric_limits<uint32_t>::max()));
});
}
TEST_F(RPCAccountObjectsHandlerTest, NFTReachLimitNoMarker)
{
auto const rawBackendPtr = static_cast<MockBackend*>(mockBackendPtr.get());
mockBackendPtr->updateRange(MINSEQ); // min
mockBackendPtr->updateRange(MAXSEQ); // max
auto const ledgerinfo = CreateLedgerInfo(LEDGERHASH, MAXSEQ);
EXPECT_CALL(*rawBackendPtr, fetchLedgerBySequence).Times(1);
ON_CALL(*rawBackendPtr, fetchLedgerBySequence).WillByDefault(Return(ledgerinfo));
auto const account = GetAccountIDWithString(ACCOUNT);
auto const accountKk = ripple::keylet::account(account).key;
ON_CALL(*rawBackendPtr, doFetchLedgerObject(accountKk, MAXSEQ, _)).WillByDefault(Return(Blob{'f', 'a', 'k', 'e'}));
auto current = ripple::keylet::nftpage_max(account).key;
std::string first{INDEX1};
sort(first.begin(), first.end());
for (auto i = 0; i < 10; i++)
{
std::next_permutation(first.begin(), first.end());
auto previous =
ripple::keylet::nftpage(ripple::keylet::nftpage_min(account), ripple::uint256{first.c_str()}).key;
auto const nftpage =
CreateNFTTokenPage(std::vector{std::make_pair<std::string, std::string>(TOKENID, "www.ok.com")}, previous);
ON_CALL(*rawBackendPtr, doFetchLedgerObject(current, 30, _))
.WillByDefault(Return(nftpage.getSerializer().peekData()));
current = previous;
}
auto const nftpage11 =
CreateNFTTokenPage(std::vector{std::make_pair<std::string, std::string>(TOKENID, "www.ok.com")}, std::nullopt);
ON_CALL(*rawBackendPtr, doFetchLedgerObject(current, 30, _))
.WillByDefault(Return(nftpage11.getSerializer().peekData()));
EXPECT_CALL(*rawBackendPtr, doFetchLedgerObject).Times(12);
auto const static input = boost::json::parse(fmt::format(
R"({{
"account":"{}",
"limit":{}
}})",
ACCOUNT,
11));
auto const handler = AnyHandler{AccountObjectsHandler{mockBackendPtr}};
runSpawn([&](auto& yield) {
auto const output = handler.process(input, Context{std::ref(yield)});
ASSERT_TRUE(output);
EXPECT_EQ(output.value().as_object().at("account_objects").as_array().size(), 11);
//"0000000000000000000000000000000000000000000000000000000000000000,4294967295"
EXPECT_EQ(
output.value().as_object().at("marker").as_string(),
fmt::format("{},{}", ripple::strHex(ripple::uint256(beast::zero)), std::numeric_limits<uint32_t>::max()));
});
}
TEST_F(RPCAccountObjectsHandlerTest, NFTMarker)
{
auto const rawBackendPtr = static_cast<MockBackend*>(mockBackendPtr.get());
mockBackendPtr->updateRange(MINSEQ); // min
mockBackendPtr->updateRange(MAXSEQ); // max
auto const ledgerinfo = CreateLedgerInfo(LEDGERHASH, MAXSEQ);
EXPECT_CALL(*rawBackendPtr, fetchLedgerBySequence).Times(1);
ON_CALL(*rawBackendPtr, fetchLedgerBySequence).WillByDefault(Return(ledgerinfo));
auto const account = GetAccountIDWithString(ACCOUNT);
auto const accountKk = ripple::keylet::account(account).key;
ON_CALL(*rawBackendPtr, doFetchLedgerObject(accountKk, MAXSEQ, _)).WillByDefault(Return(Blob{'f', 'a', 'k', 'e'}));
std::string first{INDEX1};
auto current = ripple::keylet::nftpage(ripple::keylet::nftpage_min(account), ripple::uint256{first.c_str()}).key;
const auto marker = current;
sort(first.begin(), first.end());
for (auto i = 0; i < 10; i++)
{
std::next_permutation(first.begin(), first.end());
auto previous =
ripple::keylet::nftpage(ripple::keylet::nftpage_min(account), ripple::uint256{first.c_str()}).key;
auto const nftpage =
CreateNFTTokenPage(std::vector{std::make_pair<std::string, std::string>(TOKENID, "www.ok.com")}, previous);
ON_CALL(*rawBackendPtr, doFetchLedgerObject(current, 30, _))
.WillByDefault(Return(nftpage.getSerializer().peekData()));
current = previous;
}
auto const nftpage11 =
CreateNFTTokenPage(std::vector{std::make_pair<std::string, std::string>(TOKENID, "www.ok.com")}, std::nullopt);
ON_CALL(*rawBackendPtr, doFetchLedgerObject(current, 30, _))
.WillByDefault(Return(nftpage11.getSerializer().peekData()));
auto const ownerDir =
CreateOwnerDirLedgerObject({ripple::uint256{INDEX1}, ripple::uint256{INDEX1}, ripple::uint256{INDEX1}}, INDEX1);
auto const ownerDirKk = ripple::keylet::ownerDir(account).key;
ON_CALL(*rawBackendPtr, doFetchLedgerObject(ownerDirKk, 30, _))
.WillByDefault(Return(ownerDir.getSerializer().peekData()));
auto const line =
CreateRippleStateLedgerObject(ACCOUNT, "USD", ISSUER, 100, ACCOUNT, 10, ACCOUNT2, 20, TXNID, 123, 0);
auto const channel = CreatePaymentChannelLedgerObject(ACCOUNT, ACCOUNT2, 100, 10, 32, TXNID, 28);
auto const offer = CreateOfferLedgerObject(
ACCOUNT,
10,
20,
ripple::to_string(ripple::to_currency("USD")),
ripple::to_string(ripple::xrpCurrency()),
ACCOUNT2,
toBase58(ripple::xrpAccount()),
INDEX1);
std::vector<Blob> bbs;
bbs.push_back(line.getSerializer().peekData());
bbs.push_back(channel.getSerializer().peekData());
bbs.push_back(offer.getSerializer().peekData());
ON_CALL(*rawBackendPtr, doFetchLedgerObjects).WillByDefault(Return(bbs));
EXPECT_CALL(*rawBackendPtr, doFetchLedgerObjects).Times(1);
EXPECT_CALL(*rawBackendPtr, doFetchLedgerObject).Times(13);
auto const static input = boost::json::parse(fmt::format(
R"({{
"account":"{}",
"marker":"{},{}"
}})",
ACCOUNT,
ripple::strHex(marker),
std::numeric_limits<uint32_t>::max()));
auto const handler = AnyHandler{AccountObjectsHandler{mockBackendPtr}};
runSpawn([&](auto& yield) {
auto const output = handler.process(input, Context{std::ref(yield)});
ASSERT_TRUE(output);
EXPECT_EQ(output.value().as_object().at("account_objects").as_array().size(), 11 + 3);
EXPECT_FALSE(output.value().as_object().contains("marker"));
});
}
// when limit reached, happen to be the end of NFT page list
TEST_F(RPCAccountObjectsHandlerTest, NFTMarkerNoMoreNFT)
{
auto const rawBackendPtr = static_cast<MockBackend*>(mockBackendPtr.get());
mockBackendPtr->updateRange(MINSEQ); // min
mockBackendPtr->updateRange(MAXSEQ); // max
auto const ledgerinfo = CreateLedgerInfo(LEDGERHASH, MAXSEQ);
EXPECT_CALL(*rawBackendPtr, fetchLedgerBySequence).Times(1);
ON_CALL(*rawBackendPtr, fetchLedgerBySequence).WillByDefault(Return(ledgerinfo));
auto const account = GetAccountIDWithString(ACCOUNT);
auto const accountKk = ripple::keylet::account(account).key;
ON_CALL(*rawBackendPtr, doFetchLedgerObject(accountKk, MAXSEQ, _)).WillByDefault(Return(Blob{'f', 'a', 'k', 'e'}));
auto const ownerDir =
CreateOwnerDirLedgerObject({ripple::uint256{INDEX1}, ripple::uint256{INDEX1}, ripple::uint256{INDEX1}}, INDEX1);
auto const ownerDirKk = ripple::keylet::ownerDir(account).key;
ON_CALL(*rawBackendPtr, doFetchLedgerObject(ownerDirKk, 30, _))
.WillByDefault(Return(ownerDir.getSerializer().peekData()));
auto const line =
CreateRippleStateLedgerObject(ACCOUNT, "USD", ISSUER, 100, ACCOUNT, 10, ACCOUNT2, 20, TXNID, 123, 0);
auto const channel = CreatePaymentChannelLedgerObject(ACCOUNT, ACCOUNT2, 100, 10, 32, TXNID, 28);
auto const offer = CreateOfferLedgerObject(
ACCOUNT,
10,
20,
ripple::to_string(ripple::to_currency("USD")),
ripple::to_string(ripple::xrpCurrency()),
ACCOUNT2,
toBase58(ripple::xrpAccount()),
INDEX1);
std::vector<Blob> bbs;
bbs.push_back(line.getSerializer().peekData());
bbs.push_back(channel.getSerializer().peekData());
bbs.push_back(offer.getSerializer().peekData());
ON_CALL(*rawBackendPtr, doFetchLedgerObjects).WillByDefault(Return(bbs));
EXPECT_CALL(*rawBackendPtr, doFetchLedgerObjects).Times(1);
EXPECT_CALL(*rawBackendPtr, doFetchLedgerObject).Times(2);
auto const static input = boost::json::parse(fmt::format(
R"({{
"account":"{}",
"marker":"{},{}"
}})",
ACCOUNT,
ripple::strHex(ripple::uint256{beast::zero}),
std::numeric_limits<uint32_t>::max()));
auto const handler = AnyHandler{AccountObjectsHandler{mockBackendPtr}};
runSpawn([&](auto& yield) {
auto const output = handler.process(input, Context{std::ref(yield)});
ASSERT_TRUE(output);
EXPECT_EQ(output.value().as_object().at("account_objects").as_array().size(), 3);
EXPECT_FALSE(output.value().as_object().contains("marker"));
});
}
TEST_F(RPCAccountObjectsHandlerTest, NFTMarkerNotInRange)
{
auto const rawBackendPtr = static_cast<MockBackend*>(mockBackendPtr.get());
mockBackendPtr->updateRange(MINSEQ); // min
mockBackendPtr->updateRange(MAXSEQ); // max
auto const ledgerinfo = CreateLedgerInfo(LEDGERHASH, MAXSEQ);
EXPECT_CALL(*rawBackendPtr, fetchLedgerBySequence).Times(1);
ON_CALL(*rawBackendPtr, fetchLedgerBySequence).WillByDefault(Return(ledgerinfo));
auto const account = GetAccountIDWithString(ACCOUNT);
auto const accountKk = ripple::keylet::account(account).key;
ON_CALL(*rawBackendPtr, doFetchLedgerObject(accountKk, MAXSEQ, _)).WillByDefault(Return(Blob{'f', 'a', 'k', 'e'}));
EXPECT_CALL(*rawBackendPtr, doFetchLedgerObject).Times(1);
auto const static input = boost::json::parse(fmt::format(
R"({{
"account": "{}",
"marker" : "{},{}"
}})",
ACCOUNT,
INDEX1,
std::numeric_limits<std::uint32_t>::max()));
auto const handler = AnyHandler{AccountObjectsHandler{mockBackendPtr}};
runSpawn([&](auto& yield) {
auto const output = handler.process(input, Context{std::ref(yield)});
ASSERT_FALSE(output);
auto const err = RPC::makeError(output.error());
EXPECT_EQ(err.at("error").as_string(), "invalidParams");
EXPECT_EQ(err.at("error_message").as_string(), "Invalid marker.");
});
}
TEST_F(RPCAccountObjectsHandlerTest, NFTMarkerNotExist)
{
auto const rawBackendPtr = static_cast<MockBackend*>(mockBackendPtr.get());
mockBackendPtr->updateRange(MINSEQ); // min
mockBackendPtr->updateRange(MAXSEQ); // max
auto const ledgerinfo = CreateLedgerInfo(LEDGERHASH, MAXSEQ);
EXPECT_CALL(*rawBackendPtr, fetchLedgerBySequence).Times(1);
ON_CALL(*rawBackendPtr, fetchLedgerBySequence).WillByDefault(Return(ledgerinfo));
auto const account = GetAccountIDWithString(ACCOUNT);
auto const accountKk = ripple::keylet::account(account).key;
ON_CALL(*rawBackendPtr, doFetchLedgerObject(accountKk, MAXSEQ, _)).WillByDefault(Return(Blob{'f', 'a', 'k', 'e'}));
// return null for this marker
auto const accountNftMax = ripple::keylet::nftpage_max(account).key;
ON_CALL(*rawBackendPtr, doFetchLedgerObject(accountNftMax, MAXSEQ, _)).WillByDefault(Return(std::nullopt));
EXPECT_CALL(*rawBackendPtr, doFetchLedgerObject).Times(2);
auto const static input = boost::json::parse(fmt::format(
R"({{
"account": "{}",
"marker" : "{},{}"
}})",
ACCOUNT,
ripple::strHex(accountNftMax),
std::numeric_limits<std::uint32_t>::max()));
auto const handler = AnyHandler{AccountObjectsHandler{mockBackendPtr}};
runSpawn([&](auto& yield) {
auto const output = handler.process(input, Context{std::ref(yield)});
ASSERT_FALSE(output);
auto const err = RPC::makeError(output.error());
EXPECT_EQ(err.at("error").as_string(), "invalidParams");
EXPECT_EQ(err.at("error_message").as_string(), "Invalid marker.");
});
}
TEST_F(RPCAccountObjectsHandlerTest, NFTLimitAdjust)
{
auto const rawBackendPtr = static_cast<MockBackend*>(mockBackendPtr.get());
mockBackendPtr->updateRange(MINSEQ); // min
mockBackendPtr->updateRange(MAXSEQ); // max
auto const ledgerinfo = CreateLedgerInfo(LEDGERHASH, MAXSEQ);
EXPECT_CALL(*rawBackendPtr, fetchLedgerBySequence).Times(1);
ON_CALL(*rawBackendPtr, fetchLedgerBySequence).WillByDefault(Return(ledgerinfo));
auto const account = GetAccountIDWithString(ACCOUNT);
auto const accountKk = ripple::keylet::account(account).key;
ON_CALL(*rawBackendPtr, doFetchLedgerObject(accountKk, MAXSEQ, _)).WillByDefault(Return(Blob{'f', 'a', 'k', 'e'}));
std::string first{INDEX1};
auto current = ripple::keylet::nftpage(ripple::keylet::nftpage_min(account), ripple::uint256{first.c_str()}).key;
const auto marker = current;
sort(first.begin(), first.end());
for (auto i = 0; i < 10; i++)
{
std::next_permutation(first.begin(), first.end());
auto previous =
ripple::keylet::nftpage(ripple::keylet::nftpage_min(account), ripple::uint256{first.c_str()}).key;
auto const nftpage =
CreateNFTTokenPage(std::vector{std::make_pair<std::string, std::string>(TOKENID, "www.ok.com")}, previous);
ON_CALL(*rawBackendPtr, doFetchLedgerObject(current, 30, _))
.WillByDefault(Return(nftpage.getSerializer().peekData()));
current = previous;
}
auto const nftpage11 =
CreateNFTTokenPage(std::vector{std::make_pair<std::string, std::string>(TOKENID, "www.ok.com")}, std::nullopt);
ON_CALL(*rawBackendPtr, doFetchLedgerObject(current, 30, _))
.WillByDefault(Return(nftpage11.getSerializer().peekData()));
auto const ownerDir = CreateOwnerDirLedgerObject({ripple::uint256{INDEX1}, ripple::uint256{INDEX1}}, INDEX1);
auto const ownerDirKk = ripple::keylet::ownerDir(account).key;
ON_CALL(*rawBackendPtr, doFetchLedgerObject(ownerDirKk, 30, _))
.WillByDefault(Return(ownerDir.getSerializer().peekData()));
auto const line =
CreateRippleStateLedgerObject(ACCOUNT, "USD", ISSUER, 100, ACCOUNT, 10, ACCOUNT2, 20, TXNID, 123, 0);
auto const channel = CreatePaymentChannelLedgerObject(ACCOUNT, ACCOUNT2, 100, 10, 32, TXNID, 28);
auto const offer = CreateOfferLedgerObject(
ACCOUNT,
10,
20,
ripple::to_string(ripple::to_currency("USD")),
ripple::to_string(ripple::xrpCurrency()),
ACCOUNT2,
toBase58(ripple::xrpAccount()),
INDEX1);
std::vector<Blob> bbs;
bbs.push_back(line.getSerializer().peekData());
bbs.push_back(channel.getSerializer().peekData());
bbs.push_back(offer.getSerializer().peekData());
ON_CALL(*rawBackendPtr, doFetchLedgerObjects).WillByDefault(Return(bbs));
EXPECT_CALL(*rawBackendPtr, doFetchLedgerObjects).Times(1);
EXPECT_CALL(*rawBackendPtr, doFetchLedgerObject).Times(13);
auto const static input = boost::json::parse(fmt::format(
R"({{
"account":"{}",
"marker":"{},{}",
"limit": 12
}})",
ACCOUNT,
ripple::strHex(marker),
std::numeric_limits<uint32_t>::max()));
auto const handler = AnyHandler{AccountObjectsHandler{mockBackendPtr}};
runSpawn([&](auto& yield) {
auto const output = handler.process(input, Context{std::ref(yield)});
ASSERT_TRUE(output);
EXPECT_EQ(output.value().as_object().at("account_objects").as_array().size(), 12);
// marker not in NFT "1B8590C01B0006EDFA9ED60296DD052DC5E90F99659B25014D08E1BC983515BC,0"
EXPECT_EQ(output.value().as_object().at("marker").as_string(), fmt::format("{},{}", INDEX1, 0));
});
}
TEST_F(RPCAccountObjectsHandlerTest, FilterNFT)
{
static auto constexpr expectedOut = R"({
"ledger_hash":"4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652",
"ledger_index":30,
"validated":true,
"account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
"limit": 200,
"account_objects":[
{
"Flags":0,
"LedgerEntryType":"NFTokenPage",
"NFTokens":[
{
"NFToken":{
"NFTokenID":"000827103B94ECBB7BF0A0A6ED62B3607801A27B65F4679F4AD1D4850000C0EA",
"URI":"7777772E6F6B2E636F6D"
}
}
],
"PreviousPageMin":"4B4E9C06F24296074F7BC48F92A97916C6DC5EA9659B25014D08E1BC983515BC",
"PreviousTxnID":"0000000000000000000000000000000000000000000000000000000000000000",
"PreviousTxnLgrSeq":0,
"index":"4B4E9C06F24296074F7BC48F92A97916C6DC5EA9FFFFFFFFFFFFFFFFFFFFFFFF"
},
{
"Flags":0,
"LedgerEntryType":"NFTokenPage",
"NFTokens":[
{
"NFToken":{
"NFTokenID":"000827103B94ECBB7BF0A0A6ED62B3607801A27B65F4679F4AD1D4850000C0EA",
"URI":"7777772E6F6B2E636F6D"
}
}
],
"PreviousTxnID":"0000000000000000000000000000000000000000000000000000000000000000",
"PreviousTxnLgrSeq":0,
"index":"4B4E9C06F24296074F7BC48F92A97916C6DC5EA9659B25014D08E1BC983515BC"
}
]
})";
auto const rawBackendPtr = static_cast<MockBackend*>(mockBackendPtr.get());
mockBackendPtr->updateRange(MINSEQ); // min
mockBackendPtr->updateRange(MAXSEQ); // max
auto const ledgerinfo = CreateLedgerInfo(LEDGERHASH, MAXSEQ);
EXPECT_CALL(*rawBackendPtr, fetchLedgerBySequence).Times(1);
ON_CALL(*rawBackendPtr, fetchLedgerBySequence).WillByDefault(Return(ledgerinfo));
auto const account = GetAccountIDWithString(ACCOUNT);
auto const accountKk = ripple::keylet::account(account).key;
ON_CALL(*rawBackendPtr, doFetchLedgerObject(accountKk, MAXSEQ, _)).WillByDefault(Return(Blob{'f', 'a', 'k', 'e'}));
auto const ownerDir = CreateOwnerDirLedgerObject({ripple::uint256{INDEX1}}, INDEX1);
auto const ownerDirKk = ripple::keylet::ownerDir(account).key;
ON_CALL(*rawBackendPtr, doFetchLedgerObject(ownerDirKk, 30, _))
.WillByDefault(Return(ownerDir.getSerializer().peekData()));
// nft page 1
auto const nftMaxKK = ripple::keylet::nftpage_max(account).key;
auto const nftPage2KK = ripple::keylet::nftpage(ripple::keylet::nftpage_min(account), ripple::uint256{INDEX1}).key;
auto const nftpage1 =
CreateNFTTokenPage(std::vector{std::make_pair<std::string, std::string>(TOKENID, "www.ok.com")}, nftPage2KK);
ON_CALL(*rawBackendPtr, doFetchLedgerObject(nftMaxKK, 30, _))
.WillByDefault(Return(nftpage1.getSerializer().peekData()));
// nft page 2 , end
auto const nftpage2 =
CreateNFTTokenPage(std::vector{std::make_pair<std::string, std::string>(TOKENID, "www.ok.com")}, std::nullopt);
ON_CALL(*rawBackendPtr, doFetchLedgerObject(nftPage2KK, 30, _))
.WillByDefault(Return(nftpage2.getSerializer().peekData()));
EXPECT_CALL(*rawBackendPtr, doFetchLedgerObject).Times(4);
std::vector<Blob> bbs;
auto const line1 =
CreateRippleStateLedgerObject(ACCOUNT, "USD", ISSUER, 100, ACCOUNT, 10, ACCOUNT2, 20, TXNID, 123, 0);
bbs.push_back(line1.getSerializer().peekData());
ON_CALL(*rawBackendPtr, doFetchLedgerObjects).WillByDefault(Return(bbs));
EXPECT_CALL(*rawBackendPtr, doFetchLedgerObjects).Times(1);
auto const static input = boost::json::parse(fmt::format(
R"({{
"account":"{}",
"type": "nft_page"
}})",
ACCOUNT));
auto const handler = AnyHandler{AccountObjectsHandler{mockBackendPtr}};
runSpawn([&](auto& yield) {
auto const output = handler.process(input, Context{std::ref(yield)});
ASSERT_TRUE(output);
EXPECT_EQ(*output, json::parse(expectedOut));
});
}
TEST_F(RPCAccountObjectsHandlerTest, NFTZeroMarkerNotAffectOtherMarker)
{
auto const rawBackendPtr = static_cast<MockBackend*>(mockBackendPtr.get());
mockBackendPtr->updateRange(MINSEQ); // min
mockBackendPtr->updateRange(MAXSEQ); // max
auto const ledgerinfo = CreateLedgerInfo(LEDGERHASH, MAXSEQ);
EXPECT_CALL(*rawBackendPtr, fetchLedgerBySequence).Times(1);
ON_CALL(*rawBackendPtr, fetchLedgerBySequence).WillByDefault(Return(ledgerinfo));
auto const account = GetAccountIDWithString(ACCOUNT);
auto const accountKk = ripple::keylet::account(account).key;
ON_CALL(*rawBackendPtr, doFetchLedgerObject(accountKk, MAXSEQ, _)).WillByDefault(Return(Blob{'f', 'a', 'k', 'e'}));
static auto constexpr limit = 10;
auto count = limit * 2;
// put 20 items in owner dir, but only return 10
auto const ownerDir = CreateOwnerDirLedgerObject(std::vector(count, ripple::uint256{INDEX1}), INDEX1);
auto const ownerDirKk = ripple::keylet::ownerDir(account).key;
ON_CALL(*rawBackendPtr, doFetchLedgerObject(ownerDirKk, 30, _))
.WillByDefault(Return(ownerDir.getSerializer().peekData()));
EXPECT_CALL(*rawBackendPtr, doFetchLedgerObject).Times(2);
std::vector<Blob> bbs;
while (count-- != 0)
{
auto const line1 =
CreateRippleStateLedgerObject(ACCOUNT, "USD", ISSUER, 100, ACCOUNT, 10, ACCOUNT2, 20, TXNID, 123, 0);
bbs.push_back(line1.getSerializer().peekData());
}
ON_CALL(*rawBackendPtr, doFetchLedgerObjects).WillByDefault(Return(bbs));
EXPECT_CALL(*rawBackendPtr, doFetchLedgerObjects).Times(1);
auto const static input = boost::json::parse(fmt::format(
R"({{
"account":"{}",
"limit":{},
"marker": "{},{}"
}})",
ACCOUNT,
limit,
ripple::strHex(ripple::uint256{beast::zero}),
std::numeric_limits<uint32_t>::max()));
auto const handler = AnyHandler{AccountObjectsHandler{mockBackendPtr}};
runSpawn([&](auto& yield) {
auto const output = handler.process(input, Context{std::ref(yield)});
ASSERT_TRUE(output);
EXPECT_EQ(output->as_object().at("account_objects").as_array().size(), limit);
EXPECT_EQ(output->as_object().at("marker").as_string(), fmt::format("{},{}", INDEX1, 0));
});
}

View File

@@ -129,7 +129,7 @@ generateTestValuesForParametersTest()
"MarkerInvalid",
R"({"account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", "marker": "12;xxx"})",
"invalidParams",
"Malformed cursor",
"Malformed cursor.",
},
{
"StrictFieldUnsupportedValue",
@@ -589,6 +589,6 @@ TEST_F(RPCAccountOffersHandlerTest, MarkerNotExists)
ASSERT_FALSE(output);
auto const err = RPC::makeError(output.error());
EXPECT_EQ(err.at("error").as_string(), "invalidParams");
EXPECT_EQ(err.at("error_message").as_string(), "Invalid marker");
EXPECT_EQ(err.at("error_message").as_string(), "Invalid marker.");
});
}