]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[#1548] Initial tests added
authorTomek Mrugalski <tomek@isc.org>
Mon, 14 Feb 2022 19:31:57 +0000 (20:31 +0100)
committerThomas Markwalder <tmark@isc.org>
Thu, 21 Apr 2022 14:19:19 +0000 (10:19 -0400)
src/hooks/dhcp/ddns_tuning/libloadtests/Makefile.am [new file with mode: 0644]
src/hooks/dhcp/ddns_tuning/libloadtests/callout_unittests.cc [new file with mode: 0644]
src/hooks/dhcp/ddns_tuning/libloadtests/load_unload_unittests.cc [new file with mode: 0644]
src/hooks/dhcp/ddns_tuning/libloadtests/run_unittests.cc [new file with mode: 0644]
src/hooks/dhcp/ddns_tuning/tests/.gitignore [new file with mode: 0644]
src/hooks/dhcp/ddns_tuning/tests/Makefile.am [new file with mode: 0644]
src/hooks/dhcp/ddns_tuning/tests/ddns_tuning_unittests.cc [new file with mode: 0644]
src/hooks/dhcp/ddns_tuning/tests/run_unittests.cc [new file with mode: 0644]
src/hooks/dhcp/ddns_tuning/version.cc [new file with mode: 0644]

diff --git a/src/hooks/dhcp/ddns_tuning/libloadtests/Makefile.am b/src/hooks/dhcp/ddns_tuning/libloadtests/Makefile.am
new file mode 100644 (file)
index 0000000..ba2160f
--- /dev/null
@@ -0,0 +1,60 @@
+SUBDIRS = .
+
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
+AM_CPPFLAGS += -I$(top_builddir)/src/hooks/dhcp/ddns_tuning -I$(top_srcdir)/src/hooks/dhcp/ddns_tuning
+AM_CPPFLAGS += $(BOOST_INCLUDES)
+AM_CPPFLAGS += -DDDNS_TUNING_LIB_SO=\"$(abs_top_builddir)/src/hooks/dhcp/ddns_tuning/.libs/libdhcp_ddns_tuning.so\"
+AM_CPPFLAGS += -DINSTALL_PROG=\"$(abs_top_srcdir)/install-sh\"
+
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+
+if USE_STATIC_LINK
+AM_LDFLAGS = -static
+endif
+
+# Unit test data files need to get installed.
+EXTRA_DIST =
+
+CLEANFILES = *.gcno *.gcda
+
+# TESTS_ENVIRONMENT = $(LIBTOOL) --mode=execute $(VALGRIND_COMMAND)
+LOG_COMPILER = $(LIBTOOL)
+AM_LOG_FLAGS = --mode=execute
+
+TESTS =
+if HAVE_GTEST
+TESTS += hook_unittests
+
+hook_unittests_SOURCES = run_unittests.cc
+hook_unittests_SOURCES += callout_unittests.cc
+hook_unittests_SOURCES += load_unload_unittests.cc
+
+ddns_tuning_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES) $(LOG4CPLUS_INCLUDES)
+
+ddns_tuning_unittests_LDFLAGS  = $(AM_LDFLAGS) $(CRYPTO_LDFLAGS) $(GTEST_LDFLAGS)
+
+ddns_tuning_unittests_CXXFLAGS = $(AM_CXXFLAGS)
+
+ddns_tuning_unittests_LDADD  = $(top_builddir)/src/lib/dhcpsrv/libkea-dhcpsrv.la
+ddns_tuning_unittests_LDADD += $(top_builddir)/src/lib/process/libkea-process.la
+ddns_tuning_unittests_LDADD += $(top_builddir)/src/lib/eval/libkea-eval.la
+ddns_tuning_unittests_LDADD += $(top_builddir)/src/lib/dhcp_ddns/libkea-dhcp_ddns.la
+ddns_tuning_unittests_LDADD += $(top_builddir)/src/lib/stats/libkea-stats.la
+ddns_tuning_unittests_LDADD += $(top_builddir)/src/lib/config/libkea-cfgclient.la
+ddns_tuning_unittests_LDADD += $(top_builddir)/src/lib/http/libkea-http.la
+ddns_tuning_unittests_LDADD += $(top_builddir)/src/lib/dhcp/libkea-dhcp++.la
+ddns_tuning_unittests_LDADD += $(top_builddir)/src/lib/hooks/libkea-hooks.la
+ddns_tuning_unittests_LDADD += $(top_builddir)/src/lib/database/libkea-database.la
+ddns_tuning_unittests_LDADD += $(top_builddir)/src/lib/cc/libkea-cc.la
+ddns_tuning_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libkea-asiolink.la
+ddns_tuning_unittests_LDADD += $(top_builddir)/src/lib/dns/libkea-dns++.la
+ddns_tuning_unittests_LDADD += $(top_builddir)/src/lib/cryptolink/libkea-cryptolink.la
+ddns_tuning_unittests_LDADD += $(top_builddir)/src/lib/log/libkea-log.la
+ddns_tuning_unittests_LDADD += $(top_builddir)/src/lib/util/libkea-util.la
+ddns_tuning_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
+ddns_tuning_unittests_LDADD += $(LOG4CPLUS_LIBS)
+ddns_tuning_unittests_LDADD += $(CRYPTO_LIBS)
+ddns_tuning_unittests_LDADD += $(BOOST_LIBS)
+ddns_tuning_unittests_LDADD += $(GTEST_LDADD)
+endif
+noinst_PROGRAMS = $(TESTS)
diff --git a/src/hooks/dhcp/ddns_tuning/libloadtests/callout_unittests.cc b/src/hooks/dhcp/ddns_tuning/libloadtests/callout_unittests.cc
new file mode 100644 (file)
index 0000000..289b4b4
--- /dev/null
@@ -0,0 +1,174 @@
+// Copyright (C) 2022 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/.
+
+/// @file This file contains tests which exercise the pkt[46]_send callouis
+/// called by the flexible option hook library. In order to test the callouts
+/// one must be able to pass to the load function it hook library parameters
+/// because the only way to populate these parameters is by actually loading
+/// the library via HooksManager::loadLibraries().
+
+#include <config.h>
+
+#include <ddns_tuning.h>
+#include <hooks/hooks.h>
+#include <hooks/hooks_manager.h>
+#include <hooks/callout_manager.h>
+#include <dhcp/pkt4.h>
+#include <dhcp/pkt6.h>
+#include <dhcpsrv/cfgmgr.h>
+
+#include <gtest/gtest.h>
+#include <errno.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::hooks;
+using namespace isc::data;
+using namespace isc::dhcp;
+
+namespace {
+
+/// @brief Structure that holds registered hook indexes.
+struct TestHooks {
+    /// @brief Index of pkt4_send callout.
+    int hook_index_pkt4_send_;
+
+    /// @brief Index of pkt6_send callout.
+    int hook_index_pkt6_send_;
+
+    /// @brief Constructor
+    ///
+    /// The constructor registers hook points for callout tests.
+    TestHooks() {
+        hook_index_pkt4_send_ = HooksManager::registerHook("pkt4_send");
+        hook_index_pkt6_send_ = HooksManager::registerHook("pkt6_send");
+    }
+};
+
+TestHooks testHooks;
+
+/// @brief Test fixture for testing callouts called by the flex-option library
+class CalloutTest : public ::testing::Test {
+public:
+    /// @brief Constructor
+    CalloutTest() {
+        reset();
+    }
+
+    /// @brief Destructor
+    /// Removes files that may be left over from previous tests
+    virtual ~CalloutTest() {
+        reset();
+    }
+
+    /// @brief Removes files that may be left over from previous tests
+    virtual void reset() {
+        HooksManager::unloadLibraries();
+    }
+
+    void addLib(const std::string& lib, ConstElementPtr params) {
+        libraries_.push_back(make_pair(lib, params));
+    }
+
+    void loadLibs() {
+        EXPECT_TRUE(HooksManager::loadLibraries(libraries_));
+    }
+
+    void unloadLibs() {
+        EXPECT_NO_THROW(HooksManager::unloadLibraries());
+    }
+
+    HookLibsCollection libraries_;
+};
+
+// Simple test which exercises the pkt4_send callout.
+TEST_F(CalloutTest, pkt4Send) {
+    // Prepare load() parameters.
+    ElementPtr params = Element::createMap();
+    ElementPtr options = Element::createList();
+    params->set("options", options);
+    ElementPtr option = Element::createMap();
+    options->add(option);
+    ElementPtr code = Element::create(DHO_HOST_NAME);
+    option->set("code", code);
+    ElementPtr add = Element::create(string("'abc'"));
+    option->set("add", add);
+
+    // Load the library.
+    addLib(DDNS_TUNING_LIB_SO, params);
+    loadLibs();
+
+    // Prepare packets.
+    Pkt4Ptr query(new Pkt4(DHCPDISCOVER, 12345));
+    Pkt4Ptr response(new Pkt4(DHCPOFFER, 12345));
+    EXPECT_FALSE(response->getOption(DHO_HOST_NAME));
+
+    // Get and setup the callout handle.
+    EXPECT_TRUE(HooksManager::calloutsPresent(testHooks.hook_index_pkt4_send_));
+    CalloutHandlePtr handle = HooksManager::createCalloutHandle();
+    handle->setArgument("query4", query);
+    handle->setArgument("response4", response);
+
+    // Execute the callout.
+    EXPECT_NO_THROW(HooksManager::callCallouts(testHooks.hook_index_pkt4_send_,
+                                               *handle));
+    EXPECT_EQ(0, handle->getStatus());
+
+    // Check the result.
+    OptionPtr opt = response->getOption(DHO_HOST_NAME);
+    ASSERT_TRUE(opt);
+    EXPECT_EQ(DHO_HOST_NAME, opt->getType());
+    const OptionBuffer& buffer = opt->getData();
+    ASSERT_EQ(3, buffer.size());
+    EXPECT_EQ(0, memcmp(&buffer[0], "abc", 3));
+}
+
+// Simple test which exercises the pkt6_send callout.
+TEST_F(CalloutTest, pkt6Send) {
+    // Move to DHCPv6.
+    CfgMgr::instance().setFamily(AF_INET6);
+
+    // Prepare load() parameters.
+    ElementPtr params = Element::createMap();
+    ElementPtr options = Element::createList();
+    params->set("options", options);
+    ElementPtr option = Element::createMap();
+    options->add(option);
+    ElementPtr code = Element::create(D6O_BOOTFILE_URL);
+    option->set("code", code);
+    ElementPtr supersede = Element::create(string("'abc'"));
+    option->set("supersede", supersede);
+
+    // Load the library.
+    addLib(DDNS_TUNING_LIB_SO, params);
+    loadLibs();
+
+    // Prepare packets.
+    Pkt6Ptr query(new Pkt6(DHCPV6_SOLICIT, 12345));
+    Pkt6Ptr response(new Pkt6(DHCPV6_ADVERTISE, 12345));
+    EXPECT_FALSE(response->getOption(D6O_BOOTFILE_URL));
+
+    // Get and setup the callout handle.
+    EXPECT_TRUE(HooksManager::calloutsPresent(testHooks.hook_index_pkt6_send_));
+    CalloutHandlePtr handle = HooksManager::createCalloutHandle();
+    handle->setArgument("query6", query);
+    handle->setArgument("response6", response);
+
+    // Execute the callout.
+    EXPECT_NO_THROW(HooksManager::callCallouts(testHooks.hook_index_pkt6_send_,
+                                               *handle));
+    EXPECT_EQ(0, handle->getStatus());
+
+    // Check the result.
+    OptionPtr opt = response->getOption(D6O_BOOTFILE_URL);
+    ASSERT_TRUE(opt);
+    EXPECT_EQ(D6O_BOOTFILE_URL, opt->getType());
+    const OptionBuffer& buffer = opt->getData();
+    ASSERT_EQ(3, buffer.size());
+    EXPECT_EQ(0, memcmp(&buffer[0], "abc", 3));
+}
+
+} // end of anonymous namespace
diff --git a/src/hooks/dhcp/ddns_tuning/libloadtests/load_unload_unittests.cc b/src/hooks/dhcp/ddns_tuning/libloadtests/load_unload_unittests.cc
new file mode 100644 (file)
index 0000000..657d72d
--- /dev/null
@@ -0,0 +1,80 @@
+// Copyright (C) 2022 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/.
+
+/// @file This file contains tests which exercise the load and unload
+/// functions in the ddns tuning hook library. In order to test the load
+/// function, one must be able to pass it hook library parameters. The
+/// the only way to populate these parameters is by actually loading the
+/// library via HooksManager::loadLibraries().
+
+#include <config.h>
+
+#include <ddns_tuning.h>
+#include <hooks/hooks_manager.h>
+
+#include <gtest/gtest.h>
+#include <errno.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::hooks;
+using namespace isc::data;
+using namespace isc::dhcp;
+
+namespace {
+
+/// @brief Test fixture for testing loading and unloading the flex-option library
+class LibLoadTest : public ::testing::Test {
+public:
+    /// @brief Constructor
+    LibLoadTest() {
+        reset();
+    }
+
+    /// @brief Destructor
+    /// Removes files that may be left over from previous tests
+    virtual ~LibLoadTest() {
+        reset();
+    }
+
+    /// @brief Removes files that may be left over from previous tests
+    virtual void reset() {
+        HooksManager::unloadLibraries();
+    }
+
+    void addLib(const std::string& lib, ConstElementPtr params) {
+        libraries_.push_back(make_pair(lib, params));
+    }
+
+    void loadLibs() {
+        EXPECT_TRUE(HooksManager::loadLibraries(libraries_));
+    }
+
+    void unloadLibs() {
+        EXPECT_NO_THROW(HooksManager::unloadLibraries());
+    }
+
+    HookLibsCollection libraries_;
+};
+
+// Simple test that checks the library can be loaded and unloaded several times.
+TEST_F(LibLoadTest, validLoad) {
+
+    // Prepare parameters for the callout parameters library.
+    // ElementPtr params = Element::createMap();
+    // ElementPtr options = Element::createList();
+    // params->set("hostname-expr", options);
+
+    addLib(DDNS_TUNING_LIB_SO, params);
+
+    loadLibs();
+    unloadLibs();
+
+    loadLibs();
+    unloadLibs();
+}
+
+} // end of anonymous namespace
diff --git a/src/hooks/dhcp/ddns_tuning/libloadtests/run_unittests.cc b/src/hooks/dhcp/ddns_tuning/libloadtests/run_unittests.cc
new file mode 100644 (file)
index 0000000..96a0ba7
--- /dev/null
@@ -0,0 +1,19 @@
+// Copyright (C) 2022 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 <log/logger_support.h>
+#include <gtest/gtest.h>
+
+int
+main(int argc, char* argv[]) {
+    ::testing::InitGoogleTest(&argc, argv);
+    isc::log::initLogger();
+    int result = RUN_ALL_TESTS();
+
+    return (result);
+}
diff --git a/src/hooks/dhcp/ddns_tuning/tests/.gitignore b/src/hooks/dhcp/ddns_tuning/tests/.gitignore
new file mode 100644 (file)
index 0000000..0b2fe58
--- /dev/null
@@ -0,0 +1 @@
+/ddns_tuning_unittests
diff --git a/src/hooks/dhcp/ddns_tuning/tests/Makefile.am b/src/hooks/dhcp/ddns_tuning/tests/Makefile.am
new file mode 100644 (file)
index 0000000..c888f12
--- /dev/null
@@ -0,0 +1,60 @@
+SUBDIRS = .
+
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
+AM_CPPFLAGS += -I$(top_builddir)/src/hooks/dhcp/ddns_tuning -I$(top_srcdir)/src/hooks/dhcp/ddns_tuning
+AM_CPPFLAGS += $(BOOST_INCLUDES)
+AM_CPPFLAGS += -DDDNS_TUNING_LIB_SO=\"$(abs_top_builddir)/src/hooks/dhcp/ddns_tuning/.libs/libdhcp_ddns_tuning.so\"
+AM_CPPFLAGS += -DINSTALL_PROG=\"$(abs_top_srcdir)/install-sh\"
+
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+
+if USE_STATIC_LINK
+AM_LDFLAGS = -static
+endif
+
+# Unit test data files need to get installed.
+EXTRA_DIST =
+
+CLEANFILES = *.gcno *.gcda
+
+# TESTS_ENVIRONMENT = $(LIBTOOL) --mode=execute $(VALGRIND_COMMAND)
+LOG_COMPILER = $(LIBTOOL)
+AM_LOG_FLAGS = --mode=execute
+
+TESTS =
+if HAVE_GTEST
+TESTS += ddns_tuning_unittests
+
+ddns_tuning_unittests_SOURCES = run_unittests.cc
+ddns_tuning_unittests_SOURCES += ddns_tuning_unittests.cc
+
+ddns_tuning_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES) $(LOG4CPLUS_INCLUDES)
+
+ddns_tuning_unittests_LDFLAGS  = $(AM_LDFLAGS) $(CRYPTO_LDFLAGS) $(GTEST_LDFLAGS)
+
+ddns_tuning_unittests_CXXFLAGS = $(AM_CXXFLAGS)
+
+ddns_tuning_unittests_LDADD  = $(top_builddir)/src/hooks/dhcp/ddns_tuning/libddns_tuning.la
+ddns_tuning_unittests_LDADD += $(top_builddir)/src/lib/dhcpsrv/libkea-dhcpsrv.la
+ddns_tuning_unittests_LDADD += $(top_builddir)/src/lib/process/libkea-process.la
+ddns_tuning_unittests_LDADD += $(top_builddir)/src/lib/eval/libkea-eval.la
+ddns_tuning_unittests_LDADD += $(top_builddir)/src/lib/dhcp_ddns/libkea-dhcp_ddns.la
+ddns_tuning_unittests_LDADD += $(top_builddir)/src/lib/stats/libkea-stats.la
+ddns_tuning_unittests_LDADD += $(top_builddir)/src/lib/config/libkea-cfgclient.la
+ddns_tuning_unittests_LDADD += $(top_builddir)/src/lib/http/libkea-http.la
+ddns_tuning_unittests_LDADD += $(top_builddir)/src/lib/dhcp/libkea-dhcp++.la
+ddns_tuning_unittests_LDADD += $(top_builddir)/src/lib/hooks/libkea-hooks.la
+ddns_tuning_unittests_LDADD += $(top_builddir)/src/lib/database/libkea-database.la
+ddns_tuning_unittests_LDADD += $(top_builddir)/src/lib/cc/libkea-cc.la
+ddns_tuning_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libkea-asiolink.la
+ddns_tuning_unittests_LDADD += $(top_builddir)/src/lib/dns/libkea-dns++.la
+ddns_tuning_unittests_LDADD += $(top_builddir)/src/lib/cryptolink/libkea-cryptolink.la
+ddns_tuning_unittests_LDADD += $(top_builddir)/src/lib/log/libkea-log.la
+ddns_tuning_unittests_LDADD += $(top_builddir)/src/lib/util/libkea-util.la
+ddns_tuning_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
+ddns_tuning_unittests_LDADD += $(LOG4CPLUS_LIBS)
+ddns_tuning_unittests_LDADD += $(CRYPTO_LIBS)
+ddns_tuning_unittests_LDADD += $(BOOST_LIBS)
+ddns_tuning_unittests_LDADD += $(GTEST_LDADD)
+endif
+noinst_PROGRAMS = $(TESTS)
diff --git a/src/hooks/dhcp/ddns_tuning/tests/ddns_tuning_unittests.cc b/src/hooks/dhcp/ddns_tuning/tests/ddns_tuning_unittests.cc
new file mode 100644 (file)
index 0000000..767c45c
--- /dev/null
@@ -0,0 +1,247 @@
+// Copyright (C) 2022 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/.
+
+/// @file This file contains tests which verify flexible option.
+
+#include <config.h>
+#include <ddns_tuning.h>
+#include <ddns_tuning_log.h>
+#include <dhcp/libdhcp++.h>
+#include <dhcp/option_string.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <eval/eval_context.h>
+#include <hooks/callout_manager.h>
+#include <hooks/hooks.h>
+
+#include <gtest/gtest.h>
+#include <sstream>
+
+using namespace std;
+using namespace isc;
+using namespace isc::data;
+using namespace isc::dhcp;
+using namespace isc::eval;
+using namespace isc::hooks;
+using namespace isc::ddns_tuning;
+
+namespace {
+
+/// @brief Test class derived from DdnsTuningImpl
+class TestDdnsTuningImpl : public DdnsTuningImpl {
+public:
+    /// @brief Configure clone which records the error.
+    ///
+    /// @param options The element with option config list.
+    void testConfigure(ConstElementPtr options) {
+        err_msg_.clear();
+        try {
+            configure(options);
+        } catch (const std::exception& ex) {
+            err_msg_ = string(ex.what());
+            throw;
+        }
+    }
+
+    /// @brief Get the last error message.
+    ///
+    /// @return The last error message.
+    const string& getErrMsg() const {
+        return (err_msg_);
+    }
+
+private:
+    /// @brief Last error message.
+    string err_msg_;
+};
+
+/// @brief The type of shared pointers to TestDdnsTuningImpl
+typedef boost::shared_ptr<TestDdnsTuningImpl> TestDdnsTuningImplPtr;
+
+/// @brief Test fixture for testing the DDNS Tuning library.
+class DdnsTuningTest : public ::testing::Test {
+public:
+    /// @brief Constructor.
+    DdnsTuningTest() {
+        impl_.reset(new TestDdnsTuningImpl());
+        CfgMgr::instance().setFamily(AF_INET);
+    }
+
+    /// @brief Destructor.
+    virtual ~DdnsTuningTest() {
+        CfgMgr::instance().setFamily(AF_INET);
+        impl_.reset();
+    }
+
+    /// @brief Flex Option implementation.
+    TestDdnsTuningImplPtr impl_;
+};
+
+// Verify that the configuration must exist.
+TEST_F(DdnsTuningTest, noConfig) {
+    ElementPtr options;
+    EXPECT_THROW(impl_->testConfigure(options), BadValue);
+    EXPECT_EQ("'hostname' parameter is mandatory", impl_->getErrMsg());
+}
+
+// Verify that the configuration must be a list.
+TEST_F(DdnsTuningTest, configNotList) {
+    ElementPtr options = Element::createMap();
+    EXPECT_THROW(impl_->testConfigure(options), BadValue);
+    EXPECT_EQ("'hostname' parameter must be a string", impl_->getErrMsg());
+}
+
+// Verify that the configuration can be the empty list.
+TEST_F(DdnsTuningTest, configEmpty) {
+    ElementPtr options = Element::createList();
+    EXPECT_NO_THROW(impl_->testConfigure(options));
+    EXPECT_TRUE(impl_->getErrMsg().empty());
+}
+
+
+
+// Verify that the add value must parse.
+TEST_F(DdnsTuningTest, optionConfigBadAdd) {
+    ElementPtr params = Element::createList();
+    params->set("hostname", Element::create(string("ifelse('a','b','c')")));
+    EXPECT_THROW(impl_->testConfigure(params), BadValue);
+    string expected = "can't parse add expression [ifelse('a','b','c')] ";
+    expected += "error: <string>:1.11: syntax error, ";
+    expected += "unexpected \",\", expecting == or +";
+    EXPECT_EQ(expected, impl_->getErrMsg());
+}
+
+// Verify that a valid v4 add value is accepted.
+TEST_F(DdnsTuningTest, optionConfigAdd4) {
+    ElementPtr params = Element::createList();
+    params->set("hostname", Element::create(string("ifelse('a','b','c')")));
+    EXPECT_NO_THROW(impl_->testConfigure(params));
+    EXPECT_TRUE(impl_->getErrMsg().empty());
+
+    /* TODO: verify it was really configured
+    auto map = impl_->getOptionConfigMap();
+    DdnsTuningImpl::OptionConfigPtr opt_cfg;
+    ASSERT_NO_THROW(opt_cfg = map.at(DHO_HOST_NAME));
+    ASSERT_TRUE(opt_cfg);
+    EXPECT_EQ(DHO_HOST_NAME, opt_cfg->getCode());
+    EXPECT_EQ(DdnsTuningImpl::ADD, opt_cfg->getAction());
+    EXPECT_EQ("'abc'", opt_cfg->getText());
+
+    ExpressionPtr expr = opt_cfg->getExpr();
+    ASSERT_TRUE(expr);
+    ASSERT_EQ(1, expr->size());
+    Pkt4Ptr pkt4(new Pkt4(DHCPDISCOVER, 12345));
+    ValueStack values;
+    EXPECT_NO_THROW(expr->at(0)->evaluate(*pkt4, values));
+    ASSERT_EQ(1, values.size());
+    EXPECT_EQ("abc", values.top()); */
+}
+
+// Verify that empty option config list does nothing.
+TEST_F(DdnsTuningTest, processEmpty) {
+    Pkt4Ptr query(new Pkt4(DHCPDISCOVER, 12345));
+    Pkt4Ptr response(new Pkt4(DHCPOFFER, 12345));
+    string response_txt = response->toText();
+
+    EXPECT_NO_THROW(impl_->process<Pkt4Ptr>(Option::V4, query, response));
+
+    EXPECT_EQ(response_txt, response->toText());
+}
+
+// Verify that NONE action really does nothing.
+TEST_F(DdnsTuningTest, processNone) {
+    CfgMgr::instance().setFamily(AF_INET6);
+
+    Pkt6Ptr query(new Pkt6(DHCPV6_SOLICIT, 12345));
+    Pkt6Ptr response(new Pkt6(DHCPV6_ADVERTISE, 12345));
+    string response_txt = response->toText();
+
+    EXPECT_NO_THROW(impl_->process<Pkt6Ptr>(Option::V6, query, response));
+
+    EXPECT_EQ(response_txt, response->toText());
+}
+
+// Verify that REMOVE action does nothing if the option is not present.
+TEST_F(DdnsTuningTest, processRemoveNoOption) {
+    ElementPtr options = Element::createList();
+    ElementPtr option = Element::createMap();
+    options->add(option);
+    ElementPtr code = Element::create(DHO_HOST_NAME);
+    option->set("code", code);
+    ElementPtr remove = Element::create(string("'abc' == 'abc'"));
+    option->set("remove", remove);
+    EXPECT_NO_THROW(impl_->testConfigure(options));
+    EXPECT_TRUE(impl_->getErrMsg().empty());
+
+    Pkt4Ptr query(new Pkt4(DHCPDISCOVER, 12345));
+    Pkt4Ptr response(new Pkt4(DHCPOFFER, 12345));
+    string response_txt = response->toText();
+    EXPECT_FALSE(response->getOption(DHO_HOST_NAME));
+
+    EXPECT_NO_THROW(impl_->process<Pkt4Ptr>(Option::V4, query, response));
+
+    EXPECT_EQ(response_txt, response->toText());
+    EXPECT_FALSE(response->getOption(DHO_HOST_NAME));
+}
+
+// Verify that REMOVE action does nothing when the expression evaluates to false.
+TEST_F(DdnsTuningTest, processRemoveFalse) {
+    CfgMgr::instance().setFamily(AF_INET6);
+
+    ElementPtr options = Element::createList();
+    ElementPtr option = Element::createMap();
+    options->add(option);
+    ElementPtr code = Element::create(D6O_BOOTFILE_URL);
+    option->set("code", code);
+    ElementPtr remove = Element::create(string("'abc' == 'xyz'"));
+    option->set("remove", remove);
+    EXPECT_NO_THROW(impl_->testConfigure(options));
+    EXPECT_TRUE(impl_->getErrMsg().empty());
+
+    Pkt6Ptr query(new Pkt6(DHCPV6_SOLICIT, 12345));
+    Pkt6Ptr response(new Pkt6(DHCPV6_ADVERTISE, 12345));
+    OptionStringPtr str(new OptionString(Option::V6, D6O_BOOTFILE_URL, "http"));
+    response->addOption(str);
+    string response_txt = response->toText();
+
+    EXPECT_NO_THROW(impl_->process<Pkt6Ptr>(Option::V6, query, response));
+
+    EXPECT_EQ(response_txt, response->toText());
+    EXPECT_TRUE(response->getOption(D6O_BOOTFILE_URL));
+}
+
+// A more complex check...
+TEST_F(DdnsTuningTest, processFullTest) {
+    ElementPtr options = Element::createList();
+    ElementPtr option = Element::createMap();
+    options->add(option);
+    ElementPtr code = Element::create(DHO_BOOT_FILE_NAME);
+    option->set("code", code);
+    string expr = "ifelse(option[host-name].exists,";
+    expr += "concat(option[host-name].text,'.boot'),'')";
+    ElementPtr add = Element::create(expr);
+    option->set("add", add);
+    EXPECT_NO_THROW(impl_->testConfigure(options));
+    EXPECT_TRUE(impl_->getErrMsg().empty());
+
+    Pkt4Ptr query(new Pkt4(DHCPDISCOVER, 12345));
+    Pkt4Ptr response(new Pkt4(DHCPOFFER, 12345));
+    OptionStringPtr str(new OptionString(Option::V4, DHO_HOST_NAME, "foo"));
+    query->addOption(str);
+    EXPECT_FALSE(response->getOption(DHO_BOOT_FILE_NAME));
+
+    EXPECT_NO_THROW(impl_->process<Pkt4Ptr>(Option::V4, query, response));
+
+    OptionPtr opt = response->getOption(DHO_BOOT_FILE_NAME);
+    ASSERT_TRUE(opt);
+    EXPECT_EQ(DHO_BOOT_FILE_NAME, opt->getType());
+    const OptionBuffer& buffer = opt->getData();
+    ASSERT_EQ(8, buffer.size());
+    EXPECT_EQ(0, memcmp(&buffer[0], "foo.boot", 8));
+}
+
+
+
+} // end of anonymous namespace
diff --git a/src/hooks/dhcp/ddns_tuning/tests/run_unittests.cc b/src/hooks/dhcp/ddns_tuning/tests/run_unittests.cc
new file mode 100644 (file)
index 0000000..96a0ba7
--- /dev/null
@@ -0,0 +1,19 @@
+// Copyright (C) 2022 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 <log/logger_support.h>
+#include <gtest/gtest.h>
+
+int
+main(int argc, char* argv[]) {
+    ::testing::InitGoogleTest(&argc, argv);
+    isc::log::initLogger();
+    int result = RUN_ALL_TESTS();
+
+    return (result);
+}
diff --git a/src/hooks/dhcp/ddns_tuning/version.cc b/src/hooks/dhcp/ddns_tuning/version.cc
new file mode 100644 (file)
index 0000000..c50a24b
--- /dev/null
@@ -0,0 +1,17 @@
+// Copyright (C) 2022 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 <hooks/hooks.h>
+
+extern "C" {
+
+/// @brief returns Kea hooks version.
+int version() {
+    return (KEA_HOOKS_VERSION);
+}
+
+}