mirror of
https://github.com/XRPLF/clio.git
synced 2025-11-28 07:35:52 +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:
@@ -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