mirror of
https://github.com/XRPLF/clio.git
synced 2025-12-06 17:27:58 +00:00
@@ -38,13 +38,16 @@ target_sources(
|
||||
# ETLng
|
||||
etlng/AmendmentBlockHandlerTests.cpp
|
||||
etlng/ExtractionTests.cpp
|
||||
etlng/ForwardingSourceTests.cpp
|
||||
etlng/GrpcSourceTests.cpp
|
||||
etlng/RegistryTests.cpp
|
||||
etlng/SchedulingTests.cpp
|
||||
etlng/TaskManagerTests.cpp
|
||||
etlng/LoadingTests.cpp
|
||||
etlng/LoadBalancerTests.cpp
|
||||
etlng/NetworkValidatedLedgersTests.cpp
|
||||
etlng/MonitorTests.cpp
|
||||
etlng/SourceImplTests.cpp
|
||||
etlng/ext/CoreTests.cpp
|
||||
etlng/ext/CacheTests.cpp
|
||||
etlng/ext/NFTTests.cpp
|
||||
|
||||
@@ -17,11 +17,11 @@
|
||||
*/
|
||||
//==============================================================================
|
||||
#include "app/Stopper.hpp"
|
||||
#include "etl/ETLService.hpp"
|
||||
#include "etl/LoadBalancer.hpp"
|
||||
#include "util/AsioContextTestFixture.hpp"
|
||||
#include "util/LoggerFixtures.hpp"
|
||||
#include "util/MockBackend.hpp"
|
||||
#include "util/MockETLService.hpp"
|
||||
#include "util/MockLoadBalancer.hpp"
|
||||
#include "util/MockPrometheus.hpp"
|
||||
#include "util/MockSubscriptionManager.hpp"
|
||||
#include "util/newconfig/ConfigDefinition.hpp"
|
||||
@@ -65,17 +65,11 @@ struct StopperMakeCallbackTest : util::prometheus::WithPrometheus, SyncAsioConte
|
||||
struct ServerMock : web::ng::ServerTag {
|
||||
MOCK_METHOD(void, stop, (boost::asio::yield_context), ());
|
||||
};
|
||||
struct LoadBalancerMock : etl::LoadBalancerTag {
|
||||
MOCK_METHOD(void, stop, (boost::asio::yield_context), ());
|
||||
};
|
||||
struct ETLServiceMock : etl::ETLServiceTag {
|
||||
MOCK_METHOD(void, stop, (), ());
|
||||
};
|
||||
|
||||
protected:
|
||||
testing::StrictMock<ServerMock> serverMock_;
|
||||
testing::StrictMock<LoadBalancerMock> loadBalancerMock_;
|
||||
testing::StrictMock<ETLServiceMock> etlServiceMock_;
|
||||
testing::StrictMock<MockNgLoadBalancer> loadBalancerMock_;
|
||||
testing::StrictMock<MockETLService> etlServiceMock_;
|
||||
testing::StrictMock<MockSubscriptionManager> subscriptionManagerMock_;
|
||||
testing::StrictMock<MockBackend> backendMock_{util::config::ClioConfigDefinition{}};
|
||||
boost::asio::io_context ioContextToStop_;
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
//==============================================================================
|
||||
|
||||
#include "etl/ETLState.hpp"
|
||||
#include "etl/Source.hpp"
|
||||
#include "rpc/Errors.hpp"
|
||||
#include "util/LoggerFixtures.hpp"
|
||||
#include "util/MockSource.hpp"
|
||||
|
||||
@@ -92,7 +92,7 @@ TEST_F(GrpcSourceTests, fetchLedgerNoStub)
|
||||
TEST_F(GrpcSourceTests, loadInitialLedgerNoStub)
|
||||
{
|
||||
GrpcSource wrongGrpcSource{"wrong", "wrong", mockBackend_};
|
||||
auto const [data, success] = wrongGrpcSource.loadInitialLedger(0, 0, false);
|
||||
auto const [data, success] = wrongGrpcSource.loadInitialLedger(0, 0);
|
||||
EXPECT_TRUE(data.empty());
|
||||
EXPECT_FALSE(success);
|
||||
}
|
||||
@@ -101,7 +101,6 @@ struct GrpcSourceLoadInitialLedgerTests : GrpcSourceTests {
|
||||
protected:
|
||||
uint32_t const sequence_ = 123;
|
||||
uint32_t const numMarkers_ = 4;
|
||||
bool const cacheOnly_ = false;
|
||||
};
|
||||
|
||||
TEST_F(GrpcSourceLoadInitialLedgerTests, GetLedgerDataFailed)
|
||||
@@ -116,7 +115,7 @@ TEST_F(GrpcSourceLoadInitialLedgerTests, GetLedgerDataFailed)
|
||||
return grpc::Status{grpc::StatusCode::NOT_FOUND, "Not found"};
|
||||
});
|
||||
|
||||
auto const [data, success] = grpcSource_.loadInitialLedger(sequence_, numMarkers_, cacheOnly_);
|
||||
auto const [data, success] = grpcSource_.loadInitialLedger(sequence_, numMarkers_);
|
||||
EXPECT_TRUE(data.empty());
|
||||
EXPECT_FALSE(success);
|
||||
}
|
||||
@@ -147,7 +146,7 @@ TEST_F(GrpcSourceLoadInitialLedgerTests, worksFine)
|
||||
EXPECT_CALL(*mockBackend_, writeNFTs).Times(numMarkers_);
|
||||
EXPECT_CALL(*mockBackend_, writeLedgerObject).Times(numMarkers_);
|
||||
|
||||
auto const [data, success] = grpcSource_.loadInitialLedger(sequence_, numMarkers_, cacheOnly_);
|
||||
auto const [data, success] = grpcSource_.loadInitialLedger(sequence_, numMarkers_);
|
||||
|
||||
EXPECT_TRUE(success);
|
||||
EXPECT_EQ(data, std::vector<std::string>(4, keyStr));
|
||||
|
||||
@@ -436,56 +436,50 @@ struct LoadBalancerLoadInitialLedgerTests : LoadBalancerOnConnectHookTests {
|
||||
protected:
|
||||
uint32_t const sequence_ = 123;
|
||||
uint32_t const numMarkers_ = 16;
|
||||
bool const cacheOnly_ = true;
|
||||
std::pair<std::vector<std::string>, bool> const response_ = {{"1", "2", "3"}, true};
|
||||
};
|
||||
|
||||
TEST_F(LoadBalancerLoadInitialLedgerTests, load)
|
||||
{
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(0), hasLedger(sequence_)).WillOnce(Return(true));
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(0), loadInitialLedger(sequence_, numMarkers_, cacheOnly_))
|
||||
.WillOnce(Return(response_));
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(0), loadInitialLedger(sequence_, numMarkers_)).WillOnce(Return(response_));
|
||||
|
||||
EXPECT_EQ(loadBalancer_->loadInitialLedger(sequence_, cacheOnly_), response_.first);
|
||||
EXPECT_EQ(loadBalancer_->loadInitialLedger(sequence_), response_.first);
|
||||
}
|
||||
|
||||
TEST_F(LoadBalancerLoadInitialLedgerTests, load_source0DoesntHaveLedger)
|
||||
{
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(0), hasLedger(sequence_)).WillOnce(Return(false));
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(1), hasLedger(sequence_)).WillOnce(Return(true));
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(1), loadInitialLedger(sequence_, numMarkers_, cacheOnly_))
|
||||
.WillOnce(Return(response_));
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(1), loadInitialLedger(sequence_, numMarkers_)).WillOnce(Return(response_));
|
||||
|
||||
EXPECT_EQ(loadBalancer_->loadInitialLedger(sequence_, cacheOnly_), response_.first);
|
||||
EXPECT_EQ(loadBalancer_->loadInitialLedger(sequence_), response_.first);
|
||||
}
|
||||
|
||||
TEST_F(LoadBalancerLoadInitialLedgerTests, load_bothSourcesDontHaveLedger)
|
||||
{
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(0), hasLedger(sequence_)).Times(2).WillRepeatedly(Return(false));
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(1), hasLedger(sequence_)).WillOnce(Return(false)).WillOnce(Return(true));
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(1), loadInitialLedger(sequence_, numMarkers_, cacheOnly_))
|
||||
.WillOnce(Return(response_));
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(1), loadInitialLedger(sequence_, numMarkers_)).WillOnce(Return(response_));
|
||||
|
||||
EXPECT_EQ(loadBalancer_->loadInitialLedger(sequence_, cacheOnly_, std::chrono::milliseconds{1}), response_.first);
|
||||
EXPECT_EQ(loadBalancer_->loadInitialLedger(sequence_, std::chrono::milliseconds{1}), response_.first);
|
||||
}
|
||||
|
||||
TEST_F(LoadBalancerLoadInitialLedgerTests, load_source0ReturnsStatusFalse)
|
||||
{
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(0), hasLedger(sequence_)).WillOnce(Return(true));
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(0), loadInitialLedger(sequence_, numMarkers_, cacheOnly_))
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(0), loadInitialLedger(sequence_, numMarkers_))
|
||||
.WillOnce(Return(std::make_pair(std::vector<std::string>{}, false)));
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(1), hasLedger(sequence_)).WillOnce(Return(true));
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(1), loadInitialLedger(sequence_, numMarkers_, cacheOnly_))
|
||||
.WillOnce(Return(response_));
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(1), loadInitialLedger(sequence_, numMarkers_)).WillOnce(Return(response_));
|
||||
|
||||
EXPECT_EQ(loadBalancer_->loadInitialLedger(sequence_, cacheOnly_), response_.first);
|
||||
EXPECT_EQ(loadBalancer_->loadInitialLedger(sequence_), response_.first);
|
||||
}
|
||||
|
||||
struct LoadBalancerLoadInitialLedgerCustomNumMarkersTests : LoadBalancerConstructorTests {
|
||||
protected:
|
||||
uint32_t const numMarkers_ = 16;
|
||||
uint32_t const sequence_ = 123;
|
||||
bool const cacheOnly_ = true;
|
||||
std::pair<std::vector<std::string>, bool> const response_ = {{"1", "2", "3"}, true};
|
||||
};
|
||||
|
||||
@@ -502,10 +496,9 @@ TEST_F(LoadBalancerLoadInitialLedgerCustomNumMarkersTests, loadInitialLedger)
|
||||
|
||||
util::Random::setSeed(0);
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(0), hasLedger(sequence_)).WillOnce(Return(true));
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(0), loadInitialLedger(sequence_, numMarkers_, cacheOnly_))
|
||||
.WillOnce(Return(response_));
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(0), loadInitialLedger(sequence_, numMarkers_)).WillOnce(Return(response_));
|
||||
|
||||
EXPECT_EQ(loadBalancer->loadInitialLedger(sequence_, cacheOnly_), response_.first);
|
||||
EXPECT_EQ(loadBalancer->loadInitialLedger(sequence_), response_.first);
|
||||
}
|
||||
|
||||
struct LoadBalancerFetchLegerTests : LoadBalancerOnConnectHookTests {
|
||||
|
||||
@@ -48,7 +48,7 @@ struct GrpcSourceMock {
|
||||
MOCK_METHOD(FetchLedgerReturnType, fetchLedger, (uint32_t, bool, bool));
|
||||
|
||||
using LoadLedgerReturnType = std::pair<std::vector<std::string>, bool>;
|
||||
MOCK_METHOD(LoadLedgerReturnType, loadInitialLedger, (uint32_t, uint32_t, bool));
|
||||
MOCK_METHOD(LoadLedgerReturnType, loadInitialLedger, (uint32_t, uint32_t));
|
||||
};
|
||||
|
||||
struct SubscriptionSourceMock {
|
||||
@@ -174,7 +174,7 @@ TEST_F(SourceImplTest, loadInitialLedger)
|
||||
uint32_t const ledgerSeq = 123;
|
||||
uint32_t const numMarkers = 3;
|
||||
|
||||
EXPECT_CALL(grpcSourceMock_, loadInitialLedger(ledgerSeq, numMarkers, false))
|
||||
EXPECT_CALL(grpcSourceMock_, loadInitialLedger(ledgerSeq, numMarkers))
|
||||
.WillOnce(Return(std::make_pair(std::vector<std::string>{}, true)));
|
||||
auto const [actualLedgers, actualSuccess] = source_.loadInitialLedger(ledgerSeq, numMarkers);
|
||||
|
||||
|
||||
196
tests/unit/etlng/ForwardingSourceTests.cpp
Normal file
196
tests/unit/etlng/ForwardingSourceTests.cpp
Normal file
@@ -0,0 +1,196 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of clio: https://github.com/XRPLF/clio
|
||||
Copyright (c) 2025, 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 "etlng/impl/ForwardingSource.hpp"
|
||||
#include "rpc/Errors.hpp"
|
||||
#include "util/AsioContextTestFixture.hpp"
|
||||
#include "util/TestWsServer.hpp"
|
||||
|
||||
#include <boost/asio/spawn.hpp>
|
||||
#include <boost/json/object.hpp>
|
||||
#include <boost/json/parse.hpp>
|
||||
#include <boost/json/serialize.hpp>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
using namespace etlng::impl;
|
||||
|
||||
struct ForwardingSourceNgTests : SyncAsioContextTest {
|
||||
protected:
|
||||
TestWsServer server_{ctx_, "0.0.0.0"};
|
||||
ForwardingSource forwardingSource_{
|
||||
"127.0.0.1",
|
||||
server_.port(),
|
||||
std::chrono::milliseconds{20},
|
||||
std::chrono::milliseconds{20}
|
||||
};
|
||||
};
|
||||
|
||||
TEST_F(ForwardingSourceNgTests, ConnectionFailed)
|
||||
{
|
||||
runSpawn([&](boost::asio::yield_context yield) {
|
||||
auto result = forwardingSource_.forwardToRippled({}, {}, {}, yield);
|
||||
ASSERT_FALSE(result);
|
||||
EXPECT_EQ(result.error(), rpc::ClioError::EtlConnectionError);
|
||||
});
|
||||
}
|
||||
|
||||
struct ForwardingSourceOperationsNgTests : ForwardingSourceNgTests {
|
||||
TestWsConnection
|
||||
serverConnection(boost::asio::yield_context yield)
|
||||
{
|
||||
// First connection attempt is SSL handshake so it will fail
|
||||
auto failedConnection = server_.acceptConnection(yield);
|
||||
[&]() { ASSERT_FALSE(failedConnection); }();
|
||||
|
||||
auto connection = server_.acceptConnection(yield);
|
||||
[&]() { ASSERT_TRUE(connection) << connection.error().message(); }();
|
||||
return std::move(connection).value();
|
||||
}
|
||||
|
||||
protected:
|
||||
std::string const message_ = R"({"data": "some_data"})";
|
||||
boost::json::object const reply_ = {{"reply", "some_reply"}};
|
||||
};
|
||||
|
||||
TEST_F(ForwardingSourceOperationsNgTests, XUserHeader)
|
||||
{
|
||||
std::string const xUserValue = "some_user";
|
||||
boost::asio::spawn(ctx_, [&](boost::asio::yield_context yield) {
|
||||
auto connection = serverConnection(yield);
|
||||
auto headers = connection.headers();
|
||||
ASSERT_FALSE(headers.empty());
|
||||
auto it = std::ranges::find_if(headers, [](auto const& header) {
|
||||
return std::holds_alternative<std::string>(header.name) && std::get<std::string>(header.name) == "X-User";
|
||||
});
|
||||
ASSERT_FALSE(it == headers.end());
|
||||
EXPECT_EQ(std::get<std::string>(it->name), "X-User");
|
||||
EXPECT_EQ(it->value, xUserValue);
|
||||
connection.close(yield);
|
||||
});
|
||||
|
||||
runSpawn([&](boost::asio::yield_context yield) {
|
||||
auto result =
|
||||
forwardingSource_.forwardToRippled(boost::json::parse(message_).as_object(), {}, xUserValue, yield);
|
||||
ASSERT_FALSE(result);
|
||||
EXPECT_EQ(result.error(), rpc::ClioError::EtlRequestError);
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(ForwardingSourceOperationsNgTests, ReadFailed)
|
||||
{
|
||||
boost::asio::spawn(ctx_, [&](boost::asio::yield_context yield) {
|
||||
auto connection = serverConnection(yield);
|
||||
connection.close(yield);
|
||||
});
|
||||
|
||||
runSpawn([&](boost::asio::yield_context yield) {
|
||||
auto result = forwardingSource_.forwardToRippled(boost::json::parse(message_).as_object(), {}, {}, yield);
|
||||
ASSERT_FALSE(result);
|
||||
EXPECT_EQ(result.error(), rpc::ClioError::EtlRequestError);
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(ForwardingSourceOperationsNgTests, ReadTimeout)
|
||||
{
|
||||
TestWsConnectionPtr connection;
|
||||
boost::asio::spawn(ctx_, [&](boost::asio::yield_context yield) {
|
||||
connection = std::make_unique<TestWsConnection>(serverConnection(yield));
|
||||
});
|
||||
|
||||
runSpawn([&](boost::asio::yield_context yield) {
|
||||
auto result = forwardingSource_.forwardToRippled(boost::json::parse(message_).as_object(), {}, {}, yield);
|
||||
ASSERT_FALSE(result);
|
||||
EXPECT_EQ(result.error(), rpc::ClioError::EtlRequestTimeout);
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(ForwardingSourceOperationsNgTests, ParseFailed)
|
||||
{
|
||||
boost::asio::spawn(ctx_, [&](boost::asio::yield_context yield) {
|
||||
auto connection = serverConnection(yield);
|
||||
|
||||
auto receivedMessage = connection.receive(yield);
|
||||
[&]() { ASSERT_TRUE(receivedMessage); }();
|
||||
EXPECT_EQ(boost::json::parse(*receivedMessage), boost::json::parse(message_)) << *receivedMessage;
|
||||
|
||||
auto sendError = connection.send("invalid_json", yield);
|
||||
[&]() { ASSERT_FALSE(sendError) << *sendError; }();
|
||||
|
||||
connection.close(yield);
|
||||
});
|
||||
|
||||
runSpawn([&](boost::asio::yield_context yield) {
|
||||
auto result = forwardingSource_.forwardToRippled(boost::json::parse(message_).as_object(), {}, {}, yield);
|
||||
ASSERT_FALSE(result);
|
||||
EXPECT_EQ(result.error(), rpc::ClioError::EtlInvalidResponse);
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(ForwardingSourceOperationsNgTests, GotNotAnObject)
|
||||
{
|
||||
boost::asio::spawn(ctx_, [&](boost::asio::yield_context yield) {
|
||||
auto connection = serverConnection(yield);
|
||||
|
||||
auto receivedMessage = connection.receive(yield);
|
||||
[&]() { ASSERT_TRUE(receivedMessage); }();
|
||||
EXPECT_EQ(boost::json::parse(*receivedMessage), boost::json::parse(message_)) << *receivedMessage;
|
||||
|
||||
auto sendError = connection.send(R"(["some_value"])", yield);
|
||||
|
||||
[&]() { ASSERT_FALSE(sendError) << *sendError; }();
|
||||
|
||||
connection.close(yield);
|
||||
});
|
||||
|
||||
runSpawn([&](boost::asio::yield_context yield) {
|
||||
auto result = forwardingSource_.forwardToRippled(boost::json::parse(message_).as_object(), {}, {}, yield);
|
||||
ASSERT_FALSE(result);
|
||||
EXPECT_EQ(result.error(), rpc::ClioError::EtlInvalidResponse);
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(ForwardingSourceOperationsNgTests, Success)
|
||||
{
|
||||
boost::asio::spawn(ctx_, [&](boost::asio::yield_context yield) {
|
||||
auto connection = serverConnection(yield);
|
||||
|
||||
auto receivedMessage = connection.receive(yield);
|
||||
[&]() { ASSERT_TRUE(receivedMessage); }();
|
||||
EXPECT_EQ(boost::json::parse(*receivedMessage), boost::json::parse(message_)) << *receivedMessage;
|
||||
|
||||
auto sendError = connection.send(boost::json::serialize(reply_), yield);
|
||||
[&]() { ASSERT_FALSE(sendError) << *sendError; }();
|
||||
});
|
||||
|
||||
runSpawn([&](boost::asio::yield_context yield) {
|
||||
auto result =
|
||||
forwardingSource_.forwardToRippled(boost::json::parse(message_).as_object(), "some_ip", {}, yield);
|
||||
[&]() { ASSERT_TRUE(result); }();
|
||||
auto expectedReply = reply_;
|
||||
expectedReply["forwarded"] = true;
|
||||
EXPECT_EQ(*result, expectedReply) << *result;
|
||||
});
|
||||
}
|
||||
821
tests/unit/etlng/LoadBalancerTests.cpp
Normal file
821
tests/unit/etlng/LoadBalancerTests.cpp
Normal file
@@ -0,0 +1,821 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of clio: https://github.com/XRPLF/clio
|
||||
Copyright (c) 2025, 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 "etlng/InitialLoadObserverInterface.hpp"
|
||||
#include "etlng/LoadBalancer.hpp"
|
||||
#include "etlng/Models.hpp"
|
||||
#include "etlng/Source.hpp"
|
||||
#include "rpc/Errors.hpp"
|
||||
#include "util/AsioContextTestFixture.hpp"
|
||||
#include "util/MockBackendTestFixture.hpp"
|
||||
#include "util/MockNetworkValidatedLedgers.hpp"
|
||||
#include "util/MockPrometheus.hpp"
|
||||
#include "util/MockSourceNg.hpp"
|
||||
#include "util/MockSubscriptionManager.hpp"
|
||||
#include "util/NameGenerator.hpp"
|
||||
#include "util/Random.hpp"
|
||||
#include "util/newconfig/Array.hpp"
|
||||
#include "util/newconfig/ConfigConstraints.hpp"
|
||||
#include "util/newconfig/ConfigDefinition.hpp"
|
||||
#include "util/newconfig/ConfigFileJson.hpp"
|
||||
#include "util/newconfig/ConfigValue.hpp"
|
||||
#include "util/newconfig/Types.hpp"
|
||||
|
||||
#include <boost/asio/io_context.hpp>
|
||||
#include <boost/asio/spawn.hpp>
|
||||
#include <boost/json/array.hpp>
|
||||
#include <boost/json/object.hpp>
|
||||
#include <boost/json/parse.hpp>
|
||||
#include <boost/json/value.hpp>
|
||||
#include <gmock/gmock.h>
|
||||
#include <grpcpp/support/status.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <org/xrpl/rpc/v1/get_ledger.pb.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <expected>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
using namespace etlng;
|
||||
using namespace util::config;
|
||||
using testing::Return;
|
||||
|
||||
namespace {
|
||||
|
||||
constinit auto const kTWO_SOURCES_LEDGER_RESPONSE = R"({
|
||||
"etl_sources": [
|
||||
{
|
||||
"ip": "127.0.0.1",
|
||||
"ws_port": "5005",
|
||||
"grpc_port": "source1"
|
||||
},
|
||||
{
|
||||
"ip": "127.0.0.1",
|
||||
"ws_port": "5005",
|
||||
"grpc_port": "source2"
|
||||
}
|
||||
]
|
||||
})";
|
||||
|
||||
constinit auto const kTHREE_SOURCES_LEDGER_RESPONSE = R"({
|
||||
"etl_sources": [
|
||||
{
|
||||
"ip": "127.0.0.1",
|
||||
"ws_port": "5005",
|
||||
"grpc_port": "source1"
|
||||
},
|
||||
{
|
||||
"ip": "127.0.0.1",
|
||||
"ws_port": "5005",
|
||||
"grpc_port": "source2"
|
||||
},
|
||||
{
|
||||
"ip": "127.0.0.1",
|
||||
"ws_port": "5005",
|
||||
"grpc_port": "source3"
|
||||
}
|
||||
]
|
||||
})";
|
||||
|
||||
inline ClioConfigDefinition
|
||||
getParseLoadBalancerConfig(boost::json::value val)
|
||||
{
|
||||
ClioConfigDefinition config{
|
||||
{{"forwarding.cache_timeout",
|
||||
ConfigValue{ConfigType::Double}.defaultValue(0.0).withConstraint(gValidatePositiveDouble)},
|
||||
{"forwarding.request_timeout",
|
||||
ConfigValue{ConfigType::Double}.defaultValue(10.0).withConstraint(gValidatePositiveDouble)},
|
||||
{"allow_no_etl", ConfigValue{ConfigType::Boolean}.defaultValue(false)},
|
||||
{"etl_sources.[].ip", Array{ConfigValue{ConfigType::String}.optional().withConstraint(gValidateIp)}},
|
||||
{"etl_sources.[].ws_port", Array{ConfigValue{ConfigType::String}.optional().withConstraint(gValidatePort)}},
|
||||
{"etl_sources.[].grpc_port", Array{ConfigValue{ConfigType::String}.optional()}},
|
||||
{"num_markers", ConfigValue{ConfigType::Integer}.optional().withConstraint(gValidateNumMarkers)}}
|
||||
};
|
||||
|
||||
auto const errors = config.parse(ConfigFileJson{val.as_object()});
|
||||
[&]() { ASSERT_FALSE(errors.has_value()); }();
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
struct InitialLoadObserverMock : etlng::InitialLoadObserverInterface {
|
||||
MOCK_METHOD(
|
||||
void,
|
||||
onInitialLoadGotMoreObjects,
|
||||
(uint32_t, std::vector<etlng::model::Object> const&, std::optional<std::string>),
|
||||
(override)
|
||||
);
|
||||
|
||||
void
|
||||
onInitialLoadGotMoreObjects(uint32_t seq, std::vector<etlng::model::Object> const& data)
|
||||
{
|
||||
onInitialLoadGotMoreObjects(seq, data, std::nullopt);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
struct LoadBalancerConstructorNgTests : util::prometheus::WithPrometheus, MockBackendTestStrict {
|
||||
std::unique_ptr<LoadBalancer>
|
||||
makeLoadBalancer()
|
||||
{
|
||||
auto const cfg = getParseLoadBalancerConfig(configJson_);
|
||||
return std::make_unique<LoadBalancer>(
|
||||
cfg,
|
||||
ioContext_,
|
||||
backend_,
|
||||
subscriptionManager_,
|
||||
networkManager_,
|
||||
[this](auto&&... args) -> SourcePtr { return sourceFactory_(std::forward<decltype(args)>(args)...); }
|
||||
);
|
||||
}
|
||||
|
||||
protected:
|
||||
StrictMockSubscriptionManagerSharedPtr subscriptionManager_;
|
||||
StrictMockNetworkValidatedLedgersPtr networkManager_;
|
||||
StrictMockSourceNgFactory sourceFactory_{2};
|
||||
boost::asio::io_context ioContext_;
|
||||
boost::json::value configJson_ = boost::json::parse(kTWO_SOURCES_LEDGER_RESPONSE);
|
||||
};
|
||||
|
||||
TEST_F(LoadBalancerConstructorNgTests, construct)
|
||||
{
|
||||
EXPECT_CALL(sourceFactory_, makeSource).Times(2);
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(0), forwardToRippled).WillOnce(Return(boost::json::object{}));
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(0), run);
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(1), forwardToRippled).WillOnce(Return(boost::json::object{}));
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(1), run);
|
||||
makeLoadBalancer();
|
||||
}
|
||||
|
||||
TEST_F(LoadBalancerConstructorNgTests, forwardingTimeoutPassedToSourceFactory)
|
||||
{
|
||||
auto const forwardingTimeout = 10;
|
||||
configJson_.as_object()["forwarding"] = boost::json::object{{"timeout", float{forwardingTimeout}}};
|
||||
EXPECT_CALL(
|
||||
sourceFactory_,
|
||||
makeSource(
|
||||
testing::_,
|
||||
testing::_,
|
||||
testing::_,
|
||||
testing::_,
|
||||
std::chrono::steady_clock::duration{std::chrono::seconds{forwardingTimeout}},
|
||||
testing::_,
|
||||
testing::_,
|
||||
testing::_
|
||||
)
|
||||
)
|
||||
.Times(2);
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(0), forwardToRippled).WillOnce(Return(boost::json::object{}));
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(0), run);
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(1), forwardToRippled).WillOnce(Return(boost::json::object{}));
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(1), run);
|
||||
makeLoadBalancer();
|
||||
}
|
||||
|
||||
TEST_F(LoadBalancerConstructorNgTests, fetchETLState_AllSourcesFail)
|
||||
{
|
||||
EXPECT_CALL(sourceFactory_, makeSource).Times(2);
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(0), forwardToRippled)
|
||||
.WillOnce(Return(std::unexpected{rpc::ClioError::EtlConnectionError}));
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(1), forwardToRippled)
|
||||
.WillOnce(Return(std::unexpected{rpc::ClioError::EtlConnectionError}));
|
||||
EXPECT_THROW({ makeLoadBalancer(); }, std::logic_error);
|
||||
}
|
||||
|
||||
TEST_F(LoadBalancerConstructorNgTests, fetchETLState_AllSourcesReturnError)
|
||||
{
|
||||
EXPECT_CALL(sourceFactory_, makeSource).Times(2);
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(0), forwardToRippled)
|
||||
.WillOnce(Return(boost::json::object{{"error", "some error"}}));
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(1), forwardToRippled)
|
||||
.WillOnce(Return(boost::json::object{{"error", "some error"}}));
|
||||
EXPECT_THROW({ makeLoadBalancer(); }, std::logic_error);
|
||||
}
|
||||
|
||||
TEST_F(LoadBalancerConstructorNgTests, fetchETLState_Source1Fails0OK)
|
||||
{
|
||||
EXPECT_CALL(sourceFactory_, makeSource).Times(2);
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(0), forwardToRippled).WillOnce(Return(boost::json::object{}));
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(1), forwardToRippled)
|
||||
.WillOnce(Return(std::unexpected{rpc::ClioError::EtlConnectionError}));
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(0), run);
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(1), run);
|
||||
makeLoadBalancer();
|
||||
}
|
||||
|
||||
TEST_F(LoadBalancerConstructorNgTests, fetchETLState_Source0Fails1OK)
|
||||
{
|
||||
EXPECT_CALL(sourceFactory_, makeSource).Times(2);
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(0), forwardToRippled)
|
||||
.WillOnce(Return(std::unexpected{rpc::ClioError::EtlConnectionError}));
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(1), forwardToRippled).WillOnce(Return(boost::json::object{}));
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(0), run);
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(1), run);
|
||||
makeLoadBalancer();
|
||||
}
|
||||
|
||||
TEST_F(LoadBalancerConstructorNgTests, fetchETLState_DifferentNetworkID)
|
||||
{
|
||||
auto const source1Json = boost::json::parse(R"({"result": {"info": {"network_id": 0}}})");
|
||||
auto const source2Json = boost::json::parse(R"({"result": {"info": {"network_id": 1}}})");
|
||||
|
||||
EXPECT_CALL(sourceFactory_, makeSource).Times(2);
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(0), forwardToRippled).WillOnce(Return(source1Json.as_object()));
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(1), forwardToRippled).WillOnce(Return(source2Json.as_object()));
|
||||
EXPECT_THROW({ makeLoadBalancer(); }, std::logic_error);
|
||||
}
|
||||
|
||||
TEST_F(LoadBalancerConstructorNgTests, fetchETLState_AllSourcesFailButAllowNoEtlIsTrue)
|
||||
{
|
||||
EXPECT_CALL(sourceFactory_, makeSource).Times(2);
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(0), forwardToRippled).WillOnce(Return(boost::json::object{}));
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(0), run);
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(1), forwardToRippled)
|
||||
.WillOnce(Return(std::unexpected{rpc::ClioError::EtlConnectionError}));
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(1), run);
|
||||
|
||||
configJson_.as_object()["allow_no_etl"] = true;
|
||||
makeLoadBalancer();
|
||||
}
|
||||
|
||||
TEST_F(LoadBalancerConstructorNgTests, fetchETLState_DifferentNetworkIDButAllowNoEtlIsTrue)
|
||||
{
|
||||
auto const source1Json = boost::json::parse(R"({"result": {"info": {"network_id": 0}}})");
|
||||
auto const source2Json = boost::json::parse(R"({"result": {"info": {"network_id": 1}}})");
|
||||
EXPECT_CALL(sourceFactory_, makeSource).Times(2);
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(0), forwardToRippled).WillOnce(Return(source1Json.as_object()));
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(0), run);
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(1), forwardToRippled).WillOnce(Return(source2Json.as_object()));
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(1), run);
|
||||
|
||||
configJson_.as_object()["allow_no_etl"] = true;
|
||||
makeLoadBalancer();
|
||||
}
|
||||
|
||||
struct LoadBalancerOnConnectHookNgTests : LoadBalancerConstructorNgTests {
|
||||
LoadBalancerOnConnectHookNgTests()
|
||||
{
|
||||
EXPECT_CALL(sourceFactory_, makeSource).Times(2);
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(0), forwardToRippled).WillOnce(Return(boost::json::object{}));
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(0), run);
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(1), forwardToRippled).WillOnce(Return(boost::json::object{}));
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(1), run);
|
||||
loadBalancer_ = makeLoadBalancer();
|
||||
}
|
||||
|
||||
protected:
|
||||
std::unique_ptr<LoadBalancer> loadBalancer_;
|
||||
};
|
||||
|
||||
TEST_F(LoadBalancerOnConnectHookNgTests, sourcesConnect)
|
||||
{
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(0), isConnected()).WillOnce(Return(true));
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(0), setForwarding(true));
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(1), setForwarding(false));
|
||||
sourceFactory_.callbacksAt(0).onConnect();
|
||||
sourceFactory_.callbacksAt(1).onConnect();
|
||||
}
|
||||
|
||||
TEST_F(LoadBalancerOnConnectHookNgTests, sourcesConnect_Source0IsNotConnected)
|
||||
{
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(0), isConnected()).WillOnce(Return(false));
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(0), setForwarding(false));
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(1), isConnected()).WillOnce(Return(false));
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(1), setForwarding(false));
|
||||
sourceFactory_.callbacksAt(0).onConnect(); // assuming it connects and disconnects immediately
|
||||
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(0), isConnected()).WillOnce(Return(false));
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(0), setForwarding(false));
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(1), isConnected()).WillOnce(Return(true));
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(1), setForwarding(true));
|
||||
sourceFactory_.callbacksAt(1).onConnect();
|
||||
|
||||
// Nothing is called on another connect
|
||||
sourceFactory_.callbacksAt(0).onConnect();
|
||||
}
|
||||
|
||||
TEST_F(LoadBalancerOnConnectHookNgTests, sourcesConnect_BothSourcesAreNotConnected)
|
||||
{
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(0), isConnected()).WillOnce(Return(false));
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(0), setForwarding(false));
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(1), isConnected()).WillOnce(Return(false));
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(1), setForwarding(false));
|
||||
sourceFactory_.callbacksAt(0).onConnect();
|
||||
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(0), isConnected()).WillOnce(Return(false));
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(0), setForwarding(false));
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(1), isConnected()).WillOnce(Return(false));
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(1), setForwarding(false));
|
||||
sourceFactory_.callbacksAt(1).onConnect();
|
||||
|
||||
// Then source 0 got connected
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(0), isConnected()).WillOnce(Return(true));
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(0), setForwarding(true));
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(1), setForwarding(false));
|
||||
sourceFactory_.callbacksAt(0).onConnect();
|
||||
}
|
||||
|
||||
struct LoadBalancerStopNgTests : LoadBalancerOnConnectHookNgTests, SyncAsioContextTest {};
|
||||
|
||||
TEST_F(LoadBalancerStopNgTests, stopCallsSourcesStop)
|
||||
{
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(0), stop);
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(1), stop);
|
||||
runSyncOperation([this](boost::asio::yield_context yield) { loadBalancer_->stop(yield); });
|
||||
}
|
||||
|
||||
struct LoadBalancerOnDisconnectHookNgTests : LoadBalancerOnConnectHookNgTests {
|
||||
LoadBalancerOnDisconnectHookNgTests()
|
||||
{
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(0), isConnected()).WillOnce(Return(true));
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(0), setForwarding(true));
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(1), setForwarding(false));
|
||||
sourceFactory_.callbacksAt(0).onConnect();
|
||||
|
||||
// nothing happens on source 1 connect
|
||||
sourceFactory_.callbacksAt(1).onConnect();
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(LoadBalancerOnDisconnectHookNgTests, source0Disconnects)
|
||||
{
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(0), isConnected()).WillOnce(Return(false));
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(0), setForwarding(false));
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(1), isConnected()).WillOnce(Return(true));
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(1), setForwarding(true));
|
||||
sourceFactory_.callbacksAt(0).onDisconnect(true);
|
||||
}
|
||||
|
||||
TEST_F(LoadBalancerOnDisconnectHookNgTests, source1Disconnects)
|
||||
{
|
||||
sourceFactory_.callbacksAt(1).onDisconnect(false);
|
||||
}
|
||||
|
||||
TEST_F(LoadBalancerOnDisconnectHookNgTests, source0DisconnectsAndConnectsBack)
|
||||
{
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(0), isConnected()).WillOnce(Return(false));
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(0), setForwarding(false));
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(1), isConnected()).WillOnce(Return(true));
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(1), setForwarding(true));
|
||||
sourceFactory_.callbacksAt(0).onDisconnect(true);
|
||||
|
||||
sourceFactory_.callbacksAt(0).onConnect();
|
||||
}
|
||||
|
||||
TEST_F(LoadBalancerOnDisconnectHookNgTests, source1DisconnectsAndConnectsBack)
|
||||
{
|
||||
sourceFactory_.callbacksAt(1).onDisconnect(false);
|
||||
sourceFactory_.callbacksAt(1).onConnect();
|
||||
}
|
||||
|
||||
TEST_F(LoadBalancerOnConnectHookNgTests, bothSourcesDisconnectAndConnectBack)
|
||||
{
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(0), isConnected()).WillOnce(Return(false));
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(0), setForwarding(false));
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(1), isConnected()).WillOnce(Return(false));
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(1), setForwarding(false));
|
||||
sourceFactory_.callbacksAt(0).onDisconnect(true);
|
||||
sourceFactory_.callbacksAt(1).onDisconnect(false);
|
||||
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(0), isConnected()).WillOnce(Return(true));
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(0), setForwarding(true));
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(1), setForwarding(false));
|
||||
sourceFactory_.callbacksAt(0).onConnect();
|
||||
|
||||
sourceFactory_.callbacksAt(1).onConnect();
|
||||
}
|
||||
|
||||
struct LoadBalancer3SourcesNgTests : LoadBalancerConstructorNgTests {
|
||||
LoadBalancer3SourcesNgTests()
|
||||
{
|
||||
sourceFactory_.setSourcesNumber(3);
|
||||
configJson_ = boost::json::parse(kTHREE_SOURCES_LEDGER_RESPONSE);
|
||||
|
||||
EXPECT_CALL(sourceFactory_, makeSource).Times(3);
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(0), forwardToRippled).WillOnce(Return(boost::json::object{}));
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(0), run);
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(1), forwardToRippled).WillOnce(Return(boost::json::object{}));
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(1), run);
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(2), forwardToRippled).WillOnce(Return(boost::json::object{}));
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(2), run);
|
||||
loadBalancer_ = makeLoadBalancer();
|
||||
}
|
||||
|
||||
protected:
|
||||
std::unique_ptr<LoadBalancer> loadBalancer_;
|
||||
};
|
||||
|
||||
TEST_F(LoadBalancer3SourcesNgTests, forwardingUpdate)
|
||||
{
|
||||
// Source 2 is connected first
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(0), isConnected()).WillOnce(Return(false));
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(0), setForwarding(false));
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(1), isConnected()).WillOnce(Return(false));
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(1), setForwarding(false));
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(2), isConnected()).WillOnce(Return(true));
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(2), setForwarding(true));
|
||||
sourceFactory_.callbacksAt(2).onConnect();
|
||||
|
||||
// Then source 0 and 1 are getting connected, but nothing should happen
|
||||
sourceFactory_.callbacksAt(0).onConnect();
|
||||
sourceFactory_.callbacksAt(1).onConnect();
|
||||
|
||||
// Source 0 got disconnected
|
||||
sourceFactory_.callbacksAt(0).onDisconnect(false);
|
||||
}
|
||||
|
||||
struct LoadBalancerLoadInitialLedgerNgTests : LoadBalancerOnConnectHookNgTests {
|
||||
LoadBalancerLoadInitialLedgerNgTests()
|
||||
{
|
||||
util::Random::setSeed(0);
|
||||
}
|
||||
|
||||
protected:
|
||||
uint32_t const sequence_ = 123;
|
||||
uint32_t const numMarkers_ = 16;
|
||||
std::pair<std::vector<std::string>, bool> const response_ = {{"1", "2", "3"}, true};
|
||||
testing::StrictMock<InitialLoadObserverMock> observer_;
|
||||
};
|
||||
|
||||
TEST_F(LoadBalancerLoadInitialLedgerNgTests, load)
|
||||
{
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(0), hasLedger(sequence_)).WillOnce(Return(true));
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(0), loadInitialLedger(sequence_, numMarkers_, testing::_))
|
||||
.WillOnce(Return(response_));
|
||||
|
||||
EXPECT_EQ(loadBalancer_->loadInitialLedger(sequence_, observer_, std::chrono::milliseconds{1}), response_.first);
|
||||
}
|
||||
|
||||
TEST_F(LoadBalancerLoadInitialLedgerNgTests, load_source0DoesntHaveLedger)
|
||||
{
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(0), hasLedger(sequence_)).WillOnce(Return(false));
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(1), hasLedger(sequence_)).WillOnce(Return(true));
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(1), loadInitialLedger(sequence_, numMarkers_, testing::_))
|
||||
.WillOnce(Return(response_));
|
||||
|
||||
EXPECT_EQ(loadBalancer_->loadInitialLedger(sequence_, observer_, std::chrono::milliseconds{1}), response_.first);
|
||||
}
|
||||
|
||||
TEST_F(LoadBalancerLoadInitialLedgerNgTests, load_bothSourcesDontHaveLedger)
|
||||
{
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(0), hasLedger(sequence_)).Times(2).WillRepeatedly(Return(false));
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(1), hasLedger(sequence_)).WillOnce(Return(false)).WillOnce(Return(true));
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(1), loadInitialLedger(sequence_, numMarkers_, testing::_))
|
||||
.WillOnce(Return(response_));
|
||||
|
||||
EXPECT_EQ(loadBalancer_->loadInitialLedger(sequence_, observer_, std::chrono::milliseconds{1}), response_.first);
|
||||
}
|
||||
|
||||
TEST_F(LoadBalancerLoadInitialLedgerNgTests, load_source0ReturnsStatusFalse)
|
||||
{
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(0), hasLedger(sequence_)).WillOnce(Return(true));
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(0), loadInitialLedger(sequence_, numMarkers_, testing::_))
|
||||
.WillOnce(Return(std::make_pair(std::vector<std::string>{}, false)));
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(1), hasLedger(sequence_)).WillOnce(Return(true));
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(1), loadInitialLedger(sequence_, numMarkers_, testing::_))
|
||||
.WillOnce(Return(response_));
|
||||
|
||||
EXPECT_EQ(loadBalancer_->loadInitialLedger(sequence_, observer_, std::chrono::milliseconds{1}), response_.first);
|
||||
}
|
||||
|
||||
struct LoadBalancerLoadInitialLedgerCustomNumMarkersNgTests : LoadBalancerConstructorNgTests {
|
||||
protected:
|
||||
uint32_t const numMarkers_ = 16;
|
||||
uint32_t const sequence_ = 123;
|
||||
std::pair<std::vector<std::string>, bool> const response_ = {{"1", "2", "3"}, true};
|
||||
testing::StrictMock<InitialLoadObserverMock> observer_;
|
||||
};
|
||||
|
||||
TEST_F(LoadBalancerLoadInitialLedgerCustomNumMarkersNgTests, loadInitialLedger)
|
||||
{
|
||||
configJson_.as_object()["num_markers"] = numMarkers_;
|
||||
|
||||
EXPECT_CALL(sourceFactory_, makeSource).Times(2);
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(0), forwardToRippled).WillOnce(Return(boost::json::object{}));
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(0), run);
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(1), forwardToRippled).WillOnce(Return(boost::json::object{}));
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(1), run);
|
||||
auto loadBalancer = makeLoadBalancer();
|
||||
|
||||
util::Random::setSeed(0);
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(0), hasLedger(sequence_)).WillOnce(Return(true));
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(0), loadInitialLedger(sequence_, numMarkers_, testing::_))
|
||||
.WillOnce(Return(response_));
|
||||
|
||||
EXPECT_EQ(loadBalancer->loadInitialLedger(sequence_, observer_, std::chrono::milliseconds{1}), response_.first);
|
||||
}
|
||||
|
||||
struct LoadBalancerFetchLegerNgTests : LoadBalancerOnConnectHookNgTests {
|
||||
LoadBalancerFetchLegerNgTests()
|
||||
{
|
||||
util::Random::setSeed(0);
|
||||
response_.second.set_validated(true);
|
||||
}
|
||||
|
||||
protected:
|
||||
uint32_t const sequence_ = 123;
|
||||
bool const getObjects_ = true;
|
||||
bool const getObjectNeighbors_ = false;
|
||||
std::pair<grpc::Status, org::xrpl::rpc::v1::GetLedgerResponse> response_ =
|
||||
std::make_pair(grpc::Status::OK, org::xrpl::rpc::v1::GetLedgerResponse{});
|
||||
};
|
||||
|
||||
TEST_F(LoadBalancerFetchLegerNgTests, fetch)
|
||||
{
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(0), hasLedger(sequence_)).WillOnce(Return(true));
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(0), fetchLedger(sequence_, getObjects_, getObjectNeighbors_))
|
||||
.WillOnce(Return(response_));
|
||||
|
||||
EXPECT_TRUE(loadBalancer_->fetchLedger(sequence_, getObjects_, getObjectNeighbors_).has_value());
|
||||
}
|
||||
|
||||
TEST_F(LoadBalancerFetchLegerNgTests, fetch_Source0ReturnsBadStatus)
|
||||
{
|
||||
auto source0Response = response_;
|
||||
source0Response.first = grpc::Status::CANCELLED;
|
||||
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(0), hasLedger(sequence_)).WillOnce(Return(true));
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(0), fetchLedger(sequence_, getObjects_, getObjectNeighbors_))
|
||||
.WillOnce(Return(source0Response));
|
||||
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(1), hasLedger(sequence_)).WillOnce(Return(true));
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(1), fetchLedger(sequence_, getObjects_, getObjectNeighbors_))
|
||||
.WillOnce(Return(response_));
|
||||
|
||||
EXPECT_TRUE(loadBalancer_->fetchLedger(sequence_, getObjects_, getObjectNeighbors_).has_value());
|
||||
}
|
||||
|
||||
TEST_F(LoadBalancerFetchLegerNgTests, fetch_Source0ReturnsNotValidated)
|
||||
{
|
||||
auto source0Response = response_;
|
||||
source0Response.second.set_validated(false);
|
||||
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(0), hasLedger(sequence_)).WillOnce(Return(true));
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(0), fetchLedger(sequence_, getObjects_, getObjectNeighbors_))
|
||||
.WillOnce(Return(source0Response));
|
||||
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(1), hasLedger(sequence_)).WillOnce(Return(true));
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(1), fetchLedger(sequence_, getObjects_, getObjectNeighbors_))
|
||||
.WillOnce(Return(response_));
|
||||
|
||||
EXPECT_TRUE(loadBalancer_->fetchLedger(sequence_, getObjects_, getObjectNeighbors_).has_value());
|
||||
}
|
||||
|
||||
TEST_F(LoadBalancerFetchLegerNgTests, fetch_bothSourcesFail)
|
||||
{
|
||||
auto badResponse = response_;
|
||||
badResponse.second.set_validated(false);
|
||||
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(0), hasLedger(sequence_)).Times(2).WillRepeatedly(Return(true));
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(0), fetchLedger(sequence_, getObjects_, getObjectNeighbors_))
|
||||
.WillOnce(Return(badResponse))
|
||||
.WillOnce(Return(response_));
|
||||
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(1), hasLedger(sequence_)).WillOnce(Return(true));
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(1), fetchLedger(sequence_, getObjects_, getObjectNeighbors_))
|
||||
.WillOnce(Return(badResponse));
|
||||
|
||||
EXPECT_TRUE(loadBalancer_->fetchLedger(sequence_, getObjects_, getObjectNeighbors_, std::chrono::milliseconds{1})
|
||||
.has_value());
|
||||
}
|
||||
|
||||
struct LoadBalancerForwardToRippledNgTests : LoadBalancerConstructorNgTests, SyncAsioContextTest {
|
||||
LoadBalancerForwardToRippledNgTests()
|
||||
{
|
||||
util::Random::setSeed(0);
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(0), forwardToRippled).WillOnce(Return(boost::json::object{}));
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(0), run);
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(1), forwardToRippled).WillOnce(Return(boost::json::object{}));
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(1), run);
|
||||
}
|
||||
|
||||
protected:
|
||||
boost::json::object const request_{{"command", "value"}};
|
||||
std::optional<std::string> const clientIP_ = "some_ip";
|
||||
boost::json::object const response_{{"response", "other_value"}};
|
||||
};
|
||||
|
||||
TEST_F(LoadBalancerForwardToRippledNgTests, forward)
|
||||
{
|
||||
EXPECT_CALL(sourceFactory_, makeSource).Times(2);
|
||||
auto loadBalancer = makeLoadBalancer();
|
||||
EXPECT_CALL(
|
||||
sourceFactory_.sourceAt(0),
|
||||
forwardToRippled(request_, clientIP_, LoadBalancer::kADMIN_FORWARDING_X_USER_VALUE, testing::_)
|
||||
)
|
||||
.WillOnce(Return(response_));
|
||||
|
||||
runSpawn([&](boost::asio::yield_context yield) {
|
||||
EXPECT_EQ(loadBalancer->forwardToRippled(request_, clientIP_, true, yield), response_);
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(LoadBalancerForwardToRippledNgTests, forwardWithXUserHeader)
|
||||
{
|
||||
EXPECT_CALL(sourceFactory_, makeSource).Times(2);
|
||||
auto loadBalancer = makeLoadBalancer();
|
||||
EXPECT_CALL(
|
||||
sourceFactory_.sourceAt(0),
|
||||
forwardToRippled(request_, clientIP_, LoadBalancer::kUSER_FORWARDING_X_USER_VALUE, testing::_)
|
||||
)
|
||||
.WillOnce(Return(response_));
|
||||
|
||||
runSpawn([&](boost::asio::yield_context yield) {
|
||||
EXPECT_EQ(loadBalancer->forwardToRippled(request_, clientIP_, false, yield), response_);
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(LoadBalancerForwardToRippledNgTests, source0Fails)
|
||||
{
|
||||
EXPECT_CALL(sourceFactory_, makeSource).Times(2);
|
||||
auto loadBalancer = makeLoadBalancer();
|
||||
EXPECT_CALL(
|
||||
sourceFactory_.sourceAt(0),
|
||||
forwardToRippled(request_, clientIP_, LoadBalancer::kUSER_FORWARDING_X_USER_VALUE, testing::_)
|
||||
)
|
||||
.WillOnce(Return(std::unexpected{rpc::ClioError::EtlConnectionError}));
|
||||
EXPECT_CALL(
|
||||
sourceFactory_.sourceAt(1),
|
||||
forwardToRippled(request_, clientIP_, LoadBalancer::kUSER_FORWARDING_X_USER_VALUE, testing::_)
|
||||
)
|
||||
.WillOnce(Return(response_));
|
||||
|
||||
runSpawn([&](boost::asio::yield_context yield) {
|
||||
EXPECT_EQ(loadBalancer->forwardToRippled(request_, clientIP_, false, yield), response_);
|
||||
});
|
||||
}
|
||||
|
||||
struct LoadBalancerForwardToRippledErrorNgTestBundle {
|
||||
std::string testName;
|
||||
rpc::ClioError firstSourceError;
|
||||
rpc::ClioError secondSourceError;
|
||||
rpc::ClioError responseExpectedError;
|
||||
};
|
||||
|
||||
struct LoadBalancerForwardToRippledErrorNgTests
|
||||
: LoadBalancerForwardToRippledNgTests,
|
||||
testing::WithParamInterface<LoadBalancerForwardToRippledErrorNgTestBundle> {};
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(
|
||||
LoadBalancerForwardToRippledErrorNgTests,
|
||||
LoadBalancerForwardToRippledErrorNgTests,
|
||||
testing::Values(
|
||||
LoadBalancerForwardToRippledErrorNgTestBundle{
|
||||
"ConnectionError_RequestError",
|
||||
rpc::ClioError::EtlConnectionError,
|
||||
rpc::ClioError::EtlRequestError,
|
||||
rpc::ClioError::EtlRequestError
|
||||
},
|
||||
LoadBalancerForwardToRippledErrorNgTestBundle{
|
||||
"RequestError_RequestTimeout",
|
||||
rpc::ClioError::EtlRequestError,
|
||||
rpc::ClioError::EtlRequestTimeout,
|
||||
rpc::ClioError::EtlRequestTimeout
|
||||
},
|
||||
LoadBalancerForwardToRippledErrorNgTestBundle{
|
||||
"RequestTimeout_InvalidResponse",
|
||||
rpc::ClioError::EtlRequestTimeout,
|
||||
rpc::ClioError::EtlInvalidResponse,
|
||||
rpc::ClioError::EtlInvalidResponse
|
||||
},
|
||||
LoadBalancerForwardToRippledErrorNgTestBundle{
|
||||
"BothRequestTimeout",
|
||||
rpc::ClioError::EtlRequestTimeout,
|
||||
rpc::ClioError::EtlRequestTimeout,
|
||||
rpc::ClioError::EtlRequestTimeout
|
||||
},
|
||||
LoadBalancerForwardToRippledErrorNgTestBundle{
|
||||
"InvalidResponse_RequestError",
|
||||
rpc::ClioError::EtlInvalidResponse,
|
||||
rpc::ClioError::EtlRequestError,
|
||||
rpc::ClioError::EtlInvalidResponse
|
||||
}
|
||||
),
|
||||
tests::util::kNAME_GENERATOR
|
||||
);
|
||||
|
||||
TEST_P(LoadBalancerForwardToRippledErrorNgTests, bothSourcesFail)
|
||||
{
|
||||
EXPECT_CALL(sourceFactory_, makeSource).Times(2);
|
||||
auto loadBalancer = makeLoadBalancer();
|
||||
EXPECT_CALL(
|
||||
sourceFactory_.sourceAt(0),
|
||||
forwardToRippled(request_, clientIP_, LoadBalancer::kUSER_FORWARDING_X_USER_VALUE, testing::_)
|
||||
)
|
||||
.WillOnce(Return(std::unexpected{GetParam().firstSourceError}));
|
||||
EXPECT_CALL(
|
||||
sourceFactory_.sourceAt(1),
|
||||
forwardToRippled(request_, clientIP_, LoadBalancer::kUSER_FORWARDING_X_USER_VALUE, testing::_)
|
||||
)
|
||||
.WillOnce(Return(std::unexpected{GetParam().secondSourceError}));
|
||||
|
||||
runSpawn([&](boost::asio::yield_context yield) {
|
||||
auto const response = loadBalancer->forwardToRippled(request_, clientIP_, false, yield);
|
||||
ASSERT_FALSE(response);
|
||||
EXPECT_EQ(response.error(), GetParam().responseExpectedError);
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(LoadBalancerForwardToRippledNgTests, forwardingCacheEnabled)
|
||||
{
|
||||
configJson_.as_object()["forwarding"] = boost::json::object{{"cache_timeout", 10.}};
|
||||
EXPECT_CALL(sourceFactory_, makeSource).Times(2);
|
||||
auto loadBalancer = makeLoadBalancer();
|
||||
|
||||
auto const request = boost::json::object{{"command", "server_info"}};
|
||||
|
||||
EXPECT_CALL(
|
||||
sourceFactory_.sourceAt(0),
|
||||
forwardToRippled(request, clientIP_, LoadBalancer::kUSER_FORWARDING_X_USER_VALUE, testing::_)
|
||||
)
|
||||
.WillOnce(Return(response_));
|
||||
|
||||
runSpawn([&](boost::asio::yield_context yield) {
|
||||
EXPECT_EQ(loadBalancer->forwardToRippled(request, clientIP_, false, yield), response_);
|
||||
EXPECT_EQ(loadBalancer->forwardToRippled(request, clientIP_, false, yield), response_);
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(LoadBalancerForwardToRippledNgTests, forwardingCacheDisabledOnLedgerClosedHookCalled)
|
||||
{
|
||||
EXPECT_CALL(sourceFactory_, makeSource).Times(2);
|
||||
auto loadBalancer = makeLoadBalancer();
|
||||
EXPECT_NO_THROW(sourceFactory_.callbacksAt(0).onLedgerClosed());
|
||||
}
|
||||
|
||||
TEST_F(LoadBalancerForwardToRippledNgTests, onLedgerClosedHookInvalidatesCache)
|
||||
{
|
||||
configJson_.as_object()["forwarding"] = boost::json::object{{"cache_timeout", 10.}};
|
||||
EXPECT_CALL(sourceFactory_, makeSource).Times(2);
|
||||
auto loadBalancer = makeLoadBalancer();
|
||||
|
||||
auto const request = boost::json::object{{"command", "server_info"}};
|
||||
|
||||
EXPECT_CALL(
|
||||
sourceFactory_.sourceAt(0),
|
||||
forwardToRippled(request, clientIP_, LoadBalancer::kUSER_FORWARDING_X_USER_VALUE, testing::_)
|
||||
)
|
||||
.WillOnce(Return(response_));
|
||||
EXPECT_CALL(
|
||||
sourceFactory_.sourceAt(1),
|
||||
forwardToRippled(request, clientIP_, LoadBalancer::kUSER_FORWARDING_X_USER_VALUE, testing::_)
|
||||
)
|
||||
.WillOnce(Return(boost::json::object{}));
|
||||
|
||||
runSpawn([&](boost::asio::yield_context yield) {
|
||||
EXPECT_EQ(loadBalancer->forwardToRippled(request, clientIP_, false, yield), response_);
|
||||
EXPECT_EQ(loadBalancer->forwardToRippled(request, clientIP_, false, yield), response_);
|
||||
sourceFactory_.callbacksAt(0).onLedgerClosed();
|
||||
EXPECT_EQ(loadBalancer->forwardToRippled(request, clientIP_, false, yield), boost::json::object{});
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(LoadBalancerForwardToRippledNgTests, commandLineMissing)
|
||||
{
|
||||
EXPECT_CALL(sourceFactory_, makeSource).Times(2);
|
||||
auto loadBalancer = makeLoadBalancer();
|
||||
|
||||
auto const request = boost::json::object{{"command2", "server_info"}};
|
||||
|
||||
runSpawn([&](boost::asio::yield_context yield) {
|
||||
EXPECT_EQ(
|
||||
loadBalancer->forwardToRippled(request, clientIP_, false, yield).error(),
|
||||
rpc::ClioError::RpcCommandIsMissing
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
struct LoadBalancerToJsonNgTests : LoadBalancerOnConnectHookNgTests {};
|
||||
|
||||
TEST_F(LoadBalancerToJsonNgTests, toJson)
|
||||
{
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(0), toJson).WillOnce(Return(boost::json::object{{"source1", "value1"}}));
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(1), toJson).WillOnce(Return(boost::json::object{{"source2", "value2"}}));
|
||||
|
||||
auto const expectedJson =
|
||||
boost::json::array({boost::json::object{{"source1", "value1"}}, boost::json::object{{"source2", "value2"}}});
|
||||
EXPECT_EQ(loadBalancer_->toJson(), expectedJson);
|
||||
}
|
||||
223
tests/unit/etlng/SourceImplTests.cpp
Normal file
223
tests/unit/etlng/SourceImplTests.cpp
Normal file
@@ -0,0 +1,223 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of clio: https://github.com/XRPLF/clio
|
||||
Copyright (c) 2025, 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 "etlng/InitialLoadObserverInterface.hpp"
|
||||
#include "etlng/Models.hpp"
|
||||
#include "etlng/impl/SourceImpl.hpp"
|
||||
#include "rpc/Errors.hpp"
|
||||
|
||||
#include <boost/asio/io_context.hpp>
|
||||
#include <boost/asio/spawn.hpp>
|
||||
#include <boost/json/object.hpp>
|
||||
#include <boost/json/value_to.hpp>
|
||||
#include <gmock/gmock.h>
|
||||
#include <grpcpp/support/status.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <org/xrpl/rpc/v1/get_ledger.pb.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
using namespace etlng::impl;
|
||||
|
||||
using testing::Return;
|
||||
using testing::StrictMock;
|
||||
|
||||
namespace {
|
||||
|
||||
struct GrpcSourceMock {
|
||||
using FetchLedgerReturnType = std::pair<grpc::Status, org::xrpl::rpc::v1::GetLedgerResponse>;
|
||||
MOCK_METHOD(FetchLedgerReturnType, fetchLedger, (uint32_t, bool, bool));
|
||||
|
||||
using LoadLedgerReturnType = std::pair<std::vector<std::string>, bool>;
|
||||
MOCK_METHOD(LoadLedgerReturnType, loadInitialLedger, (uint32_t, uint32_t, etlng::InitialLoadObserverInterface&));
|
||||
};
|
||||
|
||||
struct SubscriptionSourceMock {
|
||||
MOCK_METHOD(void, run, ());
|
||||
MOCK_METHOD(bool, hasLedger, (uint32_t), (const));
|
||||
MOCK_METHOD(bool, isConnected, (), (const));
|
||||
MOCK_METHOD(void, setForwarding, (bool));
|
||||
MOCK_METHOD(std::chrono::steady_clock::time_point, lastMessageTime, (), (const));
|
||||
MOCK_METHOD(std::string, validatedRange, (), (const));
|
||||
MOCK_METHOD(void, stop, (boost::asio::yield_context));
|
||||
};
|
||||
|
||||
struct ForwardingSourceMock {
|
||||
MOCK_METHOD(void, constructor, (std::string const&, std::string const&, std::chrono::steady_clock::duration));
|
||||
|
||||
using ForwardToRippledReturnType = std::expected<boost::json::object, rpc::ClioError>;
|
||||
using ClientIpOpt = std::optional<std::string>;
|
||||
MOCK_METHOD(
|
||||
ForwardToRippledReturnType,
|
||||
forwardToRippled,
|
||||
(boost::json::object const&, ClientIpOpt const&, std::string_view, boost::asio::yield_context),
|
||||
(const)
|
||||
);
|
||||
};
|
||||
|
||||
struct InitialLoadObserverMock : etlng::InitialLoadObserverInterface {
|
||||
MOCK_METHOD(
|
||||
void,
|
||||
onInitialLoadGotMoreObjects,
|
||||
(uint32_t, std::vector<etlng::model::Object> const&, std::optional<std::string>),
|
||||
(override)
|
||||
);
|
||||
|
||||
void
|
||||
onInitialLoadGotMoreObjects(uint32_t seq, std::vector<etlng::model::Object> const& data)
|
||||
{
|
||||
onInitialLoadGotMoreObjects(seq, data, std::nullopt);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
struct SourceImplNgTest : public ::testing::Test {
|
||||
protected:
|
||||
boost::asio::io_context ioc_;
|
||||
|
||||
StrictMock<GrpcSourceMock> grpcSourceMock_;
|
||||
std::shared_ptr<StrictMock<SubscriptionSourceMock>> subscriptionSourceMock_ =
|
||||
std::make_shared<StrictMock<SubscriptionSourceMock>>();
|
||||
StrictMock<ForwardingSourceMock> forwardingSourceMock_;
|
||||
|
||||
SourceImpl<
|
||||
StrictMock<GrpcSourceMock>&,
|
||||
std::shared_ptr<StrictMock<SubscriptionSourceMock>>,
|
||||
StrictMock<ForwardingSourceMock>&>
|
||||
source_{
|
||||
"some_ip",
|
||||
"some_ws_port",
|
||||
"some_grpc_port",
|
||||
grpcSourceMock_,
|
||||
subscriptionSourceMock_,
|
||||
forwardingSourceMock_
|
||||
};
|
||||
};
|
||||
|
||||
TEST_F(SourceImplNgTest, run)
|
||||
{
|
||||
EXPECT_CALL(*subscriptionSourceMock_, run());
|
||||
source_.run();
|
||||
}
|
||||
|
||||
TEST_F(SourceImplNgTest, stop)
|
||||
{
|
||||
EXPECT_CALL(*subscriptionSourceMock_, stop);
|
||||
boost::asio::io_context ctx;
|
||||
boost::asio::spawn(ctx, [&](boost::asio::yield_context yield) { source_.stop(yield); });
|
||||
ctx.run();
|
||||
}
|
||||
|
||||
TEST_F(SourceImplNgTest, isConnected)
|
||||
{
|
||||
EXPECT_CALL(*subscriptionSourceMock_, isConnected()).WillOnce(testing::Return(true));
|
||||
EXPECT_TRUE(source_.isConnected());
|
||||
}
|
||||
|
||||
TEST_F(SourceImplNgTest, setForwarding)
|
||||
{
|
||||
EXPECT_CALL(*subscriptionSourceMock_, setForwarding(true));
|
||||
source_.setForwarding(true);
|
||||
}
|
||||
|
||||
TEST_F(SourceImplNgTest, toJson)
|
||||
{
|
||||
EXPECT_CALL(*subscriptionSourceMock_, validatedRange()).WillOnce(Return(std::string("some_validated_range")));
|
||||
EXPECT_CALL(*subscriptionSourceMock_, isConnected()).WillOnce(Return(true));
|
||||
auto const lastMessageTime = std::chrono::steady_clock::now();
|
||||
EXPECT_CALL(*subscriptionSourceMock_, lastMessageTime()).WillOnce(Return(lastMessageTime));
|
||||
|
||||
auto const json = source_.toJson();
|
||||
|
||||
EXPECT_EQ(boost::json::value_to<std::string>(json.at("validated_range")), "some_validated_range");
|
||||
EXPECT_EQ(boost::json::value_to<std::string>(json.at("is_connected")), "1");
|
||||
EXPECT_EQ(boost::json::value_to<std::string>(json.at("ip")), "some_ip");
|
||||
EXPECT_EQ(boost::json::value_to<std::string>(json.at("ws_port")), "some_ws_port");
|
||||
EXPECT_EQ(boost::json::value_to<std::string>(json.at("grpc_port")), "some_grpc_port");
|
||||
auto lastMessageAgeStr = boost::json::value_to<std::string>(json.at("last_msg_age_seconds"));
|
||||
EXPECT_GE(std::stoi(lastMessageAgeStr), 0);
|
||||
}
|
||||
|
||||
TEST_F(SourceImplNgTest, toString)
|
||||
{
|
||||
EXPECT_CALL(*subscriptionSourceMock_, validatedRange()).WillOnce(Return(std::string("some_validated_range")));
|
||||
|
||||
auto const str = source_.toString();
|
||||
EXPECT_EQ(
|
||||
str,
|
||||
"{validated range: some_validated_range, ip: some_ip, web socket port: some_ws_port, grpc port: some_grpc_port}"
|
||||
);
|
||||
}
|
||||
|
||||
TEST_F(SourceImplNgTest, hasLedger)
|
||||
{
|
||||
uint32_t const ledgerSeq = 123;
|
||||
EXPECT_CALL(*subscriptionSourceMock_, hasLedger(ledgerSeq)).WillOnce(Return(true));
|
||||
EXPECT_TRUE(source_.hasLedger(ledgerSeq));
|
||||
}
|
||||
|
||||
TEST_F(SourceImplNgTest, fetchLedger)
|
||||
{
|
||||
uint32_t const ledgerSeq = 123;
|
||||
|
||||
EXPECT_CALL(grpcSourceMock_, fetchLedger(ledgerSeq, true, false));
|
||||
auto const [actualStatus, actualResponse] = source_.fetchLedger(ledgerSeq);
|
||||
|
||||
EXPECT_EQ(actualStatus.error_code(), grpc::StatusCode::OK);
|
||||
}
|
||||
|
||||
TEST_F(SourceImplNgTest, loadInitialLedger)
|
||||
{
|
||||
uint32_t const ledgerSeq = 123;
|
||||
uint32_t const numMarkers = 3;
|
||||
|
||||
auto observerMock = testing::StrictMock<InitialLoadObserverMock>();
|
||||
|
||||
EXPECT_CALL(grpcSourceMock_, loadInitialLedger(ledgerSeq, numMarkers, testing::_))
|
||||
.WillOnce(Return(std::make_pair(std::vector<std::string>{}, true)));
|
||||
auto const [actualLedgers, actualSuccess] = source_.loadInitialLedger(ledgerSeq, numMarkers, observerMock);
|
||||
|
||||
EXPECT_TRUE(actualLedgers.empty());
|
||||
EXPECT_TRUE(actualSuccess);
|
||||
}
|
||||
|
||||
TEST_F(SourceImplNgTest, forwardToRippled)
|
||||
{
|
||||
boost::json::object const request = {{"some_key", "some_value"}};
|
||||
std::optional<std::string> const clientIp = "some_client_ip";
|
||||
std::string_view xUserValue = "some_user";
|
||||
|
||||
EXPECT_CALL(forwardingSourceMock_, forwardToRippled(request, clientIp, xUserValue, testing::_))
|
||||
.WillOnce(Return(request));
|
||||
|
||||
boost::asio::io_context ioContext;
|
||||
boost::asio::spawn(ioContext, [&](boost::asio::yield_context yield) {
|
||||
auto const response = source_.forwardToRippled(request, clientIp, xUserValue, yield);
|
||||
EXPECT_EQ(response, request);
|
||||
});
|
||||
ioContext.run();
|
||||
}
|
||||
@@ -60,11 +60,7 @@ protected:
|
||||
ClioConfigDefinition const config_{{"log_tag_style", ConfigValue{ConfigType::String}.defaultValue("none")}};
|
||||
util::TagDecoratorFactory tagFactory_{config_};
|
||||
|
||||
rpc::impl::ForwardingProxy<MockLoadBalancer, MockCounters, MockHandlerProvider> proxy_{
|
||||
loadBalancer_,
|
||||
counters_,
|
||||
handlerProvider_
|
||||
};
|
||||
rpc::impl::ForwardingProxy<MockCounters, MockHandlerProvider> proxy_{loadBalancer_, counters_, handlerProvider_};
|
||||
};
|
||||
|
||||
struct ShouldForwardParamTestCaseBundle {
|
||||
|
||||
@@ -65,7 +65,7 @@ using namespace util::config;
|
||||
|
||||
namespace {
|
||||
constexpr auto kFORWARD_REPLY = R"JSON({
|
||||
"result":
|
||||
"result":
|
||||
{
|
||||
"status": "success",
|
||||
"forwarded": true
|
||||
@@ -200,16 +200,15 @@ TEST_P(RPCEngineFlowParameterTest, Test)
|
||||
{
|
||||
auto const& testBundle = GetParam();
|
||||
|
||||
std::shared_ptr<RPCEngine<MockLoadBalancer, MockCounters>> engine =
|
||||
RPCEngine<MockLoadBalancer, MockCounters>::makeRPCEngine(
|
||||
generateDefaultRPCEngineConfig(),
|
||||
backend_,
|
||||
mockLoadBalancerPtr_,
|
||||
dosGuard,
|
||||
queue,
|
||||
*mockCountersPtr_,
|
||||
handlerProvider
|
||||
);
|
||||
std::shared_ptr<RPCEngine<MockCounters>> engine = RPCEngine<MockCounters>::makeRPCEngine(
|
||||
generateDefaultRPCEngineConfig(),
|
||||
backend_,
|
||||
mockLoadBalancerPtr_,
|
||||
dosGuard,
|
||||
queue,
|
||||
*mockCountersPtr_,
|
||||
handlerProvider
|
||||
);
|
||||
|
||||
if (testBundle.forwarded) {
|
||||
EXPECT_CALL(*mockLoadBalancerPtr_, forwardToRippled)
|
||||
@@ -272,10 +271,9 @@ TEST_P(RPCEngineFlowParameterTest, Test)
|
||||
TEST_F(RPCEngineTest, ThrowDatabaseError)
|
||||
{
|
||||
auto const method = "subscribe";
|
||||
std::shared_ptr<RPCEngine<MockLoadBalancer, MockCounters>> engine =
|
||||
RPCEngine<MockLoadBalancer, MockCounters>::makeRPCEngine(
|
||||
cfg, backend_, mockLoadBalancerPtr_, dosGuard, queue, *mockCountersPtr_, handlerProvider
|
||||
);
|
||||
std::shared_ptr<RPCEngine<MockCounters>> engine = RPCEngine<MockCounters>::makeRPCEngine(
|
||||
cfg, backend_, mockLoadBalancerPtr_, dosGuard, queue, *mockCountersPtr_, handlerProvider
|
||||
);
|
||||
EXPECT_CALL(*backend_, isTooBusy).WillOnce(Return(false));
|
||||
EXPECT_CALL(*handlerProvider, getHandler(method)).WillOnce(Return(AnyHandler{tests::common::FailingHandlerFake{}}));
|
||||
EXPECT_CALL(*mockCountersPtr_, rpcErrored(method)).WillOnce(Throw(data::DatabaseTimeout{}));
|
||||
@@ -305,10 +303,9 @@ TEST_F(RPCEngineTest, ThrowDatabaseError)
|
||||
TEST_F(RPCEngineTest, ThrowException)
|
||||
{
|
||||
auto const method = "subscribe";
|
||||
std::shared_ptr<RPCEngine<MockLoadBalancer, MockCounters>> engine =
|
||||
RPCEngine<MockLoadBalancer, MockCounters>::makeRPCEngine(
|
||||
cfg, backend_, mockLoadBalancerPtr_, dosGuard, queue, *mockCountersPtr_, handlerProvider
|
||||
);
|
||||
std::shared_ptr<RPCEngine<MockCounters>> engine = RPCEngine<MockCounters>::makeRPCEngine(
|
||||
cfg, backend_, mockLoadBalancerPtr_, dosGuard, queue, *mockCountersPtr_, handlerProvider
|
||||
);
|
||||
EXPECT_CALL(*backend_, isTooBusy).WillOnce(Return(false));
|
||||
EXPECT_CALL(*handlerProvider, getHandler(method)).WillOnce(Return(AnyHandler{tests::common::FailingHandlerFake{}}));
|
||||
EXPECT_CALL(*mockCountersPtr_, rpcErrored(method)).WillOnce(Throw(std::exception{}));
|
||||
@@ -353,14 +350,14 @@ generateCacheTestValuesForParametersTest()
|
||||
.config = R"JSON({
|
||||
"server": {"max_queue_size": 2},
|
||||
"workers": 4,
|
||||
"rpc":
|
||||
"rpc":
|
||||
{"cache_timeout": 10}
|
||||
})JSON",
|
||||
.method = "server_info",
|
||||
.isAdmin = false,
|
||||
.expectedCacheEnabled = true},
|
||||
{.testName = "CacheDisabledWhenNoConfig",
|
||||
.config = R"JSON({
|
||||
.config = R"JSON({
|
||||
"server": {"max_queue_size": 2},
|
||||
"workers": 4,
|
||||
"rpc": {"cache_timeout": 0}
|
||||
@@ -369,7 +366,7 @@ generateCacheTestValuesForParametersTest()
|
||||
.isAdmin = false,
|
||||
.expectedCacheEnabled = false},
|
||||
{.testName = "CacheDisabledWhenNoTimeout",
|
||||
.config = R"JSON({
|
||||
.config = R"JSON({
|
||||
"server": {"max_queue_size": 2},
|
||||
"workers": 4,
|
||||
"rpc": {"cache_timeout": 0}
|
||||
@@ -378,7 +375,7 @@ generateCacheTestValuesForParametersTest()
|
||||
.isAdmin = false,
|
||||
.expectedCacheEnabled = false},
|
||||
{.testName = "CacheDisabledWhenTimeoutIsZero",
|
||||
.config = R"JSON({
|
||||
.config = R"JSON({
|
||||
"server": {"max_queue_size": 2},
|
||||
"workers": 4,
|
||||
"rpc": {"cache_timeout": 0}
|
||||
@@ -387,7 +384,7 @@ generateCacheTestValuesForParametersTest()
|
||||
.isAdmin = false,
|
||||
.expectedCacheEnabled = false},
|
||||
{.testName = "CacheNotWorkForAdmin",
|
||||
.config = R"JSON({
|
||||
.config = R"JSON({
|
||||
"server": {"max_queue_size": 2},
|
||||
"workers": 4,
|
||||
"rpc": { "cache_timeout": 10}
|
||||
@@ -396,7 +393,7 @@ generateCacheTestValuesForParametersTest()
|
||||
.isAdmin = true,
|
||||
.expectedCacheEnabled = false},
|
||||
{.testName = "CacheDisabledWhenCmdNotMatch",
|
||||
.config = R"JSON({
|
||||
.config = R"JSON({
|
||||
"server": {"max_queue_size": 2},
|
||||
"workers": 4,
|
||||
"rpc": {"cache_timeout": 10}
|
||||
@@ -425,10 +422,9 @@ TEST_P(RPCEngineCacheParameterTest, Test)
|
||||
|
||||
auto const admin = testParam.isAdmin;
|
||||
auto const method = testParam.method;
|
||||
std::shared_ptr<RPCEngine<MockLoadBalancer, MockCounters>> engine =
|
||||
RPCEngine<MockLoadBalancer, MockCounters>::makeRPCEngine(
|
||||
cfgCache, backend_, mockLoadBalancerPtr_, dosGuard, queue, *mockCountersPtr_, handlerProvider
|
||||
);
|
||||
std::shared_ptr<RPCEngine<MockCounters>> engine = RPCEngine<MockCounters>::makeRPCEngine(
|
||||
cfgCache, backend_, mockLoadBalancerPtr_, dosGuard, queue, *mockCountersPtr_, handlerProvider
|
||||
);
|
||||
int callTime = 2;
|
||||
EXPECT_CALL(*handlerProvider, isClioOnly).Times(callTime).WillRepeatedly(Return(false));
|
||||
if (testParam.expectedCacheEnabled) {
|
||||
@@ -474,10 +470,9 @@ TEST_F(RPCEngineTest, NotCacheIfErrorHappen)
|
||||
|
||||
auto const notAdmin = false;
|
||||
auto const method = "server_info";
|
||||
std::shared_ptr<RPCEngine<MockLoadBalancer, MockCounters>> engine =
|
||||
RPCEngine<MockLoadBalancer, MockCounters>::makeRPCEngine(
|
||||
cfgCache, backend_, mockLoadBalancerPtr_, dosGuard, queue, *mockCountersPtr_, handlerProvider
|
||||
);
|
||||
std::shared_ptr<RPCEngine<MockCounters>> engine = RPCEngine<MockCounters>::makeRPCEngine(
|
||||
cfgCache, backend_, mockLoadBalancerPtr_, dosGuard, queue, *mockCountersPtr_, handlerProvider
|
||||
);
|
||||
|
||||
int callTime = 2;
|
||||
EXPECT_CALL(*backend_, isTooBusy).Times(callTime).WillRepeatedly(Return(false));
|
||||
|
||||
@@ -74,7 +74,7 @@
|
||||
|
||||
using ::testing::Types;
|
||||
using namespace rpc;
|
||||
using TestServerInfoHandler = BaseServerInfoHandler<MockLoadBalancer, MockETLService, MockCounters>;
|
||||
using TestServerInfoHandler = BaseServerInfoHandler<MockCounters>;
|
||||
|
||||
constexpr static auto kINDEX1 = "05FB0EB4B899F056FA095537C5817163801F544BAFCEA39C995D76DB4D16F9DD";
|
||||
constexpr static auto kAMM_ACCOUNT = "rLcS7XL6nxRAi7JcbJcn1Na179oF3vdfbh";
|
||||
|
||||
@@ -46,7 +46,7 @@ using namespace data;
|
||||
namespace json = boost::json;
|
||||
using namespace testing;
|
||||
|
||||
using TestServerInfoHandler = BaseServerInfoHandler<MockLoadBalancer, MockETLService, MockCounters>;
|
||||
using TestServerInfoHandler = BaseServerInfoHandler<MockCounters>;
|
||||
|
||||
namespace {
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ using namespace data;
|
||||
namespace json = boost::json;
|
||||
using namespace testing;
|
||||
|
||||
using TestTxHandler = BaseTxHandler<MockETLService>;
|
||||
using TestTxHandler = TxHandler;
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -148,7 +148,7 @@ TEST_F(RPCTxTest, ExcessiveLgrRange)
|
||||
runSpawn([this](auto yield) {
|
||||
auto const handler = AnyHandler{TestTxHandler{backend_, mockETLServicePtr_}};
|
||||
auto const req = json::parse(fmt::format(
|
||||
R"({{
|
||||
R"({{
|
||||
"command": "tx",
|
||||
"transaction": "{}",
|
||||
"min_ledger": 1,
|
||||
@@ -182,7 +182,7 @@ TEST_F(RPCTxTest, InvalidBinaryV1)
|
||||
runSpawn([this](auto yield) {
|
||||
auto const handler = AnyHandler{TestTxHandler{backend_, mockETLServicePtr_}};
|
||||
auto const req = json::parse(fmt::format(
|
||||
R"({{
|
||||
R"({{
|
||||
"command": "tx",
|
||||
"transaction": "{}",
|
||||
"binary": 12
|
||||
@@ -199,7 +199,7 @@ TEST_F(RPCTxTest, InvalidBinaryV2)
|
||||
runSpawn([this](auto yield) {
|
||||
auto const handler = AnyHandler{TestTxHandler{backend_, mockETLServicePtr_}};
|
||||
auto const req = json::parse(fmt::format(
|
||||
R"({{
|
||||
R"({{
|
||||
"command": "tx",
|
||||
"transaction": "{}",
|
||||
"binary": 12
|
||||
@@ -220,7 +220,7 @@ TEST_F(RPCTxTest, InvalidLgrRange)
|
||||
runSpawn([this](auto yield) {
|
||||
auto const handler = AnyHandler{TestTxHandler{backend_, mockETLServicePtr_}};
|
||||
auto const req = json::parse(fmt::format(
|
||||
R"({{
|
||||
R"({{
|
||||
"command": "tx",
|
||||
"transaction": "{}",
|
||||
"max_ledger": 1,
|
||||
@@ -249,7 +249,7 @@ TEST_F(RPCTxTest, TxnNotFound)
|
||||
runSpawn([this](auto yield) {
|
||||
auto const handler = AnyHandler{TestTxHandler{backend_, mockETLServicePtr_}};
|
||||
auto const req = json::parse(fmt::format(
|
||||
R"({{
|
||||
R"({{
|
||||
"command": "tx",
|
||||
"transaction": "{}"
|
||||
}})",
|
||||
@@ -277,7 +277,7 @@ TEST_F(RPCTxTest, TxnNotFoundInGivenRangeSearchAllFalse)
|
||||
runSpawn([this](auto yield) {
|
||||
auto const handler = AnyHandler{TestTxHandler{backend_, mockETLServicePtr_}};
|
||||
auto const req = json::parse(fmt::format(
|
||||
R"({{
|
||||
R"({{
|
||||
"command": "tx",
|
||||
"transaction": "{}",
|
||||
"min_ledger": 1,
|
||||
@@ -308,7 +308,7 @@ TEST_F(RPCTxTest, TxnNotFoundInGivenRangeSearchAllTrue)
|
||||
runSpawn([this](auto yield) {
|
||||
auto const handler = AnyHandler{TestTxHandler{backend_, mockETLServicePtr_}};
|
||||
auto const req = json::parse(fmt::format(
|
||||
R"({{
|
||||
R"({{
|
||||
"command": "tx",
|
||||
"transaction": "{}",
|
||||
"min_ledger": 1,
|
||||
@@ -340,7 +340,7 @@ TEST_F(RPCTxTest, CtidNotFoundSearchAllFalse)
|
||||
runSpawn([this](auto yield) {
|
||||
auto const handler = AnyHandler{TestTxHandler{backend_, mockETLServicePtr_}};
|
||||
auto const req = json::parse(fmt::format(
|
||||
R"({{
|
||||
R"({{
|
||||
"ctid": "{}",
|
||||
"min_ledger": 1,
|
||||
"max_ledger": 1000
|
||||
@@ -375,7 +375,7 @@ TEST_F(RPCTxTest, DefaultParameter_API_v1)
|
||||
runSpawn([this](auto yield) {
|
||||
auto const handler = AnyHandler{TestTxHandler{backend_, mockETLServicePtr_}};
|
||||
auto const req = json::parse(fmt::format(
|
||||
R"({{
|
||||
R"({{
|
||||
"command": "tx",
|
||||
"transaction": "{}"
|
||||
}})",
|
||||
@@ -405,7 +405,7 @@ TEST_F(RPCTxTest, PaymentTx_API_v1)
|
||||
runSpawn([this](auto yield) {
|
||||
auto const handler = AnyHandler{TestTxHandler{backend_, mockETLServicePtr_}};
|
||||
auto const req = json::parse(fmt::format(
|
||||
R"({{
|
||||
R"({{
|
||||
"command": "tx",
|
||||
"transaction": "{}"
|
||||
}})",
|
||||
@@ -436,7 +436,7 @@ TEST_F(RPCTxTest, PaymentTx_API_v2)
|
||||
runSpawn([this](auto yield) {
|
||||
auto const handler = AnyHandler{TestTxHandler{backend_, mockETLServicePtr_}};
|
||||
auto const req = json::parse(fmt::format(
|
||||
R"({{
|
||||
R"({{
|
||||
"command": "tx",
|
||||
"transaction": "{}"
|
||||
}})",
|
||||
@@ -470,7 +470,7 @@ TEST_F(RPCTxTest, DefaultParameter_API_v2)
|
||||
runSpawn([this](auto yield) {
|
||||
auto const handler = AnyHandler{TestTxHandler{backend_, mockETLServicePtr_}};
|
||||
auto const req = json::parse(fmt::format(
|
||||
R"({{
|
||||
R"({{
|
||||
"command": "tx",
|
||||
"transaction": "{}"
|
||||
}})",
|
||||
@@ -510,7 +510,7 @@ TEST_F(RPCTxTest, ReturnBinary)
|
||||
runSpawn([this](auto yield) {
|
||||
auto const handler = AnyHandler{TestTxHandler{backend_, mockETLServicePtr_}};
|
||||
auto const req = json::parse(fmt::format(
|
||||
R"({{
|
||||
R"({{
|
||||
"command": "tx",
|
||||
"transaction": "{}",
|
||||
"binary": true
|
||||
@@ -553,7 +553,7 @@ TEST_F(RPCTxTest, ReturnBinaryWithCTID)
|
||||
runSpawn([this](auto yield) {
|
||||
auto const handler = AnyHandler{TestTxHandler{backend_, mockETLServicePtr_}};
|
||||
auto const req = json::parse(fmt::format(
|
||||
R"({{
|
||||
R"({{
|
||||
"command": "tx",
|
||||
"transaction": "{}",
|
||||
"binary": true
|
||||
@@ -585,14 +585,14 @@ TEST_F(RPCTxTest, MintNFT)
|
||||
"FinalFields": {{
|
||||
"NFTokens": [
|
||||
{{
|
||||
"NFToken":
|
||||
"NFToken":
|
||||
{{
|
||||
"NFTokenID": "{}",
|
||||
"URI": "7465737475726C"
|
||||
}}
|
||||
}},
|
||||
{{
|
||||
"NFToken":
|
||||
"NFToken":
|
||||
{{
|
||||
"NFTokenID": "1B8590C01B0006EDFA9ED60296DD052DC5E90F99659B25014D08E1BC983515BC",
|
||||
"URI": "7465737475726C"
|
||||
@@ -604,7 +604,7 @@ TEST_F(RPCTxTest, MintNFT)
|
||||
"PreviousFields": {{
|
||||
"NFTokens": [
|
||||
{{
|
||||
"NFToken":
|
||||
"NFToken":
|
||||
{{
|
||||
"NFTokenID": "1B8590C01B0006EDFA9ED60296DD052DC5E90F99659B25014D08E1BC983515BC",
|
||||
"URI": "7465737475726C"
|
||||
@@ -640,7 +640,7 @@ TEST_F(RPCTxTest, MintNFT)
|
||||
runSpawn([this](auto yield) {
|
||||
auto const handler = AnyHandler{TestTxHandler{backend_, mockETLServicePtr_}};
|
||||
auto const req = json::parse(fmt::format(
|
||||
R"({{
|
||||
R"({{
|
||||
"command": "tx",
|
||||
"transaction": "{}"
|
||||
}})",
|
||||
@@ -667,7 +667,7 @@ TEST_F(RPCTxTest, NFTAcceptOffer)
|
||||
runSpawn([this](auto yield) {
|
||||
auto const handler = AnyHandler{TestTxHandler{backend_, mockETLServicePtr_}};
|
||||
auto const req = json::parse(fmt::format(
|
||||
R"({{
|
||||
R"({{
|
||||
"command": "tx",
|
||||
"transaction": "{}"
|
||||
}})",
|
||||
@@ -695,7 +695,7 @@ TEST_F(RPCTxTest, NFTCancelOffer)
|
||||
runSpawn([this, &ids](auto yield) {
|
||||
auto const handler = AnyHandler{TestTxHandler{backend_, mockETLServicePtr_}};
|
||||
auto const req = json::parse(fmt::format(
|
||||
R"({{
|
||||
R"({{
|
||||
"command": "tx",
|
||||
"transaction": "{}"
|
||||
}})",
|
||||
@@ -730,7 +730,7 @@ TEST_F(RPCTxTest, NFTCreateOffer)
|
||||
runSpawn([this](auto yield) {
|
||||
auto const handler = AnyHandler{TestTxHandler{backend_, mockETLServicePtr_}};
|
||||
auto const req = json::parse(fmt::format(
|
||||
R"({{
|
||||
R"({{
|
||||
"command": "tx",
|
||||
"transaction": "{}"
|
||||
}})",
|
||||
@@ -747,7 +747,7 @@ TEST_F(RPCTxTest, CTIDAndTransactionBothProvided)
|
||||
runSpawn([this](auto yield) {
|
||||
auto const handler = AnyHandler{TestTxHandler{backend_, mockETLServicePtr_}};
|
||||
auto const req = json::parse(fmt::format(
|
||||
R"({{
|
||||
R"({{
|
||||
"command": "tx",
|
||||
"transaction": "{}",
|
||||
"ctid": "{}"
|
||||
@@ -819,7 +819,7 @@ TEST_F(RPCTxTest, CTIDNotMatch)
|
||||
runSpawn([this](auto yield) {
|
||||
auto const handler = AnyHandler{TestTxHandler{backend_, mockETLServicePtr_}};
|
||||
auto const req = json::parse(fmt::format(
|
||||
R"({{
|
||||
R"({{
|
||||
"command": "tx",
|
||||
"ctid": "{}"
|
||||
}})",
|
||||
@@ -899,7 +899,7 @@ TEST_F(RPCTxTest, ReturnCTIDForTxInput)
|
||||
runSpawn([this](auto yield) {
|
||||
auto const handler = AnyHandler{TestTxHandler{backend_, mockETLServicePtr_}};
|
||||
auto const req = json::parse(fmt::format(
|
||||
R"({{
|
||||
R"({{
|
||||
"command": "tx",
|
||||
"transaction": "{}"
|
||||
}})",
|
||||
@@ -972,7 +972,7 @@ TEST_F(RPCTxTest, NotReturnCTIDIfETLNotAvaiable)
|
||||
runSpawn([this](auto yield) {
|
||||
auto const handler = AnyHandler{TestTxHandler{backend_, mockETLServicePtr_}};
|
||||
auto const req = json::parse(fmt::format(
|
||||
R"({{
|
||||
R"({{
|
||||
"command": "tx",
|
||||
"transaction": "{}"
|
||||
}})",
|
||||
@@ -1057,7 +1057,7 @@ TEST_F(RPCTxTest, ViaCTID)
|
||||
runSpawn([this](auto yield) {
|
||||
auto const handler = AnyHandler{TestTxHandler{backend_, mockETLServicePtr_}};
|
||||
auto const req = json::parse(fmt::format(
|
||||
R"({{
|
||||
R"({{
|
||||
"command": "tx",
|
||||
"ctid": "{}"
|
||||
}})",
|
||||
@@ -1095,7 +1095,7 @@ TEST_F(RPCTxTest, ViaLowercaseCTID)
|
||||
runSpawn([&, this](auto yield) {
|
||||
auto const handler = AnyHandler{TestTxHandler{backend_, mockETLServicePtr_}};
|
||||
auto const req = json::parse(fmt::format(
|
||||
R"({{
|
||||
R"({{
|
||||
"command": "tx",
|
||||
"ctid": "{}"
|
||||
}})",
|
||||
|
||||
@@ -95,8 +95,8 @@ struct WebRPCServerHandlerTest : util::prometheus::WithPrometheus, MockBackendTe
|
||||
std::shared_ptr<MockAsyncRPCEngine> rpcEngine = std::make_shared<MockAsyncRPCEngine>();
|
||||
std::shared_ptr<MockETLService> etl = std::make_shared<MockETLService>();
|
||||
std::shared_ptr<util::TagDecoratorFactory> tagFactory = std::make_shared<util::TagDecoratorFactory>(cfg);
|
||||
std::shared_ptr<RPCServerHandler<MockAsyncRPCEngine, MockETLService>> handler =
|
||||
std::make_shared<RPCServerHandler<MockAsyncRPCEngine, MockETLService>>(cfg, backend_, rpcEngine, etl);
|
||||
std::shared_ptr<RPCServerHandler<MockAsyncRPCEngine>> handler =
|
||||
std::make_shared<RPCServerHandler<MockAsyncRPCEngine>>(cfg, backend_, rpcEngine, etl);
|
||||
std::shared_ptr<MockWsBase> session = std::make_shared<MockWsBase>(*tagFactory);
|
||||
};
|
||||
|
||||
@@ -756,8 +756,7 @@ TEST_F(WebRPCServerHandlerTest, WsTooBusy)
|
||||
session->upgraded = true;
|
||||
|
||||
auto localRpcEngine = std::make_shared<MockRPCEngine>();
|
||||
auto localHandler =
|
||||
std::make_shared<RPCServerHandler<MockRPCEngine, MockETLService>>(cfg, backend_, localRpcEngine, etl);
|
||||
auto localHandler = std::make_shared<RPCServerHandler<MockRPCEngine>>(cfg, backend_, localRpcEngine, etl);
|
||||
static constexpr auto kREQUEST = R"({
|
||||
"command": "server_info",
|
||||
"id": 99
|
||||
@@ -784,8 +783,7 @@ TEST_F(WebRPCServerHandlerTest, WsTooBusy)
|
||||
TEST_F(WebRPCServerHandlerTest, HTTPTooBusy)
|
||||
{
|
||||
auto localRpcEngine = std::make_shared<MockRPCEngine>();
|
||||
auto localHandler =
|
||||
std::make_shared<RPCServerHandler<MockRPCEngine, MockETLService>>(cfg, backend_, localRpcEngine, etl);
|
||||
auto localHandler = std::make_shared<RPCServerHandler<MockRPCEngine>>(cfg, backend_, localRpcEngine, etl);
|
||||
static constexpr auto kREQUEST = R"({
|
||||
"method": "server_info",
|
||||
"params": [{}]
|
||||
|
||||
@@ -72,7 +72,7 @@ protected:
|
||||
std::shared_ptr<testing::StrictMock<MockRPCEngine>> rpcEngine_ =
|
||||
std::make_shared<testing::StrictMock<MockRPCEngine>>();
|
||||
std::shared_ptr<StrictMock<MockETLService>> etl_ = std::make_shared<StrictMock<MockETLService>>();
|
||||
RPCServerHandler<MockRPCEngine, MockETLService> rpcServerHandler_{config, backend_, rpcEngine_, etl_};
|
||||
RPCServerHandler<MockRPCEngine> rpcServerHandler_{config, backend_, rpcEngine_, etl_};
|
||||
|
||||
util::TagDecoratorFactory tagFactory_{config};
|
||||
StrictMockConnectionMetadata connectionMetadata_{"some ip", tagFactory_};
|
||||
|
||||
Reference in New Issue
Block a user