#include "data/LedgerCacheInterface.hpp" #include "util/AssignRandomPort.hpp" #include "util/MockLedgerCache.hpp" #include "util/MockPrometheus.hpp" #include "util/TestHttpClient.hpp" #include "util/TestWebSocketClient.hpp" #include "util/TmpFile.hpp" #include "util/config/Array.hpp" #include "util/config/ConfigDefinition.hpp" #include "util/config/ConfigFileJson.hpp" #include "util/config/ConfigValue.hpp" #include "util/config/Types.hpp" #include "util/prometheus/Label.hpp" #include "util/prometheus/Prometheus.hpp" #include "web/AdminVerificationStrategy.hpp" #include "web/Server.hpp" #include "web/dosguard/DOSGuard.hpp" #include "web/dosguard/DOSGuardInterface.hpp" #include "web/dosguard/IntervalSweepHandler.hpp" #include "web/dosguard/Weights.hpp" #include "web/dosguard/WhitelistHandler.hpp" #include "web/interface/ConnectionBase.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace util; using namespace util::config; using namespace web::impl; using namespace web; static boost::json::value generateJSONWithDynamicPort(std::string_view port) { return boost::json::parse( 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 ) ); } static boost::json::value generateJSONDataOverload(std::string_view port) { return boost::json::parse( 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 ) ); } inline static ClioConfigDefinition getParseServerConfig(boost::json::value val) { ConfigFileJson const jsonVal{val.as_object()}; auto config = ClioConfigDefinition{ {"server.ip", ConfigValue{ConfigType::String}}, {"server.port", ConfigValue{ConfigType::Integer}}, {"server.admin_password", ConfigValue{ConfigType::String}.optional()}, {"server.local_admin", ConfigValue{ConfigType::Boolean}.optional()}, {"server.proxy.ips.[]", Array{ConfigValue{ConfigType::String}}}, {"server.proxy.tokens.[]", Array{ConfigValue{ConfigType::String}}}, {"server.ws_max_sending_queue_size", ConfigValue{ConfigType::Integer}.defaultValue(1500)}, {"log.tag_style", ConfigValue{ConfigType::String}.defaultValue("uint")}, {"dos_guard.max_fetches", ConfigValue{ConfigType::Integer}}, {"dos_guard.sweep_interval", ConfigValue{ConfigType::Integer}}, {"dos_guard.max_connections", ConfigValue{ConfigType::Integer}}, {"dos_guard.max_requests", ConfigValue{ConfigType::Integer}}, {"dos_guard.whitelist.[]", Array{ConfigValue{ConfigType::String}.optional()}}, {"ssl_key_file", ConfigValue{ConfigType::String}.optional()}, {"ssl_cert_file", ConfigValue{ConfigType::String}.optional()}, }; auto const errors = config.parse(jsonVal); [&]() { ASSERT_FALSE(errors.has_value()); }(); return config; }; struct WebServerTest : public virtual ::testing::Test { ~WebServerTest() override { work_.reset(); ctx.stop(); if (runner_->joinable()) runner_->join(); } WebServerTest() { work_.emplace(boost::asio::make_work_guard(ctx)); // make sure ctx does not stop on its own runner_.emplace([this] { ctx.run(); }); } boost::json::value addSslConfig(boost::json::value config) const { config.as_object()["ssl_key_file"] = sslKeyFile.path; config.as_object()["ssl_cert_file"] = sslCertFile.path; return config; } // this ctx is for dos timer boost::asio::io_context ctxSync; std::string const port = std::to_string(tests::util::generateFreePort()); ClioConfigDefinition cfg{getParseServerConfig(generateJSONWithDynamicPort(port))}; dosguard::WhitelistHandler whitelistHandler{cfg}; dosguard::Weights dosguardWeights{1, {}}; dosguard::DOSGuard dosGuard{cfg, whitelistHandler, dosguardWeights}; dosguard::IntervalSweepHandler sweepHandler{cfg, ctxSync, dosGuard}; ClioConfigDefinition cfgOverload{getParseServerConfig(generateJSONDataOverload(port))}; dosguard::WhitelistHandler whitelistHandlerOverload{cfgOverload}; dosguard::DOSGuard dosGuardOverload{cfgOverload, whitelistHandlerOverload, dosguardWeights}; dosguard::IntervalSweepHandler sweepHandlerOverload{cfgOverload, ctxSync, dosGuardOverload}; // this ctx is for http server boost::asio::io_context ctx; TmpFile sslCertFile{tests::sslCertFile()}; TmpFile sslKeyFile{tests::sslKeyFile()}; private: std::optional> work_; std::optional runner_; }; class EchoExecutor { public: void operator()(std::string const& reqStr, std::shared_ptr const& ws) { ws->send(std::string(reqStr), http::status::ok); } void operator()( boost::beast::error_code /* ec */, std::shared_ptr const& /* ws */ ) { } }; class ExceptionExecutor { public: void operator()(std::string const& /* req */, std::shared_ptr const& /* ws */) { throw std::runtime_error("MyError"); } void operator()( boost::beast::error_code /* ec */, std::shared_ptr const& /* ws */ ) { } }; namespace { template std::shared_ptr> makeServerSync( util::config::ClioConfigDefinition const& config, boost::asio::io_context& ioc, web::dosguard::DOSGuardInterface& dosGuard, std::shared_ptr const& handler, std::reference_wrapper cache ) { return web::makeHttpServer(config, ioc, dosGuard, handler, cache); } } // namespace TEST_F(WebServerTest, Http) { auto cache = MockLedgerCache(); auto const e = std::make_shared(); auto const server = makeServerSync(cfg, ctx, dosGuard, e, cache); auto const [status, res] = HttpSyncClient::post("localhost", port, R"JSON({"Hello":1})JSON"); EXPECT_EQ(res, R"JSON({"Hello":1})JSON"); EXPECT_EQ(status, boost::beast::http::status::ok); } TEST_F(WebServerTest, Ws) { auto cache = MockLedgerCache(); auto e = std::make_shared(); auto const server = makeServerSync(cfg, ctx, dosGuard, e, cache); WebSocketSyncClient wsClient; wsClient.connect("localhost", port); auto const res = wsClient.syncPost(R"JSON({"Hello":1})JSON"); EXPECT_EQ(res, R"JSON({"Hello":1})JSON"); wsClient.disconnect(); } TEST_F(WebServerTest, HttpInternalError) { auto cache = MockLedgerCache(); auto const e = std::make_shared(); auto const server = makeServerSync(cfg, ctx, dosGuard, e, cache); auto const [status, res] = HttpSyncClient::post("localhost", port, R"JSON({})JSON"); EXPECT_EQ( res, R"JSON({"error":"internal","error_code":73,"error_message":"Internal error.","status":"error","type":"response"})JSON" ); EXPECT_EQ(status, boost::beast::http::status::internal_server_error); } TEST_F(WebServerTest, WsInternalError) { auto cache = MockLedgerCache(); auto e = std::make_shared(); auto const server = makeServerSync(cfg, ctx, dosGuard, e, cache); WebSocketSyncClient wsClient; wsClient.connect("localhost", port); auto const res = wsClient.syncPost(R"JSON({"id":"id1"})JSON"); wsClient.disconnect(); EXPECT_EQ( res, R"JSON({"error":"internal","error_code":73,"error_message":"Internal error.","status":"error","type":"response","id":"id1","request":{"id":"id1"}})JSON" ); } TEST_F(WebServerTest, WsInternalErrorNotJson) { auto cache = MockLedgerCache(); auto e = std::make_shared(); auto const server = makeServerSync(cfg, ctx, dosGuard, e, cache); WebSocketSyncClient wsClient; wsClient.connect("localhost", port); auto const res = wsClient.syncPost("not json"); wsClient.disconnect(); EXPECT_EQ( res, R"JSON({"error":"internal","error_code":73,"error_message":"Internal error.","status":"error","type":"response","request":"not json"})JSON" ); } TEST_F(WebServerTest, IncompleteSslConfig) { auto const e = std::make_shared(); auto jsonConfig = generateJSONWithDynamicPort(port); jsonConfig.as_object()["ssl_key_file"] = sslKeyFile.path; auto cache = MockLedgerCache(); auto const server = makeServerSync(getParseServerConfig(jsonConfig), ctx, dosGuard, e, cache); EXPECT_EQ(server, nullptr); } TEST_F(WebServerTest, WrongSslConfig) { auto const e = std::make_shared(); auto jsonConfig = generateJSONWithDynamicPort(port); jsonConfig.as_object()["ssl_key_file"] = sslKeyFile.path; jsonConfig.as_object()["ssl_cert_file"] = "wrong_path"; auto cache = MockLedgerCache(); auto const server = makeServerSync(getParseServerConfig(jsonConfig), ctx, dosGuard, e, cache); EXPECT_EQ(server, nullptr); } TEST_F(WebServerTest, Https) { auto cache = MockLedgerCache(); auto const e = std::make_shared(); cfg = getParseServerConfig(addSslConfig(generateJSONWithDynamicPort(port))); auto const server = makeServerSync(cfg, ctx, dosGuard, e, cache); auto const res = HttpsSyncClient::syncPost("localhost", port, R"JSON({"Hello":1})JSON"); EXPECT_EQ(res, R"JSON({"Hello":1})JSON"); } TEST_F(WebServerTest, Wss) { auto cache = MockLedgerCache(); auto e = std::make_shared(); cfg = getParseServerConfig(addSslConfig(generateJSONWithDynamicPort(port))); auto server = makeServerSync(cfg, ctx, dosGuard, e, cache); WebServerSslSyncClient wsClient; wsClient.connect("localhost", port); auto const res = wsClient.syncPost(R"JSON({"Hello":1})JSON"); EXPECT_EQ(res, R"JSON({"Hello":1})JSON"); wsClient.disconnect(); } TEST_F(WebServerTest, HttpPayloadOverload) { std::string const s100(100, 'a'); auto cache = MockLedgerCache(); auto const e = std::make_shared(); auto server = makeServerSync(cfg, ctx, dosGuardOverload, e, cache); auto const [status, res] = HttpSyncClient::post("localhost", port, fmt::format(R"JSON({{"payload":"{}"}})JSON", s100)); EXPECT_EQ( res, R"JSON({"payload":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","warning":"load","warnings":[{"id":2003,"message":"You are about to be rate limited"}]})JSON" ); EXPECT_EQ(status, boost::beast::http::status::ok); } TEST_F(WebServerTest, WsPayloadOverload) { std::string const s100(100, 'a'); auto cache = MockLedgerCache(); auto const e = std::make_shared(); auto server = makeServerSync(cfg, ctx, dosGuardOverload, e, cache); WebSocketSyncClient wsClient; wsClient.connect("localhost", port); auto const res = wsClient.syncPost(fmt::format(R"JSON({{"payload":"{}"}})JSON", s100)); wsClient.disconnect(); EXPECT_EQ( res, R"JSON({"payload":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","warning":"load","warnings":[{"id":2003,"message":"You are about to be rate limited"}]})JSON" ); } TEST_F(WebServerTest, WsTooManyConnection) { auto cache = MockLedgerCache(); auto const e = std::make_shared(); auto server = makeServerSync(cfg, ctx, dosGuardOverload, e, cache); // max connection is 2, exception should happen when the third connection is made WebSocketSyncClient wsClient1; wsClient1.connect("localhost", port); WebSocketSyncClient wsClient2; wsClient2.connect("localhost", port); bool exceptionThrown = false; try { WebSocketSyncClient wsClient3; wsClient3.connect("localhost", port); } catch (boost::system::system_error const& ex) { exceptionThrown = true; EXPECT_EQ(ex.code(), boost::beast::websocket::error::upgrade_declined); } wsClient1.disconnect(); wsClient2.disconnect(); EXPECT_TRUE(exceptionThrown); } TEST_F(WebServerTest, HealthCheck) { auto cache = MockLedgerCache(); auto e = std::make_shared(); // request handled before we get to executor auto const server = makeServerSync(cfg, ctx, dosGuard, e, cache); auto const [status, res] = HttpSyncClient::get("localhost", port, "", "/health"); EXPECT_FALSE(res.empty()); EXPECT_EQ(status, boost::beast::http::status::ok); } TEST_F(WebServerTest, CacheStateCheckWithLoadedCache) { auto cache = MockLedgerCache(); EXPECT_CALL(cache, isFull()).WillRepeatedly(testing::Return(true)); auto e = std::make_shared(); // request handled before we get to executor auto const server = makeServerSync(cfg, ctx, dosGuard, e, cache); auto const [status, res] = HttpSyncClient::get("localhost", port, "", "/cache_state"); EXPECT_FALSE(res.empty()); EXPECT_EQ(status, boost::beast::http::status::ok); } TEST_F(WebServerTest, CacheStateCheckWithoutLoadedCache) { auto cache = MockLedgerCache(); EXPECT_CALL(cache, isFull()).WillRepeatedly(testing::Return(false)); auto e = std::make_shared(); // request handled before we get to executor auto const server = makeServerSync(cfg, ctx, dosGuard, e, cache); auto const [status, res] = HttpSyncClient::get("localhost", port, "", "/cache_state"); EXPECT_FALSE(res.empty()); EXPECT_EQ(status, boost::beast::http::status::service_unavailable); } TEST_F(WebServerTest, GetOtherThanHealthCheck) { auto cache = MockLedgerCache(); auto e = std::make_shared(); // request handled before we get to executor auto const server = makeServerSync(cfg, ctx, dosGuard, e, cache); auto const [status, res] = HttpSyncClient::get("localhost", port, "", "/"); EXPECT_FALSE(res.empty()); EXPECT_EQ(status, boost::beast::http::status::bad_request); } namespace { std::string jsonServerConfigWithAdminPassword(uint32_t const port) { return fmt::format( R"JSON({{ "server": {{ "ip": "0.0.0.0", "port": {}, "admin_password": "secret" }} }})JSON", port ); } std::string jsonServerConfigWithLocalAdmin(uint32_t const port) { return fmt::format( R"JSON({{ "server": {{ "ip": "0.0.0.0", "port": {}, "local_admin": true }} }})JSON", port ); } 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 ); } 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 constexpr auto kSECRET_SHA256 = "2bb80d537b1da3e38bd30361aa855686bde0eacd7162fef6a25fe97bf527a25b"; } // namespace class AdminCheckExecutor { public: void operator()(std::string const& reqStr, std::shared_ptr const& ws) { auto response = fmt::format("{} {}", reqStr, ws->isAdmin() ? "admin" : "user"); ws->send(std::move(response), http::status::ok); } void operator()( boost::beast::error_code /* ec */, std::shared_ptr const& /* ws */ ) { } }; struct WebServerAdminTestParams { std::string config; std::vector headers; std::string expectedResponse; }; inline static ClioConfigDefinition getParseAdminServerConfig(boost::json::value val) { ConfigFileJson const jsonVal{val.as_object()}; auto config = ClioConfigDefinition{ {"server.ip", ConfigValue{ConfigType::String}}, {"server.port", ConfigValue{ConfigType::Integer}}, {"server.admin_password", ConfigValue{ConfigType::String}.optional()}, {"server.local_admin", ConfigValue{ConfigType::Boolean}.optional()}, {"server.processing_policy", ConfigValue{ConfigType::String}.defaultValue("parallel")}, {"server.proxy.ips.[]", Array{ConfigValue{ConfigType::String}}}, {"server.proxy.tokens.[]", Array{ConfigValue{ConfigType::String}}}, {"server.parallel_requests_limit", ConfigValue{ConfigType::Integer}.optional()}, {"server.ws_max_sending_queue_size", ConfigValue{ConfigType::Integer}.defaultValue(1500)}, {"ssl_cert_file", ConfigValue{ConfigType::String}.optional()}, {"ssl_key_file", ConfigValue{ConfigType::String}.optional()}, {"prometheus.enabled", ConfigValue{ConfigType::Boolean}.defaultValue(true)}, {"prometheus.compress_reply", ConfigValue{ConfigType::Boolean}.defaultValue(true)}, {"log.tag_style", ConfigValue{ConfigType::String}.defaultValue("uint")} }; auto const errors = config.parse(jsonVal); [&]() { ASSERT_FALSE(errors.has_value()); }(); return config; }; class WebServerAdminTest : public WebServerTest, public ::testing::WithParamInterface {}; TEST_P(WebServerAdminTest, WsAdminCheck) { auto cache = MockLedgerCache(); auto e = std::make_shared(); ClioConfigDefinition const serverConfig{ getParseAdminServerConfig(boost::json::parse(GetParam().config)) }; auto server = makeServerSync(serverConfig, ctx, dosGuardOverload, e, cache); WebSocketSyncClient wsClient; uint32_t const webServerPort = serverConfig.get("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(); EXPECT_EQ(res, fmt::format("{} {}", request, GetParam().expectedResponse)); } TEST_P(WebServerAdminTest, HttpAdminCheck) { auto cache = MockLedgerCache(); auto const e = std::make_shared(); ClioConfigDefinition const serverConfig{ getParseAdminServerConfig(boost::json::parse(GetParam().config)) }; auto server = makeServerSync(serverConfig, ctx, dosGuardOverload, e, cache); std::string const request = "Why hello"; uint32_t const webServerPort = serverConfig.get("server.port"); auto const [status, res] = HttpSyncClient::post( "localhost", std::to_string(webServerPort), request, GetParam().headers ); EXPECT_EQ(res, fmt::format("{} {}", request, GetParam().expectedResponse)); EXPECT_EQ(status, boost::beast::http::status::ok); } INSTANTIATE_TEST_CASE_P( WebServerAdminTestsSuit, WebServerAdminTest, ::testing::Values( WebServerAdminTestParams{ .config = jsonServerConfigWithAdminPassword(tests::util::generateFreePort()), .headers = {}, .expectedResponse = "user" }, WebServerAdminTestParams{ .config = jsonServerConfigWithAdminPassword(tests::util::generateFreePort()), .headers = {WebHeader(http::field::authorization, "")}, .expectedResponse = "user" }, WebServerAdminTestParams{ .config = jsonServerConfigWithAdminPassword(tests::util::generateFreePort()), .headers = {WebHeader(http::field::authorization, "s")}, .expectedResponse = "user" }, WebServerAdminTestParams{ .config = jsonServerConfigWithAdminPassword(tests::util::generateFreePort()), .headers = {WebHeader(http::field::authorization, kSECRET_SHA256)}, .expectedResponse = "user" }, WebServerAdminTestParams{ .config = jsonServerConfigWithAdminPassword(tests::util::generateFreePort()), .headers = {WebHeader( http::field::authorization, fmt::format( "{}{}", PasswordAdminVerificationStrategy::kPASSWORD_PREFIX, kSECRET_SHA256 ) )}, .expectedResponse = "admin" }, WebServerAdminTestParams{ .config = jsonServerConfigWithBothAdminPasswordAndLocalAdminFalse( tests::util::generateFreePort() ), .headers = {WebHeader(http::field::authorization, kSECRET_SHA256)}, .expectedResponse = "user" }, WebServerAdminTestParams{ .config = jsonServerConfigWithBothAdminPasswordAndLocalAdminFalse( tests::util::generateFreePort() ), .headers = {WebHeader( http::field::authorization, fmt::format( "{}{}", PasswordAdminVerificationStrategy::kPASSWORD_PREFIX, kSECRET_SHA256 ) )}, .expectedResponse = "admin" }, WebServerAdminTestParams{ .config = jsonServerConfigWithAdminPassword(tests::util::generateFreePort()), .headers = {WebHeader( http::field::authentication_info, fmt::format( "{}{}", PasswordAdminVerificationStrategy::kPASSWORD_PREFIX, kSECRET_SHA256 ) )}, .expectedResponse = "user" }, WebServerAdminTestParams{ .config = jsonServerConfigWithLocalAdmin(tests::util::generateFreePort()), .headers = {}, .expectedResponse = "admin" }, WebServerAdminTestParams{ .config = jsonServerConfigWithNoSpecifiedAdmin(tests::util::generateFreePort()), .headers = {}, .expectedResponse = "admin" } ) ); TEST_F(WebServerTest, AdminErrorCfgTestBothAdminPasswordAndLocalAdminSet) { uint32_t webServerPort = tests::util::generateFreePort(); std::string const jsonServerConfigWithBothAdminPasswordAndLocalAdmin = fmt::format( R"JSON({{ "server":{{ "ip": "0.0.0.0", "port": {}, "admin_password": "secret", "local_admin": true }} }})JSON", webServerPort ); auto const e = std::make_shared(); ClioConfigDefinition const serverConfig{getParseAdminServerConfig( boost::json::parse(jsonServerConfigWithBothAdminPasswordAndLocalAdmin) )}; MockLedgerCache cache; EXPECT_THROW( web::makeHttpServer(serverConfig, ctx, dosGuardOverload, e, cache), std::logic_error ); } TEST_F(WebServerTest, AdminErrorCfgTestBothAdminPasswordAndLocalAdminFalse) { uint32_t webServerPort = tests::util::generateFreePort(); std::string const jsonServerConfigWithNoAdminPasswordAndLocalAdminFalse = fmt::format( R"JSON({{ "server": {{ "ip": "0.0.0.0", "port": {}, "local_admin": false }} }})JSON", webServerPort ); auto const e = std::make_shared(); ClioConfigDefinition const serverConfig{getParseAdminServerConfig( boost::json::parse(jsonServerConfigWithNoAdminPasswordAndLocalAdminFalse) )}; MockLedgerCache cache; EXPECT_THROW( web::makeHttpServer(serverConfig, ctx, dosGuardOverload, e, cache), std::logic_error ); } struct WebServerPrometheusTest : util::prometheus::WithPrometheus, WebServerTest {}; TEST_F(WebServerPrometheusTest, rejectedWithoutAdminPassword) { auto cache = MockLedgerCache(); auto const e = std::make_shared(); uint32_t const webServerPort = tests::util::generateFreePort(); ClioConfigDefinition const serverConfig{getParseAdminServerConfig( boost::json::parse(jsonServerConfigWithAdminPassword(webServerPort)) )}; auto server = makeServerSync(serverConfig, ctx, dosGuard, e, cache); auto const [status, res] = HttpSyncClient::get("localhost", std::to_string(webServerPort), "", "/metrics"); EXPECT_EQ(res, "Only admin is allowed to collect metrics"); EXPECT_EQ(status, boost::beast::http::status::unauthorized); } struct WebServerPrometheusDisabledTest : util::prometheus::WithPrometheusDisabled, WebServerTest {}; TEST_F(WebServerPrometheusDisabledTest, rejectedIfPrometheusIsDisabled) { uint32_t webServerPort = tests::util::generateFreePort(); std::string const jsonServerConfigWithDisabledPrometheus = fmt::format( R"JSON({{ "server":{{ "ip": "0.0.0.0", "port": {}, "admin_password": "secret", "ws_max_sending_queue_size": 1500 }} }})JSON", webServerPort ); auto cache = MockLedgerCache(); auto const e = std::make_shared(); ClioConfigDefinition const serverConfig{ getParseAdminServerConfig(boost::json::parse(jsonServerConfigWithDisabledPrometheus)) }; auto server = makeServerSync(serverConfig, ctx, dosGuard, e, cache); auto const [status, res] = HttpSyncClient::get( "localhost", std::to_string(webServerPort), "", "/metrics", {WebHeader( http::field::authorization, fmt::format("{}{}", PasswordAdminVerificationStrategy::kPASSWORD_PREFIX, kSECRET_SHA256) )} ); EXPECT_EQ(res, "Prometheus is disabled in clio config"); EXPECT_EQ(status, boost::beast::http::status::forbidden); } TEST_F(WebServerPrometheusTest, validResponse) { auto cache = MockLedgerCache(); uint32_t const webServerPort = tests::util::generateFreePort(); auto& testCounter = PrometheusService::counterInt("test_counter", util::prometheus::Labels()); ++testCounter; auto const e = std::make_shared(); ClioConfigDefinition const serverConfig{getParseAdminServerConfig( boost::json::parse(jsonServerConfigWithAdminPassword(webServerPort)) )}; auto server = makeServerSync(serverConfig, ctx, dosGuard, e, cache); auto const [status, res] = HttpSyncClient::get( "localhost", std::to_string(webServerPort), "", "/metrics", {WebHeader( http::field::authorization, fmt::format("{}{}", PasswordAdminVerificationStrategy::kPASSWORD_PREFIX, kSECRET_SHA256) )} ); EXPECT_EQ(res, "# TYPE test_counter counter\ntest_counter 1\n\n"); EXPECT_EQ(status, boost::beast::http::status::ok); }