icinga2/test/remote-httpmessage.cpp

280 lines
7.9 KiB
C++

/* 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 <BoostTestTargetConfig.h>
#include <fstream>
using namespace icinga;
using namespace boost::beast;
BOOST_FIXTURE_TEST_SUITE(remote_httpmessage, TlsStreamFixture)
BOOST_AUTO_TEST_CASE(request_parse)
{
http::request<boost::beast::http::string_body> 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<Array>());
BOOST_REQUIRE(params->Get("test2").IsBoolean());
BOOST_REQUIRE(params->Get("test2"));
BOOST_REQUIRE(params->Get("test1").IsObjectType<Array>());
BOOST_REQUIRE(HttpUtility::GetLastParameter(params, "test1"));
BOOST_REQUIRE(params->Get("test3").IsObjectType<Array>());
BOOST_REQUIRE(HttpUtility::GetLastParameter(params, "test3"));
BOOST_REQUIRE(params->Get("test4").IsObjectType<Array>());
BOOST_REQUIRE(!HttpUtility::GetLastParameter(params, "test4"));
}
BOOST_AUTO_TEST_CASE(response_flush_nothrow)
{
auto& io = IoEngine::Get();
std::promise<void> 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<void> 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<const boost::system::system_error*>(&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<http::string_body> 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<http::string_body> 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<http::string_body> 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<http::string_body> 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<http::string_body> 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<http::string_body> 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()