/* Icinga 2 | (c) 2025 Icinga GmbH | GPLv2+ */ #include "base/base64.hpp" #include "base/json.hpp" #include "remote/httpmessage.hpp" #include "remote/httputility.hpp" #include "test/base-tlsstream-fixture.hpp" #include #include using namespace icinga; using namespace boost::beast; BOOST_FIXTURE_TEST_SUITE(remote_httpmessage, TlsStreamFixture) BOOST_AUTO_TEST_CASE(request_parse) { http::request requestOut; requestOut.method(http::verb::get); requestOut.target("https://localhost:5665/v1/test"); requestOut.set(http::field::authorization, "Basic " + Base64::Encode("invalid:invalid")); requestOut.set(http::field::accept, "application/json"); requestOut.set(http::field::connection, "close"); requestOut.body() = "test"; requestOut.prepare_payload(); auto& io = IoEngine::Get(); io.SpawnCoroutine(io.GetIoContext(), [this, &requestOut](boost::asio::yield_context yc) { boost::beast::flat_buffer buf; HttpRequest request(server); BOOST_REQUIRE_NO_THROW(request.ParseHeader(buf, yc)); for (auto& field : requestOut.base()) { BOOST_REQUIRE(request.count(field.name())); } BOOST_REQUIRE_NO_THROW(request.ParseBody(buf, yc)); BOOST_REQUIRE_EQUAL(request.body(), "test"); Shutdown(server, yc); }); http::write(*client, requestOut); client->flush(); Shutdown(client); } BOOST_AUTO_TEST_CASE(request_params) { HttpRequest request(client); request.body() = JsonEncode(new Dictionary{{"test1", false}, {"test2", true}}); request.target("https://localhost:1234/v1/test?test1=1&test3=0&test3=1&test4=0"); request.DecodeParams(); auto params = request.Params(); BOOST_REQUIRE(params); BOOST_REQUIRE(!params->Get("test2").IsObjectType()); BOOST_REQUIRE(params->Get("test2").IsBoolean()); BOOST_REQUIRE(params->Get("test2")); BOOST_REQUIRE(params->Get("test1").IsObjectType()); BOOST_REQUIRE(HttpUtility::GetLastParameter(params, "test1")); BOOST_REQUIRE(params->Get("test3").IsObjectType()); BOOST_REQUIRE(HttpUtility::GetLastParameter(params, "test3")); BOOST_REQUIRE(params->Get("test4").IsObjectType()); BOOST_REQUIRE(!HttpUtility::GetLastParameter(params, "test4")); } BOOST_AUTO_TEST_CASE(response_flush_nothrow) { auto& io = IoEngine::Get(); std::promise done; io.SpawnCoroutine(io.GetIoContext(), [this, &done](boost::asio::yield_context yc) { HttpResponse response(server); response.result(http::status::ok); server->lowest_layer().close(); boost::beast::error_code ec; BOOST_REQUIRE_NO_THROW(response.Flush(yc[ec])); BOOST_REQUIRE_EQUAL(ec, boost::system::errc::bad_file_descriptor); done.set_value(); }); auto status = done.get_future().wait_for(std::chrono::seconds(1)); BOOST_REQUIRE(status == std::future_status::ready); } BOOST_AUTO_TEST_CASE(response_flush_throw) { auto& io = IoEngine::Get(); std::promise done; io.SpawnCoroutine(io.GetIoContext(), [this, &done](boost::asio::yield_context yc) { HttpResponse response(server); response.result(http::status::ok); server->lowest_layer().close(); BOOST_REQUIRE_EXCEPTION(response.Flush(yc), std::exception, [](const std::exception& ex) { auto se = dynamic_cast(&ex); return se && se->code() == boost::system::errc::bad_file_descriptor; }); done.set_value(); }); auto status = done.get_future().wait_for(std::chrono::seconds(1)); BOOST_REQUIRE(status == std::future_status::ready); } BOOST_AUTO_TEST_CASE(response_write_empty) { auto& io = IoEngine::Get(); io.SpawnCoroutine(io.GetIoContext(), [this](boost::asio::yield_context yc) { HttpResponse response(server); response.result(http::status::ok); BOOST_REQUIRE_NO_THROW(response.Flush(yc)); Shutdown(server, yc); }); http::response_parser parser; flat_buffer buf; BOOST_REQUIRE_NO_THROW(http::read(*client, buf, parser)); Shutdown(client); BOOST_REQUIRE_EQUAL(parser.get().result(), http::status::ok); BOOST_REQUIRE_EQUAL(parser.get().chunked(), false); BOOST_REQUIRE_EQUAL(parser.get().body(), ""); } BOOST_AUTO_TEST_CASE(response_write_fixed) { auto& io = IoEngine::Get(); io.SpawnCoroutine(io.GetIoContext(), [this](boost::asio::yield_context yc) { HttpResponse response(server); response.result(http::status::ok); response.body() << "test"; BOOST_REQUIRE_NO_THROW(response.Flush(yc)); Shutdown(server, yc); }); http::response_parser parser; flat_buffer buf; BOOST_REQUIRE_NO_THROW(http::read(*client, buf, parser)); Shutdown(client); BOOST_REQUIRE_EQUAL(parser.get().result(), http::status::ok); BOOST_REQUIRE_EQUAL(parser.get().chunked(), false); BOOST_REQUIRE_EQUAL(parser.get().body(), "test"); } BOOST_AUTO_TEST_CASE(response_write_chunked) { auto& io = IoEngine::Get(); io.SpawnCoroutine(io.GetIoContext(), [this](boost::asio::yield_context yc) { HttpResponse response(server); response.result(http::status::ok); response.StartStreaming(); response.body() << "test" << 1; BOOST_REQUIRE_NO_THROW(response.Flush(yc)); response.body() << "test" << 2; BOOST_REQUIRE_NO_THROW(response.Flush(yc)); response.body() << "test" << 3; response.body().Finish(); BOOST_REQUIRE_NO_THROW(response.Flush(yc)); Shutdown(server, yc); }); http::response_parser parser; flat_buffer buf; BOOST_REQUIRE_NO_THROW(http::read(*client, buf, parser)); Shutdown(client); BOOST_REQUIRE_EQUAL(parser.get().result(), http::status::ok); BOOST_REQUIRE_EQUAL(parser.get().chunked(), true); BOOST_REQUIRE_EQUAL(parser.get().body(), "test1test2test3"); } BOOST_AUTO_TEST_CASE(response_sendjsonbody) { auto& io = IoEngine::Get(); io.SpawnCoroutine(io.GetIoContext(), [this](boost::asio::yield_context yc) { HttpResponse response(server); response.result(http::status::ok); HttpUtility::SendJsonBody(response, nullptr, new Dictionary{{"test", 1}}); BOOST_REQUIRE_NO_THROW(response.Flush(yc)); Shutdown(server, yc); }); http::response_parser parser; flat_buffer buf; BOOST_REQUIRE_NO_THROW(http::read(*client, buf, parser)); Shutdown(client); BOOST_REQUIRE_EQUAL(parser.get().result(), http::status::ok); BOOST_REQUIRE_EQUAL(parser.get().chunked(), false); Dictionary::Ptr body = JsonDecode(parser.get().body()); BOOST_REQUIRE_EQUAL(body->Get("test"), 1); } BOOST_AUTO_TEST_CASE(response_sendjsonerror) { auto& io = IoEngine::Get(); io.SpawnCoroutine(io.GetIoContext(), [this](boost::asio::yield_context yc) { HttpResponse response(server); // This has to be overwritten in SendJsonError. response.result(http::status::ok); HttpUtility::SendJsonError(response, nullptr, 404, "Not found."); BOOST_REQUIRE_NO_THROW(response.Flush(yc)); Shutdown(server, yc); }); http::response_parser parser; flat_buffer buf; BOOST_REQUIRE_NO_THROW(http::read(*client, buf, parser)); Shutdown(client); BOOST_REQUIRE_EQUAL(parser.get().result(), http::status::not_found); BOOST_REQUIRE_EQUAL(parser.get().chunked(), false); Dictionary::Ptr body = JsonDecode(parser.get().body()); BOOST_REQUIRE_EQUAL(body->Get("error"), 404); BOOST_REQUIRE_EQUAL(body->Get("status"), "Not found."); } BOOST_AUTO_TEST_CASE(response_sendFile) { auto& io = IoEngine::Get(); io.SpawnCoroutine(io.GetIoContext(), [this](boost::asio::yield_context yc) { HttpResponse response(server); response.result(http::status::ok); BOOST_REQUIRE_NO_THROW(response.SendFile(m_CaCrtFile.string(), yc)); BOOST_REQUIRE_NO_THROW(response.Flush(yc)); Shutdown(server, yc); }); http::response_parser parser; flat_buffer buf; BOOST_REQUIRE_NO_THROW(http::read(*client, buf, parser)); Shutdown(client); BOOST_REQUIRE_EQUAL(parser.get().result(), http::status::ok); BOOST_REQUIRE_EQUAL(parser.get().chunked(), false); std::ifstream fp(m_CaCrtFile.string(), std::ifstream::in | std::ifstream::binary); fp.exceptions(std::ifstream::badbit); std::stringstream ss; ss << fp.rdbuf(); BOOST_REQUIRE_EQUAL(ss.str(), parser.get().body()); } BOOST_AUTO_TEST_SUITE_END()