mirror of
https://github.com/Icinga/icinga2.git
synced 2025-08-15 22:58:16 +02:00
280 lines
7.9 KiB
C++
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()
|