mirror of
https://github.com/XRPLF/clio.git
synced 2025-12-05 08:48:13 +00:00
@@ -22,7 +22,6 @@
|
||||
#include <util/MockBackend.h>
|
||||
#include <util/MockWsBase.h>
|
||||
#include <util/TestObject.h>
|
||||
#include <webserver/WsBase.h>
|
||||
|
||||
#include <boost/json/parse.hpp>
|
||||
#include <gmock/gmock.h>
|
||||
@@ -66,7 +65,7 @@ TEST(SubscriptionManagerTest, InitAndReport)
|
||||
}
|
||||
|
||||
void
|
||||
CheckSubscriberMessage(std::string out, std::shared_ptr<WsBase> session, int retry = 10)
|
||||
CheckSubscriberMessage(std::string out, std::shared_ptr<Server::ConnectionBase> session, int retry = 10)
|
||||
{
|
||||
auto sessionPtr = static_cast<MockSession*>(session.get());
|
||||
while (retry-- != 0)
|
||||
@@ -87,7 +86,7 @@ protected:
|
||||
clio::Config cfg;
|
||||
std::shared_ptr<SubscriptionManager> subManagerPtr;
|
||||
util::TagDecoratorFactory tagDecoratorFactory{cfg};
|
||||
std::shared_ptr<WsBase> session;
|
||||
std::shared_ptr<Server::ConnectionBase> session;
|
||||
void
|
||||
SetUp() override
|
||||
{
|
||||
@@ -119,8 +118,8 @@ TEST_F(SubscriptionManagerSimpleBackendTest, ReportCurrentSubscriber)
|
||||
"books":2,
|
||||
"book_changes":2
|
||||
})";
|
||||
std::shared_ptr<WsBase> session1 = std::make_shared<MockSession>(tagDecoratorFactory);
|
||||
std::shared_ptr<WsBase> session2 = std::make_shared<MockSession>(tagDecoratorFactory);
|
||||
std::shared_ptr<Server::ConnectionBase> session1 = std::make_shared<MockSession>(tagDecoratorFactory);
|
||||
std::shared_ptr<Server::ConnectionBase> session2 = std::make_shared<MockSession>(tagDecoratorFactory);
|
||||
subManagerPtr->subBookChanges(session1);
|
||||
subManagerPtr->subBookChanges(session2);
|
||||
subManagerPtr->subManifest(session1);
|
||||
@@ -259,7 +258,7 @@ TEST_F(SubscriptionManagerSimpleBackendTest, SubscriptionManagerAccountProposedT
|
||||
auto account = GetAccountIDWithString(ACCOUNT1);
|
||||
subManagerPtr->subProposedAccount(account, session);
|
||||
|
||||
std::shared_ptr<WsBase> sessionIdle = std::make_shared<MockSession>(tagDecoratorFactory);
|
||||
std::shared_ptr<Server::ConnectionBase> sessionIdle = std::make_shared<MockSession>(tagDecoratorFactory);
|
||||
auto accountIdle = GetAccountIDWithString(ACCOUNT2);
|
||||
subManagerPtr->subProposedAccount(accountIdle, sessionIdle);
|
||||
|
||||
@@ -743,7 +742,7 @@ TEST_F(SubscriptionManagerSimpleBackendTest, SubscriptionManagerOrderBook)
|
||||
CheckSubscriberMessage(OrderbookPublish, session);
|
||||
|
||||
// trigger by offer cancel meta data
|
||||
std::shared_ptr<WsBase> session1 = std::make_shared<MockSession>(tagDecoratorFactory);
|
||||
std::shared_ptr<Server::ConnectionBase> session1 = std::make_shared<MockSession>(tagDecoratorFactory);
|
||||
subManagerPtr->subBook(book, session1);
|
||||
metaObj = CreateMetaDataForCancelOffer(CURRENCY, ISSUER, 22, 3, 1);
|
||||
trans1.metadata = metaObj.getSerializer().peekData();
|
||||
@@ -832,7 +831,7 @@ TEST_F(SubscriptionManagerSimpleBackendTest, SubscriptionManagerOrderBook)
|
||||
"engine_result":"tesSUCCESS",
|
||||
"engine_result_message":"The transaction was applied. Only final in a validated ledger."
|
||||
})";
|
||||
std::shared_ptr<WsBase> session2 = std::make_shared<MockSession>(tagDecoratorFactory);
|
||||
std::shared_ptr<Server::ConnectionBase> session2 = std::make_shared<MockSession>(tagDecoratorFactory);
|
||||
subManagerPtr->subBook(book, session2);
|
||||
metaObj = CreateMetaDataForCreateOffer(CURRENCY, ISSUER, 22, 3, 1);
|
||||
trans1.metadata = metaObj.getSerializer().peekData();
|
||||
|
||||
@@ -17,7 +17,9 @@
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#include <subscriptions/Message.h>
|
||||
#include <subscriptions/SubscriptionManager.h>
|
||||
|
||||
#include <util/Fixtures.h>
|
||||
#include <util/MockWsBase.h>
|
||||
|
||||
@@ -49,8 +51,8 @@ class SubscriptionMapTest : public SubscriptionTest
|
||||
TEST_F(SubscriptionTest, SubscriptionCount)
|
||||
{
|
||||
Subscription sub(ctx);
|
||||
std::shared_ptr<WsBase> session1 = std::make_shared<MockSession>(tagDecoratorFactory);
|
||||
std::shared_ptr<WsBase> session2 = std::make_shared<MockSession>(tagDecoratorFactory);
|
||||
std::shared_ptr<Server::ConnectionBase> session1 = std::make_shared<MockSession>(tagDecoratorFactory);
|
||||
std::shared_ptr<Server::ConnectionBase> session2 = std::make_shared<MockSession>(tagDecoratorFactory);
|
||||
sub.subscribe(session1);
|
||||
sub.subscribe(session2);
|
||||
ctx.run();
|
||||
@@ -79,13 +81,13 @@ TEST_F(SubscriptionTest, SubscriptionCount)
|
||||
TEST_F(SubscriptionTest, SubscriptionPublish)
|
||||
{
|
||||
Subscription sub(ctx);
|
||||
std::shared_ptr<WsBase> session1 = std::make_shared<MockSession>(tagDecoratorFactory);
|
||||
std::shared_ptr<WsBase> session2 = std::make_shared<MockSession>(tagDecoratorFactory);
|
||||
std::shared_ptr<Server::ConnectionBase> session1 = std::make_shared<MockSession>(tagDecoratorFactory);
|
||||
std::shared_ptr<Server::ConnectionBase> session2 = std::make_shared<MockSession>(tagDecoratorFactory);
|
||||
sub.subscribe(session1);
|
||||
sub.subscribe(session2);
|
||||
ctx.run();
|
||||
EXPECT_EQ(sub.count(), 2);
|
||||
sub.publish(std::make_shared<Message>("message"));
|
||||
sub.publish(std::make_shared<std::string>("message"));
|
||||
ctx.restart();
|
||||
ctx.run();
|
||||
MockSession* p1 = (MockSession*)(session1.get());
|
||||
@@ -95,7 +97,7 @@ TEST_F(SubscriptionTest, SubscriptionPublish)
|
||||
sub.unsubscribe(session1);
|
||||
ctx.restart();
|
||||
ctx.run();
|
||||
sub.publish(std::make_shared<Message>("message2"));
|
||||
sub.publish(std::make_shared<std::string>("message2"));
|
||||
ctx.restart();
|
||||
ctx.run();
|
||||
EXPECT_EQ(p1->message, "message");
|
||||
@@ -106,16 +108,16 @@ TEST_F(SubscriptionTest, SubscriptionPublish)
|
||||
TEST_F(SubscriptionTest, SubscriptionDeadRemoveSubscriber)
|
||||
{
|
||||
Subscription sub(ctx);
|
||||
std::shared_ptr<WsBase> session1(new MockDeadSession(tagDecoratorFactory));
|
||||
std::shared_ptr<Server::ConnectionBase> session1(new MockDeadSession(tagDecoratorFactory));
|
||||
sub.subscribe(session1);
|
||||
ctx.run();
|
||||
EXPECT_EQ(sub.count(), 1);
|
||||
// trigger dead
|
||||
sub.publish(std::make_shared<Message>("message"));
|
||||
sub.publish(std::make_shared<std::string>("message"));
|
||||
ctx.restart();
|
||||
ctx.run();
|
||||
EXPECT_EQ(session1->dead(), true);
|
||||
sub.publish(std::make_shared<Message>("message"));
|
||||
sub.publish(std::make_shared<std::string>("message"));
|
||||
ctx.restart();
|
||||
ctx.run();
|
||||
EXPECT_EQ(sub.count(), 0);
|
||||
@@ -123,9 +125,9 @@ TEST_F(SubscriptionTest, SubscriptionDeadRemoveSubscriber)
|
||||
|
||||
TEST_F(SubscriptionMapTest, SubscriptionMapCount)
|
||||
{
|
||||
std::shared_ptr<WsBase> session1 = std::make_shared<MockSession>(tagDecoratorFactory);
|
||||
std::shared_ptr<WsBase> session2 = std::make_shared<MockSession>(tagDecoratorFactory);
|
||||
std::shared_ptr<WsBase> session3 = std::make_shared<MockSession>(tagDecoratorFactory);
|
||||
std::shared_ptr<Server::ConnectionBase> session1 = std::make_shared<MockSession>(tagDecoratorFactory);
|
||||
std::shared_ptr<Server::ConnectionBase> session2 = std::make_shared<MockSession>(tagDecoratorFactory);
|
||||
std::shared_ptr<Server::ConnectionBase> session3 = std::make_shared<MockSession>(tagDecoratorFactory);
|
||||
SubscriptionMap<std::string> subMap(ctx);
|
||||
subMap.subscribe(session1, "topic1");
|
||||
subMap.subscribe(session2, "topic1");
|
||||
@@ -155,8 +157,8 @@ TEST_F(SubscriptionMapTest, SubscriptionMapCount)
|
||||
|
||||
TEST_F(SubscriptionMapTest, SubscriptionMapPublish)
|
||||
{
|
||||
std::shared_ptr<WsBase> session1 = std::make_shared<MockSession>(tagDecoratorFactory);
|
||||
std::shared_ptr<WsBase> session2 = std::make_shared<MockSession>(tagDecoratorFactory);
|
||||
std::shared_ptr<Server::ConnectionBase> session1 = std::make_shared<MockSession>(tagDecoratorFactory);
|
||||
std::shared_ptr<Server::ConnectionBase> session2 = std::make_shared<MockSession>(tagDecoratorFactory);
|
||||
SubscriptionMap<std::string> subMap(ctx);
|
||||
const std::string topic1 = "topic1";
|
||||
const std::string topic2 = "topic2";
|
||||
@@ -166,9 +168,9 @@ TEST_F(SubscriptionMapTest, SubscriptionMapPublish)
|
||||
subMap.subscribe(session2, topic2);
|
||||
ctx.run();
|
||||
EXPECT_EQ(subMap.count(), 2);
|
||||
auto message1 = std::make_shared<Message>(topic1Message.data());
|
||||
subMap.publish(message1, topic1); // lvalue
|
||||
subMap.publish(std::make_shared<Message>(topic2Message.data()), topic2); // rvalue
|
||||
auto message1 = std::make_shared<std::string>(topic1Message.data());
|
||||
subMap.publish(message1, topic1); // lvalue
|
||||
subMap.publish(std::make_shared<std::string>(topic2Message.data()), topic2); // rvalue
|
||||
ctx.restart();
|
||||
ctx.run();
|
||||
MockSession* p1 = (MockSession*)(session1.get());
|
||||
@@ -179,8 +181,8 @@ TEST_F(SubscriptionMapTest, SubscriptionMapPublish)
|
||||
|
||||
TEST_F(SubscriptionMapTest, SubscriptionMapDeadRemoveSubscriber)
|
||||
{
|
||||
std::shared_ptr<WsBase> session1(new MockDeadSession(tagDecoratorFactory));
|
||||
std::shared_ptr<WsBase> session2 = std::make_shared<MockSession>(tagDecoratorFactory);
|
||||
std::shared_ptr<Server::ConnectionBase> session1(new MockDeadSession(tagDecoratorFactory));
|
||||
std::shared_ptr<Server::ConnectionBase> session2 = std::make_shared<MockSession>(tagDecoratorFactory);
|
||||
SubscriptionMap<std::string> subMap(ctx);
|
||||
const std::string topic1 = "topic1";
|
||||
const std::string topic2 = "topic2";
|
||||
@@ -190,9 +192,9 @@ TEST_F(SubscriptionMapTest, SubscriptionMapDeadRemoveSubscriber)
|
||||
subMap.subscribe(session2, topic2);
|
||||
ctx.run();
|
||||
EXPECT_EQ(subMap.count(), 2);
|
||||
auto message1 = std::make_shared<Message>(topic1Message.data());
|
||||
subMap.publish(message1, topic1); // lvalue
|
||||
subMap.publish(std::make_shared<Message>(topic2Message.data()), topic2); // rvalue
|
||||
auto message1 = std::make_shared<std::string>(topic1Message);
|
||||
subMap.publish(message1, topic1); // lvalue
|
||||
subMap.publish(std::make_shared<std::string>(topic2Message), topic2); // rvalue
|
||||
ctx.restart();
|
||||
ctx.run();
|
||||
MockDeadSession* p1 = (MockDeadSession*)(session1.get());
|
||||
|
||||
@@ -62,7 +62,7 @@ protected:
|
||||
}
|
||||
|
||||
std::shared_ptr<SubscriptionManager> subManager_;
|
||||
std::shared_ptr<WsBase> session_;
|
||||
std::shared_ptr<Server::ConnectionBase> session_;
|
||||
};
|
||||
|
||||
struct SubscribeParamTestCaseBundle
|
||||
@@ -740,17 +740,11 @@ TEST_F(RPCSubscribeHandlerTest, BooksBothSnapshotSet)
|
||||
auto const rawBackendPtr = static_cast<MockBackend*>(mockBackendPtr.get());
|
||||
auto const issuer = GetAccountIDWithString(ACCOUNT);
|
||||
|
||||
auto const getsXRPPaysUSDBook = getBookBase(std::get<ripple::Book>(RPC::parseBook(
|
||||
ripple::to_currency("USD"), // pays
|
||||
issuer,
|
||||
ripple::xrpCurrency(), // gets
|
||||
ripple::xrpAccount())));
|
||||
auto const getsXRPPaysUSDBook = getBookBase(std::get<ripple::Book>(
|
||||
RPC::parseBook(ripple::to_currency("USD"), issuer, ripple::xrpCurrency(), ripple::xrpAccount())));
|
||||
|
||||
auto const reversedBook = getBookBase(std::get<ripple::Book>(RPC::parseBook(
|
||||
ripple::xrpCurrency(), // pays
|
||||
ripple::xrpAccount(),
|
||||
ripple::to_currency("USD"), // gets
|
||||
issuer)));
|
||||
auto const reversedBook = getBookBase(std::get<ripple::Book>(
|
||||
RPC::parseBook(ripple::xrpCurrency(), ripple::xrpAccount(), ripple::to_currency("USD"), issuer)));
|
||||
|
||||
ON_CALL(*rawBackendPtr, doFetchSuccessorKey(getsXRPPaysUSDBook, MAXSEQ, _))
|
||||
.WillByDefault(Return(ripple::uint256{PAYS20USDGETS10XRPBOOKDIR}));
|
||||
|
||||
@@ -55,7 +55,7 @@ protected:
|
||||
}
|
||||
|
||||
std::shared_ptr<SubscriptionManager> subManager_;
|
||||
std::shared_ptr<WsBase> session_;
|
||||
std::shared_ptr<Server::ConnectionBase> session_;
|
||||
};
|
||||
|
||||
struct UnsubscribeParamTestCaseBundle
|
||||
|
||||
@@ -128,8 +128,18 @@ struct AsyncAsioContextTest : virtual public NoLoggerFixture
|
||||
~AsyncAsioContextTest()
|
||||
{
|
||||
work.reset();
|
||||
if (runner.joinable())
|
||||
runner.join();
|
||||
ctx.stop();
|
||||
runner.join();
|
||||
}
|
||||
|
||||
void
|
||||
stop()
|
||||
{
|
||||
work.reset();
|
||||
ctx.stop();
|
||||
if (runner.joinable())
|
||||
runner.join();
|
||||
}
|
||||
|
||||
protected:
|
||||
|
||||
73
unittests/util/MockRPCEngine.h
Normal file
73
unittests/util/MockRPCEngine.h
Normal file
@@ -0,0 +1,73 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of clio: https://github.com/XRPLF/clio
|
||||
Copyright (c) 2023, the clio developers.
|
||||
|
||||
Permission to use, copy, modify, and distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#pragma once
|
||||
#include <rpc/common/Types.h>
|
||||
#include <webserver/Context.h>
|
||||
|
||||
#include <boost/asio.hpp>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
struct MockAsyncRPCEngine
|
||||
{
|
||||
public:
|
||||
MockAsyncRPCEngine()
|
||||
{
|
||||
work_.emplace(ioc_); // make sure ctx does not stop on its own
|
||||
runner_.emplace([this] { ioc_.run(); });
|
||||
}
|
||||
|
||||
~MockAsyncRPCEngine()
|
||||
{
|
||||
work_.reset();
|
||||
ioc_.stop();
|
||||
if (runner_->joinable())
|
||||
runner_->join();
|
||||
}
|
||||
|
||||
template <typename Fn>
|
||||
bool
|
||||
post(Fn&& func, std::string const& ip)
|
||||
{
|
||||
boost::asio::spawn(ioc_, [handler = std::move(func)](auto yield) mutable { handler(yield); });
|
||||
return true;
|
||||
}
|
||||
|
||||
MOCK_METHOD(void, notifyComplete, (std::string const&, std::chrono::microseconds const&), ());
|
||||
MOCK_METHOD(void, notifyErrored, (std::string const&), ());
|
||||
MOCK_METHOD(void, notifyForwarded, (std::string const&), ());
|
||||
MOCK_METHOD(RPC::Result, buildResponse, (Web::Context const&), ());
|
||||
|
||||
private:
|
||||
boost::asio::io_context ioc_;
|
||||
std::optional<boost::asio::io_service::work> work_;
|
||||
std::optional<std::thread> runner_;
|
||||
};
|
||||
|
||||
struct MockRPCEngine
|
||||
{
|
||||
public:
|
||||
MOCK_METHOD(bool, post, (std::function<void(boost::asio::yield_context)>&&, std::string const&), ());
|
||||
MOCK_METHOD(void, notifyComplete, (std::string const&, std::chrono::microseconds const&), ());
|
||||
MOCK_METHOD(void, notifyErrored, (std::string const&), ());
|
||||
MOCK_METHOD(void, notifyForwarded, (std::string const&), ());
|
||||
MOCK_METHOD(RPC::Result, buildResponse, (Web::Context const&), ());
|
||||
};
|
||||
@@ -20,7 +20,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <ripple/ledger/ReadView.h>
|
||||
#include <webserver/WsBase.h>
|
||||
#include <webserver/interface/ConnectionBase.h>
|
||||
|
||||
#include <boost/asio/spawn.hpp>
|
||||
#include <boost/json.hpp>
|
||||
@@ -28,7 +28,11 @@
|
||||
|
||||
struct MockSubscriptionManager
|
||||
{
|
||||
using session_ptr = std::shared_ptr<WsBase>;
|
||||
public:
|
||||
using session_ptr = std::shared_ptr<Server::ConnectionBase>;
|
||||
MockSubscriptionManager()
|
||||
{
|
||||
}
|
||||
|
||||
MOCK_METHOD(boost::json::object, subLedger, (boost::asio::yield_context&, session_ptr), ());
|
||||
|
||||
@@ -60,9 +64,9 @@ struct MockSubscriptionManager
|
||||
|
||||
MOCK_METHOD(void, unsubBook, (ripple::Book const&, session_ptr), ());
|
||||
|
||||
MOCK_METHOD(void, subBookChanges, (std::shared_ptr<WsBase>), ());
|
||||
MOCK_METHOD(void, subBookChanges, (session_ptr), ());
|
||||
|
||||
MOCK_METHOD(void, unsubBookChanges, (std::shared_ptr<WsBase>), ());
|
||||
MOCK_METHOD(void, unsubBookChanges, (session_ptr), ());
|
||||
|
||||
MOCK_METHOD(void, subManifest, (session_ptr), ());
|
||||
|
||||
|
||||
@@ -19,30 +19,43 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <webserver/WsBase.h>
|
||||
#include <webserver/interface/ConnectionBase.h>
|
||||
|
||||
struct MockSession : public WsBase
|
||||
struct MockSession : public Server::ConnectionBase
|
||||
{
|
||||
std::string message;
|
||||
void
|
||||
send(std::shared_ptr<Message> msg_type) override
|
||||
send(std::shared_ptr<std::string> msg_type) override
|
||||
{
|
||||
message += std::string(msg_type->data());
|
||||
}
|
||||
MockSession(util::TagDecoratorFactory const& factory) : WsBase(factory)
|
||||
|
||||
void
|
||||
send(std::string&& msg, boost::beast::http::status status = boost::beast::http::status::ok) override
|
||||
{
|
||||
message += msg;
|
||||
}
|
||||
|
||||
MockSession(util::TagDecoratorFactory const& factory) : Server::ConnectionBase(factory, "")
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
struct MockDeadSession : public WsBase
|
||||
struct MockDeadSession : public Server::ConnectionBase
|
||||
{
|
||||
void
|
||||
send(std::shared_ptr<Message> msg_type) override
|
||||
send(std::shared_ptr<std::string> _) override
|
||||
{
|
||||
// err happen, the session should remove from subscribers
|
||||
ec_.assign(2, boost::system::system_category());
|
||||
}
|
||||
MockDeadSession(util::TagDecoratorFactory const& factory) : WsBase(factory)
|
||||
|
||||
void
|
||||
send(std::string&& _, boost::beast::http::status __ = boost::beast::http::status::ok) override
|
||||
{
|
||||
}
|
||||
|
||||
MockDeadSession(util::TagDecoratorFactory const& factory) : Server::ConnectionBase(factory, "")
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
253
unittests/util/TestHttpSyncClient.h
Normal file
253
unittests/util/TestHttpSyncClient.h
Normal file
@@ -0,0 +1,253 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of clio: https://github.com/XRPLF/clio
|
||||
Copyright (c) 2023, the clio developers.
|
||||
|
||||
Permission to use, copy, modify, and distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <boost/asio.hpp>
|
||||
#include <boost/beast.hpp>
|
||||
#include <boost/beast/http.hpp>
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace http = boost::beast::http;
|
||||
namespace net = boost::asio;
|
||||
namespace ssl = boost::asio::ssl;
|
||||
using tcp = boost::asio::ip::tcp;
|
||||
|
||||
struct HttpSyncClient
|
||||
{
|
||||
static std::string
|
||||
syncPost(std::string const& host, std::string const& port, std::string const& body)
|
||||
{
|
||||
boost::asio::io_context ioc;
|
||||
|
||||
// These objects perform our I/O
|
||||
net::ip::tcp::resolver resolver(ioc);
|
||||
boost::beast::tcp_stream stream(ioc);
|
||||
|
||||
// Look up the domain name
|
||||
auto const results = resolver.resolve(host, port);
|
||||
|
||||
// Make the connection on the IP address we get from a lookup
|
||||
stream.connect(results);
|
||||
|
||||
http::request<http::string_body> req{http::verb::post, "/", 10};
|
||||
req.set(http::field::host, host);
|
||||
req.set(http::field::user_agent, BOOST_BEAST_VERSION_STRING);
|
||||
req.body() = std::string(body);
|
||||
req.prepare_payload();
|
||||
// Send the HTTP request to the remote host
|
||||
http::write(stream, req);
|
||||
|
||||
// This buffer is used for reading and must be persisted
|
||||
boost::beast::flat_buffer buffer;
|
||||
|
||||
// Declare a container to hold the response
|
||||
http::response<http::string_body> res;
|
||||
|
||||
// Receive the HTTP response
|
||||
http::read(stream, buffer, res);
|
||||
// Gracefully close the socket
|
||||
boost::beast::error_code ec;
|
||||
stream.socket().shutdown(tcp::socket::shutdown_both, ec);
|
||||
|
||||
return std::string(res.body());
|
||||
}
|
||||
};
|
||||
|
||||
class WebSocketSyncClient
|
||||
{
|
||||
// The io_context is required for all I/O
|
||||
net::io_context ioc_;
|
||||
|
||||
// These objects perform our I/O
|
||||
tcp::resolver resolver_{ioc_};
|
||||
boost::beast::websocket::stream<tcp::socket> ws_{ioc_};
|
||||
|
||||
public:
|
||||
void
|
||||
connect(std::string const& host, std::string const& port)
|
||||
{
|
||||
// Look up the domain name
|
||||
auto const results = resolver_.resolve(host, port);
|
||||
|
||||
// Make the connection on the IP address we get from a lookup
|
||||
auto const ep = net::connect(ws_.next_layer(), results);
|
||||
|
||||
// Update the host_ string. This will provide the value of the
|
||||
// Host HTTP header during the WebSocket handshake.
|
||||
// See https://tools.ietf.org/html/rfc7230#section-5.4
|
||||
auto const hostPort = host + ':' + std::to_string(ep.port());
|
||||
|
||||
// Set a decorator to change the User-Agent of the handshake
|
||||
ws_.set_option(boost::beast::websocket::stream_base::decorator([](boost::beast::websocket::request_type& req) {
|
||||
req.set(http::field::user_agent, std::string(BOOST_BEAST_VERSION_STRING) + " websocket-client-coro");
|
||||
}));
|
||||
|
||||
// Perform the websocket handshake
|
||||
ws_.handshake(hostPort, "/");
|
||||
}
|
||||
|
||||
void
|
||||
disconnect()
|
||||
{
|
||||
ws_.close(boost::beast::websocket::close_code::normal);
|
||||
}
|
||||
|
||||
std::string
|
||||
syncPost(std::string const& body)
|
||||
{
|
||||
// Send the message
|
||||
ws_.write(net::buffer(std::string(body)));
|
||||
|
||||
// This buffer will hold the incoming message
|
||||
boost::beast::flat_buffer buffer;
|
||||
|
||||
// Read a message into our buffer
|
||||
ws_.read(buffer);
|
||||
|
||||
return boost::beast::buffers_to_string(buffer.data());
|
||||
}
|
||||
};
|
||||
|
||||
struct HttpsSyncClient
|
||||
{
|
||||
static bool
|
||||
verify_certificate(bool preverified, boost::asio::ssl::verify_context& ctx)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
static std::string
|
||||
syncPost(std::string const& host, std::string const& port, std::string const& body)
|
||||
{
|
||||
// The io_context is required for all I/O
|
||||
net::io_context ioc;
|
||||
boost::asio::ssl::context ctx(boost::asio::ssl::context::sslv23);
|
||||
ctx.set_default_verify_paths();
|
||||
// Verify the remote server's certificate
|
||||
ctx.set_verify_mode(ssl::verify_none);
|
||||
|
||||
// These objects perform our I/O
|
||||
tcp::resolver resolver(ioc);
|
||||
boost::beast::ssl_stream<boost::beast::tcp_stream> stream(ioc, ctx);
|
||||
|
||||
// disable ssl verification just for testing
|
||||
// stream.set_verify_callback(HttpsSyncClient::verify_certificate);
|
||||
|
||||
// Set SNI Hostname (many hosts need this to handshake successfully)
|
||||
if (!SSL_set_tlsext_host_name(stream.native_handle(), host.c_str()))
|
||||
{
|
||||
boost::beast::error_code ec{static_cast<int>(::ERR_get_error()), net::error::get_ssl_category()};
|
||||
throw boost::beast::system_error{ec};
|
||||
}
|
||||
|
||||
// Look up the domain name
|
||||
auto const results = resolver.resolve(host, port);
|
||||
|
||||
// Make the connection on the IP address we get from a lookup
|
||||
boost::beast::get_lowest_layer(stream).connect(results);
|
||||
|
||||
// Perform the SSL handshake
|
||||
stream.handshake(ssl::stream_base::client);
|
||||
|
||||
// Set up an HTTP GET request message
|
||||
http::request<http::string_body> req{http::verb::post, "/", 10};
|
||||
req.set(http::field::host, host);
|
||||
req.set(http::field::user_agent, BOOST_BEAST_VERSION_STRING);
|
||||
req.body() = std::string(body);
|
||||
req.prepare_payload();
|
||||
// Send the HTTP request to the remote host
|
||||
http::write(stream, req);
|
||||
|
||||
// This buffer is used for reading and must be persisted
|
||||
boost::beast::flat_buffer buffer;
|
||||
|
||||
// Declare a container to hold the response
|
||||
http::response<http::string_body> res;
|
||||
|
||||
// Receive the HTTP response
|
||||
http::read(stream, buffer, res);
|
||||
|
||||
// Write the message to standard out
|
||||
std::cout << res << std::endl;
|
||||
|
||||
// Gracefully close the stream
|
||||
boost::beast::error_code ec;
|
||||
stream.shutdown(ec);
|
||||
return std::string(res.body());
|
||||
}
|
||||
};
|
||||
|
||||
class WebServerSslSyncClient
|
||||
{
|
||||
net::io_context ioc_;
|
||||
std::optional<boost::beast::websocket::stream<boost::beast::ssl_stream<tcp::socket>>> ws_;
|
||||
|
||||
public:
|
||||
void
|
||||
connect(std::string const& host, std::string const& port)
|
||||
{
|
||||
boost::asio::ssl::context ctx(boost::asio::ssl::context::sslv23);
|
||||
ctx.set_default_verify_paths();
|
||||
// Verify the remote server's certificate
|
||||
ctx.set_verify_mode(ssl::verify_none);
|
||||
// These objects perform our I/O
|
||||
tcp::resolver resolver{ioc_};
|
||||
ws_.emplace(ioc_, ctx);
|
||||
|
||||
// Look up the domain name
|
||||
auto const results = resolver.resolve(host, port);
|
||||
|
||||
// Make the connection on the IP address we get from a lookup
|
||||
net::connect(ws_->next_layer().next_layer(), results.begin(), results.end());
|
||||
|
||||
// Perform the SSL handshake
|
||||
ws_->next_layer().handshake(ssl::stream_base::client);
|
||||
|
||||
// Set a decorator to change the User-Agent of the handshake
|
||||
ws_->set_option(boost::beast::websocket::stream_base::decorator([](boost::beast::websocket::request_type& req) {
|
||||
req.set(http::field::user_agent, std::string(BOOST_BEAST_VERSION_STRING) + " websocket-client-coro");
|
||||
}));
|
||||
|
||||
// Perform the websocket handshake
|
||||
ws_->handshake(host, "/");
|
||||
}
|
||||
|
||||
void
|
||||
disconnect()
|
||||
{
|
||||
ws_->close(boost::beast::websocket::close_code::normal);
|
||||
}
|
||||
|
||||
std::string
|
||||
syncPost(std::string const& body)
|
||||
{
|
||||
// Send the message
|
||||
ws_->write(net::buffer(std::string(body)));
|
||||
|
||||
// This buffer will hold the incoming message
|
||||
boost::beast::flat_buffer buffer;
|
||||
|
||||
// Read a message into our buffer
|
||||
ws_->read(buffer);
|
||||
|
||||
return boost::beast::buffers_to_string(buffer.data());
|
||||
}
|
||||
};
|
||||
675
unittests/webserver/RPCExecutorTest.cpp
Normal file
675
unittests/webserver/RPCExecutorTest.cpp
Normal file
@@ -0,0 +1,675 @@
|
||||
/*
|
||||
This file is part of clio: https://github.com/XRPLF/clio
|
||||
Copyright (c) 2023, the clio developers.
|
||||
|
||||
Permission to use, copy, modify, and distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#include <util/Fixtures.h>
|
||||
#include <util/MockETLService.h>
|
||||
#include <util/MockRPCEngine.h>
|
||||
#include <webserver/RPCExecutor.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
constexpr static auto MINSEQ = 10;
|
||||
constexpr static auto MAXSEQ = 30;
|
||||
|
||||
struct MockWsBase : public Server::ConnectionBase
|
||||
{
|
||||
std::string message;
|
||||
|
||||
void
|
||||
send(std::shared_ptr<std::string> msg_type) override
|
||||
{
|
||||
message += std::string(msg_type->data());
|
||||
}
|
||||
|
||||
void
|
||||
send(std::string&& msg, boost::beast::http::status status = boost::beast::http::status::ok) override
|
||||
{
|
||||
message += std::string(msg.data());
|
||||
}
|
||||
|
||||
MockWsBase(util::TagDecoratorFactory const& factory) : Server::ConnectionBase(factory, "localhost.fake.ip")
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
class WebRPCExecutorTest : public MockBackendTest
|
||||
{
|
||||
protected:
|
||||
void
|
||||
SetUp() override
|
||||
{
|
||||
MockBackendTest::SetUp();
|
||||
|
||||
etl = std::make_shared<MockETLService>();
|
||||
rpcEngine = std::make_shared<MockAsyncRPCEngine>();
|
||||
tagFactory = std::make_shared<util::TagDecoratorFactory>(cfg);
|
||||
subManager = std::make_shared<SubscriptionManager>(cfg, mockBackendPtr);
|
||||
session = std::make_shared<MockWsBase>(*tagFactory);
|
||||
rpcExecutor = std::make_shared<RPCExecutor<MockAsyncRPCEngine, MockETLService>>(
|
||||
cfg, mockBackendPtr, rpcEngine, etl, subManager);
|
||||
}
|
||||
|
||||
void
|
||||
TearDown() override
|
||||
{
|
||||
MockBackendTest::TearDown();
|
||||
}
|
||||
|
||||
std::shared_ptr<MockAsyncRPCEngine> rpcEngine;
|
||||
std::shared_ptr<MockETLService> etl;
|
||||
std::shared_ptr<SubscriptionManager> subManager;
|
||||
std::shared_ptr<util::TagDecoratorFactory> tagFactory;
|
||||
std::shared_ptr<RPCExecutor<MockAsyncRPCEngine, MockETLService>> rpcExecutor;
|
||||
std::shared_ptr<MockWsBase> session;
|
||||
clio::Config cfg;
|
||||
};
|
||||
|
||||
TEST_F(WebRPCExecutorTest, HTTPDefaultPath)
|
||||
{
|
||||
auto request = boost::json::parse(R"({
|
||||
"method": "server_info",
|
||||
"params": [{}]
|
||||
})")
|
||||
.as_object();
|
||||
|
||||
mockBackendPtr->updateRange(MINSEQ); // min
|
||||
mockBackendPtr->updateRange(MAXSEQ); // max
|
||||
|
||||
static auto constexpr result = "{}";
|
||||
static auto constexpr response = R"({
|
||||
"result":{
|
||||
"status":"success"
|
||||
},
|
||||
"warnings":[
|
||||
{
|
||||
"id":2001,
|
||||
"message":"This is a clio server. clio only serves validated data. If you want to talk to rippled, include 'ledger_index':'current' in your request"
|
||||
}
|
||||
]
|
||||
})";
|
||||
EXPECT_CALL(*rpcEngine, buildResponse(testing::_))
|
||||
.WillOnce(testing::Return(boost::json::parse(result).as_object()));
|
||||
EXPECT_CALL(*rpcEngine, notifyComplete("server_info", testing::_)).Times(1);
|
||||
|
||||
EXPECT_CALL(*etl, lastCloseAgeSeconds()).WillOnce(testing::Return(45));
|
||||
|
||||
(*rpcExecutor)(std::move(request), session);
|
||||
std::this_thread::sleep_for(200ms);
|
||||
EXPECT_EQ(boost::json::parse(session->message), boost::json::parse(response));
|
||||
}
|
||||
|
||||
TEST_F(WebRPCExecutorTest, WsNormalPath)
|
||||
{
|
||||
session->upgraded = true;
|
||||
auto request = boost::json::parse(R"({
|
||||
"command": "server_info",
|
||||
"id": 99
|
||||
})")
|
||||
.as_object();
|
||||
|
||||
mockBackendPtr->updateRange(MINSEQ); // min
|
||||
mockBackendPtr->updateRange(MAXSEQ); // max
|
||||
|
||||
static auto constexpr result = "{}";
|
||||
static auto constexpr response = R"({
|
||||
"result":{
|
||||
},
|
||||
"id":99,
|
||||
"status":"success",
|
||||
"type":"response",
|
||||
"warnings":[
|
||||
{
|
||||
"id":2001,
|
||||
"message":"This is a clio server. clio only serves validated data. If you want to talk to rippled, include 'ledger_index':'current' in your request"
|
||||
}
|
||||
]
|
||||
})";
|
||||
EXPECT_CALL(*rpcEngine, buildResponse(testing::_))
|
||||
.WillOnce(testing::Return(boost::json::parse(result).as_object()));
|
||||
EXPECT_CALL(*rpcEngine, notifyComplete("server_info", testing::_)).Times(1);
|
||||
|
||||
EXPECT_CALL(*etl, lastCloseAgeSeconds()).WillOnce(testing::Return(45));
|
||||
|
||||
(*rpcExecutor)(std::move(request), session);
|
||||
std::this_thread::sleep_for(200ms);
|
||||
EXPECT_EQ(boost::json::parse(session->message), boost::json::parse(response));
|
||||
}
|
||||
|
||||
TEST_F(WebRPCExecutorTest, HTTPForwardedPath)
|
||||
{
|
||||
auto request = boost::json::parse(R"({
|
||||
"method": "server_info",
|
||||
"params": [{}]
|
||||
})")
|
||||
.as_object();
|
||||
|
||||
mockBackendPtr->updateRange(MINSEQ); // min
|
||||
mockBackendPtr->updateRange(MAXSEQ); // max
|
||||
|
||||
static auto constexpr result = R"({
|
||||
"result": {
|
||||
"index": 1
|
||||
},
|
||||
"forwarded": true
|
||||
})";
|
||||
static auto constexpr response = R"({
|
||||
"result":{
|
||||
"index": 1,
|
||||
"status": "success"
|
||||
},
|
||||
"forwarded": true,
|
||||
"warnings":[
|
||||
{
|
||||
"id":2001,
|
||||
"message":"This is a clio server. clio only serves validated data. If you want to talk to rippled, include 'ledger_index':'current' in your request"
|
||||
}
|
||||
]
|
||||
})";
|
||||
EXPECT_CALL(*rpcEngine, buildResponse(testing::_))
|
||||
.WillOnce(testing::Return(boost::json::parse(result).as_object()));
|
||||
EXPECT_CALL(*rpcEngine, notifyComplete("server_info", testing::_)).Times(1);
|
||||
|
||||
EXPECT_CALL(*etl, lastCloseAgeSeconds()).WillOnce(testing::Return(45));
|
||||
|
||||
(*rpcExecutor)(std::move(request), session);
|
||||
std::this_thread::sleep_for(200ms);
|
||||
EXPECT_EQ(boost::json::parse(session->message), boost::json::parse(response));
|
||||
}
|
||||
|
||||
TEST_F(WebRPCExecutorTest, WsForwardedPath)
|
||||
{
|
||||
session->upgraded = true;
|
||||
auto request = boost::json::parse(R"({
|
||||
"command": "server_info",
|
||||
"id": 99
|
||||
})")
|
||||
.as_object();
|
||||
|
||||
mockBackendPtr->updateRange(MINSEQ); // min
|
||||
mockBackendPtr->updateRange(MAXSEQ); // max
|
||||
|
||||
static auto constexpr result = R"({
|
||||
"result": {
|
||||
"index": 1
|
||||
},
|
||||
"forwarded": true
|
||||
})";
|
||||
static auto constexpr response = R"({
|
||||
"result":{
|
||||
"index": 1
|
||||
},
|
||||
"forwarded": true,
|
||||
"id":99,
|
||||
"status":"success",
|
||||
"type":"response",
|
||||
"warnings":[
|
||||
{
|
||||
"id":2001,
|
||||
"message":"This is a clio server. clio only serves validated data. If you want to talk to rippled, include 'ledger_index':'current' in your request"
|
||||
}
|
||||
]
|
||||
})";
|
||||
EXPECT_CALL(*rpcEngine, buildResponse(testing::_))
|
||||
.WillOnce(testing::Return(boost::json::parse(result).as_object()));
|
||||
EXPECT_CALL(*rpcEngine, notifyComplete("server_info", testing::_)).Times(1);
|
||||
|
||||
EXPECT_CALL(*etl, lastCloseAgeSeconds()).WillOnce(testing::Return(45));
|
||||
|
||||
(*rpcExecutor)(std::move(request), session);
|
||||
std::this_thread::sleep_for(200ms);
|
||||
EXPECT_EQ(boost::json::parse(session->message), boost::json::parse(response));
|
||||
}
|
||||
|
||||
TEST_F(WebRPCExecutorTest, HTTPErrorPath)
|
||||
{
|
||||
static auto constexpr response = R"({
|
||||
"result": {
|
||||
"error": "invalidParams",
|
||||
"error_code": 31,
|
||||
"error_message": "ledgerIndexMalformed",
|
||||
"status": "error",
|
||||
"type": "response",
|
||||
"request": {
|
||||
"method": "ledger",
|
||||
"params": [
|
||||
{
|
||||
"ledger_index": "xx"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"warnings":[
|
||||
{
|
||||
"id":2001,
|
||||
"message":"This is a clio server. clio only serves validated data. If you want to talk to rippled, include 'ledger_index':'current' in your request"
|
||||
}
|
||||
]
|
||||
})";
|
||||
|
||||
mockBackendPtr->updateRange(MINSEQ); // min
|
||||
mockBackendPtr->updateRange(MAXSEQ); // max
|
||||
|
||||
static auto constexpr requestJSON = R"({
|
||||
"method": "ledger",
|
||||
"params": [
|
||||
{
|
||||
"ledger_index": "xx"
|
||||
}
|
||||
]
|
||||
})";
|
||||
auto request = boost::json::parse(requestJSON).as_object();
|
||||
EXPECT_CALL(*rpcEngine, buildResponse(testing::_))
|
||||
.WillOnce(testing::Return(RPC::Status{RPC::RippledError::rpcINVALID_PARAMS, "ledgerIndexMalformed"}));
|
||||
EXPECT_CALL(*rpcEngine, notifyErrored("ledger")).Times(1);
|
||||
|
||||
EXPECT_CALL(*etl, lastCloseAgeSeconds()).WillOnce(testing::Return(45));
|
||||
|
||||
(*rpcExecutor)(std::move(request), session);
|
||||
std::this_thread::sleep_for(200ms);
|
||||
EXPECT_EQ(boost::json::parse(session->message), boost::json::parse(response));
|
||||
}
|
||||
|
||||
TEST_F(WebRPCExecutorTest, WsErrorPath)
|
||||
{
|
||||
session->upgraded = true;
|
||||
static auto constexpr response = R"({
|
||||
"id": "123",
|
||||
"error": "invalidParams",
|
||||
"error_code": 31,
|
||||
"error_message": "ledgerIndexMalformed",
|
||||
"status": "error",
|
||||
"type": "response",
|
||||
"request": {
|
||||
"command": "ledger",
|
||||
"ledger_index": "xx",
|
||||
"id": "123"
|
||||
},
|
||||
"warnings":[
|
||||
{
|
||||
"id":2001,
|
||||
"message":"This is a clio server. clio only serves validated data. If you want to talk to rippled, include 'ledger_index':'current' in your request"
|
||||
}
|
||||
]
|
||||
})";
|
||||
|
||||
mockBackendPtr->updateRange(MINSEQ); // min
|
||||
mockBackendPtr->updateRange(MAXSEQ); // max
|
||||
|
||||
static auto constexpr requestJSON = R"({
|
||||
"command": "ledger",
|
||||
"ledger_index": "xx",
|
||||
"id": "123"
|
||||
})";
|
||||
auto request = boost::json::parse(requestJSON).as_object();
|
||||
EXPECT_CALL(*rpcEngine, buildResponse(testing::_))
|
||||
.WillOnce(testing::Return(RPC::Status{RPC::RippledError::rpcINVALID_PARAMS, "ledgerIndexMalformed"}));
|
||||
EXPECT_CALL(*rpcEngine, notifyErrored("ledger")).Times(1);
|
||||
|
||||
EXPECT_CALL(*etl, lastCloseAgeSeconds()).WillOnce(testing::Return(45));
|
||||
|
||||
(*rpcExecutor)(std::move(request), session);
|
||||
std::this_thread::sleep_for(200ms);
|
||||
EXPECT_EQ(boost::json::parse(session->message), boost::json::parse(response));
|
||||
}
|
||||
|
||||
TEST_F(WebRPCExecutorTest, HTTPNotReady)
|
||||
{
|
||||
auto request = boost::json::parse(R"({
|
||||
"method": "server_info",
|
||||
"params": [{}]
|
||||
})")
|
||||
.as_object();
|
||||
|
||||
static auto constexpr response = R"({
|
||||
"result":{
|
||||
"error":"notReady",
|
||||
"error_code":13,
|
||||
"error_message":"Not ready to handle this request.",
|
||||
"status":"error",
|
||||
"type":"response",
|
||||
"request":{
|
||||
"method":"server_info",
|
||||
"params":[
|
||||
{
|
||||
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
})";
|
||||
|
||||
(*rpcExecutor)(std::move(request), session);
|
||||
std::this_thread::sleep_for(200ms);
|
||||
EXPECT_EQ(boost::json::parse(session->message), boost::json::parse(response));
|
||||
}
|
||||
|
||||
TEST_F(WebRPCExecutorTest, WsNotReady)
|
||||
{
|
||||
session->upgraded = true;
|
||||
|
||||
auto request = boost::json::parse(R"({
|
||||
"command": "server_info",
|
||||
"id": 99
|
||||
})")
|
||||
.as_object();
|
||||
|
||||
static auto constexpr response = R"({
|
||||
"error":"notReady",
|
||||
"error_code":13,
|
||||
"error_message":"Not ready to handle this request.",
|
||||
"status":"error",
|
||||
"type":"response",
|
||||
"id":99,
|
||||
"request":{
|
||||
"command":"server_info",
|
||||
"id":99
|
||||
}
|
||||
})";
|
||||
|
||||
(*rpcExecutor)(std::move(request), session);
|
||||
std::this_thread::sleep_for(200ms);
|
||||
EXPECT_EQ(boost::json::parse(session->message), boost::json::parse(response));
|
||||
}
|
||||
|
||||
TEST_F(WebRPCExecutorTest, HTTPBadSyntax)
|
||||
{
|
||||
auto request = boost::json::parse(R"({
|
||||
"method2": "server_info"
|
||||
})")
|
||||
.as_object();
|
||||
|
||||
mockBackendPtr->updateRange(MINSEQ); // min
|
||||
mockBackendPtr->updateRange(MAXSEQ); // max
|
||||
|
||||
static auto constexpr response = R"({
|
||||
"result":{
|
||||
"error":"badSyntax",
|
||||
"error_code":1,
|
||||
"error_message":"Syntax error.",
|
||||
"status":"error",
|
||||
"type":"response",
|
||||
"request":{
|
||||
"method2":"server_info",
|
||||
"params":[{}]
|
||||
}
|
||||
}
|
||||
})";
|
||||
|
||||
(*rpcExecutor)(std::move(request), session);
|
||||
std::this_thread::sleep_for(200ms);
|
||||
EXPECT_EQ(boost::json::parse(session->message), boost::json::parse(response));
|
||||
}
|
||||
|
||||
TEST_F(WebRPCExecutorTest, HTTPBadSyntaxWhenRequestSubscribe)
|
||||
{
|
||||
auto request = boost::json::parse(R"({
|
||||
"method": "subscribe"
|
||||
})")
|
||||
.as_object();
|
||||
|
||||
mockBackendPtr->updateRange(MINSEQ); // min
|
||||
mockBackendPtr->updateRange(MAXSEQ); // max
|
||||
|
||||
static auto constexpr response = R"({
|
||||
"result":{
|
||||
"error":"badSyntax",
|
||||
"error_code":1,
|
||||
"error_message":"Syntax error.",
|
||||
"status":"error",
|
||||
"type":"response",
|
||||
"request":{
|
||||
"method":"subscribe",
|
||||
"params":[{}]
|
||||
}
|
||||
}
|
||||
})";
|
||||
|
||||
(*rpcExecutor)(std::move(request), session);
|
||||
std::this_thread::sleep_for(200ms);
|
||||
EXPECT_EQ(boost::json::parse(session->message), boost::json::parse(response));
|
||||
}
|
||||
|
||||
TEST_F(WebRPCExecutorTest, WsBadSyntax)
|
||||
{
|
||||
session->upgraded = true;
|
||||
auto request = boost::json::parse(R"(
|
||||
{
|
||||
"command2": "server_info",
|
||||
"id": 99
|
||||
})")
|
||||
.as_object();
|
||||
|
||||
mockBackendPtr->updateRange(MINSEQ); // min
|
||||
mockBackendPtr->updateRange(MAXSEQ); // max
|
||||
|
||||
static auto constexpr response = R"({
|
||||
"error":"badSyntax",
|
||||
"error_code":1,
|
||||
"error_message":"Syntax error.",
|
||||
"status":"error",
|
||||
"type":"response",
|
||||
"id":99,
|
||||
"request":{
|
||||
"command2":"server_info",
|
||||
"id":99
|
||||
}
|
||||
})";
|
||||
|
||||
(*rpcExecutor)(std::move(request), session);
|
||||
std::this_thread::sleep_for(200ms);
|
||||
EXPECT_EQ(boost::json::parse(session->message), boost::json::parse(response));
|
||||
}
|
||||
|
||||
TEST_F(WebRPCExecutorTest, HTTPInternalError)
|
||||
{
|
||||
static auto constexpr response = R"({
|
||||
"result": {
|
||||
"error":"internal",
|
||||
"error_code":73,
|
||||
"error_message":"Internal error.",
|
||||
"status":"error",
|
||||
"type":"response",
|
||||
"request":{
|
||||
"method": "ledger",
|
||||
"params": [
|
||||
{
|
||||
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
})";
|
||||
|
||||
mockBackendPtr->updateRange(MINSEQ); // min
|
||||
mockBackendPtr->updateRange(MAXSEQ); // max
|
||||
|
||||
static auto constexpr requestJSON = R"({
|
||||
"method": "ledger",
|
||||
"params": [
|
||||
{
|
||||
|
||||
}
|
||||
]
|
||||
})";
|
||||
auto request = boost::json::parse(requestJSON).as_object();
|
||||
EXPECT_CALL(*rpcEngine, buildResponse(testing::_)).Times(1).WillOnce(testing::Throw(std::runtime_error("MyError")));
|
||||
|
||||
(*rpcExecutor)(std::move(request), session);
|
||||
std::this_thread::sleep_for(200ms);
|
||||
EXPECT_EQ(boost::json::parse(session->message), boost::json::parse(response));
|
||||
}
|
||||
|
||||
TEST_F(WebRPCExecutorTest, WsInternalError)
|
||||
{
|
||||
session->upgraded = true;
|
||||
|
||||
static auto constexpr response = R"({
|
||||
"error":"internal",
|
||||
"error_code":73,
|
||||
"error_message":"Internal error.",
|
||||
"status":"error",
|
||||
"type":"response",
|
||||
"id":"123",
|
||||
"request":{
|
||||
"command":"ledger",
|
||||
"id":"123"
|
||||
}
|
||||
})";
|
||||
|
||||
mockBackendPtr->updateRange(MINSEQ); // min
|
||||
mockBackendPtr->updateRange(MAXSEQ); // max
|
||||
|
||||
static auto constexpr requestJSON = R"({
|
||||
"command": "ledger",
|
||||
"id": "123"
|
||||
})";
|
||||
auto request = boost::json::parse(requestJSON).as_object();
|
||||
EXPECT_CALL(*rpcEngine, buildResponse(testing::_)).Times(1).WillOnce(testing::Throw(std::runtime_error("MyError")));
|
||||
|
||||
(*rpcExecutor)(std::move(request), session);
|
||||
std::this_thread::sleep_for(200ms);
|
||||
EXPECT_EQ(boost::json::parse(session->message), boost::json::parse(response));
|
||||
}
|
||||
|
||||
TEST_F(WebRPCExecutorTest, HTTPOutDated)
|
||||
{
|
||||
auto request = boost::json::parse(R"({
|
||||
"method": "server_info",
|
||||
"params": [{}]
|
||||
})")
|
||||
.as_object();
|
||||
|
||||
mockBackendPtr->updateRange(MINSEQ); // min
|
||||
mockBackendPtr->updateRange(MAXSEQ); // max
|
||||
|
||||
static auto constexpr result = "{}";
|
||||
static auto constexpr response = R"({
|
||||
"result":{
|
||||
"status":"success"
|
||||
},
|
||||
"warnings":[
|
||||
{
|
||||
"id":2001,
|
||||
"message":"This is a clio server. clio only serves validated data. If you want to talk to rippled, include 'ledger_index':'current' in your request"
|
||||
},
|
||||
{
|
||||
"id":2002,
|
||||
"message":"This server may be out of date"
|
||||
}
|
||||
]
|
||||
})";
|
||||
EXPECT_CALL(*rpcEngine, buildResponse(testing::_))
|
||||
.WillOnce(testing::Return(boost::json::parse(result).as_object()));
|
||||
EXPECT_CALL(*rpcEngine, notifyComplete("server_info", testing::_)).Times(1);
|
||||
|
||||
EXPECT_CALL(*etl, lastCloseAgeSeconds()).WillOnce(testing::Return(61));
|
||||
|
||||
(*rpcExecutor)(std::move(request), session);
|
||||
std::this_thread::sleep_for(200ms);
|
||||
EXPECT_EQ(boost::json::parse(session->message), boost::json::parse(response));
|
||||
}
|
||||
|
||||
TEST_F(WebRPCExecutorTest, WsOutdated)
|
||||
{
|
||||
session->upgraded = true;
|
||||
|
||||
auto request = boost::json::parse(R"({
|
||||
"command": "server_info",
|
||||
"id": 99
|
||||
})")
|
||||
.as_object();
|
||||
|
||||
mockBackendPtr->updateRange(MINSEQ); // min
|
||||
mockBackendPtr->updateRange(MAXSEQ); // max
|
||||
|
||||
static auto constexpr result = "{}";
|
||||
static auto constexpr response = R"({
|
||||
"result":{
|
||||
},
|
||||
"id":99,
|
||||
"status":"success",
|
||||
"type":"response",
|
||||
"warnings":[
|
||||
{
|
||||
"id":2001,
|
||||
"message":"This is a clio server. clio only serves validated data. If you want to talk to rippled, include 'ledger_index':'current' in your request"
|
||||
},
|
||||
{
|
||||
"id":2002,
|
||||
"message":"This server may be out of date"
|
||||
}
|
||||
]
|
||||
})";
|
||||
EXPECT_CALL(*rpcEngine, buildResponse(testing::_))
|
||||
.WillOnce(testing::Return(boost::json::parse(result).as_object()));
|
||||
EXPECT_CALL(*rpcEngine, notifyComplete("server_info", testing::_)).Times(1);
|
||||
|
||||
EXPECT_CALL(*etl, lastCloseAgeSeconds()).WillOnce(testing::Return(61));
|
||||
|
||||
(*rpcExecutor)(std::move(request), session);
|
||||
std::this_thread::sleep_for(200ms);
|
||||
EXPECT_EQ(boost::json::parse(session->message), boost::json::parse(response));
|
||||
}
|
||||
|
||||
TEST_F(WebRPCExecutorTest, WsTooBusy)
|
||||
{
|
||||
session->upgraded = true;
|
||||
|
||||
auto rpcEngine2 = std::make_shared<MockRPCEngine>();
|
||||
auto rpcExecutor2 =
|
||||
std::make_shared<RPCExecutor<MockRPCEngine, MockETLService>>(cfg, mockBackendPtr, rpcEngine2, etl, subManager);
|
||||
auto request = boost::json::parse(R"({
|
||||
"command": "server_info",
|
||||
"id": 99
|
||||
})")
|
||||
.as_object();
|
||||
|
||||
mockBackendPtr->updateRange(MINSEQ); // min
|
||||
mockBackendPtr->updateRange(MAXSEQ); // max
|
||||
|
||||
static auto constexpr response =
|
||||
R"({"error":"tooBusy","error_code":9,"error_message":"The server is too busy to help you now.","status":"error","type":"response"})";
|
||||
EXPECT_CALL(*rpcEngine2, post).WillOnce(testing::Return(false));
|
||||
(*rpcExecutor2)(std::move(request), session);
|
||||
EXPECT_EQ(boost::json::parse(session->message), boost::json::parse(response));
|
||||
}
|
||||
|
||||
TEST_F(WebRPCExecutorTest, HTTPTooBusy)
|
||||
{
|
||||
auto rpcEngine2 = std::make_shared<MockRPCEngine>();
|
||||
auto rpcExecutor2 =
|
||||
std::make_shared<RPCExecutor<MockRPCEngine, MockETLService>>(cfg, mockBackendPtr, rpcEngine2, etl, subManager);
|
||||
auto request = boost::json::parse(R"({
|
||||
"method": "server_info",
|
||||
"params": [{}]
|
||||
})")
|
||||
.as_object();
|
||||
|
||||
mockBackendPtr->updateRange(MINSEQ); // min
|
||||
mockBackendPtr->updateRange(MAXSEQ); // max
|
||||
|
||||
static auto constexpr response =
|
||||
R"({"error":"tooBusy","error_code":9,"error_message":"The server is too busy to help you now.","status":"error","type":"response"})";
|
||||
|
||||
EXPECT_CALL(*rpcEngine2, post).WillOnce(testing::Return(false));
|
||||
(*rpcExecutor2)(std::move(request), session);
|
||||
EXPECT_EQ(boost::json::parse(session->message), boost::json::parse(response));
|
||||
}
|
||||
357
unittests/webserver/ServerTest.cpp
Normal file
357
unittests/webserver/ServerTest.cpp
Normal file
@@ -0,0 +1,357 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of clio: https://github.com/XRPLF/clio
|
||||
Copyright (c) 2023, the clio developers.
|
||||
|
||||
Permission to use, copy, modify, and distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#include <util/Fixtures.h>
|
||||
#include <util/TestHttpSyncClient.h>
|
||||
#include <webserver/Server.h>
|
||||
|
||||
#include <boost/json/parse.hpp>
|
||||
#include <fmt/core.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <optional>
|
||||
|
||||
constexpr static auto JSONData = R"JSON(
|
||||
{
|
||||
"server":{
|
||||
"ip":"0.0.0.0",
|
||||
"port":8888
|
||||
},
|
||||
"dos_guard": {
|
||||
"max_fetches": 100,
|
||||
"sweep_interval": 1000,
|
||||
"max_connections": 2,
|
||||
"max_requests": 3,
|
||||
"whitelist": ["127.0.0.1"]
|
||||
}
|
||||
}
|
||||
)JSON";
|
||||
|
||||
constexpr static auto JSONDataOverload = R"JSON(
|
||||
{
|
||||
"server":{
|
||||
"ip":"0.0.0.0",
|
||||
"port":8888
|
||||
},
|
||||
"dos_guard": {
|
||||
"max_fetches": 100,
|
||||
"sweep_interval": 1000,
|
||||
"max_connections": 2,
|
||||
"max_requests": 1
|
||||
}
|
||||
}
|
||||
)JSON";
|
||||
|
||||
// for testing, we use a self-signed certificate
|
||||
std::optional<ssl::context>
|
||||
parseCertsForTest()
|
||||
{
|
||||
std::string const key = R"(-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEpAIBAAKCAQEAqP3K4WDIhk63zbxSoN8tJqRZD3W0IWFMwCluZchUwsHPxEC4
|
||||
32sPk58YonynY5nGtTeSGhedSqHD0gFBLcU/su4dSsj+kgGgJwKmiPmoQiTpzEmd
|
||||
g2Kqrnrw6QAilyhyMgjo6lYOiCsLU2qdnXcN8AOaAD9wtqNdcoFFQJD9vU9uKA8x
|
||||
evwIF7OgpUyERlnj5ILTGlwzOr1IochpxG08JD22C9ZlSLB2DTGbW4x8OvdobAtC
|
||||
tKU+x9hRbgaAN/jgHze+CrN3Bq48RY2S51Pe/VrDnTAWoDJ/VVFvv8z4niAC5dYC
|
||||
oAdB6Zut11bUTspqp8MWt3gzEp3Z1cKs83ftaQIDAQABAoIBAGXZH48Zz4DyrGA4
|
||||
YexG1WV2o55np/p+M82Uqs55IGyIdnmnMESmt6qWtjgnvJKQuWu6ZDmJhejW+bf1
|
||||
vZyiRrPGQq0x2guRIz6foFLpdHj42lee/mmS659gxRUIWdCUNc7mA8pHt1Zl6tuJ
|
||||
ZBjlCedfpE8F7R6F8unx8xTozaRr4ZbOVnqB8YWjyuIDUnujsxKdKFASZJAEzRjh
|
||||
+lScXAdEYTaswgTWFFGKzwTjH/Yfv4y3LwE0RmR/1e+eQmQ7Z4C0HhjYe3EYXAvk
|
||||
naH2QFZaYVhu7x/+oLPetIzFJOZn61iDhUtGYdvQVvF8qQCPqeuKeLcS9X5my9aK
|
||||
nfLUryECgYEA3ZZGffe6Me6m0ZX/zwT5NbZpZCJgeALGLZPg9qulDVf8zHbDRsdn
|
||||
K6Mf/Xhy3DCfSwdwcuAKz/r+4tPFyNUJR+Y2ltXaVl72iY3uJRdriNrEbZ47Ez4z
|
||||
dhtEmDrD7C+7AusErEgjas+AKXkp1tovXrXUiVfRytBtoKqrym4IjJUCgYEAwzxz
|
||||
fTuE2nrIwFkvg0p9PtrCwkw8dnzhBeNnzFdPOVAiHCfnNcaSOWWTkGHIkGLoORqs
|
||||
fqfZCD9VkqRwsPDaSSL7vhX3oHuerDipdxOjaXVjYa7YjM6gByzo62hnG6BcQHC7
|
||||
zrj7iqjnMdyNLtXcPu6zm/j5iIOLWXMevK/OVIUCgYAey4e4cfk6f0RH1GTczIAl
|
||||
6tfyxqRJiXkpVGfrYCdsF1JWyBqTd5rrAZysiVTNLSS2NK54CJL4HJXXyD6wjorf
|
||||
pyrnA4l4f3Ib49G47exP9Ldf1KG5JufX/iomTeR0qp1+5lKb7tqdOYFCQkiCR4hV
|
||||
zUdgXwgU+6qArbd6RpiBkQKBgQCSen5jjQ5GJS0NM1y0cmS5jcPlpvEOLO9fTZiI
|
||||
9VCZPYf5++46qHr42T73aoXh3nNAtMSKWkA5MdtwJDPwbSQ5Dyg1G6IoI9eOewya
|
||||
LH/EFbC0j0wliLkD6SvvwurpDU1pg6tElAEVrVeYT1MVupp+FPVopkoBpEAeooKD
|
||||
KpvxSQKBgQDP9fNJIpuX3kaudb0pI1OvuqBYTrTExMx+JMR+Sqf0HUwavpeCn4du
|
||||
O2R4tGOOkGAX/0/actRXptFk23ucHnSIwcW6HYgDM3tDBP7n3GYdu5CSE1eiR5k7
|
||||
Zl3fuvbMYcmYKgutFcRj+8NvzRWT2suzGU2x4PiPX+fh5kpvmMdvLA==
|
||||
-----END RSA PRIVATE KEY-----)";
|
||||
std::string const cert = R"(-----BEGIN CERTIFICATE-----
|
||||
MIIDrjCCApagAwIBAgIJAOE4Hv/P8CO3MA0GCSqGSIb3DQEBCwUAMDkxEjAQBgNV
|
||||
BAMMCTEyNy4wLjAuMTELMAkGA1UEBhMCVVMxFjAUBgNVBAcMDVNhbiBGcmFuc2lz
|
||||
Y28wHhcNMjMwNTE4MTUwMzEwWhcNMjQwNTE3MTUwMzEwWjBrMQswCQYDVQQGEwJV
|
||||
UzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5zaXNjbzEN
|
||||
MAsGA1UECgwEVGVzdDEMMAoGA1UECwwDRGV2MRIwEAYDVQQDDAkxMjcuMC4wLjEw
|
||||
ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCo/crhYMiGTrfNvFKg3y0m
|
||||
pFkPdbQhYUzAKW5lyFTCwc/EQLjfaw+TnxiifKdjmca1N5IaF51KocPSAUEtxT+y
|
||||
7h1KyP6SAaAnAqaI+ahCJOnMSZ2DYqquevDpACKXKHIyCOjqVg6IKwtTap2ddw3w
|
||||
A5oAP3C2o11ygUVAkP29T24oDzF6/AgXs6ClTIRGWePkgtMaXDM6vUihyGnEbTwk
|
||||
PbYL1mVIsHYNMZtbjHw692hsC0K0pT7H2FFuBoA3+OAfN74Ks3cGrjxFjZLnU979
|
||||
WsOdMBagMn9VUW+/zPieIALl1gKgB0Hpm63XVtROymqnwxa3eDMSndnVwqzzd+1p
|
||||
AgMBAAGjgYYwgYMwUwYDVR0jBEwwSqE9pDswOTESMBAGA1UEAwwJMTI3LjAuMC4x
|
||||
MQswCQYDVQQGEwJVUzEWMBQGA1UEBwwNU2FuIEZyYW5zaXNjb4IJAKu2wr50Pfbq
|
||||
MAkGA1UdEwQCMAAwCwYDVR0PBAQDAgTwMBQGA1UdEQQNMAuCCTEyNy4wLjAuMTAN
|
||||
BgkqhkiG9w0BAQsFAAOCAQEArEjC1DmJ6q0735PxGkOmjWNsfnw8c2Zl1Z4idKfn
|
||||
svEFtegNLU7tCu4aKunxlCHWiFVpunr4X67qH1JiE93W0JADnRrPxvywiqR6nUcO
|
||||
p6HII/kzOizUXk59QMc1GLIIR6LDlNEeDlUbIc2DH8DPrRFBuIMYy4lf18qyfiUb
|
||||
8Jt8nLeAzbhA21wI6BVhEt8G/cgIi88mPifXq+YVHrJE01jUREHRwl/MMildqxgp
|
||||
LLuOOuPuy2d+HqjKE7z00j28Uf7gZK29bGx1rK+xH6veAr4plKBavBr8WWpAoUG+
|
||||
PAMNb1i80cMsjK98xXDdr+7Uvy5M4COMwA5XHmMZDEW8Jw==
|
||||
-----END CERTIFICATE-----)";
|
||||
ssl::context ctx{ssl::context::tlsv12};
|
||||
ctx.set_options(boost::asio::ssl::context::default_workarounds | boost::asio::ssl::context::no_sslv2);
|
||||
ctx.use_certificate_chain(boost::asio::buffer(cert.data(), cert.size()));
|
||||
ctx.use_private_key(boost::asio::buffer(key.data(), key.size()), boost::asio::ssl::context::file_format::pem);
|
||||
return ctx;
|
||||
}
|
||||
|
||||
class WebServerTest : public NoLoggerFixture
|
||||
{
|
||||
protected:
|
||||
WebServerTest()
|
||||
{
|
||||
work.emplace(ctx); // make sure ctx does not stop on its own
|
||||
runner.emplace([this] { ctx.run(); });
|
||||
}
|
||||
|
||||
~WebServerTest()
|
||||
{
|
||||
work.reset();
|
||||
ctx.stop();
|
||||
if (runner->joinable())
|
||||
runner->join();
|
||||
}
|
||||
|
||||
void
|
||||
SetUp() override
|
||||
{
|
||||
NoLoggerFixture::SetUp();
|
||||
}
|
||||
|
||||
// this ctx is for dos timer
|
||||
boost::asio::io_context ctxSync;
|
||||
clio::Config cfg{boost::json::parse(JSONData)};
|
||||
clio::IntervalSweepHandler sweepHandler = clio::IntervalSweepHandler{cfg, ctxSync};
|
||||
clio::DOSGuard dosGuard = clio::DOSGuard{cfg, sweepHandler};
|
||||
|
||||
clio::Config cfgOverload{boost::json::parse(JSONDataOverload)};
|
||||
clio::IntervalSweepHandler sweepHandlerOverload = clio::IntervalSweepHandler{cfgOverload, ctxSync};
|
||||
clio::DOSGuard dosGuardOverload = clio::DOSGuard{cfgOverload, sweepHandlerOverload};
|
||||
// this ctx is for http server
|
||||
boost::asio::io_context ctx;
|
||||
|
||||
private:
|
||||
std::optional<boost::asio::io_service::work> work;
|
||||
std::optional<std::thread> runner;
|
||||
};
|
||||
|
||||
class EchoExecutor
|
||||
{
|
||||
public:
|
||||
void
|
||||
operator()(boost::json::object&& req, std::shared_ptr<Server::ConnectionBase> const& ws)
|
||||
{
|
||||
ws->send(boost::json::serialize(req), http::status::ok);
|
||||
}
|
||||
|
||||
void
|
||||
operator()(boost::beast::error_code ec, std::shared_ptr<Server::ConnectionBase> const& ws)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
class ExceptionExecutor
|
||||
{
|
||||
public:
|
||||
void
|
||||
operator()(boost::json::object&& req, std::shared_ptr<Server::ConnectionBase> const& ws)
|
||||
{
|
||||
throw std::runtime_error("MyError");
|
||||
}
|
||||
|
||||
void
|
||||
operator()(boost::beast::error_code ec, std::shared_ptr<Server::ConnectionBase> const& ws)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(WebServerTest, Http)
|
||||
{
|
||||
auto e = std::make_shared<EchoExecutor>();
|
||||
auto const server = Server::make_HttpServer(cfg, ctx, std::nullopt, dosGuard, e);
|
||||
auto const res = HttpSyncClient::syncPost("localhost", "8888", R"({"Hello":1})");
|
||||
EXPECT_EQ(res, R"({"Hello":1})");
|
||||
}
|
||||
|
||||
TEST_F(WebServerTest, Ws)
|
||||
{
|
||||
auto e = std::make_shared<EchoExecutor>();
|
||||
auto const server = Server::make_HttpServer(cfg, ctx, std::nullopt, dosGuard, e);
|
||||
WebSocketSyncClient wsClient;
|
||||
wsClient.connect("localhost", "8888");
|
||||
auto const res = wsClient.syncPost(R"({"Hello":1})");
|
||||
EXPECT_EQ(res, R"({"Hello":1})");
|
||||
wsClient.disconnect();
|
||||
}
|
||||
|
||||
TEST_F(WebServerTest, HttpBodyNotJsonValue)
|
||||
{
|
||||
auto e = std::make_shared<EchoExecutor>();
|
||||
auto const server = Server::make_HttpServer(cfg, ctx, std::nullopt, dosGuard, e);
|
||||
auto const res = HttpSyncClient::syncPost("localhost", "8888", R"({)");
|
||||
EXPECT_EQ(
|
||||
res,
|
||||
R"({"error":"badSyntax","error_code":1,"error_message":"Syntax error.","status":"error","type":"response"})");
|
||||
}
|
||||
|
||||
TEST_F(WebServerTest, HttpBodyNotJsonObject)
|
||||
{
|
||||
auto e = std::make_shared<EchoExecutor>();
|
||||
auto const server = Server::make_HttpServer(cfg, ctx, std::nullopt, dosGuard, e);
|
||||
auto const res = HttpSyncClient::syncPost("localhost", "8888", R"("123")");
|
||||
EXPECT_EQ(
|
||||
res,
|
||||
R"({"error":"badSyntax","error_code":1,"error_message":"Syntax error.","status":"error","type":"response"})");
|
||||
}
|
||||
|
||||
TEST_F(WebServerTest, WsBodyNotJsonValue)
|
||||
{
|
||||
auto e = std::make_shared<EchoExecutor>();
|
||||
WebSocketSyncClient wsClient;
|
||||
auto const server = Server::make_HttpServer(cfg, ctx, std::nullopt, dosGuard, e);
|
||||
wsClient.connect("localhost", "8888");
|
||||
auto const res = wsClient.syncPost(R"({)");
|
||||
wsClient.disconnect();
|
||||
EXPECT_EQ(
|
||||
res,
|
||||
R"({"error":"badSyntax","error_code":1,"error_message":"Syntax error.","status":"error","type":"response","request":["{"]})");
|
||||
}
|
||||
|
||||
TEST_F(WebServerTest, WsBodyNotJsonObject)
|
||||
{
|
||||
auto e = std::make_shared<EchoExecutor>();
|
||||
auto const server = Server::make_HttpServer(cfg, ctx, std::nullopt, dosGuard, e);
|
||||
WebSocketSyncClient wsClient;
|
||||
wsClient.connect("localhost", "8888");
|
||||
auto const res = wsClient.syncPost(R"("Hello")");
|
||||
wsClient.disconnect();
|
||||
EXPECT_EQ(
|
||||
res,
|
||||
R"({"error":"badSyntax","error_code":1,"error_message":"Syntax error.","status":"error","type":"response","request":"Hello"})");
|
||||
}
|
||||
|
||||
TEST_F(WebServerTest, HttpInternalError)
|
||||
{
|
||||
auto e = std::make_shared<ExceptionExecutor>();
|
||||
auto const server = Server::make_HttpServer(cfg, ctx, std::nullopt, dosGuard, e);
|
||||
auto const res = HttpSyncClient::syncPost("localhost", "8888", R"({})");
|
||||
EXPECT_EQ(
|
||||
res,
|
||||
R"({"error":"internal","error_code":73,"error_message":"Internal error.","status":"error","type":"response"})");
|
||||
}
|
||||
|
||||
TEST_F(WebServerTest, WsInternalError)
|
||||
{
|
||||
auto e = std::make_shared<ExceptionExecutor>();
|
||||
auto const server = Server::make_HttpServer(cfg, ctx, std::nullopt, dosGuard, e);
|
||||
WebSocketSyncClient wsClient;
|
||||
wsClient.connect("localhost", "8888");
|
||||
auto const res = wsClient.syncPost(R"({})");
|
||||
wsClient.disconnect();
|
||||
EXPECT_EQ(
|
||||
res,
|
||||
R"({"error":"internal","error_code":73,"error_message":"Internal error.","status":"error","type":"response","request":{}})");
|
||||
}
|
||||
|
||||
TEST_F(WebServerTest, Https)
|
||||
{
|
||||
auto e = std::make_shared<EchoExecutor>();
|
||||
auto sslCtx = parseCertsForTest();
|
||||
auto const ctxSslRef = sslCtx ? std::optional<std::reference_wrapper<ssl::context>>{sslCtx.value()} : std::nullopt;
|
||||
|
||||
auto const server = Server::make_HttpServer(cfg, ctx, ctxSslRef, dosGuard, e);
|
||||
auto const res = HttpsSyncClient::syncPost("localhost", "8888", R"({"Hello":1})");
|
||||
EXPECT_EQ(res, R"({"Hello":1})");
|
||||
}
|
||||
|
||||
TEST_F(WebServerTest, Wss)
|
||||
{
|
||||
auto e = std::make_shared<EchoExecutor>();
|
||||
auto sslCtx = parseCertsForTest();
|
||||
auto const ctxSslRef = sslCtx ? std::optional<std::reference_wrapper<ssl::context>>{sslCtx.value()} : std::nullopt;
|
||||
|
||||
auto const server = Server::make_HttpServer(cfg, ctx, ctxSslRef, dosGuard, e);
|
||||
WebServerSslSyncClient wsClient;
|
||||
wsClient.connect("localhost", "8888");
|
||||
auto const res = wsClient.syncPost(R"({"Hello":1})");
|
||||
EXPECT_EQ(res, R"({"Hello":1})");
|
||||
wsClient.disconnect();
|
||||
}
|
||||
|
||||
TEST_F(WebServerTest, HttpRequestOverload)
|
||||
{
|
||||
auto e = std::make_shared<EchoExecutor>();
|
||||
auto const server = Server::make_HttpServer(cfg, ctx, std::nullopt, dosGuardOverload, e);
|
||||
auto res = HttpSyncClient::syncPost("localhost", "8888", R"({})");
|
||||
EXPECT_EQ(res, "{}");
|
||||
res = HttpSyncClient::syncPost("localhost", "8888", R"({})");
|
||||
EXPECT_EQ(
|
||||
res,
|
||||
R"({"error":"slowDown","error_code":10,"error_message":"You are placing too much load on the server.","status":"error","type":"response"})");
|
||||
}
|
||||
|
||||
TEST_F(WebServerTest, WsRequestOverload)
|
||||
{
|
||||
auto e = std::make_shared<EchoExecutor>();
|
||||
auto const server = Server::make_HttpServer(cfg, ctx, std::nullopt, dosGuardOverload, e);
|
||||
WebSocketSyncClient wsClient;
|
||||
wsClient.connect("localhost", "8888");
|
||||
auto res = wsClient.syncPost(R"({})");
|
||||
wsClient.disconnect();
|
||||
EXPECT_EQ(res, "{}");
|
||||
WebSocketSyncClient wsClient2;
|
||||
wsClient2.connect("localhost", "8888");
|
||||
res = wsClient2.syncPost(R"({})");
|
||||
wsClient2.disconnect();
|
||||
EXPECT_EQ(
|
||||
res,
|
||||
R"({"error":"slowDown","error_code":10,"error_message":"You are placing too much load on the server.","status":"error","type":"response","request":{}})");
|
||||
}
|
||||
|
||||
TEST_F(WebServerTest, HttpPayloadOverload)
|
||||
{
|
||||
std::string const s100(100, 'a');
|
||||
auto e = std::make_shared<EchoExecutor>();
|
||||
auto const server = Server::make_HttpServer(cfg, ctx, std::nullopt, dosGuardOverload, e);
|
||||
auto const res = HttpSyncClient::syncPost("localhost", "8888", fmt::format(R"({{"payload":"{}"}})", s100));
|
||||
EXPECT_EQ(
|
||||
res,
|
||||
R"({"payload":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","warning":"load","warnings":[{"id":2003,"message":"You are about to be rate limited"}]})");
|
||||
}
|
||||
|
||||
TEST_F(WebServerTest, WsPayloadOverload)
|
||||
{
|
||||
std::string const s100(100, 'a');
|
||||
auto e = std::make_shared<EchoExecutor>();
|
||||
auto const server = Server::make_HttpServer(cfg, ctx, std::nullopt, dosGuardOverload, e);
|
||||
WebSocketSyncClient wsClient;
|
||||
wsClient.connect("localhost", "8888");
|
||||
auto const res = wsClient.syncPost(fmt::format(R"({{"payload":"{}"}})", s100));
|
||||
wsClient.disconnect();
|
||||
EXPECT_EQ(
|
||||
res,
|
||||
R"({"payload":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","warning":"load","warnings":[{"id":2003,"message":"You are about to be rate limited"}]})");
|
||||
}
|
||||
Reference in New Issue
Block a user