#include "etl/InitialLoadObserverInterface.hpp" #include "etl/LoadBalancerInterface.hpp" #include "etl/Models.hpp" #include "etl/impl/SourceImpl.hpp" #include "rpc/Errors.hpp" #include "util/Spawn.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace etl::impl; using testing::Return; using testing::StrictMock; namespace { struct GrpcSourceMock { using FetchLedgerReturnType = std::pair; MOCK_METHOD(FetchLedgerReturnType, fetchLedger, (uint32_t, bool, bool)); using LoadLedgerReturnType = etl::InitialLedgerLoadResult; MOCK_METHOD( LoadLedgerReturnType, loadInitialLedger, (uint32_t, uint32_t, etl::InitialLoadObserverInterface&) ); MOCK_METHOD(void, stop, (boost::asio::yield_context), ()); }; 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; using ClientIpOpt = std::optional; MOCK_METHOD( ForwardToRippledReturnType, forwardToRippled, (boost::json::object const&, ClientIpOpt const&, std::string_view, boost::asio::yield_context), (const) ); }; struct InitialLoadObserverMock : etl::InitialLoadObserverInterface { MOCK_METHOD( void, onInitialLoadGotMoreObjects, (uint32_t, std::vector const&, std::optional), (override) ); void onInitialLoadGotMoreObjects(uint32_t seq, std::vector const& data) { onInitialLoadGotMoreObjects(seq, data, std::nullopt); } }; } // namespace struct SourceImplTest : public ::testing::Test { protected: boost::asio::io_context ioc_; StrictMock grpcSourceMock_; std::shared_ptr> subscriptionSourceMock_ = std::make_shared>(); StrictMock forwardingSourceMock_; SourceImpl< StrictMock&, std::shared_ptr>, StrictMock&> source_{ "some_ip", "some_ws_port", "some_grpc_port", grpcSourceMock_, subscriptionSourceMock_, forwardingSourceMock_ }; }; TEST_F(SourceImplTest, run) { EXPECT_CALL(*subscriptionSourceMock_, run()); source_.run(); } TEST_F(SourceImplTest, stop) { EXPECT_CALL(*subscriptionSourceMock_, stop); EXPECT_CALL(grpcSourceMock_, stop); boost::asio::io_context ctx; util::spawn(ctx, [&](boost::asio::yield_context yield) { source_.stop(yield); }); ctx.run(); } TEST_F(SourceImplTest, isConnected) { EXPECT_CALL(*subscriptionSourceMock_, isConnected()).WillOnce(testing::Return(true)); EXPECT_TRUE(source_.isConnected()); } TEST_F(SourceImplTest, setForwarding) { EXPECT_CALL(*subscriptionSourceMock_, setForwarding(true)); source_.setForwarding(true); } TEST_F(SourceImplTest, 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(json.at("validated_range")), "some_validated_range" ); EXPECT_EQ(boost::json::value_to(json.at("is_connected")), "1"); EXPECT_EQ(boost::json::value_to(json.at("ip")), "some_ip"); EXPECT_EQ(boost::json::value_to(json.at("ws_port")), "some_ws_port"); EXPECT_EQ(boost::json::value_to(json.at("grpc_port")), "some_grpc_port"); auto lastMessageAgeStr = boost::json::value_to(json.at("last_msg_age_seconds")); EXPECT_GE(std::stoi(lastMessageAgeStr), 0); } TEST_F(SourceImplTest, 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(SourceImplTest, hasLedger) { uint32_t const ledgerSeq = 123; EXPECT_CALL(*subscriptionSourceMock_, hasLedger(ledgerSeq)).WillOnce(Return(true)); EXPECT_TRUE(source_.hasLedger(ledgerSeq)); } TEST_F(SourceImplTest, 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(SourceImplTest, loadInitialLedgerErrorPath) { uint32_t const ledgerSeq = 123; uint32_t const numMarkers = 3; auto observerMock = testing::StrictMock(); EXPECT_CALL(grpcSourceMock_, loadInitialLedger(ledgerSeq, numMarkers, testing::_)) .WillOnce(Return(std::unexpected{etl::InitialLedgerLoadError::Errored})); auto const res = source_.loadInitialLedger(ledgerSeq, numMarkers, observerMock); EXPECT_FALSE(res.has_value()); } TEST_F(SourceImplTest, loadInitialLedgerSuccessPath) { uint32_t const ledgerSeq = 123; uint32_t const numMarkers = 3; auto response = etl::InitialLedgerLoadResult{{"1", "2", "3"}}; auto observerMock = testing::StrictMock(); EXPECT_CALL(grpcSourceMock_, loadInitialLedger(ledgerSeq, numMarkers, testing::_)) .WillOnce(Return(response)); auto const res = source_.loadInitialLedger(ledgerSeq, numMarkers, observerMock); EXPECT_TRUE(res.has_value()); EXPECT_EQ(res, response); } TEST_F(SourceImplTest, forwardToRippled) { boost::json::object const request = {{"some_key", "some_value"}}; std::optional 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; util::spawn(ioContext, [&](boost::asio::yield_context yield) { auto const response = source_.forwardToRippled(request, clientIp, xUserValue, yield); EXPECT_EQ(response, request); }); ioContext.run(); }