Skip to content

Commit 4b1020e

Browse files
Add unit-tests for HttpServerConnection and HTTP message classes
1 parent 3b9336a commit 4b1020e

9 files changed

+1186
-0
lines changed

test/CMakeLists.txt

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,10 @@ set(base_test_SOURCES
8787
icinga-notification.cpp
8888
icinga-perfdata.cpp
8989
methods-pluginnotificationtask.cpp
90+
remote-sslcert-fixture.cpp
9091
remote-configpackageutility.cpp
92+
remote-httpserverconnection.cpp
93+
remote-httpmessage.cpp
9194
remote-url.cpp
9295
${base_OBJS}
9396
$<TARGET_OBJECTS:config>
@@ -271,6 +274,31 @@ add_boost_test(base
271274
icinga_perfdata/parse_edgecases
272275
icinga_perfdata/empty_warn_crit_min_max
273276
methods_pluginnotificationtask/truncate_long_output
277+
remote_certs/setup_certs
278+
remote_certs/cleanup_certs
279+
remote_httpmessage/request_parse
280+
remote_httpmessage/request_params
281+
remote_httpmessage/response_flush_nothrow
282+
remote_httpmessage/response_flush_throw
283+
remote_httpmessage/response_write_empty
284+
remote_httpmessage/response_write_fixed
285+
remote_httpmessage/response_write_chunked
286+
remote_httpmessage/response_sendjsonbody
287+
remote_httpmessage/response_sendjsonerror
288+
remote_httpmessage/response_sendFile
289+
remote_httpserverconnection/expect_100_continue
290+
remote_httpserverconnection/bad_request
291+
remote_httpserverconnection/error_access_control
292+
remote_httpserverconnection/error_accept_header
293+
remote_httpserverconnection/error_authenticate_cn
294+
remote_httpserverconnection/error_authenticate_passwd
295+
remote_httpserverconnection/error_authenticate_wronguser
296+
remote_httpserverconnection/error_authenticate_wrongpasswd
297+
remote_httpserverconnection/reuse_connection
298+
remote_httpserverconnection/wg_abort
299+
remote_httpserverconnection/client_shutdown
300+
remote_httpserverconnection/handler_throw
301+
remote_httpserverconnection/liveness_disconnect
274302
remote_configpackageutility/ValidateName
275303
remote_url/id_and_path
276304
remote_url/parameters
@@ -279,6 +307,44 @@ add_boost_test(base
279307
remote_url/illegal_legal_strings
280308
)
281309

310+
if(BUILD_TESTING)
311+
set_tests_properties(
312+
base-remote_httpmessage/request_parse
313+
base-remote_httpmessage/request_params
314+
base-remote_httpmessage/response_flush_nothrow
315+
base-remote_httpmessage/response_flush_throw
316+
base-remote_httpmessage/response_write_empty
317+
base-remote_httpmessage/response_write_fixed
318+
base-remote_httpmessage/response_write_chunked
319+
base-remote_httpmessage/response_sendjsonbody
320+
base-remote_httpmessage/response_sendjsonerror
321+
base-remote_httpmessage/response_sendFile
322+
base-remote_httpserverconnection/expect_100_continue
323+
base-remote_httpserverconnection/bad_request
324+
base-remote_httpserverconnection/error_access_control
325+
base-remote_httpserverconnection/error_accept_header
326+
base-remote_httpserverconnection/error_authenticate_cn
327+
base-remote_httpserverconnection/error_authenticate_passwd
328+
base-remote_httpserverconnection/error_authenticate_wronguser
329+
base-remote_httpserverconnection/error_authenticate_wrongpasswd
330+
base-remote_httpserverconnection/reuse_connection
331+
base-remote_httpserverconnection/wg_abort
332+
base-remote_httpserverconnection/client_shutdown
333+
base-remote_httpserverconnection/handler_throw
334+
base-remote_httpserverconnection/liveness_disconnect
335+
PROPERTIES FIXTURES_REQUIRED ssl_certs)
336+
337+
set_tests_properties(
338+
base-remote_certs/setup_certs
339+
PROPERTIES FIXTURES_SETUP ssl_certs
340+
)
341+
342+
set_tests_properties(
343+
base-remote_certs/cleanup_certs
344+
PROPERTIES FIXTURES_CLEANUP ssl_certs
345+
)
346+
endif()
347+
282348
if(ICINGA2_WITH_LIVESTATUS)
283349
set(livestatus_test_SOURCES
284350
icingaapplication-fixture.cpp
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/* Icinga 2 | (c) 2025 Icinga GmbH | GPLv2+ */
2+
3+
#ifndef CONFIGURATION_FIXTURE_H
4+
#define CONFIGURATION_FIXTURE_H
5+
6+
#include "base/configuration.hpp"
7+
#include <boost/filesystem.hpp>
8+
#include <BoostTestTargetConfig.h>
9+
10+
namespace icinga {
11+
12+
struct ConfigurationDataDirFixture
13+
{
14+
ConfigurationDataDirFixture()
15+
: m_DataDir(boost::filesystem::current_path() / "data"), m_PrevDataDir(Configuration::DataDir.GetData())
16+
{
17+
Configuration::DataDir = m_DataDir.string();
18+
boost::filesystem::create_directories(m_DataDir);
19+
}
20+
21+
~ConfigurationDataDirFixture()
22+
{
23+
boost::filesystem::remove_all(m_DataDir);
24+
Configuration::DataDir = m_PrevDataDir.string();
25+
}
26+
27+
boost::filesystem::path m_DataDir;
28+
boost::filesystem::path m_PrevDataDir;
29+
};
30+
31+
struct ConfigurationCacheDirFixture
32+
{
33+
ConfigurationCacheDirFixture()
34+
: m_CacheDir(boost::filesystem::current_path() / "data"), m_PrevCacheDir(Configuration::CacheDir.GetData())
35+
{
36+
Configuration::CacheDir = m_CacheDir.string();
37+
boost::filesystem::create_directories(m_CacheDir);
38+
}
39+
40+
~ConfigurationCacheDirFixture()
41+
{
42+
boost::filesystem::remove_all(m_CacheDir);
43+
Configuration::CacheDir = m_PrevCacheDir.string();
44+
}
45+
46+
boost::filesystem::path m_CacheDir;
47+
boost::filesystem::path m_PrevCacheDir;
48+
};
49+
50+
} // namespace icinga
51+
52+
#endif // CONFIGURATION_FIXTURE_H

test/base-testloggerfixture.hpp

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
/* Icinga 2 | (c) 2025 Icinga GmbH | GPLv2+ */
2+
3+
#ifndef TEST_LOGGER_FIXTURE_H
4+
#define TEST_LOGGER_FIXTURE_H
5+
6+
#include "base/logger.hpp"
7+
#include <boost/regex.hpp>
8+
#include <boost/test/test_tools.hpp>
9+
#include <BoostTestTargetConfig.h>
10+
#include <future>
11+
12+
namespace icinga {
13+
14+
class TestLogger : public Logger
15+
{
16+
public:
17+
DECLARE_PTR_TYPEDEFS(TestLogger);
18+
19+
struct Expect
20+
{
21+
std::string pattern;
22+
std::promise<bool> prom;
23+
std::shared_future<bool> crutch;
24+
};
25+
26+
auto ExpectLogPattern(const std::string& pattern,
27+
const std::chrono::milliseconds& timeout = std::chrono::seconds(0))
28+
{
29+
std::unique_lock lock(m_Mutex);
30+
for (const auto& logEntry : m_LogEntries) {
31+
if (boost::regex_match(logEntry.Message.GetData(), boost::regex(pattern))) {
32+
return boost::test_tools::assertion_result{true};
33+
}
34+
}
35+
36+
if (timeout == std::chrono::seconds(0)) {
37+
return boost::test_tools::assertion_result{false};
38+
}
39+
40+
auto prom = std::promise<bool>();
41+
auto fut = prom.get_future().share();
42+
m_Expects.emplace_back(Expect{pattern, std::move(prom), fut});
43+
lock.unlock();
44+
45+
auto status = fut.wait_for(timeout);
46+
boost::test_tools::assertion_result ret{status == std::future_status::ready && fut.get()};
47+
ret.message() << "Pattern \"" << pattern << "\" in log within " << timeout.count() << "ms";
48+
return ret;
49+
}
50+
51+
private:
52+
void ProcessLogEntry(const LogEntry& entry) override
53+
{
54+
std::unique_lock lock(m_Mutex);
55+
m_LogEntries.push_back(entry);
56+
57+
auto it = std::remove_if(m_Expects.begin(), m_Expects.end(), [&entry](Expect& expect) {
58+
if (boost::regex_match(entry.Message.GetData(), boost::regex(expect.pattern))) {
59+
expect.prom.set_value(true);
60+
return true;
61+
}
62+
return false;
63+
});
64+
m_Expects.erase(it, m_Expects.end());
65+
}
66+
67+
void Flush() override {}
68+
69+
std::mutex m_Mutex;
70+
std::vector<Expect> m_Expects;
71+
std::vector<LogEntry> m_LogEntries;
72+
};
73+
74+
struct TestLoggerFixture
75+
{
76+
TestLoggerFixture()
77+
{
78+
testLogger->SetSeverity(testLogger->SeverityToString(LogDebug));
79+
testLogger->Activate(true);
80+
testLogger->SetActive(true);
81+
}
82+
83+
auto ExpectLogPattern(const std::string& pattern,
84+
const std::chrono::milliseconds& timeout = std::chrono::seconds(0))
85+
{
86+
return testLogger->ExpectLogPattern(pattern, timeout);
87+
}
88+
89+
TestLogger::Ptr testLogger = new TestLogger;
90+
};
91+
92+
} // namespace icinga
93+
94+
#endif // TEST_LOGGER_FIXTURE_H

test/base-tlsstream-fixture.hpp

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
/* Icinga 2 | (c) 2025 Icinga GmbH | GPLv2+ */
2+
3+
#ifndef TLSSTREAM_FIXTURE_H
4+
#define TLSSTREAM_FIXTURE_H
5+
6+
#include "base/io-engine.hpp"
7+
#include "base/tlsstream.hpp"
8+
#include "test/remote-sslcert-fixture.hpp"
9+
#include <BoostTestTargetConfig.h>
10+
#include <future>
11+
12+
namespace icinga {
13+
14+
/**
15+
* Creates a pair of TLS Streams on a random unused port.
16+
*/
17+
struct TlsStreamFixture : CertificateFixture
18+
{
19+
TlsStreamFixture()
20+
{
21+
using namespace boost::asio::ip;
22+
using handshake_type = boost::asio::ssl::stream_base::handshake_type;
23+
24+
auto serverCert = EnsureCertFor("server");
25+
auto clientCert = EnsureCertFor("client");
26+
27+
auto& io = IoEngine::Get().GetIoContext();
28+
29+
clientSslContext = SetupSslContext(clientCert.crtFile, clientCert.keyFile, m_CaCrtFile.string(), "",
30+
DEFAULT_TLS_CIPHERS, DEFAULT_TLS_PROTOCOLMIN, DebugInfo());
31+
client = Shared<AsioTlsStream>::Make(io, *clientSslContext);
32+
33+
serverSslContext = SetupSslContext(serverCert.crtFile, serverCert.keyFile, m_CaCrtFile.string(), "",
34+
DEFAULT_TLS_CIPHERS, DEFAULT_TLS_PROTOCOLMIN, DebugInfo());
35+
server = Shared<AsioTlsStream>::Make(io, *serverSslContext);
36+
37+
std::promise<void> p;
38+
39+
tcp::acceptor acceptor{io, tcp::endpoint{address_v4::loopback(), 0}};
40+
acceptor.listen();
41+
acceptor.async_accept(server->lowest_layer(), [&](const boost::system::error_code& ec) {
42+
if (ec) {
43+
BOOST_TEST_MESSAGE("Server Accept Error: " + ec.message());
44+
return;
45+
}
46+
server->next_layer().async_handshake(handshake_type::server, [&](const boost::system::error_code& ec) {
47+
if (ec) {
48+
BOOST_TEST_MESSAGE("Server Handshake Error: " + ec.message());
49+
p.set_exception(std::make_exception_ptr(boost::system::system_error{ec}));
50+
return;
51+
}
52+
53+
p.set_value();
54+
});
55+
});
56+
57+
boost::system::error_code ec;
58+
if (client->lowest_layer().connect(acceptor.local_endpoint(), ec)) {
59+
BOOST_TEST_MESSAGE("Client Connect error: " + ec.message());
60+
BOOST_THROW_EXCEPTION(boost::system::system_error{ec});
61+
}
62+
63+
if (client->next_layer().handshake(handshake_type::client, ec)) {
64+
BOOST_TEST_MESSAGE("Client Handshake error: " + ec.message());
65+
BOOST_THROW_EXCEPTION(boost::system::system_error{ec});
66+
}
67+
68+
if (!client->next_layer().IsVerifyOK()) {
69+
throw std::runtime_error{"Verfiy failed for connection."};
70+
}
71+
72+
auto f = p.get_future();
73+
f.wait();
74+
f.get();
75+
}
76+
77+
auto Shutdown(const Shared<AsioTlsStream>::Ptr& stream, std::optional<boost::asio::yield_context> yc = {})
78+
{
79+
boost::system::error_code ec;
80+
if (yc) {
81+
stream->next_layer().async_shutdown((*yc)[ec]);
82+
} else {
83+
stream->next_layer().shutdown(ec);
84+
}
85+
#if BOOST_VERSION < 107000
86+
/* On boost versions < 1.70, the end-of-file condition was propagated as an error,
87+
* even in case of a successful shutdown. This is information can be found in the
88+
* changelog for the boost Asio 1.14.0 / Boost 1.70 release.
89+
*/
90+
if (ec == boost::asio::error::eof) {
91+
BOOST_TEST_MESSAGE("Shutdown completed successfully with 'boost::asio::error::eof'.");
92+
return boost::test_tools::assertion_result{true};
93+
}
94+
#endif
95+
boost::test_tools::assertion_result ret{!ec};
96+
ret.message() << "Error: " << ec.message();
97+
return ret;
98+
}
99+
100+
Shared<boost::asio::ssl::context>::Ptr clientSslContext;
101+
Shared<AsioTlsStream>::Ptr client;
102+
103+
Shared<boost::asio::ssl::context>::Ptr serverSslContext;
104+
Shared<AsioTlsStream>::Ptr server;
105+
};
106+
107+
} // namespace icinga
108+
109+
#endif // TLSSTREAM_FIXTURE_H

test/icingaapplication-fixture.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ void IcingaApplicationFixture::InitIcingaApplication()
2626

2727
IcingaApplicationFixture::~IcingaApplicationFixture()
2828
{
29+
BOOST_TEST_MESSAGE("Uninitializing Application...");
30+
Application::UninitializeBase();
2931
IcingaApplication::GetInstance().reset();
3032
}
3133

0 commit comments

Comments
 (0)