Skip to content

Commit d5cedf1

Browse files
Add unit-tests for HttpServerConnection and HTTP message classes
1 parent 75ed41a commit d5cedf1

9 files changed

+1162
-0
lines changed

test/CMakeLists.txt

Lines changed: 64 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,30 @@ 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_body_reader
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_httpserverconnection/expect_100_continue
289+
remote_httpserverconnection/bad_request
290+
remote_httpserverconnection/error_access_control
291+
remote_httpserverconnection/error_accept_header
292+
remote_httpserverconnection/error_authenticate_cn
293+
remote_httpserverconnection/error_authenticate_passwd
294+
remote_httpserverconnection/error_authenticate_wronguser
295+
remote_httpserverconnection/error_authenticate_wrongpasswd
296+
remote_httpserverconnection/reuse_connection
297+
remote_httpserverconnection/wg_abort
298+
remote_httpserverconnection/client_shutdown
299+
remote_httpserverconnection/handler_throw
300+
remote_httpserverconnection/liveness_disconnect
274301
remote_configpackageutility/ValidateName
275302
remote_url/id_and_path
276303
remote_url/parameters
@@ -279,6 +306,43 @@ add_boost_test(base
279306
remote_url/illegal_legal_strings
280307
)
281308

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

test/base-testloggerfixture.hpp

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

test/base-tlsstream-fixture.hpp

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