]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[#3463] V6 UTs working
authorThomas Markwalder <tmark@isc.org>
Thu, 6 Feb 2025 20:22:29 +0000 (15:22 -0500)
committerThomas Markwalder <tmark@isc.org>
Tue, 18 Feb 2025 18:54:19 +0000 (18:54 +0000)
Finished V6 UTs. Also re-organized tests so those
that rely on loading the library are now in libloadtests:
    tests/lease_cmds4_unittest.cc -> libloadtests/lease_cmds4_unittest.cc
    tests/lease_cmds6_unittest.cc -> libloadtests/lease_cmds6_unittest.cc
    tests/lease_cmds_unittest.cc -> libloadtests/lease_cmds_unittest.cc
    tests/lease_cmds_unittest.h -> libloadtests/lease_cmds_unittest.h

Create new files to house new callout handler tests which rely on
the library being linked in so functions are visible:

new file:   tests/lease_cmds_func4_unittest.cc
new file:   tests/lease_cmds_func6_unittest.cc
new file:   tests/lease_cmds_func_unittest.h

/src/hooks/dhcp/lease_cmds/lease_cmds.cc
    LeaseCmdsImpl::leases6Committed() - track but don't
    throw on leases that fail so all leases are attempted

/src/hooks/dhcp/lease_cmds/lease_cmds_messages.mes
    Tweaked and reordered

/src/hooks/dhcp/lease_cmds/libloadtests/Makefile.am
    Added lease_cmds_unittest* here since they rely on
    loading the library

/src/hooks/dhcp/lease_cmds/libloadtests/lease_cmds4_unittest.cc
/src/hooks/dhcp/lease_cmds/libloadtests/lease_cmds6_unittest.cc
    removed new code relating to callout handler tests as they
    use lib functions directly

/src/hooks/dhcp/lease_cmds/tests/Makefile.am
    Removed lease_cmds*_unittest.* (now in libloadatests)
    Added lease_cmds_func*_unittest.*

/src/hooks/dhcp/lease_cmds/tests/binding_variables_unittest.cc
    Moved BindingVariableHandlerTest code into
    lease_cmds_func*_unittest.*

15 files changed:
src/hooks/dhcp/lease_cmds/lease_cmds.cc
src/hooks/dhcp/lease_cmds/lease_cmds.h
src/hooks/dhcp/lease_cmds/lease_cmds_messages.cc
src/hooks/dhcp/lease_cmds/lease_cmds_messages.h
src/hooks/dhcp/lease_cmds/lease_cmds_messages.mes
src/hooks/dhcp/lease_cmds/libloadtests/Makefile.am
src/hooks/dhcp/lease_cmds/libloadtests/lease_cmds4_unittest.cc [moved from src/hooks/dhcp/lease_cmds/tests/lease_cmds4_unittest.cc with 91% similarity]
src/hooks/dhcp/lease_cmds/libloadtests/lease_cmds6_unittest.cc [moved from src/hooks/dhcp/lease_cmds/tests/lease_cmds6_unittest.cc with 96% similarity]
src/hooks/dhcp/lease_cmds/libloadtests/lease_cmds_unittest.cc [moved from src/hooks/dhcp/lease_cmds/tests/lease_cmds_unittest.cc with 100% similarity]
src/hooks/dhcp/lease_cmds/libloadtests/lease_cmds_unittest.h [moved from src/hooks/dhcp/lease_cmds/tests/lease_cmds_unittest.h with 99% similarity]
src/hooks/dhcp/lease_cmds/tests/Makefile.am
src/hooks/dhcp/lease_cmds/tests/binding_variables_unittest.cc
src/hooks/dhcp/lease_cmds/tests/lease_cmds_func4_unittest.cc [new file with mode: 0644]
src/hooks/dhcp/lease_cmds/tests/lease_cmds_func6_unittest.cc [new file with mode: 0644]
src/hooks/dhcp/lease_cmds/tests/lease_cmds_func_unittest.h [new file with mode: 0644]

index ce0b6d0dae61383e7e3c003c93079904435b266b..db606ce315556c57c98a0951d5845f655ae37e0b 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2017-2024 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2017-2025 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
@@ -2833,6 +2833,8 @@ LeaseCmdsImpl::leases6Committed(CalloutHandle& callout_handle,
         return;
     }
 
+    int attempted = 0;
+    int failed = 0;
     for (auto lease : *leases) {
         try {
             /// @todo - Users might want to only update NA or PD leases.
@@ -2842,24 +2844,30 @@ LeaseCmdsImpl::leases6Committed(CalloutHandle& callout_handle,
             /// both NA and PD leases.
             // Only update a lease if its active.
             if (lease->valid_lft_) {
+                ++attempted;
                 if (mgr->evaluateVariables(query, response, lease)) {
                     LeaseMgrFactory::instance().updateLease6(lease);
                 }
             }
-        /// @todo - for now if any leases fail we stop.  This could lead
-        /// to inconsistencies in user-context content for leases belonging
-        /// the the same response. The lease6BulkApplyHandler() accumlates
-        /// failures but iterates over all the leases..
         } catch (const NoSuchLease&) {
-            isc_throw(LeaseCmdsConflict, "failed to update"
-                      " the lease with address " << lease->addr_ <<
-                      " either because the lease has been"
-                      " deleted or it has changed in the database");
+            ++failed;
+            LOG_ERROR(lease_cmds_logger, LEASE_CMDS_LEASES6_COMMITTED_CONFLICT)
+                .arg("WTF") //lease->addr_.toText())
+                .arg(query->getLabel());
         } catch (const std::exception& ex) {
-            isc_throw(Unexpected, "evaluating binding variables failed for: "
-                      << query->getLabel() << ", :" << ex.what());
+            ++failed;
+            LOG_ERROR(lease_cmds_logger, LEASE_CMDS_LEASES6_COMMITTED_LEASE_ERROR)
+                .arg(query->getLabel())
+                .arg(lease->addr_.toText())
+                .arg(ex.what());
         }
     }
+
+    if (failed) {
+        isc_throw(Unexpected, failed << " out of " << attempted
+                              << " leases failed to update for "
+                              << query->getLabel());
+    }
 }
 
 int
index 9695a5d03666a2b8d4fb5b0c02dccebad84222f8..a50699b877dd0805447c39ef8270c65d560fff0d 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2017-2023 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2017-2025 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
index 13ad0f3605c0b3ff6c021cdd98a54705b18cb0c0..815a4f145c32341d88ab0143b80aa544fc176116 100644 (file)
@@ -22,7 +22,9 @@ extern const isc::log::MessageID LEASE_CMDS_GET6_FAILED = "LEASE_CMDS_GET6_FAILE
 extern const isc::log::MessageID LEASE_CMDS_INIT_OK = "LEASE_CMDS_INIT_OK";
 extern const isc::log::MessageID LEASE_CMDS_LEASE4_OFFER_FAILED = "LEASE_CMDS_LEASE4_OFFER_FAILED";
 extern const isc::log::MessageID LEASE_CMDS_LEASES4_COMMITTED_FAILED = "LEASE_CMDS_LEASES4_COMMITTED_FAILED";
+extern const isc::log::MessageID LEASE_CMDS_LEASES6_COMMITTED_CONFLICT = "LEASE_CMDS_LEASES6_COMMITTED_CONFLICT";
 extern const isc::log::MessageID LEASE_CMDS_LEASES6_COMMITTED_FAILED = "LEASE_CMDS_LEASES6_COMMITTED_FAILED";
+extern const isc::log::MessageID LEASE_CMDS_LEASES6_COMMITTED_LEASE_ERROR = "LEASE_CMDS_LEASES6_COMMITTED_LEASE_ERROR";
 extern const isc::log::MessageID LEASE_CMDS_RESEND_DDNS4 = "LEASE_CMDS_RESEND_DDNS4";
 extern const isc::log::MessageID LEASE_CMDS_RESEND_DDNS4_FAILED = "LEASE_CMDS_RESEND_DDNS4_FAILED";
 extern const isc::log::MessageID LEASE_CMDS_RESEND_DDNS6 = "LEASE_CMDS_RESEND_DDNS6";
@@ -59,9 +61,11 @@ const char* values[] = {
     "LEASE_CMDS_GET4_FAILED", "lease4-get command failed (parameters: %1, reason: %2)",
     "LEASE_CMDS_GET6_FAILED", "lease6-get command failed (parameters: %1, reason: %2)",
     "LEASE_CMDS_INIT_OK", "loading Lease Commands hooks library successful",
-    "LEASE_CMDS_LEASE4_OFFER_FAILED", "processing error occurred evaluating binding variables:%1",
+    "LEASE_CMDS_LEASE4_OFFER_FAILED", "processing error occurred evaluating binding variables: %1",
     "LEASE_CMDS_LEASES4_COMMITTED_FAILED", "processing error occurred evaluating binding variables: %1",
-    "LEASE_CMDS_LEASES6_COMMITTED_FAILED", "processing error occurred evaluating binding variables: %1",
+    "LEASE_CMDS_LEASES6_COMMITTED_CONFLICT", "could not updating lease: %1 for: %2",
+    "LEASE_CMDS_LEASES6_COMMITTED_FAILED", "reason: %1",
+    "LEASE_CMDS_LEASES6_COMMITTED_LEASE_ERROR", "evaluating binding-variables for lease: %1 for: %2, reason: %3",
     "LEASE_CMDS_RESEND_DDNS4", "lease4-resend-ddns command successful: %1",
     "LEASE_CMDS_RESEND_DDNS4_FAILED", "lease4-resend-ddns command failed: %1",
     "LEASE_CMDS_RESEND_DDNS6", "lease6-resend-ddns command successful: %1",
index 37218828e3a84201e36388692811be0213cd4622..4a696a9100741b1238f88436be299ba195b195c0 100644 (file)
@@ -23,7 +23,9 @@ extern const isc::log::MessageID LEASE_CMDS_GET6_FAILED;
 extern const isc::log::MessageID LEASE_CMDS_INIT_OK;
 extern const isc::log::MessageID LEASE_CMDS_LEASE4_OFFER_FAILED;
 extern const isc::log::MessageID LEASE_CMDS_LEASES4_COMMITTED_FAILED;
+extern const isc::log::MessageID LEASE_CMDS_LEASES6_COMMITTED_CONFLICT;
 extern const isc::log::MessageID LEASE_CMDS_LEASES6_COMMITTED_FAILED;
+extern const isc::log::MessageID LEASE_CMDS_LEASES6_COMMITTED_LEASE_ERROR;
 extern const isc::log::MessageID LEASE_CMDS_RESEND_DDNS4;
 extern const isc::log::MessageID LEASE_CMDS_RESEND_DDNS4_FAILED;
 extern const isc::log::MessageID LEASE_CMDS_RESEND_DDNS6;
index 4edc4c316a31278d6099b5afb551f1c6488effa9..73ea4c024c0dd774cb51f6ff444e8e377eff8277 100644 (file)
@@ -71,6 +71,31 @@ parameters passed are logged.
 This info message indicates that the Lease Commands hooks library has been
 loaded successfully. Enjoy!
 
+% LEASE_CMDS_LEASE4_OFFER_FAILED processing error occurred evaluating binding variables: %1
+This error log is emitted when an error occurs in  the lease4_offer
+handler is invoked. The argument provides an explanation.
+
+% LEASE_CMDS_LEASES4_COMMITTED_FAILED processing error occurred evaluating binding variables: %1
+This error log is emitted when an error occurs in  the leases4_committed
+handler is invoked. The argument provides an explanation.
+
+% LEASE_CMDS_LEASES6_COMMITTED_CONFLICT could not updating lease: %1 for: %2
+This error log is emitted by the leases6_committed callback when attempting
+to update a lease with new binding-variable values but a conflicting change
+has occurred rendering the update invalid. The arguments provide the lease
+address and the query details.
+
+% LEASE_CMDS_LEASES6_COMMITTED_FAILED reason: %1
+This error log is emitted when one or more leases associated with a client
+query failed to be updated with binding-variable values. The argument
+provides details. Individual errors for each lease should precede this log.
+
+% LEASE_CMDS_LEASES6_COMMITTED_LEASE_ERROR evaluating binding-variables for lease: %1 for: %2, reason: %3
+This error log is emitted by the leases6_committed callback when an
+unexpected error occurs evaluating the binding-variables for a given
+lease. The arguments provide the lease address, the query details, and
+an error explanation.
+
 % LEASE_CMDS_RESEND_DDNS4 lease4-resend-ddns command successful: %1
 A request to update DNS for the requested IPv4 lease has been
 successfully queued for transmission to kea-dhcp-ddns.
@@ -138,15 +163,3 @@ The lease6-wipe command is deprecated and it will be removed in the future.
 % LEASE_CMDS_WIPE6_FAILED lease6-wipe command failed (parameters: %1, reason: %2)
 The lease6-wipe command has failed. Both the reason as well as the
 parameters passed are logged.
-
-% LEASE_CMDS_LEASE4_OFFER_FAILED processing error occurred evaluating binding variables:%1 
-This debug log is emitted when an error occurs in  the lease4_offer 
-handler is invoked. The argument provides an explanation.
-
-% LEASE_CMDS_LEASES4_COMMITTED_FAILED processing error occurred evaluating binding variables: %1
-This debug log is emitted when an error occurs in  the leases4_committed 
-handler is invoked. The argument provides an explanation.
-
-% LEASE_CMDS_LEASES6_COMMITTED_FAILED processing error occurred evaluating binding variables: %1
-This debug log is emitted when an error occurs in  the leases6_committed 
-handler is invoked. The argument provides an explanation.
index a35e8265a5620efb3d1f75adf166591c36a73f55..8bd0e6003eb8bee9e132afe6efd82de1c5853249 100644 (file)
@@ -23,6 +23,9 @@ TESTS = hook_load_unittests
 
 hook_load_unittests_SOURCES  =
 hook_load_unittests_SOURCES += load_unload_unittests.cc
+hook_load_unittests_SOURCES += lease_cmds_unittest.cc lease_cmds_unittest.h
+hook_load_unittests_SOURCES += lease_cmds4_unittest.cc
+hook_load_unittests_SOURCES += lease_cmds6_unittest.cc
 hook_load_unittests_SOURCES += run_unittests.cc
 hook_load_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES) $(LOG4CPLUS_INCLUDES)
 hook_load_unittests_CXXFLAGS = $(AM_CXXFLAGS)
similarity index 91%
rename from src/hooks/dhcp/lease_cmds/tests/lease_cmds4_unittest.cc
rename to src/hooks/dhcp/lease_cmds/libloadtests/lease_cmds4_unittest.cc
index eb32463e799881ff857f16f974339156cfa00fc2..4f3a99b8e507ab5a8d4115bee06249a4054165ce 100644 (file)
@@ -447,21 +447,6 @@ public:
 
     /// @brief Check that lease4-write works as expected.
     void testLease4Write();
-
-    /// @brief Check that lease4_offer handler works as expected with
-    /// valid inputs.
-    void testValidLease4Offer();
-
-    /// @brief Check that leases4_committed handler works as expected with
-    /// valid inputs.
-    void testValidLeases4Committed();
-
-    /// @brief Check that leases4_committed handler does not throw or alter
-    /// the lease under NOP conditions: 
-    /// 1. There is no repsonse packet
-    /// 2. Response packet is a DHCPACK 
-    /// 3. There is no active lease
-    void testNopLeases4Committed();
 };
 
 void Lease4CmdsTest::testLease4AddMissingParams() {
@@ -3500,380 +3485,6 @@ void Lease4CmdsTest::testLease4Write() {
     testCommand(txt, CONTROL_RESULT_ERROR, exp_rsp);
 }
 
-void
-Lease4CmdsTest::testValidLease4Offer() {
-    // Initialize lease manager (false = v4, true = add leases)
-    initLeaseMgr(false, true);
-
-    struct Scenario {
-        uint32_t line_;
-        std::string config_;
-        std::string orig_context_;
-        std::string exp_context_;
-        uint32_t offer_lifetime_;
-    };
-
-    std::list<Scenario> scenarios = {
-    {
-        // No variables configured, nothing in lease context.
-        __LINE__,
-        R"({})",
-        R"({})",
-        R"({})",
-        500
-    },
-    {
-        // lease context has no binding-variables, two configured
-        __LINE__,
-        R"^({"binding-variables":[
-            {
-                "name": "hwaddr",
-                "expression": "hexstring(pkt4.mac,':')",
-                "source": "query"
-            },
-            {
-                "name": "yiaddr",
-                "expression": "addrtotext(pkt4.yiaddr)",
-                "source": "response"
-            }]})^",
-        R"({})",
-        R"({"ISC":{
-            "binding-variables":{
-                "hwaddr": "01:02:03:04:05:06",
-                "yiaddr": "192.0.2.1"
-            }
-        }})",
-        500
-    },
-    {
-        // lease context has no binding-variables, two configured
-        // offer lifetime is 0.
-        __LINE__,
-        R"^({"binding-variables":[
-            {
-                "name": "hwaddr",
-                "expression": "hexstring(pkt4.mac,':')",
-                "source": "query"
-            },
-            {
-                "name": "yiaddr",
-                "expression": "addrtotext(pkt4.yiaddr)",
-                "source": "response"
-            }]})^",
-        R"({})",
-        R"({})",
-        0
-    },
-    {
-        // lease context has binding-variables, none configured
-        // Current logic leaves lease untouched.
-        __LINE__,
-        R"({})",
-        R"({"ISC":{
-            "binding-variables":{
-                "hwaddr": "01:02:03:04:05:06",
-                "yiaddr": "192.0.2.1"
-            }
-        }})",
-        R"({"ISC":{
-            "binding-variables":{
-                "hwaddr": "01:02:03:04:05:06",
-                "yiaddr": "192.0.2.1"
-            }
-        }})",
-        500
-    },
-    {
-        // Evaluated variable value is an empty string.
-        __LINE__,
-        R"^({"binding-variables":[
-            {
-                "name": "hwaddr",
-                "expression": "''",
-                "source": "query"
-            }]})^",
-        R"({"ISC":{
-            "binding-variables":{
-                "hwaddr": "01:02:03:04:05:06"
-            }
-        }})",
-        R"({"ISC":{
-            "binding-variables":{
-                "hwaddr": ""
-            }
-        }})",
-        500
-    }};
-
-    // Create packet pair and lease.
-    Pkt4Ptr query(new Pkt4(DHCPDISCOVER, 1234));
-    query->setHWAddr(1, 6, { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 });
-
-    Pkt4Ptr response(new Pkt4(DHCPOFFER, 1234));
-    IOAddress yiaddr("192.0.2.1");
-    response->setYiaddr(yiaddr);
-
-    // Iterater over scenarios.
-    for (auto const& scenario : scenarios) {
-        SCOPED_LINE(scenario.line_);
-
-        // Create and configure the manager.
-        BindingVariableMgrPtr mgr;
-        ASSERT_NO_THROW_LOG(mgr.reset(new BindingVariableMgr(AF_INET)));
-        ConstElementPtr config;
-        ASSERT_NO_THROW_LOG(config = Element::fromJSON(scenario.config_));
-        ASSERT_NO_THROW_LOG(mgr->configure(config));
-
-        // Fetch the lease and set its user-context to the original content.
-        Lease4Ptr orig_lease = lmptr_->getLease4(yiaddr);
-        ASSERT_TRUE(orig_lease);
-        ConstElementPtr orig_context;
-        ASSERT_NO_THROW_LOG(orig_context = Element::fromJSON(scenario.orig_context_));
-        orig_lease->setContext(orig_context);
-        ASSERT_NO_THROW_LOG(lmptr_->updateLease4(orig_lease));
-
-        Lease4CollectionPtr leases(new Lease4Collection());
-        leases->push_back(orig_lease);
-
-        // Create a callout handle and add the expected arguments.
-        CalloutHandlePtr callout_handle = HooksManager::createCalloutHandle();
-        callout_handle->setArgument("query4", query);
-        callout_handle->setArgument("response4", response);
-        callout_handle->setArgument("leases4", leases);
-        callout_handle->setArgument("offer_lifetime", scenario.offer_lifetime_);
-
-        // Invoke the leases4Committed handler.
-        LeaseCmds cmds;
-        ASSERT_NO_THROW_LOG(cmds.lease4Offer(*callout_handle, mgr));
-
-        // Fetch the lease.
-        Lease4Ptr after_lease = lmptr_->getLease4(yiaddr);
-        ASSERT_TRUE(after_lease);
-
-        // Context contents should match the expected context content.
-        ConstElementPtr exp_context;
-        ASSERT_NO_THROW_LOG(exp_context = Element::fromJSON(scenario.exp_context_));
-        ASSERT_EQ(*(after_lease->getContext()), *exp_context);
-    }
-}
-
-void
-Lease4CmdsTest::testValidLeases4Committed() {
-    // Initialize lease manager (false = v4, true = add leases)
-    initLeaseMgr(false, true);
-
-    struct Scenario {
-        uint32_t line_;
-        std::string config_;
-        std::string orig_context_;
-        std::string exp_context_;
-    };
-
-    std::list<Scenario> scenarios = {
-    {
-        // No variables configured, nothing in lease context.
-        __LINE__,
-        R"({})",
-        R"({})",
-        R"({})"
-    },
-    {
-        // lease context has no binding-variables, two configured
-        __LINE__,
-        R"^({"binding-variables":[
-            {
-                "name": "hwaddr",
-                "expression": "hexstring(pkt4.mac,':')",
-                "source": "query"
-            },
-            {
-                "name": "yiaddr",
-                "expression": "addrtotext(pkt4.yiaddr)",
-                "source": "response"
-            }]})^",
-        R"({})",
-        R"({"ISC":{
-            "binding-variables":{
-                "hwaddr": "01:02:03:04:05:06",
-                "yiaddr": "192.0.2.1"
-            }
-        }})",
-    },
-    {
-        // lease context has binding-variables, none configured
-        // Current logic leaves lease untouched.
-        __LINE__,
-        R"({})",
-        R"({"ISC":{
-            "binding-variables":{
-                "hwaddr": "01:02:03:04:05:06",
-                "yiaddr": "192.0.2.1"
-            }
-        }})",
-        R"({"ISC":{
-            "binding-variables":{
-                "hwaddr": "01:02:03:04:05:06",
-                "yiaddr": "192.0.2.1"
-            }
-        }})",
-    },
-    {
-        // Evaluated variable value is an empty string.
-        __LINE__,
-        R"^({"binding-variables":[
-            {
-                "name": "hwaddr",
-                "expression": "''",
-                "source": "query"
-            }]})^",
-        R"({"ISC":{
-            "binding-variables":{
-                "hwaddr": "01:02:03:04:05:06"
-            }
-        }})",
-        R"({"ISC":{
-            "binding-variables":{
-                "hwaddr": ""
-            }
-        }})",
-    }};
-
-    // Create packet pair and lease.
-    Pkt4Ptr query(new Pkt4(DHCPREQUEST, 1234));
-    query->setHWAddr(1, 6, { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 });
-
-    Pkt4Ptr response(new Pkt4(DHCPACK, 1234));
-    IOAddress yiaddr("192.0.2.1");
-    response->setYiaddr(yiaddr);
-
-    // Iterater over scenarios.
-    for (auto const& scenario : scenarios) {
-        SCOPED_LINE(scenario.line_);
-
-        // Create and configure the manager.
-        BindingVariableMgrPtr mgr;
-        ASSERT_NO_THROW_LOG(mgr.reset(new BindingVariableMgr(AF_INET)));
-        ConstElementPtr config;
-        ASSERT_NO_THROW_LOG(config = Element::fromJSON(scenario.config_));
-        ASSERT_NO_THROW_LOG(mgr->configure(config));
-
-        // Fetch the lease and set its user-context to the original content.
-        Lease4Ptr orig_lease = lmptr_->getLease4(yiaddr);
-        ASSERT_TRUE(orig_lease);
-        ConstElementPtr orig_context;
-        ASSERT_NO_THROW_LOG(orig_context = Element::fromJSON(scenario.orig_context_));
-        orig_lease->setContext(orig_context);
-        ASSERT_NO_THROW_LOG(lmptr_->updateLease4(orig_lease));
-
-        Lease4CollectionPtr leases(new Lease4Collection());
-        leases->push_back(orig_lease);
-
-        // Create a callout handle and add the expected arguments.
-        CalloutHandlePtr callout_handle = HooksManager::createCalloutHandle();
-        callout_handle->setArgument("query4", query);
-        callout_handle->setArgument("response4", response);
-        callout_handle->setArgument("leases4", leases);
-
-        // Invoke the leases4Committed handler.
-        LeaseCmds cmds;
-        ASSERT_NO_THROW_LOG(cmds.leases4Committed(*callout_handle, mgr));
-
-        // Fetch the lease.
-        Lease4Ptr after_lease = lmptr_->getLease4(yiaddr);
-        ASSERT_TRUE(after_lease);
-
-        // Context contents should match the expected context content.
-        ConstElementPtr exp_context;
-        ASSERT_NO_THROW_LOG(exp_context = Element::fromJSON(scenario.exp_context_));
-        ASSERT_EQ(*(after_lease->getContext()), *exp_context);
-    }
-}
-
-void
-Lease4CmdsTest::testNopLeases4Committed() {
-    // Initialize lease manager (false = v4, true = add leases)
-    initLeaseMgr(false, true);
-
-    struct Scenario {
-        uint32_t line_;
-        DHCPMessageType response_type_;
-        bool send_lease_;
-    };
-
-    // Configure a single variable.
-    std::string config =
-    R"^({"binding-variables":[
-    {
-        "name": "hwaddr",
-        "expression": "hexstring(pkt4.mac,':')",
-        "source": "query"
-    }]})^";
-
-    // Create and configure the manager.
-    BindingVariableMgrPtr mgr;
-    ASSERT_NO_THROW_LOG(mgr.reset(new BindingVariableMgr(AF_INET)));
-    ASSERT_NO_THROW_LOG(mgr->configure(Element::fromJSON(config)));
-
-    // Scenarios should all result in no change to the lease.
-    std::list<Scenario> scenarios = {
-    {
-        // Response is not a DHCPACK.
-        __LINE__,
-        DHCPNAK,
-        true
-    },
-    {
-        // No active lease.
-        __LINE__,
-        DHCPACK,
-        false
-    },
-    {
-        // No response.
-        __LINE__,
-        DHCP_NOTYPE,
-        false
-    }};
-
-    // Create packet pair and lease.
-    Pkt4Ptr query(new Pkt4(DHCPREQUEST, 1234));
-    query->setHWAddr(1, 6, { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 });
-    IOAddress yiaddr("192.0.2.1");
-    for (auto const& scenario : scenarios) {
-        SCOPED_LINE(scenario.line_);
-
-        Pkt4Ptr response;
-        if (scenario.response_type_ != DHCP_NOTYPE) {
-            response.reset(new Pkt4(scenario.response_type_, 1234));
-        }
-
-        // Fetch the lease and set its user-context to the original content.
-        Lease4Ptr orig_lease = lmptr_->getLease4(yiaddr);
-        ASSERT_TRUE(orig_lease);
-        ASSERT_FALSE(orig_lease->getContext());
-
-        Lease4CollectionPtr leases(new Lease4Collection());
-        if (scenario.send_lease_) {
-            leases->push_back(orig_lease);
-        }
-
-        // Create a callout handle and add the expected arguments.
-        CalloutHandlePtr callout_handle = HooksManager::createCalloutHandle();
-        callout_handle->setArgument("query4", query);
-        callout_handle->setArgument("response4", response);
-        callout_handle->setArgument("leases4", leases);
-
-        // Invoke the leases4Committed handler.
-        LeaseCmds cmds;
-        ASSERT_NO_THROW_LOG(cmds.leases4Committed(*callout_handle, mgr));
-
-        // Fetch the lease. Context should still be empty.
-        Lease4Ptr after_lease = lmptr_->getLease4(yiaddr);
-        ASSERT_TRUE(after_lease);
-        ASSERT_FALSE(after_lease->getContext());
-    }
-}
-
 TEST_F(Lease4CmdsTest, lease4AddMissingParams) {
     testLease4AddMissingParams();
 }
@@ -4602,30 +4213,4 @@ TEST_F(Lease4CmdsTest, lease4WriteMultiThreading) {
     testLease4Write();
 }
 
-TEST_F(Lease4CmdsTest, validLease4Offer) {
-    testValidLease4Offer();
-}
-
-TEST_F(Lease4CmdsTest, validLease4OfferMultiThreading) {
-    MultiThreadingTest mt(true);
-    testValidLease4Offer();
-}
-
-TEST_F(Lease4CmdsTest, validLeases4Committed) {
-    testValidLeases4Committed();
-}
-
-TEST_F(Lease4CmdsTest, validLeases4CommittedMultiThreading) {
-    MultiThreadingTest mt(true);
-    testValidLeases4Committed();
-}
-
-TEST_F(Lease4CmdsTest, nopLeases4Committed) {
-    testNopLeases4Committed();
-}
-
-TEST_F(Lease4CmdsTest, nopLeases4CommittedMultiThreading) {
-    MultiThreadingTest mt(true);
-    testNopLeases4Committed();
-}
 } // end of anonymous namespace
similarity index 96%
rename from src/hooks/dhcp/lease_cmds/tests/lease_cmds6_unittest.cc
rename to src/hooks/dhcp/lease_cmds/libloadtests/lease_cmds6_unittest.cc
index f2388cdb53895ee86d29b30e7a29c08c5ec6defa..29ec92aff47d85aa289a8dc8fc7acf7022171fff 100644 (file)
@@ -481,6 +481,7 @@ public:
     /// @brief Check that lease6-write works as expected.
     void testLease6Write();
 
+#if 0
     /// @brief Check that leases6_committed handler works as expected with
     /// valid inputs.
     void testValidLeases6Committed();
@@ -491,6 +492,9 @@ public:
     /// 2. There are no leases
     /// 3. There are leases but none active
     void testNopLeases6Committed();
+
+    void testLeases6CommittedErrors();
+#endif
 };
 
 void Lease6CmdsTest::testLease6AddMissingParams() {
@@ -4232,9 +4236,10 @@ void Lease6CmdsTest::testLease6Write() {
     testCommand(txt, CONTROL_RESULT_ERROR, exp_rsp);
 }
 
+#if 0
 void
 Lease6CmdsTest::testValidLeases6Committed() {
-    // Initialize lease manager (false = v4, true = add leases)
+    // Initialize lease manager (true = v6, true = add leases)
     initLeaseMgr(true, true);
 
     struct Scenario {
@@ -4253,7 +4258,7 @@ Lease6CmdsTest::testValidLeases6Committed() {
         R"({})"
     },
     {
-        // lease context has no binding-variables, two configured
+        // Lease context has no binding-variables, two configured.
         __LINE__,
         R"^({"binding-variables":[
             {
@@ -4275,7 +4280,7 @@ Lease6CmdsTest::testValidLeases6Committed() {
         }})",
     },
     {
-        // lease context has binding-variables, none configured
+        // Lease context has binding-variables, none configured.
         // Current logic leaves lease untouched.
         __LINE__,
         R"({})",
@@ -4316,7 +4321,6 @@ Lease6CmdsTest::testValidLeases6Committed() {
 
     // Create packet pair and lease.
     Pkt6Ptr query(new Pkt6(DHCPV6_REQUEST, 1234));
-    // Add the client id.
     OptionPtr client_id(new Option(Option::V6, D6O_CLIENTID,
                                    { 0x01, 0x02, 0x03, 0x04 }));
     query->addOption(client_id);
@@ -4326,7 +4330,11 @@ Lease6CmdsTest::testValidLeases6Committed() {
                                        { 0x05, 0x06, 0x07, 0x08 }));
     response->addOption(subscriber_id);
 
-    IOAddress na_addr("2001:db8:1::1");
+    // Create a list of the lease addresses.
+    std::list<IOAddress> lease_addrs;
+    lease_addrs.push_back(IOAddress("2001:db8:1::1"));
+    lease_addrs.push_back(IOAddress("2001:db8:1::2"));
+    lease_addrs.push_back(IOAddress("2001:db8:2::2"));
 
     // Iterater over scenarios.
     for (auto const& scenario : scenarios) {
@@ -4340,42 +4348,46 @@ Lease6CmdsTest::testValidLeases6Committed() {
         ASSERT_NO_THROW_LOG(mgr->configure(config));
 
         // Fetch the lease and set its user-context to the original content.
-        Lease6Ptr orig_lease = lmptr_->getLease6(Lease::TYPE_NA, na_addr);
-        ASSERT_TRUE(orig_lease);
-        ASSERT_TRUE(orig_lease->valid_lft_ > 0);
-
-        ConstElementPtr orig_context;
-        ASSERT_NO_THROW_LOG(orig_context = Element::fromJSON(scenario.orig_context_));
-        orig_lease->setContext(orig_context);
-        ASSERT_NO_THROW_LOG(lmptr_->updateLease6(orig_lease));
-
-        Lease6CollectionPtr leases(new Lease6Collection());
-        leases->push_back(orig_lease);
+        Lease6CollectionPtr orig_leases(new Lease6Collection());
+        for (auto const& address : lease_addrs) {
+            Lease6Ptr orig_lease = lmptr_->getLease6(Lease::TYPE_NA, address);
+            ASSERT_TRUE(orig_lease);
+            ASSERT_TRUE(orig_lease->valid_lft_ > 0);
+
+            ConstElementPtr orig_context;
+            ASSERT_NO_THROW_LOG(orig_context = Element::fromJSON(scenario.orig_context_));
+            orig_lease->setContext(orig_context);
+            ASSERT_NO_THROW_LOG(lmptr_->updateLease6(orig_lease));
+            orig_leases->push_back(orig_lease);
+        }
 
         // Create a callout handle and add the expected arguments.
         CalloutHandlePtr callout_handle = HooksManager::createCalloutHandle();
         callout_handle->setArgument("query6", query);
         callout_handle->setArgument("response6", response);
-        callout_handle->setArgument("leases6", leases);
+        callout_handle->setArgument("leases6", orig_leases);
 
-        // Invoke the leases4Committed handler.
+        // Invoke the leases6Committed handler.
         LeaseCmds cmds;
         ASSERT_NO_THROW_LOG(cmds.leases6Committed(*callout_handle, mgr));
 
-        // Fetch the lease.
-        Lease6Ptr after_lease = lmptr_->getLease6(Lease::TYPE_NA, na_addr);
-        ASSERT_TRUE(after_lease);
+        // Iterate over the leases and make sure the user-contexts are as expected.
+        for (auto const& lease : *orig_leases) {
+            // Fetch the lease.
+            Lease6Ptr after_lease = lmptr_->getLease6(Lease::TYPE_NA, lease->addr_);
+            ASSERT_TRUE(after_lease);
 
-        // Context contents should match the expected context content.
-        ConstElementPtr exp_context;
-        ASSERT_NO_THROW_LOG(exp_context = Element::fromJSON(scenario.exp_context_));
-        ASSERT_EQ(*(after_lease->getContext()), *exp_context);
+            // Context contents should match the expected context content.
+            ConstElementPtr exp_context;
+            ASSERT_NO_THROW_LOG(exp_context = Element::fromJSON(scenario.exp_context_));
+            ASSERT_EQ(*(after_lease->getContext()), *exp_context);
+        }
     }
 }
 
 void
 Lease6CmdsTest::testNopLeases6Committed() {
-    // Initialize lease manager (false = v4, true = add leases)
+    // Initialize lease manager (true = v6, true = add leases)
     initLeaseMgr(true, true);
 
     struct Scenario {
@@ -4433,6 +4445,7 @@ Lease6CmdsTest::testNopLeases6Committed() {
     for (auto const& scenario : scenarios) {
         SCOPED_LINE(scenario.line_);
 
+        // Create the response packet, if one.
         Pkt6Ptr response;
         if (scenario.response_type_ != DHCPV6_NOTYPE) {
             response.reset(new Pkt6(scenario.response_type_, 1234));
@@ -4456,7 +4469,7 @@ Lease6CmdsTest::testNopLeases6Committed() {
         callout_handle->setArgument("response6", response);
         callout_handle->setArgument("leases6", leases);
 
-        // Invoke the leases4Committed handler.
+        // Invoke the leases6Committed handler.
         LeaseCmds cmds;
         ASSERT_NO_THROW_LOG(cmds.leases6Committed(*callout_handle, mgr));
 
@@ -4467,6 +4480,100 @@ Lease6CmdsTest::testNopLeases6Committed() {
     }
 }
 
+void
+Lease6CmdsTest::testLeases6CommittedErrors() {
+    // Initialize lease manager (false = v4, true = add leases)
+    initLeaseMgr(true, true);
+
+    // Create a config with two binding variables.
+    std::string config_str =
+        R"^({"binding-variables":[
+            {
+                "name": "duid",
+                "expression": "hexstring(option[1].hex,':')",
+                "source": "query"
+            },
+            {
+                "name": "sub-id",
+                "expression": "hexstring(option[38].hex, ':')",
+                "source": "response"
+            }]})^";
+
+    ConstElementPtr config;
+    ASSERT_NO_THROW_LOG(config = Element::fromJSON(config_str));
+
+    // Create the expected context contents.
+    std::string exp_context_str =
+        R"({"ISC":{
+            "binding-variables":{
+                "duid": "01:02:03:04",
+                "sub-id": "05:06:07:08"
+            }
+        }})";
+
+
+    ConstElementPtr exp_context;
+    ASSERT_NO_THROW_LOG(exp_context = Element::fromJSON(exp_context_str));
+
+    // Create packet pair and lease.
+    Pkt6Ptr query(new Pkt6(DHCPV6_REQUEST, 1234));
+    OptionPtr client_id(new Option(Option::V6, D6O_CLIENTID,
+                                   { 0x01, 0x02, 0x03, 0x04 }));
+    query->addOption(client_id);
+
+    Pkt6Ptr response(new Pkt6(DHCPV6_REPLY, 7890));
+    OptionPtr subscriber_id(new Option(Option::V6, D6O_SUBSCRIBER_ID,
+                                       { 0x05, 0x06, 0x07, 0x08 }));
+    response->addOption(subscriber_id);
+
+    // Create and configure the manager.
+    BindingVariableMgrPtr mgr;
+    ASSERT_NO_THROW_LOG(mgr.reset(new BindingVariableMgr(AF_INET6)));
+    ASSERT_NO_THROW_LOG(mgr->configure(config));
+
+    // Fetch the leases.
+    std::list<IOAddress> lease_addrs;
+    lease_addrs.push_back(IOAddress("2001:db8:1::1"));
+    lease_addrs.push_back(IOAddress("2001:db8:1::2"));
+    lease_addrs.push_back(IOAddress("2001:db8:2::2"));
+
+    Lease6CollectionPtr orig_leases(new Lease6Collection());
+    for (auto const& address : lease_addrs) {
+        Lease6Ptr orig_lease = lmptr_->getLease6(Lease::TYPE_NA, address);
+        ASSERT_TRUE(orig_lease);
+        orig_leases->push_back(orig_lease);
+    }
+
+    // Delete the middle lease from the back end. This should cause a conflict error.
+    ASSERT_NO_THROW_LOG(lmptr_->deleteLease((*orig_leases)[1]));
+
+    // Create a callout handle and add the expected arguments.
+    CalloutHandlePtr callout_handle = HooksManager::createCalloutHandle();
+    callout_handle->setArgument("query6", query);
+    callout_handle->setArgument("response6", response);
+    callout_handle->setArgument("leases6", orig_leases);
+
+    // Invoke the leases6Committed handler.
+    LeaseCmds cmds;
+    ASSERT_THROW_MSG(cmds.leases6Committed(*callout_handle, mgr),
+                     Unexpected,
+                     "1 out of 3 leases failed to update for "
+                     "duid=[01:02:03:04], [no hwaddr info], tid=0x4d2");
+
+    for (auto const& lease : *orig_leases) {
+        // Fetch the lease.
+        Lease6Ptr after_lease = lmptr_->getLease6(Lease::TYPE_NA, lease->addr_);
+        if (after_lease) {
+            // Context contents should match the expected context content.
+            ASSERT_EQ(*(after_lease->getContext()), *exp_context);
+        } else {
+            // Middle lease should not be found.
+            EXPECT_EQ(lease->addr_, (*orig_leases)[1]->addr_);
+        }
+    }
+}
+#endif
+
 TEST_F(Lease6CmdsTest, lease6AddMissingParams) {
     testLease6AddMissingParams();
 }
@@ -5248,6 +5355,7 @@ TEST_F(Lease6CmdsTest, lease6WriteMultiThreading) {
     testLease6Write();
 }
 
+#if 0
 TEST_F(Lease6CmdsTest, validLeases6Committed) {
     testValidLeases6Committed();
 }
@@ -5266,4 +5374,14 @@ TEST_F(Lease6CmdsTest, nopLeases6CommittedMultiThreading) {
     testNopLeases6Committed();
 }
 
+TEST_F(Lease6CmdsTest, leases6CommittedErrors) {
+    testLeases6CommittedErrors();
+}
+
+TEST_F(Lease6CmdsTest, leases6CommittedErrorsMultiThreading) {
+    MultiThreadingTest mt(true);
+    testLeases6CommittedErrors();
+}
+#endif
+
 } // end of anonymous namespace
similarity index 99%
rename from src/hooks/dhcp/lease_cmds/tests/lease_cmds_unittest.h
rename to src/hooks/dhcp/lease_cmds/libloadtests/lease_cmds_unittest.h
index 55635f55e0e2d67ac9e37c1082b91c05d6b32ebc..759d1980dfea38643a1b82c20d25a299d5d5273c 100644 (file)
@@ -276,7 +276,7 @@ public:
     /// Also ensured there is no lease manager leftovers from previous
     /// test.
     LeaseCmdsTest()
-        : LibLoadTest(LEASE_CMDS_LIB_SO),
+        : LibLoadTest(LIBDHCP_LEASE_CMDS_SO),
         d2_mgr_(isc::dhcp::CfgMgr::instance().getD2ClientMgr()) {
         isc::dhcp::LeaseMgrFactory::destroy();
         enableD2();
index 5c947eefc2e9e2c6ff7f1b59f846f330ed4a1355..6e440e87c367f97c22c8180bad1df34eba49ccad 100644 (file)
@@ -27,10 +27,10 @@ if HAVE_GTEST
 TESTS += lease_cmds_unittests
 
 lease_cmds_unittests_SOURCES = run_unittests.cc
-lease_cmds_unittests_SOURCES += lease_cmds_unittest.h lease_cmds_unittest.cc
-lease_cmds_unittests_SOURCES += lease_cmds4_unittest.cc
-lease_cmds_unittests_SOURCES += lease_cmds6_unittest.cc
 lease_cmds_unittests_SOURCES += binding_variables_unittest.cc
+lease_cmds_unittests_SOURCES += lease_cmds_func_unittest.h
+lease_cmds_unittests_SOURCES += lease_cmds_func4_unittest.cc
+lease_cmds_unittests_SOURCES += lease_cmds_func6_unittest.cc
 
 lease_cmds_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES) $(LOG4CPLUS_INCLUDES)
 
index 184cd47e475b573d28478e9e05cd8abf0caf0156..68fd8897cab99b1f889b606e3c71d01e8de6c03e 100644 (file)
@@ -6,7 +6,10 @@
 
 #include <config.h>
 
-#include <binding_variables.h>
+#include <lease_cmds.h>
+#include <hooks/hooks_manager.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/lease_mgr_factory.h>
 #include <exceptions/exceptions.h>
 #include <cc/data.h>
 #include <dhcp/dhcp6.h>
@@ -817,4 +820,881 @@ TEST(BindingVariableMgrTest, evaluateVariables6) {
     }
 }
 
+#if 0
+class BindingVariableHandlerTest :  public ::testing::Test { 
+public:
+
+    /// @brief Pointer to the lease manager
+    isc::dhcp::LeaseMgr* lmptr_;
+
+    /// @brief Constructor
+    BindingVariableHandlerTest() {
+        isc::dhcp::LeaseMgrFactory::destroy();
+        lmptr_ = 0;
+    }
+
+    /// @brief Destructor
+    ///
+    virtual ~BindingVariableHandlerTest() {
+        isc::dhcp::LeaseMgrFactory::destroy();
+        lmptr_ = 0;
+    }
+
+    /// @brief Creates an IPv4 lease
+    ///
+    /// Lease parameters: valid lifetime = 0xFFFFFFFE, cltt = 1923222072, fqdn-fwd = false,
+    /// fqdn-rev = true, hostname = myhost.example.com
+    ///
+    /// @param ip_address IP address for the lease.
+    /// @param subnet_id subnet identifier
+    /// @param hw_address_pattern value to be used for generating HW address by repeating
+    /// it 6 times.
+    /// @param client_id_pattern value to be used for generating client identifier by
+    /// repeating it 8 times.
+    /// @param declined controls whether the lease should be in declined state.
+    ///
+    /// @return Returns the lease created
+    isc::dhcp::Lease4Ptr createLease4(const std::string& ip_address,
+                                      const isc::dhcp::SubnetID& subnet_id,
+                                      const uint8_t hw_address_pattern,
+                                      const uint8_t client_id_pattern,
+                                      uint32_t pool_id = 0) {
+        isc::dhcp::Lease4Ptr lease(new isc::dhcp::Lease4());
+
+        lease->addr_ = isc::asiolink::IOAddress(ip_address);
+
+        lease->hwaddr_.reset(new isc::dhcp::HWAddr(std::vector<uint8_t>(6, hw_address_pattern), isc::dhcp::HTYPE_ETHER));
+        lease->client_id_ = isc::dhcp::ClientIdPtr(new isc::dhcp::ClientId(std::vector<uint8_t>(8, client_id_pattern)));
+        lease->valid_lft_ = 2400;
+        lease->updateCurrentExpirationTime();
+        lease->subnet_id_ = subnet_id;
+        lease->pool_id_ = pool_id;
+        return (lease);
+    }
+
+    /// @brief Creates an IPv6 lease
+    ///
+    /// @param ip_address IP address for the lease.
+    /// @param subnet_id subnet identifier
+    /// @param duid_address_pattern value to be used for generating DUID by
+    /// repeating it 8 times
+    /// @param declined controls whether the lease should be in declined state.
+    ///
+    /// @return Returns the lease created
+    isc::dhcp::Lease6Ptr createLease6(const std::string& ip_address,
+                                      const isc::dhcp::SubnetID& subnet_id,
+                                      const uint8_t duid_pattern,
+                                      uint32_t pool_id = 0) {
+        isc::dhcp::Lease6Ptr lease(new isc::dhcp::Lease6());
+
+        lease->addr_ = isc::asiolink::IOAddress(ip_address);
+        lease->type_ = isc::dhcp::Lease::TYPE_NA;
+        lease->prefixlen_ = 128;
+        lease->iaid_ = 42;
+        lease->duid_ = isc::dhcp::DuidPtr(new isc::dhcp::DUID(std::vector<uint8_t>(8, duid_pattern)));
+        lease->preferred_lft_ = 1800;
+        lease->valid_lft_ = 2400;
+        lease->subnet_id_ = subnet_id;
+        lease->pool_id_ = pool_id;
+
+        return (lease);
+    }
+
+    /// @brief Initializes lease manager (and optionally populates it with a lease)
+    ///
+    /// Creates a lease manager (memfile, trimmed down to keep everything in memory
+    /// only) and optionally can create a lease, which is useful for leaseX-get and
+    /// leaseX-del type of tests. For lease details, see @ref createLease4 and
+    /// @ref createLease6.
+    ///
+    /// @param famiily protocol family AF_INET or AF_INET6
+    /// @param insert_lease governs whether a lease should be pre-inserted
+    /// @param declined governs whether a lease should be in declined state
+    void initLeaseMgr(int family) {
+        isc::dhcp::LeaseMgrFactory::destroy();
+        if (family == AF_INET6) {
+            isc::dhcp::LeaseMgrFactory::create("type=memfile persist=false " 
+                                               "universe=6 extended-info-tables=true");
+            lmptr_ = &(isc::dhcp::LeaseMgrFactory::instance());
+            ASSERT_TRUE(lmptr_);
+            lmptr_->addLease(createLease6("2001:db8:1::1", 66, 0x42));
+            lmptr_->addLease(createLease6("2001:db8:1::2", 66, 0x56));
+            lmptr_->addLease(createLease6("2001:db8:2::1", 99, 0x42));
+            lmptr_->addLease(createLease6("2001:db8:2::2", 99, 0x56));
+        } else {
+            isc::dhcp::LeaseMgrFactory::create("type=memfile persist=false universe=4");
+            lmptr_ = &(isc::dhcp::LeaseMgrFactory::instance());
+            ASSERT_TRUE(lmptr_);
+            lmptr_->addLease(createLease4("192.0.2.1", 44, 0x08, 0x42));
+            lmptr_->addLease(createLease4("192.0.2.2", 44, 0x09, 0x56));
+            lmptr_->addLease(createLease4("192.0.3.1", 88, 0x08, 0x42));
+            lmptr_->addLease(createLease4("192.0.3.2", 88, 0x09, 0x56));
+        }
+    }
+
+#if 0
+    /// @brief Check that lease4_offer handler works as expected with
+    /// valid inputs.
+    void testValidLease4Offer();
+
+    /// @brief Check that leases4_committed handler works as expected with
+    /// valid inputs.
+    void testValidLeases4Committed();
+
+    /// @brief Check that leases4_committed handler does not throw or alter
+    /// the lease under NOP conditions: 
+    /// 1. There is no repsonse packet
+    /// 2. Response packet is a DHCPACK 
+    /// 3. There is no active lease
+    void testNopLeases4Committed();
+#endif
+
+    /// @brief Check that leases6_committed handler works as expected with
+    /// valid inputs.
+    void testValidLeases6Committed();
+
+    /// @brief Check that leases6_committed handler does not throw or alter
+    /// leases under NOP conditions:
+    /// 1. There is no repsonse packet
+    /// 2. There are no leases
+    /// 3. There are leases but none active
+    void testNopLeases6Committed();
+
+    void testLeases6CommittedErrors();
+};
+
+#if 0
+void
+BindingVariableHandlerTest::testValidLease4Offer() {
+    initLeaseMgr(AF_INET);
+
+    struct Scenario {
+        uint32_t line_;
+        std::string config_;
+        std::string orig_context_;
+        std::string exp_context_;
+        uint32_t offer_lifetime_;
+    };
+
+    std::list<Scenario> scenarios = {
+    {
+        // No variables configured, nothing in lease context.
+        __LINE__,
+        R"({})",
+        R"({})",
+        R"({})",
+        500
+    },
+    {
+        // lease context has no binding-variables, two configured
+        __LINE__,
+        R"^({"binding-variables":[
+            {
+                "name": "hwaddr",
+                "expression": "hexstring(pkt4.mac,':')",
+                "source": "query"
+            },
+            {
+                "name": "yiaddr",
+                "expression": "addrtotext(pkt4.yiaddr)",
+                "source": "response"
+            }]})^",
+        R"({})",
+        R"({"ISC":{
+            "binding-variables":{
+                "hwaddr": "01:02:03:04:05:06",
+                "yiaddr": "192.0.2.1"
+            }
+        }})",
+        500
+    },
+    {
+        // lease context has no binding-variables, two configured
+        // offer lifetime is 0.
+        __LINE__,
+        R"^({"binding-variables":[
+            {
+                "name": "hwaddr",
+                "expression": "hexstring(pkt4.mac,':')",
+                "source": "query"
+            },
+            {
+                "name": "yiaddr",
+                "expression": "addrtotext(pkt4.yiaddr)",
+                "source": "response"
+            }]})^",
+        R"({})",
+        R"({})",
+        0
+    },
+    {
+        // lease context has binding-variables, none configured
+        // Current logic leaves lease untouched.
+        __LINE__,
+        R"({})",
+        R"({"ISC":{
+            "binding-variables":{
+                "hwaddr": "01:02:03:04:05:06",
+                "yiaddr": "192.0.2.1"
+            }
+        }})",
+        R"({"ISC":{
+            "binding-variables":{
+                "hwaddr": "01:02:03:04:05:06",
+                "yiaddr": "192.0.2.1"
+            }
+        }})",
+        500
+    },
+    {
+        // Evaluated variable value is an empty string.
+        __LINE__,
+        R"^({"binding-variables":[
+            {
+                "name": "hwaddr",
+                "expression": "''",
+                "source": "query"
+            }]})^",
+        R"({"ISC":{
+            "binding-variables":{
+                "hwaddr": "01:02:03:04:05:06"
+            }
+        }})",
+        R"({"ISC":{
+            "binding-variables":{
+                "hwaddr": ""
+            }
+        }})",
+        500
+    }};
+
+    // Create packet pair and lease.
+    Pkt4Ptr query(new Pkt4(DHCPDISCOVER, 1234));
+    query->setHWAddr(1, 6, { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 });
+
+    Pkt4Ptr response(new Pkt4(DHCPOFFER, 1234));
+    IOAddress yiaddr("192.0.2.1");
+    response->setYiaddr(yiaddr);
+
+    // Iterater over scenarios.
+    for (auto const& scenario : scenarios) {
+        SCOPED_LINE(scenario.line_);
+
+        // Create and configure the manager.
+        BindingVariableMgrPtr mgr;
+        ASSERT_NO_THROW_LOG(mgr.reset(new BindingVariableMgr(AF_INET)));
+        ConstElementPtr config;
+        ASSERT_NO_THROW_LOG(config = Element::fromJSON(scenario.config_));
+        ASSERT_NO_THROW_LOG(mgr->configure(config));
+
+        // Fetch the lease and set its user-context to the original content.
+        Lease4Ptr orig_lease = lmptr_->getLease4(yiaddr);
+        ASSERT_TRUE(orig_lease);
+        ConstElementPtr orig_context;
+        ASSERT_NO_THROW_LOG(orig_context = Element::fromJSON(scenario.orig_context_));
+        orig_lease->setContext(orig_context);
+        ASSERT_NO_THROW_LOG(lmptr_->updateLease4(orig_lease));
+
+        Lease4CollectionPtr leases(new Lease4Collection());
+        leases->push_back(orig_lease);
+
+        // Create a callout handle and add the expected arguments.
+        CalloutHandlePtr callout_handle = HooksManager::createCalloutHandle();
+        callout_handle->setArgument("query4", query);
+        callout_handle->setArgument("response4", response);
+        callout_handle->setArgument("leases4", leases);
+        callout_handle->setArgument("offer_lifetime", scenario.offer_lifetime_);
+
+        // Invoke the leases4Committed handler.
+        LeaseCmds cmds;
+        ASSERT_NO_THROW_LOG(cmds.lease4Offer(*callout_handle, mgr));
+
+        // Fetch the lease.
+        Lease4Ptr after_lease = lmptr_->getLease4(yiaddr);
+        ASSERT_TRUE(after_lease);
+
+        // Context contents should match the expected context content.
+        ConstElementPtr exp_context;
+        ASSERT_NO_THROW_LOG(exp_context = Element::fromJSON(scenario.exp_context_));
+        ASSERT_EQ(*(after_lease->getContext()), *exp_context);
+    }
+}
+
+void
+BindingVariableHandlerTest::testValidLeases4Committed() {
+    initLeaseMgr(AF_INET);
+
+    struct Scenario {
+        uint32_t line_;
+        std::string config_;
+        std::string orig_context_;
+        std::string exp_context_;
+    };
+
+    std::list<Scenario> scenarios = {
+    {
+        // No variables configured, nothing in lease context.
+        __LINE__,
+        R"({})",
+        R"({})",
+        R"({})"
+    },
+    {
+        // lease context has no binding-variables, two configured
+        __LINE__,
+        R"^({"binding-variables":[
+            {
+                "name": "hwaddr",
+                "expression": "hexstring(pkt4.mac,':')",
+                "source": "query"
+            },
+            {
+                "name": "yiaddr",
+                "expression": "addrtotext(pkt4.yiaddr)",
+                "source": "response"
+            }]})^",
+        R"({})",
+        R"({"ISC":{
+            "binding-variables":{
+                "hwaddr": "01:02:03:04:05:06",
+                "yiaddr": "192.0.2.1"
+            }
+        }})",
+    },
+    {
+        // lease context has binding-variables, none configured
+        // Current logic leaves lease untouched.
+        __LINE__,
+        R"({})",
+        R"({"ISC":{
+            "binding-variables":{
+                "hwaddr": "01:02:03:04:05:06",
+                "yiaddr": "192.0.2.1"
+            }
+        }})",
+        R"({"ISC":{
+            "binding-variables":{
+                "hwaddr": "01:02:03:04:05:06",
+                "yiaddr": "192.0.2.1"
+            }
+        }})",
+    },
+    {
+        // Evaluated variable value is an empty string.
+        __LINE__,
+        R"^({"binding-variables":[
+            {
+                "name": "hwaddr",
+                "expression": "''",
+                "source": "query"
+            }]})^",
+        R"({"ISC":{
+            "binding-variables":{
+                "hwaddr": "01:02:03:04:05:06"
+            }
+        }})",
+        R"({"ISC":{
+            "binding-variables":{
+                "hwaddr": ""
+            }
+        }})",
+    }};
+
+    // Create packet pair and lease.
+    Pkt4Ptr query(new Pkt4(DHCPREQUEST, 1234));
+    query->setHWAddr(1, 6, { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 });
+
+    Pkt4Ptr response(new Pkt4(DHCPACK, 1234));
+    IOAddress yiaddr("192.0.2.1");
+    response->setYiaddr(yiaddr);
+
+    // Iterater over scenarios.
+    for (auto const& scenario : scenarios) {
+        SCOPED_LINE(scenario.line_);
+
+        // Create and configure the manager.
+        BindingVariableMgrPtr mgr;
+        ASSERT_NO_THROW_LOG(mgr.reset(new BindingVariableMgr(AF_INET)));
+        ConstElementPtr config;
+        ASSERT_NO_THROW_LOG(config = Element::fromJSON(scenario.config_));
+        ASSERT_NO_THROW_LOG(mgr->configure(config));
+
+        // Fetch the lease and set its user-context to the original content.
+        Lease4Ptr orig_lease = lmptr_->getLease4(yiaddr);
+        ASSERT_TRUE(orig_lease);
+        ConstElementPtr orig_context;
+        ASSERT_NO_THROW_LOG(orig_context = Element::fromJSON(scenario.orig_context_));
+        orig_lease->setContext(orig_context);
+        ASSERT_NO_THROW_LOG(lmptr_->updateLease4(orig_lease));
+
+        Lease4CollectionPtr leases(new Lease4Collection());
+        leases->push_back(orig_lease);
+
+        // Create a callout handle and add the expected arguments.
+        CalloutHandlePtr callout_handle = HooksManager::createCalloutHandle();
+        callout_handle->setArgument("query4", query);
+        callout_handle->setArgument("response4", response);
+        callout_handle->setArgument("leases4", leases);
+
+        // Invoke the leases4Committed handler.
+        LeaseCmds cmds;
+        ASSERT_NO_THROW_LOG(cmds.leases4Committed(*callout_handle, mgr));
+
+        // Fetch the lease.
+        Lease4Ptr after_lease = lmptr_->getLease4(yiaddr);
+        ASSERT_TRUE(after_lease);
+
+        // Context contents should match the expected context content.
+        ConstElementPtr exp_context;
+        ASSERT_NO_THROW_LOG(exp_context = Element::fromJSON(scenario.exp_context_));
+        ASSERT_EQ(*(after_lease->getContext()), *exp_context);
+    }
+}
+
+void
+BindingVariableHandlerTest::testNopLeases4Committed() {
+    initLeaseMgr(AF_INET);
+
+    struct Scenario {
+        uint32_t line_;
+        DHCPMessageType response_type_;
+        bool send_lease_;
+    };
+
+    // Configure a single variable.
+    std::string config =
+    R"^({"binding-variables":[
+    {
+        "name": "hwaddr",
+        "expression": "hexstring(pkt4.mac,':')",
+        "source": "query"
+    }]})^";
+
+    // Create and configure the manager.
+    BindingVariableMgrPtr mgr;
+    ASSERT_NO_THROW_LOG(mgr.reset(new BindingVariableMgr(AF_INET)));
+    ASSERT_NO_THROW_LOG(mgr->configure(Element::fromJSON(config)));
+
+    // Scenarios should all result in no change to the lease.
+    std::list<Scenario> scenarios = {
+    {
+        // Response is not a DHCPACK.
+        __LINE__,
+        DHCPNAK,
+        true
+    },
+    {
+        // No active lease.
+        __LINE__,
+        DHCPACK,
+        false
+    },
+    {
+        // No response.
+        __LINE__,
+        DHCP_NOTYPE,
+        false
+    }};
+
+    // Create packet pair and lease.
+    Pkt4Ptr query(new Pkt4(DHCPREQUEST, 1234));
+    query->setHWAddr(1, 6, { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 });
+    IOAddress yiaddr("192.0.2.1");
+    for (auto const& scenario : scenarios) {
+        SCOPED_LINE(scenario.line_);
+
+        Pkt4Ptr response;
+        if (scenario.response_type_ != DHCP_NOTYPE) {
+            response.reset(new Pkt4(scenario.response_type_, 1234));
+        }
+
+        // Fetch the lease and set its user-context to the original content.
+        Lease4Ptr orig_lease = lmptr_->getLease4(yiaddr);
+        ASSERT_TRUE(orig_lease);
+        ASSERT_FALSE(orig_lease->getContext());
+
+        Lease4CollectionPtr leases(new Lease4Collection());
+        if (scenario.send_lease_) {
+            leases->push_back(orig_lease);
+        }
+
+        // Create a callout handle and add the expected arguments.
+        CalloutHandlePtr callout_handle = HooksManager::createCalloutHandle();
+        callout_handle->setArgument("query4", query);
+        callout_handle->setArgument("response4", response);
+        callout_handle->setArgument("leases4", leases);
+
+        // Invoke the leases4Committed handler.
+        LeaseCmds cmds;
+        ASSERT_NO_THROW_LOG(cmds.leases4Committed(*callout_handle, mgr));
+
+        // Fetch the lease. Context should still be empty.
+        Lease4Ptr after_lease = lmptr_->getLease4(yiaddr);
+        ASSERT_TRUE(after_lease);
+        ASSERT_FALSE(after_lease->getContext());
+    }
+}
+#endif
+
+void
+BindingVariableHandlerTest::testValidLeases6Committed() {
+    initLeaseMgr(AF_INET6);
+
+    struct Scenario {
+        uint32_t line_;
+        std::string config_;
+        std::string orig_context_;
+        std::string exp_context_;
+    };
+
+    std::list<Scenario> scenarios = {
+    {
+        // No variables configured, nothing in lease context.
+        __LINE__,
+        R"({})",
+        R"({})",
+        R"({})"
+    },
+    {
+        // Lease context has no binding-variables, two configured.
+        __LINE__,
+        R"^({"binding-variables":[
+            {
+                "name": "duid",
+                "expression": "hexstring(option[1].hex,':')",
+                "source": "query"
+            },
+            {
+                "name": "sub-id",
+                "expression": "hexstring(option[38].hex, ':')",
+                "source": "response"
+            }]})^",
+        R"({})",
+        R"({"ISC":{
+            "binding-variables":{
+                "duid": "01:02:03:04",
+                "sub-id": "05:06:07:08"
+            }
+        }})",
+    },
+    {
+        // Lease context has binding-variables, none configured.
+        // Current logic leaves lease untouched.
+        __LINE__,
+        R"({})",
+        R"({"ISC":{
+            "binding-variables":{
+                "duid": "01:02:03:04",
+                "sub-id": "05:06:07:08"
+            }
+        }})",
+        R"({"ISC":{
+            "binding-variables":{
+                "duid": "01:02:03:04",
+                "sub-id": "05:06:07:08"
+            }
+        }})",
+    },
+    {
+        // Evaluated variable value is an empty string.
+        __LINE__,
+        R"^({"binding-variables":[
+            {
+                "name": "my-var",
+                "expression": "''",
+                "source": "query"
+            }]})^",
+        R"({"ISC":{
+            "binding-variables":{
+                "my-var": "pre-existing value"
+            }
+        }})",
+        R"({"ISC":{
+            "binding-variables":{
+                "my-var": ""
+            }
+        }})",
+    }
+    };
+
+    // Create packet pair and lease.
+    Pkt6Ptr query(new Pkt6(DHCPV6_REQUEST, 1234));
+    OptionPtr client_id(new Option(Option::V6, D6O_CLIENTID,
+                                   { 0x01, 0x02, 0x03, 0x04 }));
+    query->addOption(client_id);
+
+    Pkt6Ptr response(new Pkt6(DHCPV6_REPLY, 7890));
+    OptionPtr subscriber_id(new Option(Option::V6, D6O_SUBSCRIBER_ID,
+                                       { 0x05, 0x06, 0x07, 0x08 }));
+    response->addOption(subscriber_id);
+
+    // Create a list of the lease addresses.
+    std::list<IOAddress> lease_addrs;
+    lease_addrs.push_back(IOAddress("2001:db8:1::1"));
+    lease_addrs.push_back(IOAddress("2001:db8:1::2"));
+    lease_addrs.push_back(IOAddress("2001:db8:2::2"));
+
+    // Iterater over scenarios.
+    for (auto const& scenario : scenarios) {
+        SCOPED_LINE(scenario.line_);
+
+        // Create and configure the manager.
+        BindingVariableMgrPtr mgr;
+        ASSERT_NO_THROW_LOG(mgr.reset(new BindingVariableMgr(AF_INET6)));
+        ConstElementPtr config;
+        ASSERT_NO_THROW_LOG(config = Element::fromJSON(scenario.config_));
+        ASSERT_NO_THROW_LOG(mgr->configure(config));
+
+        // Fetch the lease and set its user-context to the original content.
+        Lease6CollectionPtr orig_leases(new Lease6Collection());
+        for (auto const& address : lease_addrs) {
+            Lease6Ptr orig_lease = lmptr_->getLease6(Lease::TYPE_NA, address);
+            ASSERT_TRUE(orig_lease);
+            ASSERT_TRUE(orig_lease->valid_lft_ > 0);
+
+            ConstElementPtr orig_context;
+            ASSERT_NO_THROW_LOG(orig_context = Element::fromJSON(scenario.orig_context_));
+            orig_lease->setContext(orig_context);
+            ASSERT_NO_THROW_LOG(lmptr_->updateLease6(orig_lease));
+            orig_leases->push_back(orig_lease);
+        }
+
+        // Create a callout handle and add the expected arguments.
+        CalloutHandlePtr callout_handle = HooksManager::createCalloutHandle();
+        callout_handle->setArgument("query6", query);
+        callout_handle->setArgument("response6", response);
+        callout_handle->setArgument("leases6", orig_leases);
+
+        // Invoke the leases6Committed handler.
+        LeaseCmds cmds;
+        ASSERT_NO_THROW_LOG(cmds.leases6Committed(*callout_handle, mgr));
+
+        // Iterate over the leases and make sure the user-contexts are as expected.
+        for (auto const& lease : *orig_leases) {
+            // Fetch the lease.
+            Lease6Ptr after_lease = lmptr_->getLease6(Lease::TYPE_NA, lease->addr_);
+            ASSERT_TRUE(after_lease);
+
+            // Context contents should match the expected context content.
+            ConstElementPtr exp_context;
+            ASSERT_NO_THROW_LOG(exp_context = Element::fromJSON(scenario.exp_context_));
+            ASSERT_EQ(*(after_lease->getContext()), *exp_context);
+        }
+    }
+}
+
+void
+BindingVariableHandlerTest::testNopLeases6Committed() {
+    initLeaseMgr(AF_INET6);
+
+    struct Scenario {
+        uint32_t line_;
+        DHCPv6MessageType response_type_;
+        bool send_lease_;
+        uint32_t valid_lft_;
+    };
+
+    // Configure a single variable.
+    std::string config =
+    R"^({"binding-variables":[
+    {
+        "name": "duid",
+        "expression": "hexstring(option[1].hex,':')",
+        "source": "query"
+    }]})^";
+
+    // Create and configure the manager.
+    BindingVariableMgrPtr mgr;
+    ASSERT_NO_THROW_LOG(mgr.reset(new BindingVariableMgr(AF_INET6)));
+    ASSERT_NO_THROW_LOG(mgr->configure(Element::fromJSON(config)));
+
+    // Scenarios should all result in no change to the lease.
+    std::list<Scenario> scenarios = {
+    {
+        // No leases.
+        __LINE__,
+        DHCPV6_REPLY,
+        false,
+        0
+    },
+    {
+        // No active leases.
+        __LINE__,
+        DHCPV6_REPLY,
+        true,
+        0
+    },
+    {
+        // No response.
+        __LINE__,
+        DHCPV6_NOTYPE,
+        true,
+        1000
+    }};
+
+    // Create query packet.
+    Pkt6Ptr query(new Pkt6(DHCPV6_REQUEST, 1234));
+    OptionPtr client_id(new Option(Option::V6, D6O_CLIENTID,
+                                   { 0x01, 0x02, 0x03, 0x04 }));
+    query->addOption(client_id);
+
+    IOAddress na_addr("2001:db8:1::1");
+    for (auto const& scenario : scenarios) {
+        SCOPED_LINE(scenario.line_);
+
+        // Create the response packet, if one.
+        Pkt6Ptr response;
+        if (scenario.response_type_ != DHCPV6_NOTYPE) {
+            response.reset(new Pkt6(scenario.response_type_, 1234));
+        }
+
+
+        // Fetch the lease and verify there is no context content.
+        Lease6Ptr orig_lease = lmptr_->getLease6(Lease::TYPE_NA, na_addr);
+        ASSERT_TRUE(orig_lease);
+        ASSERT_FALSE(orig_lease->getContext());
+        orig_lease->valid_lft_ =  scenario.valid_lft_;
+
+        Lease6CollectionPtr leases(new Lease6Collection());
+        if (scenario.send_lease_) {
+            leases->push_back(orig_lease);
+        }
+
+        // Create a callout handle and add the expected arguments.
+        CalloutHandlePtr callout_handle = HooksManager::createCalloutHandle();
+        callout_handle->setArgument("query6", query);
+        callout_handle->setArgument("response6", response);
+        callout_handle->setArgument("leases6", leases);
+
+        // Invoke the leases6Committed handler.
+        LeaseCmds cmds;
+        ASSERT_NO_THROW_LOG(cmds.leases6Committed(*callout_handle, mgr));
+
+        // Fetch the lease. Context should still be empty.
+        Lease6Ptr after_lease = lmptr_->getLease6(Lease::TYPE_NA, na_addr);
+        ASSERT_TRUE(after_lease);
+        ASSERT_FALSE(after_lease->getContext());
+    }
+}
+
+void
+BindingVariableHandlerTest::testLeases6CommittedErrors() {
+    initLeaseMgr(AF_INET6);
+
+    // Create a config with two binding variables.
+    std::string config_str =
+        R"^({"binding-variables":[
+            {
+                "name": "duid",
+                "expression": "hexstring(option[1].hex,':')",
+                "source": "query"
+            },
+            {
+                "name": "sub-id",
+                "expression": "hexstring(option[38].hex, ':')",
+                "source": "response"
+            }]})^";
+
+    ConstElementPtr config;
+    ASSERT_NO_THROW_LOG(config = Element::fromJSON(config_str));
+
+    // Create the expected context contents.
+    std::string exp_context_str =
+        R"({"ISC":{
+            "binding-variables":{
+                "duid": "01:02:03:04",
+                "sub-id": "05:06:07:08"
+            }
+        }})";
+
+
+    ConstElementPtr exp_context;
+    ASSERT_NO_THROW_LOG(exp_context = Element::fromJSON(exp_context_str));
+
+    // Create packet pair and lease.
+    Pkt6Ptr query(new Pkt6(DHCPV6_REQUEST, 1234));
+    OptionPtr client_id(new Option(Option::V6, D6O_CLIENTID,
+                                   { 0x01, 0x02, 0x03, 0x04 }));
+    query->addOption(client_id);
+
+    Pkt6Ptr response(new Pkt6(DHCPV6_REPLY, 7890));
+    OptionPtr subscriber_id(new Option(Option::V6, D6O_SUBSCRIBER_ID,
+                                       { 0x05, 0x06, 0x07, 0x08 }));
+    response->addOption(subscriber_id);
+
+    // Create and configure the manager.
+    BindingVariableMgrPtr mgr;
+    ASSERT_NO_THROW_LOG(mgr.reset(new BindingVariableMgr(AF_INET6)));
+    ASSERT_NO_THROW_LOG(mgr->configure(config));
+
+    // Fetch the leases.
+    std::list<IOAddress> lease_addrs;
+    lease_addrs.push_back(IOAddress("2001:db8:1::1"));
+    lease_addrs.push_back(IOAddress("2001:db8:1::2"));
+    lease_addrs.push_back(IOAddress("2001:db8:2::2"));
+
+    Lease6CollectionPtr orig_leases(new Lease6Collection());
+    for (auto const& address : lease_addrs) {
+        Lease6Ptr orig_lease = lmptr_->getLease6(Lease::TYPE_NA, address);
+        ASSERT_TRUE(orig_lease);
+        orig_leases->push_back(orig_lease);
+    }
+
+    // Delete the middle lease from the back end. This should cause a conflict error.
+    ASSERT_NO_THROW_LOG(lmptr_->deleteLease((*orig_leases)[1]));
+
+    // Create a callout handle and add the expected arguments.
+    CalloutHandlePtr callout_handle = HooksManager::createCalloutHandle();
+    callout_handle->setArgument("query6", query);
+    callout_handle->setArgument("response6", response);
+    callout_handle->setArgument("leases6", orig_leases);
+
+    // Invoke the leases6Committed handler.
+    LeaseCmds cmds;
+    ASSERT_THROW_MSG(cmds.leases6Committed(*callout_handle, mgr),
+                     Unexpected,
+                     "1 out of 3 leases failed to update for "
+                     "duid=[01:02:03:04], [no hwaddr info], tid=0x4d2");
+
+    for (auto const& lease : *orig_leases) {
+        // Fetch the lease.
+        Lease6Ptr after_lease = lmptr_->getLease6(Lease::TYPE_NA, lease->addr_);
+        if (after_lease) {
+            // Context contents should match the expected context content.
+            ASSERT_EQ(*(after_lease->getContext()), *exp_context);
+        } else {
+            // Middle lease should not be found.
+            EXPECT_EQ(lease->addr_, (*orig_leases)[1]->addr_);
+        }
+    }
+}
+
+TEST_F(BindingVariableHandlerTest, validLeases6Committed) {
+    testValidLeases6Committed();
+}
+
+TEST_F(BindingVariableHandlerTest, validLeases6CommittedMultiThreading) {
+    MultiThreadingTest mt(true);
+    testValidLeases6Committed();
+}
+
+TEST_F(BindingVariableHandlerTest, nopLeases6Committed) {
+    testNopLeases6Committed();
+}
+
+TEST_F(BindingVariableHandlerTest, nopLeases6CommittedMultiThreading) {
+    MultiThreadingTest mt(true);
+    testNopLeases6Committed();
+}
+
+TEST_F(BindingVariableHandlerTest, leases6CommittedErrors) {
+    testLeases6CommittedErrors();
+}
+
+TEST_F(BindingVariableHandlerTest, leases6CommittedErrorsMultiThreading) {
+    MultiThreadingTest mt(true);
+    testLeases6CommittedErrors();
+}
+#endif
+
 } // end of anonymous namespace
diff --git a/src/hooks/dhcp/lease_cmds/tests/lease_cmds_func4_unittest.cc b/src/hooks/dhcp/lease_cmds/tests/lease_cmds_func4_unittest.cc
new file mode 100644 (file)
index 0000000..1aa3488
--- /dev/null
@@ -0,0 +1,501 @@
+// Copyright (C) 2025 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 <lease_cmds.h>
+#include <lease_cmds_func_unittest.h>
+#include <hooks/hooks_manager.h>
+#include <dhcpsrv/lease_mgr.h>
+#include <exceptions/exceptions.h>
+#include <cc/data.h>
+#include <dhcp/pkt4.h>
+
+#include <testutils/gtest_utils.h>
+#include <testutils/user_context_utils.h>
+#include <testutils/multi_threading_utils.h>
+
+#include <gtest/gtest.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::dhcp;
+using namespace isc::data;
+using namespace isc::test;
+using namespace isc::asiolink;
+using namespace isc::hooks;
+
+using namespace isc::lease_cmds;
+
+namespace {
+
+class LeaseCmdsFuncTest4 :  public LeaseCmdsFuncTest { 
+public:
+    /// @brief Constructor
+    LeaseCmdsFuncTest4() = default;
+
+    /// @brief Destructor
+    ///
+    virtual ~LeaseCmdsFuncTest4() = default;
+
+    /// @brief Creates an IPv4 lease
+    ///
+    /// Lease parameters: valid lifetime = 0xFFFFFFFE, cltt = 1923222072, fqdn-fwd = false,
+    /// fqdn-rev = true, hostname = myhost.example.com
+    ///
+    /// @param ip_address IP address for the lease.
+    /// @param subnet_id subnet identifier
+    /// @param hw_address_pattern value to be used for generating HW address by repeating
+    /// it 6 times.
+    /// @param client_id_pattern value to be used for generating client identifier by
+    /// repeating it 8 times.
+    ///
+    /// @return Returns the lease created
+    Lease4Ptr createLease(const std::string& ip_address,
+                          const SubnetID& subnet_id,
+                          const uint8_t hw_address_pattern,
+                          const uint8_t client_id_pattern,
+                          uint32_t pool_id = 0) {
+        Lease4Ptr lease(new Lease4());
+        lease->addr_ = IOAddress(ip_address);
+
+        lease->hwaddr_.reset(new HWAddr(std::vector<uint8_t>(6, hw_address_pattern), HTYPE_ETHER));
+        lease->client_id_ = ClientIdPtr(new ClientId(std::vector<uint8_t>(8, client_id_pattern)));
+        lease->valid_lft_ = 2400;
+        lease->updateCurrentExpirationTime();
+        lease->subnet_id_ = subnet_id;
+        lease->pool_id_ = pool_id;
+        return (lease);
+    }
+
+    /// @brief Initializes lease manager (and optionally populates it with a lease)
+    ///
+    /// Creates a lease manager and initial leases.
+    /// only) and optionally can create a lease, which is useful for leaseX-get and
+    /// leaseX-del type of tests. For lease details, see @ref createLease and
+    /// @ref createLease6.
+    ///
+    virtual void initLeaseMgr() {
+        LeaseMgrFactory::destroy();
+        LeaseMgrFactory::create("type=memfile persist=false universe=4");
+        lmptr_ = &(LeaseMgrFactory::instance());
+        ASSERT_TRUE(lmptr_);
+
+        lmptr_->addLease(createLease("192.0.2.1", 44, 0x08, 0x42));
+        lmptr_->addLease(createLease("192.0.2.2", 44, 0x09, 0x56));
+        lmptr_->addLease(createLease("192.0.3.1", 88, 0x08, 0x42));
+        lmptr_->addLease(createLease("192.0.3.2", 88, 0x09, 0x56));
+    }
+
+    /// @brief Check that lease4_offer handler works as expected with
+    /// valid inputs.
+    void testValidLease4Offer();
+
+    /// @brief Check that leases4_committed handler works as expected with
+    /// valid inputs.
+    void testValidLeases4Committed();
+
+    /// @brief Check that leases4_committed handler does not throw or alter
+    /// the lease under NOP conditions: 
+    /// 1. There is no repsonse packet
+    /// 2. Response packet is a DHCPACK 
+    /// 3. There is no active lease
+    void testNopLeases4Committed();
+};
+
+void
+LeaseCmdsFuncTest4::testValidLease4Offer() {
+    struct Scenario {
+        uint32_t line_;
+        std::string config_;
+        std::string orig_context_;
+        std::string exp_context_;
+        uint32_t offer_lifetime_;
+    };
+
+    std::list<Scenario> scenarios = {
+    {
+        // No variables configured, nothing in lease context.
+        __LINE__,
+        R"({})",
+        R"({})",
+        R"({})",
+        500
+    },
+    {
+        // lease context has no binding-variables, two configured
+        __LINE__,
+        R"^({"binding-variables":[
+            {
+                "name": "hwaddr",
+                "expression": "hexstring(pkt4.mac,':')",
+                "source": "query"
+            },
+            {
+                "name": "yiaddr",
+                "expression": "addrtotext(pkt4.yiaddr)",
+                "source": "response"
+            }]})^",
+        R"({})",
+        R"({"ISC":{
+            "binding-variables":{
+                "hwaddr": "01:02:03:04:05:06",
+                "yiaddr": "192.0.2.1"
+            }
+        }})",
+        500
+    },
+    {
+        // lease context has no binding-variables, two configured
+        // offer lifetime is 0.
+        __LINE__,
+        R"^({"binding-variables":[
+            {
+                "name": "hwaddr",
+                "expression": "hexstring(pkt4.mac,':')",
+                "source": "query"
+            },
+            {
+                "name": "yiaddr",
+                "expression": "addrtotext(pkt4.yiaddr)",
+                "source": "response"
+            }]})^",
+        R"({})",
+        R"({})",
+        0
+    },
+    {
+        // lease context has binding-variables, none configured
+        // Current logic leaves lease untouched.
+        __LINE__,
+        R"({})",
+        R"({"ISC":{
+            "binding-variables":{
+                "hwaddr": "01:02:03:04:05:06",
+                "yiaddr": "192.0.2.1"
+            }
+        }})",
+        R"({"ISC":{
+            "binding-variables":{
+                "hwaddr": "01:02:03:04:05:06",
+                "yiaddr": "192.0.2.1"
+            }
+        }})",
+        500
+    },
+    {
+        // Evaluated variable value is an empty string.
+        __LINE__,
+        R"^({"binding-variables":[
+            {
+                "name": "hwaddr",
+                "expression": "''",
+                "source": "query"
+            }]})^",
+        R"({"ISC":{
+            "binding-variables":{
+                "hwaddr": "01:02:03:04:05:06"
+            }
+        }})",
+        R"({"ISC":{
+            "binding-variables":{
+                "hwaddr": ""
+            }
+        }})",
+        500
+    }};
+
+    // Create packet pair and lease.
+    Pkt4Ptr query(new Pkt4(DHCPDISCOVER, 1234));
+    query->setHWAddr(1, 6, { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 });
+
+    Pkt4Ptr response(new Pkt4(DHCPOFFER, 1234));
+    IOAddress yiaddr("192.0.2.1");
+    response->setYiaddr(yiaddr);
+
+    // Iterater over scenarios.
+    for (auto const& scenario : scenarios) {
+        SCOPED_LINE(scenario.line_);
+
+        // Create and configure the manager.
+        BindingVariableMgrPtr mgr;
+        ASSERT_NO_THROW_LOG(mgr.reset(new BindingVariableMgr(AF_INET)));
+        ConstElementPtr config;
+        ASSERT_NO_THROW_LOG(config = Element::fromJSON(scenario.config_));
+        ASSERT_NO_THROW_LOG(mgr->configure(config));
+
+        // Fetch the lease and set its user-context to the original content.
+        Lease4Ptr orig_lease = lmptr_->getLease4(yiaddr);
+        ASSERT_TRUE(orig_lease);
+        ConstElementPtr orig_context;
+        ASSERT_NO_THROW_LOG(orig_context = Element::fromJSON(scenario.orig_context_));
+        orig_lease->setContext(orig_context);
+        ASSERT_NO_THROW_LOG(lmptr_->updateLease4(orig_lease));
+
+        Lease4CollectionPtr leases(new Lease4Collection());
+        leases->push_back(orig_lease);
+
+        // Create a callout handle and add the expected arguments.
+        CalloutHandlePtr callout_handle = HooksManager::createCalloutHandle();
+        callout_handle->setArgument("query4", query);
+        callout_handle->setArgument("response4", response);
+        callout_handle->setArgument("leases4", leases);
+        callout_handle->setArgument("offer_lifetime", scenario.offer_lifetime_);
+
+        // Invoke the leases4Committed handler.
+        LeaseCmds cmds;
+        ASSERT_NO_THROW_LOG(cmds.lease4Offer(*callout_handle, mgr));
+
+        // Fetch the lease.
+        Lease4Ptr after_lease = lmptr_->getLease4(yiaddr);
+        ASSERT_TRUE(after_lease);
+
+        // Context contents should match the expected context content.
+        ConstElementPtr exp_context;
+        ASSERT_NO_THROW_LOG(exp_context = Element::fromJSON(scenario.exp_context_));
+        ASSERT_EQ(*(after_lease->getContext()), *exp_context);
+    }
+}
+
+void
+LeaseCmdsFuncTest4::testValidLeases4Committed() {
+    struct Scenario {
+        uint32_t line_;
+        std::string config_;
+        std::string orig_context_;
+        std::string exp_context_;
+    };
+
+    std::list<Scenario> scenarios = {
+    {
+        // No variables configured, nothing in lease context.
+        __LINE__,
+        R"({})",
+        R"({})",
+        R"({})"
+    },
+    {
+        // lease context has no binding-variables, two configured
+        __LINE__,
+        R"^({"binding-variables":[
+            {
+                "name": "hwaddr",
+                "expression": "hexstring(pkt4.mac,':')",
+                "source": "query"
+            },
+            {
+                "name": "yiaddr",
+                "expression": "addrtotext(pkt4.yiaddr)",
+                "source": "response"
+            }]})^",
+        R"({})",
+        R"({"ISC":{
+            "binding-variables":{
+                "hwaddr": "01:02:03:04:05:06",
+                "yiaddr": "192.0.2.1"
+            }
+        }})",
+    },
+    {
+        // lease context has binding-variables, none configured
+        // Current logic leaves lease untouched.
+        __LINE__,
+        R"({})",
+        R"({"ISC":{
+            "binding-variables":{
+                "hwaddr": "01:02:03:04:05:06",
+                "yiaddr": "192.0.2.1"
+            }
+        }})",
+        R"({"ISC":{
+            "binding-variables":{
+                "hwaddr": "01:02:03:04:05:06",
+                "yiaddr": "192.0.2.1"
+            }
+        }})",
+    },
+    {
+        // Evaluated variable value is an empty string.
+        __LINE__,
+        R"^({"binding-variables":[
+            {
+                "name": "hwaddr",
+                "expression": "''",
+                "source": "query"
+            }]})^",
+        R"({"ISC":{
+            "binding-variables":{
+                "hwaddr": "01:02:03:04:05:06"
+            }
+        }})",
+        R"({"ISC":{
+            "binding-variables":{
+                "hwaddr": ""
+            }
+        }})",
+    }};
+
+    // Create packet pair and lease.
+    Pkt4Ptr query(new Pkt4(DHCPREQUEST, 1234));
+    query->setHWAddr(1, 6, { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 });
+
+    Pkt4Ptr response(new Pkt4(DHCPACK, 1234));
+    IOAddress yiaddr("192.0.2.1");
+    response->setYiaddr(yiaddr);
+
+    // Iterater over scenarios.
+    for (auto const& scenario : scenarios) {
+        SCOPED_LINE(scenario.line_);
+
+        // Create and configure the manager.
+        BindingVariableMgrPtr mgr;
+        ASSERT_NO_THROW_LOG(mgr.reset(new BindingVariableMgr(AF_INET)));
+        ConstElementPtr config;
+        ASSERT_NO_THROW_LOG(config = Element::fromJSON(scenario.config_));
+        ASSERT_NO_THROW_LOG(mgr->configure(config));
+
+        // Fetch the lease and set its user-context to the original content.
+        Lease4Ptr orig_lease = lmptr_->getLease4(yiaddr);
+        ASSERT_TRUE(orig_lease);
+        ConstElementPtr orig_context;
+        ASSERT_NO_THROW_LOG(orig_context = Element::fromJSON(scenario.orig_context_));
+        orig_lease->setContext(orig_context);
+        ASSERT_NO_THROW_LOG(lmptr_->updateLease4(orig_lease));
+
+        Lease4CollectionPtr leases(new Lease4Collection());
+        leases->push_back(orig_lease);
+
+        // Create a callout handle and add the expected arguments.
+        CalloutHandlePtr callout_handle = HooksManager::createCalloutHandle();
+        callout_handle->setArgument("query4", query);
+        callout_handle->setArgument("response4", response);
+        callout_handle->setArgument("leases4", leases);
+
+        // Invoke the leases4Committed handler.
+        LeaseCmds cmds;
+        ASSERT_NO_THROW_LOG(cmds.leases4Committed(*callout_handle, mgr));
+
+        // Fetch the lease.
+        Lease4Ptr after_lease = lmptr_->getLease4(yiaddr);
+        ASSERT_TRUE(after_lease);
+
+        // Context contents should match the expected context content.
+        ConstElementPtr exp_context;
+        ASSERT_NO_THROW_LOG(exp_context = Element::fromJSON(scenario.exp_context_));
+        ASSERT_EQ(*(after_lease->getContext()), *exp_context);
+    }
+}
+
+void
+LeaseCmdsFuncTest4::testNopLeases4Committed() {
+    struct Scenario {
+        uint32_t line_;
+        DHCPMessageType response_type_;
+        bool send_lease_;
+    };
+
+    // Configure a single variable.
+    std::string config =
+    R"^({"binding-variables":[
+    {
+        "name": "hwaddr",
+        "expression": "hexstring(pkt4.mac,':')",
+        "source": "query"
+    }]})^";
+
+    // Create and configure the manager.
+    BindingVariableMgrPtr mgr;
+    ASSERT_NO_THROW_LOG(mgr.reset(new BindingVariableMgr(AF_INET)));
+    ASSERT_NO_THROW_LOG(mgr->configure(Element::fromJSON(config)));
+
+    // Scenarios should all result in no change to the lease.
+    std::list<Scenario> scenarios = {
+    {
+        // Response is not a DHCPACK.
+        __LINE__,
+        DHCPNAK,
+        true
+    },
+    {
+        // No active lease.
+        __LINE__,
+        DHCPACK,
+        false
+    },
+    {
+        // No response.
+        __LINE__,
+        DHCP_NOTYPE,
+        false
+    }};
+
+    // Create packet pair and lease.
+    Pkt4Ptr query(new Pkt4(DHCPREQUEST, 1234));
+    query->setHWAddr(1, 6, { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 });
+    IOAddress yiaddr("192.0.2.1");
+    for (auto const& scenario : scenarios) {
+        SCOPED_LINE(scenario.line_);
+
+        Pkt4Ptr response;
+        if (scenario.response_type_ != DHCP_NOTYPE) {
+            response.reset(new Pkt4(scenario.response_type_, 1234));
+        }
+
+        // Fetch the lease and set its user-context to the original content.
+        Lease4Ptr orig_lease = lmptr_->getLease4(yiaddr);
+        ASSERT_TRUE(orig_lease);
+        ASSERT_FALSE(orig_lease->getContext());
+
+        Lease4CollectionPtr leases(new Lease4Collection());
+        if (scenario.send_lease_) {
+            leases->push_back(orig_lease);
+        }
+
+        // Create a callout handle and add the expected arguments.
+        CalloutHandlePtr callout_handle = HooksManager::createCalloutHandle();
+        callout_handle->setArgument("query4", query);
+        callout_handle->setArgument("response4", response);
+        callout_handle->setArgument("leases4", leases);
+
+        // Invoke the leases4Committed handler.
+        LeaseCmds cmds;
+        ASSERT_NO_THROW_LOG(cmds.leases4Committed(*callout_handle, mgr));
+
+        // Fetch the lease. Context should still be empty.
+        Lease4Ptr after_lease = lmptr_->getLease4(yiaddr);
+        ASSERT_TRUE(after_lease);
+        ASSERT_FALSE(after_lease->getContext());
+    }
+}
+
+TEST_F(LeaseCmdsFuncTest4, validLease4Offer) {
+    testValidLease4Offer();
+}
+
+TEST_F(LeaseCmdsFuncTest4, validLease4OfferMultiThreading) {
+    MultiThreadingTest mt(true);
+    testValidLease4Offer();
+}
+
+TEST_F(LeaseCmdsFuncTest4, validLeases4Committed) {
+    testValidLeases4Committed();
+}
+
+TEST_F(LeaseCmdsFuncTest4, validLeases4CommittedMultiThreading) {
+    MultiThreadingTest mt(true);
+    testValidLeases4Committed();
+}
+
+TEST_F(LeaseCmdsFuncTest4, nopLeases4Committed) {
+    testNopLeases4Committed();
+}
+
+TEST_F(LeaseCmdsFuncTest4, nopLeases4CommittedMultiThreading) {
+    MultiThreadingTest mt(true);
+    testNopLeases4Committed();
+}
+
+} // end of anonymous namespace
diff --git a/src/hooks/dhcp/lease_cmds/tests/lease_cmds_func6_unittest.cc b/src/hooks/dhcp/lease_cmds/tests/lease_cmds_func6_unittest.cc
new file mode 100644 (file)
index 0000000..8f7341b
--- /dev/null
@@ -0,0 +1,449 @@
+// Copyright (C) 2025 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 <lease_cmds.h>
+#include <lease_cmds_func_unittest.h>
+#include <hooks/hooks_manager.h>
+#include <dhcpsrv/lease_mgr.h>
+#include <exceptions/exceptions.h>
+#include <cc/data.h>
+#include <dhcp/dhcp6.h>
+#include <dhcp/pkt6.h>
+
+#include <testutils/gtest_utils.h>
+#include <testutils/user_context_utils.h>
+#include <testutils/multi_threading_utils.h>
+
+#include <gtest/gtest.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::dhcp;
+using namespace isc::data;
+using namespace isc::test;
+using namespace isc::asiolink;
+using namespace isc::hooks;
+
+using namespace isc::lease_cmds;
+
+namespace {
+
+class LeaseCmdsFuncTest6 :  public LeaseCmdsFuncTest { 
+public:
+    /// @brief Constructor
+    LeaseCmdsFuncTest6() = default;
+
+    /// @brief Destructor
+    virtual ~LeaseCmdsFuncTest6() = default;
+
+    /// @brief Initializes lease manager (and optionally populates it with a lease)
+    void initLeaseMgr() {
+        LeaseMgrFactory::destroy();
+        LeaseMgrFactory::create("type=memfile persist=false " 
+                                               "universe=6 extended-info-tables=true");
+        lmptr_ = &(isc::dhcp::LeaseMgrFactory::instance());
+        ASSERT_TRUE(lmptr_);
+        lmptr_->addLease(createLease6("2001:db8:1::1", 66, 0x42));
+        lmptr_->addLease(createLease6("2001:db8:1::2", 66, 0x56));
+        lmptr_->addLease(createLease6("2001:db8:2::1", 99, 0x42));
+        lmptr_->addLease(createLease6("2001:db8:2::2", 99, 0x56));
+    }
+
+    /// @brief Creates an IPv6 lease
+    ///
+    /// @param ip_address IP address for the lease.
+    /// @param subnet_id subnet identifier
+    /// @param duid_address_pattern value to be used for generating DUID by
+    /// repeating it 8 times
+    /// @return Returns the lease created
+    isc::dhcp::Lease6Ptr createLease6(const std::string& ip_address,
+                                      const isc::dhcp::SubnetID& subnet_id,
+                                      const uint8_t duid_pattern) {
+        isc::dhcp::Lease6Ptr lease(new isc::dhcp::Lease6());
+        lease->addr_ = isc::asiolink::IOAddress(ip_address);
+        lease->type_ = isc::dhcp::Lease::TYPE_NA;
+        lease->prefixlen_ = 128;
+        lease->iaid_ = 42;
+        lease->duid_ = isc::dhcp::DuidPtr(new isc::dhcp::DUID(std::vector<uint8_t>(8, duid_pattern)));
+        lease->preferred_lft_ = 1800;
+        lease->valid_lft_ = 2400;
+        lease->subnet_id_ = subnet_id;
+        return (lease);
+    }
+
+    /// @brief Check that leases6_committed handler works as expected with
+    /// valid inputs.
+    void testValidLeases6Committed();
+
+    /// @brief Check that leases6_committed handler does not throw or alter
+    /// leases under NOP conditions:
+    /// 1. There is no repsonse packet
+    /// 2. There are no leases
+    /// 3. There are leases but none active
+    void testNopLeases6Committed();
+
+    /// @brief Checks that errors occurring in leases6Committed() are handled
+    /// properly.
+    void testLeases6CommittedErrors();
+};
+
+void
+LeaseCmdsFuncTest6::testValidLeases6Committed() {
+    struct Scenario {
+        uint32_t line_;
+        std::string config_;
+        std::string orig_context_;
+        std::string exp_context_;
+    };
+
+    std::list<Scenario> scenarios = {
+    {
+        // No variables configured, nothing in lease context.
+        __LINE__,
+        R"({})",
+        R"({})",
+        R"({})"
+    },
+    {
+        // Lease context has no binding-variables, two configured.
+        __LINE__,
+        R"^({"binding-variables":[
+            {
+                "name": "duid",
+                "expression": "hexstring(option[1].hex,':')",
+                "source": "query"
+            },
+            {
+                "name": "sub-id",
+                "expression": "hexstring(option[38].hex, ':')",
+                "source": "response"
+            }]})^",
+        R"({})",
+        R"({"ISC":{
+            "binding-variables":{
+                "duid": "01:02:03:04",
+                "sub-id": "05:06:07:08"
+            }
+        }})",
+    },
+    {
+        // Lease context has binding-variables, none configured.
+        // Current logic leaves lease untouched.
+        __LINE__,
+        R"({})",
+        R"({"ISC":{
+            "binding-variables":{
+                "duid": "01:02:03:04",
+                "sub-id": "05:06:07:08"
+            }
+        }})",
+        R"({"ISC":{
+            "binding-variables":{
+                "duid": "01:02:03:04",
+                "sub-id": "05:06:07:08"
+            }
+        }})",
+    },
+    {
+        // Evaluated variable value is an empty string.
+        __LINE__,
+        R"^({"binding-variables":[
+            {
+                "name": "my-var",
+                "expression": "''",
+                "source": "query"
+            }]})^",
+        R"({"ISC":{
+            "binding-variables":{
+                "my-var": "pre-existing value"
+            }
+        }})",
+        R"({"ISC":{
+            "binding-variables":{
+                "my-var": ""
+            }
+        }})",
+    }
+    };
+
+    // Create packet pair and lease.
+    Pkt6Ptr query(new Pkt6(DHCPV6_REQUEST, 1234));
+    OptionPtr client_id(new Option(Option::V6, D6O_CLIENTID,
+                                   { 0x01, 0x02, 0x03, 0x04 }));
+    query->addOption(client_id);
+
+    Pkt6Ptr response(new Pkt6(DHCPV6_REPLY, 7890));
+    OptionPtr subscriber_id(new Option(Option::V6, D6O_SUBSCRIBER_ID,
+                                       { 0x05, 0x06, 0x07, 0x08 }));
+    response->addOption(subscriber_id);
+
+    // Create a list of the lease addresses.
+    std::list<IOAddress> lease_addrs;
+    lease_addrs.push_back(IOAddress("2001:db8:1::1"));
+    lease_addrs.push_back(IOAddress("2001:db8:1::2"));
+    lease_addrs.push_back(IOAddress("2001:db8:2::2"));
+
+    // Iterater over scenarios.
+    for (auto const& scenario : scenarios) {
+        SCOPED_LINE(scenario.line_);
+
+        // Create and configure the manager.
+        BindingVariableMgrPtr mgr;
+        ASSERT_NO_THROW_LOG(mgr.reset(new BindingVariableMgr(AF_INET6)));
+        ConstElementPtr config;
+        ASSERT_NO_THROW_LOG(config = Element::fromJSON(scenario.config_));
+        ASSERT_NO_THROW_LOG(mgr->configure(config));
+
+        // Fetch the lease and set its user-context to the original content.
+        Lease6CollectionPtr orig_leases(new Lease6Collection());
+        for (auto const& address : lease_addrs) {
+            Lease6Ptr orig_lease = lmptr_->getLease6(Lease::TYPE_NA, address);
+            ASSERT_TRUE(orig_lease);
+            ASSERT_TRUE(orig_lease->valid_lft_ > 0);
+
+            ConstElementPtr orig_context;
+            ASSERT_NO_THROW_LOG(orig_context = Element::fromJSON(scenario.orig_context_));
+            orig_lease->setContext(orig_context);
+            ASSERT_NO_THROW_LOG(lmptr_->updateLease6(orig_lease));
+            orig_leases->push_back(orig_lease);
+        }
+
+        // Create a callout handle and add the expected arguments.
+        CalloutHandlePtr callout_handle = HooksManager::createCalloutHandle();
+        callout_handle->setArgument("query6", query);
+        callout_handle->setArgument("response6", response);
+        callout_handle->setArgument("leases6", orig_leases);
+
+        // Invoke the leases6Committed handler.
+        LeaseCmds cmds;
+        ASSERT_NO_THROW_LOG(cmds.leases6Committed(*callout_handle, mgr));
+
+        // Iterate over the leases and make sure the user-contexts are as expected.
+        for (auto const& lease : *orig_leases) {
+            // Fetch the lease.
+            Lease6Ptr after_lease = lmptr_->getLease6(Lease::TYPE_NA, lease->addr_);
+            ASSERT_TRUE(after_lease);
+
+            // Context contents should match the expected context content.
+            ConstElementPtr exp_context;
+            ASSERT_NO_THROW_LOG(exp_context = Element::fromJSON(scenario.exp_context_));
+            ASSERT_EQ(*(after_lease->getContext()), *exp_context);
+        }
+    }
+}
+
+void
+LeaseCmdsFuncTest6::testNopLeases6Committed() {
+    struct Scenario {
+        uint32_t line_;
+        DHCPv6MessageType response_type_;
+        bool send_lease_;
+        uint32_t valid_lft_;
+    };
+
+    // Configure a single variable.
+    std::string config =
+    R"^({"binding-variables":[
+    {
+        "name": "duid",
+        "expression": "hexstring(option[1].hex,':')",
+        "source": "query"
+    }]})^";
+
+    // Create and configure the manager.
+    BindingVariableMgrPtr mgr;
+    ASSERT_NO_THROW_LOG(mgr.reset(new BindingVariableMgr(AF_INET6)));
+    ASSERT_NO_THROW_LOG(mgr->configure(Element::fromJSON(config)));
+
+    // Scenarios should all result in no change to the lease.
+    std::list<Scenario> scenarios = {
+    {
+        // No leases.
+        __LINE__,
+        DHCPV6_REPLY,
+        false,
+        0
+    },
+    {
+        // No active leases.
+        __LINE__,
+        DHCPV6_REPLY,
+        true,
+        0
+    },
+    {
+        // No response.
+        __LINE__,
+        DHCPV6_NOTYPE,
+        true,
+        1000
+    }};
+
+    // Create query packet.
+    Pkt6Ptr query(new Pkt6(DHCPV6_REQUEST, 1234));
+    OptionPtr client_id(new Option(Option::V6, D6O_CLIENTID,
+                                   { 0x01, 0x02, 0x03, 0x04 }));
+    query->addOption(client_id);
+
+    IOAddress na_addr("2001:db8:1::1");
+    for (auto const& scenario : scenarios) {
+        SCOPED_LINE(scenario.line_);
+
+        // Create the response packet, if one.
+        Pkt6Ptr response;
+        if (scenario.response_type_ != DHCPV6_NOTYPE) {
+            response.reset(new Pkt6(scenario.response_type_, 1234));
+        }
+
+
+        // Fetch the lease and verify there is no context content.
+        Lease6Ptr orig_lease = lmptr_->getLease6(Lease::TYPE_NA, na_addr);
+        ASSERT_TRUE(orig_lease);
+        ASSERT_FALSE(orig_lease->getContext());
+        orig_lease->valid_lft_ =  scenario.valid_lft_;
+
+        Lease6CollectionPtr leases(new Lease6Collection());
+        if (scenario.send_lease_) {
+            leases->push_back(orig_lease);
+        }
+
+        // Create a callout handle and add the expected arguments.
+        CalloutHandlePtr callout_handle = HooksManager::createCalloutHandle();
+        callout_handle->setArgument("query6", query);
+        callout_handle->setArgument("response6", response);
+        callout_handle->setArgument("leases6", leases);
+
+        // Invoke the leases6Committed handler.
+        LeaseCmds cmds;
+        ASSERT_NO_THROW_LOG(cmds.leases6Committed(*callout_handle, mgr));
+
+        // Fetch the lease. Context should still be empty.
+        Lease6Ptr after_lease = lmptr_->getLease6(Lease::TYPE_NA, na_addr);
+        ASSERT_TRUE(after_lease);
+        ASSERT_FALSE(after_lease->getContext());
+    }
+}
+
+void
+LeaseCmdsFuncTest6::testLeases6CommittedErrors() {
+    // Create a config with two binding variables.
+    std::string config_str =
+        R"^({"binding-variables":[
+            {
+                "name": "duid",
+                "expression": "hexstring(option[1].hex,':')",
+                "source": "query"
+            },
+            {
+                "name": "sub-id",
+                "expression": "hexstring(option[38].hex, ':')",
+                "source": "response"
+            }]})^";
+
+    ConstElementPtr config;
+    ASSERT_NO_THROW_LOG(config = Element::fromJSON(config_str));
+
+    // Create the expected context contents.
+    std::string exp_context_str =
+        R"({"ISC":{
+            "binding-variables":{
+                "duid": "01:02:03:04",
+                "sub-id": "05:06:07:08"
+            }
+        }})";
+
+
+    ConstElementPtr exp_context;
+    ASSERT_NO_THROW_LOG(exp_context = Element::fromJSON(exp_context_str));
+
+    // Create packet pair and lease.
+    Pkt6Ptr query(new Pkt6(DHCPV6_REQUEST, 1234));
+    OptionPtr client_id(new Option(Option::V6, D6O_CLIENTID,
+                                   { 0x01, 0x02, 0x03, 0x04 }));
+    query->addOption(client_id);
+
+    Pkt6Ptr response(new Pkt6(DHCPV6_REPLY, 7890));
+    OptionPtr subscriber_id(new Option(Option::V6, D6O_SUBSCRIBER_ID,
+                                       { 0x05, 0x06, 0x07, 0x08 }));
+    response->addOption(subscriber_id);
+
+    // Create and configure the manager.
+    BindingVariableMgrPtr mgr;
+    ASSERT_NO_THROW_LOG(mgr.reset(new BindingVariableMgr(AF_INET6)));
+    ASSERT_NO_THROW_LOG(mgr->configure(config));
+
+    // Fetch the leases.
+    std::list<IOAddress> lease_addrs;
+    lease_addrs.push_back(IOAddress("2001:db8:1::1"));
+    lease_addrs.push_back(IOAddress("2001:db8:1::2"));
+    lease_addrs.push_back(IOAddress("2001:db8:2::2"));
+
+    Lease6CollectionPtr orig_leases(new Lease6Collection());
+    for (auto const& address : lease_addrs) {
+        Lease6Ptr orig_lease = lmptr_->getLease6(Lease::TYPE_NA, address);
+        ASSERT_TRUE(orig_lease);
+        orig_leases->push_back(orig_lease);
+    }
+
+    // Delete the middle lease from the back end. This should cause a conflict error.
+    ASSERT_NO_THROW_LOG(lmptr_->deleteLease((*orig_leases)[1]));
+
+    // Create a callout handle and add the expected arguments.
+    CalloutHandlePtr callout_handle = HooksManager::createCalloutHandle();
+    callout_handle->setArgument("query6", query);
+    callout_handle->setArgument("response6", response);
+    callout_handle->setArgument("leases6", orig_leases);
+
+    // Invoke the leases6Committed handler.
+    LeaseCmds cmds;
+    ASSERT_THROW_MSG(cmds.leases6Committed(*callout_handle, mgr),
+                     Unexpected,
+                     "1 out of 3 leases failed to update for "
+                     "duid=[01:02:03:04], [no hwaddr info], tid=0x4d2");
+
+    for (auto const& lease : *orig_leases) {
+        // Fetch the lease.
+        Lease6Ptr after_lease = lmptr_->getLease6(Lease::TYPE_NA, lease->addr_);
+        if (after_lease) {
+            // Context contents should match the expected context content.
+            ASSERT_EQ(*(after_lease->getContext()), *exp_context);
+        } else {
+            // Middle lease should not be found.
+            EXPECT_EQ(lease->addr_, (*orig_leases)[1]->addr_);
+        }
+    }
+}
+
+TEST_F(LeaseCmdsFuncTest6, validLeases6Committed) {
+    testValidLeases6Committed();
+}
+
+TEST_F(LeaseCmdsFuncTest6, validLeases6CommittedMultiThreading) {
+    MultiThreadingTest mt(true);
+    testValidLeases6Committed();
+}
+
+TEST_F(LeaseCmdsFuncTest6, nopLeases6Committed) {
+    testNopLeases6Committed();
+}
+
+TEST_F(LeaseCmdsFuncTest6, nopLeases6CommittedMultiThreading) {
+    MultiThreadingTest mt(true);
+    testNopLeases6Committed();
+}
+
+TEST_F(LeaseCmdsFuncTest6, leases6CommittedErrors) {
+    testLeases6CommittedErrors();
+}
+
+TEST_F(LeaseCmdsFuncTest6, leases6CommittedErrorsMultiThreading) {
+    MultiThreadingTest mt(true);
+    testLeases6CommittedErrors();
+}
+
+} // end of anonymous namespace
diff --git a/src/hooks/dhcp/lease_cmds/tests/lease_cmds_func_unittest.h b/src/hooks/dhcp/lease_cmds/tests/lease_cmds_func_unittest.h
new file mode 100644 (file)
index 0000000..30a2f75
--- /dev/null
@@ -0,0 +1,47 @@
+// Copyright (C) 2025 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 <dhcpsrv/lease_mgr_factory.h>
+
+#include <gtest/gtest.h>
+
+namespace {
+
+#define SCOPED_LINE(line) \
+    std::stringstream ss; \
+    ss << "Scenario at line: " << line; \
+    SCOPED_TRACE(ss.str());
+
+class LeaseCmdsFuncTest :  public ::testing::Test { 
+public:
+    /// @brief Constructor
+    LeaseCmdsFuncTest() {
+        isc::dhcp::LeaseMgrFactory::destroy();
+        lmptr_ = 0;
+    }
+
+    virtual void SetUp() {
+        initLeaseMgr();
+    }
+
+    /// @brief Destructor
+    ///
+    virtual ~LeaseCmdsFuncTest() {
+        isc::dhcp::LeaseMgrFactory::destroy();
+        lmptr_ = 0;
+    }
+
+    /// @brief Initializes lease manager creates the lease manager
+    /// and initial leases.
+    virtual void initLeaseMgr() = 0;
+
+    /// @brief Pointer to the lease manager
+    isc::dhcp::LeaseMgr* lmptr_;
+};
+
+} // end of anonymous namespace