mirror of
https://github.com/XRPLF/clio.git
synced 2025-12-05 16:58:00 +00:00
feat: Integrate new webserver (#1722)
For #919. The new web server is not using dosguard yet. It will be fixed by a separate PR.
This commit is contained in:
@@ -23,7 +23,7 @@
|
||||
#include "util/MockPrometheus.hpp"
|
||||
#include "util/MockWsBase.hpp"
|
||||
#include "util/SyncExecutionCtxFixture.hpp"
|
||||
#include "web/interface/ConnectionBase.hpp"
|
||||
#include "web/SubscriptionContextInterface.hpp"
|
||||
|
||||
#include <boost/json/parse.hpp>
|
||||
#include <gtest/gtest.h>
|
||||
@@ -37,25 +37,9 @@
|
||||
template <typename TestedFeed>
|
||||
struct FeedBaseTest : util::prometheus::WithPrometheus, MockBackendTest, SyncExecutionCtxFixture {
|
||||
protected:
|
||||
std::shared_ptr<web::ConnectionBase> sessionPtr;
|
||||
std::shared_ptr<TestedFeed> testFeedPtr;
|
||||
MockSession* mockSessionPtr = nullptr;
|
||||
|
||||
void
|
||||
SetUp() override
|
||||
{
|
||||
testFeedPtr = std::make_shared<TestedFeed>(ctx);
|
||||
sessionPtr = std::make_shared<MockSession>();
|
||||
sessionPtr->apiSubVersion = 1;
|
||||
mockSessionPtr = dynamic_cast<MockSession*>(sessionPtr.get());
|
||||
}
|
||||
|
||||
void
|
||||
TearDown() override
|
||||
{
|
||||
sessionPtr.reset();
|
||||
testFeedPtr.reset();
|
||||
}
|
||||
web::SubscriptionContextPtr sessionPtr = std::make_shared<MockSession>();
|
||||
std::shared_ptr<TestedFeed> testFeedPtr = std::make_shared<TestedFeed>(ctx);
|
||||
MockSession* mockSessionPtr = dynamic_cast<MockSession*>(sessionPtr.get());
|
||||
};
|
||||
|
||||
namespace impl {
|
||||
|
||||
@@ -34,22 +34,7 @@ template <template <typename> typename MockType = ::testing::NiceMock>
|
||||
struct HandlerBaseTestBase : util::prometheus::WithPrometheus,
|
||||
MockBackendTestBase<MockType>,
|
||||
SyncAsioContextTest,
|
||||
MockETLServiceTestBase<MockType> {
|
||||
protected:
|
||||
void
|
||||
SetUp() override
|
||||
{
|
||||
SyncAsioContextTest::SetUp();
|
||||
MockETLServiceTestBase<MockType>::SetUp();
|
||||
}
|
||||
|
||||
void
|
||||
TearDown() override
|
||||
{
|
||||
MockETLServiceTestBase<MockType>::TearDown();
|
||||
SyncAsioContextTest::TearDown();
|
||||
}
|
||||
};
|
||||
MockETLServiceTestBase<MockType> {};
|
||||
|
||||
/**
|
||||
* @brief Fixture with a "nice" backend mock and an embedded boost::asio context.
|
||||
|
||||
@@ -21,20 +21,25 @@
|
||||
|
||||
#include "util/Taggable.hpp"
|
||||
#include "util/config/Config.hpp"
|
||||
#include "web/SubscriptionContextInterface.hpp"
|
||||
#include "web/interface/ConnectionBase.hpp"
|
||||
|
||||
#include <boost/beast/http/status.hpp>
|
||||
#include <gmock/gmock.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
struct MockSession : public web::ConnectionBase {
|
||||
struct MockSession : public web::SubscriptionContextInterface {
|
||||
MOCK_METHOD(void, send, (std::shared_ptr<std::string>), (override));
|
||||
MOCK_METHOD(void, send, (std::string&&, boost::beast::http::status), (override));
|
||||
MOCK_METHOD(void, onDisconnect, (OnDisconnectSlot const&), (override));
|
||||
MOCK_METHOD(void, setApiSubversion, (uint32_t), (override));
|
||||
MOCK_METHOD(uint32_t, apiSubversion, (), (const, override));
|
||||
|
||||
util::TagDecoratorFactory tagDecoratorFactory{util::Config{}};
|
||||
|
||||
MockSession() : web::ConnectionBase(tagDecoratorFactory, "")
|
||||
MockSession() : web::SubscriptionContextInterface(tagDecoratorFactory)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
#include <boost/asio/ssl/stream_base.hpp>
|
||||
#include <boost/asio/ssl/verify_context.hpp>
|
||||
#include <boost/asio/ssl/verify_mode.hpp>
|
||||
#include <boost/beast/core/buffers_to_string.hpp>
|
||||
#include <boost/beast/core/error.hpp>
|
||||
#include <boost/beast/core/flat_buffer.hpp>
|
||||
#include <boost/beast/core/stream_traits.hpp>
|
||||
|
||||
45
tests/common/web/interface/ConnectionBaseMock.hpp
Normal file
45
tests/common/web/interface/ConnectionBaseMock.hpp
Normal file
@@ -0,0 +1,45 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of clio: https://github.com/XRPLF/clio
|
||||
Copyright (c) 2024, 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 "util/Taggable.hpp"
|
||||
#include "web/SubscriptionContextInterface.hpp"
|
||||
#include "web/interface/ConnectionBase.hpp"
|
||||
|
||||
#include <boost/beast/http/status.hpp>
|
||||
#include <gmock/gmock.h>
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
struct ConnectionBaseMock : web::ConnectionBase {
|
||||
using ConnectionBase::ConnectionBase;
|
||||
|
||||
MOCK_METHOD(void, send, (std::string&&, boost::beast::http::status), (override));
|
||||
MOCK_METHOD(void, send, (std::shared_ptr<std::string>), (override));
|
||||
MOCK_METHOD(
|
||||
web::SubscriptionContextPtr,
|
||||
makeSubscriptionContext,
|
||||
(util::TagDecoratorFactory const& factory),
|
||||
(override)
|
||||
);
|
||||
};
|
||||
|
||||
using ConnectionBaseStrictMockPtr = std::shared_ptr<testing::StrictMock<ConnectionBaseMock>>;
|
||||
@@ -19,18 +19,29 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "util/Taggable.hpp"
|
||||
#include "web/ng/Connection.hpp"
|
||||
#include "web/ng/Error.hpp"
|
||||
#include "web/ng/Request.hpp"
|
||||
#include "web/ng/Response.hpp"
|
||||
#include "web/ng/impl/HttpConnection.hpp"
|
||||
|
||||
#include <boost/asio/spawn.hpp>
|
||||
#include <boost/asio/ssl/context.hpp>
|
||||
#include <gmock/gmock.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
|
||||
struct MockConnectionMetadataImpl : web::ng::ConnectionMetadata {
|
||||
using web::ng::ConnectionMetadata::ConnectionMetadata;
|
||||
MOCK_METHOD(bool, wasUpgraded, (), (const, override));
|
||||
};
|
||||
|
||||
using MockConnectionMetadata = testing::NiceMock<MockConnectionMetadataImpl>;
|
||||
using StrictMockConnectionMetadata = testing::StrictMock<MockConnectionMetadataImpl>;
|
||||
|
||||
struct MockConnectionImpl : web::ng::Connection {
|
||||
using web::ng::Connection::Connection;
|
||||
|
||||
|
||||
85
tests/common/web/ng/impl/MockHttpConnection.hpp
Normal file
85
tests/common/web/ng/impl/MockHttpConnection.hpp
Normal file
@@ -0,0 +1,85 @@
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of clio: https://github.com/XRPLF/clio
|
||||
Copyright (c) 2024, 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 "util/Taggable.hpp"
|
||||
#include "web/ng/Connection.hpp"
|
||||
#include "web/ng/Error.hpp"
|
||||
#include "web/ng/Request.hpp"
|
||||
#include "web/ng/Response.hpp"
|
||||
#include "web/ng/impl/HttpConnection.hpp"
|
||||
|
||||
#include <boost/asio/spawn.hpp>
|
||||
#include <boost/asio/ssl/context.hpp>
|
||||
#include <gmock/gmock.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
|
||||
struct MockHttpConnectionImpl : web::ng::impl::UpgradableConnection {
|
||||
using UpgradableConnection::UpgradableConnection;
|
||||
|
||||
MOCK_METHOD(bool, wasUpgraded, (), (const, override));
|
||||
|
||||
using SendReturnType = std::optional<web::ng::Error>;
|
||||
MOCK_METHOD(
|
||||
SendReturnType,
|
||||
send,
|
||||
(web::ng::Response, boost::asio::yield_context, std::chrono::steady_clock::duration),
|
||||
(override)
|
||||
);
|
||||
|
||||
using ReceiveReturnType = std::expected<web::ng::Request, web::ng::Error>;
|
||||
MOCK_METHOD(
|
||||
ReceiveReturnType,
|
||||
receive,
|
||||
(boost::asio::yield_context, std::chrono::steady_clock::duration),
|
||||
(override)
|
||||
);
|
||||
|
||||
MOCK_METHOD(void, close, (boost::asio::yield_context, std::chrono::steady_clock::duration));
|
||||
|
||||
using IsUpgradeRequestedReturnType = std::expected<bool, web::ng::Error>;
|
||||
MOCK_METHOD(
|
||||
IsUpgradeRequestedReturnType,
|
||||
isUpgradeRequested,
|
||||
(boost::asio::yield_context, std::chrono::steady_clock::duration),
|
||||
(override)
|
||||
);
|
||||
|
||||
using UpgradeReturnType = std::expected<web::ng::ConnectionPtr, web::ng::Error>;
|
||||
using OptionalSslContext = std::optional<boost::asio::ssl::context>;
|
||||
MOCK_METHOD(
|
||||
UpgradeReturnType,
|
||||
upgrade,
|
||||
(OptionalSslContext & sslContext,
|
||||
util::TagDecoratorFactory const& tagDecoratorFactory,
|
||||
boost::asio::yield_context yield),
|
||||
(override)
|
||||
);
|
||||
};
|
||||
|
||||
using MockHttpConnection = testing::NiceMock<MockHttpConnectionImpl>;
|
||||
using MockHttpConnectionPtr = std::unique_ptr<testing::NiceMock<MockHttpConnectionImpl>>;
|
||||
|
||||
using StrictMockHttpConnection = testing::StrictMock<MockHttpConnectionImpl>;
|
||||
using StrictMockHttpConnectionPtr = std::unique_ptr<testing::StrictMock<MockHttpConnectionImpl>>;
|
||||
73
tests/common/web/ng/impl/MockWsConnection.hpp
Normal file
73
tests/common/web/ng/impl/MockWsConnection.hpp
Normal file
@@ -0,0 +1,73 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of clio: https://github.com/XRPLF/clio
|
||||
Copyright (c) 2024, 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 "web/ng/Connection.hpp"
|
||||
#include "web/ng/Error.hpp"
|
||||
#include "web/ng/Request.hpp"
|
||||
#include "web/ng/Response.hpp"
|
||||
#include "web/ng/impl/WsConnection.hpp"
|
||||
|
||||
#include <boost/asio/buffer.hpp>
|
||||
#include <boost/asio/spawn.hpp>
|
||||
#include <boost/asio/ssl/context.hpp>
|
||||
#include <gmock/gmock.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
|
||||
struct MockWsConnectionImpl : web::ng::impl::WsConnectionBase {
|
||||
using WsConnectionBase::WsConnectionBase;
|
||||
|
||||
MOCK_METHOD(bool, wasUpgraded, (), (const, override));
|
||||
|
||||
using SendReturnType = std::optional<web::ng::Error>;
|
||||
MOCK_METHOD(
|
||||
SendReturnType,
|
||||
send,
|
||||
(web::ng::Response, boost::asio::yield_context, std::chrono::steady_clock::duration),
|
||||
(override)
|
||||
);
|
||||
|
||||
using ReceiveReturnType = std::expected<web::ng::Request, web::ng::Error>;
|
||||
MOCK_METHOD(
|
||||
ReceiveReturnType,
|
||||
receive,
|
||||
(boost::asio::yield_context, std::chrono::steady_clock::duration),
|
||||
(override)
|
||||
);
|
||||
|
||||
MOCK_METHOD(void, close, (boost::asio::yield_context, std::chrono::steady_clock::duration));
|
||||
|
||||
using SendBufferReturnType = std::optional<web::ng::Error>;
|
||||
MOCK_METHOD(
|
||||
SendBufferReturnType,
|
||||
sendBuffer,
|
||||
(boost::asio::const_buffer, boost::asio::yield_context, std::chrono::steady_clock::duration),
|
||||
(override)
|
||||
);
|
||||
};
|
||||
|
||||
using MockWsConnection = testing::NiceMock<MockWsConnectionImpl>;
|
||||
using MockWsConnectionPtr = std::unique_ptr<testing::NiceMock<MockWsConnectionImpl>>;
|
||||
|
||||
using StrictMockWsConnection = testing::StrictMock<MockWsConnectionImpl>;
|
||||
using StrictMockWsConnectionPtr = std::unique_ptr<testing::StrictMock<MockWsConnectionImpl>>;
|
||||
@@ -134,19 +134,24 @@ target_sources(
|
||||
util/TxUtilTests.cpp
|
||||
util/WithTimeout.cpp
|
||||
# Webserver
|
||||
web/AdminVerificationTests.cpp
|
||||
web/dosguard/DOSGuardTests.cpp
|
||||
web/dosguard/IntervalSweepHandlerTests.cpp
|
||||
web/dosguard/WhitelistHandlerTests.cpp
|
||||
web/impl/AdminVerificationTests.cpp
|
||||
web/impl/ErrorHandlingTests.cpp
|
||||
web/ng/ResponseTests.cpp
|
||||
web/ng/RequestTests.cpp
|
||||
web/ng/RPCServerHandlerTests.cpp
|
||||
web/ng/ServerTests.cpp
|
||||
web/ng/SubscriptionContextTests.cpp
|
||||
web/ng/impl/ConnectionHandlerTests.cpp
|
||||
web/ng/impl/ErrorHandlingTests.cpp
|
||||
web/ng/impl/HttpConnectionTests.cpp
|
||||
web/ng/impl/ServerSslContextTests.cpp
|
||||
web/ng/impl/WsConnectionTests.cpp
|
||||
web/RPCServerHandlerTests.cpp
|
||||
web/ServerTests.cpp
|
||||
web/SubscriptionContextTests.cpp
|
||||
# New Config
|
||||
util/newconfig/ArrayTests.cpp
|
||||
util/newconfig/ArrayViewTests.cpp
|
||||
|
||||
@@ -41,11 +41,27 @@ TEST_F(CliArgsTests, Parse_NoArgs)
|
||||
int const returnCode = 123;
|
||||
EXPECT_CALL(onRunMock, Call).WillOnce([](CliArgs::Action::Run const& run) {
|
||||
EXPECT_EQ(run.configPath, CliArgs::defaultConfigPath);
|
||||
EXPECT_FALSE(run.useNgWebServer);
|
||||
return returnCode;
|
||||
});
|
||||
EXPECT_EQ(action.apply(onRunMock.AsStdFunction(), onExitMock.AsStdFunction()), returnCode);
|
||||
}
|
||||
|
||||
TEST_F(CliArgsTests, Parse_NgWebServer)
|
||||
{
|
||||
for (auto& argv : {std::array{"clio_server", "-w"}, std::array{"clio_server", "--ng-web-server"}}) {
|
||||
auto const action = CliArgs::parse(argv.size(), const_cast<char const**>(argv.data()));
|
||||
|
||||
int const returnCode = 123;
|
||||
EXPECT_CALL(onRunMock, Call).WillOnce([](CliArgs::Action::Run const& run) {
|
||||
EXPECT_EQ(run.configPath, CliArgs::defaultConfigPath);
|
||||
EXPECT_TRUE(run.useNgWebServer);
|
||||
return returnCode;
|
||||
});
|
||||
EXPECT_EQ(action.apply(onRunMock.AsStdFunction(), onExitMock.AsStdFunction()), returnCode);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(CliArgsTests, Parse_VersionHelp)
|
||||
{
|
||||
for (auto& argv :
|
||||
|
||||
@@ -41,6 +41,7 @@ using FeedBookChangeTest = FeedBaseTest<BookChangesFeed>;
|
||||
|
||||
TEST_F(FeedBookChangeTest, Pub)
|
||||
{
|
||||
EXPECT_CALL(*mockSessionPtr, onDisconnect);
|
||||
testFeedPtr->sub(sessionPtr);
|
||||
EXPECT_EQ(testFeedPtr->count(), 1);
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#include "feed/FeedTestUtil.hpp"
|
||||
#include "feed/impl/ForwardFeed.hpp"
|
||||
#include "util/async/AnyExecutionContext.hpp"
|
||||
#include "web/SubscriptionContextInterface.hpp"
|
||||
|
||||
#include <boost/json/parse.hpp>
|
||||
#include <gmock/gmock.h>
|
||||
@@ -44,11 +45,14 @@ using FeedForwardTest = FeedBaseTest<NamedForwardFeedTest>;
|
||||
|
||||
TEST_F(FeedForwardTest, Pub)
|
||||
{
|
||||
EXPECT_CALL(*mockSessionPtr, onDisconnect);
|
||||
testFeedPtr->sub(sessionPtr);
|
||||
EXPECT_EQ(testFeedPtr->count(), 1);
|
||||
|
||||
auto const json = json::parse(FEED).as_object();
|
||||
EXPECT_CALL(*mockSessionPtr, send(SharedStringJsonEq(FEED))).Times(1);
|
||||
testFeedPtr->pub(json);
|
||||
|
||||
testFeedPtr->unsub(sessionPtr);
|
||||
EXPECT_EQ(testFeedPtr->count(), 0);
|
||||
testFeedPtr->pub(json);
|
||||
@@ -56,12 +60,18 @@ TEST_F(FeedForwardTest, Pub)
|
||||
|
||||
TEST_F(FeedForwardTest, AutoDisconnect)
|
||||
{
|
||||
web::SubscriptionContextInterface::OnDisconnectSlot slot;
|
||||
EXPECT_CALL(*mockSessionPtr, onDisconnect).WillOnce(testing::SaveArg<0>(&slot));
|
||||
testFeedPtr->sub(sessionPtr);
|
||||
EXPECT_EQ(testFeedPtr->count(), 1);
|
||||
|
||||
auto const json = json::parse(FEED).as_object();
|
||||
EXPECT_CALL(*mockSessionPtr, send(SharedStringJsonEq(FEED))).Times(1);
|
||||
EXPECT_CALL(*mockSessionPtr, send(SharedStringJsonEq(FEED)));
|
||||
testFeedPtr->pub(json);
|
||||
|
||||
slot(sessionPtr.get());
|
||||
sessionPtr.reset();
|
||||
EXPECT_EQ(testFeedPtr->count(), 0);
|
||||
|
||||
testFeedPtr->pub(json);
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#include "feed/FeedTestUtil.hpp"
|
||||
#include "feed/impl/LedgerFeed.hpp"
|
||||
#include "util/TestObject.hpp"
|
||||
#include "web/SubscriptionContextInterface.hpp"
|
||||
|
||||
#include <boost/asio/io_context.hpp>
|
||||
#include <boost/asio/spawn.hpp>
|
||||
@@ -60,6 +61,7 @@ TEST_F(FeedLedgerTest, SubPub)
|
||||
})";
|
||||
boost::asio::io_context ioContext;
|
||||
boost::asio::spawn(ioContext, [this](boost::asio::yield_context yield) {
|
||||
EXPECT_CALL(*mockSessionPtr, onDisconnect);
|
||||
auto res = testFeedPtr->sub(yield, backend, sessionPtr);
|
||||
// check the response
|
||||
EXPECT_EQ(res, json::parse(LedgerResponse));
|
||||
@@ -112,6 +114,10 @@ TEST_F(FeedLedgerTest, AutoDisconnect)
|
||||
"reserve_base":3,
|
||||
"reserve_inc":2
|
||||
})";
|
||||
|
||||
web::SubscriptionContextInterface::OnDisconnectSlot slot;
|
||||
EXPECT_CALL(*mockSessionPtr, onDisconnect).WillOnce(testing::SaveArg<0>(&slot));
|
||||
|
||||
boost::asio::spawn(ctx, [this](boost::asio::yield_context yield) {
|
||||
auto res = testFeedPtr->sub(yield, backend, sessionPtr);
|
||||
// check the response
|
||||
@@ -120,7 +126,10 @@ TEST_F(FeedLedgerTest, AutoDisconnect)
|
||||
EXPECT_EQ(testFeedPtr->count(), 1);
|
||||
EXPECT_CALL(*mockSessionPtr, send(_)).Times(0);
|
||||
|
||||
ASSERT_TRUE(slot);
|
||||
slot(sessionPtr.get());
|
||||
sessionPtr.reset();
|
||||
|
||||
EXPECT_EQ(testFeedPtr->count(), 0);
|
||||
|
||||
auto const ledgerHeader2 = CreateLedgerHeader(LEDGERHASH, 31);
|
||||
|
||||
@@ -24,13 +24,15 @@
|
||||
#include "util/SyncExecutionCtxFixture.hpp"
|
||||
#include "util/TestObject.hpp"
|
||||
#include "util/prometheus/Gauge.hpp"
|
||||
#include "web/interface/ConnectionBase.hpp"
|
||||
#include "web/SubscriptionContextInterface.hpp"
|
||||
|
||||
#include <boost/json/parse.hpp>
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
constexpr static auto ACCOUNT1 = "rh1HPuRVsYYvThxG2Bs1MfjmrVC73S16Fb";
|
||||
constexpr static auto ACCOUNT2 = "rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun";
|
||||
@@ -60,10 +62,11 @@ using FeedProposedTransactionTest = FeedBaseTest<ProposedTransactionFeed>;
|
||||
|
||||
TEST_F(FeedProposedTransactionTest, ProposedTransaction)
|
||||
{
|
||||
EXPECT_CALL(*mockSessionPtr, onDisconnect);
|
||||
testFeedPtr->sub(sessionPtr);
|
||||
EXPECT_EQ(testFeedPtr->transactionSubcount(), 1);
|
||||
|
||||
EXPECT_CALL(*mockSessionPtr, send(SharedStringJsonEq(DUMMY_TRANSACTION))).Times(1);
|
||||
EXPECT_CALL(*mockSessionPtr, send(SharedStringJsonEq(DUMMY_TRANSACTION)));
|
||||
testFeedPtr->pub(json::parse(DUMMY_TRANSACTION).get_object());
|
||||
|
||||
testFeedPtr->unsub(sessionPtr);
|
||||
@@ -75,15 +78,19 @@ TEST_F(FeedProposedTransactionTest, ProposedTransaction)
|
||||
TEST_F(FeedProposedTransactionTest, AccountProposedTransaction)
|
||||
{
|
||||
auto const account = GetAccountIDWithString(ACCOUNT1);
|
||||
|
||||
EXPECT_CALL(*mockSessionPtr, onDisconnect);
|
||||
testFeedPtr->sub(account, sessionPtr);
|
||||
EXPECT_EQ(testFeedPtr->accountSubCount(), 1);
|
||||
|
||||
std::shared_ptr<web::ConnectionBase> const sessionIdle = std::make_shared<MockSession>();
|
||||
web::SubscriptionContextPtr const sessionIdle = std::make_shared<MockSession>();
|
||||
auto const accountIdle = GetAccountIDWithString(ACCOUNT3);
|
||||
|
||||
EXPECT_CALL(*dynamic_cast<MockSession*>(sessionIdle.get()), onDisconnect);
|
||||
testFeedPtr->sub(accountIdle, sessionIdle);
|
||||
EXPECT_EQ(testFeedPtr->accountSubCount(), 2);
|
||||
|
||||
EXPECT_CALL(*mockSessionPtr, send(SharedStringJsonEq(DUMMY_TRANSACTION))).Times(1);
|
||||
EXPECT_CALL(*mockSessionPtr, send(SharedStringJsonEq(DUMMY_TRANSACTION)));
|
||||
|
||||
testFeedPtr->pub(json::parse(DUMMY_TRANSACTION).get_object());
|
||||
|
||||
@@ -97,8 +104,11 @@ TEST_F(FeedProposedTransactionTest, AccountProposedTransaction)
|
||||
TEST_F(FeedProposedTransactionTest, SubStreamAndAccount)
|
||||
{
|
||||
auto const account = GetAccountIDWithString(ACCOUNT1);
|
||||
|
||||
EXPECT_CALL(*mockSessionPtr, onDisconnect).Times(2);
|
||||
testFeedPtr->sub(account, sessionPtr);
|
||||
testFeedPtr->sub(sessionPtr);
|
||||
|
||||
EXPECT_EQ(testFeedPtr->accountSubCount(), 1);
|
||||
EXPECT_EQ(testFeedPtr->transactionSubcount(), 1);
|
||||
EXPECT_CALL(*mockSessionPtr, send(SharedStringJsonEq(DUMMY_TRANSACTION))).Times(2);
|
||||
@@ -108,7 +118,7 @@ TEST_F(FeedProposedTransactionTest, SubStreamAndAccount)
|
||||
// unsub
|
||||
testFeedPtr->unsub(account, sessionPtr);
|
||||
EXPECT_EQ(testFeedPtr->accountSubCount(), 0);
|
||||
EXPECT_CALL(*mockSessionPtr, send(SharedStringJsonEq(DUMMY_TRANSACTION))).Times(1);
|
||||
EXPECT_CALL(*mockSessionPtr, send(SharedStringJsonEq(DUMMY_TRANSACTION)));
|
||||
|
||||
testFeedPtr->pub(json::parse(DUMMY_TRANSACTION).get_object());
|
||||
|
||||
@@ -124,17 +134,18 @@ TEST_F(FeedProposedTransactionTest, AccountProposedTransactionDuplicate)
|
||||
auto const account = GetAccountIDWithString(ACCOUNT1);
|
||||
auto const account2 = GetAccountIDWithString(ACCOUNT2);
|
||||
|
||||
EXPECT_CALL(*mockSessionPtr, onDisconnect).Times(2);
|
||||
testFeedPtr->sub(account, sessionPtr);
|
||||
testFeedPtr->sub(account2, sessionPtr);
|
||||
EXPECT_EQ(testFeedPtr->accountSubCount(), 2);
|
||||
|
||||
EXPECT_CALL(*mockSessionPtr, send(SharedStringJsonEq(DUMMY_TRANSACTION))).Times(1);
|
||||
EXPECT_CALL(*mockSessionPtr, send(SharedStringJsonEq(DUMMY_TRANSACTION)));
|
||||
testFeedPtr->pub(json::parse(DUMMY_TRANSACTION).get_object());
|
||||
|
||||
// unsub account1
|
||||
testFeedPtr->unsub(account, sessionPtr);
|
||||
EXPECT_EQ(testFeedPtr->accountSubCount(), 1);
|
||||
EXPECT_CALL(*mockSessionPtr, send(SharedStringJsonEq(DUMMY_TRANSACTION))).Times(1);
|
||||
EXPECT_CALL(*mockSessionPtr, send(SharedStringJsonEq(DUMMY_TRANSACTION)));
|
||||
testFeedPtr->pub(json::parse(DUMMY_TRANSACTION).get_object());
|
||||
|
||||
// unsub account2
|
||||
@@ -146,24 +157,33 @@ TEST_F(FeedProposedTransactionTest, AccountProposedTransactionDuplicate)
|
||||
|
||||
TEST_F(FeedProposedTransactionTest, Count)
|
||||
{
|
||||
EXPECT_CALL(*mockSessionPtr, onDisconnect);
|
||||
testFeedPtr->sub(sessionPtr);
|
||||
// repeat
|
||||
testFeedPtr->sub(sessionPtr);
|
||||
EXPECT_EQ(testFeedPtr->transactionSubcount(), 1);
|
||||
|
||||
auto const account1 = GetAccountIDWithString(ACCOUNT1);
|
||||
|
||||
EXPECT_CALL(*mockSessionPtr, onDisconnect);
|
||||
testFeedPtr->sub(account1, sessionPtr);
|
||||
// repeat
|
||||
testFeedPtr->sub(account1, sessionPtr);
|
||||
EXPECT_EQ(testFeedPtr->accountSubCount(), 1);
|
||||
|
||||
auto const sessionPtr2 = std::make_shared<MockSession>();
|
||||
|
||||
EXPECT_CALL(*dynamic_cast<MockSession*>(sessionPtr2.get()), onDisconnect);
|
||||
testFeedPtr->sub(sessionPtr2);
|
||||
EXPECT_EQ(testFeedPtr->transactionSubcount(), 2);
|
||||
|
||||
auto const account2 = GetAccountIDWithString(ACCOUNT2);
|
||||
|
||||
EXPECT_CALL(*dynamic_cast<MockSession*>(sessionPtr2.get()), onDisconnect);
|
||||
testFeedPtr->sub(account2, sessionPtr2);
|
||||
EXPECT_EQ(testFeedPtr->accountSubCount(), 2);
|
||||
|
||||
EXPECT_CALL(*dynamic_cast<MockSession*>(sessionPtr2.get()), onDisconnect);
|
||||
testFeedPtr->sub(account1, sessionPtr2);
|
||||
EXPECT_EQ(testFeedPtr->accountSubCount(), 3);
|
||||
|
||||
@@ -184,31 +204,51 @@ TEST_F(FeedProposedTransactionTest, Count)
|
||||
|
||||
TEST_F(FeedProposedTransactionTest, AutoDisconnect)
|
||||
{
|
||||
std::vector<web::SubscriptionContextInterface::OnDisconnectSlot> sessionOnDisconnectSlots;
|
||||
ON_CALL(*mockSessionPtr, onDisconnect).WillByDefault([&sessionOnDisconnectSlots](auto slot) {
|
||||
sessionOnDisconnectSlots.push_back(slot);
|
||||
});
|
||||
EXPECT_CALL(*mockSessionPtr, onDisconnect);
|
||||
testFeedPtr->sub(sessionPtr);
|
||||
// repeat
|
||||
testFeedPtr->sub(sessionPtr);
|
||||
EXPECT_EQ(testFeedPtr->transactionSubcount(), 1);
|
||||
|
||||
auto const account1 = GetAccountIDWithString(ACCOUNT1);
|
||||
|
||||
EXPECT_CALL(*mockSessionPtr, onDisconnect);
|
||||
testFeedPtr->sub(account1, sessionPtr);
|
||||
// repeat
|
||||
testFeedPtr->sub(account1, sessionPtr);
|
||||
EXPECT_EQ(testFeedPtr->accountSubCount(), 1);
|
||||
|
||||
auto sessionPtr2 = std::make_shared<MockSession>();
|
||||
auto mockSessionPtr2 = dynamic_cast<MockSession*>(sessionPtr2.get());
|
||||
std::vector<web::SubscriptionContextInterface::OnDisconnectSlot> session2OnDisconnectSlots;
|
||||
ON_CALL(*mockSessionPtr2, onDisconnect).WillByDefault([&session2OnDisconnectSlots](auto slot) {
|
||||
session2OnDisconnectSlots.push_back(slot);
|
||||
});
|
||||
|
||||
EXPECT_CALL(*mockSessionPtr2, onDisconnect);
|
||||
testFeedPtr->sub(sessionPtr2);
|
||||
EXPECT_EQ(testFeedPtr->transactionSubcount(), 2);
|
||||
|
||||
auto const account2 = GetAccountIDWithString(ACCOUNT2);
|
||||
|
||||
EXPECT_CALL(*mockSessionPtr2, onDisconnect);
|
||||
testFeedPtr->sub(account2, sessionPtr2);
|
||||
EXPECT_EQ(testFeedPtr->accountSubCount(), 2);
|
||||
|
||||
EXPECT_CALL(*mockSessionPtr2, onDisconnect);
|
||||
testFeedPtr->sub(account1, sessionPtr2);
|
||||
EXPECT_EQ(testFeedPtr->accountSubCount(), 3);
|
||||
|
||||
std::ranges::for_each(session2OnDisconnectSlots, [&sessionPtr2](auto& slot) { slot(sessionPtr2.get()); });
|
||||
sessionPtr2.reset();
|
||||
EXPECT_EQ(testFeedPtr->accountSubCount(), 1);
|
||||
EXPECT_EQ(testFeedPtr->transactionSubcount(), 1);
|
||||
|
||||
std::ranges::for_each(sessionOnDisconnectSlots, [this](auto& slot) { slot(sessionPtr.get()); });
|
||||
sessionPtr.reset();
|
||||
EXPECT_EQ(testFeedPtr->accountSubCount(), 0);
|
||||
EXPECT_EQ(testFeedPtr->transactionSubcount(), 0);
|
||||
@@ -216,21 +256,9 @@ TEST_F(FeedProposedTransactionTest, AutoDisconnect)
|
||||
|
||||
struct ProposedTransactionFeedMockPrometheusTest : WithMockPrometheus, SyncExecutionCtxFixture {
|
||||
protected:
|
||||
std::shared_ptr<web::ConnectionBase> sessionPtr;
|
||||
std::shared_ptr<ProposedTransactionFeed> testFeedPtr;
|
||||
|
||||
void
|
||||
SetUp() override
|
||||
{
|
||||
testFeedPtr = std::make_shared<ProposedTransactionFeed>(ctx);
|
||||
sessionPtr = std::make_shared<MockSession>();
|
||||
}
|
||||
void
|
||||
TearDown() override
|
||||
{
|
||||
sessionPtr.reset();
|
||||
testFeedPtr.reset();
|
||||
}
|
||||
web::SubscriptionContextPtr sessionPtr = std::make_shared<MockSession>();
|
||||
std::shared_ptr<ProposedTransactionFeed> testFeedPtr = std::make_shared<ProposedTransactionFeed>(ctx);
|
||||
MockSession* mockSessionPtr = dynamic_cast<MockSession*>(sessionPtr.get());
|
||||
};
|
||||
|
||||
TEST_F(ProposedTransactionFeedMockPrometheusTest, subUnsub)
|
||||
@@ -243,10 +271,12 @@ TEST_F(ProposedTransactionFeedMockPrometheusTest, subUnsub)
|
||||
EXPECT_CALL(counterAccount, add(1));
|
||||
EXPECT_CALL(counterAccount, add(-1));
|
||||
|
||||
EXPECT_CALL(*mockSessionPtr, onDisconnect);
|
||||
testFeedPtr->sub(sessionPtr);
|
||||
testFeedPtr->unsub(sessionPtr);
|
||||
|
||||
auto const account = GetAccountIDWithString(ACCOUNT1);
|
||||
EXPECT_CALL(*mockSessionPtr, onDisconnect);
|
||||
testFeedPtr->sub(account, sessionPtr);
|
||||
testFeedPtr->unsub(account, sessionPtr);
|
||||
}
|
||||
@@ -256,15 +286,24 @@ TEST_F(ProposedTransactionFeedMockPrometheusTest, AutoDisconnect)
|
||||
auto& counterTx = makeMock<GaugeInt>("subscriptions_current_number", "{stream=\"tx_proposed\"}");
|
||||
auto& counterAccount = makeMock<GaugeInt>("subscriptions_current_number", "{stream=\"account_proposed\"}");
|
||||
|
||||
std::vector<web::SubscriptionContextInterface::OnDisconnectSlot> sessionOnDisconnectSlots;
|
||||
|
||||
EXPECT_CALL(counterTx, add(1));
|
||||
EXPECT_CALL(counterTx, add(-1));
|
||||
EXPECT_CALL(counterAccount, add(1));
|
||||
EXPECT_CALL(counterAccount, add(-1));
|
||||
|
||||
EXPECT_CALL(*mockSessionPtr, onDisconnect).WillOnce([&sessionOnDisconnectSlots](auto slot) {
|
||||
sessionOnDisconnectSlots.push_back(slot);
|
||||
});
|
||||
testFeedPtr->sub(sessionPtr);
|
||||
|
||||
auto const account = GetAccountIDWithString(ACCOUNT1);
|
||||
EXPECT_CALL(*mockSessionPtr, onDisconnect).WillOnce([&sessionOnDisconnectSlots](auto slot) {
|
||||
sessionOnDisconnectSlots.push_back(slot);
|
||||
});
|
||||
testFeedPtr->sub(account, sessionPtr);
|
||||
|
||||
std::ranges::for_each(sessionOnDisconnectSlots, [this](auto& slot) { slot(sessionPtr.get()); });
|
||||
sessionPtr.reset();
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
#include "util/SyncExecutionCtxFixture.hpp"
|
||||
#include "util/async/AnyExecutionContext.hpp"
|
||||
#include "util/prometheus/Gauge.hpp"
|
||||
#include "web/interface/ConnectionBase.hpp"
|
||||
#include "web/SubscriptionContextInterface.hpp"
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
@@ -38,23 +38,9 @@ using namespace util::prometheus;
|
||||
|
||||
struct FeedBaseMockPrometheusTest : WithMockPrometheus, SyncExecutionCtxFixture {
|
||||
protected:
|
||||
std::shared_ptr<web::ConnectionBase> sessionPtr;
|
||||
std::shared_ptr<SingleFeedBase> testFeedPtr;
|
||||
MockSession* mockSessionPtr = nullptr;
|
||||
|
||||
void
|
||||
SetUp() override
|
||||
{
|
||||
testFeedPtr = std::make_shared<SingleFeedBase>(ctx, "testFeed");
|
||||
sessionPtr = std::make_shared<MockSession>();
|
||||
mockSessionPtr = dynamic_cast<MockSession*>(sessionPtr.get());
|
||||
}
|
||||
void
|
||||
TearDown() override
|
||||
{
|
||||
sessionPtr.reset();
|
||||
testFeedPtr.reset();
|
||||
}
|
||||
web::SubscriptionContextPtr sessionPtr = std::make_shared<MockSession>();
|
||||
std::shared_ptr<SingleFeedBase> testFeedPtr = std::make_shared<SingleFeedBase>(ctx, "testFeed");
|
||||
MockSession* mockSessionPtr = dynamic_cast<MockSession*>(sessionPtr.get());
|
||||
};
|
||||
|
||||
TEST_F(FeedBaseMockPrometheusTest, subUnsub)
|
||||
@@ -63,6 +49,7 @@ TEST_F(FeedBaseMockPrometheusTest, subUnsub)
|
||||
EXPECT_CALL(counter, add(1));
|
||||
EXPECT_CALL(counter, add(-1));
|
||||
|
||||
EXPECT_CALL(*mockSessionPtr, onDisconnect);
|
||||
testFeedPtr->sub(sessionPtr);
|
||||
testFeedPtr->unsub(sessionPtr);
|
||||
}
|
||||
@@ -73,7 +60,10 @@ TEST_F(FeedBaseMockPrometheusTest, AutoUnsub)
|
||||
EXPECT_CALL(counter, add(1));
|
||||
EXPECT_CALL(counter, add(-1));
|
||||
|
||||
web::SubscriptionContextInterface::OnDisconnectSlot slot;
|
||||
EXPECT_CALL(*mockSessionPtr, onDisconnect).WillOnce(testing::SaveArg<0>(&slot));
|
||||
testFeedPtr->sub(sessionPtr);
|
||||
slot(sessionPtr.get());
|
||||
sessionPtr.reset();
|
||||
}
|
||||
|
||||
@@ -88,7 +78,8 @@ using SingleFeedBaseTest = FeedBaseTest<NamedSingleFeedTest>;
|
||||
|
||||
TEST_F(SingleFeedBaseTest, Test)
|
||||
{
|
||||
EXPECT_CALL(*mockSessionPtr, send(SharedStringJsonEq(FEED))).Times(1);
|
||||
EXPECT_CALL(*mockSessionPtr, onDisconnect);
|
||||
EXPECT_CALL(*mockSessionPtr, send(SharedStringJsonEq(FEED)));
|
||||
testFeedPtr->sub(sessionPtr);
|
||||
EXPECT_EQ(testFeedPtr->count(), 1);
|
||||
testFeedPtr->pub(FEED);
|
||||
@@ -100,17 +91,21 @@ TEST_F(SingleFeedBaseTest, Test)
|
||||
|
||||
TEST_F(SingleFeedBaseTest, TestAutoDisconnect)
|
||||
{
|
||||
EXPECT_CALL(*mockSessionPtr, send(SharedStringJsonEq(FEED))).Times(1);
|
||||
web::SubscriptionContextInterface::OnDisconnectSlot slot;
|
||||
EXPECT_CALL(*mockSessionPtr, onDisconnect).WillOnce(testing::SaveArg<0>(&slot));
|
||||
EXPECT_CALL(*mockSessionPtr, send(SharedStringJsonEq(FEED)));
|
||||
testFeedPtr->sub(sessionPtr);
|
||||
EXPECT_EQ(testFeedPtr->count(), 1);
|
||||
testFeedPtr->pub(FEED);
|
||||
|
||||
slot(sessionPtr.get());
|
||||
sessionPtr.reset();
|
||||
EXPECT_EQ(testFeedPtr->count(), 0);
|
||||
}
|
||||
|
||||
TEST_F(SingleFeedBaseTest, RepeatSub)
|
||||
{
|
||||
EXPECT_CALL(*mockSessionPtr, onDisconnect);
|
||||
testFeedPtr->sub(sessionPtr);
|
||||
EXPECT_EQ(testFeedPtr->count(), 1);
|
||||
testFeedPtr->sub(sessionPtr);
|
||||
|
||||
@@ -20,13 +20,14 @@
|
||||
#include "data/Types.hpp"
|
||||
#include "feed/FeedTestUtil.hpp"
|
||||
#include "feed/SubscriptionManager.hpp"
|
||||
#include "util/Assert.hpp"
|
||||
#include "util/MockBackendTestFixture.hpp"
|
||||
#include "util/MockPrometheus.hpp"
|
||||
#include "util/MockWsBase.hpp"
|
||||
#include "util/TestObject.hpp"
|
||||
#include "util/async/context/BasicExecutionContext.hpp"
|
||||
#include "util/async/context/SyncExecutionContext.hpp"
|
||||
#include "web/interface/ConnectionBase.hpp"
|
||||
#include "web/SubscriptionContextInterface.hpp"
|
||||
|
||||
#include <boost/asio/io_context.hpp>
|
||||
#include <boost/asio/spawn.hpp>
|
||||
@@ -39,8 +40,8 @@
|
||||
#include <xrpl/protocol/Issue.h>
|
||||
#include <xrpl/protocol/STObject.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
constexpr static auto ACCOUNT1 = "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn";
|
||||
@@ -56,25 +57,15 @@ using namespace feed::impl;
|
||||
template <class Execution>
|
||||
class SubscriptionManagerBaseTest : public util::prometheus::WithPrometheus, public MockBackendTest {
|
||||
protected:
|
||||
std::shared_ptr<SubscriptionManager> subscriptionManagerPtr;
|
||||
std::shared_ptr<web::ConnectionBase> session;
|
||||
MockSession* sessionPtr = nullptr;
|
||||
|
||||
void
|
||||
SetUp() override
|
||||
SubscriptionManagerBaseTest()
|
||||
{
|
||||
subscriptionManagerPtr = std::make_shared<SubscriptionManager>(Execution(2), backend);
|
||||
session = std::make_shared<MockSession>();
|
||||
session->apiSubVersion = 1;
|
||||
sessionPtr = dynamic_cast<MockSession*>(session.get());
|
||||
ASSERT(sessionPtr != nullptr, "dynamic_cast failed");
|
||||
}
|
||||
|
||||
void
|
||||
TearDown() override
|
||||
{
|
||||
session.reset();
|
||||
subscriptionManagerPtr.reset();
|
||||
}
|
||||
std::shared_ptr<SubscriptionManager> subscriptionManagerPtr =
|
||||
std::make_shared<SubscriptionManager>(Execution(2), backend);
|
||||
web::SubscriptionContextPtr session = std::make_shared<MockSession>();
|
||||
MockSession* sessionPtr = dynamic_cast<MockSession*>(session.get());
|
||||
};
|
||||
|
||||
using SubscriptionManagerTest = SubscriptionManagerBaseTest<util::async::SyncExecutionContext>;
|
||||
@@ -83,7 +74,9 @@ using SubscriptionManagerAsyncTest = SubscriptionManagerBaseTest<util::async::Po
|
||||
|
||||
TEST_F(SubscriptionManagerAsyncTest, MultipleThreadCtx)
|
||||
{
|
||||
EXPECT_CALL(*sessionPtr, onDisconnect);
|
||||
subscriptionManagerPtr->subManifest(session);
|
||||
EXPECT_CALL(*sessionPtr, onDisconnect);
|
||||
subscriptionManagerPtr->subValidation(session);
|
||||
|
||||
constexpr static auto jsonManifest = R"({"manifest":"test"})";
|
||||
@@ -97,7 +90,9 @@ TEST_F(SubscriptionManagerAsyncTest, MultipleThreadCtx)
|
||||
|
||||
TEST_F(SubscriptionManagerAsyncTest, MultipleThreadCtxSessionDieEarly)
|
||||
{
|
||||
EXPECT_CALL(*sessionPtr, onDisconnect);
|
||||
subscriptionManagerPtr->subManifest(session);
|
||||
EXPECT_CALL(*sessionPtr, onDisconnect);
|
||||
subscriptionManagerPtr->subValidation(session);
|
||||
|
||||
EXPECT_CALL(*sessionPtr, send(testing::_)).Times(0);
|
||||
@@ -121,8 +116,18 @@ TEST_F(SubscriptionManagerTest, ReportCurrentSubscriber)
|
||||
"books":2,
|
||||
"book_changes":2
|
||||
})";
|
||||
std::shared_ptr<web::ConnectionBase> const session1 = std::make_shared<MockSession>();
|
||||
std::shared_ptr<web::ConnectionBase> session2 = std::make_shared<MockSession>();
|
||||
web::SubscriptionContextPtr const session1 = std::make_shared<MockSession>();
|
||||
MockSession* mockSession1 = dynamic_cast<MockSession*>(session1.get());
|
||||
|
||||
web::SubscriptionContextPtr session2 = std::make_shared<MockSession>();
|
||||
MockSession* mockSession2 = dynamic_cast<MockSession*>(session2.get());
|
||||
std::vector<web::SubscriptionContextInterface::OnDisconnectSlot> session2OnDisconnectSlots;
|
||||
ON_CALL(*mockSession2, onDisconnect).WillByDefault([&session2OnDisconnectSlots](auto slot) {
|
||||
session2OnDisconnectSlots.push_back(slot);
|
||||
});
|
||||
|
||||
EXPECT_CALL(*mockSession1, onDisconnect).Times(5);
|
||||
EXPECT_CALL(*mockSession2, onDisconnect).Times(4);
|
||||
subscriptionManagerPtr->subBookChanges(session1);
|
||||
subscriptionManagerPtr->subBookChanges(session2);
|
||||
subscriptionManagerPtr->subManifest(session1);
|
||||
@@ -130,7 +135,10 @@ TEST_F(SubscriptionManagerTest, ReportCurrentSubscriber)
|
||||
subscriptionManagerPtr->subProposedTransactions(session1);
|
||||
subscriptionManagerPtr->subProposedTransactions(session2);
|
||||
subscriptionManagerPtr->subTransactions(session1);
|
||||
session2->apiSubVersion = 2;
|
||||
|
||||
// session2->apiSubVersion = 2;
|
||||
EXPECT_CALL(*mockSession1, onDisconnect).Times(5);
|
||||
EXPECT_CALL(*mockSession2, onDisconnect).Times(6);
|
||||
subscriptionManagerPtr->subTransactions(session2);
|
||||
subscriptionManagerPtr->subValidation(session1);
|
||||
subscriptionManagerPtr->subValidation(session2);
|
||||
@@ -172,6 +180,7 @@ TEST_F(SubscriptionManagerTest, ReportCurrentSubscriber)
|
||||
checkResult(subscriptionManagerPtr->report(), 1);
|
||||
|
||||
// count down when session disconnect
|
||||
std::ranges::for_each(session2OnDisconnectSlots, [&session2](auto& slot) { slot(session2.get()); });
|
||||
session2.reset();
|
||||
checkResult(subscriptionManagerPtr->report(), 0);
|
||||
}
|
||||
@@ -179,7 +188,8 @@ TEST_F(SubscriptionManagerTest, ReportCurrentSubscriber)
|
||||
TEST_F(SubscriptionManagerTest, ManifestTest)
|
||||
{
|
||||
constexpr static auto dummyManifest = R"({"manifest":"test"})";
|
||||
EXPECT_CALL(*sessionPtr, send(SharedStringJsonEq(dummyManifest))).Times(1);
|
||||
EXPECT_CALL(*sessionPtr, onDisconnect);
|
||||
EXPECT_CALL(*sessionPtr, send(SharedStringJsonEq(dummyManifest)));
|
||||
subscriptionManagerPtr->subManifest(session);
|
||||
subscriptionManagerPtr->forwardManifest(json::parse(dummyManifest).get_object());
|
||||
|
||||
@@ -191,7 +201,8 @@ TEST_F(SubscriptionManagerTest, ManifestTest)
|
||||
TEST_F(SubscriptionManagerTest, ValidationTest)
|
||||
{
|
||||
constexpr static auto dummy = R"({"validation":"test"})";
|
||||
EXPECT_CALL(*sessionPtr, send(SharedStringJsonEq(dummy))).Times(1);
|
||||
EXPECT_CALL(*sessionPtr, onDisconnect);
|
||||
EXPECT_CALL(*sessionPtr, send(SharedStringJsonEq(dummy)));
|
||||
subscriptionManagerPtr->subValidation(session);
|
||||
subscriptionManagerPtr->forwardValidation(json::parse(dummy).get_object());
|
||||
|
||||
@@ -202,6 +213,7 @@ TEST_F(SubscriptionManagerTest, ValidationTest)
|
||||
|
||||
TEST_F(SubscriptionManagerTest, BookChangesTest)
|
||||
{
|
||||
EXPECT_CALL(*sessionPtr, onDisconnect);
|
||||
subscriptionManagerPtr->subBookChanges(session);
|
||||
EXPECT_EQ(subscriptionManagerPtr->report()["book_changes"], 1);
|
||||
|
||||
@@ -234,7 +246,7 @@ TEST_F(SubscriptionManagerTest, BookChangesTest)
|
||||
}
|
||||
]
|
||||
})";
|
||||
EXPECT_CALL(*sessionPtr, send(SharedStringJsonEq(bookChangePublish))).Times(1);
|
||||
EXPECT_CALL(*sessionPtr, send(SharedStringJsonEq(bookChangePublish)));
|
||||
|
||||
subscriptionManagerPtr->pubBookChanges(ledgerHeader, transactions);
|
||||
|
||||
@@ -266,6 +278,7 @@ TEST_F(SubscriptionManagerTest, LedgerTest)
|
||||
})";
|
||||
boost::asio::io_context ctx;
|
||||
boost::asio::spawn(ctx, [this](boost::asio::yield_context yield) {
|
||||
EXPECT_CALL(*sessionPtr, onDisconnect);
|
||||
auto const res = subscriptionManagerPtr->subLedger(yield, session);
|
||||
// check the response
|
||||
EXPECT_EQ(res, json::parse(LedgerResponse));
|
||||
@@ -289,7 +302,7 @@ TEST_F(SubscriptionManagerTest, LedgerTest)
|
||||
"validated_ledgers":"10-31",
|
||||
"txn_count":8
|
||||
})";
|
||||
EXPECT_CALL(*sessionPtr, send(SharedStringJsonEq(ledgerPub))).Times(1);
|
||||
EXPECT_CALL(*sessionPtr, send(SharedStringJsonEq(ledgerPub)));
|
||||
subscriptionManagerPtr->pubLedger(ledgerHeader2, fee2, "10-31", 8);
|
||||
|
||||
// test unsub
|
||||
@@ -302,6 +315,7 @@ TEST_F(SubscriptionManagerTest, TransactionTest)
|
||||
auto const issue1 = GetIssue(CURRENCY, ISSUER);
|
||||
auto const account = GetAccountIDWithString(ISSUER);
|
||||
ripple::Book const book{ripple::xrpIssue(), issue1};
|
||||
EXPECT_CALL(*sessionPtr, onDisconnect).Times(3);
|
||||
subscriptionManagerPtr->subBook(book, session);
|
||||
subscriptionManagerPtr->subTransactions(session);
|
||||
subscriptionManagerPtr->subAccount(account, session);
|
||||
@@ -378,6 +392,7 @@ TEST_F(SubscriptionManagerTest, TransactionTest)
|
||||
"engine_result_message":"The transaction was applied. Only final in a validated ledger."
|
||||
})";
|
||||
EXPECT_CALL(*sessionPtr, send(SharedStringJsonEq(OrderbookPublish))).Times(3);
|
||||
EXPECT_CALL(*sessionPtr, apiSubversion).Times(3).WillRepeatedly(testing::Return(1));
|
||||
subscriptionManagerPtr->pubTransaction(trans1, ledgerHeader);
|
||||
|
||||
subscriptionManagerPtr->unsubBook(book, session);
|
||||
@@ -391,6 +406,7 @@ TEST_F(SubscriptionManagerTest, TransactionTest)
|
||||
TEST_F(SubscriptionManagerTest, ProposedTransactionTest)
|
||||
{
|
||||
auto const account = GetAccountIDWithString(ACCOUNT1);
|
||||
EXPECT_CALL(*sessionPtr, onDisconnect).Times(4);
|
||||
subscriptionManagerPtr->subProposedAccount(account, session);
|
||||
subscriptionManagerPtr->subProposedTransactions(session);
|
||||
EXPECT_EQ(subscriptionManagerPtr->report()["accounts_proposed"], 1);
|
||||
@@ -476,6 +492,7 @@ TEST_F(SubscriptionManagerTest, ProposedTransactionTest)
|
||||
|
||||
auto const metaObj = CreateMetaDataForBookChange(CURRENCY, ACCOUNT1, 22, 3, 1, 1, 3);
|
||||
trans1.metadata = metaObj.getSerializer().peekData();
|
||||
EXPECT_CALL(*sessionPtr, apiSubversion).Times(2).WillRepeatedly(testing::Return(1));
|
||||
subscriptionManagerPtr->pubTransaction(trans1, ledgerHeader);
|
||||
|
||||
// unsub account1
|
||||
@@ -487,6 +504,7 @@ TEST_F(SubscriptionManagerTest, ProposedTransactionTest)
|
||||
|
||||
TEST_F(SubscriptionManagerTest, DuplicateResponseSubTxAndProposedTx)
|
||||
{
|
||||
EXPECT_CALL(*sessionPtr, onDisconnect).Times(3);
|
||||
subscriptionManagerPtr->subProposedTransactions(session);
|
||||
subscriptionManagerPtr->subTransactions(session);
|
||||
EXPECT_EQ(subscriptionManagerPtr->report()["transactions"], 1);
|
||||
@@ -502,6 +520,7 @@ TEST_F(SubscriptionManagerTest, DuplicateResponseSubTxAndProposedTx)
|
||||
|
||||
auto const metaObj = CreateMetaDataForBookChange(CURRENCY, ACCOUNT1, 22, 3, 1, 1, 3);
|
||||
trans1.metadata = metaObj.getSerializer().peekData();
|
||||
EXPECT_CALL(*sessionPtr, apiSubversion).Times(2).WillRepeatedly(testing::Return(1));
|
||||
subscriptionManagerPtr->pubTransaction(trans1, ledgerHeader);
|
||||
|
||||
subscriptionManagerPtr->unsubTransactions(session);
|
||||
@@ -513,12 +532,13 @@ TEST_F(SubscriptionManagerTest, DuplicateResponseSubTxAndProposedTx)
|
||||
TEST_F(SubscriptionManagerTest, NoDuplicateResponseSubAccountAndProposedAccount)
|
||||
{
|
||||
auto const account = GetAccountIDWithString(ACCOUNT1);
|
||||
EXPECT_CALL(*sessionPtr, onDisconnect).Times(3);
|
||||
subscriptionManagerPtr->subProposedAccount(account, session);
|
||||
subscriptionManagerPtr->subAccount(account, session);
|
||||
EXPECT_EQ(subscriptionManagerPtr->report()["accounts_proposed"], 1);
|
||||
EXPECT_EQ(subscriptionManagerPtr->report()["account"], 1);
|
||||
|
||||
EXPECT_CALL(*sessionPtr, send(testing::_)).Times(1);
|
||||
EXPECT_CALL(*sessionPtr, send(testing::_));
|
||||
|
||||
auto const ledgerHeader = CreateLedgerHeader(LEDGERHASH, 33);
|
||||
auto trans1 = TransactionAndMetadata();
|
||||
@@ -528,6 +548,7 @@ TEST_F(SubscriptionManagerTest, NoDuplicateResponseSubAccountAndProposedAccount)
|
||||
|
||||
auto const metaObj = CreateMetaDataForBookChange(CURRENCY, ACCOUNT1, 22, 3, 1, 1, 3);
|
||||
trans1.metadata = metaObj.getSerializer().peekData();
|
||||
EXPECT_CALL(*sessionPtr, apiSubversion).WillRepeatedly(testing::Return(1));
|
||||
subscriptionManagerPtr->pubTransaction(trans1, ledgerHeader);
|
||||
|
||||
// unsub account1
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
#include "feed/impl/TrackableSignal.hpp"
|
||||
#include "feed/impl/TrackableSignalMap.hpp"
|
||||
#include "util/MockWsBase.hpp"
|
||||
#include "web/interface/ConnectionBase.hpp"
|
||||
#include "web/SubscriptionContextInterface.hpp"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
@@ -30,24 +30,12 @@
|
||||
using namespace testing;
|
||||
|
||||
struct FeedTrackableSignalTests : Test {
|
||||
protected:
|
||||
std::shared_ptr<web::ConnectionBase> sessionPtr;
|
||||
|
||||
void
|
||||
SetUp() override
|
||||
{
|
||||
sessionPtr = std::make_shared<MockSession>();
|
||||
}
|
||||
|
||||
void
|
||||
TearDown() override
|
||||
{
|
||||
}
|
||||
web::SubscriptionContextPtr sessionPtr = std::make_shared<MockSession>();
|
||||
};
|
||||
|
||||
TEST_F(FeedTrackableSignalTests, Connect)
|
||||
{
|
||||
feed::impl::TrackableSignal<web::ConnectionBase, std::string> signal;
|
||||
feed::impl::TrackableSignal<web::SubscriptionContextInterface, std::string> signal;
|
||||
std::string testString;
|
||||
auto const slot = [&](std::string const& s) { testString += s; };
|
||||
EXPECT_TRUE(signal.connectTrackableSlot(sessionPtr, slot));
|
||||
@@ -69,7 +57,7 @@ TEST_F(FeedTrackableSignalTests, Connect)
|
||||
|
||||
TEST_F(FeedTrackableSignalTests, AutoDisconnect)
|
||||
{
|
||||
feed::impl::TrackableSignal<web::ConnectionBase, std::string> signal;
|
||||
feed::impl::TrackableSignal<web::SubscriptionContextInterface, std::string> signal;
|
||||
std::string testString;
|
||||
auto const slot = [&](std::string const& s) { testString += s; };
|
||||
EXPECT_TRUE(signal.connectTrackableSlot(sessionPtr, slot));
|
||||
@@ -91,7 +79,7 @@ TEST_F(FeedTrackableSignalTests, AutoDisconnect)
|
||||
|
||||
TEST_F(FeedTrackableSignalTests, MapConnect)
|
||||
{
|
||||
feed::impl::TrackableSignalMap<std::string, web::ConnectionBase, std::string> signalMap;
|
||||
feed::impl::TrackableSignalMap<std::string, web::SubscriptionContextInterface, std::string> signalMap;
|
||||
std::string testString;
|
||||
auto const slot = [&](std::string const& s) { testString += s; };
|
||||
EXPECT_TRUE(signalMap.connectTrackableSlot(sessionPtr, "test", slot));
|
||||
@@ -115,7 +103,7 @@ TEST_F(FeedTrackableSignalTests, MapConnect)
|
||||
|
||||
TEST_F(FeedTrackableSignalTests, MapAutoDisconnect)
|
||||
{
|
||||
feed::impl::TrackableSignalMap<std::string, web::ConnectionBase, std::string> signalMap;
|
||||
feed::impl::TrackableSignalMap<std::string, web::SubscriptionContextInterface, std::string> signalMap;
|
||||
std::string testString;
|
||||
auto const slot = [&](std::string const& s) { testString += s; };
|
||||
EXPECT_TRUE(signalMap.connectTrackableSlot(sessionPtr, "test", slot));
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
#include "util/SyncExecutionCtxFixture.hpp"
|
||||
#include "util/TestObject.hpp"
|
||||
#include "util/prometheus/Gauge.hpp"
|
||||
#include "web/interface/ConnectionBase.hpp"
|
||||
#include "web/SubscriptionContextInterface.hpp"
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
@@ -39,7 +39,9 @@
|
||||
#include <xrpl/protocol/STObject.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
constexpr static auto ACCOUNT1 = "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn";
|
||||
constexpr static auto ACCOUNT2 = "rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun";
|
||||
@@ -164,6 +166,7 @@ using FeedTransactionTest = FeedBaseTest<TransactionFeed>;
|
||||
|
||||
TEST_F(FeedTransactionTest, SubTransactionV1)
|
||||
{
|
||||
EXPECT_CALL(*mockSessionPtr, onDisconnect);
|
||||
testFeedPtr->sub(sessionPtr);
|
||||
EXPECT_EQ(testFeedPtr->transactionSubCount(), 1);
|
||||
|
||||
@@ -173,7 +176,9 @@ TEST_F(FeedTransactionTest, SubTransactionV1)
|
||||
trans1.transaction = obj.getSerializer().peekData();
|
||||
trans1.ledgerSequence = 32;
|
||||
trans1.metadata = CreatePaymentTransactionMetaObject(ACCOUNT1, ACCOUNT2, 110, 30, 22).getSerializer().peekData();
|
||||
EXPECT_CALL(*mockSessionPtr, send(SharedStringJsonEq(TRAN_V1))).Times(1);
|
||||
|
||||
EXPECT_CALL(*mockSessionPtr, apiSubversion).WillOnce(testing::Return(1));
|
||||
EXPECT_CALL(*mockSessionPtr, send(SharedStringJsonEq(TRAN_V1)));
|
||||
testFeedPtr->pub(trans1, ledgerHeader, backend);
|
||||
|
||||
testFeedPtr->unsub(sessionPtr);
|
||||
@@ -183,6 +188,7 @@ TEST_F(FeedTransactionTest, SubTransactionV1)
|
||||
|
||||
TEST_F(FeedTransactionTest, SubTransactionForProposedTx)
|
||||
{
|
||||
EXPECT_CALL(*mockSessionPtr, onDisconnect);
|
||||
testFeedPtr->subProposed(sessionPtr);
|
||||
EXPECT_EQ(testFeedPtr->transactionSubCount(), 0);
|
||||
|
||||
@@ -193,7 +199,8 @@ TEST_F(FeedTransactionTest, SubTransactionForProposedTx)
|
||||
trans1.ledgerSequence = 32;
|
||||
trans1.metadata = CreatePaymentTransactionMetaObject(ACCOUNT1, ACCOUNT2, 110, 30, 22).getSerializer().peekData();
|
||||
|
||||
EXPECT_CALL(*mockSessionPtr, send(SharedStringJsonEq(TRAN_V1))).Times(1);
|
||||
EXPECT_CALL(*mockSessionPtr, apiSubversion).WillOnce(testing::Return(1));
|
||||
EXPECT_CALL(*mockSessionPtr, send(SharedStringJsonEq(TRAN_V1)));
|
||||
testFeedPtr->pub(trans1, ledgerHeader, backend);
|
||||
|
||||
testFeedPtr->unsubProposed(sessionPtr);
|
||||
@@ -202,7 +209,7 @@ TEST_F(FeedTransactionTest, SubTransactionForProposedTx)
|
||||
|
||||
TEST_F(FeedTransactionTest, SubTransactionV2)
|
||||
{
|
||||
sessionPtr->apiSubVersion = 2;
|
||||
EXPECT_CALL(*mockSessionPtr, onDisconnect);
|
||||
testFeedPtr->sub(sessionPtr);
|
||||
EXPECT_EQ(testFeedPtr->transactionSubCount(), 1);
|
||||
|
||||
@@ -213,7 +220,8 @@ TEST_F(FeedTransactionTest, SubTransactionV2)
|
||||
trans1.ledgerSequence = 32;
|
||||
trans1.metadata = CreatePaymentTransactionMetaObject(ACCOUNT1, ACCOUNT2, 110, 30, 22).getSerializer().peekData();
|
||||
|
||||
EXPECT_CALL(*mockSessionPtr, send(SharedStringJsonEq(TRAN_V2))).Times(1);
|
||||
EXPECT_CALL(*mockSessionPtr, apiSubversion).WillOnce(testing::Return(2));
|
||||
EXPECT_CALL(*mockSessionPtr, send(SharedStringJsonEq(TRAN_V2)));
|
||||
testFeedPtr->pub(trans1, ledgerHeader, backend);
|
||||
|
||||
testFeedPtr->unsub(sessionPtr);
|
||||
@@ -225,6 +233,8 @@ TEST_F(FeedTransactionTest, SubTransactionV2)
|
||||
TEST_F(FeedTransactionTest, SubAccountV1)
|
||||
{
|
||||
auto const account = GetAccountIDWithString(ACCOUNT1);
|
||||
|
||||
EXPECT_CALL(*mockSessionPtr, onDisconnect);
|
||||
testFeedPtr->sub(account, sessionPtr);
|
||||
EXPECT_EQ(testFeedPtr->accountSubCount(), 1);
|
||||
|
||||
@@ -236,7 +246,8 @@ TEST_F(FeedTransactionTest, SubAccountV1)
|
||||
trans1.ledgerSequence = 32;
|
||||
trans1.metadata = CreatePaymentTransactionMetaObject(ACCOUNT1, ACCOUNT2, 110, 30, 22).getSerializer().peekData();
|
||||
|
||||
EXPECT_CALL(*mockSessionPtr, send(SharedStringJsonEq(TRAN_V1))).Times(1);
|
||||
EXPECT_CALL(*mockSessionPtr, apiSubversion).WillOnce(testing::Return(1));
|
||||
EXPECT_CALL(*mockSessionPtr, send(SharedStringJsonEq(TRAN_V1)));
|
||||
testFeedPtr->pub(trans1, ledgerHeader, backend);
|
||||
|
||||
testFeedPtr->unsub(account, sessionPtr);
|
||||
@@ -248,6 +259,8 @@ TEST_F(FeedTransactionTest, SubAccountV1)
|
||||
TEST_F(FeedTransactionTest, SubForProposedAccount)
|
||||
{
|
||||
auto const account = GetAccountIDWithString(ACCOUNT1);
|
||||
|
||||
EXPECT_CALL(*mockSessionPtr, onDisconnect);
|
||||
testFeedPtr->subProposed(account, sessionPtr);
|
||||
EXPECT_EQ(testFeedPtr->accountSubCount(), 0);
|
||||
|
||||
@@ -259,7 +272,8 @@ TEST_F(FeedTransactionTest, SubForProposedAccount)
|
||||
trans1.ledgerSequence = 32;
|
||||
trans1.metadata = CreatePaymentTransactionMetaObject(ACCOUNT1, ACCOUNT2, 110, 30, 22).getSerializer().peekData();
|
||||
|
||||
EXPECT_CALL(*mockSessionPtr, send(SharedStringJsonEq(TRAN_V1))).Times(1);
|
||||
EXPECT_CALL(*mockSessionPtr, apiSubversion).WillOnce(testing::Return(1));
|
||||
EXPECT_CALL(*mockSessionPtr, send(SharedStringJsonEq(TRAN_V1)));
|
||||
testFeedPtr->pub(trans1, ledgerHeader, backend);
|
||||
|
||||
testFeedPtr->unsubProposed(account, sessionPtr);
|
||||
@@ -269,7 +283,7 @@ TEST_F(FeedTransactionTest, SubForProposedAccount)
|
||||
TEST_F(FeedTransactionTest, SubAccountV2)
|
||||
{
|
||||
auto const account = GetAccountIDWithString(ACCOUNT1);
|
||||
sessionPtr->apiSubVersion = 2;
|
||||
EXPECT_CALL(*mockSessionPtr, onDisconnect);
|
||||
testFeedPtr->sub(account, sessionPtr);
|
||||
EXPECT_EQ(testFeedPtr->accountSubCount(), 1);
|
||||
|
||||
@@ -281,7 +295,8 @@ TEST_F(FeedTransactionTest, SubAccountV2)
|
||||
trans1.ledgerSequence = 32;
|
||||
trans1.metadata = CreatePaymentTransactionMetaObject(ACCOUNT1, ACCOUNT2, 110, 30, 22).getSerializer().peekData();
|
||||
|
||||
EXPECT_CALL(*mockSessionPtr, send(SharedStringJsonEq(TRAN_V2))).Times(1);
|
||||
EXPECT_CALL(*mockSessionPtr, apiSubversion).WillOnce(testing::Return(2));
|
||||
EXPECT_CALL(*mockSessionPtr, send(SharedStringJsonEq(TRAN_V2)));
|
||||
testFeedPtr->pub(trans1, ledgerHeader, backend);
|
||||
|
||||
testFeedPtr->unsub(account, sessionPtr);
|
||||
@@ -293,7 +308,7 @@ TEST_F(FeedTransactionTest, SubAccountV2)
|
||||
TEST_F(FeedTransactionTest, SubBothTransactionAndAccount)
|
||||
{
|
||||
auto const account = GetAccountIDWithString(ACCOUNT1);
|
||||
sessionPtr->apiSubVersion = 2;
|
||||
EXPECT_CALL(*mockSessionPtr, onDisconnect).Times(2);
|
||||
testFeedPtr->sub(account, sessionPtr);
|
||||
testFeedPtr->sub(sessionPtr);
|
||||
EXPECT_EQ(testFeedPtr->accountSubCount(), 1);
|
||||
@@ -307,6 +322,7 @@ TEST_F(FeedTransactionTest, SubBothTransactionAndAccount)
|
||||
trans1.ledgerSequence = 32;
|
||||
trans1.metadata = CreatePaymentTransactionMetaObject(ACCOUNT1, ACCOUNT2, 110, 30, 22).getSerializer().peekData();
|
||||
|
||||
EXPECT_CALL(*mockSessionPtr, apiSubversion).Times(2).WillRepeatedly(testing::Return(2));
|
||||
EXPECT_CALL(*mockSessionPtr, send(SharedStringJsonEq(TRAN_V2))).Times(2);
|
||||
testFeedPtr->pub(trans1, ledgerHeader, backend);
|
||||
|
||||
@@ -322,6 +338,8 @@ TEST_F(FeedTransactionTest, SubBookV1)
|
||||
{
|
||||
auto const issue1 = GetIssue(CURRENCY, ISSUER);
|
||||
ripple::Book const book{ripple::xrpIssue(), issue1};
|
||||
|
||||
EXPECT_CALL(*mockSessionPtr, onDisconnect);
|
||||
testFeedPtr->sub(book, sessionPtr);
|
||||
EXPECT_EQ(testFeedPtr->bookSubCount(), 1);
|
||||
|
||||
@@ -394,6 +412,7 @@ TEST_F(FeedTransactionTest, SubBookV1)
|
||||
"engine_result_message":"The transaction was applied. Only final in a validated ledger."
|
||||
})";
|
||||
|
||||
EXPECT_CALL(*mockSessionPtr, apiSubversion).WillOnce(testing::Return(1));
|
||||
EXPECT_CALL(*mockSessionPtr, send(SharedStringJsonEq(OrderbookPublish))).Times(1);
|
||||
testFeedPtr->pub(trans1, ledgerHeader, backend);
|
||||
|
||||
@@ -448,6 +467,8 @@ TEST_F(FeedTransactionTest, SubBookV1)
|
||||
"close_time_iso": "2000-01-01T00:00:00Z",
|
||||
"engine_result_message":"The transaction was applied. Only final in a validated ledger."
|
||||
})";
|
||||
|
||||
EXPECT_CALL(*mockSessionPtr, apiSubversion).WillOnce(testing::Return(1));
|
||||
EXPECT_CALL(*mockSessionPtr, send(SharedStringJsonEq(OrderbookCancelPublish))).Times(1);
|
||||
testFeedPtr->pub(trans1, ledgerHeader, backend);
|
||||
|
||||
@@ -504,6 +525,7 @@ TEST_F(FeedTransactionTest, SubBookV1)
|
||||
metaObj = CreateMetaDataForCreateOffer(CURRENCY, ISSUER, 22, 3, 1);
|
||||
trans1.metadata = metaObj.getSerializer().peekData();
|
||||
|
||||
EXPECT_CALL(*mockSessionPtr, apiSubversion).WillOnce(testing::Return(1));
|
||||
EXPECT_CALL(*mockSessionPtr, send(SharedStringJsonEq(OrderbookCreatePublish))).Times(1);
|
||||
testFeedPtr->pub(trans1, ledgerHeader, backend);
|
||||
|
||||
@@ -517,7 +539,8 @@ TEST_F(FeedTransactionTest, SubBookV2)
|
||||
{
|
||||
auto const issue1 = GetIssue(CURRENCY, ISSUER);
|
||||
ripple::Book const book{ripple::xrpIssue(), issue1};
|
||||
sessionPtr->apiSubVersion = 2;
|
||||
|
||||
EXPECT_CALL(*mockSessionPtr, onDisconnect);
|
||||
testFeedPtr->sub(book, sessionPtr);
|
||||
EXPECT_EQ(testFeedPtr->bookSubCount(), 1);
|
||||
|
||||
@@ -590,6 +613,7 @@ TEST_F(FeedTransactionTest, SubBookV2)
|
||||
"engine_result_message":"The transaction was applied. Only final in a validated ledger."
|
||||
})";
|
||||
|
||||
EXPECT_CALL(*mockSessionPtr, apiSubversion).WillOnce(testing::Return(2));
|
||||
EXPECT_CALL(*mockSessionPtr, send(SharedStringJsonEq(OrderbookPublish))).Times(1);
|
||||
testFeedPtr->pub(trans1, ledgerHeader, backend);
|
||||
|
||||
@@ -601,11 +625,13 @@ TEST_F(FeedTransactionTest, SubBookV2)
|
||||
|
||||
TEST_F(FeedTransactionTest, TransactionContainsBothAccountsSubed)
|
||||
{
|
||||
sessionPtr->apiSubVersion = 2;
|
||||
auto const account = GetAccountIDWithString(ACCOUNT1);
|
||||
|
||||
EXPECT_CALL(*mockSessionPtr, onDisconnect);
|
||||
testFeedPtr->sub(account, sessionPtr);
|
||||
|
||||
auto const account2 = GetAccountIDWithString(ACCOUNT2);
|
||||
EXPECT_CALL(*mockSessionPtr, onDisconnect);
|
||||
testFeedPtr->sub(account2, sessionPtr);
|
||||
|
||||
EXPECT_EQ(testFeedPtr->accountSubCount(), 2);
|
||||
@@ -617,12 +643,14 @@ TEST_F(FeedTransactionTest, TransactionContainsBothAccountsSubed)
|
||||
trans1.ledgerSequence = 32;
|
||||
trans1.metadata = CreatePaymentTransactionMetaObject(ACCOUNT1, ACCOUNT2, 110, 30, 22).getSerializer().peekData();
|
||||
|
||||
EXPECT_CALL(*mockSessionPtr, apiSubversion).WillOnce(testing::Return(2));
|
||||
EXPECT_CALL(*mockSessionPtr, send(SharedStringJsonEq(TRAN_V2))).Times(1);
|
||||
testFeedPtr->pub(trans1, ledgerHeader, backend);
|
||||
|
||||
testFeedPtr->unsub(account, sessionPtr);
|
||||
EXPECT_EQ(testFeedPtr->accountSubCount(), 1);
|
||||
|
||||
EXPECT_CALL(*mockSessionPtr, apiSubversion).WillOnce(testing::Return(2));
|
||||
EXPECT_CALL(*mockSessionPtr, send(SharedStringJsonEq(TRAN_V2))).Times(1);
|
||||
testFeedPtr->pub(trans1, ledgerHeader, backend);
|
||||
|
||||
@@ -634,10 +662,13 @@ TEST_F(FeedTransactionTest, TransactionContainsBothAccountsSubed)
|
||||
TEST_F(FeedTransactionTest, SubAccountRepeatWithDifferentVersion)
|
||||
{
|
||||
auto const account = GetAccountIDWithString(ACCOUNT1);
|
||||
|
||||
EXPECT_CALL(*mockSessionPtr, onDisconnect);
|
||||
testFeedPtr->sub(account, sessionPtr);
|
||||
|
||||
auto const account2 = GetAccountIDWithString(ACCOUNT2);
|
||||
sessionPtr->apiSubVersion = 2;
|
||||
|
||||
EXPECT_CALL(*mockSessionPtr, onDisconnect);
|
||||
testFeedPtr->sub(account2, sessionPtr);
|
||||
|
||||
EXPECT_EQ(testFeedPtr->accountSubCount(), 2);
|
||||
@@ -649,12 +680,14 @@ TEST_F(FeedTransactionTest, SubAccountRepeatWithDifferentVersion)
|
||||
trans1.ledgerSequence = 32;
|
||||
trans1.metadata = CreatePaymentTransactionMetaObject(ACCOUNT1, ACCOUNT2, 110, 30, 22).getSerializer().peekData();
|
||||
|
||||
EXPECT_CALL(*mockSessionPtr, apiSubversion).WillOnce(testing::Return(2));
|
||||
EXPECT_CALL(*mockSessionPtr, send(SharedStringJsonEq(TRAN_V2))).Times(1);
|
||||
testFeedPtr->pub(trans1, ledgerHeader, backend);
|
||||
|
||||
testFeedPtr->unsub(account, sessionPtr);
|
||||
EXPECT_EQ(testFeedPtr->accountSubCount(), 1);
|
||||
|
||||
EXPECT_CALL(*mockSessionPtr, apiSubversion).WillOnce(testing::Return(2));
|
||||
EXPECT_CALL(*mockSessionPtr, send(SharedStringJsonEq(TRAN_V2))).Times(1);
|
||||
testFeedPtr->pub(trans1, ledgerHeader, backend);
|
||||
|
||||
@@ -667,10 +700,10 @@ TEST_F(FeedTransactionTest, SubAccountRepeatWithDifferentVersion)
|
||||
TEST_F(FeedTransactionTest, SubTransactionRepeatWithDifferentVersion)
|
||||
{
|
||||
// sub version 1 first
|
||||
sessionPtr->apiSubVersion = 1;
|
||||
EXPECT_CALL(*mockSessionPtr, onDisconnect);
|
||||
testFeedPtr->sub(sessionPtr);
|
||||
|
||||
// sub version 2 later
|
||||
sessionPtr->apiSubVersion = 2;
|
||||
testFeedPtr->sub(sessionPtr);
|
||||
EXPECT_EQ(testFeedPtr->transactionSubCount(), 1);
|
||||
|
||||
@@ -681,6 +714,7 @@ TEST_F(FeedTransactionTest, SubTransactionRepeatWithDifferentVersion)
|
||||
trans1.ledgerSequence = 32;
|
||||
trans1.metadata = CreatePaymentTransactionMetaObject(ACCOUNT1, ACCOUNT2, 110, 30, 22).getSerializer().peekData();
|
||||
|
||||
EXPECT_CALL(*mockSessionPtr, apiSubversion).WillOnce(testing::Return(2));
|
||||
EXPECT_CALL(*mockSessionPtr, send(SharedStringJsonEq(TRAN_V2))).Times(1);
|
||||
testFeedPtr->pub(trans1, ledgerHeader, backend);
|
||||
|
||||
@@ -693,9 +727,11 @@ TEST_F(FeedTransactionTest, SubTransactionRepeatWithDifferentVersion)
|
||||
TEST_F(FeedTransactionTest, SubRepeat)
|
||||
{
|
||||
auto const session2 = std::make_shared<MockSession>();
|
||||
session2->apiSubVersion = 1;
|
||||
|
||||
EXPECT_CALL(*mockSessionPtr, onDisconnect);
|
||||
testFeedPtr->sub(sessionPtr);
|
||||
|
||||
EXPECT_CALL(*session2, onDisconnect);
|
||||
testFeedPtr->sub(session2);
|
||||
EXPECT_EQ(testFeedPtr->transactionSubCount(), 2);
|
||||
|
||||
@@ -712,7 +748,11 @@ TEST_F(FeedTransactionTest, SubRepeat)
|
||||
|
||||
auto const account = GetAccountIDWithString(ACCOUNT1);
|
||||
auto const account2 = GetAccountIDWithString(ACCOUNT2);
|
||||
|
||||
EXPECT_CALL(*mockSessionPtr, onDisconnect);
|
||||
testFeedPtr->sub(account, sessionPtr);
|
||||
|
||||
EXPECT_CALL(*session2, onDisconnect);
|
||||
testFeedPtr->sub(account2, session2);
|
||||
EXPECT_EQ(testFeedPtr->accountSubCount(), 2);
|
||||
|
||||
@@ -729,8 +769,12 @@ TEST_F(FeedTransactionTest, SubRepeat)
|
||||
|
||||
auto const issue1 = GetIssue(CURRENCY, ISSUER);
|
||||
ripple::Book const book{ripple::xrpIssue(), issue1};
|
||||
|
||||
EXPECT_CALL(*mockSessionPtr, onDisconnect);
|
||||
testFeedPtr->sub(book, sessionPtr);
|
||||
EXPECT_EQ(testFeedPtr->bookSubCount(), 1);
|
||||
|
||||
EXPECT_CALL(*session2, onDisconnect);
|
||||
testFeedPtr->sub(book, session2);
|
||||
EXPECT_EQ(testFeedPtr->bookSubCount(), 2);
|
||||
|
||||
@@ -744,6 +788,7 @@ TEST_F(FeedTransactionTest, SubRepeat)
|
||||
|
||||
TEST_F(FeedTransactionTest, PubTransactionWithOwnerFund)
|
||||
{
|
||||
EXPECT_CALL(*mockSessionPtr, onDisconnect);
|
||||
testFeedPtr->sub(sessionPtr);
|
||||
|
||||
auto const ledgerHeader = CreateLedgerHeader(LEDGERHASH, 33);
|
||||
@@ -814,6 +859,7 @@ TEST_F(FeedTransactionTest, PubTransactionWithOwnerFund)
|
||||
"engine_result_message":"The transaction was applied. Only final in a validated ledger."
|
||||
})";
|
||||
|
||||
EXPECT_CALL(*mockSessionPtr, apiSubversion).WillOnce(testing::Return(1));
|
||||
EXPECT_CALL(*mockSessionPtr, send(SharedStringJsonEq(TransactionForOwnerFund))).Times(1);
|
||||
testFeedPtr->pub(trans1, ledgerHeader, backend);
|
||||
}
|
||||
@@ -856,6 +902,7 @@ constexpr static auto TRAN_FROZEN =
|
||||
|
||||
TEST_F(FeedTransactionTest, PubTransactionOfferCreationFrozenLine)
|
||||
{
|
||||
EXPECT_CALL(*mockSessionPtr, onDisconnect);
|
||||
testFeedPtr->sub(sessionPtr);
|
||||
|
||||
auto const ledgerHeader = CreateLedgerHeader(LEDGERHASH, 33);
|
||||
@@ -888,12 +935,14 @@ TEST_F(FeedTransactionTest, PubTransactionOfferCreationFrozenLine)
|
||||
ON_CALL(*backend, doFetchLedgerObject(kk, testing::_, testing::_))
|
||||
.WillByDefault(testing::Return(accountRoot.getSerializer().peekData()));
|
||||
|
||||
EXPECT_CALL(*mockSessionPtr, apiSubversion).WillOnce(testing::Return(1));
|
||||
EXPECT_CALL(*mockSessionPtr, send(SharedStringJsonEq(TRAN_FROZEN))).Times(1);
|
||||
testFeedPtr->pub(trans1, ledgerHeader, backend);
|
||||
}
|
||||
|
||||
TEST_F(FeedTransactionTest, SubTransactionOfferCreationGlobalFrozen)
|
||||
{
|
||||
EXPECT_CALL(*mockSessionPtr, onDisconnect);
|
||||
testFeedPtr->sub(sessionPtr);
|
||||
|
||||
auto const ledgerHeader = CreateLedgerHeader(LEDGERHASH, 33);
|
||||
@@ -926,6 +975,7 @@ TEST_F(FeedTransactionTest, SubTransactionOfferCreationGlobalFrozen)
|
||||
ON_CALL(*backend, doFetchLedgerObject(kk, testing::_, testing::_))
|
||||
.WillByDefault(testing::Return(accountRoot.getSerializer().peekData()));
|
||||
|
||||
EXPECT_CALL(*mockSessionPtr, apiSubversion).WillOnce(testing::Return(1));
|
||||
EXPECT_CALL(*mockSessionPtr, send(SharedStringJsonEq(TRAN_FROZEN))).Times(1);
|
||||
testFeedPtr->pub(trans1, ledgerHeader, backend);
|
||||
}
|
||||
@@ -933,7 +983,11 @@ TEST_F(FeedTransactionTest, SubTransactionOfferCreationGlobalFrozen)
|
||||
TEST_F(FeedTransactionTest, SubBothProposedAndValidatedAccount)
|
||||
{
|
||||
auto const account = GetAccountIDWithString(ACCOUNT1);
|
||||
|
||||
EXPECT_CALL(*mockSessionPtr, onDisconnect);
|
||||
testFeedPtr->sub(account, sessionPtr);
|
||||
|
||||
EXPECT_CALL(*mockSessionPtr, onDisconnect);
|
||||
testFeedPtr->subProposed(account, sessionPtr);
|
||||
EXPECT_EQ(testFeedPtr->accountSubCount(), 1);
|
||||
|
||||
@@ -943,6 +997,8 @@ TEST_F(FeedTransactionTest, SubBothProposedAndValidatedAccount)
|
||||
trans1.transaction = obj.getSerializer().peekData();
|
||||
trans1.ledgerSequence = 32;
|
||||
trans1.metadata = CreatePaymentTransactionMetaObject(ACCOUNT1, ACCOUNT2, 110, 30, 22).getSerializer().peekData();
|
||||
|
||||
EXPECT_CALL(*mockSessionPtr, apiSubversion).WillOnce(testing::Return(1));
|
||||
EXPECT_CALL(*mockSessionPtr, send(SharedStringJsonEq(TRAN_V1))).Times(1);
|
||||
testFeedPtr->pub(trans1, ledgerHeader, backend);
|
||||
|
||||
@@ -955,7 +1011,10 @@ TEST_F(FeedTransactionTest, SubBothProposedAndValidatedAccount)
|
||||
|
||||
TEST_F(FeedTransactionTest, SubBothProposedAndValidated)
|
||||
{
|
||||
EXPECT_CALL(*mockSessionPtr, onDisconnect);
|
||||
testFeedPtr->sub(sessionPtr);
|
||||
|
||||
EXPECT_CALL(*mockSessionPtr, onDisconnect);
|
||||
testFeedPtr->subProposed(sessionPtr);
|
||||
EXPECT_EQ(testFeedPtr->transactionSubCount(), 1);
|
||||
|
||||
@@ -966,6 +1025,7 @@ TEST_F(FeedTransactionTest, SubBothProposedAndValidated)
|
||||
trans1.ledgerSequence = 32;
|
||||
trans1.metadata = CreatePaymentTransactionMetaObject(ACCOUNT1, ACCOUNT2, 110, 30, 22).getSerializer().peekData();
|
||||
|
||||
EXPECT_CALL(*mockSessionPtr, apiSubversion).Times(2).WillRepeatedly(testing::Return(1));
|
||||
EXPECT_CALL(*mockSessionPtr, send(SharedStringJsonEq(TRAN_V1))).Times(2);
|
||||
testFeedPtr->pub(trans1, ledgerHeader, backend);
|
||||
|
||||
@@ -976,6 +1036,7 @@ TEST_F(FeedTransactionTest, SubBothProposedAndValidated)
|
||||
|
||||
TEST_F(FeedTransactionTest, SubProposedDisconnect)
|
||||
{
|
||||
EXPECT_CALL(*mockSessionPtr, onDisconnect);
|
||||
testFeedPtr->subProposed(sessionPtr);
|
||||
EXPECT_EQ(testFeedPtr->transactionSubCount(), 0);
|
||||
|
||||
@@ -986,6 +1047,7 @@ TEST_F(FeedTransactionTest, SubProposedDisconnect)
|
||||
trans1.ledgerSequence = 32;
|
||||
trans1.metadata = CreatePaymentTransactionMetaObject(ACCOUNT1, ACCOUNT2, 110, 30, 22).getSerializer().peekData();
|
||||
|
||||
EXPECT_CALL(*mockSessionPtr, apiSubversion).WillOnce(testing::Return(1));
|
||||
EXPECT_CALL(*mockSessionPtr, send(SharedStringJsonEq(TRAN_V1))).Times(1);
|
||||
testFeedPtr->pub(trans1, ledgerHeader, backend);
|
||||
|
||||
@@ -996,6 +1058,8 @@ TEST_F(FeedTransactionTest, SubProposedDisconnect)
|
||||
TEST_F(FeedTransactionTest, SubProposedAccountDisconnect)
|
||||
{
|
||||
auto const account = GetAccountIDWithString(ACCOUNT1);
|
||||
|
||||
EXPECT_CALL(*mockSessionPtr, onDisconnect);
|
||||
testFeedPtr->subProposed(account, sessionPtr);
|
||||
EXPECT_EQ(testFeedPtr->accountSubCount(), 0);
|
||||
|
||||
@@ -1006,6 +1070,7 @@ TEST_F(FeedTransactionTest, SubProposedAccountDisconnect)
|
||||
trans1.ledgerSequence = 32;
|
||||
trans1.metadata = CreatePaymentTransactionMetaObject(ACCOUNT1, ACCOUNT2, 110, 30, 22).getSerializer().peekData();
|
||||
|
||||
EXPECT_CALL(*mockSessionPtr, apiSubversion).WillOnce(testing::Return(1));
|
||||
EXPECT_CALL(*mockSessionPtr, send(SharedStringJsonEq(TRAN_V1))).Times(1);
|
||||
testFeedPtr->pub(trans1, ledgerHeader, backend);
|
||||
|
||||
@@ -1015,21 +1080,9 @@ TEST_F(FeedTransactionTest, SubProposedAccountDisconnect)
|
||||
|
||||
struct TransactionFeedMockPrometheusTest : WithMockPrometheus, SyncExecutionCtxFixture {
|
||||
protected:
|
||||
std::shared_ptr<web::ConnectionBase> sessionPtr;
|
||||
std::shared_ptr<TransactionFeed> testFeedPtr;
|
||||
|
||||
void
|
||||
SetUp() override
|
||||
{
|
||||
testFeedPtr = std::make_shared<TransactionFeed>(ctx);
|
||||
sessionPtr = std::make_shared<MockSession>();
|
||||
}
|
||||
void
|
||||
TearDown() override
|
||||
{
|
||||
sessionPtr.reset();
|
||||
testFeedPtr.reset();
|
||||
}
|
||||
web::SubscriptionContextPtr sessionPtr = std::make_shared<MockSession>();
|
||||
std::shared_ptr<TransactionFeed> testFeedPtr = std::make_shared<TransactionFeed>(ctx);
|
||||
MockSession* mockSessionPtr = dynamic_cast<MockSession*>(sessionPtr.get());
|
||||
};
|
||||
|
||||
TEST_F(TransactionFeedMockPrometheusTest, subUnsub)
|
||||
@@ -1045,15 +1098,18 @@ TEST_F(TransactionFeedMockPrometheusTest, subUnsub)
|
||||
EXPECT_CALL(counterBook, add(1));
|
||||
EXPECT_CALL(counterBook, add(-1));
|
||||
|
||||
EXPECT_CALL(*mockSessionPtr, onDisconnect);
|
||||
testFeedPtr->sub(sessionPtr);
|
||||
testFeedPtr->unsub(sessionPtr);
|
||||
|
||||
auto const account = GetAccountIDWithString(ACCOUNT1);
|
||||
EXPECT_CALL(*mockSessionPtr, onDisconnect);
|
||||
testFeedPtr->sub(account, sessionPtr);
|
||||
testFeedPtr->unsub(account, sessionPtr);
|
||||
|
||||
auto const issue1 = GetIssue(CURRENCY, ISSUER);
|
||||
ripple::Book const book{ripple::xrpIssue(), issue1};
|
||||
EXPECT_CALL(*mockSessionPtr, onDisconnect);
|
||||
testFeedPtr->sub(book, sessionPtr);
|
||||
testFeedPtr->unsub(book, sessionPtr);
|
||||
}
|
||||
@@ -1071,6 +1127,11 @@ TEST_F(TransactionFeedMockPrometheusTest, AutoDisconnect)
|
||||
EXPECT_CALL(counterBook, add(1));
|
||||
EXPECT_CALL(counterBook, add(-1));
|
||||
|
||||
std::vector<web::SubscriptionContextInterface::OnDisconnectSlot> onDisconnectSlots;
|
||||
|
||||
EXPECT_CALL(*mockSessionPtr, onDisconnect).Times(3).WillRepeatedly([&onDisconnectSlots](auto const& slot) {
|
||||
onDisconnectSlots.push_back(slot);
|
||||
});
|
||||
testFeedPtr->sub(sessionPtr);
|
||||
|
||||
auto const account = GetAccountIDWithString(ACCOUNT1);
|
||||
@@ -1080,5 +1141,9 @@ TEST_F(TransactionFeedMockPrometheusTest, AutoDisconnect)
|
||||
ripple::Book const book{ripple::xrpIssue(), issue1};
|
||||
testFeedPtr->sub(book, sessionPtr);
|
||||
|
||||
// Emulate onDisconnect signal is called
|
||||
for (auto const& slot : onDisconnectSlots)
|
||||
slot(sessionPtr.get());
|
||||
|
||||
sessionPtr.reset();
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
#include "util/MockWsBase.hpp"
|
||||
#include "util/NameGenerator.hpp"
|
||||
#include "util/TestObject.hpp"
|
||||
#include "web/interface/ConnectionBase.hpp"
|
||||
#include "web/SubscriptionContextInterface.hpp"
|
||||
|
||||
#include <boost/json/object.hpp>
|
||||
#include <boost/json/parse.hpp>
|
||||
@@ -63,21 +63,9 @@ constexpr static auto PAYS20XRPGETS10USDBOOKDIR = "7B1767D41DBCE79D9585CF9D0262A
|
||||
constexpr static auto INDEX1 = "1B8590C01B0006EDFA9ED60296DD052DC5E90F99659B25014D08E1BC983515BC";
|
||||
constexpr static auto INDEX2 = "E6DBAFC99223B42257915A63DFC6B0C032D4070F9A574B255AD97466726FC321";
|
||||
|
||||
class RPCSubscribeHandlerTest : public HandlerBaseTest {
|
||||
protected:
|
||||
void
|
||||
SetUp() override
|
||||
{
|
||||
HandlerBaseTest::SetUp();
|
||||
session_ = std::make_shared<MockSession>();
|
||||
}
|
||||
void
|
||||
TearDown() override
|
||||
{
|
||||
HandlerBaseTest::TearDown();
|
||||
}
|
||||
|
||||
std::shared_ptr<web::ConnectionBase> session_;
|
||||
struct RPCSubscribeHandlerTest : HandlerBaseTest {
|
||||
web::SubscriptionContextPtr session_ = std::make_shared<MockSession>();
|
||||
MockSession* mockSession_ = dynamic_cast<MockSession*>(session_.get());
|
||||
StrictMockSubscriptionManagerSharedPtr mockSubscriptionManagerPtr;
|
||||
};
|
||||
|
||||
@@ -578,6 +566,7 @@ TEST_F(RPCSubscribeHandlerTest, EmptyResponse)
|
||||
{
|
||||
runSpawn([&, this](auto yield) {
|
||||
auto const handler = AnyHandler{SubscribeHandler{backend, mockSubscriptionManagerPtr}};
|
||||
EXPECT_CALL(*mockSession_, setApiSubversion(0));
|
||||
auto const output = handler.process(json::parse(R"({})"), Context{yield, session_});
|
||||
ASSERT_TRUE(output);
|
||||
EXPECT_TRUE(output.result->as_object().empty());
|
||||
@@ -594,12 +583,13 @@ TEST_F(RPCSubscribeHandlerTest, StreamsWithoutLedger)
|
||||
);
|
||||
runSpawn([&, this](auto yield) {
|
||||
auto const handler = AnyHandler{SubscribeHandler{backend, mockSubscriptionManagerPtr}};
|
||||
EXPECT_CALL(*mockSubscriptionManagerPtr, subTransactions).Times(1);
|
||||
EXPECT_CALL(*mockSubscriptionManagerPtr, subValidation).Times(1);
|
||||
EXPECT_CALL(*mockSubscriptionManagerPtr, subManifest).Times(1);
|
||||
EXPECT_CALL(*mockSubscriptionManagerPtr, subBookChanges).Times(1);
|
||||
EXPECT_CALL(*mockSubscriptionManagerPtr, subProposedTransactions).Times(1);
|
||||
EXPECT_CALL(*mockSubscriptionManagerPtr, subTransactions);
|
||||
EXPECT_CALL(*mockSubscriptionManagerPtr, subValidation);
|
||||
EXPECT_CALL(*mockSubscriptionManagerPtr, subManifest);
|
||||
EXPECT_CALL(*mockSubscriptionManagerPtr, subBookChanges);
|
||||
EXPECT_CALL(*mockSubscriptionManagerPtr, subProposedTransactions);
|
||||
|
||||
EXPECT_CALL(*mockSession_, setApiSubversion(0));
|
||||
auto const output = handler.process(input, Context{yield, session_});
|
||||
ASSERT_TRUE(output);
|
||||
EXPECT_TRUE(output.result->as_object().empty());
|
||||
@@ -630,6 +620,7 @@ TEST_F(RPCSubscribeHandlerTest, StreamsLedger)
|
||||
EXPECT_CALL(*mockSubscriptionManagerPtr, subLedger)
|
||||
.WillOnce(testing::Return(boost::json::parse(expectedOutput).as_object()));
|
||||
|
||||
EXPECT_CALL(*mockSession_, setApiSubversion(0));
|
||||
auto const output = handler.process(input, Context{yield, session_});
|
||||
ASSERT_TRUE(output);
|
||||
EXPECT_EQ(output.result->as_object(), json::parse(expectedOutput));
|
||||
@@ -649,8 +640,9 @@ TEST_F(RPCSubscribeHandlerTest, Accounts)
|
||||
runSpawn([&, this](auto yield) {
|
||||
auto const handler = AnyHandler{SubscribeHandler{backend, mockSubscriptionManagerPtr}};
|
||||
|
||||
EXPECT_CALL(*mockSubscriptionManagerPtr, subAccount(GetAccountIDWithString(ACCOUNT), session_)).Times(1);
|
||||
EXPECT_CALL(*mockSubscriptionManagerPtr, subAccount(GetAccountIDWithString(ACCOUNT), session_));
|
||||
EXPECT_CALL(*mockSubscriptionManagerPtr, subAccount(GetAccountIDWithString(ACCOUNT2), session_)).Times(2);
|
||||
EXPECT_CALL(*mockSession_, setApiSubversion(0));
|
||||
auto const output = handler.process(input, Context{yield, session_});
|
||||
ASSERT_TRUE(output);
|
||||
EXPECT_TRUE(output.result->as_object().empty());
|
||||
@@ -670,10 +662,10 @@ TEST_F(RPCSubscribeHandlerTest, AccountsProposed)
|
||||
runSpawn([&, this](auto yield) {
|
||||
auto const handler = AnyHandler{SubscribeHandler{backend, mockSubscriptionManagerPtr}};
|
||||
|
||||
EXPECT_CALL(*mockSubscriptionManagerPtr, subProposedAccount(GetAccountIDWithString(ACCOUNT), session_))
|
||||
.Times(1);
|
||||
EXPECT_CALL(*mockSubscriptionManagerPtr, subProposedAccount(GetAccountIDWithString(ACCOUNT), session_));
|
||||
EXPECT_CALL(*mockSubscriptionManagerPtr, subProposedAccount(GetAccountIDWithString(ACCOUNT2), session_))
|
||||
.Times(2);
|
||||
EXPECT_CALL(*mockSession_, setApiSubversion(0));
|
||||
auto const output = handler.process(input, Context{yield, session_});
|
||||
ASSERT_TRUE(output);
|
||||
EXPECT_TRUE(output.result->as_object().empty());
|
||||
@@ -703,7 +695,8 @@ TEST_F(RPCSubscribeHandlerTest, JustBooks)
|
||||
));
|
||||
runSpawn([&, this](auto yield) {
|
||||
auto const handler = AnyHandler{SubscribeHandler{backend, mockSubscriptionManagerPtr}};
|
||||
EXPECT_CALL(*mockSubscriptionManagerPtr, subBook).Times(1);
|
||||
EXPECT_CALL(*mockSubscriptionManagerPtr, subBook);
|
||||
EXPECT_CALL(*mockSession_, setApiSubversion(0));
|
||||
auto const output = handler.process(input, Context{yield, session_});
|
||||
ASSERT_TRUE(output);
|
||||
EXPECT_TRUE(output.result->as_object().empty());
|
||||
@@ -735,6 +728,7 @@ TEST_F(RPCSubscribeHandlerTest, BooksBothSet)
|
||||
runSpawn([&, this](auto yield) {
|
||||
auto const handler = AnyHandler{SubscribeHandler{backend, mockSubscriptionManagerPtr}};
|
||||
EXPECT_CALL(*mockSubscriptionManagerPtr, subBook).Times(2);
|
||||
EXPECT_CALL(*mockSession_, setApiSubversion(0));
|
||||
auto const output = handler.process(input, Context{yield, session_});
|
||||
ASSERT_TRUE(output);
|
||||
EXPECT_TRUE(output.result->as_object().empty());
|
||||
@@ -899,6 +893,7 @@ TEST_F(RPCSubscribeHandlerTest, BooksBothSnapshotSet)
|
||||
runSpawn([&, this](auto yield) {
|
||||
auto const handler = AnyHandler{SubscribeHandler{backend, mockSubscriptionManagerPtr}};
|
||||
EXPECT_CALL(*mockSubscriptionManagerPtr, subBook).Times(2);
|
||||
EXPECT_CALL(*mockSession_, setApiSubversion(0));
|
||||
auto const output = handler.process(input, Context{yield, session_});
|
||||
ASSERT_TRUE(output);
|
||||
EXPECT_EQ(output.result->as_object().at("bids").as_array().size(), 10);
|
||||
@@ -1007,7 +1002,7 @@ TEST_F(RPCSubscribeHandlerTest, BooksBothUnsetSnapshotSet)
|
||||
std::vector<Blob> const bbs2(10, gets10USDPays20XRPOffer.getSerializer().peekData());
|
||||
ON_CALL(*backend, doFetchLedgerObjects(indexes2, MAXSEQ, _)).WillByDefault(Return(bbs2));
|
||||
|
||||
EXPECT_CALL(*backend, doFetchLedgerObjects).Times(1);
|
||||
EXPECT_CALL(*backend, doFetchLedgerObjects);
|
||||
|
||||
static auto const expectedOffer = fmt::format(
|
||||
R"({{
|
||||
@@ -1038,7 +1033,8 @@ TEST_F(RPCSubscribeHandlerTest, BooksBothUnsetSnapshotSet)
|
||||
|
||||
runSpawn([&, this](auto yield) {
|
||||
auto const handler = AnyHandler{SubscribeHandler{backend, mockSubscriptionManagerPtr}};
|
||||
EXPECT_CALL(*mockSubscriptionManagerPtr, subBook).Times(1);
|
||||
EXPECT_CALL(*mockSubscriptionManagerPtr, subBook);
|
||||
EXPECT_CALL(*mockSession_, setApiSubversion(0));
|
||||
auto const output = handler.process(input, Context{yield, session_});
|
||||
ASSERT_TRUE(output);
|
||||
EXPECT_EQ(output.result->as_object().at("offers").as_array().size(), 10);
|
||||
@@ -1056,11 +1052,12 @@ TEST_F(RPCSubscribeHandlerTest, APIVersion)
|
||||
auto const apiVersion = 2;
|
||||
runSpawn([&, this](auto yield) {
|
||||
auto const handler = AnyHandler{SubscribeHandler{backend, mockSubscriptionManagerPtr}};
|
||||
EXPECT_CALL(*mockSubscriptionManagerPtr, subProposedTransactions).Times(1);
|
||||
EXPECT_CALL(*mockSubscriptionManagerPtr, subProposedTransactions);
|
||||
EXPECT_CALL(*mockSession_, setApiSubversion(apiVersion));
|
||||
auto const output =
|
||||
handler.process(input, Context{.yield = yield, .session = session_, .apiVersion = apiVersion});
|
||||
ASSERT_TRUE(output);
|
||||
EXPECT_EQ(session_->apiSubVersion, apiVersion);
|
||||
// EXPECT_EQ(session_->apiSubVersion, apiVersion);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
#include "util/MockSubscriptionManager.hpp"
|
||||
#include "util/MockWsBase.hpp"
|
||||
#include "util/NameGenerator.hpp"
|
||||
#include "web/interface/ConnectionBase.hpp"
|
||||
#include "web/SubscriptionContextInterface.hpp"
|
||||
|
||||
#include <boost/json/parse.hpp>
|
||||
#include <boost/json/value.hpp>
|
||||
@@ -49,19 +49,7 @@ constexpr static auto ACCOUNT = "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn";
|
||||
constexpr static auto ACCOUNT2 = "rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun";
|
||||
|
||||
struct RPCUnsubscribeTest : HandlerBaseTest {
|
||||
void
|
||||
SetUp() override
|
||||
{
|
||||
HandlerBaseTest::SetUp();
|
||||
session_ = std::make_shared<MockSession>();
|
||||
}
|
||||
void
|
||||
TearDown() override
|
||||
{
|
||||
HandlerBaseTest::TearDown();
|
||||
}
|
||||
|
||||
std::shared_ptr<web::ConnectionBase> session_;
|
||||
web::SubscriptionContextPtr session_ = std::make_shared<MockSession>();
|
||||
StrictMockSubscriptionManagerSharedPtr mockSubscriptionManagerPtr;
|
||||
};
|
||||
|
||||
|
||||
@@ -155,10 +155,12 @@ TEST_F(CoroutineGroupTests, TooManyCoroutines)
|
||||
}));
|
||||
|
||||
EXPECT_FALSE(group.spawn(yield, [this](boost::asio::yield_context) { callback2_.Call(); }));
|
||||
EXPECT_TRUE(group.isFull());
|
||||
|
||||
boost::asio::steady_timer timer{yield.get_executor(), std::chrono::milliseconds{2}};
|
||||
timer.async_wait(yield);
|
||||
|
||||
EXPECT_FALSE(group.isFull());
|
||||
EXPECT_TRUE(group.spawn(yield, [this](boost::asio::yield_context) { callback2_.Call(); }));
|
||||
|
||||
group.asyncWait(yield);
|
||||
@@ -166,16 +168,28 @@ TEST_F(CoroutineGroupTests, TooManyCoroutines)
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(CoroutineGroupTests, CanSpawn)
|
||||
TEST_F(CoroutineGroupTests, SpawnForeign)
|
||||
{
|
||||
EXPECT_CALL(callback1_, Call);
|
||||
testing::Sequence const sequence;
|
||||
EXPECT_CALL(callback1_, Call).InSequence(sequence);
|
||||
EXPECT_CALL(callback2_, Call).InSequence(sequence);
|
||||
|
||||
runSpawn([this](boost::asio::yield_context yield) {
|
||||
CoroutineGroup group{yield, 1};
|
||||
EXPECT_TRUE(group.canSpawn());
|
||||
group.spawn(yield, [&group, this](boost::asio::yield_context) {
|
||||
|
||||
auto const onForeignComplete = group.registerForeign();
|
||||
[&]() { ASSERT_TRUE(onForeignComplete.has_value()); }();
|
||||
|
||||
[&]() { ASSERT_FALSE(group.registerForeign().has_value()); }();
|
||||
|
||||
boost::asio::spawn(ctx, [this, &onForeignComplete](boost::asio::yield_context innerYield) {
|
||||
boost::asio::steady_timer timer{innerYield.get_executor(), std::chrono::milliseconds{2}};
|
||||
timer.async_wait(innerYield);
|
||||
callback1_.Call();
|
||||
EXPECT_FALSE(group.canSpawn());
|
||||
onForeignComplete->operator()();
|
||||
});
|
||||
|
||||
group.asyncWait(yield);
|
||||
callback2_.Call();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -18,8 +18,9 @@
|
||||
//==============================================================================
|
||||
|
||||
#include "util/LoggerFixtures.hpp"
|
||||
#include "util/NameGenerator.hpp"
|
||||
#include "util/config/Config.hpp"
|
||||
#include "web/impl/AdminVerificationStrategy.hpp"
|
||||
#include "web/AdminVerificationStrategy.hpp"
|
||||
|
||||
#include <boost/beast/http/field.hpp>
|
||||
#include <boost/beast/http/message.hpp>
|
||||
@@ -34,7 +35,7 @@ namespace http = boost::beast::http;
|
||||
|
||||
class IPAdminVerificationStrategyTest : public NoLoggerFixture {
|
||||
protected:
|
||||
web::impl::IPAdminVerificationStrategy strat_;
|
||||
web::IPAdminVerificationStrategy strat_;
|
||||
http::request<http::string_body> request_;
|
||||
};
|
||||
|
||||
@@ -52,7 +53,7 @@ protected:
|
||||
std::string const password_ = "secret";
|
||||
std::string const passwordHash_ = "2bb80d537b1da3e38bd30361aa855686bde0eacd7162fef6a25fe97bf527a25b";
|
||||
|
||||
web::impl::PasswordAdminVerificationStrategy strat_{password_};
|
||||
web::PasswordAdminVerificationStrategy strat_{password_};
|
||||
|
||||
static http::request<http::string_body>
|
||||
makeRequest(std::string const& password, http::field const field = http::field::authorization)
|
||||
@@ -92,10 +93,10 @@ class MakeAdminVerificationStrategyTest : public testing::TestWithParam<MakeAdmi
|
||||
|
||||
TEST_P(MakeAdminVerificationStrategyTest, ChoosesStrategyCorrectly)
|
||||
{
|
||||
auto strat = web::impl::make_AdminVerificationStrategy(GetParam().passwordOpt);
|
||||
auto ipStrat = dynamic_cast<web::impl::IPAdminVerificationStrategy*>(strat.get());
|
||||
auto strat = web::make_AdminVerificationStrategy(GetParam().passwordOpt);
|
||||
auto ipStrat = dynamic_cast<web::IPAdminVerificationStrategy*>(strat.get());
|
||||
EXPECT_EQ(ipStrat != nullptr, GetParam().expectIpStrategy);
|
||||
auto passwordStrat = dynamic_cast<web::impl::PasswordAdminVerificationStrategy*>(strat.get());
|
||||
auto passwordStrat = dynamic_cast<web::PasswordAdminVerificationStrategy*>(strat.get());
|
||||
EXPECT_EQ(passwordStrat != nullptr, GetParam().expectPasswordStrategy);
|
||||
}
|
||||
|
||||
@@ -136,10 +137,8 @@ struct MakeAdminVerificationStrategyFromConfigTest
|
||||
TEST_P(MakeAdminVerificationStrategyFromConfigTest, ChecksConfig)
|
||||
{
|
||||
util::Config const serverConfig{boost::json::parse(GetParam().config)};
|
||||
auto const result = web::impl::make_AdminVerificationStrategy(serverConfig);
|
||||
if (GetParam().expectedError) {
|
||||
EXPECT_FALSE(result.has_value());
|
||||
}
|
||||
auto const result = web::make_AdminVerificationStrategy(serverConfig);
|
||||
EXPECT_EQ(not result.has_value(), GetParam().expectedError);
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(
|
||||
@@ -149,32 +148,33 @@ INSTANTIATE_TEST_SUITE_P(
|
||||
MakeAdminVerificationStrategyFromConfigTestParams{
|
||||
.testName = "NoPasswordNoLocalAdmin",
|
||||
.config = "{}",
|
||||
.expectedError = true
|
||||
.expectedError = false
|
||||
},
|
||||
MakeAdminVerificationStrategyFromConfigTestParams{
|
||||
.testName = "OnlyPassword",
|
||||
.config = R"({"admin_password": "password"})",
|
||||
.config = R"({"server": {"admin_password": "password"}})",
|
||||
.expectedError = false
|
||||
},
|
||||
MakeAdminVerificationStrategyFromConfigTestParams{
|
||||
.testName = "OnlyLocalAdmin",
|
||||
.config = R"({"local_admin": true})",
|
||||
.config = R"({"server": {"local_admin": true}})",
|
||||
.expectedError = false
|
||||
},
|
||||
MakeAdminVerificationStrategyFromConfigTestParams{
|
||||
.testName = "OnlyLocalAdminDisabled",
|
||||
.config = R"({"local_admin": false})",
|
||||
.config = R"({"server": {"local_admin": false}})",
|
||||
.expectedError = true
|
||||
},
|
||||
MakeAdminVerificationStrategyFromConfigTestParams{
|
||||
.testName = "LocalAdminAndPassword",
|
||||
.config = R"({"local_admin": true, "admin_password": "password"})",
|
||||
.config = R"({"server": {"local_admin": true, "admin_password": "password"}})",
|
||||
.expectedError = true
|
||||
},
|
||||
MakeAdminVerificationStrategyFromConfigTestParams{
|
||||
.testName = "LocalAdminDisabledAndPassword",
|
||||
.config = R"({"local_admin": false, "admin_password": "password"})",
|
||||
.config = R"({"server": {"local_admin": false, "admin_password": "password"}})",
|
||||
.expectedError = false
|
||||
}
|
||||
)
|
||||
),
|
||||
tests::util::NameGenerator
|
||||
);
|
||||
@@ -26,6 +26,7 @@
|
||||
#include "util/Taggable.hpp"
|
||||
#include "util/config/Config.hpp"
|
||||
#include "web/RPCServerHandler.hpp"
|
||||
#include "web/SubscriptionContextInterface.hpp"
|
||||
#include "web/interface/ConnectionBase.hpp"
|
||||
|
||||
#include <boost/beast/http/status.hpp>
|
||||
@@ -61,28 +62,25 @@ struct MockWsBase : public web::ConnectionBase {
|
||||
lastStatus = status;
|
||||
}
|
||||
|
||||
SubscriptionContextPtr
|
||||
makeSubscriptionContext(util::TagDecoratorFactory const&) override
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
MockWsBase(util::TagDecoratorFactory const& factory) : web::ConnectionBase(factory, "localhost.fake.ip")
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
struct WebRPCServerHandlerTest : util::prometheus::WithPrometheus, MockBackendTest, SyncAsioContextTest {
|
||||
void
|
||||
SetUp() override
|
||||
{
|
||||
etl = std::make_shared<MockETLService>();
|
||||
rpcEngine = std::make_shared<MockAsyncRPCEngine>();
|
||||
tagFactory = std::make_shared<util::TagDecoratorFactory>(cfg);
|
||||
session = std::make_shared<MockWsBase>(*tagFactory);
|
||||
handler = std::make_shared<RPCServerHandler<MockAsyncRPCEngine, MockETLService>>(cfg, backend, rpcEngine, etl);
|
||||
}
|
||||
|
||||
std::shared_ptr<MockAsyncRPCEngine> rpcEngine;
|
||||
std::shared_ptr<MockETLService> etl;
|
||||
std::shared_ptr<util::TagDecoratorFactory> tagFactory;
|
||||
std::shared_ptr<RPCServerHandler<MockAsyncRPCEngine, MockETLService>> handler;
|
||||
std::shared_ptr<MockWsBase> session;
|
||||
util::Config cfg;
|
||||
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<MockWsBase> session = std::make_shared<MockWsBase>(*tagFactory);
|
||||
};
|
||||
|
||||
TEST_F(WebRPCServerHandlerTest, HTTPDefaultPath)
|
||||
@@ -524,7 +522,7 @@ TEST_F(WebRPCServerHandlerTest, HTTPBadSyntaxWhenRequestSubscribe)
|
||||
"result": {
|
||||
"error": "badSyntax",
|
||||
"error_code": 1,
|
||||
"error_message": "Subscribe and unsubscribe are only allowed or websocket.",
|
||||
"error_message": "Subscribe and unsubscribe are only allowed for websocket.",
|
||||
"status": "error",
|
||||
"type": "response",
|
||||
"request": {
|
||||
|
||||
@@ -26,12 +26,12 @@
|
||||
#include "util/config/Config.hpp"
|
||||
#include "util/prometheus/Label.hpp"
|
||||
#include "util/prometheus/Prometheus.hpp"
|
||||
#include "web/AdminVerificationStrategy.hpp"
|
||||
#include "web/Server.hpp"
|
||||
#include "web/dosguard/DOSGuard.hpp"
|
||||
#include "web/dosguard/DOSGuardInterface.hpp"
|
||||
#include "web/dosguard/IntervalSweepHandler.hpp"
|
||||
#include "web/dosguard/WhitelistHandler.hpp"
|
||||
#include "web/impl/AdminVerificationStrategy.hpp"
|
||||
#include "web/interface/ConnectionBase.hpp"
|
||||
|
||||
#include <boost/asio/io_context.hpp>
|
||||
|
||||
72
tests/unit/web/SubscriptionContextTests.cpp
Normal file
72
tests/unit/web/SubscriptionContextTests.cpp
Normal file
@@ -0,0 +1,72 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of clio: https://github.com/XRPLF/clio
|
||||
Copyright (c) 2024, 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 "util/LoggerFixtures.hpp"
|
||||
#include "util/Taggable.hpp"
|
||||
#include "util/config/Config.hpp"
|
||||
#include "web/SubscriptionContext.hpp"
|
||||
#include "web/SubscriptionContextInterface.hpp"
|
||||
#include "web/interface/ConnectionBaseMock.hpp"
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
using namespace web;
|
||||
|
||||
struct SubscriptionContextTests : NoLoggerFixture {
|
||||
util::TagDecoratorFactory tagFactory_{util::Config{}};
|
||||
ConnectionBaseStrictMockPtr connection_ =
|
||||
std::make_shared<testing::StrictMock<ConnectionBaseMock>>(tagFactory_, "some ip");
|
||||
|
||||
SubscriptionContext subscriptionContext_{tagFactory_, connection_};
|
||||
testing::StrictMock<testing::MockFunction<void(SubscriptionContextInterface*)>> callbackMock_;
|
||||
};
|
||||
|
||||
TEST_F(SubscriptionContextTests, send)
|
||||
{
|
||||
auto message = std::make_shared<std::string>("message");
|
||||
EXPECT_CALL(*connection_, send(message));
|
||||
subscriptionContext_.send(message);
|
||||
}
|
||||
|
||||
TEST_F(SubscriptionContextTests, sendConnectionExpired)
|
||||
{
|
||||
auto message = std::make_shared<std::string>("message");
|
||||
connection_.reset();
|
||||
subscriptionContext_.send(message);
|
||||
}
|
||||
|
||||
TEST_F(SubscriptionContextTests, onDisconnect)
|
||||
{
|
||||
auto localContext = std::make_unique<SubscriptionContext>(tagFactory_, connection_);
|
||||
localContext->onDisconnect(callbackMock_.AsStdFunction());
|
||||
|
||||
EXPECT_CALL(callbackMock_, Call(localContext.get()));
|
||||
localContext.reset();
|
||||
}
|
||||
|
||||
TEST_F(SubscriptionContextTests, setApiSubversion)
|
||||
{
|
||||
EXPECT_EQ(subscriptionContext_.apiSubversion(), 0);
|
||||
subscriptionContext_.setApiSubversion(42);
|
||||
EXPECT_EQ(subscriptionContext_.apiSubversion(), 42);
|
||||
}
|
||||
288
tests/unit/web/impl/ErrorHandlingTests.cpp
Normal file
288
tests/unit/web/impl/ErrorHandlingTests.cpp
Normal file
@@ -0,0 +1,288 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of clio: https://github.com/XRPLF/clio
|
||||
Copyright (c) 2024, 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 "rpc/Errors.hpp"
|
||||
#include "util/LoggerFixtures.hpp"
|
||||
#include "util/NameGenerator.hpp"
|
||||
#include "util/Taggable.hpp"
|
||||
#include "util/config/Config.hpp"
|
||||
#include "web/SubscriptionContextInterface.hpp"
|
||||
#include "web/impl/ErrorHandling.hpp"
|
||||
#include "web/interface/ConnectionBase.hpp"
|
||||
#include "web/interface/ConnectionBaseMock.hpp"
|
||||
|
||||
#include <boost/beast/http/status.hpp>
|
||||
#include <boost/json/object.hpp>
|
||||
#include <boost/json/serialize.hpp>
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
using namespace web::impl;
|
||||
using namespace web;
|
||||
|
||||
struct ErrorHandlingTests : NoLoggerFixture {
|
||||
util::TagDecoratorFactory tagFactory_{util::Config{}};
|
||||
std::string const clientIp_ = "some ip";
|
||||
ConnectionBaseStrictMockPtr connection_ =
|
||||
std::make_shared<testing::StrictMock<ConnectionBaseMock>>(tagFactory_, clientIp_);
|
||||
};
|
||||
|
||||
struct ErrorHandlingComposeErrorTestBundle {
|
||||
std::string testName;
|
||||
bool connectionUpgraded;
|
||||
std::optional<boost::json::object> request;
|
||||
boost::json::object expectedResult;
|
||||
};
|
||||
|
||||
struct ErrorHandlingComposeErrorTest : ErrorHandlingTests,
|
||||
testing::WithParamInterface<ErrorHandlingComposeErrorTestBundle> {};
|
||||
|
||||
TEST_P(ErrorHandlingComposeErrorTest, composeError)
|
||||
{
|
||||
connection_->upgraded = GetParam().connectionUpgraded;
|
||||
ErrorHelper errorHelper{connection_, GetParam().request};
|
||||
auto const result = errorHelper.composeError(rpc::RippledError::rpcNOT_READY);
|
||||
EXPECT_EQ(boost::json::serialize(result), boost::json::serialize(GetParam().expectedResult));
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(
|
||||
ErrorHandlingComposeErrorTestGroup,
|
||||
ErrorHandlingComposeErrorTest,
|
||||
testing::ValuesIn(
|
||||
{ErrorHandlingComposeErrorTestBundle{
|
||||
"NoRequest_UpgradedConnection",
|
||||
true,
|
||||
std::nullopt,
|
||||
{{"error", "notReady"},
|
||||
{"error_code", 13},
|
||||
{"error_message", "Not ready to handle this request."},
|
||||
{"status", "error"},
|
||||
{"type", "response"}}
|
||||
},
|
||||
ErrorHandlingComposeErrorTestBundle{
|
||||
"NoRequest_NotUpgradedConnection",
|
||||
false,
|
||||
std::nullopt,
|
||||
{{"result",
|
||||
{{"error", "notReady"},
|
||||
{"error_code", 13},
|
||||
{"error_message", "Not ready to handle this request."},
|
||||
{"status", "error"},
|
||||
{"type", "response"}}}}
|
||||
},
|
||||
ErrorHandlingComposeErrorTestBundle{
|
||||
"Request_UpgradedConnection",
|
||||
true,
|
||||
boost::json::object{{"id", 1}, {"api_version", 2}},
|
||||
{{"error", "notReady"},
|
||||
{"error_code", 13},
|
||||
{"error_message", "Not ready to handle this request."},
|
||||
{"status", "error"},
|
||||
{"type", "response"},
|
||||
{"id", 1},
|
||||
{"api_version", 2},
|
||||
{"request", {{"id", 1}, {"api_version", 2}}}}
|
||||
},
|
||||
ErrorHandlingComposeErrorTestBundle{
|
||||
"Request_NotUpgradedConnection",
|
||||
false,
|
||||
boost::json::object{{"id", 1}, {"api_version", 2}},
|
||||
{{"result",
|
||||
{{"error", "notReady"},
|
||||
{"error_code", 13},
|
||||
{"error_message", "Not ready to handle this request."},
|
||||
{"status", "error"},
|
||||
{"type", "response"},
|
||||
{"id", 1},
|
||||
{"request", {{"id", 1}, {"api_version", 2}}}}}}
|
||||
}}
|
||||
),
|
||||
tests::util::NameGenerator
|
||||
);
|
||||
|
||||
struct ErrorHandlingSendErrorTestBundle {
|
||||
std::string testName;
|
||||
bool connectionUpgraded;
|
||||
rpc::Status status;
|
||||
std::string expectedMessage;
|
||||
boost::beast::http::status expectedStatus;
|
||||
};
|
||||
|
||||
struct ErrorHandlingSendErrorTest : ErrorHandlingTests,
|
||||
testing::WithParamInterface<ErrorHandlingSendErrorTestBundle> {};
|
||||
|
||||
TEST_P(ErrorHandlingSendErrorTest, sendError)
|
||||
{
|
||||
connection_->upgraded = GetParam().connectionUpgraded;
|
||||
ErrorHelper errorHelper{connection_};
|
||||
|
||||
EXPECT_CALL(*connection_, send(std::string{GetParam().expectedMessage}, GetParam().expectedStatus));
|
||||
errorHelper.sendError(GetParam().status);
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(
|
||||
ErrorHandlingSendErrorTestGroup,
|
||||
ErrorHandlingSendErrorTest,
|
||||
testing::ValuesIn({
|
||||
ErrorHandlingSendErrorTestBundle{
|
||||
"UpgradedConnection",
|
||||
true,
|
||||
rpc::Status{rpc::RippledError::rpcTOO_BUSY},
|
||||
R"({"error":"tooBusy","error_code":9,"error_message":"The server is too busy to help you now.","status":"error","type":"response"})",
|
||||
boost::beast::http::status::ok
|
||||
},
|
||||
ErrorHandlingSendErrorTestBundle{
|
||||
"NotUpgradedConnection_InvalidApiVersion",
|
||||
false,
|
||||
rpc::Status{rpc::ClioError::rpcINVALID_API_VERSION},
|
||||
"invalid_API_version",
|
||||
boost::beast::http::status::bad_request
|
||||
},
|
||||
ErrorHandlingSendErrorTestBundle{
|
||||
"NotUpgradedConnection_CommandIsMissing",
|
||||
false,
|
||||
rpc::Status{rpc::ClioError::rpcCOMMAND_IS_MISSING},
|
||||
"Null method",
|
||||
boost::beast::http::status::bad_request
|
||||
},
|
||||
ErrorHandlingSendErrorTestBundle{
|
||||
"NotUpgradedConnection_CommandIsEmpty",
|
||||
false,
|
||||
rpc::Status{rpc::ClioError::rpcCOMMAND_IS_EMPTY},
|
||||
"method is empty",
|
||||
boost::beast::http::status::bad_request
|
||||
},
|
||||
ErrorHandlingSendErrorTestBundle{
|
||||
"NotUpgradedConnection_CommandNotString",
|
||||
false,
|
||||
rpc::Status{rpc::ClioError::rpcCOMMAND_NOT_STRING},
|
||||
"method is not string",
|
||||
boost::beast::http::status::bad_request
|
||||
},
|
||||
ErrorHandlingSendErrorTestBundle{
|
||||
"NotUpgradedConnection_ParamsUnparseable",
|
||||
false,
|
||||
rpc::Status{rpc::ClioError::rpcPARAMS_UNPARSEABLE},
|
||||
"params unparseable",
|
||||
boost::beast::http::status::bad_request
|
||||
},
|
||||
ErrorHandlingSendErrorTestBundle{
|
||||
"NotUpgradedConnection_RippledError",
|
||||
false,
|
||||
rpc::Status{rpc::RippledError::rpcTOO_BUSY},
|
||||
R"({"result":{"error":"tooBusy","error_code":9,"error_message":"The server is too busy to help you now.","status":"error","type":"response"}})",
|
||||
boost::beast::http::status::bad_request
|
||||
},
|
||||
}),
|
||||
tests::util::NameGenerator
|
||||
);
|
||||
|
||||
TEST_F(ErrorHandlingTests, sendInternalError)
|
||||
{
|
||||
ErrorHelper errorHelper{connection_};
|
||||
|
||||
EXPECT_CALL(
|
||||
*connection_,
|
||||
send(
|
||||
std::string{
|
||||
R"({"result":{"error":"internal","error_code":73,"error_message":"Internal error.","status":"error","type":"response"}})"
|
||||
},
|
||||
boost::beast::http::status::internal_server_error
|
||||
)
|
||||
);
|
||||
errorHelper.sendInternalError();
|
||||
}
|
||||
|
||||
TEST_F(ErrorHandlingTests, sendNotReadyError)
|
||||
{
|
||||
ErrorHelper errorHelper{connection_};
|
||||
EXPECT_CALL(
|
||||
*connection_,
|
||||
send(
|
||||
std::string{
|
||||
R"({"result":{"error":"notReady","error_code":13,"error_message":"Not ready to handle this request.","status":"error","type":"response"}})"
|
||||
},
|
||||
boost::beast::http::status::ok
|
||||
)
|
||||
);
|
||||
errorHelper.sendNotReadyError();
|
||||
}
|
||||
|
||||
TEST_F(ErrorHandlingTests, sendTooBusyError_UpgradedConnection)
|
||||
{
|
||||
connection_->upgraded = true;
|
||||
ErrorHelper errorHelper{connection_};
|
||||
EXPECT_CALL(
|
||||
*connection_,
|
||||
send(
|
||||
std::string{
|
||||
R"({"error":"tooBusy","error_code":9,"error_message":"The server is too busy to help you now.","status":"error","type":"response"})"
|
||||
},
|
||||
boost::beast::http::status::ok
|
||||
)
|
||||
);
|
||||
errorHelper.sendTooBusyError();
|
||||
}
|
||||
|
||||
TEST_F(ErrorHandlingTests, sendTooBusyError_NotUpgradedConnection)
|
||||
{
|
||||
connection_->upgraded = false;
|
||||
ErrorHelper errorHelper{connection_};
|
||||
EXPECT_CALL(
|
||||
*connection_,
|
||||
send(
|
||||
std::string{
|
||||
R"({"error":"tooBusy","error_code":9,"error_message":"The server is too busy to help you now.","status":"error","type":"response"})"
|
||||
},
|
||||
boost::beast::http::status::service_unavailable
|
||||
)
|
||||
);
|
||||
errorHelper.sendTooBusyError();
|
||||
}
|
||||
|
||||
TEST_F(ErrorHandlingTests, sendJsonParsingError_UpgradedConnection)
|
||||
{
|
||||
connection_->upgraded = true;
|
||||
ErrorHelper errorHelper{connection_};
|
||||
EXPECT_CALL(
|
||||
*connection_,
|
||||
send(
|
||||
std::string{
|
||||
R"({"error":"badSyntax","error_code":1,"error_message":"Syntax error.","status":"error","type":"response"})"
|
||||
},
|
||||
boost::beast::http::status::ok
|
||||
)
|
||||
);
|
||||
errorHelper.sendJsonParsingError();
|
||||
}
|
||||
|
||||
TEST_F(ErrorHandlingTests, sendJsonParsingError_NotUpgradedConnection)
|
||||
{
|
||||
connection_->upgraded = false;
|
||||
ErrorHelper errorHelper{connection_};
|
||||
EXPECT_CALL(
|
||||
*connection_,
|
||||
send(std::string{"Unable to parse JSON from the request"}, boost::beast::http::status::bad_request)
|
||||
);
|
||||
errorHelper.sendJsonParsingError();
|
||||
}
|
||||
441
tests/unit/web/ng/RPCServerHandlerTests.cpp
Normal file
441
tests/unit/web/ng/RPCServerHandlerTests.cpp
Normal file
@@ -0,0 +1,441 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of clio: https://github.com/XRPLF/clio
|
||||
Copyright (c) 2024, 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 "rpc/Errors.hpp"
|
||||
#include "rpc/common/Types.hpp"
|
||||
#include "util/AsioContextTestFixture.hpp"
|
||||
#include "util/MockBackendTestFixture.hpp"
|
||||
#include "util/MockETLService.hpp"
|
||||
#include "util/MockPrometheus.hpp"
|
||||
#include "util/MockRPCEngine.hpp"
|
||||
#include "util/Taggable.hpp"
|
||||
#include "util/config/Config.hpp"
|
||||
#include "web/SubscriptionContextInterface.hpp"
|
||||
#include "web/ng/MockConnection.hpp"
|
||||
#include "web/ng/RPCServerHandler.hpp"
|
||||
#include "web/ng/Request.hpp"
|
||||
|
||||
#include <boost/asio/spawn.hpp>
|
||||
#include <boost/beast/http/message.hpp>
|
||||
#include <boost/beast/http/status.hpp>
|
||||
#include <boost/beast/http/string_body.hpp>
|
||||
#include <boost/beast/http/verb.hpp>
|
||||
#include <boost/json/object.hpp>
|
||||
#include <boost/json/parse.hpp>
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <iterator>
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <unordered_set>
|
||||
#include <utility>
|
||||
|
||||
using namespace web::ng;
|
||||
using testing::Return;
|
||||
using testing::StrictMock;
|
||||
|
||||
namespace http = boost::beast::http;
|
||||
|
||||
struct ng_RPCServerHandlerTest : util::prometheus::WithPrometheus, MockBackendTestStrict, SyncAsioContextTest {
|
||||
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_{util::Config{}, backend, rpcEngine_, etl_};
|
||||
|
||||
util::TagDecoratorFactory tagFactory_{util::Config{}};
|
||||
StrictMockConnectionMetadata connectionMetadata_{"some ip", tagFactory_};
|
||||
|
||||
static Request
|
||||
makeHttpRequest(std::string_view body)
|
||||
{
|
||||
return Request{http::request<http::string_body>{http::verb::post, "/", 11, body}};
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(ng_RPCServerHandlerTest, PostToRpcEngineFailed)
|
||||
{
|
||||
runSpawn([&](boost::asio::yield_context yield) {
|
||||
auto const request = makeHttpRequest("some message");
|
||||
|
||||
EXPECT_CALL(*rpcEngine_, post).WillOnce(Return(false));
|
||||
EXPECT_CALL(*rpcEngine_, notifyTooBusy());
|
||||
auto response = rpcServerHandler_(request, connectionMetadata_, nullptr, yield);
|
||||
|
||||
EXPECT_EQ(std::move(response).intoHttpResponse().result(), http::status::service_unavailable);
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(ng_RPCServerHandlerTest, CoroutineSleepsUntilRpcEngineFinishes)
|
||||
{
|
||||
StrictMock<testing::MockFunction<void()>> rpcServerHandlerDone;
|
||||
StrictMock<testing::MockFunction<void()>> rpcEngineDone;
|
||||
testing::Expectation const expectedRpcEngineDone = EXPECT_CALL(rpcEngineDone, Call);
|
||||
EXPECT_CALL(rpcServerHandlerDone, Call).After(expectedRpcEngineDone);
|
||||
|
||||
runSpawn([&](boost::asio::yield_context yield) {
|
||||
auto const request = makeHttpRequest("some message");
|
||||
|
||||
EXPECT_CALL(*rpcEngine_, post).WillOnce([&](auto&& fn, auto&&) {
|
||||
boost::asio::spawn(
|
||||
ctx,
|
||||
[this, &rpcEngineDone, fn = std::forward<decltype(fn)>(fn)](boost::asio::yield_context yield) {
|
||||
EXPECT_CALL(*rpcEngine_, notifyBadSyntax);
|
||||
fn(yield);
|
||||
rpcEngineDone.Call();
|
||||
}
|
||||
);
|
||||
return true;
|
||||
});
|
||||
|
||||
auto response = rpcServerHandler_(request, connectionMetadata_, nullptr, yield);
|
||||
rpcServerHandlerDone.Call();
|
||||
|
||||
EXPECT_EQ(std::move(response).intoHttpResponse().result(), http::status::bad_request);
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(ng_RPCServerHandlerTest, JsonParseFailed)
|
||||
{
|
||||
runSpawn([&](boost::asio::yield_context yield) {
|
||||
auto const request = makeHttpRequest("not a json");
|
||||
|
||||
EXPECT_CALL(*rpcEngine_, post).WillOnce([&](auto&& fn, auto&&) {
|
||||
EXPECT_CALL(*rpcEngine_, notifyBadSyntax);
|
||||
fn(yield);
|
||||
return true;
|
||||
});
|
||||
auto response = rpcServerHandler_(request, connectionMetadata_, nullptr, yield);
|
||||
EXPECT_EQ(std::move(response).intoHttpResponse().result(), http::status::bad_request);
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(ng_RPCServerHandlerTest, GotNotJsonObject)
|
||||
{
|
||||
runSpawn([&](boost::asio::yield_context yield) {
|
||||
auto const request = makeHttpRequest("[]");
|
||||
EXPECT_CALL(*rpcEngine_, post).WillOnce([&](auto&& fn, auto&&) {
|
||||
EXPECT_CALL(*rpcEngine_, notifyBadSyntax);
|
||||
fn(yield);
|
||||
return true;
|
||||
});
|
||||
auto response = rpcServerHandler_(request, connectionMetadata_, nullptr, yield);
|
||||
EXPECT_EQ(std::move(response).intoHttpResponse().result(), http::status::bad_request);
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(ng_RPCServerHandlerTest, HandleRequest_NoRangeFromBackend)
|
||||
{
|
||||
runSpawn([&](boost::asio::yield_context yield) {
|
||||
auto const request = makeHttpRequest("{}");
|
||||
|
||||
EXPECT_CALL(*rpcEngine_, post).WillOnce([&](auto&& fn, auto&&) {
|
||||
EXPECT_CALL(connectionMetadata_, wasUpgraded).WillOnce(Return(not request.isHttp()));
|
||||
EXPECT_CALL(*rpcEngine_, notifyNotReady);
|
||||
fn(yield);
|
||||
return true;
|
||||
});
|
||||
auto response = rpcServerHandler_(request, connectionMetadata_, nullptr, yield);
|
||||
|
||||
auto const httpResponse = std::move(response).intoHttpResponse();
|
||||
EXPECT_EQ(httpResponse.result(), http::status::ok);
|
||||
|
||||
auto const jsonResponse = boost::json::parse(httpResponse.body()).as_object();
|
||||
EXPECT_EQ(jsonResponse.at("result").at("error").as_string(), "notReady");
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(ng_RPCServerHandlerTest, HandleRequest_ContextCreationFailed)
|
||||
{
|
||||
backend->setRange(0, 1);
|
||||
runSpawn([&](boost::asio::yield_context yield) {
|
||||
auto const request = makeHttpRequest("{}");
|
||||
|
||||
EXPECT_CALL(*rpcEngine_, post).WillOnce([&](auto&& fn, auto&&) {
|
||||
EXPECT_CALL(connectionMetadata_, wasUpgraded).WillRepeatedly(Return(not request.isHttp()));
|
||||
EXPECT_CALL(*rpcEngine_, notifyBadSyntax);
|
||||
fn(yield);
|
||||
return true;
|
||||
});
|
||||
auto response = rpcServerHandler_(request, connectionMetadata_, nullptr, yield);
|
||||
|
||||
auto const httpResponse = std::move(response).intoHttpResponse();
|
||||
EXPECT_EQ(httpResponse.result(), http::status::bad_request);
|
||||
EXPECT_EQ(httpResponse.body(), "Null method");
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(ng_RPCServerHandlerTest, HandleRequest_BuildResponseFailed)
|
||||
{
|
||||
backend->setRange(0, 1);
|
||||
runSpawn([&](boost::asio::yield_context yield) {
|
||||
auto const request = makeHttpRequest(R"json({"method":"some_method"})json");
|
||||
|
||||
EXPECT_CALL(*rpcEngine_, post).WillOnce([&](auto&& fn, auto&&) {
|
||||
EXPECT_CALL(connectionMetadata_, wasUpgraded).WillRepeatedly(Return(not request.isHttp()));
|
||||
EXPECT_CALL(*rpcEngine_, buildResponse)
|
||||
.WillOnce(Return(rpc::Result{rpc::Status{rpc::ClioError::rpcUNKNOWN_OPTION}}));
|
||||
EXPECT_CALL(*etl_, lastCloseAgeSeconds).WillOnce(Return(1));
|
||||
fn(yield);
|
||||
return true;
|
||||
});
|
||||
auto response = rpcServerHandler_(request, connectionMetadata_, nullptr, yield);
|
||||
|
||||
auto const httpResponse = std::move(response).intoHttpResponse();
|
||||
EXPECT_EQ(httpResponse.result(), http::status::ok);
|
||||
|
||||
auto const jsonResponse = boost::json::parse(httpResponse.body()).as_object();
|
||||
EXPECT_EQ(jsonResponse.at("result").at("error").as_string(), "unknownOption");
|
||||
|
||||
ASSERT_EQ(jsonResponse.at("warnings").as_array().size(), 1);
|
||||
EXPECT_EQ(jsonResponse.at("warnings").as_array().at(0).as_object().at("id").as_int64(), rpc::warnRPC_CLIO);
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(ng_RPCServerHandlerTest, HandleRequest_BuildResponseThrewAnException)
|
||||
{
|
||||
backend->setRange(0, 1);
|
||||
runSpawn([&](boost::asio::yield_context yield) {
|
||||
auto const request = makeHttpRequest(R"json({"method":"some_method"})json");
|
||||
|
||||
EXPECT_CALL(*rpcEngine_, post).WillOnce([&](auto&& fn, auto&&) {
|
||||
EXPECT_CALL(connectionMetadata_, wasUpgraded).WillRepeatedly(Return(not request.isHttp()));
|
||||
EXPECT_CALL(*rpcEngine_, buildResponse).WillOnce([](auto&&) -> rpc::Result {
|
||||
throw std::runtime_error("some error");
|
||||
});
|
||||
EXPECT_CALL(*rpcEngine_, notifyInternalError);
|
||||
fn(yield);
|
||||
return true;
|
||||
});
|
||||
auto response = rpcServerHandler_(request, connectionMetadata_, nullptr, yield);
|
||||
|
||||
auto const httpResponse = std::move(response).intoHttpResponse();
|
||||
EXPECT_EQ(httpResponse.result(), http::status::internal_server_error);
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(ng_RPCServerHandlerTest, HandleRequest_Successful_HttpRequest)
|
||||
{
|
||||
backend->setRange(0, 1);
|
||||
runSpawn([&](boost::asio::yield_context yield) {
|
||||
auto const request = makeHttpRequest(R"json({"method":"some_method"})json");
|
||||
|
||||
EXPECT_CALL(*rpcEngine_, post).WillOnce([&](auto&& fn, auto&&) {
|
||||
EXPECT_CALL(connectionMetadata_, wasUpgraded).WillRepeatedly(Return(not request.isHttp()));
|
||||
EXPECT_CALL(*rpcEngine_, buildResponse)
|
||||
.WillOnce(Return(rpc::Result{rpc::ReturnType{boost::json::object{{"some key", "some value"}}}}));
|
||||
EXPECT_CALL(*rpcEngine_, notifyComplete);
|
||||
EXPECT_CALL(*etl_, lastCloseAgeSeconds).WillOnce(Return(1));
|
||||
fn(yield);
|
||||
return true;
|
||||
});
|
||||
auto response = rpcServerHandler_(request, connectionMetadata_, nullptr, yield);
|
||||
|
||||
auto const httpResponse = std::move(response).intoHttpResponse();
|
||||
EXPECT_EQ(httpResponse.result(), http::status::ok);
|
||||
|
||||
auto const jsonResponse = boost::json::parse(httpResponse.body()).as_object();
|
||||
EXPECT_EQ(jsonResponse.at("result").at("some key").as_string(), "some value");
|
||||
EXPECT_EQ(jsonResponse.at("result").at("status").as_string(), "success");
|
||||
|
||||
ASSERT_EQ(jsonResponse.at("warnings").as_array().size(), 1) << jsonResponse;
|
||||
EXPECT_EQ(jsonResponse.at("warnings").as_array().at(0).as_object().at("id").as_int64(), rpc::warnRPC_CLIO);
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(ng_RPCServerHandlerTest, HandleRequest_OutdatedWarning)
|
||||
{
|
||||
backend->setRange(0, 1);
|
||||
runSpawn([&](boost::asio::yield_context yield) {
|
||||
auto const request = makeHttpRequest(R"json({"method":"some_method"})json");
|
||||
|
||||
EXPECT_CALL(*rpcEngine_, post).WillOnce([&](auto&& fn, auto&&) {
|
||||
EXPECT_CALL(connectionMetadata_, wasUpgraded).WillRepeatedly(Return(not request.isHttp()));
|
||||
EXPECT_CALL(*rpcEngine_, buildResponse)
|
||||
.WillOnce(Return(rpc::Result{rpc::ReturnType{boost::json::object{{"some key", "some value"}}}}));
|
||||
EXPECT_CALL(*rpcEngine_, notifyComplete);
|
||||
EXPECT_CALL(*etl_, lastCloseAgeSeconds).WillOnce(Return(61));
|
||||
fn(yield);
|
||||
return true;
|
||||
});
|
||||
auto response = rpcServerHandler_(request, connectionMetadata_, nullptr, yield);
|
||||
|
||||
auto const httpResponse = std::move(response).intoHttpResponse();
|
||||
EXPECT_EQ(httpResponse.result(), http::status::ok);
|
||||
|
||||
auto const jsonResponse = boost::json::parse(httpResponse.body()).as_object();
|
||||
|
||||
std::unordered_set<int64_t> warningCodes;
|
||||
std::ranges::transform(
|
||||
jsonResponse.at("warnings").as_array(),
|
||||
std::inserter(warningCodes, warningCodes.end()),
|
||||
[](auto const& w) { return w.as_object().at("id").as_int64(); }
|
||||
);
|
||||
|
||||
EXPECT_EQ(warningCodes.size(), 2);
|
||||
EXPECT_TRUE(warningCodes.contains(rpc::warnRPC_CLIO));
|
||||
EXPECT_TRUE(warningCodes.contains(rpc::warnRPC_OUTDATED));
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(ng_RPCServerHandlerTest, HandleRequest_Successful_HttpRequest_Forwarded)
|
||||
{
|
||||
backend->setRange(0, 1);
|
||||
runSpawn([&](boost::asio::yield_context yield) {
|
||||
auto const request = makeHttpRequest(R"json({"method":"some_method"})json");
|
||||
|
||||
EXPECT_CALL(*rpcEngine_, post).WillOnce([&](auto&& fn, auto&&) {
|
||||
EXPECT_CALL(connectionMetadata_, wasUpgraded).WillRepeatedly(Return(not request.isHttp()));
|
||||
EXPECT_CALL(*rpcEngine_, buildResponse)
|
||||
.WillOnce(Return(rpc::Result{rpc::ReturnType{boost::json::object{
|
||||
{"result", boost::json::object{{"some key", "some value"}}}, {"forwarded", true}
|
||||
}}}));
|
||||
EXPECT_CALL(*rpcEngine_, notifyComplete);
|
||||
EXPECT_CALL(*etl_, lastCloseAgeSeconds).WillOnce(Return(1));
|
||||
fn(yield);
|
||||
return true;
|
||||
});
|
||||
auto response = rpcServerHandler_(request, connectionMetadata_, nullptr, yield);
|
||||
|
||||
auto const httpResponse = std::move(response).intoHttpResponse();
|
||||
EXPECT_EQ(httpResponse.result(), http::status::ok);
|
||||
|
||||
auto const jsonResponse = boost::json::parse(httpResponse.body()).as_object();
|
||||
EXPECT_EQ(jsonResponse.at("result").at("some key").as_string(), "some value");
|
||||
EXPECT_EQ(jsonResponse.at("result").at("status").as_string(), "success");
|
||||
EXPECT_EQ(jsonResponse.at("forwarded").as_bool(), true);
|
||||
|
||||
ASSERT_EQ(jsonResponse.at("warnings").as_array().size(), 1) << jsonResponse;
|
||||
EXPECT_EQ(jsonResponse.at("warnings").as_array().at(0).as_object().at("id").as_int64(), rpc::warnRPC_CLIO);
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(ng_RPCServerHandlerTest, HandleRequest_Successful_HttpRequest_HasError)
|
||||
{
|
||||
backend->setRange(0, 1);
|
||||
runSpawn([&](boost::asio::yield_context yield) {
|
||||
auto const request = makeHttpRequest(R"json({"method":"some_method"})json");
|
||||
|
||||
EXPECT_CALL(*rpcEngine_, post).WillOnce([&](auto&& fn, auto&&) {
|
||||
EXPECT_CALL(connectionMetadata_, wasUpgraded).WillRepeatedly(Return(not request.isHttp()));
|
||||
EXPECT_CALL(*rpcEngine_, buildResponse)
|
||||
.WillOnce(Return(rpc::Result{
|
||||
rpc::ReturnType{boost::json::object{{"some key", "some value"}, {"error", "some error"}}}
|
||||
}));
|
||||
EXPECT_CALL(*rpcEngine_, notifyComplete);
|
||||
EXPECT_CALL(*etl_, lastCloseAgeSeconds).WillOnce(Return(1));
|
||||
fn(yield);
|
||||
return true;
|
||||
});
|
||||
auto response = rpcServerHandler_(request, connectionMetadata_, nullptr, yield);
|
||||
|
||||
auto const httpResponse = std::move(response).intoHttpResponse();
|
||||
EXPECT_EQ(httpResponse.result(), http::status::ok);
|
||||
|
||||
auto const jsonResponse = boost::json::parse(httpResponse.body()).as_object();
|
||||
EXPECT_EQ(jsonResponse.at("result").at("some key").as_string(), "some value");
|
||||
EXPECT_EQ(jsonResponse.at("result").at("error").as_string(), "some error");
|
||||
|
||||
ASSERT_EQ(jsonResponse.at("warnings").as_array().size(), 1) << jsonResponse;
|
||||
EXPECT_EQ(jsonResponse.at("warnings").as_array().at(0).as_object().at("id").as_int64(), rpc::warnRPC_CLIO);
|
||||
});
|
||||
}
|
||||
|
||||
struct ng_RPCServerHandlerWsTest : ng_RPCServerHandlerTest {
|
||||
struct MockSubscriptionContext : web::SubscriptionContextInterface {
|
||||
using web::SubscriptionContextInterface::SubscriptionContextInterface;
|
||||
|
||||
MOCK_METHOD(void, send, (std::shared_ptr<std::string>), (override));
|
||||
MOCK_METHOD(void, onDisconnect, (web::SubscriptionContextInterface::OnDisconnectSlot const&), (override));
|
||||
MOCK_METHOD(void, setApiSubversion, (uint32_t), (override));
|
||||
MOCK_METHOD(uint32_t, apiSubversion, (), (const, override));
|
||||
};
|
||||
using StrictMockSubscriptionContext = testing::StrictMock<MockSubscriptionContext>;
|
||||
|
||||
std::shared_ptr<StrictMockSubscriptionContext> subscriptionContext_ =
|
||||
std::make_shared<StrictMockSubscriptionContext>(tagFactory_);
|
||||
};
|
||||
|
||||
TEST_F(ng_RPCServerHandlerWsTest, HandleRequest_Successful_WsRequest)
|
||||
{
|
||||
backend->setRange(0, 1);
|
||||
runSpawn([&](boost::asio::yield_context yield) {
|
||||
Request::HttpHeaders headers;
|
||||
auto const request = Request(R"json({"method":"some_method", "id": 1234, "api_version": 1})json", headers);
|
||||
|
||||
EXPECT_CALL(*rpcEngine_, post).WillOnce([&](auto&& fn, auto&&) {
|
||||
EXPECT_CALL(connectionMetadata_, wasUpgraded).WillRepeatedly(Return(not request.isHttp()));
|
||||
EXPECT_CALL(*rpcEngine_, buildResponse)
|
||||
.WillOnce(Return(rpc::Result{rpc::ReturnType{boost::json::object{{"some key", "some value"}}}}));
|
||||
EXPECT_CALL(*rpcEngine_, notifyComplete);
|
||||
EXPECT_CALL(*etl_, lastCloseAgeSeconds).WillOnce(Return(1));
|
||||
fn(yield);
|
||||
return true;
|
||||
});
|
||||
auto const response = rpcServerHandler_(request, connectionMetadata_, subscriptionContext_, yield);
|
||||
|
||||
auto const jsonResponse = boost::json::parse(response.message()).as_object();
|
||||
EXPECT_EQ(jsonResponse.at("result").at("some key").as_string(), "some value");
|
||||
EXPECT_EQ(jsonResponse.at("status").as_string(), "success");
|
||||
|
||||
EXPECT_EQ(jsonResponse.at("type").as_string(), "response");
|
||||
EXPECT_EQ(jsonResponse.at("id").as_int64(), 1234);
|
||||
EXPECT_EQ(jsonResponse.at("api_version").as_int64(), 1);
|
||||
|
||||
ASSERT_EQ(jsonResponse.at("warnings").as_array().size(), 1) << jsonResponse;
|
||||
EXPECT_EQ(jsonResponse.at("warnings").as_array().at(0).as_object().at("id").as_int64(), rpc::warnRPC_CLIO);
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(ng_RPCServerHandlerWsTest, HandleRequest_Successful_WsRequest_HasError)
|
||||
{
|
||||
backend->setRange(0, 1);
|
||||
runSpawn([&](boost::asio::yield_context yield) {
|
||||
Request::HttpHeaders headers;
|
||||
auto const request = Request(R"json({"method":"some_method", "id": 1234, "api_version": 1})json", headers);
|
||||
|
||||
EXPECT_CALL(*rpcEngine_, post).WillOnce([&](auto&& fn, auto&&) {
|
||||
EXPECT_CALL(connectionMetadata_, wasUpgraded).WillRepeatedly(Return(not request.isHttp()));
|
||||
EXPECT_CALL(*rpcEngine_, buildResponse)
|
||||
.WillOnce(Return(rpc::Result{
|
||||
rpc::ReturnType{boost::json::object{{"some key", "some value"}, {"error", "some error"}}}
|
||||
}));
|
||||
EXPECT_CALL(*rpcEngine_, notifyComplete);
|
||||
EXPECT_CALL(*etl_, lastCloseAgeSeconds).WillOnce(Return(1));
|
||||
fn(yield);
|
||||
return true;
|
||||
});
|
||||
auto const response = rpcServerHandler_(request, connectionMetadata_, subscriptionContext_, yield);
|
||||
|
||||
auto const jsonResponse = boost::json::parse(response.message()).as_object();
|
||||
EXPECT_EQ(jsonResponse.at("result").at("some key").as_string(), "some value");
|
||||
EXPECT_EQ(jsonResponse.at("result").at("error").as_string(), "some error");
|
||||
|
||||
EXPECT_EQ(jsonResponse.at("type").as_string(), "response");
|
||||
EXPECT_EQ(jsonResponse.at("id").as_int64(), 1234);
|
||||
EXPECT_EQ(jsonResponse.at("api_version").as_int64(), 1);
|
||||
|
||||
ASSERT_EQ(jsonResponse.at("warnings").as_array().size(), 1) << jsonResponse;
|
||||
EXPECT_EQ(jsonResponse.at("warnings").as_array().at(0).as_object().at("id").as_int64(), rpc::warnRPC_CLIO);
|
||||
});
|
||||
}
|
||||
@@ -26,8 +26,10 @@
|
||||
#include <boost/beast/http/verb.hpp>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <iterator>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
using namespace web::ng;
|
||||
namespace http = boost::beast::http;
|
||||
@@ -176,6 +178,35 @@ INSTANTIATE_TEST_SUITE_P(
|
||||
tests::util::NameGenerator
|
||||
);
|
||||
|
||||
struct RequestHttpHeadersTest : RequestTest {
|
||||
http::field const headerName_ = http::field::user_agent;
|
||||
std::string const headerValue_ = "clio";
|
||||
};
|
||||
|
||||
TEST_F(RequestHttpHeadersTest, httpHeaders_HttpRequest)
|
||||
{
|
||||
auto httpRequest = http::request<http::string_body>{http::verb::get, "/", 11};
|
||||
httpRequest.set(headerName_, headerValue_);
|
||||
Request const request{std::move(httpRequest)};
|
||||
|
||||
auto const& headersFromRequest = request.httpHeaders();
|
||||
ASSERT_EQ(headersFromRequest.count(headerName_), 1);
|
||||
ASSERT_EQ(std::distance(headersFromRequest.cbegin(), headersFromRequest.cend()), 1);
|
||||
EXPECT_EQ(headersFromRequest.at(headerName_), headerValue_);
|
||||
}
|
||||
|
||||
TEST_F(RequestHttpHeadersTest, httpHeaders_WsRequest)
|
||||
{
|
||||
Request::HttpHeaders headers;
|
||||
headers.set(headerName_, headerValue_);
|
||||
Request const request{"websocket message", headers};
|
||||
|
||||
auto const& headersFromRequest = request.httpHeaders();
|
||||
ASSERT_EQ(std::distance(headersFromRequest.cbegin(), headersFromRequest.cend()), 1);
|
||||
ASSERT_EQ(headersFromRequest.count(headerName_), 1);
|
||||
EXPECT_EQ(headersFromRequest.at(headerName_), headerValue_);
|
||||
}
|
||||
|
||||
struct RequestHeaderValueTest : RequestTest {};
|
||||
|
||||
TEST_F(RequestHeaderValueTest, headerValue)
|
||||
|
||||
@@ -50,7 +50,7 @@ TEST_F(ResponseDeathTest, asConstBufferWithHttpData)
|
||||
{
|
||||
Request const request{http::request<http::string_body>{http::verb::get, "/", 11}};
|
||||
web::ng::Response const response{boost::beast::http::status::ok, "message", request};
|
||||
EXPECT_DEATH(response.asConstBuffer(), "");
|
||||
EXPECT_DEATH(response.asWsResponse(), "");
|
||||
}
|
||||
|
||||
struct ResponseTest : testing::Test {
|
||||
@@ -104,7 +104,7 @@ TEST_F(ResponseTest, asConstBuffer)
|
||||
std::string const responseMessage = "response message";
|
||||
web::ng::Response const response{responseStatus_, responseMessage, request};
|
||||
|
||||
auto const buffer = response.asConstBuffer();
|
||||
auto const buffer = response.asWsResponse();
|
||||
EXPECT_EQ(buffer.size(), responseMessage.size());
|
||||
|
||||
std::string const messageFromBuffer{static_cast<char const*>(buffer.data()), buffer.size()};
|
||||
@@ -117,7 +117,7 @@ TEST_F(ResponseTest, asConstBufferJson)
|
||||
boost::json::object const responseMessage{{"key", "value"}};
|
||||
web::ng::Response const response{responseStatus_, responseMessage, request};
|
||||
|
||||
auto const buffer = response.asConstBuffer();
|
||||
auto const buffer = response.asWsResponse();
|
||||
EXPECT_EQ(buffer.size(), boost::json::serialize(responseMessage).size());
|
||||
|
||||
std::string const messageFromBuffer{static_cast<char const*>(buffer.data()), buffer.size()};
|
||||
|
||||
@@ -25,7 +25,9 @@
|
||||
#include "util/TestHttpClient.hpp"
|
||||
#include "util/TestWebSocketClient.hpp"
|
||||
#include "util/config/Config.hpp"
|
||||
#include "web/SubscriptionContextInterface.hpp"
|
||||
#include "web/ng/Connection.hpp"
|
||||
#include "web/ng/ProcessingPolicy.hpp"
|
||||
#include "web/ng/Request.hpp"
|
||||
#include "web/ng/Response.hpp"
|
||||
#include "web/ng/Server.hpp"
|
||||
@@ -48,7 +50,6 @@
|
||||
#include <optional>
|
||||
#include <ranges>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
using namespace web::ng;
|
||||
|
||||
@@ -164,20 +165,24 @@ struct ServerTest : SyncAsioContextTest {
|
||||
std::string const headerName_ = "Some-header";
|
||||
std::string const headerValue_ = "some value";
|
||||
|
||||
testing::StrictMock<testing::MockFunction<Response(Request const&, ConnectionContext, boost::asio::yield_context)>>
|
||||
testing::StrictMock<testing::MockFunction<
|
||||
Response(Request const&, ConnectionMetadata const&, web::SubscriptionContextPtr, boost::asio::yield_context)>>
|
||||
getHandler_;
|
||||
testing::StrictMock<testing::MockFunction<Response(Request const&, ConnectionContext, boost::asio::yield_context)>>
|
||||
testing::StrictMock<testing::MockFunction<
|
||||
Response(Request const&, ConnectionMetadata const&, web::SubscriptionContextPtr, boost::asio::yield_context)>>
|
||||
postHandler_;
|
||||
testing::StrictMock<testing::MockFunction<Response(Request const&, ConnectionContext, boost::asio::yield_context)>>
|
||||
testing::StrictMock<testing::MockFunction<
|
||||
Response(Request const&, ConnectionMetadata const&, web::SubscriptionContextPtr, boost::asio::yield_context)>>
|
||||
wsHandler_;
|
||||
};
|
||||
|
||||
TEST_F(ServerTest, BadEndpoint)
|
||||
{
|
||||
boost::asio::ip::tcp::endpoint const endpoint{boost::asio::ip::address_v4::from_string("1.2.3.4"), 0};
|
||||
impl::ConnectionHandler connectionHandler{impl::ConnectionHandler::ProcessingPolicy::Sequential, std::nullopt};
|
||||
util::TagDecoratorFactory const tagDecoratorFactory{util::Config{boost::json::value{}}};
|
||||
Server server{ctx, endpoint, std::nullopt, std::move(connectionHandler), tagDecoratorFactory};
|
||||
Server server{
|
||||
ctx, endpoint, std::nullopt, ProcessingPolicy::Sequential, std::nullopt, tagDecoratorFactory, std::nullopt
|
||||
};
|
||||
auto maybeError = server.run();
|
||||
ASSERT_TRUE(maybeError.has_value());
|
||||
EXPECT_THAT(*maybeError, testing::HasSubstr("Error creating TCP acceptor"));
|
||||
@@ -251,7 +256,7 @@ TEST_P(ServerHttpTest, RequestResponse)
|
||||
|
||||
EXPECT_CALL(handler, Call)
|
||||
.Times(3)
|
||||
.WillRepeatedly([&, response = response](Request const& receivedRequest, auto&&, auto&&) {
|
||||
.WillRepeatedly([&, response = response](Request const& receivedRequest, auto&&, auto&&, auto&&) {
|
||||
EXPECT_TRUE(receivedRequest.isHttp());
|
||||
EXPECT_EQ(receivedRequest.method(), GetParam().expectedMethod());
|
||||
EXPECT_EQ(receivedRequest.message(), request.body());
|
||||
@@ -317,7 +322,7 @@ TEST_F(ServerTest, WsRequestResponse)
|
||||
|
||||
EXPECT_CALL(wsHandler_, Call)
|
||||
.Times(3)
|
||||
.WillRepeatedly([&, response = response](Request const& receivedRequest, auto&&, auto&&) {
|
||||
.WillRepeatedly([&, response = response](Request const& receivedRequest, auto&&, auto&&, auto&&) {
|
||||
EXPECT_FALSE(receivedRequest.isHttp());
|
||||
EXPECT_EQ(receivedRequest.method(), Request::Method::Websocket);
|
||||
EXPECT_EQ(receivedRequest.message(), requestMessage_);
|
||||
|
||||
166
tests/unit/web/ng/SubscriptionContextTests.cpp
Normal file
166
tests/unit/web/ng/SubscriptionContextTests.cpp
Normal file
@@ -0,0 +1,166 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of clio: https://github.com/XRPLF/clio
|
||||
Copyright (c) 2024, 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 "util/AsioContextTestFixture.hpp"
|
||||
#include "util/Taggable.hpp"
|
||||
#include "util/config/Config.hpp"
|
||||
#include "web/SubscriptionContextInterface.hpp"
|
||||
#include "web/ng/Connection.hpp"
|
||||
#include "web/ng/Error.hpp"
|
||||
#include "web/ng/SubscriptionContext.hpp"
|
||||
#include "web/ng/impl/MockWsConnection.hpp"
|
||||
|
||||
#include <boost/asio/buffer.hpp>
|
||||
#include <boost/asio/post.hpp>
|
||||
#include <boost/asio/spawn.hpp>
|
||||
#include <boost/beast/core/buffers_to_string.hpp>
|
||||
#include <boost/beast/core/flat_buffer.hpp>
|
||||
#include <boost/system/errc.hpp>
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
using namespace web::ng;
|
||||
|
||||
struct ng_SubscriptionContextTests : SyncAsioContextTest {
|
||||
util::TagDecoratorFactory tagFactory_{util::Config{}};
|
||||
MockWsConnectionImpl connection_{"some ip", boost::beast::flat_buffer{}, tagFactory_};
|
||||
testing::StrictMock<testing::MockFunction<bool(Error const&, Connection const&)>> errorHandler_;
|
||||
|
||||
SubscriptionContext
|
||||
makeSubscriptionContext(boost::asio::yield_context yield, std::optional<size_t> maxSendQueueSize = std::nullopt)
|
||||
{
|
||||
return SubscriptionContext{tagFactory_, connection_, maxSendQueueSize, yield, errorHandler_.AsStdFunction()};
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(ng_SubscriptionContextTests, Send)
|
||||
{
|
||||
runSpawn([this](boost::asio::yield_context yield) {
|
||||
auto subscriptionContext = makeSubscriptionContext(yield);
|
||||
auto const message = std::make_shared<std::string>("some message");
|
||||
|
||||
EXPECT_CALL(connection_, sendBuffer).WillOnce([&message](boost::asio::const_buffer buffer, auto, auto) {
|
||||
EXPECT_EQ(boost::beast::buffers_to_string(buffer), *message);
|
||||
return std::nullopt;
|
||||
});
|
||||
subscriptionContext.send(message);
|
||||
subscriptionContext.disconnect(yield);
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(ng_SubscriptionContextTests, SendOrder)
|
||||
{
|
||||
runSpawn([this](boost::asio::yield_context yield) {
|
||||
auto subscriptionContext = makeSubscriptionContext(yield);
|
||||
auto const message1 = std::make_shared<std::string>("message1");
|
||||
auto const message2 = std::make_shared<std::string>("message2");
|
||||
|
||||
testing::Sequence sequence;
|
||||
EXPECT_CALL(connection_, sendBuffer)
|
||||
.InSequence(sequence)
|
||||
.WillOnce([&message1](boost::asio::const_buffer buffer, auto, auto) {
|
||||
EXPECT_EQ(boost::beast::buffers_to_string(buffer), *message1);
|
||||
return std::nullopt;
|
||||
});
|
||||
EXPECT_CALL(connection_, sendBuffer)
|
||||
.InSequence(sequence)
|
||||
.WillOnce([&message2](boost::asio::const_buffer buffer, auto, auto) {
|
||||
EXPECT_EQ(boost::beast::buffers_to_string(buffer), *message2);
|
||||
return std::nullopt;
|
||||
});
|
||||
|
||||
subscriptionContext.send(message1);
|
||||
subscriptionContext.send(message2);
|
||||
subscriptionContext.disconnect(yield);
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(ng_SubscriptionContextTests, SendFailed)
|
||||
{
|
||||
runSpawn([this](boost::asio::yield_context yield) {
|
||||
auto subscriptionContext = makeSubscriptionContext(yield);
|
||||
auto const message = std::make_shared<std::string>("some message");
|
||||
|
||||
EXPECT_CALL(connection_, sendBuffer).WillOnce([&message](boost::asio::const_buffer buffer, auto, auto) {
|
||||
EXPECT_EQ(boost::beast::buffers_to_string(buffer), *message);
|
||||
return boost::system::errc::make_error_code(boost::system::errc::not_supported);
|
||||
});
|
||||
EXPECT_CALL(errorHandler_, Call).WillOnce(testing::Return(true));
|
||||
EXPECT_CALL(connection_, close);
|
||||
subscriptionContext.send(message);
|
||||
subscriptionContext.disconnect(yield);
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(ng_SubscriptionContextTests, SendTooManySubscriptions)
|
||||
{
|
||||
runSpawn([this](boost::asio::yield_context yield) {
|
||||
auto subscriptionContext = makeSubscriptionContext(yield, 1);
|
||||
auto const message = std::make_shared<std::string>("message1");
|
||||
|
||||
EXPECT_CALL(connection_, sendBuffer)
|
||||
.WillOnce([&message](boost::asio::const_buffer buffer, boost::asio::yield_context innerYield, auto) {
|
||||
boost::asio::post(innerYield); // simulate send is slow by switching to another coroutine
|
||||
EXPECT_EQ(boost::beast::buffers_to_string(buffer), *message);
|
||||
return std::nullopt;
|
||||
});
|
||||
EXPECT_CALL(connection_, close);
|
||||
|
||||
subscriptionContext.send(message);
|
||||
subscriptionContext.send(message);
|
||||
subscriptionContext.send(message);
|
||||
subscriptionContext.disconnect(yield);
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(ng_SubscriptionContextTests, SendAfterDisconnect)
|
||||
{
|
||||
runSpawn([this](boost::asio::yield_context yield) {
|
||||
auto subscriptionContext = makeSubscriptionContext(yield);
|
||||
auto const message = std::make_shared<std::string>("some message");
|
||||
subscriptionContext.disconnect(yield);
|
||||
subscriptionContext.send(message);
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(ng_SubscriptionContextTests, OnDisconnect)
|
||||
{
|
||||
testing::StrictMock<testing::MockFunction<void(web::SubscriptionContextInterface*)>> onDisconnect;
|
||||
|
||||
runSpawn([&](boost::asio::yield_context yield) {
|
||||
auto subscriptionContext = makeSubscriptionContext(yield);
|
||||
subscriptionContext.onDisconnect(onDisconnect.AsStdFunction());
|
||||
EXPECT_CALL(onDisconnect, Call(&subscriptionContext));
|
||||
subscriptionContext.disconnect(yield);
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(ng_SubscriptionContextTests, SetApiSubversion)
|
||||
{
|
||||
runSpawn([this](boost::asio::yield_context yield) {
|
||||
auto subscriptionContext = makeSubscriptionContext(yield);
|
||||
subscriptionContext.setApiSubversion(42);
|
||||
EXPECT_EQ(subscriptionContext.apiSubversion(), 42);
|
||||
});
|
||||
}
|
||||
@@ -21,16 +21,21 @@
|
||||
#include "util/Taggable.hpp"
|
||||
#include "util/UnsupportedType.hpp"
|
||||
#include "util/config/Config.hpp"
|
||||
#include "web/SubscriptionContextInterface.hpp"
|
||||
#include "web/ng/Connection.hpp"
|
||||
#include "web/ng/Error.hpp"
|
||||
#include "web/ng/MockConnection.hpp"
|
||||
#include "web/ng/ProcessingPolicy.hpp"
|
||||
#include "web/ng/Request.hpp"
|
||||
#include "web/ng/Response.hpp"
|
||||
#include "web/ng/impl/ConnectionHandler.hpp"
|
||||
#include "web/ng/impl/MockHttpConnection.hpp"
|
||||
#include "web/ng/impl/MockWsConnection.hpp"
|
||||
|
||||
#include <boost/asio/buffer.hpp>
|
||||
#include <boost/asio/error.hpp>
|
||||
#include <boost/asio/spawn.hpp>
|
||||
#include <boost/asio/steady_timer.hpp>
|
||||
#include <boost/beast/core/buffers_to_string.hpp>
|
||||
#include <boost/beast/core/flat_buffer.hpp>
|
||||
#include <boost/beast/http/error.hpp>
|
||||
#include <boost/beast/http/message.hpp>
|
||||
@@ -58,8 +63,8 @@ namespace http = boost::beast::http;
|
||||
namespace websocket = boost::beast::websocket;
|
||||
|
||||
struct ConnectionHandlerTest : SyncAsioContextTest {
|
||||
ConnectionHandlerTest(ConnectionHandler::ProcessingPolicy policy, std::optional<size_t> maxParallelConnections)
|
||||
: connectionHandler_{policy, maxParallelConnections}
|
||||
ConnectionHandlerTest(ProcessingPolicy policy, std::optional<size_t> maxParallelConnections)
|
||||
: tagFactory_{util::Config{}}, connectionHandler_{policy, maxParallelConnections, tagFactory_, std::nullopt}
|
||||
{
|
||||
}
|
||||
|
||||
@@ -88,65 +93,71 @@ struct ConnectionHandlerTest : SyncAsioContextTest {
|
||||
return Request{std::forward<Args>(args)...};
|
||||
}
|
||||
|
||||
util::TagDecoratorFactory tagFactory_;
|
||||
ConnectionHandler connectionHandler_;
|
||||
|
||||
util::TagDecoratorFactory tagDecoratorFactory_{util::Config(boost::json::object{{"log_tag_style", "uint"}})};
|
||||
StrictMockConnectionPtr mockConnection_ =
|
||||
std::make_unique<StrictMockConnection>("1.2.3.4", beast::flat_buffer{}, tagDecoratorFactory_);
|
||||
StrictMockHttpConnectionPtr mockHttpConnection_ =
|
||||
std::make_unique<StrictMockHttpConnection>("1.2.3.4", beast::flat_buffer{}, tagDecoratorFactory_);
|
||||
StrictMockWsConnectionPtr mockWsConnection_ =
|
||||
std::make_unique<StrictMockWsConnection>("1.2.3.4", beast::flat_buffer{}, tagDecoratorFactory_);
|
||||
};
|
||||
|
||||
struct ConnectionHandlerSequentialProcessingTest : ConnectionHandlerTest {
|
||||
ConnectionHandlerSequentialProcessingTest()
|
||||
: ConnectionHandlerTest(ConnectionHandler::ProcessingPolicy::Sequential, std::nullopt)
|
||||
ConnectionHandlerSequentialProcessingTest() : ConnectionHandlerTest(ProcessingPolicy::Sequential, std::nullopt)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(ConnectionHandlerSequentialProcessingTest, ReceiveError)
|
||||
{
|
||||
EXPECT_CALL(*mockConnection_, receive).WillOnce(Return(makeError(http::error::end_of_stream)));
|
||||
EXPECT_CALL(*mockHttpConnection_, wasUpgraded).WillOnce(Return(false));
|
||||
EXPECT_CALL(*mockHttpConnection_, receive).WillOnce(Return(makeError(http::error::end_of_stream)));
|
||||
|
||||
runSpawn([this](boost::asio::yield_context yield) {
|
||||
connectionHandler_.processConnection(std::move(mockConnection_), yield);
|
||||
connectionHandler_.processConnection(std::move(mockHttpConnection_), yield);
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(ConnectionHandlerSequentialProcessingTest, ReceiveError_CloseConnection)
|
||||
{
|
||||
EXPECT_CALL(*mockConnection_, receive).WillOnce(Return(makeError(boost::asio::error::timed_out)));
|
||||
EXPECT_CALL(*mockConnection_, close);
|
||||
EXPECT_CALL(*mockHttpConnection_, wasUpgraded).WillOnce(Return(false));
|
||||
EXPECT_CALL(*mockHttpConnection_, receive).WillOnce(Return(makeError(boost::asio::error::timed_out)));
|
||||
EXPECT_CALL(*mockHttpConnection_, close);
|
||||
|
||||
runSpawn([this](boost::asio::yield_context yield) {
|
||||
connectionHandler_.processConnection(std::move(mockConnection_), yield);
|
||||
connectionHandler_.processConnection(std::move(mockHttpConnection_), yield);
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(ConnectionHandlerSequentialProcessingTest, Receive_Handle_NoHandler_Send)
|
||||
{
|
||||
EXPECT_CALL(*mockConnection_, receive)
|
||||
EXPECT_CALL(*mockHttpConnection_, wasUpgraded).WillOnce(Return(false));
|
||||
EXPECT_CALL(*mockHttpConnection_, receive)
|
||||
.WillOnce(Return(makeRequest("some_request", Request::HttpHeaders{})))
|
||||
.WillOnce(Return(makeError(websocket::error::closed)));
|
||||
|
||||
EXPECT_CALL(*mockConnection_, send).WillOnce([](Response response, auto&&, auto&&) {
|
||||
EXPECT_CALL(*mockHttpConnection_, send).WillOnce([](Response response, auto&&, auto&&) {
|
||||
EXPECT_EQ(response.message(), "WebSocket is not supported by this server");
|
||||
return std::nullopt;
|
||||
});
|
||||
|
||||
runSpawn([this](boost::asio::yield_context yield) {
|
||||
connectionHandler_.processConnection(std::move(mockConnection_), yield);
|
||||
connectionHandler_.processConnection(std::move(mockHttpConnection_), yield);
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(ConnectionHandlerSequentialProcessingTest, Receive_Handle_BadTarget_Send)
|
||||
{
|
||||
std::string const target = "/some/target";
|
||||
|
||||
std::string const requestMessage = "some message";
|
||||
EXPECT_CALL(*mockConnection_, receive)
|
||||
|
||||
EXPECT_CALL(*mockHttpConnection_, wasUpgraded).WillOnce(Return(false));
|
||||
EXPECT_CALL(*mockHttpConnection_, receive)
|
||||
.WillOnce(Return(makeRequest(http::request<http::string_body>{http::verb::get, target, 11, requestMessage})))
|
||||
.WillOnce(Return(makeError(http::error::end_of_stream)));
|
||||
|
||||
EXPECT_CALL(*mockConnection_, send).WillOnce([](Response response, auto&&, auto&&) {
|
||||
EXPECT_CALL(*mockHttpConnection_, send).WillOnce([](Response response, auto&&, auto&&) {
|
||||
EXPECT_EQ(response.message(), "Bad target");
|
||||
auto const httpResponse = std::move(response).intoHttpResponse();
|
||||
EXPECT_EQ(httpResponse.result(), http::status::bad_request);
|
||||
@@ -155,57 +166,126 @@ TEST_F(ConnectionHandlerSequentialProcessingTest, Receive_Handle_BadTarget_Send)
|
||||
});
|
||||
|
||||
runSpawn([this](boost::asio::yield_context yield) {
|
||||
connectionHandler_.processConnection(std::move(mockConnection_), yield);
|
||||
connectionHandler_.processConnection(std::move(mockHttpConnection_), yield);
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(ConnectionHandlerSequentialProcessingTest, Receive_Handle_BadMethod_Send)
|
||||
{
|
||||
EXPECT_CALL(*mockConnection_, receive)
|
||||
EXPECT_CALL(*mockHttpConnection_, wasUpgraded).WillOnce(Return(false));
|
||||
EXPECT_CALL(*mockHttpConnection_, receive)
|
||||
.WillOnce(Return(makeRequest(http::request<http::string_body>{http::verb::acl, "/", 11})))
|
||||
.WillOnce(Return(makeError(http::error::end_of_stream)));
|
||||
|
||||
EXPECT_CALL(*mockConnection_, send).WillOnce([](Response response, auto&&, auto&&) {
|
||||
EXPECT_CALL(*mockHttpConnection_, send).WillOnce([](Response response, auto&&, auto&&) {
|
||||
EXPECT_EQ(response.message(), "Unsupported http method");
|
||||
return std::nullopt;
|
||||
});
|
||||
|
||||
runSpawn([this](boost::asio::yield_context yield) {
|
||||
connectionHandler_.processConnection(std::move(mockConnection_), yield);
|
||||
connectionHandler_.processConnection(std::move(mockHttpConnection_), yield);
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(ConnectionHandlerSequentialProcessingTest, Receive_Handle_Send)
|
||||
{
|
||||
testing::StrictMock<testing::MockFunction<Response(Request const&, ConnectionContext, boost::asio::yield_context)>>
|
||||
testing::StrictMock<testing::MockFunction<
|
||||
Response(Request const&, ConnectionMetadata const&, web::SubscriptionContextPtr, boost::asio::yield_context)>>
|
||||
wsHandlerMock;
|
||||
connectionHandler_.onWs(wsHandlerMock.AsStdFunction());
|
||||
|
||||
std::string const requestMessage = "some message";
|
||||
std::string const responseMessage = "some response";
|
||||
EXPECT_CALL(*mockConnection_, receive)
|
||||
|
||||
EXPECT_CALL(*mockWsConnection_, wasUpgraded).WillOnce(Return(true));
|
||||
EXPECT_CALL(*mockWsConnection_, receive)
|
||||
.WillOnce(Return(makeRequest(requestMessage, Request::HttpHeaders{})))
|
||||
.WillOnce(Return(makeError(websocket::error::closed)));
|
||||
|
||||
EXPECT_CALL(wsHandlerMock, Call).WillOnce([&](Request const& request, auto&&, auto&&) {
|
||||
EXPECT_CALL(wsHandlerMock, Call).WillOnce([&](Request const& request, auto&&, auto&&, auto&&) {
|
||||
EXPECT_EQ(request.message(), requestMessage);
|
||||
return Response(http::status::ok, responseMessage, request);
|
||||
});
|
||||
|
||||
EXPECT_CALL(*mockConnection_, send).WillOnce([&responseMessage](Response response, auto&&, auto&&) {
|
||||
EXPECT_CALL(*mockWsConnection_, send).WillOnce([&responseMessage](Response response, auto&&, auto&&) {
|
||||
EXPECT_EQ(response.message(), responseMessage);
|
||||
return std::nullopt;
|
||||
});
|
||||
|
||||
runSpawn([this](boost::asio::yield_context yield) {
|
||||
connectionHandler_.processConnection(std::move(mockConnection_), yield);
|
||||
connectionHandler_.processConnection(std::move(mockWsConnection_), yield);
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(ConnectionHandlerSequentialProcessingTest, Receive_Handle_Send_Loop)
|
||||
TEST_F(ConnectionHandlerSequentialProcessingTest, SendSubscriptionMessage)
|
||||
{
|
||||
testing::StrictMock<testing::MockFunction<
|
||||
Response(Request const&, ConnectionMetadata const&, web::SubscriptionContextPtr, boost::asio::yield_context)>>
|
||||
wsHandlerMock;
|
||||
connectionHandler_.onWs(wsHandlerMock.AsStdFunction());
|
||||
|
||||
std::string const subscriptionMessage = "subscription message";
|
||||
|
||||
EXPECT_CALL(*mockWsConnection_, wasUpgraded).WillOnce(Return(true));
|
||||
EXPECT_CALL(*mockWsConnection_, receive)
|
||||
.WillOnce(Return(makeRequest("", Request::HttpHeaders{})))
|
||||
.WillOnce(Return(makeError(websocket::error::closed)));
|
||||
|
||||
EXPECT_CALL(wsHandlerMock, Call)
|
||||
.WillOnce([&](Request const& request, auto&&, web::SubscriptionContextPtr subscriptionContext, auto&&) {
|
||||
EXPECT_NE(subscriptionContext, nullptr);
|
||||
subscriptionContext->send(std::make_shared<std::string>(subscriptionMessage));
|
||||
return Response(http::status::ok, "", request);
|
||||
});
|
||||
|
||||
EXPECT_CALL(*mockWsConnection_, send).WillOnce(Return(std::nullopt));
|
||||
|
||||
EXPECT_CALL(*mockWsConnection_, sendBuffer)
|
||||
.WillOnce([&subscriptionMessage](boost::asio::const_buffer buffer, auto&&, auto&&) {
|
||||
EXPECT_EQ(boost::beast::buffers_to_string(buffer), subscriptionMessage);
|
||||
return std::nullopt;
|
||||
});
|
||||
|
||||
runSpawn([this](boost::asio::yield_context yield) {
|
||||
connectionHandler_.processConnection(std::move(mockWsConnection_), yield);
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(ConnectionHandlerSequentialProcessingTest, SubscriptionContextIsDisconnectedAfterProcessingFinished)
|
||||
{
|
||||
testing::StrictMock<testing::MockFunction<
|
||||
Response(Request const&, ConnectionMetadata const&, web::SubscriptionContextPtr, boost::asio::yield_context)>>
|
||||
wsHandlerMock;
|
||||
connectionHandler_.onWs(wsHandlerMock.AsStdFunction());
|
||||
|
||||
testing::StrictMock<testing::MockFunction<void(web::SubscriptionContextInterface*)>> onDisconnectHook;
|
||||
|
||||
EXPECT_CALL(*mockWsConnection_, wasUpgraded).WillOnce(Return(true));
|
||||
testing::Expectation const expectationReceiveCalled = EXPECT_CALL(*mockWsConnection_, receive)
|
||||
.WillOnce(Return(makeRequest("", Request::HttpHeaders{})))
|
||||
.WillOnce(Return(makeError(websocket::error::closed)));
|
||||
|
||||
EXPECT_CALL(wsHandlerMock, Call)
|
||||
.WillOnce([&](Request const& request, auto&&, web::SubscriptionContextPtr subscriptionContext, auto&&) {
|
||||
EXPECT_NE(subscriptionContext, nullptr);
|
||||
subscriptionContext->onDisconnect(onDisconnectHook.AsStdFunction());
|
||||
return Response(http::status::ok, "", request);
|
||||
});
|
||||
|
||||
EXPECT_CALL(*mockWsConnection_, send).WillOnce(Return(std::nullopt));
|
||||
|
||||
EXPECT_CALL(onDisconnectHook, Call).After(expectationReceiveCalled);
|
||||
|
||||
runSpawn([this](boost::asio::yield_context yield) {
|
||||
connectionHandler_.processConnection(std::move(mockWsConnection_), yield);
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(ConnectionHandlerSequentialProcessingTest, SubscriptionContextIsNullForHttpConnection)
|
||||
{
|
||||
std::string const target = "/some/target";
|
||||
testing::StrictMock<testing::MockFunction<Response(Request const&, ConnectionContext, boost::asio::yield_context)>>
|
||||
testing::StrictMock<testing::MockFunction<
|
||||
Response(Request const&, ConnectionMetadata const&, web::SubscriptionContextPtr, boost::asio::yield_context)>>
|
||||
postHandlerMock;
|
||||
connectionHandler_.onPost(target, postHandlerMock.AsStdFunction());
|
||||
|
||||
@@ -214,33 +294,76 @@ TEST_F(ConnectionHandlerSequentialProcessingTest, Receive_Handle_Send_Loop)
|
||||
|
||||
auto const returnRequest =
|
||||
Return(makeRequest(http::request<http::string_body>{http::verb::post, target, 11, requestMessage}));
|
||||
EXPECT_CALL(*mockConnection_, receive)
|
||||
|
||||
EXPECT_CALL(*mockHttpConnection_, wasUpgraded).WillOnce(Return(false));
|
||||
EXPECT_CALL(*mockHttpConnection_, receive)
|
||||
.WillOnce(returnRequest)
|
||||
.WillOnce(Return(makeError(http::error::partial_message)));
|
||||
|
||||
EXPECT_CALL(postHandlerMock, Call)
|
||||
.WillOnce([&](Request const& request, auto&&, web::SubscriptionContextPtr subscriptionContext, auto&&) {
|
||||
EXPECT_EQ(subscriptionContext, nullptr);
|
||||
|
||||
return Response(http::status::ok, responseMessage, request);
|
||||
});
|
||||
|
||||
EXPECT_CALL(*mockHttpConnection_, send).WillOnce([&responseMessage](Response response, auto&&, auto&&) {
|
||||
EXPECT_EQ(response.message(), responseMessage);
|
||||
return std::nullopt;
|
||||
});
|
||||
|
||||
EXPECT_CALL(*mockHttpConnection_, close);
|
||||
|
||||
runSpawn([this](boost::asio::yield_context yield) {
|
||||
connectionHandler_.processConnection(std::move(mockHttpConnection_), yield);
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(ConnectionHandlerSequentialProcessingTest, Receive_Handle_Send_Loop)
|
||||
{
|
||||
std::string const target = "/some/target";
|
||||
testing::StrictMock<testing::MockFunction<
|
||||
Response(Request const&, ConnectionMetadata const&, web::SubscriptionContextPtr, boost::asio::yield_context)>>
|
||||
postHandlerMock;
|
||||
connectionHandler_.onPost(target, postHandlerMock.AsStdFunction());
|
||||
|
||||
std::string const requestMessage = "some message";
|
||||
std::string const responseMessage = "some response";
|
||||
|
||||
auto const returnRequest =
|
||||
Return(makeRequest(http::request<http::string_body>{http::verb::post, target, 11, requestMessage}));
|
||||
|
||||
EXPECT_CALL(*mockHttpConnection_, wasUpgraded).WillOnce(Return(false));
|
||||
EXPECT_CALL(*mockHttpConnection_, receive)
|
||||
.WillOnce(returnRequest)
|
||||
.WillOnce(returnRequest)
|
||||
.WillOnce(returnRequest)
|
||||
.WillOnce(Return(makeError(http::error::partial_message)));
|
||||
|
||||
EXPECT_CALL(postHandlerMock, Call).Times(3).WillRepeatedly([&](Request const& request, auto&&, auto&&) {
|
||||
EXPECT_CALL(postHandlerMock, Call).Times(3).WillRepeatedly([&](Request const& request, auto&&, auto&&, auto&&) {
|
||||
EXPECT_EQ(request.message(), requestMessage);
|
||||
return Response(http::status::ok, responseMessage, request);
|
||||
});
|
||||
|
||||
EXPECT_CALL(*mockConnection_, send).Times(3).WillRepeatedly([&responseMessage](Response response, auto&&, auto&&) {
|
||||
EXPECT_EQ(response.message(), responseMessage);
|
||||
return std::nullopt;
|
||||
});
|
||||
EXPECT_CALL(*mockHttpConnection_, send)
|
||||
.Times(3)
|
||||
.WillRepeatedly([&responseMessage](Response response, auto&&, auto&&) {
|
||||
EXPECT_EQ(response.message(), responseMessage);
|
||||
return std::nullopt;
|
||||
});
|
||||
|
||||
EXPECT_CALL(*mockConnection_, close);
|
||||
EXPECT_CALL(*mockHttpConnection_, close);
|
||||
|
||||
runSpawn([this](boost::asio::yield_context yield) {
|
||||
connectionHandler_.processConnection(std::move(mockConnection_), yield);
|
||||
connectionHandler_.processConnection(std::move(mockHttpConnection_), yield);
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(ConnectionHandlerSequentialProcessingTest, Receive_Handle_SendError)
|
||||
{
|
||||
std::string const target = "/some/target";
|
||||
testing::StrictMock<testing::MockFunction<Response(Request const&, ConnectionContext, boost::asio::yield_context)>>
|
||||
testing::StrictMock<testing::MockFunction<
|
||||
Response(Request const&, ConnectionMetadata const&, web::SubscriptionContextPtr, boost::asio::yield_context)>>
|
||||
getHandlerMock;
|
||||
|
||||
std::string const requestMessage = "some message";
|
||||
@@ -248,34 +371,38 @@ TEST_F(ConnectionHandlerSequentialProcessingTest, Receive_Handle_SendError)
|
||||
|
||||
connectionHandler_.onGet(target, getHandlerMock.AsStdFunction());
|
||||
|
||||
EXPECT_CALL(*mockConnection_, receive)
|
||||
EXPECT_CALL(*mockHttpConnection_, wasUpgraded).WillOnce(Return(false));
|
||||
EXPECT_CALL(*mockHttpConnection_, receive)
|
||||
.WillOnce(Return(makeRequest(http::request<http::string_body>{http::verb::get, target, 11, requestMessage})));
|
||||
|
||||
EXPECT_CALL(getHandlerMock, Call).WillOnce([&](Request const& request, auto&&, auto&&) {
|
||||
EXPECT_CALL(getHandlerMock, Call).WillOnce([&](Request const& request, auto&&, auto&&, auto&&) {
|
||||
EXPECT_EQ(request.message(), requestMessage);
|
||||
return Response(http::status::ok, responseMessage, request);
|
||||
});
|
||||
|
||||
EXPECT_CALL(*mockConnection_, send).WillOnce([&responseMessage](Response response, auto&&, auto&&) {
|
||||
EXPECT_CALL(*mockHttpConnection_, send).WillOnce([&responseMessage](Response response, auto&&, auto&&) {
|
||||
EXPECT_EQ(response.message(), responseMessage);
|
||||
return makeError(http::error::end_of_stream).error();
|
||||
});
|
||||
|
||||
runSpawn([this](boost::asio::yield_context yield) {
|
||||
connectionHandler_.processConnection(std::move(mockConnection_), yield);
|
||||
connectionHandler_.processConnection(std::move(mockHttpConnection_), yield);
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(ConnectionHandlerSequentialProcessingTest, Stop)
|
||||
{
|
||||
testing::StrictMock<testing::MockFunction<Response(Request const&, ConnectionContext, boost::asio::yield_context)>>
|
||||
testing::StrictMock<testing::MockFunction<
|
||||
Response(Request const&, ConnectionMetadata const&, web::SubscriptionContextPtr, boost::asio::yield_context)>>
|
||||
wsHandlerMock;
|
||||
connectionHandler_.onWs(wsHandlerMock.AsStdFunction());
|
||||
|
||||
std::string const requestMessage = "some message";
|
||||
std::string const responseMessage = "some response";
|
||||
bool connectionClosed = false;
|
||||
EXPECT_CALL(*mockConnection_, receive)
|
||||
|
||||
EXPECT_CALL(*mockWsConnection_, wasUpgraded).WillOnce(Return(true));
|
||||
EXPECT_CALL(*mockWsConnection_, receive)
|
||||
.Times(4)
|
||||
.WillRepeatedly([&](auto&&, auto&&) -> std::expected<Request, Error> {
|
||||
if (connectionClosed) {
|
||||
@@ -284,13 +411,13 @@ TEST_F(ConnectionHandlerSequentialProcessingTest, Stop)
|
||||
return makeRequest(requestMessage, Request::HttpHeaders{});
|
||||
});
|
||||
|
||||
EXPECT_CALL(wsHandlerMock, Call).Times(3).WillRepeatedly([&](Request const& request, auto&&, auto&&) {
|
||||
EXPECT_CALL(wsHandlerMock, Call).Times(3).WillRepeatedly([&](Request const& request, auto&&, auto&&, auto&&) {
|
||||
EXPECT_EQ(request.message(), requestMessage);
|
||||
return Response(http::status::ok, responseMessage, request);
|
||||
});
|
||||
|
||||
size_t numCalls = 0;
|
||||
EXPECT_CALL(*mockConnection_, send).Times(3).WillRepeatedly([&](Response response, auto&&, auto&&) {
|
||||
EXPECT_CALL(*mockWsConnection_, send).Times(3).WillRepeatedly([&](Response response, auto&&, auto&&) {
|
||||
EXPECT_EQ(response.message(), responseMessage);
|
||||
|
||||
++numCalls;
|
||||
@@ -300,10 +427,10 @@ TEST_F(ConnectionHandlerSequentialProcessingTest, Stop)
|
||||
return std::nullopt;
|
||||
});
|
||||
|
||||
EXPECT_CALL(*mockConnection_, close).WillOnce([&connectionClosed]() { connectionClosed = true; });
|
||||
EXPECT_CALL(*mockWsConnection_, close).WillOnce([&connectionClosed]() { connectionClosed = true; });
|
||||
|
||||
runSpawn([this](boost::asio::yield_context yield) {
|
||||
connectionHandler_.processConnection(std::move(mockConnection_), yield);
|
||||
connectionHandler_.processConnection(std::move(mockWsConnection_), yield);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -312,7 +439,7 @@ struct ConnectionHandlerParallelProcessingTest : ConnectionHandlerTest {
|
||||
|
||||
ConnectionHandlerParallelProcessingTest()
|
||||
: ConnectionHandlerTest(
|
||||
ConnectionHandler::ProcessingPolicy::Parallel,
|
||||
ProcessingPolicy::Parallel,
|
||||
ConnectionHandlerParallelProcessingTest::maxParallelRequests
|
||||
)
|
||||
{
|
||||
@@ -329,43 +456,48 @@ struct ConnectionHandlerParallelProcessingTest : ConnectionHandlerTest {
|
||||
|
||||
TEST_F(ConnectionHandlerParallelProcessingTest, ReceiveError)
|
||||
{
|
||||
EXPECT_CALL(*mockConnection_, receive).WillOnce(Return(makeError(http::error::end_of_stream)));
|
||||
EXPECT_CALL(*mockHttpConnection_, wasUpgraded).WillOnce(Return(false));
|
||||
EXPECT_CALL(*mockHttpConnection_, receive).WillOnce(Return(makeError(http::error::end_of_stream)));
|
||||
|
||||
runSpawn([this](boost::asio::yield_context yield) {
|
||||
connectionHandler_.processConnection(std::move(mockConnection_), yield);
|
||||
connectionHandler_.processConnection(std::move(mockHttpConnection_), yield);
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(ConnectionHandlerParallelProcessingTest, Receive_Handle_Send)
|
||||
{
|
||||
testing::StrictMock<testing::MockFunction<Response(Request const&, ConnectionContext, boost::asio::yield_context)>>
|
||||
testing::StrictMock<testing::MockFunction<
|
||||
Response(Request const&, ConnectionMetadata const&, web::SubscriptionContextPtr, boost::asio::yield_context)>>
|
||||
wsHandlerMock;
|
||||
connectionHandler_.onWs(wsHandlerMock.AsStdFunction());
|
||||
|
||||
std::string const requestMessage = "some message";
|
||||
std::string const responseMessage = "some response";
|
||||
EXPECT_CALL(*mockConnection_, receive)
|
||||
|
||||
EXPECT_CALL(*mockWsConnection_, wasUpgraded).WillOnce(Return(true));
|
||||
EXPECT_CALL(*mockWsConnection_, receive)
|
||||
.WillOnce(Return(makeRequest(requestMessage, Request::HttpHeaders{})))
|
||||
.WillOnce(Return(makeError(websocket::error::closed)));
|
||||
|
||||
EXPECT_CALL(wsHandlerMock, Call).WillOnce([&](Request const& request, auto&&, auto&&) {
|
||||
EXPECT_CALL(wsHandlerMock, Call).WillOnce([&](Request const& request, auto&&, auto&&, auto&&) {
|
||||
EXPECT_EQ(request.message(), requestMessage);
|
||||
return Response(http::status::ok, responseMessage, request);
|
||||
});
|
||||
|
||||
EXPECT_CALL(*mockConnection_, send).WillOnce([&responseMessage](Response response, auto&&, auto&&) {
|
||||
EXPECT_CALL(*mockWsConnection_, send).WillOnce([&responseMessage](Response response, auto&&, auto&&) {
|
||||
EXPECT_EQ(response.message(), responseMessage);
|
||||
return std::nullopt;
|
||||
});
|
||||
|
||||
runSpawn([this](boost::asio::yield_context yield) {
|
||||
connectionHandler_.processConnection(std::move(mockConnection_), yield);
|
||||
connectionHandler_.processConnection(std::move(mockWsConnection_), yield);
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(ConnectionHandlerParallelProcessingTest, Receive_Handle_Send_Loop)
|
||||
{
|
||||
testing::StrictMock<testing::MockFunction<Response(Request const&, ConnectionContext, boost::asio::yield_context)>>
|
||||
testing::StrictMock<testing::MockFunction<
|
||||
Response(Request const&, ConnectionMetadata const&, web::SubscriptionContextPtr, boost::asio::yield_context)>>
|
||||
wsHandlerMock;
|
||||
connectionHandler_.onWs(wsHandlerMock.AsStdFunction());
|
||||
|
||||
@@ -373,29 +505,34 @@ TEST_F(ConnectionHandlerParallelProcessingTest, Receive_Handle_Send_Loop)
|
||||
std::string const responseMessage = "some response";
|
||||
|
||||
auto const returnRequest = [&](auto&&, auto&&) { return makeRequest(requestMessage, Request::HttpHeaders{}); };
|
||||
EXPECT_CALL(*mockConnection_, receive)
|
||||
|
||||
EXPECT_CALL(*mockWsConnection_, wasUpgraded).WillOnce(Return(true));
|
||||
EXPECT_CALL(*mockWsConnection_, receive)
|
||||
.WillOnce(returnRequest)
|
||||
.WillOnce(returnRequest)
|
||||
.WillOnce(Return(makeError(websocket::error::closed)));
|
||||
|
||||
EXPECT_CALL(wsHandlerMock, Call).Times(2).WillRepeatedly([&](Request const& request, auto&&, auto&&) {
|
||||
EXPECT_CALL(wsHandlerMock, Call).Times(2).WillRepeatedly([&](Request const& request, auto&&, auto&&, auto&&) {
|
||||
EXPECT_EQ(request.message(), requestMessage);
|
||||
return Response(http::status::ok, responseMessage, request);
|
||||
});
|
||||
|
||||
EXPECT_CALL(*mockConnection_, send).Times(2).WillRepeatedly([&responseMessage](Response response, auto&&, auto&&) {
|
||||
EXPECT_EQ(response.message(), responseMessage);
|
||||
return std::nullopt;
|
||||
});
|
||||
EXPECT_CALL(*mockWsConnection_, send)
|
||||
.Times(2)
|
||||
.WillRepeatedly([&responseMessage](Response response, auto&&, auto&&) {
|
||||
EXPECT_EQ(response.message(), responseMessage);
|
||||
return std::nullopt;
|
||||
});
|
||||
|
||||
runSpawn([this](boost::asio::yield_context yield) {
|
||||
connectionHandler_.processConnection(std::move(mockConnection_), yield);
|
||||
connectionHandler_.processConnection(std::move(mockWsConnection_), yield);
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(ConnectionHandlerParallelProcessingTest, Receive_Handle_Send_Loop_TooManyRequest)
|
||||
{
|
||||
testing::StrictMock<testing::MockFunction<Response(Request const&, ConnectionContext, boost::asio::yield_context)>>
|
||||
testing::StrictMock<testing::MockFunction<
|
||||
Response(Request const&, ConnectionMetadata const&, web::SubscriptionContextPtr, boost::asio::yield_context)>>
|
||||
wsHandlerMock;
|
||||
connectionHandler_.onWs(wsHandlerMock.AsStdFunction());
|
||||
|
||||
@@ -404,7 +541,9 @@ TEST_F(ConnectionHandlerParallelProcessingTest, Receive_Handle_Send_Loop_TooMany
|
||||
|
||||
auto const returnRequest = [&](auto&&, auto&&) { return makeRequest(requestMessage, Request::HttpHeaders{}); };
|
||||
testing::Sequence const sequence;
|
||||
EXPECT_CALL(*mockConnection_, receive)
|
||||
|
||||
EXPECT_CALL(*mockWsConnection_, wasUpgraded).WillOnce(Return(true));
|
||||
EXPECT_CALL(*mockWsConnection_, receive)
|
||||
.WillOnce(returnRequest)
|
||||
.WillOnce(returnRequest)
|
||||
.WillOnce(returnRequest)
|
||||
@@ -414,14 +553,14 @@ TEST_F(ConnectionHandlerParallelProcessingTest, Receive_Handle_Send_Loop_TooMany
|
||||
|
||||
EXPECT_CALL(wsHandlerMock, Call)
|
||||
.Times(3)
|
||||
.WillRepeatedly([&](Request const& request, auto&&, boost::asio::yield_context yield) {
|
||||
.WillRepeatedly([&](Request const& request, auto&&, auto&&, boost::asio::yield_context yield) {
|
||||
EXPECT_EQ(request.message(), requestMessage);
|
||||
asyncSleep(yield, std::chrono::milliseconds{3});
|
||||
return Response(http::status::ok, responseMessage, request);
|
||||
});
|
||||
|
||||
EXPECT_CALL(
|
||||
*mockConnection_,
|
||||
*mockWsConnection_,
|
||||
send(
|
||||
testing::ResultOf([](Response response) { return response.message(); }, responseMessage),
|
||||
testing::_,
|
||||
@@ -432,7 +571,7 @@ TEST_F(ConnectionHandlerParallelProcessingTest, Receive_Handle_Send_Loop_TooMany
|
||||
.WillRepeatedly(Return(std::nullopt));
|
||||
|
||||
EXPECT_CALL(
|
||||
*mockConnection_,
|
||||
*mockWsConnection_,
|
||||
send(
|
||||
testing::ResultOf(
|
||||
[](Response response) { return response.message(); }, "Too many requests for one connection"
|
||||
@@ -445,6 +584,6 @@ TEST_F(ConnectionHandlerParallelProcessingTest, Receive_Handle_Send_Loop_TooMany
|
||||
.WillRepeatedly(Return(std::nullopt));
|
||||
|
||||
runSpawn([this](boost::asio::yield_context yield) {
|
||||
connectionHandler_.processConnection(std::move(mockConnection_), yield);
|
||||
connectionHandler_.processConnection(std::move(mockWsConnection_), yield);
|
||||
});
|
||||
}
|
||||
|
||||
358
tests/unit/web/ng/impl/ErrorHandlingTests.cpp
Normal file
358
tests/unit/web/ng/impl/ErrorHandlingTests.cpp
Normal file
@@ -0,0 +1,358 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of clio: https://github.com/XRPLF/clio
|
||||
Copyright (c) 2024, 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 "rpc/Errors.hpp"
|
||||
#include "util/LoggerFixtures.hpp"
|
||||
#include "util/NameGenerator.hpp"
|
||||
#include "web/ng/Request.hpp"
|
||||
#include "web/ng/impl/ErrorHandling.hpp"
|
||||
|
||||
#include <boost/beast/http/field.hpp>
|
||||
#include <boost/beast/http/message.hpp>
|
||||
#include <boost/beast/http/status.hpp>
|
||||
#include <boost/beast/http/string_body.hpp>
|
||||
#include <boost/beast/http/verb.hpp>
|
||||
#include <boost/json/object.hpp>
|
||||
#include <boost/json/parse.hpp>
|
||||
#include <boost/json/serialize.hpp>
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <variant>
|
||||
|
||||
using namespace web::ng::impl;
|
||||
using namespace web::ng;
|
||||
|
||||
namespace http = boost::beast::http;
|
||||
|
||||
struct ng_ErrorHandlingTests : NoLoggerFixture {
|
||||
static Request
|
||||
makeRequest(bool isHttp, std::optional<std::string> body = std::nullopt)
|
||||
{
|
||||
if (isHttp)
|
||||
return Request{http::request<http::string_body>{http::verb::post, "/", 11, body.value_or("")}};
|
||||
return Request{body.value_or(""), Request::HttpHeaders{}};
|
||||
}
|
||||
};
|
||||
|
||||
struct ng_ErrorHandlingMakeErrorTestBundle {
|
||||
std::string testName;
|
||||
bool isHttp;
|
||||
rpc::Status status;
|
||||
std::string expectedMessage;
|
||||
boost::beast::http::status expectedStatus;
|
||||
};
|
||||
|
||||
struct ng_ErrorHandlingMakeErrorTest : ng_ErrorHandlingTests,
|
||||
testing::WithParamInterface<ng_ErrorHandlingMakeErrorTestBundle> {};
|
||||
|
||||
TEST_P(ng_ErrorHandlingMakeErrorTest, MakeError)
|
||||
{
|
||||
auto const request = makeRequest(GetParam().isHttp);
|
||||
ErrorHelper errorHelper{request};
|
||||
|
||||
auto response = errorHelper.makeError(GetParam().status);
|
||||
EXPECT_EQ(response.message(), GetParam().expectedMessage);
|
||||
if (GetParam().isHttp) {
|
||||
auto const httpResponse = std::move(response).intoHttpResponse();
|
||||
EXPECT_EQ(httpResponse.result(), GetParam().expectedStatus);
|
||||
|
||||
std::string expectedContentType = "text/html";
|
||||
if (std::holds_alternative<rpc::RippledError>(GetParam().status.code))
|
||||
expectedContentType = "application/json";
|
||||
|
||||
EXPECT_EQ(httpResponse.at(http::field::content_type), expectedContentType);
|
||||
}
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(
|
||||
ng_ErrorHandlingMakeErrorTestGroup,
|
||||
ng_ErrorHandlingMakeErrorTest,
|
||||
testing::ValuesIn({
|
||||
ng_ErrorHandlingMakeErrorTestBundle{
|
||||
"WsRequest",
|
||||
false,
|
||||
rpc::Status{rpc::RippledError::rpcTOO_BUSY},
|
||||
R"({"error":"tooBusy","error_code":9,"error_message":"The server is too busy to help you now.","status":"error","type":"response"})",
|
||||
boost::beast::http::status::ok
|
||||
},
|
||||
ng_ErrorHandlingMakeErrorTestBundle{
|
||||
"HttpRequest_InvalidApiVersion",
|
||||
true,
|
||||
rpc::Status{rpc::ClioError::rpcINVALID_API_VERSION},
|
||||
"invalid_API_version",
|
||||
boost::beast::http::status::bad_request
|
||||
},
|
||||
ng_ErrorHandlingMakeErrorTestBundle{
|
||||
"HttpRequest_CommandIsMissing",
|
||||
true,
|
||||
rpc::Status{rpc::ClioError::rpcCOMMAND_IS_MISSING},
|
||||
"Null method",
|
||||
boost::beast::http::status::bad_request
|
||||
},
|
||||
ng_ErrorHandlingMakeErrorTestBundle{
|
||||
"HttpRequest_CommandIsEmpty",
|
||||
true,
|
||||
rpc::Status{rpc::ClioError::rpcCOMMAND_IS_EMPTY},
|
||||
"method is empty",
|
||||
boost::beast::http::status::bad_request
|
||||
},
|
||||
ng_ErrorHandlingMakeErrorTestBundle{
|
||||
"HttpRequest_CommandNotString",
|
||||
true,
|
||||
rpc::Status{rpc::ClioError::rpcCOMMAND_NOT_STRING},
|
||||
"method is not string",
|
||||
boost::beast::http::status::bad_request
|
||||
},
|
||||
ng_ErrorHandlingMakeErrorTestBundle{
|
||||
"HttpRequest_ParamsUnparseable",
|
||||
true,
|
||||
rpc::Status{rpc::ClioError::rpcPARAMS_UNPARSEABLE},
|
||||
"params unparseable",
|
||||
boost::beast::http::status::bad_request
|
||||
},
|
||||
ng_ErrorHandlingMakeErrorTestBundle{
|
||||
"HttpRequest_RippledError",
|
||||
true,
|
||||
rpc::Status{rpc::RippledError::rpcTOO_BUSY},
|
||||
R"({"result":{"error":"tooBusy","error_code":9,"error_message":"The server is too busy to help you now.","status":"error","type":"response"}})",
|
||||
boost::beast::http::status::bad_request
|
||||
},
|
||||
}),
|
||||
tests::util::NameGenerator
|
||||
);
|
||||
|
||||
struct ng_ErrorHandlingMakeInternalErrorTestBundle {
|
||||
std::string testName;
|
||||
bool isHttp;
|
||||
std::optional<std::string> request;
|
||||
boost::json::object expectedResult;
|
||||
};
|
||||
|
||||
struct ng_ErrorHandlingMakeInternalErrorTest
|
||||
: ng_ErrorHandlingTests,
|
||||
testing::WithParamInterface<ng_ErrorHandlingMakeInternalErrorTestBundle> {};
|
||||
|
||||
TEST_P(ng_ErrorHandlingMakeInternalErrorTest, ComposeError)
|
||||
{
|
||||
auto const request = makeRequest(GetParam().isHttp, GetParam().request);
|
||||
std::optional<boost::json::object> const requestJson = GetParam().request.has_value()
|
||||
? std::make_optional(boost::json::parse(*GetParam().request).as_object())
|
||||
: std::nullopt;
|
||||
ErrorHelper errorHelper{request, requestJson};
|
||||
|
||||
auto response = errorHelper.makeInternalError();
|
||||
|
||||
EXPECT_EQ(response.message(), boost::json::serialize(GetParam().expectedResult));
|
||||
if (GetParam().isHttp) {
|
||||
auto const httpResponse = std::move(response).intoHttpResponse();
|
||||
EXPECT_EQ(httpResponse.result(), boost::beast::http::status::internal_server_error);
|
||||
EXPECT_EQ(httpResponse.at(http::field::content_type), "application/json");
|
||||
}
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(
|
||||
ng_ErrorHandlingComposeErrorTestGroup,
|
||||
ng_ErrorHandlingMakeInternalErrorTest,
|
||||
testing::ValuesIn(
|
||||
{ng_ErrorHandlingMakeInternalErrorTestBundle{
|
||||
"NoRequest_WebsocketConnection",
|
||||
false,
|
||||
std::nullopt,
|
||||
{{"error", "internal"},
|
||||
{"error_code", 73},
|
||||
{"error_message", "Internal error."},
|
||||
{"status", "error"},
|
||||
{"type", "response"}}
|
||||
},
|
||||
ng_ErrorHandlingMakeInternalErrorTestBundle{
|
||||
"NoRequest_HttpConnection",
|
||||
true,
|
||||
std::nullopt,
|
||||
{{"result",
|
||||
{{"error", "internal"},
|
||||
{"error_code", 73},
|
||||
{"error_message", "Internal error."},
|
||||
{"status", "error"},
|
||||
{"type", "response"}}}}
|
||||
},
|
||||
ng_ErrorHandlingMakeInternalErrorTestBundle{
|
||||
"Request_WebsocketConnection",
|
||||
false,
|
||||
std::string{R"({"id": 1, "api_version": 2})"},
|
||||
{{"error", "internal"},
|
||||
{"error_code", 73},
|
||||
{"error_message", "Internal error."},
|
||||
{"status", "error"},
|
||||
{"type", "response"},
|
||||
{"id", 1},
|
||||
{"api_version", 2},
|
||||
{"request", {{"id", 1}, {"api_version", 2}}}}
|
||||
},
|
||||
ng_ErrorHandlingMakeInternalErrorTestBundle{
|
||||
"Request_WebsocketConnection_NoId",
|
||||
false,
|
||||
std::string{R"({"api_version": 2})"},
|
||||
{{"error", "internal"},
|
||||
{"error_code", 73},
|
||||
{"error_message", "Internal error."},
|
||||
{"status", "error"},
|
||||
{"type", "response"},
|
||||
{"api_version", 2},
|
||||
{"request", {{"api_version", 2}}}}
|
||||
},
|
||||
ng_ErrorHandlingMakeInternalErrorTestBundle{
|
||||
"Request_HttpConnection",
|
||||
true,
|
||||
std::string{R"({"id": 1, "api_version": 2})"},
|
||||
{{"result",
|
||||
{{"error", "internal"},
|
||||
{"error_code", 73},
|
||||
{"error_message", "Internal error."},
|
||||
{"status", "error"},
|
||||
{"type", "response"},
|
||||
{"id", 1},
|
||||
{"request", {{"id", 1}, {"api_version", 2}}}}}}
|
||||
}}
|
||||
),
|
||||
tests::util::NameGenerator
|
||||
);
|
||||
|
||||
TEST_F(ng_ErrorHandlingTests, MakeNotReadyError)
|
||||
{
|
||||
auto const request = makeRequest(true);
|
||||
auto response = ErrorHelper{request}.makeNotReadyError();
|
||||
EXPECT_EQ(
|
||||
response.message(),
|
||||
std::string{
|
||||
R"({"result":{"error":"notReady","error_code":13,"error_message":"Not ready to handle this request.","status":"error","type":"response"}})"
|
||||
}
|
||||
);
|
||||
auto const httpResponse = std::move(response).intoHttpResponse();
|
||||
EXPECT_EQ(httpResponse.result(), http::status::ok);
|
||||
EXPECT_EQ(httpResponse.at(http::field::content_type), "application/json");
|
||||
}
|
||||
|
||||
TEST_F(ng_ErrorHandlingTests, MakeTooBusyError_WebsocketRequest)
|
||||
{
|
||||
auto const request = makeRequest(false);
|
||||
auto response = ErrorHelper{request}.makeTooBusyError();
|
||||
EXPECT_EQ(
|
||||
response.message(),
|
||||
std::string{
|
||||
R"({"error":"tooBusy","error_code":9,"error_message":"The server is too busy to help you now.","status":"error","type":"response"})"
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
TEST_F(ng_ErrorHandlingTests, sendTooBusyError_HttpConnection)
|
||||
{
|
||||
auto const request = makeRequest(true);
|
||||
auto response = ErrorHelper{request}.makeTooBusyError();
|
||||
EXPECT_EQ(
|
||||
response.message(),
|
||||
std::string{
|
||||
R"({"error":"tooBusy","error_code":9,"error_message":"The server is too busy to help you now.","status":"error","type":"response"})"
|
||||
}
|
||||
);
|
||||
auto const httpResponse = std::move(response).intoHttpResponse();
|
||||
EXPECT_EQ(httpResponse.result(), boost::beast::http::status::service_unavailable);
|
||||
EXPECT_EQ(httpResponse.at(http::field::content_type), "application/json");
|
||||
}
|
||||
|
||||
TEST_F(ng_ErrorHandlingTests, makeJsonParsingError_WebsocketConnection)
|
||||
{
|
||||
auto const request = makeRequest(false);
|
||||
auto response = ErrorHelper{request}.makeJsonParsingError();
|
||||
EXPECT_EQ(
|
||||
response.message(),
|
||||
std::string{
|
||||
R"({"error":"badSyntax","error_code":1,"error_message":"Syntax error.","status":"error","type":"response"})"
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
TEST_F(ng_ErrorHandlingTests, makeJsonParsingError_HttpConnection)
|
||||
{
|
||||
auto const request = makeRequest(true);
|
||||
auto response = ErrorHelper{request}.makeJsonParsingError();
|
||||
EXPECT_EQ(response.message(), std::string{"Unable to parse JSON from the request"});
|
||||
auto const httpResponse = std::move(response).intoHttpResponse();
|
||||
EXPECT_EQ(httpResponse.result(), boost::beast::http::status::bad_request);
|
||||
EXPECT_EQ(httpResponse.at(http::field::content_type), "text/html");
|
||||
}
|
||||
|
||||
struct ng_ErrorHandlingComposeErrorTestBundle {
|
||||
std::string testName;
|
||||
bool isHttp;
|
||||
std::optional<boost::json::object> request;
|
||||
std::string expectedMessage;
|
||||
};
|
||||
|
||||
struct ng_ErrorHandlingComposeErrorTest : ng_ErrorHandlingTests,
|
||||
testing::WithParamInterface<ng_ErrorHandlingComposeErrorTestBundle> {};
|
||||
|
||||
TEST_P(ng_ErrorHandlingComposeErrorTest, ComposeError)
|
||||
{
|
||||
auto const request = makeRequest(GetParam().isHttp);
|
||||
ErrorHelper errorHelper{request, GetParam().request};
|
||||
auto const response = errorHelper.composeError(rpc::Status{rpc::RippledError::rpcINTERNAL});
|
||||
EXPECT_EQ(boost::json::serialize(response), GetParam().expectedMessage);
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(
|
||||
ng_ErrorHandlingComposeErrorTestGroup,
|
||||
ng_ErrorHandlingComposeErrorTest,
|
||||
testing::ValuesIn(
|
||||
{ng_ErrorHandlingComposeErrorTestBundle{
|
||||
"NoRequest_WebsocketConnection",
|
||||
false,
|
||||
std::nullopt,
|
||||
R"({"error":"internal","error_code":73,"error_message":"Internal error.","status":"error","type":"response"})"
|
||||
},
|
||||
ng_ErrorHandlingComposeErrorTestBundle{
|
||||
"NoRequest_HttpConnection",
|
||||
true,
|
||||
std::nullopt,
|
||||
R"({"result":{"error":"internal","error_code":73,"error_message":"Internal error.","status":"error","type":"response"}})"
|
||||
},
|
||||
ng_ErrorHandlingComposeErrorTestBundle{
|
||||
"Request_WebsocketConnection",
|
||||
false,
|
||||
boost::json::object{{"id", 1}, {"api_version", 2}},
|
||||
R"({"error":"internal","error_code":73,"error_message":"Internal error.","status":"error","type":"response","id":1,"api_version":2,"request":{"id":1,"api_version":2}})",
|
||||
},
|
||||
ng_ErrorHandlingComposeErrorTestBundle{
|
||||
"Request_WebsocketConnection_NoId",
|
||||
false,
|
||||
boost::json::object{{"api_version", 2}},
|
||||
R"({"error":"internal","error_code":73,"error_message":"Internal error.","status":"error","type":"response","api_version":2,"request":{"api_version":2}})",
|
||||
},
|
||||
ng_ErrorHandlingComposeErrorTestBundle{
|
||||
"Request_HttpConnection",
|
||||
true,
|
||||
boost::json::object{{"id", 1}, {"api_version", 2}},
|
||||
R"({"result":{"error":"internal","error_code":73,"error_message":"Internal error.","status":"error","type":"response","id":1,"request":{"id":1,"api_version":2}}})"
|
||||
}}
|
||||
),
|
||||
tests::util::NameGenerator
|
||||
);
|
||||
@@ -27,6 +27,7 @@
|
||||
#include "web/ng/Response.hpp"
|
||||
#include "web/ng/impl/HttpConnection.hpp"
|
||||
|
||||
#include <boost/asio/error.hpp>
|
||||
#include <boost/asio/ip/tcp.hpp>
|
||||
#include <boost/asio/spawn.hpp>
|
||||
#include <boost/asio/ssl/context.hpp>
|
||||
@@ -37,6 +38,7 @@
|
||||
#include <boost/beast/http/string_body.hpp>
|
||||
#include <boost/beast/http/verb.hpp>
|
||||
#include <boost/json/object.hpp>
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <chrono>
|
||||
@@ -94,7 +96,6 @@ TEST_F(HttpConnectionTests, Receive)
|
||||
|
||||
runSpawn([this](boost::asio::yield_context yield) {
|
||||
auto connection = acceptConnection(yield);
|
||||
EXPECT_TRUE(connection.ip() == "127.0.0.1" or connection.ip() == "::1") << connection.ip();
|
||||
|
||||
auto expectedRequest = connection.receive(yield, std::chrono::milliseconds{100});
|
||||
ASSERT_TRUE(expectedRequest.has_value()) << expectedRequest.error().message();
|
||||
@@ -293,3 +294,39 @@ TEST_F(HttpConnectionTests, Upgrade)
|
||||
[&]() { ASSERT_TRUE(expectedWsConnection.has_value()) << expectedWsConnection.error().message(); }();
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(HttpConnectionTests, Ip)
|
||||
{
|
||||
boost::asio::spawn(ctx, [this](boost::asio::yield_context yield) mutable {
|
||||
auto maybeError = httpClient_.connect("localhost", httpServer_.port(), yield, std::chrono::milliseconds{100});
|
||||
[&]() { ASSERT_FALSE(maybeError.has_value()) << maybeError->message(); }();
|
||||
});
|
||||
|
||||
runSpawn([this](boost::asio::yield_context yield) {
|
||||
auto connection = acceptConnection(yield);
|
||||
EXPECT_TRUE(connection.ip() == "127.0.0.1" or connection.ip() == "::1") << connection.ip();
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(HttpConnectionTests, isAdminSetAdmin)
|
||||
{
|
||||
testing::StrictMock<testing::MockFunction<bool()>> adminSetter;
|
||||
EXPECT_CALL(adminSetter, Call).WillOnce(testing::Return(true));
|
||||
|
||||
boost::asio::spawn(ctx, [this](boost::asio::yield_context yield) mutable {
|
||||
auto maybeError = httpClient_.connect("localhost", httpServer_.port(), yield, std::chrono::milliseconds{100});
|
||||
[&]() { ASSERT_FALSE(maybeError.has_value()) << maybeError->message(); }();
|
||||
});
|
||||
|
||||
runSpawn([&](boost::asio::yield_context yield) {
|
||||
auto connection = acceptConnection(yield);
|
||||
EXPECT_FALSE(connection.isAdmin());
|
||||
|
||||
connection.setIsAdmin(adminSetter.AsStdFunction());
|
||||
EXPECT_TRUE(connection.isAdmin());
|
||||
|
||||
// Setter shouldn't not be called here because isAdmin is already set
|
||||
connection.setIsAdmin(adminSetter.AsStdFunction());
|
||||
EXPECT_TRUE(connection.isAdmin());
|
||||
});
|
||||
}
|
||||
|
||||
@@ -23,7 +23,10 @@
|
||||
#include "web/ng/impl/ServerSslContext.hpp"
|
||||
|
||||
#include <boost/json/object.hpp>
|
||||
#include <boost/json/parse.hpp>
|
||||
#include <boost/json/value.hpp>
|
||||
#include <fmt/compile.h>
|
||||
#include <fmt/core.h>
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <test_data/SslCert.hpp>
|
||||
|
||||
Reference in New Issue
Block a user