mirror of
https://github.com/XRPLF/clio.git
synced 2025-12-06 17:27:58 +00:00
@@ -5,6 +5,7 @@ target_sources(
|
||||
PRIVATE # Common
|
||||
ConfigTests.cpp
|
||||
app/CliArgsTests.cpp
|
||||
app/WebHandlersTests.cpp
|
||||
data/AmendmentCenterTests.cpp
|
||||
data/BackendCountersTests.cpp
|
||||
data/BackendInterfaceTests.cpp
|
||||
|
||||
321
tests/unit/app/WebHandlersTests.cpp
Normal file
321
tests/unit/app/WebHandlersTests.cpp
Normal file
@@ -0,0 +1,321 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
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 "app/WebHandlers.hpp"
|
||||
#include "util/AsioContextTestFixture.hpp"
|
||||
#include "util/LoggerFixtures.hpp"
|
||||
#include "util/MockPrometheus.hpp"
|
||||
#include "util/Taggable.hpp"
|
||||
#include "util/config/Config.hpp"
|
||||
#include "web/AdminVerificationStrategy.hpp"
|
||||
#include "web/SubscriptionContextInterface.hpp"
|
||||
#include "web/dosguard/DOSGuardMock.hpp"
|
||||
#include "web/ng/Connection.hpp"
|
||||
#include "web/ng/MockConnection.hpp"
|
||||
#include "web/ng/Request.hpp"
|
||||
#include "web/ng/Response.hpp"
|
||||
|
||||
#include <boost/asio/spawn.hpp>
|
||||
#include <boost/beast/core/flat_buffer.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/parse.hpp>
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
|
||||
using namespace app;
|
||||
namespace http = boost::beast::http;
|
||||
|
||||
struct WebHandlersTest : virtual NoLoggerFixture {
|
||||
DOSGuardStrictMock dosGuardMock_;
|
||||
util::TagDecoratorFactory tagFactory_{util::Config{}};
|
||||
std::string const ip_ = "some ip";
|
||||
StrictMockConnection connectionMock_{ip_, boost::beast::flat_buffer{}, tagFactory_};
|
||||
|
||||
struct AdminVerificationStrategyMock : web::AdminVerificationStrategy {
|
||||
MOCK_METHOD(bool, isAdmin, (RequestHeader const&, std::string_view), (const, override));
|
||||
};
|
||||
using AdminVerificationStrategyStrictMockPtr = std::shared_ptr<testing::StrictMock<AdminVerificationStrategyMock>>;
|
||||
};
|
||||
|
||||
struct OnConnectCheckTests : WebHandlersTest {
|
||||
OnConnectCheck onConnectCheck_{dosGuardMock_};
|
||||
};
|
||||
|
||||
TEST_F(OnConnectCheckTests, Ok)
|
||||
{
|
||||
EXPECT_CALL(dosGuardMock_, increment(ip_));
|
||||
EXPECT_CALL(dosGuardMock_, isOk(ip_)).WillOnce(testing::Return(true));
|
||||
EXPECT_TRUE(onConnectCheck_(connectionMock_).has_value());
|
||||
}
|
||||
|
||||
TEST_F(OnConnectCheckTests, RateLimited)
|
||||
{
|
||||
EXPECT_CALL(dosGuardMock_, increment(ip_));
|
||||
EXPECT_CALL(dosGuardMock_, isOk(ip_)).WillOnce(testing::Return(false));
|
||||
EXPECT_CALL(connectionMock_, wasUpgraded).WillOnce(testing::Return(false));
|
||||
|
||||
auto response = onConnectCheck_(connectionMock_);
|
||||
ASSERT_FALSE(response.has_value());
|
||||
auto const httpResponse = std::move(response).error().intoHttpResponse();
|
||||
EXPECT_EQ(httpResponse.result(), boost::beast::http::status::too_many_requests);
|
||||
EXPECT_EQ(httpResponse.body(), "Too many requests");
|
||||
}
|
||||
|
||||
struct DisconnectHookTests : WebHandlersTest {
|
||||
DisconnectHook disconnectHook_{dosGuardMock_};
|
||||
};
|
||||
|
||||
TEST_F(DisconnectHookTests, CallsDecrement)
|
||||
{
|
||||
EXPECT_CALL(dosGuardMock_, decrement(ip_));
|
||||
disconnectHook_(connectionMock_);
|
||||
}
|
||||
|
||||
struct MetricsHandlerTests : util::prometheus::WithPrometheus, SyncAsioContextTest, WebHandlersTest {
|
||||
AdminVerificationStrategyStrictMockPtr adminVerifier_{
|
||||
std::make_shared<testing::StrictMock<AdminVerificationStrategyMock>>()
|
||||
};
|
||||
|
||||
MetricsHandler metricsHandler_{adminVerifier_};
|
||||
web::ng::Request request_{http::request<http::string_body>{http::verb::get, "/metrics", 11}};
|
||||
};
|
||||
|
||||
TEST_F(MetricsHandlerTests, Call)
|
||||
{
|
||||
EXPECT_CALL(*adminVerifier_, isAdmin).WillOnce(testing::Return(true));
|
||||
runSpawn([&](boost::asio::yield_context yield) {
|
||||
auto response = metricsHandler_(request_, connectionMock_, nullptr, yield);
|
||||
auto const httpResponse = std::move(response).intoHttpResponse();
|
||||
EXPECT_EQ(httpResponse.result(), boost::beast::http::status::ok);
|
||||
});
|
||||
}
|
||||
|
||||
struct HealthCheckHandlerTests : SyncAsioContextTest, WebHandlersTest {
|
||||
web::ng::Request request_{http::request<http::string_body>{http::verb::get, "/", 11}};
|
||||
HealthCheckHandler healthCheckHandler_;
|
||||
};
|
||||
|
||||
TEST_F(HealthCheckHandlerTests, Call)
|
||||
{
|
||||
runSpawn([&](boost::asio::yield_context yield) {
|
||||
auto response = healthCheckHandler_(request_, connectionMock_, nullptr, yield);
|
||||
auto const httpResponse = std::move(response).intoHttpResponse();
|
||||
EXPECT_EQ(httpResponse.result(), boost::beast::http::status::ok);
|
||||
});
|
||||
}
|
||||
|
||||
struct RequestHandlerTest : SyncAsioContextTest, WebHandlersTest {
|
||||
AdminVerificationStrategyStrictMockPtr adminVerifier_{
|
||||
std::make_shared<testing::StrictMock<AdminVerificationStrategyMock>>()
|
||||
};
|
||||
|
||||
struct RpcHandlerMock {
|
||||
MOCK_METHOD(
|
||||
web::ng::Response,
|
||||
call,
|
||||
(web::ng::Request const&,
|
||||
web::ng::ConnectionMetadata const&,
|
||||
web::SubscriptionContextPtr,
|
||||
boost::asio::yield_context),
|
||||
()
|
||||
);
|
||||
|
||||
web::ng::Response
|
||||
operator()(
|
||||
web::ng::Request const& request,
|
||||
web::ng::ConnectionMetadata const& connectionMetadata,
|
||||
web::SubscriptionContextPtr subscriptionContext,
|
||||
boost::asio::yield_context yield
|
||||
)
|
||||
{
|
||||
return call(request, connectionMetadata, std::move(subscriptionContext), yield);
|
||||
}
|
||||
};
|
||||
|
||||
testing::StrictMock<RpcHandlerMock> rpcHandler_;
|
||||
StrictMockConnection connectionMock_{ip_, boost::beast::flat_buffer{}, tagFactory_};
|
||||
RequestHandler<RpcHandlerMock> requestHandler_{adminVerifier_, rpcHandler_, dosGuardMock_};
|
||||
};
|
||||
|
||||
TEST_F(RequestHandlerTest, DosguardRateLimited_Http)
|
||||
{
|
||||
web::ng::Request const request{http::request<http::string_body>{http::verb::get, "/", 11}};
|
||||
|
||||
EXPECT_CALL(dosGuardMock_, request(ip_)).WillOnce(testing::Return(false));
|
||||
|
||||
runSpawn([&](boost::asio::yield_context yield) {
|
||||
auto response = requestHandler_(request, connectionMock_, nullptr, yield);
|
||||
auto const httpResponse = std::move(response).intoHttpResponse();
|
||||
|
||||
EXPECT_EQ(httpResponse.result(), boost::beast::http::status::service_unavailable);
|
||||
|
||||
auto const body = boost::json::parse(httpResponse.body()).as_object();
|
||||
EXPECT_EQ(body.at("error").as_string(), "slowDown");
|
||||
EXPECT_EQ(body.at("error_code").as_int64(), 10);
|
||||
EXPECT_EQ(body.at("status").as_string(), "error");
|
||||
EXPECT_FALSE(body.contains("id"));
|
||||
EXPECT_FALSE(body.contains("request"));
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(RequestHandlerTest, DosguardRateLimited_Ws)
|
||||
{
|
||||
auto const requestMessage = R"json({"some": "request", "id": "some id"})json";
|
||||
web::ng::Request::HttpHeaders const headers{};
|
||||
web::ng::Request const request{requestMessage, headers};
|
||||
|
||||
EXPECT_CALL(dosGuardMock_, request(ip_)).WillOnce(testing::Return(false));
|
||||
|
||||
runSpawn([&](boost::asio::yield_context yield) {
|
||||
auto const response = requestHandler_(request, connectionMock_, nullptr, yield);
|
||||
auto const message = boost::json::parse(response.message()).as_object();
|
||||
|
||||
EXPECT_EQ(message.at("error").as_string(), "slowDown");
|
||||
EXPECT_EQ(message.at("error_code").as_int64(), 10);
|
||||
EXPECT_EQ(message.at("status").as_string(), "error");
|
||||
EXPECT_EQ(message.at("id").as_string(), "some id");
|
||||
EXPECT_EQ(message.at("request").as_string(), requestMessage);
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(RequestHandlerTest, DosguardRateLimited_Ws_ErrorParsing)
|
||||
{
|
||||
auto const requestMessage = R"json(some request "id": "some id")json";
|
||||
web::ng::Request::HttpHeaders const headers{};
|
||||
web::ng::Request const request{requestMessage, headers};
|
||||
|
||||
EXPECT_CALL(dosGuardMock_, request(ip_)).WillOnce(testing::Return(false));
|
||||
|
||||
runSpawn([&](boost::asio::yield_context yield) {
|
||||
auto const response = requestHandler_(request, connectionMock_, nullptr, yield);
|
||||
auto const message = boost::json::parse(response.message()).as_object();
|
||||
|
||||
EXPECT_EQ(message.at("error").as_string(), "slowDown");
|
||||
EXPECT_EQ(message.at("error_code").as_int64(), 10);
|
||||
EXPECT_EQ(message.at("status").as_string(), "error");
|
||||
EXPECT_FALSE(message.contains("id"));
|
||||
EXPECT_EQ(message.at("request").as_string(), requestMessage);
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(RequestHandlerTest, RpcHandlerThrows)
|
||||
{
|
||||
web::ng::Request const request{http::request<http::string_body>{http::verb::get, "/", 11}};
|
||||
|
||||
EXPECT_CALL(dosGuardMock_, request(ip_)).WillOnce(testing::Return(true));
|
||||
EXPECT_CALL(*adminVerifier_, isAdmin).WillOnce(testing::Return(true));
|
||||
EXPECT_CALL(rpcHandler_, call).WillOnce(testing::Throw(std::runtime_error{"some error"}));
|
||||
|
||||
runSpawn([&](boost::asio::yield_context yield) {
|
||||
auto response = requestHandler_(request, connectionMock_, nullptr, yield);
|
||||
|
||||
auto const httpResponse = std::move(response).intoHttpResponse();
|
||||
|
||||
EXPECT_EQ(httpResponse.result(), boost::beast::http::status::internal_server_error);
|
||||
|
||||
auto const body = boost::json::parse(httpResponse.body()).as_object();
|
||||
EXPECT_EQ(body.at("error").as_string(), "internal");
|
||||
EXPECT_EQ(body.at("error_code").as_int64(), 73);
|
||||
EXPECT_EQ(body.at("status").as_string(), "error");
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(RequestHandlerTest, NoErrors)
|
||||
{
|
||||
web::ng::Request const request{http::request<http::string_body>{http::verb::get, "/", 11}};
|
||||
web::ng::Response const response{http::status::ok, "some response", request};
|
||||
auto const httpResponse = web::ng::Response{response}.intoHttpResponse();
|
||||
|
||||
EXPECT_CALL(dosGuardMock_, request(ip_)).WillOnce(testing::Return(true));
|
||||
EXPECT_CALL(*adminVerifier_, isAdmin).WillOnce(testing::Return(true));
|
||||
EXPECT_CALL(rpcHandler_, call).WillOnce(testing::Return(response));
|
||||
EXPECT_CALL(dosGuardMock_, add(ip_, testing::_)).WillOnce(testing::Return(true));
|
||||
|
||||
runSpawn([&](boost::asio::yield_context yield) {
|
||||
auto actualResponse = requestHandler_(request, connectionMock_, nullptr, yield);
|
||||
|
||||
auto const actualHttpResponse = std::move(actualResponse).intoHttpResponse();
|
||||
|
||||
EXPECT_EQ(actualHttpResponse.result(), httpResponse.result());
|
||||
EXPECT_EQ(actualHttpResponse.body(), httpResponse.body());
|
||||
EXPECT_EQ(actualHttpResponse.version(), 11);
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(RequestHandlerTest, ResponseDosGuardWarning_ResponseHasWarnings)
|
||||
{
|
||||
web::ng::Request const request{http::request<http::string_body>{http::verb::get, "/", 11}};
|
||||
web::ng::Response const response{
|
||||
http::status::ok, R"json({"some":"response", "warnings":["some warning"]})json", request
|
||||
};
|
||||
auto const httpResponse = web::ng::Response{response}.intoHttpResponse();
|
||||
|
||||
EXPECT_CALL(dosGuardMock_, request(ip_)).WillOnce(testing::Return(true));
|
||||
EXPECT_CALL(*adminVerifier_, isAdmin).WillOnce(testing::Return(true));
|
||||
EXPECT_CALL(rpcHandler_, call).WillOnce(testing::Return(response));
|
||||
EXPECT_CALL(dosGuardMock_, add(ip_, testing::_)).WillOnce(testing::Return(false));
|
||||
|
||||
runSpawn([&](boost::asio::yield_context yield) {
|
||||
auto actualResponse = requestHandler_(request, connectionMock_, nullptr, yield);
|
||||
|
||||
auto const actualHttpResponse = std::move(actualResponse).intoHttpResponse();
|
||||
|
||||
EXPECT_EQ(actualHttpResponse.result(), httpResponse.result());
|
||||
EXPECT_EQ(actualHttpResponse.version(), 11);
|
||||
|
||||
auto actualBody = boost::json::parse(actualHttpResponse.body()).as_object();
|
||||
EXPECT_EQ(actualBody.at("some").as_string(), "response");
|
||||
EXPECT_EQ(actualBody.at("warnings").as_array().size(), 2);
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(RequestHandlerTest, ResponseDosGuardWarning_ResponseDoesntHaveWarnings)
|
||||
{
|
||||
web::ng::Request const request{http::request<http::string_body>{http::verb::get, "/", 11}};
|
||||
web::ng::Response const response{http::status::ok, R"json({"some":"response"})json", request};
|
||||
auto const httpResponse = web::ng::Response{response}.intoHttpResponse();
|
||||
|
||||
EXPECT_CALL(dosGuardMock_, request(ip_)).WillOnce(testing::Return(true));
|
||||
EXPECT_CALL(*adminVerifier_, isAdmin).WillOnce(testing::Return(true));
|
||||
EXPECT_CALL(rpcHandler_, call).WillOnce(testing::Return(response));
|
||||
EXPECT_CALL(dosGuardMock_, add(ip_, testing::_)).WillOnce(testing::Return(false));
|
||||
|
||||
runSpawn([&](boost::asio::yield_context yield) {
|
||||
auto actualResponse = requestHandler_(request, connectionMock_, nullptr, yield);
|
||||
|
||||
auto const actualHttpResponse = std::move(actualResponse).intoHttpResponse();
|
||||
|
||||
EXPECT_EQ(actualHttpResponse.result(), httpResponse.result());
|
||||
EXPECT_EQ(actualHttpResponse.version(), 11);
|
||||
|
||||
auto actualBody = boost::json::parse(actualHttpResponse.body()).as_object();
|
||||
EXPECT_EQ(actualBody.at("some").as_string(), "response");
|
||||
EXPECT_EQ(actualBody.at("warnings").as_array().size(), 1);
|
||||
});
|
||||
}
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
#include "util/AsioContextTestFixture.hpp"
|
||||
#include "util/config/Config.hpp"
|
||||
#include "web/dosguard/DOSGuardInterface.hpp"
|
||||
#include "web/dosguard/DOSGuardMock.hpp"
|
||||
#include "web/dosguard/IntervalSweepHandler.hpp"
|
||||
|
||||
#include <boost/json/parse.hpp>
|
||||
@@ -40,10 +40,7 @@ protected:
|
||||
}
|
||||
)JSON";
|
||||
|
||||
struct DosGuardMock : BaseDOSGuard {
|
||||
MOCK_METHOD(void, clear, (), (noexcept, override));
|
||||
};
|
||||
testing::StrictMock<DosGuardMock> guardMock;
|
||||
DOSGuardStrictMock guardMock;
|
||||
|
||||
util::Config cfg{boost::json::parse(JSONData)};
|
||||
IntervalSweepHandler sweepHandler{cfg, ctx, guardMock};
|
||||
|
||||
@@ -34,7 +34,10 @@
|
||||
using namespace web::ng;
|
||||
namespace http = boost::beast::http;
|
||||
|
||||
struct RequestTest : public ::testing::Test {};
|
||||
struct RequestTest : public ::testing::Test {
|
||||
static Request::HttpHeaders const headers_;
|
||||
};
|
||||
Request::HttpHeaders const RequestTest::headers_ = {};
|
||||
|
||||
struct RequestMethodTestBundle {
|
||||
std::string testName;
|
||||
@@ -65,7 +68,7 @@ INSTANTIATE_TEST_SUITE_P(
|
||||
},
|
||||
RequestMethodTestBundle{
|
||||
.testName = "WebSocket",
|
||||
.request = Request{"websocket message", Request::HttpHeaders{}},
|
||||
.request = Request{"websocket message", RequestTest::headers_},
|
||||
.expectedMethod = Request::Method::Websocket,
|
||||
},
|
||||
RequestMethodTestBundle{
|
||||
@@ -101,7 +104,7 @@ INSTANTIATE_TEST_SUITE_P(
|
||||
},
|
||||
RequestIsHttpTestBundle{
|
||||
.testName = "WebSocketRequest",
|
||||
.request = Request{"websocket message", Request::HttpHeaders{}},
|
||||
.request = Request{"websocket message", RequestTest::headers_},
|
||||
.expectedIsHttp = false,
|
||||
}
|
||||
),
|
||||
@@ -124,7 +127,7 @@ TEST_F(RequestAsHttpRequestTest, HttpRequest)
|
||||
|
||||
TEST_F(RequestAsHttpRequestTest, WebSocketRequest)
|
||||
{
|
||||
Request const request{"websocket message", Request::HttpHeaders{}};
|
||||
Request const request{"websocket message", RequestTest::headers_};
|
||||
auto const maybeHttpRequest = request.asHttpRequest();
|
||||
EXPECT_FALSE(maybeHttpRequest.has_value());
|
||||
}
|
||||
@@ -142,7 +145,7 @@ TEST_F(RequestMessageTest, HttpRequest)
|
||||
TEST_F(RequestMessageTest, WebSocketRequest)
|
||||
{
|
||||
std::string const message = "websocket message";
|
||||
Request const request{message, Request::HttpHeaders{}};
|
||||
Request const request{message, RequestTest::headers_};
|
||||
EXPECT_EQ(request.message(), message);
|
||||
}
|
||||
|
||||
@@ -171,7 +174,7 @@ INSTANTIATE_TEST_SUITE_P(
|
||||
},
|
||||
RequestTargetTestBundle{
|
||||
.testName = "WebSocketRequest",
|
||||
.request = Request{"websocket message", Request::HttpHeaders{}},
|
||||
.request = Request{"websocket message", RequestTest::headers_},
|
||||
.expectedTarget = std::nullopt,
|
||||
}
|
||||
),
|
||||
|
||||
@@ -17,10 +17,14 @@
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#include "util/Taggable.hpp"
|
||||
#include "util/build/Build.hpp"
|
||||
#include "util/config/Config.hpp"
|
||||
#include "web/ng/MockConnection.hpp"
|
||||
#include "web/ng/Request.hpp"
|
||||
#include "web/ng/Response.hpp"
|
||||
|
||||
#include <boost/beast/core/flat_buffer.hpp>
|
||||
#include <boost/beast/http/field.hpp>
|
||||
#include <boost/beast/http/message.hpp>
|
||||
#include <boost/beast/http/status.hpp>
|
||||
@@ -29,6 +33,7 @@
|
||||
#include <boost/json/object.hpp>
|
||||
#include <boost/json/serialize.hpp>
|
||||
#include <fmt/core.h>
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <string>
|
||||
@@ -41,21 +46,23 @@ struct ResponseDeathTest : testing::Test {};
|
||||
|
||||
TEST_F(ResponseDeathTest, intoHttpResponseWithoutHttpData)
|
||||
{
|
||||
Request const request{"some messsage", Request::HttpHeaders{}};
|
||||
web::ng::Response response{boost::beast::http::status::ok, "message", request};
|
||||
Request::HttpHeaders const headers{};
|
||||
Request const request{"some message", headers};
|
||||
Response response{boost::beast::http::status::ok, "message", request};
|
||||
EXPECT_DEATH(std::move(response).intoHttpResponse(), "");
|
||||
}
|
||||
|
||||
TEST_F(ResponseDeathTest, asConstBufferWithHttpData)
|
||||
{
|
||||
Request const request{http::request<http::string_body>{http::verb::get, "/", 11}};
|
||||
web::ng::Response const response{boost::beast::http::status::ok, "message", request};
|
||||
Response const response{boost::beast::http::status::ok, "message", request};
|
||||
EXPECT_DEATH(response.asWsResponse(), "");
|
||||
}
|
||||
|
||||
struct ResponseTest : testing::Test {
|
||||
int const httpVersion_ = 11;
|
||||
http::status const responseStatus_ = http::status::ok;
|
||||
Request::HttpHeaders const headers_;
|
||||
};
|
||||
|
||||
TEST_F(ResponseTest, intoHttpResponse)
|
||||
@@ -63,7 +70,7 @@ TEST_F(ResponseTest, intoHttpResponse)
|
||||
Request const request{http::request<http::string_body>{http::verb::post, "/", httpVersion_, "some message"}};
|
||||
std::string const responseMessage = "response message";
|
||||
|
||||
web::ng::Response response{responseStatus_, responseMessage, request};
|
||||
Response response{responseStatus_, responseMessage, request};
|
||||
|
||||
auto const httpResponse = std::move(response).intoHttpResponse();
|
||||
EXPECT_EQ(httpResponse.result(), responseStatus_);
|
||||
@@ -83,7 +90,7 @@ TEST_F(ResponseTest, intoHttpResponseJson)
|
||||
Request const request{http::request<http::string_body>{http::verb::post, "/", httpVersion_, "some message"}};
|
||||
boost::json::object const responseMessage{{"key", "value"}};
|
||||
|
||||
web::ng::Response response{responseStatus_, responseMessage, request};
|
||||
Response response{responseStatus_, responseMessage, request};
|
||||
|
||||
auto const httpResponse = std::move(response).intoHttpResponse();
|
||||
EXPECT_EQ(httpResponse.result(), responseStatus_);
|
||||
@@ -100,9 +107,9 @@ TEST_F(ResponseTest, intoHttpResponseJson)
|
||||
|
||||
TEST_F(ResponseTest, asConstBuffer)
|
||||
{
|
||||
Request const request("some request", Request::HttpHeaders{});
|
||||
Request const request("some request", headers_);
|
||||
std::string const responseMessage = "response message";
|
||||
web::ng::Response const response{responseStatus_, responseMessage, request};
|
||||
Response const response{responseStatus_, responseMessage, request};
|
||||
|
||||
auto const buffer = response.asWsResponse();
|
||||
EXPECT_EQ(buffer.size(), responseMessage.size());
|
||||
@@ -113,9 +120,9 @@ TEST_F(ResponseTest, asConstBuffer)
|
||||
|
||||
TEST_F(ResponseTest, asConstBufferJson)
|
||||
{
|
||||
Request const request("some request", Request::HttpHeaders{});
|
||||
Request const request("some request", headers_);
|
||||
boost::json::object const responseMessage{{"key", "value"}};
|
||||
web::ng::Response const response{responseStatus_, responseMessage, request};
|
||||
Response const response{responseStatus_, responseMessage, request};
|
||||
|
||||
auto const buffer = response.asWsResponse();
|
||||
EXPECT_EQ(buffer.size(), boost::json::serialize(responseMessage).size());
|
||||
@@ -123,3 +130,88 @@ TEST_F(ResponseTest, asConstBufferJson)
|
||||
std::string const messageFromBuffer{static_cast<char const*>(buffer.data()), buffer.size()};
|
||||
EXPECT_EQ(messageFromBuffer, boost::json::serialize(responseMessage));
|
||||
}
|
||||
|
||||
TEST_F(ResponseTest, createFromStringAndConnection)
|
||||
{
|
||||
util::TagDecoratorFactory tagDecoratorFactory{util::Config{}};
|
||||
StrictMockConnection connection{"some ip", boost::beast::flat_buffer{}, tagDecoratorFactory};
|
||||
std::string const responseMessage = "response message";
|
||||
|
||||
EXPECT_CALL(connection, wasUpgraded()).WillOnce(testing::Return(false));
|
||||
Response response{responseStatus_, responseMessage, connection};
|
||||
|
||||
EXPECT_EQ(response.message(), responseMessage);
|
||||
auto const httpResponse = std::move(response).intoHttpResponse();
|
||||
EXPECT_EQ(httpResponse.result(), responseStatus_);
|
||||
auto const it = httpResponse.find(http::field::content_type);
|
||||
ASSERT_NE(it, httpResponse.end());
|
||||
EXPECT_EQ(it->value(), "text/html");
|
||||
}
|
||||
|
||||
TEST_F(ResponseTest, createFromJsonAndConnection)
|
||||
{
|
||||
util::TagDecoratorFactory tagDecoratorFactory{util::Config{}};
|
||||
StrictMockConnection connection{"some ip", boost::beast::flat_buffer{}, tagDecoratorFactory};
|
||||
boost::json::object const responseMessage{{"key", "value"}};
|
||||
|
||||
EXPECT_CALL(connection, wasUpgraded()).WillOnce(testing::Return(false));
|
||||
Response response{responseStatus_, responseMessage, connection};
|
||||
|
||||
EXPECT_EQ(response.message(), boost::json::serialize(responseMessage));
|
||||
auto const httpResponse = std::move(response).intoHttpResponse();
|
||||
EXPECT_EQ(httpResponse.result(), responseStatus_);
|
||||
auto const it = httpResponse.find(http::field::content_type);
|
||||
ASSERT_NE(it, httpResponse.end());
|
||||
EXPECT_EQ(it->value(), "application/json");
|
||||
}
|
||||
|
||||
TEST_F(ResponseTest, setMessageString_HttpResponse)
|
||||
{
|
||||
Request const request{http::request<http::string_body>{http::verb::post, "/", httpVersion_, "some request"}};
|
||||
Response response{boost::beast::http::status::ok, "message", request};
|
||||
|
||||
std::string const newMessage = "new message";
|
||||
response.setMessage(newMessage);
|
||||
|
||||
EXPECT_EQ(response.message(), newMessage);
|
||||
auto const httpResponse = std::move(response).intoHttpResponse();
|
||||
auto it = httpResponse.find(http::field::content_type);
|
||||
ASSERT_NE(it, httpResponse.end());
|
||||
EXPECT_EQ(it->value(), "text/html");
|
||||
}
|
||||
|
||||
TEST_F(ResponseTest, setMessageString_WsResponse)
|
||||
{
|
||||
Request const request{"some request", headers_};
|
||||
Response response{boost::beast::http::status::ok, "message", request};
|
||||
|
||||
std::string const newMessage = "new message";
|
||||
response.setMessage(newMessage);
|
||||
|
||||
EXPECT_EQ(response.message(), newMessage);
|
||||
}
|
||||
|
||||
TEST_F(ResponseTest, setMessageJson_HttpResponse)
|
||||
{
|
||||
Request const request{http::request<http::string_body>{http::verb::post, "/", httpVersion_, "some request"}};
|
||||
Response response{boost::beast::http::status::ok, "message", request};
|
||||
|
||||
boost::json::object const newMessage{{"key", "value"}};
|
||||
response.setMessage(newMessage);
|
||||
|
||||
auto const httpResponse = std::move(response).intoHttpResponse();
|
||||
auto it = httpResponse.find(http::field::content_type);
|
||||
ASSERT_NE(it, httpResponse.end());
|
||||
EXPECT_EQ(it->value(), "application/json");
|
||||
}
|
||||
|
||||
TEST_F(ResponseTest, setMessageJson_WsResponse)
|
||||
{
|
||||
Request const request{"some request", headers_};
|
||||
Response response{boost::beast::http::status::ok, "message", request};
|
||||
|
||||
boost::json::object const newMessage{{"key", "value"}};
|
||||
response.setMessage(newMessage);
|
||||
|
||||
EXPECT_EQ(response.message(), boost::json::serialize(newMessage));
|
||||
}
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
#include <boost/asio/ip/address_v4.hpp>
|
||||
#include <boost/asio/ip/tcp.hpp>
|
||||
#include <boost/asio/spawn.hpp>
|
||||
#include <boost/asio/steady_timer.hpp>
|
||||
#include <boost/beast/http/message.hpp>
|
||||
#include <boost/beast/http/status.hpp>
|
||||
#include <boost/beast/http/string_body.hpp>
|
||||
@@ -68,7 +69,8 @@ struct MakeServerTest : NoLoggerFixture, testing::WithParamInterface<MakeServerT
|
||||
TEST_P(MakeServerTest, Make)
|
||||
{
|
||||
util::Config const config{boost::json::parse(GetParam().configJson)};
|
||||
auto const expectedServer = make_Server(config, ioContext_);
|
||||
auto const expectedServer =
|
||||
make_Server(config, [](auto&&) -> std::expected<void, Response> { return {}; }, [](auto&&) {}, ioContext_);
|
||||
EXPECT_EQ(expectedServer.has_value(), GetParam().expectSuccess);
|
||||
}
|
||||
|
||||
@@ -159,7 +161,9 @@ struct ServerTest : SyncAsioContextTest {
|
||||
boost::json::object{{"server", boost::json::object{{"ip", "127.0.0.1"}, {"port", serverPort_}}}}
|
||||
};
|
||||
|
||||
std::expected<Server, std::string> server_ = make_Server(config_, ctx);
|
||||
Server::OnConnectCheck emptyOnConnectCheck_ = [](auto&&) -> std::expected<void, Response> { return {}; };
|
||||
|
||||
std::expected<Server, std::string> server_ = make_Server(config_, emptyOnConnectCheck_, [](auto&&) {}, ctx);
|
||||
|
||||
std::string requestMessage_ = "some request";
|
||||
std::string const headerName_ = "Some-header";
|
||||
@@ -181,8 +185,17 @@ TEST_F(ServerTest, BadEndpoint)
|
||||
boost::asio::ip::tcp::endpoint const endpoint{boost::asio::ip::address_v4::from_string("1.2.3.4"), 0};
|
||||
util::TagDecoratorFactory const tagDecoratorFactory{util::Config{boost::json::value{}}};
|
||||
Server server{
|
||||
ctx, endpoint, std::nullopt, ProcessingPolicy::Sequential, std::nullopt, tagDecoratorFactory, std::nullopt
|
||||
ctx,
|
||||
endpoint,
|
||||
std::nullopt,
|
||||
ProcessingPolicy::Sequential,
|
||||
std::nullopt,
|
||||
tagDecoratorFactory,
|
||||
std::nullopt,
|
||||
emptyOnConnectCheck_,
|
||||
[](auto&&) {}
|
||||
};
|
||||
|
||||
auto maybeError = server.run();
|
||||
ASSERT_TRUE(maybeError.has_value());
|
||||
EXPECT_THAT(*maybeError, testing::HasSubstr("Error creating TCP acceptor"));
|
||||
@@ -224,6 +237,170 @@ TEST_F(ServerHttpTest, ClientDisconnects)
|
||||
runContext();
|
||||
}
|
||||
|
||||
TEST_F(ServerHttpTest, OnConnectCheck)
|
||||
{
|
||||
auto const serverPort = tests::util::generateFreePort();
|
||||
boost::asio::ip::tcp::endpoint const endpoint{boost::asio::ip::address_v4::from_string("0.0.0.0"), serverPort};
|
||||
util::TagDecoratorFactory const tagDecoratorFactory{util::Config{boost::json::value{}}};
|
||||
|
||||
testing::StrictMock<testing::MockFunction<std::expected<void, Response>(Connection const&)>> onConnectCheck;
|
||||
|
||||
Server server{
|
||||
ctx,
|
||||
endpoint,
|
||||
std::nullopt,
|
||||
ProcessingPolicy::Sequential,
|
||||
std::nullopt,
|
||||
tagDecoratorFactory,
|
||||
std::nullopt,
|
||||
onConnectCheck.AsStdFunction(),
|
||||
[](auto&&) {}
|
||||
};
|
||||
|
||||
HttpAsyncClient client{ctx};
|
||||
|
||||
boost::asio::spawn(ctx, [&](boost::asio::yield_context yield) {
|
||||
boost::asio::steady_timer timer{yield.get_executor()};
|
||||
|
||||
EXPECT_CALL(onConnectCheck, Call)
|
||||
.WillOnce([&timer](Connection const& connection) -> std::expected<void, Response> {
|
||||
EXPECT_EQ(connection.ip(), "127.0.0.1");
|
||||
timer.cancel();
|
||||
return {};
|
||||
});
|
||||
|
||||
auto maybeError =
|
||||
client.connect("127.0.0.1", std::to_string(serverPort), yield, std::chrono::milliseconds{100});
|
||||
[&]() { ASSERT_FALSE(maybeError.has_value()) << maybeError->message(); }();
|
||||
|
||||
// Have to send a request here because the server does async_detect_ssl() which waits for some data to appear
|
||||
client.send(
|
||||
http::request<http::string_body>{http::verb::get, "/", 11, requestMessage_},
|
||||
yield,
|
||||
std::chrono::milliseconds{100}
|
||||
);
|
||||
|
||||
// Wait for the onConnectCheck to be called
|
||||
timer.expires_after(std::chrono::milliseconds{100});
|
||||
boost::system::error_code error; // Unused
|
||||
timer.async_wait(yield[error]);
|
||||
|
||||
client.gracefulShutdown();
|
||||
ctx.stop();
|
||||
});
|
||||
|
||||
server.run();
|
||||
|
||||
runContext();
|
||||
}
|
||||
|
||||
TEST_F(ServerHttpTest, OnConnectCheckFailed)
|
||||
{
|
||||
auto const serverPort = tests::util::generateFreePort();
|
||||
boost::asio::ip::tcp::endpoint const endpoint{boost::asio::ip::address_v4::from_string("0.0.0.0"), serverPort};
|
||||
util::TagDecoratorFactory const tagDecoratorFactory{util::Config{boost::json::value{}}};
|
||||
|
||||
testing::StrictMock<testing::MockFunction<std::expected<void, Response>(Connection const&)>> onConnectCheck;
|
||||
|
||||
Server server{
|
||||
ctx,
|
||||
endpoint,
|
||||
std::nullopt,
|
||||
ProcessingPolicy::Sequential,
|
||||
std::nullopt,
|
||||
tagDecoratorFactory,
|
||||
std::nullopt,
|
||||
onConnectCheck.AsStdFunction(),
|
||||
[](auto&&) {}
|
||||
};
|
||||
|
||||
HttpAsyncClient client{ctx};
|
||||
|
||||
EXPECT_CALL(onConnectCheck, Call).WillOnce([](Connection const& connection) {
|
||||
EXPECT_EQ(connection.ip(), "127.0.0.1");
|
||||
return std::unexpected{
|
||||
Response{http::status::too_many_requests, boost::json::object{{"error", "some error"}}, connection}
|
||||
};
|
||||
});
|
||||
|
||||
boost::asio::spawn(ctx, [&](boost::asio::yield_context yield) {
|
||||
auto maybeError =
|
||||
client.connect("127.0.0.1", std::to_string(serverPort), yield, std::chrono::milliseconds{100});
|
||||
[&]() { ASSERT_FALSE(maybeError.has_value()) << maybeError->message(); }();
|
||||
|
||||
// Have to send a request here because the server does async_detect_ssl() which waits for some data to appear
|
||||
client.send(
|
||||
http::request<http::string_body>{http::verb::get, "/", 11, requestMessage_},
|
||||
yield,
|
||||
std::chrono::milliseconds{100}
|
||||
);
|
||||
|
||||
auto const response = client.receive(yield, std::chrono::milliseconds{100});
|
||||
[&]() { ASSERT_TRUE(response.has_value()) << response.error().message(); }();
|
||||
EXPECT_EQ(response->result(), http::status::too_many_requests);
|
||||
EXPECT_EQ(response->body(), R"json({"error":"some error"})json");
|
||||
EXPECT_EQ(response->version(), 11);
|
||||
|
||||
client.gracefulShutdown();
|
||||
ctx.stop();
|
||||
});
|
||||
|
||||
server.run();
|
||||
|
||||
runContext();
|
||||
}
|
||||
|
||||
TEST_F(ServerHttpTest, OnDisconnectHook)
|
||||
{
|
||||
auto const serverPort = tests::util::generateFreePort();
|
||||
boost::asio::ip::tcp::endpoint const endpoint{boost::asio::ip::address_v4::from_string("0.0.0.0"), serverPort};
|
||||
util::TagDecoratorFactory const tagDecoratorFactory{util::Config{boost::json::value{}}};
|
||||
|
||||
testing::StrictMock<testing::MockFunction<void(Connection const&)>> OnDisconnectHookMock;
|
||||
|
||||
Server server{
|
||||
ctx,
|
||||
endpoint,
|
||||
std::nullopt,
|
||||
ProcessingPolicy::Sequential,
|
||||
std::nullopt,
|
||||
tagDecoratorFactory,
|
||||
std::nullopt,
|
||||
emptyOnConnectCheck_,
|
||||
OnDisconnectHookMock.AsStdFunction()
|
||||
};
|
||||
|
||||
HttpAsyncClient client{ctx};
|
||||
|
||||
boost::asio::spawn(ctx, [&](boost::asio::yield_context yield) {
|
||||
boost::asio::steady_timer timer{ctx.get_executor(), std::chrono::milliseconds{100}};
|
||||
|
||||
EXPECT_CALL(OnDisconnectHookMock, Call).WillOnce([&timer](auto&&) { timer.cancel(); });
|
||||
|
||||
auto maybeError =
|
||||
client.connect("127.0.0.1", std::to_string(serverPort), yield, std::chrono::milliseconds{100});
|
||||
[&]() { ASSERT_FALSE(maybeError.has_value()) << maybeError->message(); }();
|
||||
|
||||
client.send(
|
||||
http::request<http::string_body>{http::verb::get, "/", 11, requestMessage_},
|
||||
yield,
|
||||
std::chrono::milliseconds{100}
|
||||
);
|
||||
|
||||
client.gracefulShutdown();
|
||||
|
||||
// Wait for OnDisconnectHook is called
|
||||
boost::system::error_code error;
|
||||
timer.async_wait(yield[error]);
|
||||
|
||||
ctx.stop();
|
||||
});
|
||||
|
||||
server.run();
|
||||
|
||||
runContext();
|
||||
}
|
||||
|
||||
TEST_P(ServerHttpTest, RequestResponse)
|
||||
{
|
||||
HttpAsyncClient client{ctx};
|
||||
@@ -300,7 +477,8 @@ TEST_F(ServerTest, WsRequestResponse)
|
||||
{
|
||||
WebSocketAsyncClient client{ctx};
|
||||
|
||||
Response const response{http::status::ok, "some response", Request{requestMessage_, Request::HttpHeaders{}}};
|
||||
Request::HttpHeaders const headers{};
|
||||
Response const response{http::status::ok, "some response", Request{requestMessage_, headers}};
|
||||
|
||||
boost::asio::spawn(ctx, [&](boost::asio::yield_context yield) {
|
||||
auto maybeError =
|
||||
|
||||
@@ -64,7 +64,14 @@ namespace websocket = boost::beast::websocket;
|
||||
|
||||
struct ConnectionHandlerTest : SyncAsioContextTest {
|
||||
ConnectionHandlerTest(ProcessingPolicy policy, std::optional<size_t> maxParallelConnections)
|
||||
: tagFactory_{util::Config{}}, connectionHandler_{policy, maxParallelConnections, tagFactory_, std::nullopt}
|
||||
: tagFactory_{util::Config{}}
|
||||
, connectionHandler_{
|
||||
policy,
|
||||
maxParallelConnections,
|
||||
tagFactory_,
|
||||
std::nullopt,
|
||||
onDisconnectMock_.AsStdFunction()
|
||||
}
|
||||
{
|
||||
}
|
||||
|
||||
@@ -93,6 +100,7 @@ struct ConnectionHandlerTest : SyncAsioContextTest {
|
||||
return Request{std::forward<Args>(args)...};
|
||||
}
|
||||
|
||||
testing::StrictMock<testing::MockFunction<void(Connection const&)>> onDisconnectMock_;
|
||||
util::TagDecoratorFactory tagFactory_;
|
||||
ConnectionHandler connectionHandler_;
|
||||
|
||||
@@ -101,6 +109,8 @@ struct ConnectionHandlerTest : SyncAsioContextTest {
|
||||
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_);
|
||||
|
||||
Request::HttpHeaders headers_;
|
||||
};
|
||||
|
||||
struct ConnectionHandlerSequentialProcessingTest : ConnectionHandlerTest {
|
||||
@@ -113,6 +123,9 @@ TEST_F(ConnectionHandlerSequentialProcessingTest, ReceiveError)
|
||||
{
|
||||
EXPECT_CALL(*mockHttpConnection_, wasUpgraded).WillOnce(Return(false));
|
||||
EXPECT_CALL(*mockHttpConnection_, receive).WillOnce(Return(makeError(http::error::end_of_stream)));
|
||||
EXPECT_CALL(onDisconnectMock_, Call).WillOnce([connectionPtr = mockHttpConnection_.get()](Connection const& c) {
|
||||
EXPECT_EQ(&c, connectionPtr);
|
||||
});
|
||||
|
||||
runSpawn([this](boost::asio::yield_context yield) {
|
||||
connectionHandler_.processConnection(std::move(mockHttpConnection_), yield);
|
||||
@@ -124,6 +137,9 @@ TEST_F(ConnectionHandlerSequentialProcessingTest, ReceiveError_CloseConnection)
|
||||
EXPECT_CALL(*mockHttpConnection_, wasUpgraded).WillOnce(Return(false));
|
||||
EXPECT_CALL(*mockHttpConnection_, receive).WillOnce(Return(makeError(boost::asio::error::timed_out)));
|
||||
EXPECT_CALL(*mockHttpConnection_, close);
|
||||
EXPECT_CALL(onDisconnectMock_, Call).WillOnce([connectionPtr = mockHttpConnection_.get()](Connection const& c) {
|
||||
EXPECT_EQ(&c, connectionPtr);
|
||||
});
|
||||
|
||||
runSpawn([this](boost::asio::yield_context yield) {
|
||||
connectionHandler_.processConnection(std::move(mockHttpConnection_), yield);
|
||||
@@ -134,7 +150,7 @@ TEST_F(ConnectionHandlerSequentialProcessingTest, Receive_Handle_NoHandler_Send)
|
||||
{
|
||||
EXPECT_CALL(*mockHttpConnection_, wasUpgraded).WillOnce(Return(false));
|
||||
EXPECT_CALL(*mockHttpConnection_, receive)
|
||||
.WillOnce(Return(makeRequest("some_request", Request::HttpHeaders{})))
|
||||
.WillOnce(Return(makeRequest("some_request", headers_)))
|
||||
.WillOnce(Return(makeError(websocket::error::closed)));
|
||||
|
||||
EXPECT_CALL(*mockHttpConnection_, send).WillOnce([](Response response, auto&&, auto&&) {
|
||||
@@ -142,6 +158,10 @@ TEST_F(ConnectionHandlerSequentialProcessingTest, Receive_Handle_NoHandler_Send)
|
||||
return std::nullopt;
|
||||
});
|
||||
|
||||
EXPECT_CALL(onDisconnectMock_, Call).WillOnce([connectionPtr = mockHttpConnection_.get()](Connection const& c) {
|
||||
EXPECT_EQ(&c, connectionPtr);
|
||||
});
|
||||
|
||||
runSpawn([this](boost::asio::yield_context yield) {
|
||||
connectionHandler_.processConnection(std::move(mockHttpConnection_), yield);
|
||||
});
|
||||
@@ -165,6 +185,10 @@ TEST_F(ConnectionHandlerSequentialProcessingTest, Receive_Handle_BadTarget_Send)
|
||||
return std::nullopt;
|
||||
});
|
||||
|
||||
EXPECT_CALL(onDisconnectMock_, Call).WillOnce([connectionPtr = mockHttpConnection_.get()](Connection const& c) {
|
||||
EXPECT_EQ(&c, connectionPtr);
|
||||
});
|
||||
|
||||
runSpawn([this](boost::asio::yield_context yield) {
|
||||
connectionHandler_.processConnection(std::move(mockHttpConnection_), yield);
|
||||
});
|
||||
@@ -182,6 +206,10 @@ TEST_F(ConnectionHandlerSequentialProcessingTest, Receive_Handle_BadMethod_Send)
|
||||
return std::nullopt;
|
||||
});
|
||||
|
||||
EXPECT_CALL(onDisconnectMock_, Call).WillOnce([connectionPtr = mockHttpConnection_.get()](Connection const& c) {
|
||||
EXPECT_EQ(&c, connectionPtr);
|
||||
});
|
||||
|
||||
runSpawn([this](boost::asio::yield_context yield) {
|
||||
connectionHandler_.processConnection(std::move(mockHttpConnection_), yield);
|
||||
});
|
||||
@@ -199,7 +227,7 @@ TEST_F(ConnectionHandlerSequentialProcessingTest, Receive_Handle_Send)
|
||||
|
||||
EXPECT_CALL(*mockWsConnection_, wasUpgraded).WillOnce(Return(true));
|
||||
EXPECT_CALL(*mockWsConnection_, receive)
|
||||
.WillOnce(Return(makeRequest(requestMessage, Request::HttpHeaders{})))
|
||||
.WillOnce(Return(makeRequest(requestMessage, headers_)))
|
||||
.WillOnce(Return(makeError(websocket::error::closed)));
|
||||
|
||||
EXPECT_CALL(wsHandlerMock, Call).WillOnce([&](Request const& request, auto&&, auto&&, auto&&) {
|
||||
@@ -212,6 +240,10 @@ TEST_F(ConnectionHandlerSequentialProcessingTest, Receive_Handle_Send)
|
||||
return std::nullopt;
|
||||
});
|
||||
|
||||
EXPECT_CALL(onDisconnectMock_, Call).WillOnce([connectionPtr = mockWsConnection_.get()](Connection const& c) {
|
||||
EXPECT_EQ(&c, connectionPtr);
|
||||
});
|
||||
|
||||
runSpawn([this](boost::asio::yield_context yield) {
|
||||
connectionHandler_.processConnection(std::move(mockWsConnection_), yield);
|
||||
});
|
||||
@@ -228,7 +260,7 @@ TEST_F(ConnectionHandlerSequentialProcessingTest, SendSubscriptionMessage)
|
||||
|
||||
EXPECT_CALL(*mockWsConnection_, wasUpgraded).WillOnce(Return(true));
|
||||
EXPECT_CALL(*mockWsConnection_, receive)
|
||||
.WillOnce(Return(makeRequest("", Request::HttpHeaders{})))
|
||||
.WillOnce(Return(makeRequest("", headers_)))
|
||||
.WillOnce(Return(makeError(websocket::error::closed)));
|
||||
|
||||
EXPECT_CALL(wsHandlerMock, Call)
|
||||
@@ -246,6 +278,10 @@ TEST_F(ConnectionHandlerSequentialProcessingTest, SendSubscriptionMessage)
|
||||
return std::nullopt;
|
||||
});
|
||||
|
||||
EXPECT_CALL(onDisconnectMock_, Call).WillOnce([connectionPtr = mockWsConnection_.get()](Connection const& c) {
|
||||
EXPECT_EQ(&c, connectionPtr);
|
||||
});
|
||||
|
||||
runSpawn([this](boost::asio::yield_context yield) {
|
||||
connectionHandler_.processConnection(std::move(mockWsConnection_), yield);
|
||||
});
|
||||
@@ -262,7 +298,7 @@ TEST_F(ConnectionHandlerSequentialProcessingTest, SubscriptionContextIsDisconnec
|
||||
|
||||
EXPECT_CALL(*mockWsConnection_, wasUpgraded).WillOnce(Return(true));
|
||||
testing::Expectation const expectationReceiveCalled = EXPECT_CALL(*mockWsConnection_, receive)
|
||||
.WillOnce(Return(makeRequest("", Request::HttpHeaders{})))
|
||||
.WillOnce(Return(makeRequest("", headers_)))
|
||||
.WillOnce(Return(makeError(websocket::error::closed)));
|
||||
|
||||
EXPECT_CALL(wsHandlerMock, Call)
|
||||
@@ -276,6 +312,10 @@ TEST_F(ConnectionHandlerSequentialProcessingTest, SubscriptionContextIsDisconnec
|
||||
|
||||
EXPECT_CALL(onDisconnectHook, Call).After(expectationReceiveCalled);
|
||||
|
||||
EXPECT_CALL(onDisconnectMock_, Call).WillOnce([connectionPtr = mockWsConnection_.get()](Connection const& c) {
|
||||
EXPECT_EQ(&c, connectionPtr);
|
||||
});
|
||||
|
||||
runSpawn([this](boost::asio::yield_context yield) {
|
||||
connectionHandler_.processConnection(std::move(mockWsConnection_), yield);
|
||||
});
|
||||
@@ -314,6 +354,10 @@ TEST_F(ConnectionHandlerSequentialProcessingTest, SubscriptionContextIsNullForHt
|
||||
|
||||
EXPECT_CALL(*mockHttpConnection_, close);
|
||||
|
||||
EXPECT_CALL(onDisconnectMock_, Call).WillOnce([connectionPtr = mockHttpConnection_.get()](Connection const& c) {
|
||||
EXPECT_EQ(&c, connectionPtr);
|
||||
});
|
||||
|
||||
runSpawn([this](boost::asio::yield_context yield) {
|
||||
connectionHandler_.processConnection(std::move(mockHttpConnection_), yield);
|
||||
});
|
||||
@@ -354,6 +398,10 @@ TEST_F(ConnectionHandlerSequentialProcessingTest, Receive_Handle_Send_Loop)
|
||||
|
||||
EXPECT_CALL(*mockHttpConnection_, close);
|
||||
|
||||
EXPECT_CALL(onDisconnectMock_, Call).WillOnce([connectionPtr = mockHttpConnection_.get()](Connection const& c) {
|
||||
EXPECT_EQ(&c, connectionPtr);
|
||||
});
|
||||
|
||||
runSpawn([this](boost::asio::yield_context yield) {
|
||||
connectionHandler_.processConnection(std::move(mockHttpConnection_), yield);
|
||||
});
|
||||
@@ -385,6 +433,10 @@ TEST_F(ConnectionHandlerSequentialProcessingTest, Receive_Handle_SendError)
|
||||
return makeError(http::error::end_of_stream).error();
|
||||
});
|
||||
|
||||
EXPECT_CALL(onDisconnectMock_, Call).WillOnce([connectionPtr = mockHttpConnection_.get()](Connection const& c) {
|
||||
EXPECT_EQ(&c, connectionPtr);
|
||||
});
|
||||
|
||||
runSpawn([this](boost::asio::yield_context yield) {
|
||||
connectionHandler_.processConnection(std::move(mockHttpConnection_), yield);
|
||||
});
|
||||
@@ -408,7 +460,7 @@ TEST_F(ConnectionHandlerSequentialProcessingTest, Stop)
|
||||
if (connectionClosed) {
|
||||
return makeError(websocket::error::closed);
|
||||
}
|
||||
return makeRequest(requestMessage, Request::HttpHeaders{});
|
||||
return makeRequest(requestMessage, headers_);
|
||||
});
|
||||
|
||||
EXPECT_CALL(wsHandlerMock, Call).Times(3).WillRepeatedly([&](Request const& request, auto&&, auto&&, auto&&) {
|
||||
@@ -429,6 +481,10 @@ TEST_F(ConnectionHandlerSequentialProcessingTest, Stop)
|
||||
|
||||
EXPECT_CALL(*mockWsConnection_, close).WillOnce([&connectionClosed]() { connectionClosed = true; });
|
||||
|
||||
EXPECT_CALL(onDisconnectMock_, Call).WillOnce([connectionPtr = mockWsConnection_.get()](Connection const& c) {
|
||||
EXPECT_EQ(&c, connectionPtr);
|
||||
});
|
||||
|
||||
runSpawn([this](boost::asio::yield_context yield) {
|
||||
connectionHandler_.processConnection(std::move(mockWsConnection_), yield);
|
||||
});
|
||||
@@ -459,6 +515,10 @@ TEST_F(ConnectionHandlerParallelProcessingTest, ReceiveError)
|
||||
EXPECT_CALL(*mockHttpConnection_, wasUpgraded).WillOnce(Return(false));
|
||||
EXPECT_CALL(*mockHttpConnection_, receive).WillOnce(Return(makeError(http::error::end_of_stream)));
|
||||
|
||||
EXPECT_CALL(onDisconnectMock_, Call).WillOnce([connectionPtr = mockHttpConnection_.get()](Connection const& c) {
|
||||
EXPECT_EQ(&c, connectionPtr);
|
||||
});
|
||||
|
||||
runSpawn([this](boost::asio::yield_context yield) {
|
||||
connectionHandler_.processConnection(std::move(mockHttpConnection_), yield);
|
||||
});
|
||||
@@ -476,7 +536,7 @@ TEST_F(ConnectionHandlerParallelProcessingTest, Receive_Handle_Send)
|
||||
|
||||
EXPECT_CALL(*mockWsConnection_, wasUpgraded).WillOnce(Return(true));
|
||||
EXPECT_CALL(*mockWsConnection_, receive)
|
||||
.WillOnce(Return(makeRequest(requestMessage, Request::HttpHeaders{})))
|
||||
.WillOnce(Return(makeRequest(requestMessage, headers_)))
|
||||
.WillOnce(Return(makeError(websocket::error::closed)));
|
||||
|
||||
EXPECT_CALL(wsHandlerMock, Call).WillOnce([&](Request const& request, auto&&, auto&&, auto&&) {
|
||||
@@ -489,6 +549,10 @@ TEST_F(ConnectionHandlerParallelProcessingTest, Receive_Handle_Send)
|
||||
return std::nullopt;
|
||||
});
|
||||
|
||||
EXPECT_CALL(onDisconnectMock_, Call).WillOnce([connectionPtr = mockWsConnection_.get()](Connection const& c) {
|
||||
EXPECT_EQ(&c, connectionPtr);
|
||||
});
|
||||
|
||||
runSpawn([this](boost::asio::yield_context yield) {
|
||||
connectionHandler_.processConnection(std::move(mockWsConnection_), yield);
|
||||
});
|
||||
@@ -504,7 +568,7 @@ TEST_F(ConnectionHandlerParallelProcessingTest, Receive_Handle_Send_Loop)
|
||||
std::string const requestMessage = "some message";
|
||||
std::string const responseMessage = "some response";
|
||||
|
||||
auto const returnRequest = [&](auto&&, auto&&) { return makeRequest(requestMessage, Request::HttpHeaders{}); };
|
||||
auto const returnRequest = [&](auto&&, auto&&) { return makeRequest(requestMessage, headers_); };
|
||||
|
||||
EXPECT_CALL(*mockWsConnection_, wasUpgraded).WillOnce(Return(true));
|
||||
EXPECT_CALL(*mockWsConnection_, receive)
|
||||
@@ -524,6 +588,10 @@ TEST_F(ConnectionHandlerParallelProcessingTest, Receive_Handle_Send_Loop)
|
||||
return std::nullopt;
|
||||
});
|
||||
|
||||
EXPECT_CALL(onDisconnectMock_, Call).WillOnce([connectionPtr = mockWsConnection_.get()](Connection const& c) {
|
||||
EXPECT_EQ(&c, connectionPtr);
|
||||
});
|
||||
|
||||
runSpawn([this](boost::asio::yield_context yield) {
|
||||
connectionHandler_.processConnection(std::move(mockWsConnection_), yield);
|
||||
});
|
||||
@@ -539,7 +607,7 @@ TEST_F(ConnectionHandlerParallelProcessingTest, Receive_Handle_Send_Loop_TooMany
|
||||
std::string const requestMessage = "some message";
|
||||
std::string const responseMessage = "some response";
|
||||
|
||||
auto const returnRequest = [&](auto&&, auto&&) { return makeRequest(requestMessage, Request::HttpHeaders{}); };
|
||||
auto const returnRequest = [&](auto&&, auto&&) { return makeRequest(requestMessage, headers_); };
|
||||
testing::Sequence const sequence;
|
||||
|
||||
EXPECT_CALL(*mockWsConnection_, wasUpgraded).WillOnce(Return(true));
|
||||
@@ -583,6 +651,10 @@ TEST_F(ConnectionHandlerParallelProcessingTest, Receive_Handle_Send_Loop_TooMany
|
||||
.Times(2)
|
||||
.WillRepeatedly(Return(std::nullopt));
|
||||
|
||||
EXPECT_CALL(onDisconnectMock_, Call).WillOnce([connectionPtr = mockWsConnection_.get()](Connection const& c) {
|
||||
EXPECT_EQ(&c, connectionPtr);
|
||||
});
|
||||
|
||||
runSpawn([this](boost::asio::yield_context yield) {
|
||||
connectionHandler_.processConnection(std::move(mockWsConnection_), yield);
|
||||
});
|
||||
|
||||
@@ -49,7 +49,8 @@ struct ng_ErrorHandlingTests : NoLoggerFixture {
|
||||
{
|
||||
if (isHttp)
|
||||
return Request{http::request<http::string_body>{http::verb::post, "/", 11, body.value_or("")}};
|
||||
return Request{body.value_or(""), Request::HttpHeaders{}};
|
||||
static Request::HttpHeaders const headers_;
|
||||
return Request{body.value_or(""), headers_};
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -51,7 +51,8 @@ struct web_WsConnectionTests : SyncAsioContextTest {
|
||||
util::TagDecoratorFactory tagDecoratorFactory_{util::Config{boost::json::object{{"log_tag_style", "int"}}}};
|
||||
TestHttpServer httpServer_{ctx, "localhost"};
|
||||
WebSocketAsyncClient wsClient_{ctx};
|
||||
Request request_{"some request", Request::HttpHeaders{}};
|
||||
Request::HttpHeaders const headers_;
|
||||
Request request_{"some request", headers_};
|
||||
|
||||
std::unique_ptr<PlainWsConnection>
|
||||
acceptConnection(boost::asio::yield_context yield)
|
||||
|
||||
Reference in New Issue
Block a user