From e135aa49d51beca5e8e613ada6c4324810e9df0e Mon Sep 17 00:00:00 2001 From: Peter Chen <34582813+PeterChen13579@users.noreply.github.com> Date: Tue, 18 Jun 2024 06:29:05 -0400 Subject: [PATCH] Create generate free port class to avoid conflicting ports (#1439) Fixes #1317 --- tests/common/CMakeLists.txt | 1 + tests/common/util/AssignRandomPort.cpp | 45 +++ tests/common/util/AssignRandomPort.hpp | 29 ++ tests/common/util/MockXrpLedgerAPIService.hpp | 8 +- tests/common/util/TestHttpServer.cpp | 10 +- tests/common/util/TestHttpServer.hpp | 11 +- tests/common/util/TestWsServer.cpp | 10 +- tests/common/util/TestWsServer.hpp | 5 +- tests/unit/etl/ForwardingSourceTests.cpp | 9 +- tests/unit/etl/GrpcSourceTests.cpp | 4 +- tests/unit/etl/SubscriptionSourceTests.cpp | 7 +- .../util/requests/RequestBuilderTests.cpp | 8 +- .../unit/util/requests/WsConnectionTests.cpp | 6 +- tests/unit/web/ServerTests.cpp | 309 ++++++++++-------- 14 files changed, 313 insertions(+), 149 deletions(-) create mode 100644 tests/common/util/AssignRandomPort.cpp create mode 100644 tests/common/util/AssignRandomPort.hpp diff --git a/tests/common/CMakeLists.txt b/tests/common/CMakeLists.txt index 2493fbaf..c8ce7457 100644 --- a/tests/common/CMakeLists.txt +++ b/tests/common/CMakeLists.txt @@ -2,6 +2,7 @@ add_library(clio_testing_common) target_sources( clio_testing_common PRIVATE util/StringUtils.cpp util/TestHttpServer.cpp util/TestWsServer.cpp util/TestObject.cpp + util/AssignRandomPort.cpp ) include(deps/gtest) diff --git a/tests/common/util/AssignRandomPort.cpp b/tests/common/util/AssignRandomPort.cpp new file mode 100644 index 00000000..ae3a8149 --- /dev/null +++ b/tests/common/util/AssignRandomPort.cpp @@ -0,0 +1,45 @@ +//------------------------------------------------------------------------------ +/* + This file is part of clio: https://github.com/XRPLF/clio + Copyright (c) 2024, the clio developers. + + Permission to use, copy, modify, and distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#include "util/AssignRandomPort.hpp" + +#include +#include + +#include + +using tcp = boost::asio::ip::tcp; + +namespace tests::util { + +uint32_t +generateFreePort() +{ + boost::asio::io_context io_context; + tcp::acceptor acceptor(io_context); + tcp::endpoint const endpoint(tcp::v4(), 0); + + acceptor.open(endpoint.protocol()); + acceptor.set_option(tcp::acceptor::reuse_address(true)); + acceptor.bind(endpoint); + + return acceptor.local_endpoint().port(); +} + +} // namespace tests::util diff --git a/tests/common/util/AssignRandomPort.hpp b/tests/common/util/AssignRandomPort.hpp new file mode 100644 index 00000000..e3ea2fa0 --- /dev/null +++ b/tests/common/util/AssignRandomPort.hpp @@ -0,0 +1,29 @@ +//------------------------------------------------------------------------------ +/* + This file is part of clio: https://github.com/XRPLF/clio + Copyright (c) 2024, the clio developers. + + Permission to use, copy, modify, and distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#pragma once + +#include + +namespace tests::util { + +uint32_t +generateFreePort(); + +} // namespace tests::util diff --git a/tests/common/util/MockXrpLedgerAPIService.hpp b/tests/common/util/MockXrpLedgerAPIService.hpp index 2fd07a50..56b85d2f 100644 --- a/tests/common/util/MockXrpLedgerAPIService.hpp +++ b/tests/common/util/MockXrpLedgerAPIService.hpp @@ -82,7 +82,7 @@ struct WithMockXrpLedgerAPIService : virtual ::testing::Test { WithMockXrpLedgerAPIService(std::string serverAddress) { grpc::ServerBuilder builder; - builder.AddListeningPort(serverAddress, grpc::InsecureServerCredentials()); + builder.AddListeningPort(serverAddress, grpc::InsecureServerCredentials(), &port_); builder.RegisterService(&mockXrpLedgerAPIService); server_ = builder.BuildAndStart(); serverThread_ = std::thread([this] { server_->Wait(); }); @@ -94,11 +94,17 @@ struct WithMockXrpLedgerAPIService : virtual ::testing::Test { serverThread_.join(); } + int + getXRPLMockPort() const + { + return port_; + } MockXrpLedgerAPIService mockXrpLedgerAPIService; private: std::unique_ptr server_; std::thread serverThread_; + int port_{}; }; } // namespace tests::util diff --git a/tests/common/util/TestHttpServer.cpp b/tests/common/util/TestHttpServer.cpp index cac069e2..df6cf107 100644 --- a/tests/common/util/TestHttpServer.cpp +++ b/tests/common/util/TestHttpServer.cpp @@ -105,9 +105,9 @@ doSession( } // namespace -TestHttpServer::TestHttpServer(boost::asio::io_context& context, std::string host, int const port) : acceptor_(context) +TestHttpServer::TestHttpServer(boost::asio::io_context& context, std::string host) : acceptor_(context) { - boost::asio::ip::tcp::endpoint const endpoint(boost::asio::ip::make_address(host), port); + boost::asio::ip::tcp::endpoint const endpoint(boost::asio::ip::make_address(host), 0); acceptor_.open(endpoint.protocol()); acceptor_.set_option(asio::socket_base::reuse_address(true)); acceptor_.bind(endpoint); @@ -134,3 +134,9 @@ TestHttpServer::handleRequest(TestHttpServer::RequestHandler handler, bool const boost::asio::detached ); } + +std::string +TestHttpServer::port() const +{ + return std::to_string(acceptor_.local_endpoint().port()); +} diff --git a/tests/common/util/TestHttpServer.hpp b/tests/common/util/TestHttpServer.hpp index b62773ca..052581e2 100644 --- a/tests/common/util/TestHttpServer.hpp +++ b/tests/common/util/TestHttpServer.hpp @@ -41,9 +41,8 @@ public: * * @param context boost::asio::io_context to use for networking * @param host host to bind to - * @param port port to bind to */ - TestHttpServer(boost::asio::io_context& context, std::string host, int port); + TestHttpServer(boost::asio::io_context& context, std::string host); /** * @brief Start the server @@ -56,6 +55,14 @@ public: void handleRequest(RequestHandler handler, bool allowToFail = false); + /** + * @brief Return the port HTTP server is connected to + * + * @return string port number + */ + std::string + port() const; + private: boost::asio::ip::tcp::acceptor acceptor_; }; diff --git a/tests/common/util/TestWsServer.cpp b/tests/common/util/TestWsServer.cpp index ae50cad5..66c8b175 100644 --- a/tests/common/util/TestWsServer.cpp +++ b/tests/common/util/TestWsServer.cpp @@ -105,14 +105,20 @@ TestWsConnection::headers() const return headers_; } -TestWsServer::TestWsServer(asio::io_context& context, std::string const& host, int port) : acceptor_(context) +TestWsServer::TestWsServer(asio::io_context& context, std::string const& host) : acceptor_(context) { - auto endpoint = asio::ip::tcp::endpoint(boost::asio::ip::make_address(host), port); + auto endpoint = asio::ip::tcp::endpoint(boost::asio::ip::make_address(host), 0); acceptor_.open(endpoint.protocol()); acceptor_.set_option(asio::socket_base::reuse_address(true)); acceptor_.bind(endpoint); } +std::string +TestWsServer::port() const +{ + return std::to_string(this->acceptor_.local_endpoint().port()); +} + std::expected TestWsServer::acceptConnection(asio::yield_context yield) { diff --git a/tests/common/util/TestWsServer.hpp b/tests/common/util/TestWsServer.hpp index 87c291f1..96cf0885 100644 --- a/tests/common/util/TestWsServer.hpp +++ b/tests/common/util/TestWsServer.hpp @@ -70,7 +70,10 @@ class TestWsServer { boost::asio::ip::tcp::acceptor acceptor_; public: - TestWsServer(boost::asio::io_context& context, std::string const& host, int port); + TestWsServer(boost::asio::io_context& context, std::string const& host); + + std::string + port() const; std::expected acceptConnection(boost::asio::yield_context yield); diff --git a/tests/unit/etl/ForwardingSourceTests.cpp b/tests/unit/etl/ForwardingSourceTests.cpp index 32716b55..54385b90 100644 --- a/tests/unit/etl/ForwardingSourceTests.cpp +++ b/tests/unit/etl/ForwardingSourceTests.cpp @@ -37,8 +37,13 @@ using namespace etl::impl; struct ForwardingSourceTests : SyncAsioContextTest { - TestWsServer server_{ctx, "0.0.0.0", 11114}; - ForwardingSource forwardingSource{"127.0.0.1", "11114", std::chrono::milliseconds{1}, std::chrono::milliseconds{1}}; + TestWsServer server_{ctx, "0.0.0.0"}; + ForwardingSource forwardingSource{ + "127.0.0.1", + server_.port(), + std::chrono::milliseconds{1}, + std::chrono::milliseconds{1} + }; }; TEST_F(ForwardingSourceTests, ConnectionFailed) diff --git a/tests/unit/etl/GrpcSourceTests.cpp b/tests/unit/etl/GrpcSourceTests.cpp index 55f9de78..2a2172b4 100644 --- a/tests/unit/etl/GrpcSourceTests.cpp +++ b/tests/unit/etl/GrpcSourceTests.cpp @@ -42,9 +42,9 @@ using namespace etl::impl; struct GrpcSourceTests : NoLoggerFixture, util::prometheus::WithPrometheus, tests::util::WithMockXrpLedgerAPIService { GrpcSourceTests() - : WithMockXrpLedgerAPIService("localhost:55051") + : WithMockXrpLedgerAPIService("localhost:0") , mockBackend_(std::make_shared>(util::Config{})) - , grpcSource_("127.0.0.1", "55051", mockBackend_) + , grpcSource_("127.0.0.1", std::to_string(getXRPLMockPort()), mockBackend_) { } diff --git a/tests/unit/etl/SubscriptionSourceTests.cpp b/tests/unit/etl/SubscriptionSourceTests.cpp index 1d4e99ba..1752da7f 100644 --- a/tests/unit/etl/SubscriptionSourceTests.cpp +++ b/tests/unit/etl/SubscriptionSourceTests.cpp @@ -18,6 +18,7 @@ //============================================================================== #include "etl/impl/SubscriptionSource.hpp" +#include "util/AssignRandomPort.hpp" #include "util/Fixtures.hpp" #include "util/MockNetworkValidatedLedgers.hpp" #include "util/MockSubscriptionManager.hpp" @@ -31,6 +32,7 @@ #include #include +#include #include #include #include @@ -46,8 +48,7 @@ struct SubscriptionSourceConnectionTests : public NoLoggerFixture { } boost::asio::io_context ioContext_; - - TestWsServer wsServer_{ioContext_, "0.0.0.0", 11113}; + TestWsServer wsServer_{ioContext_, "0.0.0.0"}; StrictMockNetworkValidatedLedgersPtr networkValidatedLedgers_; StrictMockSubscriptionManagerSharedPtr subscriptionManager_; @@ -59,7 +60,7 @@ struct SubscriptionSourceConnectionTests : public NoLoggerFixture { SubscriptionSource subscriptionSource_{ ioContext_, "127.0.0.1", - "11113", + wsServer_.port(), networkValidatedLedgers_, subscriptionManager_, onConnectHook_.AsStdFunction(), diff --git a/tests/unit/util/requests/RequestBuilderTests.cpp b/tests/unit/util/requests/RequestBuilderTests.cpp index 98021ec5..092e5471 100644 --- a/tests/unit/util/requests/RequestBuilderTests.cpp +++ b/tests/unit/util/requests/RequestBuilderTests.cpp @@ -17,6 +17,7 @@ */ //============================================================================== +#include "util/AssignRandomPort.hpp" #include "util/Fixtures.hpp" #include "util/TestHttpServer.hpp" #include "util/requests/RequestBuilder.hpp" @@ -31,6 +32,7 @@ #include #include +#include #include #include #include @@ -50,8 +52,8 @@ struct RequestBuilderTestBundle { }; struct RequestBuilderTestBase : SyncAsioContextTest { - TestHttpServer server{ctx, "0.0.0.0", 11111}; - RequestBuilder builder{"localhost", "11111"}; + TestHttpServer server{ctx, "0.0.0.0"}; + RequestBuilder builder{"localhost", server.port()}; }; struct RequestBuilderTest : RequestBuilderTestBase, testing::WithParamInterface {}; @@ -182,7 +184,7 @@ TEST_F(RequestBuilderTest, ResolveError) TEST_F(RequestBuilderTest, ConnectionError) { - builder = RequestBuilder{"localhost", "11112"}; + builder = RequestBuilder{"localhost", std::to_string(tests::util::generateFreePort())}; builder.setTimeout(std::chrono::milliseconds{1}); runSpawn([this](asio::yield_context yield) { auto const response = builder.getPlain(yield); diff --git a/tests/unit/util/requests/WsConnectionTests.cpp b/tests/unit/util/requests/WsConnectionTests.cpp index a10ef52e..214b7a53 100644 --- a/tests/unit/util/requests/WsConnectionTests.cpp +++ b/tests/unit/util/requests/WsConnectionTests.cpp @@ -17,6 +17,7 @@ */ //============================================================================== +#include "util/AssignRandomPort.hpp" #include "util/Fixtures.hpp" #include "util/TestWsServer.hpp" #include "util/requests/Types.hpp" @@ -29,6 +30,7 @@ #include #include +#include #include #include #include @@ -41,8 +43,8 @@ namespace asio = boost::asio; namespace http = boost::beast::http; struct WsConnectionTestsBase : SyncAsioContextTest { - WsConnectionBuilder builder{"localhost", "11112"}; - TestWsServer server{ctx, "0.0.0.0", 11112}; + TestWsServer server{ctx, "0.0.0.0"}; + WsConnectionBuilder builder{"localhost", server.port()}; template T diff --git a/tests/unit/web/ServerTests.cpp b/tests/unit/web/ServerTests.cpp index f89e657f..635d534c 100644 --- a/tests/unit/web/ServerTests.cpp +++ b/tests/unit/web/ServerTests.cpp @@ -17,6 +17,7 @@ */ //============================================================================== +#include "util/AssignRandomPort.hpp" #include "util/Fixtures.hpp" #include "util/MockPrometheus.hpp" #include "util/TestHttpSyncClient.hpp" @@ -43,13 +44,17 @@ #include #include +#include #include +#include #include +#include #include #include #include #include #include +#include #include #include #include @@ -57,37 +62,48 @@ using namespace util; using namespace web::impl; using namespace web; +using namespace boost::json; -constexpr static auto JSONData = R"JSON( - { - "server":{ - "ip":"0.0.0.0", - "port":8888 - }, - "dos_guard": { - "max_fetches": 100, - "sweep_interval": 1000, - "max_connections": 2, - "max_requests": 3, - "whitelist": ["127.0.0.1"] - } - } -)JSON"; +std::string +generateJSONWithDynamicPort(std::string_view port) +{ + return fmt::format( + R"JSON({{ + "server": {{ + "ip": "0.0.0.0", + "port": {} + }}, + "dos_guard": {{ + "max_fetches": 100, + "sweep_interval": 1000, + "max_connections": 2, + "max_requests": 3, + "whitelist": ["127.0.0.1"] + }} + }})JSON", + port + ); +} -constexpr static auto JSONDataOverload = R"JSON( - { - "server":{ - "ip":"0.0.0.0", - "port":8888 - }, - "dos_guard": { - "max_fetches": 100, - "sweep_interval": 1000, - "max_connections": 2, - "max_requests": 1 - } - } -)JSON"; +std::string +generateJSONDataOverload(std::string_view port) +{ + return fmt::format( + R"JSON({{ + "server": {{ + "ip": "0.0.0.0", + "port": {} + }}, + "dos_guard": {{ + "max_fetches": 100, + "sweep_interval": 1000, + "max_connections": 2, + "max_requests": 1 + }} + }})JSON", + port + ); +} // for testing, we use a self-signed certificate std::optional @@ -168,12 +184,13 @@ protected: // this ctx is for dos timer boost::asio::io_context ctxSync; - Config cfg{boost::json::parse(JSONData)}; + std::string const port = std::to_string(tests::util::generateFreePort()); + Config cfg{parse(generateJSONWithDynamicPort(port))}; IntervalSweepHandler sweepHandler = web::IntervalSweepHandler{cfg, ctxSync}; WhitelistHandler whitelistHandler = web::WhitelistHandler{cfg}; DOSGuard dosGuard = web::DOSGuard{cfg, whitelistHandler, sweepHandler}; - Config cfgOverload{boost::json::parse(JSONDataOverload)}; + Config cfgOverload{parse(generateJSONDataOverload(port))}; IntervalSweepHandler sweepHandlerOverload = web::IntervalSweepHandler{cfgOverload, ctxSync}; WhitelistHandler whitelistHandlerOverload = web::WhitelistHandler{cfgOverload}; DOSGuard dosGuardOverload = web::DOSGuard{cfgOverload, whitelistHandlerOverload, sweepHandlerOverload}; @@ -250,7 +267,7 @@ TEST_F(WebServerTest, Http) { auto e = std::make_shared(); auto const server = makeServerSync(cfg, ctx, std::nullopt, dosGuard, e); - auto const res = HttpSyncClient::syncPost("localhost", "8888", R"({"Hello":1})"); + auto const res = HttpSyncClient::syncPost("localhost", port, R"({"Hello":1})"); EXPECT_EQ(res, R"({"Hello":1})"); } @@ -259,7 +276,7 @@ TEST_F(WebServerTest, Ws) auto e = std::make_shared(); auto const server = makeServerSync(cfg, ctx, std::nullopt, dosGuard, e); WebSocketSyncClient wsClient; - wsClient.connect("localhost", "8888"); + wsClient.connect("localhost", port); auto const res = wsClient.syncPost(R"({"Hello":1})"); EXPECT_EQ(res, R"({"Hello":1})"); wsClient.disconnect(); @@ -269,7 +286,7 @@ TEST_F(WebServerTest, HttpInternalError) { auto e = std::make_shared(); auto const server = makeServerSync(cfg, ctx, std::nullopt, dosGuard, e); - auto const res = HttpSyncClient::syncPost("localhost", "8888", R"({})"); + auto const res = HttpSyncClient::syncPost("localhost", port, R"({})"); EXPECT_EQ( res, R"({"error":"internal","error_code":73,"error_message":"Internal error.","status":"error","type":"response"})" @@ -281,7 +298,7 @@ TEST_F(WebServerTest, WsInternalError) auto e = std::make_shared(); auto const server = makeServerSync(cfg, ctx, std::nullopt, dosGuard, e); WebSocketSyncClient wsClient; - wsClient.connect("localhost", "8888"); + wsClient.connect("localhost", port); auto const res = wsClient.syncPost(R"({"id":"id1"})"); wsClient.disconnect(); EXPECT_EQ( @@ -295,7 +312,7 @@ TEST_F(WebServerTest, WsInternalErrorNotJson) auto e = std::make_shared(); auto const server = makeServerSync(cfg, ctx, std::nullopt, dosGuard, e); WebSocketSyncClient wsClient; - wsClient.connect("localhost", "8888"); + wsClient.connect("localhost", port); auto const res = wsClient.syncPost("not json"); wsClient.disconnect(); EXPECT_EQ( @@ -310,7 +327,7 @@ TEST_F(WebServerTest, Https) auto sslCtx = parseCertsForTest(); auto const ctxSslRef = sslCtx ? std::optional>{sslCtx.value()} : std::nullopt; auto const server = makeServerSync(cfg, ctx, ctxSslRef, dosGuard, e); - auto const res = HttpsSyncClient::syncPost("localhost", "8888", R"({"Hello":1})"); + auto const res = HttpsSyncClient::syncPost("localhost", port, R"({"Hello":1})"); EXPECT_EQ(res, R"({"Hello":1})"); } @@ -322,7 +339,7 @@ TEST_F(WebServerTest, Wss) auto server = makeServerSync(cfg, ctx, ctxSslRef, dosGuard, e); WebServerSslSyncClient wsClient; - wsClient.connect("localhost", "8888"); + wsClient.connect("localhost", port); auto const res = wsClient.syncPost(R"({"Hello":1})"); EXPECT_EQ(res, R"({"Hello":1})"); wsClient.disconnect(); @@ -332,9 +349,9 @@ TEST_F(WebServerTest, HttpRequestOverload) { auto e = std::make_shared(); auto const server = makeServerSync(cfg, ctx, std::nullopt, dosGuardOverload, e); - auto res = HttpSyncClient::syncPost("localhost", "8888", R"({})"); + auto res = HttpSyncClient::syncPost("localhost", port, R"({})"); EXPECT_EQ(res, "{}"); - res = HttpSyncClient::syncPost("localhost", "8888", R"({})"); + res = HttpSyncClient::syncPost("localhost", port, R"({})"); EXPECT_EQ( res, R"({"error":"slowDown","error_code":10,"error_message":"You are placing too much load on the server.","status":"error","type":"response"})" @@ -346,12 +363,12 @@ TEST_F(WebServerTest, WsRequestOverload) auto e = std::make_shared(); auto const server = makeServerSync(cfg, ctx, std::nullopt, dosGuardOverload, e); WebSocketSyncClient wsClient; - wsClient.connect("localhost", "8888"); + wsClient.connect("localhost", port); auto res = wsClient.syncPost(R"({})"); wsClient.disconnect(); EXPECT_EQ(res, "{}"); WebSocketSyncClient wsClient2; - wsClient2.connect("localhost", "8888"); + wsClient2.connect("localhost", port); res = wsClient2.syncPost(R"({})"); wsClient2.disconnect(); EXPECT_EQ( @@ -365,7 +382,7 @@ TEST_F(WebServerTest, HttpPayloadOverload) std::string const s100(100, 'a'); auto e = std::make_shared(); auto server = makeServerSync(cfg, ctx, std::nullopt, dosGuardOverload, e); - auto const res = HttpSyncClient::syncPost("localhost", "8888", fmt::format(R"({{"payload":"{}"}})", s100)); + auto const res = HttpSyncClient::syncPost("localhost", port, fmt::format(R"({{"payload":"{}"}})", s100)); EXPECT_EQ( res, R"({"payload":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","warning":"load","warnings":[{"id":2003,"message":"You are about to be rate limited"}]})" @@ -378,7 +395,7 @@ TEST_F(WebServerTest, WsPayloadOverload) auto e = std::make_shared(); auto server = makeServerSync(cfg, ctx, std::nullopt, dosGuardOverload, e); WebSocketSyncClient wsClient; - wsClient.connect("localhost", "8888"); + wsClient.connect("localhost", port); auto const res = wsClient.syncPost(fmt::format(R"({{"payload":"{}"}})", s100)); wsClient.disconnect(); EXPECT_EQ( @@ -393,13 +410,13 @@ TEST_F(WebServerTest, WsTooManyConnection) auto server = makeServerSync(cfg, ctx, std::nullopt, dosGuardOverload, e); // max connection is 2, exception should happen when the third connection is made WebSocketSyncClient wsClient1; - wsClient1.connect("localhost", "8888"); + wsClient1.connect("localhost", port); WebSocketSyncClient wsClient2; - wsClient2.connect("localhost", "8888"); + wsClient2.connect("localhost", port); bool exceptionThrown = false; try { WebSocketSyncClient wsClient3; - wsClient3.connect("localhost", "8888"); + wsClient3.connect("localhost", port); } catch (boost::system::system_error const& ex) { exceptionThrown = true; EXPECT_EQ(ex.code(), boost::beast::websocket::error::upgrade_declined); @@ -409,45 +426,65 @@ TEST_F(WebServerTest, WsTooManyConnection) EXPECT_TRUE(exceptionThrown); } -static auto constexpr JSONServerConfigWithAdminPassword = R"JSON( - { - "server":{ - "ip": "0.0.0.0", - "port": 8888, - "admin_password": "secret" - } - } -)JSON"; +std::string +JSONServerConfigWithAdminPassword(uint32_t const port) +{ + return fmt::format( + R"JSON({{ + "server": {{ + "ip": "0.0.0.0", + "port": {}, + "admin_password": "secret" + }} + }})JSON", + port + ); +} -static auto constexpr JSONServerConfigWithLocalAdmin = R"JSON( - { - "server":{ - "ip": "0.0.0.0", - "port": 8888, - "local_admin": true - } - } -)JSON"; +std::string +JSONServerConfigWithLocalAdmin(uint32_t const port) +{ + return fmt::format( + R"JSON({{ + "server": {{ + "ip": "0.0.0.0", + "port": {}, + "local_admin": true + }} + }})JSON", + port + ); +} -static auto constexpr JSONServerConfigWithBothAdminPasswordAndLocalAdminFalse = R"JSON( - { - "server":{ - "ip": "0.0.0.0", - "port": 8888, - "admin_password": "secret", - "local_admin": false - } - } -)JSON"; +std::string +JSONServerConfigWithBothAdminPasswordAndLocalAdminFalse(uint32_t const port) +{ + return fmt::format( + R"JSON({{ + "server": {{ + "ip": "0.0.0.0", + "port": {}, + "admin_password": "secret", + "local_admin": false + }} + }})JSON", + port + ); +} -static auto constexpr JSONServerConfigWithNoSpecifiedAdmin = R"JSON( - { - "server":{ - "ip": "0.0.0.0", - "port": 8888 - } - } -)JSON"; +std::string +JSONServerConfigWithNoSpecifiedAdmin(uint32_t const port) +{ + return fmt::format( + R"JSON({{ + "server": {{ + "ip": "0.0.0.0", + "port": {} + }} + }})JSON", + port + ); +} // get this value from online sha256 generator static auto constexpr SecertSha256 = "2bb80d537b1da3e38bd30361aa855686bde0eacd7162fef6a25fe97bf527a25b"; @@ -478,10 +515,11 @@ class WebServerAdminTest : public WebServerTest, public ::testing::WithParamInte TEST_P(WebServerAdminTest, WsAdminCheck) { auto e = std::make_shared(); - Config const serverConfig{boost::json::parse(GetParam().config)}; + Config const serverConfig{parse(GetParam().config)}; auto server = makeServerSync(serverConfig, ctx, std::nullopt, dosGuardOverload, e); WebSocketSyncClient wsClient; - wsClient.connect("localhost", "8888", GetParam().headers); + uint32_t webServerPort = serverConfig.value("server.port"); + wsClient.connect("localhost", std::to_string(webServerPort), GetParam().headers); std::string const request = "Why hello"; auto const res = wsClient.syncPost(request); wsClient.disconnect(); @@ -491,10 +529,11 @@ TEST_P(WebServerAdminTest, WsAdminCheck) TEST_P(WebServerAdminTest, HttpAdminCheck) { auto e = std::make_shared(); - Config const serverConfig{boost::json::parse(GetParam().config)}; + Config const serverConfig{parse(GetParam().config)}; auto server = makeServerSync(serverConfig, ctx, std::nullopt, dosGuardOverload, e); std::string const request = "Why hello"; - auto const res = HttpSyncClient::syncPost("localhost", "8888", request, GetParam().headers); + uint32_t webServerPort = serverConfig.value("server.port"); + auto const res = HttpSyncClient::syncPost("localhost", std::to_string(webServerPort), request, GetParam().headers); EXPECT_EQ(res, fmt::format("{} {}", request, GetParam().expectedResponse)); } @@ -503,27 +542,27 @@ INSTANTIATE_TEST_CASE_P( WebServerAdminTest, ::testing::Values( WebServerAdminTestParams{ - .config = JSONServerConfigWithAdminPassword, + .config = JSONServerConfigWithAdminPassword(tests::util::generateFreePort()), .headers = {}, .expectedResponse = "user" }, WebServerAdminTestParams{ - .config = JSONServerConfigWithAdminPassword, + .config = JSONServerConfigWithAdminPassword(tests::util::generateFreePort()), .headers = {WebHeader(http::field::authorization, "")}, .expectedResponse = "user" }, WebServerAdminTestParams{ - .config = JSONServerConfigWithAdminPassword, + .config = JSONServerConfigWithAdminPassword(tests::util::generateFreePort()), .headers = {WebHeader(http::field::authorization, "s")}, .expectedResponse = "user" }, WebServerAdminTestParams{ - .config = JSONServerConfigWithAdminPassword, + .config = JSONServerConfigWithAdminPassword(tests::util::generateFreePort()), .headers = {WebHeader(http::field::authorization, SecertSha256)}, .expectedResponse = "user" }, WebServerAdminTestParams{ - .config = JSONServerConfigWithAdminPassword, + .config = JSONServerConfigWithAdminPassword(tests::util::generateFreePort()), .headers = {WebHeader( http::field::authorization, fmt::format("{}{}", PasswordAdminVerificationStrategy::passwordPrefix, SecertSha256) @@ -531,12 +570,12 @@ INSTANTIATE_TEST_CASE_P( .expectedResponse = "admin" }, WebServerAdminTestParams{ - .config = JSONServerConfigWithBothAdminPasswordAndLocalAdminFalse, + .config = JSONServerConfigWithBothAdminPasswordAndLocalAdminFalse(tests::util::generateFreePort()), .headers = {WebHeader(http::field::authorization, SecertSha256)}, .expectedResponse = "user" }, WebServerAdminTestParams{ - .config = JSONServerConfigWithBothAdminPasswordAndLocalAdminFalse, + .config = JSONServerConfigWithBothAdminPasswordAndLocalAdminFalse(tests::util::generateFreePort()), .headers = {WebHeader( http::field::authorization, fmt::format("{}{}", PasswordAdminVerificationStrategy::passwordPrefix, SecertSha256) @@ -544,16 +583,20 @@ INSTANTIATE_TEST_CASE_P( .expectedResponse = "admin" }, WebServerAdminTestParams{ - .config = JSONServerConfigWithAdminPassword, + .config = JSONServerConfigWithAdminPassword(tests::util::generateFreePort()), .headers = {WebHeader( http::field::authentication_info, fmt::format("{}{}", PasswordAdminVerificationStrategy::passwordPrefix, SecertSha256) )}, .expectedResponse = "user" }, - WebServerAdminTestParams{.config = JSONServerConfigWithLocalAdmin, .headers = {}, .expectedResponse = "admin"}, WebServerAdminTestParams{ - .config = JSONServerConfigWithNoSpecifiedAdmin, + .config = JSONServerConfigWithLocalAdmin(tests::util::generateFreePort()), + .headers = {}, + .expectedResponse = "admin" + }, + WebServerAdminTestParams{ + .config = JSONServerConfigWithNoSpecifiedAdmin(tests::util::generateFreePort()), .headers = {}, .expectedResponse = "admin" } @@ -563,36 +606,40 @@ INSTANTIATE_TEST_CASE_P( TEST_F(WebServerTest, AdminErrorCfgTestBothAdminPasswordAndLocalAdminSet) { - static auto constexpr JSONServerConfigWithBothAdminPasswordAndLocalAdmin = R"JSON( - { - "server":{ + uint32_t webServerPort = tests::util::generateFreePort(); + std::string JSONServerConfigWithBothAdminPasswordAndLocalAdmin = fmt::format( + R"JSON({{ + "server":{{ "ip": "0.0.0.0", - "port": 8888, + "port": {}, "admin_password": "secret", "local_admin": true - } - } - )JSON"; + }} + }})JSON", + webServerPort + ); auto e = std::make_shared(); - Config const serverConfig{boost::json::parse(JSONServerConfigWithBothAdminPasswordAndLocalAdmin)}; + Config const serverConfig{parse(JSONServerConfigWithBothAdminPasswordAndLocalAdmin)}; EXPECT_THROW(web::make_HttpServer(serverConfig, ctx, std::nullopt, dosGuardOverload, e), std::logic_error); } TEST_F(WebServerTest, AdminErrorCfgTestBothAdminPasswordAndLocalAdminFalse) { - static auto constexpr JSONServerConfigWithNoAdminPasswordAndLocalAdminFalse = R"JSON( - { - "server":{ - "ip": "0.0.0.0", - "port": 8888, - "local_admin": false - } - } - )JSON"; + uint32_t webServerPort = tests::util::generateFreePort(); + std::string JSONServerConfigWithNoAdminPasswordAndLocalAdminFalse = fmt::format( + R"JSON({{ + "server": {{ + "ip": "0.0.0.0", + "port": {}, + "local_admin": false + }} + }})JSON", + webServerPort + ); auto e = std::make_shared(); - Config const serverConfig{boost::json::parse(JSONServerConfigWithNoAdminPasswordAndLocalAdminFalse)}; + Config const serverConfig{parse(JSONServerConfigWithNoAdminPasswordAndLocalAdminFalse)}; EXPECT_THROW(web::make_HttpServer(serverConfig, ctx, std::nullopt, dosGuardOverload, e), std::logic_error); } @@ -601,32 +648,35 @@ struct WebServerPrometheusTest : util::prometheus::WithPrometheus, WebServerTest TEST_F(WebServerPrometheusTest, rejectedWithoutAdminPassword) { auto e = std::make_shared(); - Config const serverConfig{boost::json::parse(JSONServerConfigWithAdminPassword)}; + uint32_t webServerPort = tests::util::generateFreePort(); + Config const serverConfig{parse(JSONServerConfigWithAdminPassword(webServerPort))}; auto server = makeServerSync(serverConfig, ctx, std::nullopt, dosGuard, e); - auto const res = HttpSyncClient::syncGet("localhost", "8888", "", "/metrics"); + auto const res = HttpSyncClient::syncGet("localhost", std::to_string(webServerPort), "", "/metrics"); EXPECT_EQ(res, "Only admin is allowed to collect metrics"); } TEST_F(WebServerPrometheusTest, rejectedIfPrometheusIsDisabled) { - static auto constexpr JSONServerConfigWithDisabledPrometheus = R"JSON( - { - "server": { + uint32_t webServerPort = tests::util::generateFreePort(); + std::string JSONServerConfigWithDisabledPrometheus = fmt::format( + R"JSON({{ + "server":{{ "ip": "0.0.0.0", - "port": 8888, + "port": {}, "admin_password": "secret" - }, - "prometheus": { "enabled": false } - } - )JSON"; + }}, + "prometheus": {{ "enabled": false }} + }})JSON", + webServerPort + ); auto e = std::make_shared(); - Config const serverConfig{boost::json::parse(JSONServerConfigWithDisabledPrometheus)}; + Config const serverConfig{parse(JSONServerConfigWithDisabledPrometheus)}; PrometheusService::init(serverConfig); auto server = makeServerSync(serverConfig, ctx, std::nullopt, dosGuard, e); auto const res = HttpSyncClient::syncGet( "localhost", - "8888", + std::to_string(webServerPort), "", "/metrics", {WebHeader( @@ -639,14 +689,15 @@ TEST_F(WebServerPrometheusTest, rejectedIfPrometheusIsDisabled) TEST_F(WebServerPrometheusTest, validResponse) { + uint32_t webServerPort = tests::util::generateFreePort(); auto& testCounter = PrometheusService::counterInt("test_counter", util::prometheus::Labels()); ++testCounter; auto e = std::make_shared(); - Config const serverConfig{boost::json::parse(JSONServerConfigWithAdminPassword)}; + Config const serverConfig{parse(JSONServerConfigWithAdminPassword(webServerPort))}; auto server = makeServerSync(serverConfig, ctx, std::nullopt, dosGuard, e); auto const res = HttpSyncClient::syncGet( "localhost", - "8888", + std::to_string(webServerPort), "", "/metrics", {WebHeader(