mirror of
https://gitlab.isc.org/isc-projects/kea
synced 2025-08-30 13:37:55 +00:00
[#3477] Checkpoint: added HttpCommandMgr UTs
This commit is contained in:
@@ -25,6 +25,7 @@ run_unittests_SOURCES += cmd_http_listener_unittests.cc
|
||||
run_unittests_SOURCES += cmd_response_creator_unittests.cc
|
||||
run_unittests_SOURCES += cmd_response_creator_factory_unittests.cc
|
||||
run_unittests_SOURCES += http_command_config_unittests.cc
|
||||
run_unittests_SOURCES += http_command_mgr_unittests.cc
|
||||
run_unittests_SOURCES += http_command_response_creator_factory_unittests.cc
|
||||
run_unittests_SOURCES += http_command_response_creator_unittests.cc
|
||||
|
||||
|
283
src/lib/config/tests/http_command_mgr_unittests.cc
Normal file
283
src/lib/config/tests/http_command_mgr_unittests.cc
Normal file
@@ -0,0 +1,283 @@
|
||||
// Copyright (C) 2021-2024 Internet Systems Consortium, Inc. ("ISC")
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
#include <config.h>
|
||||
|
||||
#include <asiolink/asio_wrapper.h>
|
||||
#include <asiolink/interval_timer.h>
|
||||
#include <asiolink/testutils/test_tls.h>
|
||||
#include <cc/command_interpreter.h>
|
||||
#include <config/http_command_mgr.h>
|
||||
#include <config/command_mgr.h>
|
||||
#include <http/response.h>
|
||||
#include <http/response_parser.h>
|
||||
#include <http/tests/test_http_client.h>
|
||||
#include <testutils/gtest_utils.h>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <list>
|
||||
#include <sstream>
|
||||
|
||||
using namespace isc;
|
||||
using namespace isc::asiolink;
|
||||
using namespace isc::asiolink::test;
|
||||
using namespace isc::config;
|
||||
using namespace isc::data;
|
||||
using namespace isc::dhcp;
|
||||
using namespace isc::http;
|
||||
using namespace isc::util;
|
||||
using namespace std;
|
||||
using namespace boost::asio::ip;
|
||||
namespace ph = std::placeholders;
|
||||
|
||||
namespace {
|
||||
|
||||
/// @brief IP address to which HTTP service is bound.
|
||||
const std::string SERVER_ADDRESS = "127.0.0.1";
|
||||
|
||||
/// @brief Port number to which HTTP service is bound.
|
||||
const unsigned short SERVER_PORT = 18123;
|
||||
|
||||
/// @brief Test timeout (ms).
|
||||
const long TEST_TIMEOUT = 10000;
|
||||
|
||||
/// @brief Test fixture class for @ref CmdHttpListener.
|
||||
class HttpCommandMgrTest : public ::testing::Test {
|
||||
public:
|
||||
|
||||
/// @brief Constructor.
|
||||
///
|
||||
/// Resets state, starts test timer which detects timeouts,
|
||||
/// initializes HTTP control socket config.
|
||||
HttpCommandMgrTest()
|
||||
: io_service_(new IOService()), test_timer_(io_service_), client_(),
|
||||
http_config_() {
|
||||
resetState(io_service_);
|
||||
test_timer_.setup(std::bind(&HttpCommandMgrTest::timeoutHandler, this, true),
|
||||
TEST_TIMEOUT, IntervalTimer::ONE_SHOT);
|
||||
HttpCommandMgr::instance().setIOService(io_service_);
|
||||
|
||||
// Initializes the HTTP control socket config.
|
||||
ElementPtr config = Element::createMap();
|
||||
config->set("socket-address", Element::create(SERVER_ADDRESS));
|
||||
config->set("socket-port", Element::create(SERVER_PORT));
|
||||
http_config_.reset(new HttpCommandConfig(config));
|
||||
}
|
||||
|
||||
/// @brief Destructor.
|
||||
///
|
||||
/// Closes HTTP client, cancels timer, resets state.
|
||||
virtual ~HttpCommandMgrTest() {
|
||||
if (client_) {
|
||||
client_->close();
|
||||
}
|
||||
test_timer_.cancel();
|
||||
resetState();
|
||||
}
|
||||
|
||||
/// @brief Resets state.
|
||||
///
|
||||
/// @param io_service The IO service of the @c HttpCommandMgr.
|
||||
void resetState(IOServicePtr io_service = IOServicePtr()) {
|
||||
// Deregisters commands.
|
||||
config::CommandMgr::instance().deregisterAll();
|
||||
|
||||
if (HttpCommandMgr::instance().getHttpListener()) {
|
||||
HttpCommandMgr::instance().close();
|
||||
}
|
||||
if (io_service) {
|
||||
HttpCommandMgr::instance().setIOService(io_service);
|
||||
} else {
|
||||
io_service_->stopAndPoll();
|
||||
HttpCommandMgr::instance().setIOService(IOServicePtr());
|
||||
}
|
||||
}
|
||||
|
||||
/// @brief Constructs a complete HTTP POST given a request body.
|
||||
///
|
||||
/// @param request_body string containing the desired request body.
|
||||
///
|
||||
/// @return string containing the constructed POST.
|
||||
std::string buildPostStr(const std::string& request_body) {
|
||||
// Create the command string.
|
||||
std::stringstream ss;
|
||||
ss << "POST /foo/bar HTTP/1.1\r\n"
|
||||
"Content-Type: application/json\r\n"
|
||||
"Content-Length: "
|
||||
<< request_body.size() << "\r\n\r\n"
|
||||
<< request_body;
|
||||
return (ss.str());
|
||||
}
|
||||
|
||||
/// @brief Initiates a command via a new HTTP client.
|
||||
///
|
||||
/// This method creates a TestHttpClient instance, and starts a
|
||||
/// request based on the given command.
|
||||
///
|
||||
/// @param request_body JSON String containing the API command
|
||||
/// to be sent.
|
||||
void startRequest(const std::string& request_body = "{ }") {
|
||||
std::string request_str = buildPostStr(request_body);
|
||||
|
||||
// Instantiate the client.
|
||||
client_.reset(new TestHttpClient(io_service_, SERVER_ADDRESS,
|
||||
SERVER_PORT));
|
||||
|
||||
// Start the request. Note, nothing happens until the IOService runs.
|
||||
client_->startRequest(request_str);
|
||||
}
|
||||
|
||||
/// @brief Callback function invoke upon test timeout.
|
||||
///
|
||||
/// It stops the IO service and reports test timeout.
|
||||
///
|
||||
/// @param fail_on_timeout Specifies if test failure should be reported.
|
||||
void timeoutHandler(const bool fail_on_timeout) {
|
||||
if (fail_on_timeout) {
|
||||
ADD_FAILURE() << "Timeout occurred while running the test!";
|
||||
}
|
||||
io_service_->stop();
|
||||
}
|
||||
|
||||
/// @brief Create an HttpResponse from a response string.
|
||||
///
|
||||
/// @param response_str a string containing the whole HTTP
|
||||
/// response received.
|
||||
///
|
||||
/// @return An HttpResponse constructed from by parsing the
|
||||
/// response string.
|
||||
HttpResponsePtr parseResponse(const std::string response_str) {
|
||||
HttpResponsePtr hr(new HttpResponse());
|
||||
HttpResponseParser parser(*hr);
|
||||
parser.initModel();
|
||||
parser.postBuffer(&response_str[0], response_str.size());
|
||||
parser.poll();
|
||||
if (!parser.httpParseOk()) {
|
||||
isc_throw(Unexpected, "response_str: '" << response_str
|
||||
<< "' failed to parse: " << parser.getErrorMessage());
|
||||
}
|
||||
|
||||
return (hr);
|
||||
}
|
||||
|
||||
/// @brief IO service used in drive the test and test clients.
|
||||
IOServicePtr io_service_;
|
||||
|
||||
/// @brief Asynchronous timer service to detect timeouts.
|
||||
IntervalTimer test_timer_;
|
||||
|
||||
/// @brief Client connection.
|
||||
TestHttpClientPtr client_;
|
||||
|
||||
/// @brief HTTP control socket config.
|
||||
HttpCommandConfigPtr http_config_;
|
||||
};
|
||||
|
||||
/// Verifies the configure and close of HttpCommandMgr.
|
||||
TEST_F(HttpCommandMgrTest, basics) {
|
||||
// Make sure we can create one.
|
||||
ASSERT_NO_THROW_LOG(HttpCommandMgr::instance().configure(http_config_));
|
||||
auto listener = HttpCommandMgr::instance().getHttpListener();
|
||||
ASSERT_TRUE(listener);
|
||||
|
||||
// Verify the getters do what we expect.
|
||||
EXPECT_EQ(SERVER_ADDRESS, listener->getLocalAddress().toText());
|
||||
EXPECT_EQ(SERVER_PORT, listener->getLocalPort());
|
||||
|
||||
// Stop it and verify we're no longer listening.
|
||||
ASSERT_NO_THROW_LOG(HttpCommandMgr::instance().close());
|
||||
EXPECT_FALSE(HttpCommandMgr::instance().getHttpListener());
|
||||
|
||||
// Make sure we can call stop again without problems.
|
||||
ASSERT_NO_THROW_LOG(HttpCommandMgr::instance().close());
|
||||
|
||||
// We should be able to restart it.
|
||||
ASSERT_NO_THROW_LOG(HttpCommandMgr::instance().configure(http_config_));
|
||||
EXPECT_TRUE(HttpCommandMgr::instance().getHttpListener());
|
||||
|
||||
// Close it with postponed garbage collection.
|
||||
ASSERT_NO_THROW_LOG(HttpCommandMgr::instance().close(false));
|
||||
EXPECT_TRUE(HttpCommandMgr::instance().getHttpListener());
|
||||
ASSERT_NO_THROW_LOG(HttpCommandMgr::instance().garbageCollectListeners());
|
||||
EXPECT_FALSE(HttpCommandMgr::instance().getHttpListener());
|
||||
}
|
||||
|
||||
#if 0
|
||||
// This test verifies that an HTTP connection can be established and used to
|
||||
// transmit an HTTP request and receive the response.
|
||||
TEST_F(HttpCommandMgrTest, basicListenAndRespond) {
|
||||
|
||||
// Create a listener.
|
||||
ASSERT_NO_THROW_LOG(listener_.reset(new HttpCommandMgr(IOAddress(SERVER_ADDRESS),
|
||||
SERVER_PORT)));
|
||||
ASSERT_TRUE(listener_);
|
||||
|
||||
// Start the listener and verify it's listening.
|
||||
ASSERT_NO_THROW_LOG(listener_->start());
|
||||
ASSERT_TRUE(listener_->isRunning());
|
||||
|
||||
// Now let's send a "foo" command. This should create a client, connect
|
||||
// to our listener, post our request and retrieve our reply.
|
||||
ASSERT_NO_THROW(startRequest("{\"command\": \"foo\"}"));
|
||||
ASSERT_TRUE(client_);
|
||||
ASSERT_NO_THROW(runIOService());
|
||||
ASSERT_TRUE(client_);
|
||||
|
||||
// Parse the response into an HttpResponse.
|
||||
HttpResponsePtr hr;
|
||||
ASSERT_NO_THROW_LOG(hr = parseResponse(client_->getResponse()));
|
||||
|
||||
// Without a command handler loaded, we should get an unsupported command response.
|
||||
EXPECT_EQ(hr->getBody(), "[ { \"result\": 2, \"text\": \"'foo' command not supported.\" } ]");
|
||||
|
||||
// Now let's register the foo command handler.
|
||||
CommandMgr::instance().registerCommand("foo",
|
||||
std::bind(&HttpCommandMgrTest::fooCommandHandler,
|
||||
this, ph::_1, ph::_2));
|
||||
// Try posting the foo command again.
|
||||
ASSERT_NO_THROW(startRequest("{\"command\": \"foo\"}"));
|
||||
ASSERT_TRUE(client_);
|
||||
|
||||
// Parse the response.
|
||||
ASSERT_NO_THROW_LOG(hr = parseResponse(client_->getResponse()));
|
||||
|
||||
// We should have a response from our command handler.
|
||||
EXPECT_EQ(hr->getBody(), "[ { \"arguments\": [ \"bar\" ], \"result\": 0 } ]");
|
||||
|
||||
// Make sure the listener is still listening.
|
||||
ASSERT_TRUE(listener_->isRunning());
|
||||
|
||||
// Stop the listener then verify it has stopped.
|
||||
ASSERT_NO_THROW_LOG(listener_->stop());
|
||||
ASSERT_TRUE(listener_->isStopped());
|
||||
}
|
||||
|
||||
// Check if a TLS listener can be created.
|
||||
TEST_F(HttpCommandMgrTest, tls) {
|
||||
IOAddress address(SERVER_ADDRESS);
|
||||
uint16_t port = SERVER_PORT;
|
||||
TlsContextPtr context;
|
||||
configServer(context);
|
||||
|
||||
// Make sure we can create the listener.
|
||||
ASSERT_NO_THROW_LOG(listener_.reset(new HttpCommandMgr(address, port, 1, context)));
|
||||
EXPECT_EQ(listener_->getAddress(), address);
|
||||
EXPECT_EQ(listener_->getPort(), port);
|
||||
EXPECT_EQ(listener_->getTlsContext(), context);
|
||||
EXPECT_TRUE(listener_->isStopped());
|
||||
|
||||
// Make sure we can start it and it's listening.
|
||||
ASSERT_NO_THROW_LOG(listener_->start());
|
||||
ASSERT_TRUE(listener_->isRunning());
|
||||
|
||||
// Stop it.
|
||||
ASSERT_NO_THROW_LOG(listener_->stop());
|
||||
ASSERT_TRUE(listener_->isStopped());
|
||||
}
|
||||
#endif
|
||||
|
||||
} // end of anonymous namespace
|
Reference in New Issue
Block a user