mirror of
https://github.com/XRPLF/clio.git
synced 2025-11-20 19:56:00 +00:00
@@ -106,6 +106,9 @@ if(BUILD_TESTS)
|
||||
unittests/Config.cpp
|
||||
unittests/ProfilerTest.cpp
|
||||
unittests/DOSGuard.cpp
|
||||
unittests/SubscriptionTest.cpp
|
||||
unittests/SubscriptionManagerTest.cpp
|
||||
unittests/util/TestObject.cpp
|
||||
unittests/rpc/ErrorTests.cpp
|
||||
unittests/rpc/BaseTests.cpp
|
||||
unittests/rpc/handlers/TestHandlerTests.cpp
|
||||
|
||||
@@ -21,57 +21,6 @@
|
||||
#include <subscriptions/SubscriptionManager.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
|
||||
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
|
||||
getLedgerPubMessage(
|
||||
ripple::LedgerInfo const& lgrInfo,
|
||||
@@ -345,8 +245,6 @@ SubscriptionManager::pubTransaction(
|
||||
|
||||
for (auto const& node : meta->getNodes())
|
||||
{
|
||||
if (!node.isFieldPresent(ripple::sfLedgerEntryType))
|
||||
assert(false);
|
||||
if (node.getFieldU16(ripple::sfLedgerEntryType) == ripple::ltOFFER)
|
||||
{
|
||||
ripple::SField const* field = nullptr;
|
||||
@@ -388,9 +286,6 @@ SubscriptionManager::pubBookChanges(
|
||||
ripple::LedgerInfo const& lgrInfo,
|
||||
std::vector<Backend::TransactionAndMetadata> const& transactions)
|
||||
{
|
||||
if (bookChangesSubscribers_.empty())
|
||||
return;
|
||||
|
||||
auto const json = RPC::computeBookChanges(lgrInfo, transactions);
|
||||
auto const bookChangesMsg =
|
||||
std::make_shared<Message>(boost::json::serialize(json));
|
||||
|
||||
@@ -95,7 +95,7 @@ public:
|
||||
unsubscribe(std::shared_ptr<WsBase> const& session, Key const& key);
|
||||
|
||||
void
|
||||
publish(std::shared_ptr<Message>& message, Key const& key);
|
||||
publish(std::shared_ptr<Message> const& message, Key const& key);
|
||||
|
||||
std::uint64_t
|
||||
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
|
||||
{
|
||||
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 <string>
|
||||
|
||||
using namespace testing;
|
||||
using namespace clio;
|
||||
using namespace std;
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
|
||||
#include <boost/asio.hpp>
|
||||
|
||||
#include "MockBackend.h"
|
||||
#include <gtest/gtest.h>
|
||||
#include <log/Logger.h>
|
||||
|
||||
@@ -154,3 +155,25 @@ struct SyncAsioContextTest : public NoLoggerFixture
|
||||
protected:
|
||||
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