mirror of
https://github.com/XRPLF/clio.git
synced 2025-11-21 20:25:52 +00:00
@@ -106,6 +106,9 @@ if(BUILD_TESTS)
|
|||||||
unittests/Config.cpp
|
unittests/Config.cpp
|
||||||
unittests/ProfilerTest.cpp
|
unittests/ProfilerTest.cpp
|
||||||
unittests/DOSGuard.cpp
|
unittests/DOSGuard.cpp
|
||||||
|
unittests/SubscriptionTest.cpp
|
||||||
|
unittests/SubscriptionManagerTest.cpp
|
||||||
|
unittests/util/TestObject.cpp
|
||||||
unittests/rpc/ErrorTests.cpp
|
unittests/rpc/ErrorTests.cpp
|
||||||
unittests/rpc/BaseTests.cpp
|
unittests/rpc/BaseTests.cpp
|
||||||
unittests/rpc/handlers/TestHandlerTests.cpp
|
unittests/rpc/handlers/TestHandlerTests.cpp
|
||||||
|
|||||||
@@ -21,57 +21,6 @@
|
|||||||
#include <subscriptions/SubscriptionManager.h>
|
#include <subscriptions/SubscriptionManager.h>
|
||||||
#include <webserver/WsBase.h>
|
#include <webserver/WsBase.h>
|
||||||
|
|
||||||
template <class T>
|
|
||||||
inline void
|
|
||||||
sendToSubscribers(
|
|
||||||
std::shared_ptr<Message> const& message,
|
|
||||||
T& subscribers,
|
|
||||||
std::atomic_uint64_t& counter)
|
|
||||||
{
|
|
||||||
for (auto it = subscribers.begin(); it != subscribers.end();)
|
|
||||||
{
|
|
||||||
auto& session = *it;
|
|
||||||
if (session->dead())
|
|
||||||
{
|
|
||||||
it = subscribers.erase(it);
|
|
||||||
--counter;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
session->send(message);
|
|
||||||
++it;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
template <class T>
|
|
||||||
inline void
|
|
||||||
addSession(
|
|
||||||
std::shared_ptr<WsBase> session,
|
|
||||||
T& subscribers,
|
|
||||||
std::atomic_uint64_t& counter)
|
|
||||||
{
|
|
||||||
if (!subscribers.contains(session))
|
|
||||||
{
|
|
||||||
subscribers.insert(session);
|
|
||||||
++counter;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
template <class T>
|
|
||||||
inline void
|
|
||||||
removeSession(
|
|
||||||
std::shared_ptr<WsBase> session,
|
|
||||||
T& subscribers,
|
|
||||||
std::atomic_uint64_t& counter)
|
|
||||||
{
|
|
||||||
if (subscribers.contains(session))
|
|
||||||
{
|
|
||||||
subscribers.erase(session);
|
|
||||||
--counter;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
void
|
||||||
Subscription::subscribe(std::shared_ptr<WsBase> const& session)
|
Subscription::subscribe(std::shared_ptr<WsBase> const& session)
|
||||||
{
|
{
|
||||||
@@ -96,55 +45,6 @@ Subscription::publish(std::shared_ptr<Message> const& message)
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
template <class Key>
|
|
||||||
void
|
|
||||||
SubscriptionMap<Key>::subscribe(
|
|
||||||
std::shared_ptr<WsBase> const& session,
|
|
||||||
Key const& account)
|
|
||||||
{
|
|
||||||
boost::asio::post(strand_, [this, session, account]() {
|
|
||||||
addSession(session, subscribers_[account], subCount_);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
template <class Key>
|
|
||||||
void
|
|
||||||
SubscriptionMap<Key>::unsubscribe(
|
|
||||||
std::shared_ptr<WsBase> const& session,
|
|
||||||
Key const& account)
|
|
||||||
{
|
|
||||||
boost::asio::post(strand_, [this, account, session]() {
|
|
||||||
if (!subscribers_.contains(account))
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (!subscribers_[account].contains(session))
|
|
||||||
return;
|
|
||||||
|
|
||||||
--subCount_;
|
|
||||||
|
|
||||||
subscribers_[account].erase(session);
|
|
||||||
|
|
||||||
if (subscribers_[account].size() == 0)
|
|
||||||
{
|
|
||||||
subscribers_.erase(account);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
template <class Key>
|
|
||||||
void
|
|
||||||
SubscriptionMap<Key>::publish(
|
|
||||||
std::shared_ptr<Message>& message,
|
|
||||||
Key const& account)
|
|
||||||
{
|
|
||||||
boost::asio::post(strand_, [this, account, message]() {
|
|
||||||
if (!subscribers_.contains(account))
|
|
||||||
return;
|
|
||||||
|
|
||||||
sendToSubscribers(message, subscribers_[account], subCount_);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
boost::json::object
|
boost::json::object
|
||||||
getLedgerPubMessage(
|
getLedgerPubMessage(
|
||||||
ripple::LedgerInfo const& lgrInfo,
|
ripple::LedgerInfo const& lgrInfo,
|
||||||
@@ -345,8 +245,6 @@ SubscriptionManager::pubTransaction(
|
|||||||
|
|
||||||
for (auto const& node : meta->getNodes())
|
for (auto const& node : meta->getNodes())
|
||||||
{
|
{
|
||||||
if (!node.isFieldPresent(ripple::sfLedgerEntryType))
|
|
||||||
assert(false);
|
|
||||||
if (node.getFieldU16(ripple::sfLedgerEntryType) == ripple::ltOFFER)
|
if (node.getFieldU16(ripple::sfLedgerEntryType) == ripple::ltOFFER)
|
||||||
{
|
{
|
||||||
ripple::SField const* field = nullptr;
|
ripple::SField const* field = nullptr;
|
||||||
@@ -388,9 +286,6 @@ SubscriptionManager::pubBookChanges(
|
|||||||
ripple::LedgerInfo const& lgrInfo,
|
ripple::LedgerInfo const& lgrInfo,
|
||||||
std::vector<Backend::TransactionAndMetadata> const& transactions)
|
std::vector<Backend::TransactionAndMetadata> const& transactions)
|
||||||
{
|
{
|
||||||
if (bookChangesSubscribers_.empty())
|
|
||||||
return;
|
|
||||||
|
|
||||||
auto const json = RPC::computeBookChanges(lgrInfo, transactions);
|
auto const json = RPC::computeBookChanges(lgrInfo, transactions);
|
||||||
auto const bookChangesMsg =
|
auto const bookChangesMsg =
|
||||||
std::make_shared<Message>(boost::json::serialize(json));
|
std::make_shared<Message>(boost::json::serialize(json));
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ public:
|
|||||||
unsubscribe(std::shared_ptr<WsBase> const& session, Key const& key);
|
unsubscribe(std::shared_ptr<WsBase> const& session, Key const& key);
|
||||||
|
|
||||||
void
|
void
|
||||||
publish(std::shared_ptr<Message>& message, Key const& key);
|
publish(std::shared_ptr<Message> const& message, Key const& key);
|
||||||
|
|
||||||
std::uint64_t
|
std::uint64_t
|
||||||
count()
|
count()
|
||||||
@@ -104,6 +104,106 @@ public:
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
inline void
|
||||||
|
sendToSubscribers(
|
||||||
|
std::shared_ptr<Message> const& message,
|
||||||
|
T& subscribers,
|
||||||
|
std::atomic_uint64_t& counter)
|
||||||
|
{
|
||||||
|
for (auto it = subscribers.begin(); it != subscribers.end();)
|
||||||
|
{
|
||||||
|
auto& session = *it;
|
||||||
|
if (session->dead())
|
||||||
|
{
|
||||||
|
it = subscribers.erase(it);
|
||||||
|
--counter;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
session->send(message);
|
||||||
|
++it;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
inline void
|
||||||
|
addSession(
|
||||||
|
std::shared_ptr<WsBase> session,
|
||||||
|
T& subscribers,
|
||||||
|
std::atomic_uint64_t& counter)
|
||||||
|
{
|
||||||
|
if (!subscribers.contains(session))
|
||||||
|
{
|
||||||
|
subscribers.insert(session);
|
||||||
|
++counter;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
inline void
|
||||||
|
removeSession(
|
||||||
|
std::shared_ptr<WsBase> session,
|
||||||
|
T& subscribers,
|
||||||
|
std::atomic_uint64_t& counter)
|
||||||
|
{
|
||||||
|
if (subscribers.contains(session))
|
||||||
|
{
|
||||||
|
subscribers.erase(session);
|
||||||
|
--counter;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class Key>
|
||||||
|
void
|
||||||
|
SubscriptionMap<Key>::subscribe(
|
||||||
|
std::shared_ptr<WsBase> const& session,
|
||||||
|
Key const& account)
|
||||||
|
{
|
||||||
|
boost::asio::post(strand_, [this, session, account]() {
|
||||||
|
addSession(session, subscribers_[account], subCount_);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class Key>
|
||||||
|
void
|
||||||
|
SubscriptionMap<Key>::unsubscribe(
|
||||||
|
std::shared_ptr<WsBase> const& session,
|
||||||
|
Key const& account)
|
||||||
|
{
|
||||||
|
boost::asio::post(strand_, [this, account, session]() {
|
||||||
|
if (!subscribers_.contains(account))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!subscribers_[account].contains(session))
|
||||||
|
return;
|
||||||
|
|
||||||
|
--subCount_;
|
||||||
|
|
||||||
|
subscribers_[account].erase(session);
|
||||||
|
|
||||||
|
if (subscribers_[account].size() == 0)
|
||||||
|
{
|
||||||
|
subscribers_.erase(account);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class Key>
|
||||||
|
void
|
||||||
|
SubscriptionMap<Key>::publish(
|
||||||
|
std::shared_ptr<Message> const& message,
|
||||||
|
Key const& account)
|
||||||
|
{
|
||||||
|
boost::asio::post(strand_, [this, account, message]() {
|
||||||
|
if (!subscribers_.contains(account))
|
||||||
|
return;
|
||||||
|
|
||||||
|
sendToSubscribers(message, subscribers_[account], subCount_);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
class SubscriptionManager
|
class SubscriptionManager
|
||||||
{
|
{
|
||||||
using session_ptr = std::shared_ptr<WsBase>;
|
using session_ptr = std::shared_ptr<WsBase>;
|
||||||
|
|||||||
880
unittests/SubscriptionManagerTest.cpp
Normal file
880
unittests/SubscriptionManagerTest.cpp
Normal file
@@ -0,0 +1,880 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
|
/*
|
||||||
|
This file is part of clio: https://github.com/XRPLF/clio
|
||||||
|
Copyright (c) 2023, the clio developers.
|
||||||
|
|
||||||
|
Permission to use, copy, modify, and distribute this software for any
|
||||||
|
purpose with or without fee is hereby granted, provided that the above
|
||||||
|
copyright notice and this permission notice appear in all copies.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
//==============================================================================
|
||||||
|
|
||||||
|
#include <subscriptions/SubscriptionManager.h>
|
||||||
|
#include <util/Fixtures.h>
|
||||||
|
#include <util/MockBackend.h>
|
||||||
|
#include <util/MockWsBase.h>
|
||||||
|
#include <util/TestObject.h>
|
||||||
|
#include <webserver/WsBase.h>
|
||||||
|
|
||||||
|
#include <boost/json/parse.hpp>
|
||||||
|
#include <gmock/gmock.h>
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
|
|
||||||
|
using namespace std::chrono_literals;
|
||||||
|
namespace json = boost::json;
|
||||||
|
using namespace Backend;
|
||||||
|
using ::testing::Return;
|
||||||
|
|
||||||
|
// common const
|
||||||
|
constexpr static auto CURRENCY = "0158415500000000C1F76FF6ECB0BAC600000000";
|
||||||
|
constexpr static auto ISSUER = "rK9DrarGKnVEo2nYp5MfVRXRYf5yRX3mwD";
|
||||||
|
constexpr static auto ACCOUNT1 = "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn";
|
||||||
|
constexpr static auto ACCOUNT2 = "rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun";
|
||||||
|
constexpr static auto LEDGERHASH =
|
||||||
|
"4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652";
|
||||||
|
constexpr static auto LEDGERHASH2 =
|
||||||
|
"1B8590C01B0006EDFA9ED60296DD052DC5E90F99659B25014D08E1BC983515BC";
|
||||||
|
constexpr static auto TXNID =
|
||||||
|
"E6DBAFC99223B42257915A63DFC6B0C032D4070F9A574B255AD97466726FC321";
|
||||||
|
|
||||||
|
/*
|
||||||
|
* test subscription factory method and report function
|
||||||
|
*/
|
||||||
|
TEST(SubscriptionManagerTest, InitAndReport)
|
||||||
|
{
|
||||||
|
constexpr static auto ReportReturn = R"({
|
||||||
|
"ledger":0,
|
||||||
|
"transactions":0,
|
||||||
|
"transactions_proposed":0,
|
||||||
|
"manifests":0,
|
||||||
|
"validations":0,
|
||||||
|
"account":0,
|
||||||
|
"accounts_proposed":0,
|
||||||
|
"books":0,
|
||||||
|
"book_changes":0
|
||||||
|
})";
|
||||||
|
clio::Config cfg;
|
||||||
|
auto backend = std::make_shared<MockBackend>(cfg);
|
||||||
|
auto subManager =
|
||||||
|
SubscriptionManager::make_SubscriptionManager(cfg, backend);
|
||||||
|
EXPECT_EQ(subManager->report(), json::parse(ReportReturn));
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
CheckSubscriberMessage(
|
||||||
|
std::string out,
|
||||||
|
std::shared_ptr<WsBase> session,
|
||||||
|
int retry = 10)
|
||||||
|
{
|
||||||
|
auto sessionPtr = static_cast<MockSession*>(session.get());
|
||||||
|
while (retry-- != 0)
|
||||||
|
{
|
||||||
|
std::this_thread::sleep_for(20ms);
|
||||||
|
if ((!sessionPtr->message.empty()) &&
|
||||||
|
json::parse(sessionPtr->message) == json::parse(out))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EXPECT_TRUE(false) << "Could not wait the subscriber message, expect:"
|
||||||
|
<< out << " Get:" << sessionPtr->message;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fixture contains test target and mock backend
|
||||||
|
class SubscriptionManagerSimpleBackendTest : public MockBackendTest
|
||||||
|
{
|
||||||
|
protected:
|
||||||
|
clio::Config cfg;
|
||||||
|
std::shared_ptr<SubscriptionManager> subManagerPtr;
|
||||||
|
util::TagDecoratorFactory tagDecoratorFactory{cfg};
|
||||||
|
std::shared_ptr<WsBase> session;
|
||||||
|
void
|
||||||
|
SetUp() override
|
||||||
|
{
|
||||||
|
MockBackendTest::SetUp();
|
||||||
|
subManagerPtr =
|
||||||
|
SubscriptionManager::make_SubscriptionManager(cfg, mockBackendPtr);
|
||||||
|
session = std::make_shared<MockSession>(tagDecoratorFactory);
|
||||||
|
}
|
||||||
|
void
|
||||||
|
TearDown() override
|
||||||
|
{
|
||||||
|
MockBackendTest::TearDown();
|
||||||
|
subManagerPtr.reset();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* test report function and unsub functions
|
||||||
|
*/
|
||||||
|
TEST_F(SubscriptionManagerSimpleBackendTest, ReportCurrentSubscriber)
|
||||||
|
{
|
||||||
|
constexpr static auto ReportReturn = R"({
|
||||||
|
"ledger":0,
|
||||||
|
"transactions":2,
|
||||||
|
"transactions_proposed":2,
|
||||||
|
"manifests":2,
|
||||||
|
"validations":2,
|
||||||
|
"account":2,
|
||||||
|
"accounts_proposed":2,
|
||||||
|
"books":2,
|
||||||
|
"book_changes":2
|
||||||
|
})";
|
||||||
|
std::shared_ptr<WsBase> session1 =
|
||||||
|
std::make_shared<MockSession>(tagDecoratorFactory);
|
||||||
|
std::shared_ptr<WsBase> session2 =
|
||||||
|
std::make_shared<MockSession>(tagDecoratorFactory);
|
||||||
|
subManagerPtr->subBookChanges(session1);
|
||||||
|
subManagerPtr->subBookChanges(session2);
|
||||||
|
subManagerPtr->subManifest(session1);
|
||||||
|
subManagerPtr->subManifest(session2);
|
||||||
|
subManagerPtr->subProposedTransactions(session1);
|
||||||
|
subManagerPtr->subProposedTransactions(session2);
|
||||||
|
subManagerPtr->subTransactions(session1);
|
||||||
|
subManagerPtr->subTransactions(session2);
|
||||||
|
subManagerPtr->subValidation(session1);
|
||||||
|
subManagerPtr->subValidation(session2);
|
||||||
|
auto account = GetAccountIDWithString(ACCOUNT1);
|
||||||
|
subManagerPtr->subAccount(account, session1);
|
||||||
|
subManagerPtr->subAccount(account, session2);
|
||||||
|
subManagerPtr->subProposedAccount(account, session1);
|
||||||
|
subManagerPtr->subProposedAccount(account, session2);
|
||||||
|
auto issue1 = GetIssue(CURRENCY, ISSUER);
|
||||||
|
ripple::Book book{ripple::xrpIssue(), issue1};
|
||||||
|
subManagerPtr->subBook(book, session1);
|
||||||
|
subManagerPtr->subBook(book, session2);
|
||||||
|
std::this_thread::sleep_for(20ms);
|
||||||
|
EXPECT_EQ(subManagerPtr->report(), json::parse(ReportReturn));
|
||||||
|
subManagerPtr->unsubBookChanges(session1);
|
||||||
|
subManagerPtr->unsubManifest(session1);
|
||||||
|
subManagerPtr->unsubProposedTransactions(session1);
|
||||||
|
subManagerPtr->unsubTransactions(session1);
|
||||||
|
subManagerPtr->unsubValidation(session1);
|
||||||
|
subManagerPtr->unsubAccount(account, session1);
|
||||||
|
subManagerPtr->unsubProposedAccount(account, session1);
|
||||||
|
subManagerPtr->unsubBook(book, session1);
|
||||||
|
std::this_thread::sleep_for(20ms);
|
||||||
|
auto checkResult = [](json::object reportReturn, int result) {
|
||||||
|
EXPECT_EQ(reportReturn["book_changes"], result);
|
||||||
|
EXPECT_EQ(reportReturn["validations"], result);
|
||||||
|
EXPECT_EQ(reportReturn["transactions_proposed"], result);
|
||||||
|
EXPECT_EQ(reportReturn["transactions"], result);
|
||||||
|
EXPECT_EQ(reportReturn["manifests"], result);
|
||||||
|
EXPECT_EQ(reportReturn["accounts_proposed"], result);
|
||||||
|
EXPECT_EQ(reportReturn["account"], result);
|
||||||
|
EXPECT_EQ(reportReturn["books"], result);
|
||||||
|
};
|
||||||
|
checkResult(subManagerPtr->report(), 1);
|
||||||
|
subManagerPtr->cleanup(session2);
|
||||||
|
subManagerPtr->cleanup(session2); // clean a removed session
|
||||||
|
std::this_thread::sleep_for(20ms);
|
||||||
|
checkResult(subManagerPtr->report(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(SubscriptionManagerSimpleBackendTest, SubscriptionManagerLedgerUnSub)
|
||||||
|
{
|
||||||
|
MockBackend* rawBackendPtr =
|
||||||
|
static_cast<MockBackend*>(mockBackendPtr.get());
|
||||||
|
mockBackendPtr->updateRange(10); // min
|
||||||
|
mockBackendPtr->updateRange(30); // max
|
||||||
|
boost::asio::io_context ctx;
|
||||||
|
auto ledgerinfo = CreateLedgerInfo(LEDGERHASH, 30);
|
||||||
|
// mock fetchLedgerBySequence return this ledger
|
||||||
|
ON_CALL(*rawBackendPtr, fetchLedgerBySequence)
|
||||||
|
.WillByDefault(Return(ledgerinfo));
|
||||||
|
EXPECT_CALL(*rawBackendPtr, fetchLedgerBySequence).Times(1);
|
||||||
|
// mock doFetchLedgerObject return fee setting ledger object
|
||||||
|
auto feeBlob = CreateFeeSettingBlob(1, 2, 3, 4, 0);
|
||||||
|
ON_CALL(*rawBackendPtr, doFetchLedgerObject).WillByDefault(Return(feeBlob));
|
||||||
|
EXPECT_CALL(*rawBackendPtr, doFetchLedgerObject).Times(1);
|
||||||
|
boost::asio::spawn(ctx, [this](boost::asio::yield_context yield) {
|
||||||
|
subManagerPtr->subLedger(yield, session);
|
||||||
|
});
|
||||||
|
ctx.run();
|
||||||
|
std::this_thread::sleep_for(20ms);
|
||||||
|
auto report = subManagerPtr->report();
|
||||||
|
EXPECT_EQ(report["ledger"], 1);
|
||||||
|
subManagerPtr->cleanup(session);
|
||||||
|
subManagerPtr->unsubLedger(session);
|
||||||
|
std::this_thread::sleep_for(20ms);
|
||||||
|
report = subManagerPtr->report();
|
||||||
|
EXPECT_EQ(report["ledger"], 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* test Manifest
|
||||||
|
* Subscription Manager forward the manifest message to subscribers
|
||||||
|
*/
|
||||||
|
TEST_F(SubscriptionManagerSimpleBackendTest, SubscriptionManagerManifestTest)
|
||||||
|
{
|
||||||
|
subManagerPtr->subManifest(session);
|
||||||
|
constexpr static auto dummyManifest = R"({"manifest":"test"})";
|
||||||
|
subManagerPtr->forwardManifest(json::parse(dummyManifest).get_object());
|
||||||
|
CheckSubscriberMessage(dummyManifest, session);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* test Validation
|
||||||
|
* Subscription Manager forward the validation message to subscribers
|
||||||
|
*/
|
||||||
|
TEST_F(SubscriptionManagerSimpleBackendTest, SubscriptionManagerValidation)
|
||||||
|
{
|
||||||
|
subManagerPtr->subValidation(session);
|
||||||
|
constexpr static auto dummyValidation = R"({"validation":"test"})";
|
||||||
|
subManagerPtr->forwardValidation(json::parse(dummyValidation).get_object());
|
||||||
|
CheckSubscriberMessage(dummyValidation, session);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* test ProposedTransaction
|
||||||
|
* We don't need the valid transaction in this test, subscription manager just
|
||||||
|
* forward the message to subscriber
|
||||||
|
*/
|
||||||
|
TEST_F(
|
||||||
|
SubscriptionManagerSimpleBackendTest,
|
||||||
|
SubscriptionManagerProposedTransaction)
|
||||||
|
{
|
||||||
|
subManagerPtr->subProposedTransactions(session);
|
||||||
|
constexpr static auto dummyTransaction = R"({
|
||||||
|
"transaction":
|
||||||
|
{
|
||||||
|
"Account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn"
|
||||||
|
}
|
||||||
|
})";
|
||||||
|
subManagerPtr->forwardProposedTransaction(
|
||||||
|
json::parse(dummyTransaction).get_object());
|
||||||
|
CheckSubscriberMessage(dummyTransaction, session);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* test ProposedTransaction for one account
|
||||||
|
* we need to construct a valid account in the transaction
|
||||||
|
* this test subscribe the proposed transaction for two accounts
|
||||||
|
* but only forward a transaction with one of them
|
||||||
|
* check the correct session is called
|
||||||
|
*/
|
||||||
|
TEST_F(
|
||||||
|
SubscriptionManagerSimpleBackendTest,
|
||||||
|
SubscriptionManagerAccountProposedTransaction)
|
||||||
|
{
|
||||||
|
auto account = GetAccountIDWithString(ACCOUNT1);
|
||||||
|
subManagerPtr->subProposedAccount(account, session);
|
||||||
|
|
||||||
|
std::shared_ptr<WsBase> sessionIdle =
|
||||||
|
std::make_shared<MockSession>(tagDecoratorFactory);
|
||||||
|
auto accountIdle = GetAccountIDWithString(ACCOUNT2);
|
||||||
|
subManagerPtr->subProposedAccount(accountIdle, sessionIdle);
|
||||||
|
|
||||||
|
constexpr static auto dummyTransaction = R"({
|
||||||
|
"transaction":
|
||||||
|
{
|
||||||
|
"Account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn"
|
||||||
|
}
|
||||||
|
})";
|
||||||
|
subManagerPtr->forwardProposedTransaction(
|
||||||
|
json::parse(dummyTransaction).get_object());
|
||||||
|
CheckSubscriberMessage(dummyTransaction, session);
|
||||||
|
auto rawIdle = (MockSession*)(sessionIdle.get());
|
||||||
|
EXPECT_EQ("", rawIdle->message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* test ledger stream
|
||||||
|
* check 1 subscribe response, 2 publish message
|
||||||
|
* mock backend to return fee ledger object
|
||||||
|
*/
|
||||||
|
TEST_F(SubscriptionManagerSimpleBackendTest, SubscriptionManagerLedger)
|
||||||
|
{
|
||||||
|
MockBackend* rawBackendPtr =
|
||||||
|
static_cast<MockBackend*>(mockBackendPtr.get());
|
||||||
|
mockBackendPtr->updateRange(10); // min
|
||||||
|
mockBackendPtr->updateRange(30); // max
|
||||||
|
boost::asio::io_context ctx;
|
||||||
|
auto ledgerinfo = CreateLedgerInfo(LEDGERHASH, 30);
|
||||||
|
// mock fetchLedgerBySequence return this ledger
|
||||||
|
ON_CALL(*rawBackendPtr, fetchLedgerBySequence)
|
||||||
|
.WillByDefault(Return(ledgerinfo));
|
||||||
|
EXPECT_CALL(*rawBackendPtr, fetchLedgerBySequence).Times(1);
|
||||||
|
// mock doFetchLedgerObject return fee setting ledger object
|
||||||
|
auto feeBlob = CreateFeeSettingBlob(1, 2, 3, 4, 0);
|
||||||
|
ON_CALL(*rawBackendPtr, doFetchLedgerObject).WillByDefault(Return(feeBlob));
|
||||||
|
EXPECT_CALL(*rawBackendPtr, doFetchLedgerObject).Times(1);
|
||||||
|
// check the function response
|
||||||
|
// Information about the ledgers on hand and current fee schedule. This
|
||||||
|
// includes the same fields as a ledger stream message, except that it omits
|
||||||
|
// the type and txn_count fields
|
||||||
|
constexpr static auto LedgerResponse = R"({
|
||||||
|
"validated_ledgers":"10-30",
|
||||||
|
"ledger_index":30,
|
||||||
|
"ledger_hash":"4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652",
|
||||||
|
"ledger_time":0,
|
||||||
|
"fee_ref":4,
|
||||||
|
"fee_base":1,
|
||||||
|
"reserve_base":3,
|
||||||
|
"reserve_inc":2
|
||||||
|
})";
|
||||||
|
boost::asio::spawn(ctx, [this](boost::asio::yield_context yield) {
|
||||||
|
auto res = subManagerPtr->subLedger(yield, session);
|
||||||
|
// check the response
|
||||||
|
EXPECT_EQ(res, json::parse(LedgerResponse));
|
||||||
|
});
|
||||||
|
ctx.run();
|
||||||
|
// test publish
|
||||||
|
auto ledgerinfo2 = CreateLedgerInfo(LEDGERHASH, 31);
|
||||||
|
auto fee2 = ripple::Fees();
|
||||||
|
fee2.reserve = 10;
|
||||||
|
subManagerPtr->pubLedger(ledgerinfo2, fee2, "10-31", 8);
|
||||||
|
constexpr static auto LedgerPub = R"({
|
||||||
|
"type":"ledgerClosed",
|
||||||
|
"ledger_index":31,
|
||||||
|
"ledger_hash":"4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652",
|
||||||
|
"ledger_time":0,
|
||||||
|
"fee_ref":0,
|
||||||
|
"fee_base":0,
|
||||||
|
"reserve_base":10,
|
||||||
|
"reserve_inc":0,
|
||||||
|
"validated_ledgers":"10-31",
|
||||||
|
"txn_count":8
|
||||||
|
})";
|
||||||
|
CheckSubscriberMessage(LedgerPub, session);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* test book change
|
||||||
|
* create a book change meta data for
|
||||||
|
* XRP vs A token
|
||||||
|
* the transaction is just placeholder
|
||||||
|
* Book change computing only needs meta data
|
||||||
|
*/
|
||||||
|
TEST_F(SubscriptionManagerSimpleBackendTest, SubscriptionManagerBookChange)
|
||||||
|
{
|
||||||
|
subManagerPtr->subBookChanges(session);
|
||||||
|
auto ledgerinfo = CreateLedgerInfo(LEDGERHASH, 32);
|
||||||
|
auto transactions = std::vector<TransactionAndMetadata>{};
|
||||||
|
auto trans1 = TransactionAndMetadata();
|
||||||
|
ripple::STObject obj =
|
||||||
|
CreatePaymentTransactionObject(ACCOUNT1, ACCOUNT2, 1, 1, 32);
|
||||||
|
trans1.transaction = obj.getSerializer().peekData();
|
||||||
|
trans1.ledgerSequence = 32;
|
||||||
|
ripple::STObject metaObj =
|
||||||
|
CreateMetaDataForBookChange(CURRENCY, ISSUER, 22, 1, 3, 3, 1);
|
||||||
|
trans1.metadata = metaObj.getSerializer().peekData();
|
||||||
|
transactions.push_back(trans1);
|
||||||
|
subManagerPtr->pubBookChanges(ledgerinfo, transactions);
|
||||||
|
constexpr static auto BookChangePublish = R"({
|
||||||
|
"type":"bookChanges",
|
||||||
|
"ledger_index":32,
|
||||||
|
"ledger_hash":"4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652",
|
||||||
|
"ledger_time":0,
|
||||||
|
"changes":[
|
||||||
|
{
|
||||||
|
"currency_a":"XRP_drops",
|
||||||
|
"currency_b":"rK9DrarGKnVEo2nYp5MfVRXRYf5yRX3mwD/0158415500000000C1F76FF6ECB0BAC600000000",
|
||||||
|
"volume_a":"2",
|
||||||
|
"volume_b":"2",
|
||||||
|
"high":"-1",
|
||||||
|
"low":"-1",
|
||||||
|
"open":"-1",
|
||||||
|
"close":"-1"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})";
|
||||||
|
CheckSubscriberMessage(BookChangePublish, session, 20);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* test transaction stream
|
||||||
|
*/
|
||||||
|
TEST_F(SubscriptionManagerSimpleBackendTest, SubscriptionManagerTransaction)
|
||||||
|
{
|
||||||
|
subManagerPtr->subTransactions(session);
|
||||||
|
|
||||||
|
auto ledgerinfo = CreateLedgerInfo(LEDGERHASH2, 33);
|
||||||
|
|
||||||
|
auto trans1 = TransactionAndMetadata();
|
||||||
|
ripple::STObject obj =
|
||||||
|
CreatePaymentTransactionObject(ACCOUNT1, ACCOUNT2, 1, 1, 32);
|
||||||
|
trans1.transaction = obj.getSerializer().peekData();
|
||||||
|
trans1.ledgerSequence = 32;
|
||||||
|
// create an empty meta object
|
||||||
|
ripple::STArray metaArray{0};
|
||||||
|
ripple::STObject metaObj(ripple::sfTransactionMetaData);
|
||||||
|
metaObj.setFieldArray(ripple::sfAffectedNodes, metaArray);
|
||||||
|
metaObj.setFieldU8(ripple::sfTransactionResult, ripple::tesSUCCESS);
|
||||||
|
metaObj.setFieldU32(ripple::sfTransactionIndex, 22);
|
||||||
|
trans1.metadata = metaObj.getSerializer().peekData();
|
||||||
|
subManagerPtr->pubTransaction(trans1, ledgerinfo);
|
||||||
|
constexpr static auto TransactionPublish = R"({
|
||||||
|
"transaction":{
|
||||||
|
"Account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
|
||||||
|
"Amount":"1",
|
||||||
|
"Destination":"rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun",
|
||||||
|
"Fee":"1",
|
||||||
|
"Sequence":32,
|
||||||
|
"SigningPubKey":"74657374",
|
||||||
|
"TransactionType":"Payment",
|
||||||
|
"hash":"51D2AAA6B8E4E16EF22F6424854283D8391B56875858A711B8CE4D5B9A422CC2",
|
||||||
|
"date":0
|
||||||
|
},
|
||||||
|
"meta":{
|
||||||
|
"AffectedNodes":[],
|
||||||
|
"TransactionIndex":22,
|
||||||
|
"TransactionResult":"tesSUCCESS",
|
||||||
|
"delivered_amount":"unavailable"
|
||||||
|
},
|
||||||
|
"type":"transaction",
|
||||||
|
"validated":true,
|
||||||
|
"status":"closed",
|
||||||
|
"ledger_index":33,
|
||||||
|
"ledger_hash":"1B8590C01B0006EDFA9ED60296DD052DC5E90F99659B25014D08E1BC983515BC",
|
||||||
|
"engine_result_code":0,
|
||||||
|
"engine_result":"tesSUCCESS",
|
||||||
|
"engine_result_message":"The transaction was applied. Only final in a validated ledger."
|
||||||
|
})";
|
||||||
|
CheckSubscriberMessage(TransactionPublish, session);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* test transaction for offer creation
|
||||||
|
* check owner_funds
|
||||||
|
* mock backend return a trustline
|
||||||
|
*/
|
||||||
|
TEST_F(
|
||||||
|
SubscriptionManagerSimpleBackendTest,
|
||||||
|
SubscriptionManagerTransactionOfferCreation)
|
||||||
|
{
|
||||||
|
subManagerPtr->subTransactions(session);
|
||||||
|
|
||||||
|
auto ledgerinfo = CreateLedgerInfo(LEDGERHASH2, 33);
|
||||||
|
auto trans1 = TransactionAndMetadata();
|
||||||
|
ripple::STObject obj = CreateCreateOfferTransactionObject(
|
||||||
|
ACCOUNT1, 1, 32, CURRENCY, ISSUER, 1, 3);
|
||||||
|
trans1.transaction = obj.getSerializer().peekData();
|
||||||
|
trans1.ledgerSequence = 32;
|
||||||
|
ripple::STArray metaArray{0};
|
||||||
|
ripple::STObject metaObj(ripple::sfTransactionMetaData);
|
||||||
|
metaObj.setFieldArray(ripple::sfAffectedNodes, metaArray);
|
||||||
|
metaObj.setFieldU8(ripple::sfTransactionResult, ripple::tesSUCCESS);
|
||||||
|
metaObj.setFieldU32(ripple::sfTransactionIndex, 22);
|
||||||
|
trans1.metadata = metaObj.getSerializer().peekData();
|
||||||
|
|
||||||
|
ripple::STObject line(ripple::sfIndexes);
|
||||||
|
line.setFieldU16(ripple::sfLedgerEntryType, ripple::ltRIPPLE_STATE);
|
||||||
|
line.setFieldAmount(ripple::sfLowLimit, ripple::STAmount(10, false));
|
||||||
|
line.setFieldAmount(ripple::sfHighLimit, ripple::STAmount(100, false));
|
||||||
|
line.setFieldH256(ripple::sfPreviousTxnID, ripple::uint256{TXNID});
|
||||||
|
line.setFieldU32(ripple::sfPreviousTxnLgrSeq, 3);
|
||||||
|
line.setFieldU32(ripple::sfFlags, 0);
|
||||||
|
auto issue2 = GetIssue(CURRENCY, ISSUER);
|
||||||
|
line.setFieldAmount(ripple::sfBalance, ripple::STAmount(issue2, 100));
|
||||||
|
MockBackend* rawBackendPtr =
|
||||||
|
static_cast<MockBackend*>(mockBackendPtr.get());
|
||||||
|
EXPECT_CALL(*rawBackendPtr, doFetchLedgerObject).Times(3);
|
||||||
|
ON_CALL(*rawBackendPtr, doFetchLedgerObject)
|
||||||
|
.WillByDefault(Return(line.getSerializer().peekData()));
|
||||||
|
subManagerPtr->pubTransaction(trans1, ledgerinfo);
|
||||||
|
constexpr static auto TransactionForOwnerFund = R"({
|
||||||
|
"transaction":{
|
||||||
|
"Account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
|
||||||
|
"Fee":"1",
|
||||||
|
"Sequence":32,
|
||||||
|
"SigningPubKey":"74657374",
|
||||||
|
"TakerGets":{
|
||||||
|
"currency":"0158415500000000C1F76FF6ECB0BAC600000000",
|
||||||
|
"issuer":"rK9DrarGKnVEo2nYp5MfVRXRYf5yRX3mwD",
|
||||||
|
"value":"1"
|
||||||
|
},
|
||||||
|
"TakerPays":"3",
|
||||||
|
"TransactionType":"OfferCreate",
|
||||||
|
"hash":"EE8775B43A67F4803DECEC5E918E0EA9C56D8ED93E512EBE9F2891846509AAAB",
|
||||||
|
"date":0,
|
||||||
|
"owner_funds":"100"
|
||||||
|
},
|
||||||
|
"meta":{
|
||||||
|
"AffectedNodes":[],
|
||||||
|
"TransactionIndex":22,
|
||||||
|
"TransactionResult":"tesSUCCESS"
|
||||||
|
},
|
||||||
|
"type":"transaction",
|
||||||
|
"validated":true,
|
||||||
|
"status":"closed",
|
||||||
|
"ledger_index":33,
|
||||||
|
"ledger_hash":"1B8590C01B0006EDFA9ED60296DD052DC5E90F99659B25014D08E1BC983515BC",
|
||||||
|
"engine_result_code":0,
|
||||||
|
"engine_result":"tesSUCCESS",
|
||||||
|
"engine_result_message":"The transaction was applied. Only final in a validated ledger."
|
||||||
|
})";
|
||||||
|
CheckSubscriberMessage(TransactionForOwnerFund, session);
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr static auto TransactionForOwnerFundFrozen = R"({
|
||||||
|
"transaction":{
|
||||||
|
"Account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
|
||||||
|
"Fee":"1",
|
||||||
|
"Sequence":32,
|
||||||
|
"SigningPubKey":"74657374",
|
||||||
|
"TakerGets":{
|
||||||
|
"currency":"0158415500000000C1F76FF6ECB0BAC600000000",
|
||||||
|
"issuer":"rK9DrarGKnVEo2nYp5MfVRXRYf5yRX3mwD",
|
||||||
|
"value":"1"
|
||||||
|
},
|
||||||
|
"TakerPays":"3",
|
||||||
|
"TransactionType":"OfferCreate",
|
||||||
|
"hash":"EE8775B43A67F4803DECEC5E918E0EA9C56D8ED93E512EBE9F2891846509AAAB",
|
||||||
|
"date":0,
|
||||||
|
"owner_funds":"0"
|
||||||
|
},
|
||||||
|
"meta":{
|
||||||
|
"AffectedNodes":[],
|
||||||
|
"TransactionIndex":22,
|
||||||
|
"TransactionResult":"tesSUCCESS"
|
||||||
|
},
|
||||||
|
"type":"transaction",
|
||||||
|
"validated":true,
|
||||||
|
"status":"closed",
|
||||||
|
"ledger_index":33,
|
||||||
|
"ledger_hash":"1B8590C01B0006EDFA9ED60296DD052DC5E90F99659B25014D08E1BC983515BC",
|
||||||
|
"engine_result_code":0,
|
||||||
|
"engine_result":"tesSUCCESS",
|
||||||
|
"engine_result_message":"The transaction was applied. Only final in a validated ledger."
|
||||||
|
})";
|
||||||
|
|
||||||
|
/*
|
||||||
|
* test transaction for offer creation
|
||||||
|
* check owner_funds when line is frozen
|
||||||
|
* mock backend return a trustline
|
||||||
|
*/
|
||||||
|
TEST_F(
|
||||||
|
SubscriptionManagerSimpleBackendTest,
|
||||||
|
SubscriptionManagerTransactionOfferCreationFrozenLine)
|
||||||
|
{
|
||||||
|
subManagerPtr->subTransactions(session);
|
||||||
|
|
||||||
|
auto ledgerinfo = CreateLedgerInfo(LEDGERHASH2, 33);
|
||||||
|
auto trans1 = TransactionAndMetadata();
|
||||||
|
ripple::STObject obj = CreateCreateOfferTransactionObject(
|
||||||
|
ACCOUNT1, 1, 32, CURRENCY, ISSUER, 1, 3);
|
||||||
|
trans1.transaction = obj.getSerializer().peekData();
|
||||||
|
trans1.ledgerSequence = 32;
|
||||||
|
ripple::STArray metaArray{0};
|
||||||
|
ripple::STObject metaObj(ripple::sfTransactionMetaData);
|
||||||
|
metaObj.setFieldArray(ripple::sfAffectedNodes, metaArray);
|
||||||
|
metaObj.setFieldU8(ripple::sfTransactionResult, ripple::tesSUCCESS);
|
||||||
|
metaObj.setFieldU32(ripple::sfTransactionIndex, 22);
|
||||||
|
trans1.metadata = metaObj.getSerializer().peekData();
|
||||||
|
|
||||||
|
ripple::STObject line(ripple::sfIndexes);
|
||||||
|
line.setFieldU16(ripple::sfLedgerEntryType, ripple::ltRIPPLE_STATE);
|
||||||
|
line.setFieldAmount(ripple::sfLowLimit, ripple::STAmount(10, false));
|
||||||
|
line.setFieldAmount(ripple::sfHighLimit, ripple::STAmount(100, false));
|
||||||
|
line.setFieldH256(ripple::sfPreviousTxnID, ripple::uint256{TXNID});
|
||||||
|
line.setFieldU32(ripple::sfPreviousTxnLgrSeq, 3);
|
||||||
|
line.setFieldU32(ripple::sfFlags, ripple::lsfHighFreeze);
|
||||||
|
line.setFieldAmount(
|
||||||
|
ripple::sfBalance, ripple::STAmount(GetIssue(CURRENCY, ISSUER), 100));
|
||||||
|
MockBackend* rawBackendPtr =
|
||||||
|
static_cast<MockBackend*>(mockBackendPtr.get());
|
||||||
|
EXPECT_CALL(*rawBackendPtr, doFetchLedgerObject).Times(3);
|
||||||
|
ON_CALL(*rawBackendPtr, doFetchLedgerObject)
|
||||||
|
.WillByDefault(Return(line.getSerializer().peekData()));
|
||||||
|
subManagerPtr->pubTransaction(trans1, ledgerinfo);
|
||||||
|
CheckSubscriberMessage(TransactionForOwnerFundFrozen, session);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* test transaction for offer creation
|
||||||
|
* check owner_funds when issue global frozen
|
||||||
|
* mock backend return a frozen account setting
|
||||||
|
*/
|
||||||
|
TEST_F(
|
||||||
|
SubscriptionManagerSimpleBackendTest,
|
||||||
|
SubscriptionManagerTransactionOfferCreationGlobalFrozen)
|
||||||
|
{
|
||||||
|
subManagerPtr->subTransactions(session);
|
||||||
|
|
||||||
|
auto ledgerinfo = CreateLedgerInfo(LEDGERHASH2, 33);
|
||||||
|
auto trans1 = TransactionAndMetadata();
|
||||||
|
ripple::STObject obj = CreateCreateOfferTransactionObject(
|
||||||
|
ACCOUNT1, 1, 32, CURRENCY, ISSUER, 1, 3);
|
||||||
|
trans1.transaction = obj.getSerializer().peekData();
|
||||||
|
trans1.ledgerSequence = 32;
|
||||||
|
ripple::STArray metaArray{0};
|
||||||
|
ripple::STObject metaObj(ripple::sfTransactionMetaData);
|
||||||
|
metaObj.setFieldArray(ripple::sfAffectedNodes, metaArray);
|
||||||
|
metaObj.setFieldU8(ripple::sfTransactionResult, ripple::tesSUCCESS);
|
||||||
|
metaObj.setFieldU32(ripple::sfTransactionIndex, 22);
|
||||||
|
trans1.metadata = metaObj.getSerializer().peekData();
|
||||||
|
|
||||||
|
ripple::STObject line(ripple::sfIndexes);
|
||||||
|
line.setFieldU16(ripple::sfLedgerEntryType, ripple::ltRIPPLE_STATE);
|
||||||
|
line.setFieldAmount(ripple::sfLowLimit, ripple::STAmount(10, false));
|
||||||
|
line.setFieldAmount(ripple::sfHighLimit, ripple::STAmount(100, false));
|
||||||
|
line.setFieldH256(ripple::sfPreviousTxnID, ripple::uint256{TXNID});
|
||||||
|
line.setFieldU32(ripple::sfPreviousTxnLgrSeq, 3);
|
||||||
|
line.setFieldU32(ripple::sfFlags, ripple::lsfHighFreeze);
|
||||||
|
auto issueAccount = GetAccountIDWithString(ISSUER);
|
||||||
|
line.setFieldAmount(
|
||||||
|
ripple::sfBalance, ripple::STAmount(GetIssue(CURRENCY, ISSUER), 100));
|
||||||
|
MockBackend* rawBackendPtr =
|
||||||
|
static_cast<MockBackend*>(mockBackendPtr.get());
|
||||||
|
EXPECT_CALL(*rawBackendPtr, doFetchLedgerObject).Times(2);
|
||||||
|
auto kk = ripple::keylet::account(issueAccount).key;
|
||||||
|
ON_CALL(
|
||||||
|
*rawBackendPtr, doFetchLedgerObject(testing::_, testing::_, testing::_))
|
||||||
|
.WillByDefault(Return(line.getSerializer().peekData()));
|
||||||
|
ripple::STObject accountRoot = CreateAccountRootObject(
|
||||||
|
ISSUER, ripple::lsfGlobalFreeze, 1, 10, 2, TXNID, 3);
|
||||||
|
ON_CALL(*rawBackendPtr, doFetchLedgerObject(kk, testing::_, testing::_))
|
||||||
|
.WillByDefault(Return(accountRoot.getSerializer().peekData()));
|
||||||
|
subManagerPtr->pubTransaction(trans1, ledgerinfo);
|
||||||
|
CheckSubscriberMessage(TransactionForOwnerFundFrozen, session);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* test subscribe account
|
||||||
|
*/
|
||||||
|
TEST_F(SubscriptionManagerSimpleBackendTest, SubscriptionManagerAccount)
|
||||||
|
{
|
||||||
|
auto account = GetAccountIDWithString(ACCOUNT1);
|
||||||
|
subManagerPtr->subAccount(account, session);
|
||||||
|
auto ledgerinfo = CreateLedgerInfo(LEDGERHASH2, 33);
|
||||||
|
|
||||||
|
ripple::STObject obj =
|
||||||
|
CreatePaymentTransactionObject(ACCOUNT1, ACCOUNT2, 1, 1, 32);
|
||||||
|
auto trans1 = TransactionAndMetadata();
|
||||||
|
trans1.transaction = obj.getSerializer().peekData();
|
||||||
|
trans1.ledgerSequence = 32;
|
||||||
|
ripple::STArray metaArray{1};
|
||||||
|
ripple::STObject node(ripple::sfModifiedNode);
|
||||||
|
// emplace account into meta, trigger publish
|
||||||
|
ripple::STObject finalFields(ripple::sfFinalFields);
|
||||||
|
finalFields.setAccountID(ripple::sfAccount, account);
|
||||||
|
node.emplace_back(finalFields);
|
||||||
|
node.setFieldU16(ripple::sfLedgerEntryType, ripple::ltACCOUNT_ROOT);
|
||||||
|
metaArray.push_back(node);
|
||||||
|
ripple::STObject metaObj(ripple::sfTransactionMetaData);
|
||||||
|
metaObj.setFieldArray(ripple::sfAffectedNodes, metaArray);
|
||||||
|
metaObj.setFieldU8(ripple::sfTransactionResult, ripple::tesSUCCESS);
|
||||||
|
metaObj.setFieldU32(ripple::sfTransactionIndex, 22);
|
||||||
|
trans1.metadata = metaObj.getSerializer().peekData();
|
||||||
|
|
||||||
|
subManagerPtr->pubTransaction(trans1, ledgerinfo);
|
||||||
|
constexpr static auto AccountPublish = R"({
|
||||||
|
"transaction":{
|
||||||
|
"Account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
|
||||||
|
"Amount":"1",
|
||||||
|
"Destination":"rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun",
|
||||||
|
"Fee":"1",
|
||||||
|
"Sequence":32,
|
||||||
|
"SigningPubKey":"74657374",
|
||||||
|
"TransactionType":"Payment",
|
||||||
|
"hash":"51D2AAA6B8E4E16EF22F6424854283D8391B56875858A711B8CE4D5B9A422CC2",
|
||||||
|
"date":0
|
||||||
|
},
|
||||||
|
"meta":{
|
||||||
|
"AffectedNodes":[
|
||||||
|
{
|
||||||
|
"ModifiedNode":{
|
||||||
|
"FinalFields":{
|
||||||
|
"Account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn"
|
||||||
|
},
|
||||||
|
"LedgerEntryType":"AccountRoot"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"TransactionIndex":22,
|
||||||
|
"TransactionResult":"tesSUCCESS",
|
||||||
|
"delivered_amount":"unavailable"
|
||||||
|
},
|
||||||
|
"type":"transaction",
|
||||||
|
"validated":true,
|
||||||
|
"status":"closed",
|
||||||
|
"ledger_index":33,
|
||||||
|
"ledger_hash":"1B8590C01B0006EDFA9ED60296DD052DC5E90F99659B25014D08E1BC983515BC",
|
||||||
|
"engine_result_code":0,
|
||||||
|
"engine_result":"tesSUCCESS",
|
||||||
|
"engine_result_message":"The transaction was applied. Only final in a validated ledger."
|
||||||
|
})";
|
||||||
|
CheckSubscriberMessage(AccountPublish, session);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* test subscribe order book
|
||||||
|
* Create/Delete/Update offer node will trigger publish
|
||||||
|
*/
|
||||||
|
TEST_F(SubscriptionManagerSimpleBackendTest, SubscriptionManagerOrderBook)
|
||||||
|
{
|
||||||
|
auto issue1 = GetIssue(CURRENCY, ISSUER);
|
||||||
|
ripple::Book book{ripple::xrpIssue(), issue1};
|
||||||
|
subManagerPtr->subBook(book, session);
|
||||||
|
auto ledgerinfo = CreateLedgerInfo(LEDGERHASH2, 33);
|
||||||
|
|
||||||
|
auto trans1 = TransactionAndMetadata();
|
||||||
|
auto obj = CreatePaymentTransactionObject(ACCOUNT1, ACCOUNT2, 1, 1, 32);
|
||||||
|
trans1.transaction = obj.getSerializer().peekData();
|
||||||
|
trans1.ledgerSequence = 32;
|
||||||
|
|
||||||
|
auto metaObj =
|
||||||
|
CreateMetaDataForBookChange(CURRENCY, ISSUER, 22, 3, 1, 1, 3);
|
||||||
|
trans1.metadata = metaObj.getSerializer().peekData();
|
||||||
|
subManagerPtr->pubTransaction(trans1, ledgerinfo);
|
||||||
|
|
||||||
|
constexpr static auto OrderbookPublish = R"({
|
||||||
|
"transaction":{
|
||||||
|
"Account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
|
||||||
|
"Amount":"1",
|
||||||
|
"Destination":"rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun",
|
||||||
|
"Fee":"1",
|
||||||
|
"Sequence":32,
|
||||||
|
"SigningPubKey":"74657374",
|
||||||
|
"TransactionType":"Payment",
|
||||||
|
"hash":"51D2AAA6B8E4E16EF22F6424854283D8391B56875858A711B8CE4D5B9A422CC2",
|
||||||
|
"date":0
|
||||||
|
},
|
||||||
|
"meta":{
|
||||||
|
"AffectedNodes":[
|
||||||
|
{
|
||||||
|
"ModifiedNode":{
|
||||||
|
"FinalFields":{
|
||||||
|
"TakerGets":"3",
|
||||||
|
"TakerPays":{
|
||||||
|
"currency":"0158415500000000C1F76FF6ECB0BAC600000000",
|
||||||
|
"issuer":"rK9DrarGKnVEo2nYp5MfVRXRYf5yRX3mwD",
|
||||||
|
"value":"1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"LedgerEntryType":"Offer",
|
||||||
|
"PreviousFields":{
|
||||||
|
"TakerGets":"1",
|
||||||
|
"TakerPays":{
|
||||||
|
"currency":"0158415500000000C1F76FF6ECB0BAC600000000",
|
||||||
|
"issuer":"rK9DrarGKnVEo2nYp5MfVRXRYf5yRX3mwD",
|
||||||
|
"value":"3"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"TransactionIndex":22,
|
||||||
|
"TransactionResult":"tesSUCCESS",
|
||||||
|
"delivered_amount":"unavailable"
|
||||||
|
},
|
||||||
|
"type":"transaction",
|
||||||
|
"validated":true,
|
||||||
|
"status":"closed",
|
||||||
|
"ledger_index":33,
|
||||||
|
"ledger_hash":"1B8590C01B0006EDFA9ED60296DD052DC5E90F99659B25014D08E1BC983515BC",
|
||||||
|
"engine_result_code":0,
|
||||||
|
"engine_result":"tesSUCCESS",
|
||||||
|
"engine_result_message":"The transaction was applied. Only final in a validated ledger."
|
||||||
|
})";
|
||||||
|
CheckSubscriberMessage(OrderbookPublish, session);
|
||||||
|
|
||||||
|
// trigger by offer cancel meta data
|
||||||
|
std::shared_ptr<WsBase> session1 =
|
||||||
|
std::make_shared<MockSession>(tagDecoratorFactory);
|
||||||
|
subManagerPtr->subBook(book, session1);
|
||||||
|
metaObj = CreateMetaDataForCancelOffer(CURRENCY, ISSUER, 22, 3, 1);
|
||||||
|
trans1.metadata = metaObj.getSerializer().peekData();
|
||||||
|
subManagerPtr->pubTransaction(trans1, ledgerinfo);
|
||||||
|
constexpr static auto OrderbookCancelPublish = R"({
|
||||||
|
"transaction":{
|
||||||
|
"Account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
|
||||||
|
"Amount":"1",
|
||||||
|
"Destination":"rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun",
|
||||||
|
"Fee":"1",
|
||||||
|
"Sequence":32,
|
||||||
|
"SigningPubKey":"74657374",
|
||||||
|
"TransactionType":"Payment",
|
||||||
|
"hash":"51D2AAA6B8E4E16EF22F6424854283D8391B56875858A711B8CE4D5B9A422CC2",
|
||||||
|
"date":0
|
||||||
|
},
|
||||||
|
"meta":{
|
||||||
|
"AffectedNodes":[
|
||||||
|
{
|
||||||
|
"DeletedNode":{
|
||||||
|
"FinalFields":{
|
||||||
|
"TakerGets":"3",
|
||||||
|
"TakerPays":{
|
||||||
|
"currency":"0158415500000000C1F76FF6ECB0BAC600000000",
|
||||||
|
"issuer":"rK9DrarGKnVEo2nYp5MfVRXRYf5yRX3mwD",
|
||||||
|
"value":"1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"LedgerEntryType":"Offer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"TransactionIndex":22,
|
||||||
|
"TransactionResult":"tesSUCCESS",
|
||||||
|
"delivered_amount":"unavailable"
|
||||||
|
},
|
||||||
|
"type":"transaction",
|
||||||
|
"validated":true,
|
||||||
|
"status":"closed",
|
||||||
|
"ledger_index":33,
|
||||||
|
"ledger_hash":"1B8590C01B0006EDFA9ED60296DD052DC5E90F99659B25014D08E1BC983515BC",
|
||||||
|
"engine_result_code":0,
|
||||||
|
"engine_result":"tesSUCCESS",
|
||||||
|
"engine_result_message":"The transaction was applied. Only final in a validated ledger."
|
||||||
|
})";
|
||||||
|
CheckSubscriberMessage(OrderbookCancelPublish, session1);
|
||||||
|
// trigger by offer create meta data
|
||||||
|
constexpr static auto OrderbookCreatePublish = R"({
|
||||||
|
"transaction":{
|
||||||
|
"Account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
|
||||||
|
"Amount":"1",
|
||||||
|
"Destination":"rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun",
|
||||||
|
"Fee":"1",
|
||||||
|
"Sequence":32,
|
||||||
|
"SigningPubKey":"74657374",
|
||||||
|
"TransactionType":"Payment",
|
||||||
|
"hash":"51D2AAA6B8E4E16EF22F6424854283D8391B56875858A711B8CE4D5B9A422CC2",
|
||||||
|
"date":0
|
||||||
|
},
|
||||||
|
"meta":{
|
||||||
|
"AffectedNodes":[
|
||||||
|
{
|
||||||
|
"CreatedNode":{
|
||||||
|
"NewFields":{
|
||||||
|
"TakerGets":"3",
|
||||||
|
"TakerPays":{
|
||||||
|
"currency":"0158415500000000C1F76FF6ECB0BAC600000000",
|
||||||
|
"issuer":"rK9DrarGKnVEo2nYp5MfVRXRYf5yRX3mwD",
|
||||||
|
"value":"1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"LedgerEntryType":"Offer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"TransactionIndex":22,
|
||||||
|
"TransactionResult":"tesSUCCESS",
|
||||||
|
"delivered_amount":"unavailable"
|
||||||
|
},
|
||||||
|
"type":"transaction",
|
||||||
|
"validated":true,
|
||||||
|
"status":"closed",
|
||||||
|
"ledger_index":33,
|
||||||
|
"ledger_hash":"1B8590C01B0006EDFA9ED60296DD052DC5E90F99659B25014D08E1BC983515BC",
|
||||||
|
"engine_result_code":0,
|
||||||
|
"engine_result":"tesSUCCESS",
|
||||||
|
"engine_result_message":"The transaction was applied. Only final in a validated ledger."
|
||||||
|
})";
|
||||||
|
std::shared_ptr<WsBase> session2 =
|
||||||
|
std::make_shared<MockSession>(tagDecoratorFactory);
|
||||||
|
subManagerPtr->subBook(book, session2);
|
||||||
|
metaObj = CreateMetaDataForCreateOffer(CURRENCY, ISSUER, 22, 3, 1);
|
||||||
|
trans1.metadata = metaObj.getSerializer().peekData();
|
||||||
|
subManagerPtr->pubTransaction(trans1, ledgerinfo);
|
||||||
|
CheckSubscriberMessage(OrderbookCreatePublish, session2);
|
||||||
|
}
|
||||||
218
unittests/SubscriptionTest.cpp
Normal file
218
unittests/SubscriptionTest.cpp
Normal file
@@ -0,0 +1,218 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
|
/*
|
||||||
|
This file is part of clio: https://github.com/XRPLF/clio
|
||||||
|
Copyright (c) 2023, the clio developers.
|
||||||
|
|
||||||
|
Permission to use, copy, modify, and distribute this software for any
|
||||||
|
purpose with or without fee is hereby granted, provided that the above
|
||||||
|
copyright notice and this permission notice appear in all copies.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
//==============================================================================
|
||||||
|
|
||||||
|
#include <subscriptions/SubscriptionManager.h>
|
||||||
|
#include <util/Fixtures.h>
|
||||||
|
#include <util/MockWsBase.h>
|
||||||
|
|
||||||
|
#include <boost/json/parse.hpp>
|
||||||
|
#include <gmock/gmock.h>
|
||||||
|
|
||||||
|
namespace json = boost::json;
|
||||||
|
|
||||||
|
TEST(MessageTest, Message)
|
||||||
|
{
|
||||||
|
auto m = Message{"test"};
|
||||||
|
EXPECT_STREQ(m.data(), "test");
|
||||||
|
EXPECT_EQ(m.size(), 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
// io_context
|
||||||
|
class SubscriptionTest : public SyncAsioContextTest
|
||||||
|
{
|
||||||
|
protected:
|
||||||
|
clio::Config cfg;
|
||||||
|
util::TagDecoratorFactory tagDecoratorFactory{cfg};
|
||||||
|
};
|
||||||
|
|
||||||
|
class SubscriptionMapTest : public SubscriptionTest
|
||||||
|
{
|
||||||
|
};
|
||||||
|
|
||||||
|
// subscribe/unsubscribe the same session would not change the count
|
||||||
|
TEST_F(SubscriptionTest, SubscriptionCount)
|
||||||
|
{
|
||||||
|
Subscription sub(ctx);
|
||||||
|
std::shared_ptr<WsBase> session1 =
|
||||||
|
std::make_shared<MockSession>(tagDecoratorFactory);
|
||||||
|
std::shared_ptr<WsBase> session2 =
|
||||||
|
std::make_shared<MockSession>(tagDecoratorFactory);
|
||||||
|
sub.subscribe(session1);
|
||||||
|
sub.subscribe(session2);
|
||||||
|
ctx.run();
|
||||||
|
EXPECT_EQ(sub.count(), 2);
|
||||||
|
sub.subscribe(session1);
|
||||||
|
ctx.restart();
|
||||||
|
ctx.run();
|
||||||
|
EXPECT_EQ(sub.count(), 2);
|
||||||
|
EXPECT_FALSE(sub.empty());
|
||||||
|
sub.unsubscribe(session1);
|
||||||
|
ctx.restart();
|
||||||
|
ctx.run();
|
||||||
|
EXPECT_EQ(sub.count(), 1);
|
||||||
|
sub.unsubscribe(session1);
|
||||||
|
ctx.restart();
|
||||||
|
ctx.run();
|
||||||
|
EXPECT_EQ(sub.count(), 1);
|
||||||
|
sub.unsubscribe(session2);
|
||||||
|
ctx.restart();
|
||||||
|
ctx.run();
|
||||||
|
EXPECT_EQ(sub.count(), 0);
|
||||||
|
EXPECT_TRUE(sub.empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
// send interface will be called when publish called
|
||||||
|
TEST_F(SubscriptionTest, SubscriptionPublish)
|
||||||
|
{
|
||||||
|
Subscription sub(ctx);
|
||||||
|
std::shared_ptr<WsBase> session1 =
|
||||||
|
std::make_shared<MockSession>(tagDecoratorFactory);
|
||||||
|
std::shared_ptr<WsBase> session2 =
|
||||||
|
std::make_shared<MockSession>(tagDecoratorFactory);
|
||||||
|
sub.subscribe(session1);
|
||||||
|
sub.subscribe(session2);
|
||||||
|
ctx.run();
|
||||||
|
EXPECT_EQ(sub.count(), 2);
|
||||||
|
sub.publish(std::make_shared<Message>("message"));
|
||||||
|
ctx.restart();
|
||||||
|
ctx.run();
|
||||||
|
MockSession* p1 = (MockSession*)(session1.get());
|
||||||
|
EXPECT_EQ(p1->message, "message");
|
||||||
|
MockSession* p2 = (MockSession*)(session2.get());
|
||||||
|
EXPECT_EQ(p2->message, "message");
|
||||||
|
sub.unsubscribe(session1);
|
||||||
|
ctx.restart();
|
||||||
|
ctx.run();
|
||||||
|
sub.publish(std::make_shared<Message>("message2"));
|
||||||
|
ctx.restart();
|
||||||
|
ctx.run();
|
||||||
|
EXPECT_EQ(p1->message, "message");
|
||||||
|
EXPECT_EQ(p2->message, "messagemessage2");
|
||||||
|
}
|
||||||
|
|
||||||
|
// when error happen during send(), the subsciber will be removed after
|
||||||
|
TEST_F(SubscriptionTest, SubscriptionDeadRemoveSubscriber)
|
||||||
|
{
|
||||||
|
Subscription sub(ctx);
|
||||||
|
std::shared_ptr<WsBase> session1(new MockDeadSession(tagDecoratorFactory));
|
||||||
|
sub.subscribe(session1);
|
||||||
|
ctx.run();
|
||||||
|
EXPECT_EQ(sub.count(), 1);
|
||||||
|
// trigger dead
|
||||||
|
sub.publish(std::make_shared<Message>("message"));
|
||||||
|
ctx.restart();
|
||||||
|
ctx.run();
|
||||||
|
EXPECT_EQ(session1->dead(), true);
|
||||||
|
sub.publish(std::make_shared<Message>("message"));
|
||||||
|
ctx.restart();
|
||||||
|
ctx.run();
|
||||||
|
EXPECT_EQ(sub.count(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(SubscriptionMapTest, SubscriptionMapCount)
|
||||||
|
{
|
||||||
|
std::shared_ptr<WsBase> session1 =
|
||||||
|
std::make_shared<MockSession>(tagDecoratorFactory);
|
||||||
|
std::shared_ptr<WsBase> session2 =
|
||||||
|
std::make_shared<MockSession>(tagDecoratorFactory);
|
||||||
|
std::shared_ptr<WsBase> session3 =
|
||||||
|
std::make_shared<MockSession>(tagDecoratorFactory);
|
||||||
|
SubscriptionMap<std::string> subMap(ctx);
|
||||||
|
subMap.subscribe(session1, "topic1");
|
||||||
|
subMap.subscribe(session2, "topic1");
|
||||||
|
subMap.subscribe(session3, "topic2");
|
||||||
|
ctx.run();
|
||||||
|
EXPECT_EQ(subMap.count(), 3);
|
||||||
|
subMap.subscribe(session1, "topic1");
|
||||||
|
subMap.subscribe(session2, "topic1");
|
||||||
|
ctx.restart();
|
||||||
|
ctx.run();
|
||||||
|
EXPECT_EQ(subMap.count(), 3);
|
||||||
|
subMap.unsubscribe(session1, "topic1");
|
||||||
|
ctx.restart();
|
||||||
|
ctx.run();
|
||||||
|
subMap.unsubscribe(session1, "topic1");
|
||||||
|
subMap.unsubscribe(session2, "topic1");
|
||||||
|
subMap.unsubscribe(session3, "topic2");
|
||||||
|
ctx.restart();
|
||||||
|
ctx.run();
|
||||||
|
EXPECT_EQ(subMap.count(), 0);
|
||||||
|
subMap.unsubscribe(session3, "topic2");
|
||||||
|
subMap.unsubscribe(session3, "no exist");
|
||||||
|
ctx.restart();
|
||||||
|
ctx.run();
|
||||||
|
EXPECT_EQ(subMap.count(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(SubscriptionMapTest, SubscriptionMapPublish)
|
||||||
|
{
|
||||||
|
std::shared_ptr<WsBase> session1 =
|
||||||
|
std::make_shared<MockSession>(tagDecoratorFactory);
|
||||||
|
std::shared_ptr<WsBase> session2 =
|
||||||
|
std::make_shared<MockSession>(tagDecoratorFactory);
|
||||||
|
SubscriptionMap<std::string> subMap(ctx);
|
||||||
|
const std::string topic1 = "topic1";
|
||||||
|
const std::string topic2 = "topic2";
|
||||||
|
const std::string topic1Message = "topic1Message";
|
||||||
|
const std::string topic2Message = "topic2Message";
|
||||||
|
subMap.subscribe(session1, topic1);
|
||||||
|
subMap.subscribe(session2, topic2);
|
||||||
|
ctx.run();
|
||||||
|
EXPECT_EQ(subMap.count(), 2);
|
||||||
|
auto message1 = std::make_shared<Message>(topic1Message.data());
|
||||||
|
subMap.publish(message1, topic1); // lvalue
|
||||||
|
subMap.publish(
|
||||||
|
std::make_shared<Message>(topic2Message.data()), topic2); // rvalue
|
||||||
|
ctx.restart();
|
||||||
|
ctx.run();
|
||||||
|
MockSession* p1 = (MockSession*)(session1.get());
|
||||||
|
EXPECT_EQ(p1->message, topic1Message);
|
||||||
|
MockSession* p2 = (MockSession*)(session2.get());
|
||||||
|
EXPECT_EQ(p2->message, topic2Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(SubscriptionMapTest, SubscriptionMapDeadRemoveSubscriber)
|
||||||
|
{
|
||||||
|
std::shared_ptr<WsBase> session1(new MockDeadSession(tagDecoratorFactory));
|
||||||
|
std::shared_ptr<WsBase> session2 =
|
||||||
|
std::make_shared<MockSession>(tagDecoratorFactory);
|
||||||
|
SubscriptionMap<std::string> subMap(ctx);
|
||||||
|
const std::string topic1 = "topic1";
|
||||||
|
const std::string topic2 = "topic2";
|
||||||
|
const std::string topic1Message = "topic1Message";
|
||||||
|
const std::string topic2Message = "topic2Message";
|
||||||
|
subMap.subscribe(session1, topic1);
|
||||||
|
subMap.subscribe(session2, topic2);
|
||||||
|
ctx.run();
|
||||||
|
EXPECT_EQ(subMap.count(), 2);
|
||||||
|
auto message1 = std::make_shared<Message>(topic1Message.data());
|
||||||
|
subMap.publish(message1, topic1); // lvalue
|
||||||
|
subMap.publish(
|
||||||
|
std::make_shared<Message>(topic2Message.data()), topic2); // rvalue
|
||||||
|
ctx.restart();
|
||||||
|
ctx.run();
|
||||||
|
MockDeadSession* p1 = (MockDeadSession*)(session1.get());
|
||||||
|
EXPECT_EQ(p1->dead(), true);
|
||||||
|
MockSession* p2 = (MockSession*)(session2.get());
|
||||||
|
EXPECT_EQ(p2->message, topic2Message);
|
||||||
|
subMap.publish(message1, topic1);
|
||||||
|
ctx.restart();
|
||||||
|
ctx.run();
|
||||||
|
EXPECT_EQ(subMap.count(), 1);
|
||||||
|
}
|
||||||
@@ -30,7 +30,6 @@
|
|||||||
#include <optional>
|
#include <optional>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
using namespace testing;
|
|
||||||
using namespace clio;
|
using namespace clio;
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,7 @@
|
|||||||
|
|
||||||
#include <boost/asio.hpp>
|
#include <boost/asio.hpp>
|
||||||
|
|
||||||
|
#include "MockBackend.h"
|
||||||
#include <gtest/gtest.h>
|
#include <gtest/gtest.h>
|
||||||
#include <log/Logger.h>
|
#include <log/Logger.h>
|
||||||
|
|
||||||
@@ -154,3 +155,25 @@ struct SyncAsioContextTest : public NoLoggerFixture
|
|||||||
protected:
|
protected:
|
||||||
boost::asio::io_context ctx;
|
boost::asio::io_context ctx;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Fixture with an mock backend
|
||||||
|
*/
|
||||||
|
struct MockBackendTest : public NoLoggerFixture
|
||||||
|
{
|
||||||
|
void
|
||||||
|
SetUp() override
|
||||||
|
{
|
||||||
|
NoLoggerFixture::SetUp();
|
||||||
|
clio::Config cfg;
|
||||||
|
mockBackendPtr = std::make_shared<MockBackend>(cfg);
|
||||||
|
}
|
||||||
|
void
|
||||||
|
TearDown() override
|
||||||
|
{
|
||||||
|
mockBackendPtr.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
std::shared_ptr<BackendInterface> mockBackendPtr;
|
||||||
|
};
|
||||||
|
|||||||
203
unittests/util/MockBackend.h
Normal file
203
unittests/util/MockBackend.h
Normal file
@@ -0,0 +1,203 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
|
/*
|
||||||
|
This file is part of clio: https://github.com/XRPLF/clio
|
||||||
|
Copyright (c) 2023, the clio developers.
|
||||||
|
|
||||||
|
Permission to use, copy, modify, and distribute this software for any
|
||||||
|
purpose with or without fee is hereby granted, provided that the above
|
||||||
|
copyright notice and this permission notice appear in all copies.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
//==============================================================================
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <backend/BackendInterface.h>
|
||||||
|
#include <gmock/gmock.h>
|
||||||
|
|
||||||
|
using namespace Backend;
|
||||||
|
|
||||||
|
class MockBackend : public BackendInterface
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
MockBackend(clio::Config cfg) : BackendInterface(cfg)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
MOCK_METHOD(
|
||||||
|
std::optional<ripple::LedgerInfo>,
|
||||||
|
fetchLedgerBySequence,
|
||||||
|
(std::uint32_t const sequence, boost::asio::yield_context& yield),
|
||||||
|
(const, override));
|
||||||
|
|
||||||
|
MOCK_METHOD(
|
||||||
|
std::optional<ripple::LedgerInfo>,
|
||||||
|
fetchLedgerByHash,
|
||||||
|
(ripple::uint256 const& hash, boost::asio::yield_context& yield),
|
||||||
|
(const, override));
|
||||||
|
|
||||||
|
MOCK_METHOD(
|
||||||
|
std::optional<std::uint32_t>,
|
||||||
|
fetchLatestLedgerSequence,
|
||||||
|
(boost::asio::yield_context & yield),
|
||||||
|
(const, override));
|
||||||
|
|
||||||
|
MOCK_METHOD(
|
||||||
|
std::optional<TransactionAndMetadata>,
|
||||||
|
fetchTransaction,
|
||||||
|
(ripple::uint256 const& hash, boost::asio::yield_context& yield),
|
||||||
|
(const, override));
|
||||||
|
|
||||||
|
MOCK_METHOD(
|
||||||
|
std::vector<TransactionAndMetadata>,
|
||||||
|
fetchTransactions,
|
||||||
|
(std::vector<ripple::uint256> const& hashes,
|
||||||
|
boost::asio::yield_context& yield),
|
||||||
|
(const, override));
|
||||||
|
|
||||||
|
MOCK_METHOD(
|
||||||
|
TransactionsAndCursor,
|
||||||
|
fetchAccountTransactions,
|
||||||
|
(ripple::AccountID const& account,
|
||||||
|
std::uint32_t const limit,
|
||||||
|
bool forward,
|
||||||
|
std::optional<TransactionsCursor> const& cursor,
|
||||||
|
boost::asio::yield_context& yield),
|
||||||
|
(const, override));
|
||||||
|
|
||||||
|
MOCK_METHOD(
|
||||||
|
std::vector<TransactionAndMetadata>,
|
||||||
|
fetchAllTransactionsInLedger,
|
||||||
|
(std::uint32_t const ledgerSequence, boost::asio::yield_context& yield),
|
||||||
|
(const, override));
|
||||||
|
|
||||||
|
MOCK_METHOD(
|
||||||
|
std::vector<ripple::uint256>,
|
||||||
|
fetchAllTransactionHashesInLedger,
|
||||||
|
(std::uint32_t const ledgerSequence, boost::asio::yield_context& yield),
|
||||||
|
(const, override));
|
||||||
|
|
||||||
|
MOCK_METHOD(
|
||||||
|
std::optional<NFT>,
|
||||||
|
fetchNFT,
|
||||||
|
(ripple::uint256 const& tokenID,
|
||||||
|
std::uint32_t const ledgerSequence,
|
||||||
|
boost::asio::yield_context& yieldd),
|
||||||
|
(const, override));
|
||||||
|
|
||||||
|
MOCK_METHOD(
|
||||||
|
TransactionsAndCursor,
|
||||||
|
fetchNFTTransactions,
|
||||||
|
(ripple::uint256 const& tokenID,
|
||||||
|
std::uint32_t const limit,
|
||||||
|
bool const forward,
|
||||||
|
std::optional<TransactionsCursor> const& cursorIn,
|
||||||
|
boost::asio::yield_context& yield),
|
||||||
|
(const, override));
|
||||||
|
|
||||||
|
MOCK_METHOD(
|
||||||
|
std::vector<Blob>,
|
||||||
|
doFetchLedgerObjects,
|
||||||
|
(std::vector<ripple::uint256> const& key,
|
||||||
|
std::uint32_t const sequence,
|
||||||
|
boost::asio::yield_context& yield),
|
||||||
|
(const, override));
|
||||||
|
|
||||||
|
MOCK_METHOD(
|
||||||
|
std::optional<Blob>,
|
||||||
|
doFetchLedgerObject,
|
||||||
|
(ripple::uint256 const& key,
|
||||||
|
std::uint32_t const sequence,
|
||||||
|
boost::asio::yield_context& yield),
|
||||||
|
(const, override));
|
||||||
|
|
||||||
|
MOCK_METHOD(
|
||||||
|
std::vector<LedgerObject>,
|
||||||
|
fetchLedgerDiff,
|
||||||
|
(std::uint32_t const ledgerSequence, boost::asio::yield_context& yield),
|
||||||
|
(const, override));
|
||||||
|
|
||||||
|
MOCK_METHOD(
|
||||||
|
std::optional<ripple::uint256>,
|
||||||
|
doFetchSuccessorKey,
|
||||||
|
(ripple::uint256 key,
|
||||||
|
std::uint32_t const ledgerSequence,
|
||||||
|
boost::asio::yield_context& yield),
|
||||||
|
(const, override));
|
||||||
|
|
||||||
|
MOCK_METHOD(
|
||||||
|
std::optional<LedgerRange>,
|
||||||
|
hardFetchLedgerRange,
|
||||||
|
(boost::asio::yield_context & yield),
|
||||||
|
(const, override));
|
||||||
|
|
||||||
|
MOCK_METHOD(
|
||||||
|
void,
|
||||||
|
writeLedger,
|
||||||
|
(ripple::LedgerInfo const& ledgerInfo, std::string&& ledgerHeader),
|
||||||
|
(override));
|
||||||
|
|
||||||
|
MOCK_METHOD(
|
||||||
|
void,
|
||||||
|
writeLedgerObject,
|
||||||
|
(std::string && key, std::uint32_t const seq, std::string&& blob),
|
||||||
|
(override));
|
||||||
|
|
||||||
|
MOCK_METHOD(
|
||||||
|
void,
|
||||||
|
writeTransaction,
|
||||||
|
(std::string && hash,
|
||||||
|
std::uint32_t const seq,
|
||||||
|
std::uint32_t const date,
|
||||||
|
std::string&& transaction,
|
||||||
|
std::string&& metadata),
|
||||||
|
(override));
|
||||||
|
|
||||||
|
MOCK_METHOD(void, writeNFTs, (std::vector<NFTsData> && blob), (override));
|
||||||
|
|
||||||
|
MOCK_METHOD(
|
||||||
|
void,
|
||||||
|
writeAccountTransactions,
|
||||||
|
(std::vector<AccountTransactionsData> && blob),
|
||||||
|
(override));
|
||||||
|
|
||||||
|
MOCK_METHOD(
|
||||||
|
void,
|
||||||
|
writeNFTTransactions,
|
||||||
|
(std::vector<NFTTransactionsData> && blob),
|
||||||
|
(override));
|
||||||
|
|
||||||
|
MOCK_METHOD(
|
||||||
|
void,
|
||||||
|
writeSuccessor,
|
||||||
|
(std::string && key, std::uint32_t const seq, std::string&& successor),
|
||||||
|
(override));
|
||||||
|
|
||||||
|
MOCK_METHOD(void, startWrites, (), (const, override));
|
||||||
|
|
||||||
|
MOCK_METHOD(
|
||||||
|
bool,
|
||||||
|
doOnlineDelete,
|
||||||
|
(std::uint32_t numLedgersToKeep, boost::asio::yield_context& yield),
|
||||||
|
(const, override));
|
||||||
|
|
||||||
|
MOCK_METHOD(bool, isTooBusy, (), (const, override));
|
||||||
|
|
||||||
|
MOCK_METHOD(void, open, (bool), (override));
|
||||||
|
|
||||||
|
MOCK_METHOD(void, close, (), (override));
|
||||||
|
|
||||||
|
MOCK_METHOD(
|
||||||
|
void,
|
||||||
|
doWriteLedgerObject,
|
||||||
|
(std::string && key, std::uint32_t const seq, std::string&& blob),
|
||||||
|
(override));
|
||||||
|
|
||||||
|
MOCK_METHOD(bool, doFinishWrites, (), (override));
|
||||||
|
};
|
||||||
48
unittests/util/MockWsBase.h
Normal file
48
unittests/util/MockWsBase.h
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
|
/*
|
||||||
|
This file is part of clio: https://github.com/XRPLF/clio
|
||||||
|
Copyright (c) 2023, the clio developers.
|
||||||
|
|
||||||
|
Permission to use, copy, modify, and distribute this software for any
|
||||||
|
purpose with or without fee is hereby granted, provided that the above
|
||||||
|
copyright notice and this permission notice appear in all copies.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
//==============================================================================
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <webserver/WsBase.h>
|
||||||
|
|
||||||
|
struct MockSession : public WsBase
|
||||||
|
{
|
||||||
|
std::string message;
|
||||||
|
void
|
||||||
|
send(std::shared_ptr<Message> msg_type) override
|
||||||
|
{
|
||||||
|
message += std::string(msg_type->data());
|
||||||
|
}
|
||||||
|
MockSession(util::TagDecoratorFactory const& factory) : WsBase(factory)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct MockDeadSession : public WsBase
|
||||||
|
{
|
||||||
|
void
|
||||||
|
send(std::shared_ptr<Message> msg_type) override
|
||||||
|
{
|
||||||
|
// err happen, the session should remove from subscribers
|
||||||
|
ec_.assign(2, boost::system::system_category());
|
||||||
|
}
|
||||||
|
MockDeadSession(util::TagDecoratorFactory const& factory) : WsBase(factory)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
};
|
||||||
245
unittests/util/TestObject.cpp
Normal file
245
unittests/util/TestObject.cpp
Normal file
@@ -0,0 +1,245 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
|
/*
|
||||||
|
This file is part of clio: https://github.com/XRPLF/clio
|
||||||
|
Copyright (c) 2023, the clio developers.
|
||||||
|
|
||||||
|
Permission to use, copy, modify, and distribute this software for any
|
||||||
|
purpose with or without fee is hereby granted, provided that the above
|
||||||
|
copyright notice and this permission notice appear in all copies.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
//==============================================================================
|
||||||
|
|
||||||
|
#include "TestObject.h"
|
||||||
|
|
||||||
|
#include <ripple/protocol/STArray.h>
|
||||||
|
#include <ripple/protocol/TER.h>
|
||||||
|
|
||||||
|
ripple::AccountID
|
||||||
|
GetAccountIDWithString(std::string_view id)
|
||||||
|
{
|
||||||
|
return ripple::parseBase58<ripple::AccountID>(std::string(id)).value();
|
||||||
|
}
|
||||||
|
|
||||||
|
ripple::LedgerInfo
|
||||||
|
CreateLedgerInfo(std::string_view ledgerHash, ripple::LedgerIndex seq)
|
||||||
|
{
|
||||||
|
auto ledgerinfo = ripple::LedgerInfo();
|
||||||
|
ledgerinfo.hash = ripple::uint256{ledgerHash};
|
||||||
|
ledgerinfo.seq = seq;
|
||||||
|
return ledgerinfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
ripple::STObject
|
||||||
|
CreateFeeSettingLedgerObject(
|
||||||
|
uint64_t base,
|
||||||
|
uint32_t reserveInc,
|
||||||
|
uint32_t reserveBase,
|
||||||
|
uint32_t refFeeUnit,
|
||||||
|
uint32_t flag)
|
||||||
|
{
|
||||||
|
ripple::STObject obj(ripple::sfFee);
|
||||||
|
obj.setFieldU16(ripple::sfLedgerEntryType, ripple::ltFEE_SETTINGS);
|
||||||
|
obj.setFieldU64(ripple::sfBaseFee, base);
|
||||||
|
obj.setFieldU32(ripple::sfReserveIncrement, reserveInc);
|
||||||
|
obj.setFieldU32(ripple::sfReserveBase, reserveBase);
|
||||||
|
obj.setFieldU32(ripple::sfReferenceFeeUnits, refFeeUnit);
|
||||||
|
obj.setFieldU32(ripple::sfFlags, flag);
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
ripple::Blob
|
||||||
|
CreateFeeSettingBlob(
|
||||||
|
uint64_t base,
|
||||||
|
uint32_t reserveInc,
|
||||||
|
uint32_t reserveBase,
|
||||||
|
uint32_t refFeeUnit,
|
||||||
|
uint32_t flag)
|
||||||
|
{
|
||||||
|
auto lo = CreateFeeSettingLedgerObject(
|
||||||
|
base, reserveInc, reserveBase, refFeeUnit, flag);
|
||||||
|
return lo.getSerializer().peekData();
|
||||||
|
}
|
||||||
|
|
||||||
|
ripple::STObject
|
||||||
|
CreatePaymentTransactionObject(
|
||||||
|
std::string_view accountId1,
|
||||||
|
std::string_view accountId2,
|
||||||
|
int amount,
|
||||||
|
int fee,
|
||||||
|
uint32_t seq)
|
||||||
|
{
|
||||||
|
ripple::STObject obj(ripple::sfTransaction);
|
||||||
|
obj.setFieldU16(ripple::sfTransactionType, ripple::ttPAYMENT);
|
||||||
|
auto account =
|
||||||
|
ripple::parseBase58<ripple::AccountID>(std::string(accountId1));
|
||||||
|
obj.setAccountID(ripple::sfAccount, account.value());
|
||||||
|
obj.setFieldAmount(ripple::sfAmount, ripple::STAmount(amount, false));
|
||||||
|
obj.setFieldAmount(ripple::sfFee, ripple::STAmount(fee, false));
|
||||||
|
auto account2 =
|
||||||
|
ripple::parseBase58<ripple::AccountID>(std::string(accountId2));
|
||||||
|
obj.setAccountID(ripple::sfDestination, account2.value());
|
||||||
|
obj.setFieldU32(ripple::sfSequence, seq);
|
||||||
|
const char* key = "test";
|
||||||
|
ripple::Slice slice(key, 4);
|
||||||
|
obj.setFieldVL(ripple::sfSigningPubKey, slice);
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
ripple::STObject
|
||||||
|
CreateAccountRootObject(
|
||||||
|
std::string_view accountId,
|
||||||
|
uint32_t flag,
|
||||||
|
uint32_t seq,
|
||||||
|
int balance,
|
||||||
|
uint32_t ownerCount,
|
||||||
|
std::string_view previousTxnID,
|
||||||
|
uint32_t previousTxnSeq)
|
||||||
|
{
|
||||||
|
ripple::STObject accountRoot(ripple::sfAccount);
|
||||||
|
accountRoot.setFieldU16(ripple::sfLedgerEntryType, ripple::ltACCOUNT_ROOT);
|
||||||
|
accountRoot.setFieldU32(ripple::sfFlags, flag);
|
||||||
|
accountRoot.setAccountID(
|
||||||
|
ripple::sfAccount, GetAccountIDWithString(accountId));
|
||||||
|
accountRoot.setFieldU32(ripple::sfSequence, seq);
|
||||||
|
accountRoot.setFieldAmount(
|
||||||
|
ripple::sfBalance, ripple::STAmount(balance, false));
|
||||||
|
accountRoot.setFieldU32(ripple::sfOwnerCount, ownerCount);
|
||||||
|
accountRoot.setFieldH256(
|
||||||
|
ripple::sfPreviousTxnID, ripple::uint256{previousTxnID});
|
||||||
|
accountRoot.setFieldU32(ripple::sfPreviousTxnLgrSeq, previousTxnSeq);
|
||||||
|
return accountRoot;
|
||||||
|
}
|
||||||
|
|
||||||
|
ripple::STObject
|
||||||
|
CreateCreateOfferTransactionObject(
|
||||||
|
std::string_view accountId,
|
||||||
|
int fee,
|
||||||
|
uint32_t seq,
|
||||||
|
std::string_view currency,
|
||||||
|
std::string_view issuer,
|
||||||
|
int takerGets,
|
||||||
|
int takerPays)
|
||||||
|
{
|
||||||
|
ripple::STObject obj(ripple::sfTransaction);
|
||||||
|
obj.setFieldU16(ripple::sfTransactionType, ripple::ttOFFER_CREATE);
|
||||||
|
auto account =
|
||||||
|
ripple::parseBase58<ripple::AccountID>(std::string(accountId));
|
||||||
|
obj.setAccountID(ripple::sfAccount, account.value());
|
||||||
|
auto amount = ripple::STAmount(fee, false);
|
||||||
|
obj.setFieldAmount(ripple::sfFee, amount);
|
||||||
|
obj.setFieldU32(ripple::sfSequence, seq);
|
||||||
|
// add amount
|
||||||
|
ripple::Issue issue1(
|
||||||
|
ripple::Currency{currency},
|
||||||
|
ripple::parseBase58<ripple::AccountID>(std::string(issuer)).value());
|
||||||
|
obj.setFieldAmount(
|
||||||
|
ripple::sfTakerGets, ripple::STAmount(issue1, takerGets));
|
||||||
|
obj.setFieldAmount(ripple::sfTakerPays, ripple::STAmount(takerPays, false));
|
||||||
|
|
||||||
|
auto key = "test";
|
||||||
|
ripple::Slice slice(key, 4);
|
||||||
|
obj.setFieldVL(ripple::sfSigningPubKey, slice);
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
ripple::Issue
|
||||||
|
GetIssue(std::string_view currency, std::string_view issuerId)
|
||||||
|
{
|
||||||
|
return ripple::Issue(
|
||||||
|
ripple::Currency{currency},
|
||||||
|
ripple::parseBase58<ripple::AccountID>(std::string(issuerId)).value());
|
||||||
|
}
|
||||||
|
|
||||||
|
ripple::STObject
|
||||||
|
CreateMetaDataForBookChange(
|
||||||
|
std::string_view currency,
|
||||||
|
std::string_view issueId,
|
||||||
|
uint32_t transactionIndex,
|
||||||
|
int finalTakerGets,
|
||||||
|
int perviousTakerGets,
|
||||||
|
int finalTakerPays,
|
||||||
|
int perviousTakerPays)
|
||||||
|
{
|
||||||
|
ripple::STObject finalFields(ripple::sfFinalFields);
|
||||||
|
ripple::Issue issue1 = GetIssue(currency, issueId);
|
||||||
|
finalFields.setFieldAmount(
|
||||||
|
ripple::sfTakerPays, ripple::STAmount(issue1, finalTakerPays));
|
||||||
|
finalFields.setFieldAmount(
|
||||||
|
ripple::sfTakerGets, ripple::STAmount(finalTakerGets, false));
|
||||||
|
ripple::STObject previousFields(ripple::sfPreviousFields);
|
||||||
|
previousFields.setFieldAmount(
|
||||||
|
ripple::sfTakerPays, ripple::STAmount(issue1, perviousTakerPays));
|
||||||
|
previousFields.setFieldAmount(
|
||||||
|
ripple::sfTakerGets, ripple::STAmount(perviousTakerGets, false));
|
||||||
|
ripple::STObject metaObj(ripple::sfTransactionMetaData);
|
||||||
|
ripple::STArray metaArray{1};
|
||||||
|
ripple::STObject node(ripple::sfModifiedNode);
|
||||||
|
node.setFieldU16(ripple::sfLedgerEntryType, ripple::ltOFFER);
|
||||||
|
node.emplace_back(std::move(finalFields));
|
||||||
|
node.emplace_back(std::move(previousFields));
|
||||||
|
metaArray.push_back(node);
|
||||||
|
metaObj.setFieldArray(ripple::sfAffectedNodes, metaArray);
|
||||||
|
metaObj.setFieldU8(ripple::sfTransactionResult, ripple::tesSUCCESS);
|
||||||
|
metaObj.setFieldU32(ripple::sfTransactionIndex, transactionIndex);
|
||||||
|
return metaObj;
|
||||||
|
}
|
||||||
|
|
||||||
|
ripple::STObject
|
||||||
|
CreateMetaDataForCreateOffer(
|
||||||
|
std::string_view currency,
|
||||||
|
std::string_view issueId,
|
||||||
|
uint32_t transactionIndex,
|
||||||
|
int finalTakerGets,
|
||||||
|
int finalTakerPays)
|
||||||
|
{
|
||||||
|
ripple::STObject finalFields(ripple::sfNewFields);
|
||||||
|
ripple::Issue issue1 = GetIssue(currency, issueId);
|
||||||
|
finalFields.setFieldAmount(
|
||||||
|
ripple::sfTakerPays, ripple::STAmount(issue1, finalTakerPays));
|
||||||
|
finalFields.setFieldAmount(
|
||||||
|
ripple::sfTakerGets, ripple::STAmount(finalTakerGets, false));
|
||||||
|
ripple::STObject metaObj(ripple::sfTransactionMetaData);
|
||||||
|
ripple::STArray metaArray{1};
|
||||||
|
ripple::STObject node(ripple::sfCreatedNode);
|
||||||
|
node.setFieldU16(ripple::sfLedgerEntryType, ripple::ltOFFER);
|
||||||
|
node.emplace_back(std::move(finalFields));
|
||||||
|
metaArray.push_back(node);
|
||||||
|
metaObj.setFieldArray(ripple::sfAffectedNodes, metaArray);
|
||||||
|
metaObj.setFieldU8(ripple::sfTransactionResult, ripple::tesSUCCESS);
|
||||||
|
metaObj.setFieldU32(ripple::sfTransactionIndex, transactionIndex);
|
||||||
|
return metaObj;
|
||||||
|
}
|
||||||
|
|
||||||
|
ripple::STObject
|
||||||
|
CreateMetaDataForCancelOffer(
|
||||||
|
std::string_view currency,
|
||||||
|
std::string_view issueId,
|
||||||
|
uint32_t transactionIndex,
|
||||||
|
int finalTakerGets,
|
||||||
|
int finalTakerPays)
|
||||||
|
{
|
||||||
|
ripple::STObject finalFields(ripple::sfFinalFields);
|
||||||
|
ripple::Issue issue1 = GetIssue(currency, issueId);
|
||||||
|
finalFields.setFieldAmount(
|
||||||
|
ripple::sfTakerPays, ripple::STAmount(issue1, finalTakerPays));
|
||||||
|
finalFields.setFieldAmount(
|
||||||
|
ripple::sfTakerGets, ripple::STAmount(finalTakerGets, false));
|
||||||
|
ripple::STObject metaObj(ripple::sfTransactionMetaData);
|
||||||
|
ripple::STArray metaArray{1};
|
||||||
|
ripple::STObject node(ripple::sfDeletedNode);
|
||||||
|
node.setFieldU16(ripple::sfLedgerEntryType, ripple::ltOFFER);
|
||||||
|
node.emplace_back(std::move(finalFields));
|
||||||
|
metaArray.push_back(node);
|
||||||
|
metaObj.setFieldArray(ripple::sfAffectedNodes, metaArray);
|
||||||
|
metaObj.setFieldU8(ripple::sfTransactionResult, ripple::tesSUCCESS);
|
||||||
|
metaObj.setFieldU32(ripple::sfTransactionIndex, transactionIndex);
|
||||||
|
return metaObj;
|
||||||
|
}
|
||||||
137
unittests/util/TestObject.h
Normal file
137
unittests/util/TestObject.h
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
|
/*
|
||||||
|
This file is part of clio: https://github.com/XRPLF/clio
|
||||||
|
Copyright (c) 2023, the clio developers.
|
||||||
|
|
||||||
|
Permission to use, copy, modify, and distribute this software for any
|
||||||
|
purpose with or without fee is hereby granted, provided that the above
|
||||||
|
copyright notice and this permission notice appear in all copies.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
//==============================================================================
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <ripple/ledger/ReadView.h>
|
||||||
|
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Create AccountID object with string
|
||||||
|
*/
|
||||||
|
[[nodiscard]] ripple::AccountID
|
||||||
|
GetAccountIDWithString(std::string_view id);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Create a simple ledgerInfo object with only hash and seq
|
||||||
|
*/
|
||||||
|
[[nodiscard]] ripple::LedgerInfo
|
||||||
|
CreateLedgerInfo(std::string_view ledgerHash, ripple::LedgerIndex seq);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Create a FeeSetting ledger object
|
||||||
|
*/
|
||||||
|
[[nodiscard]] ripple::STObject
|
||||||
|
CreateFeeSettingLedgerObject(
|
||||||
|
uint64_t base,
|
||||||
|
uint32_t reserveInc,
|
||||||
|
uint32_t reserveBase,
|
||||||
|
uint32_t refFeeUnit,
|
||||||
|
uint32_t flag);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Create a FeeSetting ledger object and return its blob
|
||||||
|
*/
|
||||||
|
[[nodiscard]] ripple::Blob
|
||||||
|
CreateFeeSettingBlob(
|
||||||
|
uint64_t base,
|
||||||
|
uint32_t reserveInc,
|
||||||
|
uint32_t reserveBase,
|
||||||
|
uint32_t refFeeUnit,
|
||||||
|
uint32_t flag);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Create a payment transaction object
|
||||||
|
*/
|
||||||
|
[[nodiscard]] ripple::STObject
|
||||||
|
CreatePaymentTransactionObject(
|
||||||
|
std::string_view accountId1,
|
||||||
|
std::string_view accountId2,
|
||||||
|
int amount,
|
||||||
|
int fee,
|
||||||
|
uint32_t seq);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Create an account root ledger object
|
||||||
|
*/
|
||||||
|
[[nodiscard]] ripple::STObject
|
||||||
|
CreateAccountRootObject(
|
||||||
|
std::string_view accountId,
|
||||||
|
uint32_t flag,
|
||||||
|
uint32_t seq,
|
||||||
|
int balance,
|
||||||
|
uint32_t ownerCount,
|
||||||
|
std::string_view previousTxnID,
|
||||||
|
uint32_t previousTxnSeq);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Create a createoffer treansaction
|
||||||
|
* Taker pay is XRP
|
||||||
|
*/
|
||||||
|
[[nodiscard]] ripple::STObject
|
||||||
|
CreateCreateOfferTransactionObject(
|
||||||
|
std::string_view accountId,
|
||||||
|
int fee,
|
||||||
|
uint32_t seq,
|
||||||
|
std::string_view currency,
|
||||||
|
std::string_view issuer,
|
||||||
|
int takerGets,
|
||||||
|
int takerPays);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Return an issue object with given currency and issue account
|
||||||
|
*/
|
||||||
|
[[nodiscard]] ripple::Issue
|
||||||
|
GetIssue(std::string_view currency, std::string_view issuerId);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Create a offer change meta data
|
||||||
|
*/
|
||||||
|
[[nodiscard]] ripple::STObject
|
||||||
|
CreateMetaDataForBookChange(
|
||||||
|
std::string_view currency,
|
||||||
|
std::string_view issueId,
|
||||||
|
uint32_t transactionIndex,
|
||||||
|
int finalTakerGets,
|
||||||
|
int perviousTakerGets,
|
||||||
|
int finalTakerPays,
|
||||||
|
int perviousTakerPays);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Meta data for adding a offer object
|
||||||
|
*/
|
||||||
|
[[nodiscard]] ripple::STObject
|
||||||
|
CreateMetaDataForCreateOffer(
|
||||||
|
std::string_view currency,
|
||||||
|
std::string_view issueId,
|
||||||
|
uint32_t transactionIndex,
|
||||||
|
int finalTakerGets,
|
||||||
|
int finalTakerPays);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Meta data for removing a offer object
|
||||||
|
*/
|
||||||
|
[[nodiscard]] ripple::STObject
|
||||||
|
CreateMetaDataForCancelOffer(
|
||||||
|
std::string_view currency,
|
||||||
|
std::string_view issueId,
|
||||||
|
uint32_t transactionIndex,
|
||||||
|
int finalTakerGets,
|
||||||
|
int finalTakerPays);
|
||||||
Reference in New Issue
Block a user