mirror of
https://github.com/XRPLF/clio.git
synced 2025-11-30 08:35:52 +00:00
@@ -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};
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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{};
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -63,7 +63,7 @@ AccountCurrenciesHandler::process(AccountCurrenciesHandler::Input input, Context
|
||||
};
|
||||
|
||||
// traverse all owned nodes, limit->max, marker->empty
|
||||
ngTraverseOwnedNodes(
|
||||
traverseOwnedNodes(
|
||||
*sharedPtrBackend_,
|
||||
*accountID,
|
||||
lgrInfo.seq,
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -85,7 +85,7 @@ NoRippleCheckHandler::process(NoRippleCheckHandler::Input input, Context const&
|
||||
|
||||
auto limit = input.limit;
|
||||
|
||||
ngTraverseOwnedNodes(
|
||||
traverseOwnedNodes(
|
||||
*sharedPtrBackend_,
|
||||
*accountID,
|
||||
lgrInfo.seq,
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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}};
|
||||
|
||||
@@ -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}};
|
||||
|
||||
@@ -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));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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.");
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user