// Copyright (C) 2015-2024 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

#ifndef LIBDHCPSRV_ALLOC_ENGINE_UTILS_H
#define LIBDHCPSRV_ALLOC_ENGINE_UTILS_H

#include <asiolink/io_address.h>
#include <dhcpsrv/alloc_engine.h>
#include <dhcpsrv/cfgmgr.h>
#include <dhcpsrv/iterative_allocator.h>
#include <dhcpsrv/lease_mgr.h>
#include <dhcpsrv/lease_mgr_factory.h>

#include <gtest/gtest.h>
#include <vector>

namespace isc {
namespace dhcp {
namespace test {

/// @file   alloc_engine_utils.h
///
/// @brief This is a header file for all Allocation Engine tests.
///
/// There used to be one, huge (over 3kloc) alloc_engine_unittest.cc. It is now
/// split into serveral smaller files:
/// alloc_engine_utils.h - contains test class definitions (this file)
/// alloc_engine_utils.cc - contains test class implementation
/// alloc_engine4_unittest.cc - all unit-tests dedicated to IPv4
/// alloc_engine6_unittest.cc - all unit-tests dedicated to IPv6
/// alloc_engine_hooks_unittest.cc - all unit-tests dedicated to hooks

/// @brief Test that statistic manager holds a given value.
///
/// This function may be used in many allocation tests and there's no
/// single base class for it. @todo consider moving it src/lib/util.
///
/// It checks statistics by name, either the global version when the subnet ID
/// is SUBNET_ID_UNUSED or the subnet statistic, including the pool 0 respective
/// statistic.
///
/// @param stat_name Statistic name.
/// @param exp_value Expected value.
/// @param subnet_id subnet_id of the desired subnet, if not zero
/// @param fail_missing flag which indicate if test should fail if the statistic
/// does not exist, or simply ignore it.
///
/// @return true if the statistic manager holds a particular value,
/// false otherwise.
bool testStatistics(const std::string& stat_name,
                    const int64_t exp_value,
                    const SubnetID subnet_id = SUBNET_ID_UNUSED,
                    bool fail_missing = true);

/// @brief Get a value held by statistic manager.
///
/// This function may be used in some allocation tests and there's no
/// single base class for it. @todo consider moving it src/lib/util.
///
/// @param stat_name Statistic name.
/// @param subnet_id subnet_id of the desired subnet, if not zero.
///
/// @return the value held by the statistic manager or zero.
int64_t getStatistics(const std::string& stat_name,
                      const SubnetID subnet_id = SUBNET_ID_UNUSED);

/// @brief IterativeAllocator with internal methods exposed
class NakedIterativeAllocator : public IterativeAllocator {
public:
    /// @brief constructor
    ///
    /// @param type pool types that will be iterated through
    NakedIterativeAllocator(Lease::Type type, const WeakSubnetPtr& subnet)
        : IterativeAllocator(type, subnet) {
    }

    using IterativeAllocator::increaseAddress;
    using IterativeAllocator::increasePrefix;
};

/// @brief Allocation engine with some internal methods exposed
class NakedAllocEngine : public AllocEngine {
public:
    /// @brief the sole constructor
    ///
    /// @param attempts number of lease selection attempts before giving up
    NakedAllocEngine(unsigned int attempts)
        : AllocEngine(attempts) {
    }

    // Expose internal classes for testing purposes
    using AllocEngine::updateLease4ExtendedInfo;

    /// @brief Wrapper method for invoking AllocEngine4::updateLease4ExtendedInfo().
    ///
    /// @param lease lease to update
    /// @param ctx current packet processing context
    ///
    /// @return true if extended information was changed
    bool callUpdateLease4ExtendedInfo(const Lease4Ptr& lease,
                                      AllocEngine::ClientContext4& ctx) const {
        return (updateLease4ExtendedInfo(lease, ctx));
    }

    /// @brief Wrapper method for invoking AllocEngine6::updateLease6ExtendedInfo().
    ///
    /// @param lease lease to update
    /// @param ctx current packet processing context
    void callUpdateLease6ExtendedInfo(const Lease6Ptr& lease,
                                      AllocEngine::ClientContext6& ctx) const {
        updateLease6ExtendedInfo(lease, ctx);
    }
};

/// @brief Used in Allocation Engine tests for IPv6
class AllocEngine6Test : public ::testing::Test {
public:

    /// @brief Specified expected result of a given operation
    enum ExpectedResult {
        SHOULD_PASS,
        SHOULD_FAIL
    };

    /// @brief Default constructor
    ///
    /// Sets duid_, iaid_, subnet_, pool_ fields to example values used
    /// in many tests, initializes cfg_mgr configuration and creates
    /// lease database.
    AllocEngine6Test();

    /// @brief Configures a subnet and adds one pool to it.
    ///
    /// This function removes existing v6 subnets before configuring
    /// a new one.
    ///
    /// @param subnet Address of a subnet to be configured.
    /// @param pool_start First address in the address pool.
    /// @param pool_end Last address in the address pool.
    /// @param pd_pool_prefix Prefix for the prefix delegation pool. It
    /// defaults to 0 which means that PD pool is not specified.
    /// @param pd_pool_length Length of the PD pool prefix.
    /// @param pd_delegated_length Delegated prefix length.
    void initSubnet(const asiolink::IOAddress& subnet,
                    const asiolink::IOAddress& pool_start,
                    const asiolink::IOAddress& pool_end,
                    const asiolink::IOAddress& pd_pool_prefix =
                    asiolink::IOAddress::IPV6_ZERO_ADDRESS(),
                    const uint8_t pd_pool_length = 0,
                    const uint8_t pd_delegated_length = 0);

    /// @brief Initializes FQDN data for a test.
    ///
    /// The initialized values are used by the test fixture class members to
    /// verify the correctness of a lease.
    ///
    /// @param hostname Hostname to be assigned to a lease.
    /// @param fqdn_fwd Indicates whether or not to perform forward DNS update
    /// for a lease.
    /// @param fqdn_fwd Indicates whether or not to perform reverse DNS update
    /// for a lease.
    void initFqdn(const std::string& hostname, const bool fqdn_fwd,
                  const bool fqdn_rev) {
        hostname_ = hostname;
        fqdn_fwd_ = fqdn_fwd;
        fqdn_rev_ = fqdn_rev;
    }

    /// @brief Wrapper around call to AllocEngine6::findReservation
    ///
    /// If a reservation is found by the engine, the function sets
    /// ctx.hostname_ accordingly.
    ///
    /// @param engine allocation engine to use
    /// @param ctx client context to pass into engine's findReservation method
    void findReservation(AllocEngine& engine, AllocEngine::ClientContext6& ctx);

    /// @brief attempts to convert leases collection to a single lease
    ///
    /// This operation makes sense if there is at most one lease in the
    /// collection. Otherwise it will throw.
    ///
    /// @param col collection of leases (zero or one leases allowed)
    /// @throw MultipleRecords if there is more than one lease
    ///
    /// @return Lease6 pointer (or NULL if collection was empty)
    Lease6Ptr expectOneLease(const Lease6Collection& col) {
        if (col.size() > 1) {
            isc_throw(db::MultipleRecords, "More than one lease found in collection");
        }
        if (col.empty()) {
            return (Lease6Ptr());
        }
        return (*col.begin());
    }

    /// @brief checks if Lease6 matches expected configuration
    ///
    /// @param duid pointer to the client's DUID.
    /// @param lease lease to be checked
    /// @param exp_type expected lease type
    /// @param exp_pd_len expected prefix length
    /// @param expected_in_subnet whether the lease is expected to be in subnet
    /// @param expected_in_pool whether the lease is expected to be in dynamic
    void checkLease6(const DuidPtr& duid,
                     const Lease6Ptr& lease,
                     Lease::Type exp_type,
                     uint8_t exp_pd_len = 128,
                     bool expected_in_subnet = true,
                     bool expected_in_pool = true) {

        // that is belongs to the right subnet
        EXPECT_EQ(lease->subnet_id_, subnet_->getID());

        if (expected_in_subnet) {
            EXPECT_TRUE(subnet_->inRange(lease->addr_))
                << " address: " << lease->addr_.toText();
        } else {
            EXPECT_FALSE(subnet_->inRange(lease->addr_))
                << " address: " << lease->addr_.toText();
        }

        if (expected_in_pool) {
            EXPECT_TRUE(subnet_->inPool(exp_type, lease->addr_));
        } else {
            EXPECT_FALSE(subnet_->inPool(exp_type, lease->addr_));
        }

        // that it have proper parameters
        EXPECT_EQ(exp_type, lease->type_);
        EXPECT_EQ(iaid_, lease->iaid_);
        if (subnet_->getValid().getMin() == subnet_->getValid().getMax()) {
            EXPECT_EQ(subnet_->getValid(), lease->valid_lft_);
        } else {
            EXPECT_LE(subnet_->getValid().getMin(), lease->valid_lft_);
            EXPECT_GE(subnet_->getValid().getMax(), lease->valid_lft_);
        }
        if (subnet_->getPreferred().getMin() == subnet_->getPreferred().getMax()) {
            EXPECT_EQ(subnet_->getPreferred(), lease->preferred_lft_);
        } else {
            EXPECT_LE(subnet_->getPreferred().getMin(), lease->preferred_lft_);
            EXPECT_GE(subnet_->getPreferred().getMax(), lease->preferred_lft_);
        }
        EXPECT_EQ(exp_pd_len, lease->prefixlen_);
        EXPECT_EQ(fqdn_fwd_, lease->fqdn_fwd_);
        EXPECT_EQ(fqdn_rev_, lease->fqdn_rev_);
        EXPECT_EQ(hostname_, lease->hostname_);
        EXPECT_TRUE(*lease->duid_ == *duid);
        EXPECT_EQ(0, lease->reuseable_valid_lft_);
        /// @todo: check cltt
    }

    /// @brief Checks if specified address or prefix has been recorded as
    /// allocated to the client.
    ///
    /// @param lease Allocated lease.
    /// @param ctx Context structure in which this function should check if
    /// leased address is stored as allocated resource.
    void checkAllocatedResources(const Lease6Ptr& lease,
                                 AllocEngine::ClientContext6& ctx) {
        EXPECT_TRUE(ctx.isAllocated(lease->addr_, lease->prefixlen_));
    }

    /// @brief Checks if specified address is increased properly
    ///
    /// Method uses gtest macros to mark check failure. This is a proxy
    /// method, since increaseAddress was moved to IOAddress class.
    ///
    /// @param alloc IterativeAllocator that is tested
    /// @param input address to be increased
    /// @param exp_output expected address after increase
    void checkAddrIncrease(NakedIterativeAllocator& alloc,
                           std::string input,
                           std::string exp_output) {
        EXPECT_EQ(exp_output, alloc.increaseAddress(asiolink::IOAddress(input),
                                                    false, 0).toText());
    }

    /// @brief Checks if increasePrefix() works as expected
    ///
    /// Method uses gtest macros to mark check failure.
    ///
    /// @param alloc allocator to be tested
    /// @param input IPv6 prefix (as a string)
    /// @param prefix_len prefix len
    /// @param exp_output expected output (string)
    void checkPrefixIncrease(NakedIterativeAllocator& alloc,
                             std::string input,
                             uint8_t prefix_len,
                             std::string exp_output) {
        EXPECT_EQ(exp_output,
                  alloc.increasePrefix(asiolink::IOAddress(input),
                                       prefix_len).toText());
    }

    /// @brief Checks if the simple allocation can succeed
    ///
    /// The type of lease is determined by pool type (pool->getType())
    ///
    /// @param pool pool from which the lease will be allocated from
    /// @param hint address to be used as a hint
    /// @param fake true - this is fake allocation (SOLICIT)
    /// @param in_pool specifies whether the lease is expected to be in pool
    ///
    /// @return allocated lease (or NULL)
    Lease6Ptr simpleAlloc6Test(const Pool6Ptr& pool,
                               const asiolink::IOAddress& hint,
                               bool fake,
                               bool in_pool = true);

    /// @brief Checks if the simple allocation can succeed with lifetimes.
    ///
    /// The type of lease is determined by pool type (pool->getType())
    ///
    /// @param pool pool from which the lease will be allocated from
    /// @param hint address to be used as a hint
    /// @param preferred preferred lifetime to be used as a hint
    /// @param valid valid lifetime to be used as a hint
    /// @param exp_preferred expected lease preferred lifetime
    /// @param exp_valid expected lease valid lifetime
    /// @param class_def class definition to add to the context
    ///
    /// @return allocated lease (or NULL)
    Lease6Ptr simpleAlloc6Test(const Pool6Ptr& pool,
                               const asiolink::IOAddress& hint,
                               uint32_t preferred,
                               uint32_t valid,
                               uint32_t exp_preferred,
                               uint32_t exp_valid,
                               ClientClassDefPtr class_def = ClientClassDefPtr());

    /// @brief Checks if the simple allocation can succeed for custom DUID.
    ///
    /// The type of lease is determined by pool type (pool->getType())
    ///
    /// @param pool pool from which the lease will be allocated from
    /// @param duid pointer to the DUID used for allocation.
    /// @param hint address to be used as a hint
    /// @param fake true - this is fake allocation (SOLICIT)
    /// @param in_pool specifies whether the lease is expected to be in pool
    ///
    /// @return allocated lease (or NULL)
    Lease6Ptr simpleAlloc6Test(const Pool6Ptr& pool,
                               const DuidPtr& duid,
                               const asiolink::IOAddress& hint,
                               bool fake,
                               bool in_pool = true);

    /// @brief Checks if the allocation can succeed.
    ///
    /// The type of lease is determined by pool type (pool->getType()).
    /// This test is particularly useful in connection with @ref renewTest.
    ///
    /// @param engine a reference to Allocation Engine
    /// @param pool pool from which the lease will be allocated from
    /// @param hint address to be used as a hint
    /// @param fake true - this is fake allocation (SOLICIT)
    /// @param in_pool specifies whether the lease is expected to be in pool
    /// @param hint_prefix_length The hint prefix length that the client
    /// provided.
    ///
    /// @return allocated lease(s) (may be empty)
    Lease6Collection allocateTest(AllocEngine& engine,
                                  const Pool6Ptr& pool,
                                  const asiolink::IOAddress& hint,
                                  bool fake,
                                  bool in_pool = true,
                                  uint8_t hint_prefix_length = 128);

    /// @brief Checks if the allocation can be renewed.
    ///
    /// The type of lease is determined by pool type (pool->getType()).
    /// This test is particularly useful as a follow up to @ref allocateTest.
    ///
    /// @param engine a reference to Allocation Engine
    /// @param pool pool from which the lease will be allocated from
    /// @param hints address to be used as a hint
    /// @param in_subnet whether the lease is expected to be in subnet
    /// @param in_pool specifies whether the lease is expected to be in pool
    ///
    /// @return allocated lease(s) (may be empty)
    Lease6Collection renewTest(AllocEngine& engine,
                               const Pool6Ptr& pool,
                               AllocEngine::HintContainer& hints,
                               bool in_subnet,
                               bool in_pool);

    /// @brief Checks if the address allocation with a hint that is in range,
    ///        in pool, but is currently used, can succeed
    ///
    /// Method uses gtest macros to mark check failure.
    ///
    /// @param type lease type
    /// @param used_addr address should be preallocated (simulates prior
    ///        allocation by some other user)
    /// @param requested address requested by the client
    /// @param expected_pd_len expected PD len (128 for addresses)
    void allocWithUsedHintTest(Lease::Type type,
                               asiolink::IOAddress used_addr,
                               asiolink::IOAddress requested,
                               uint8_t expected_pd_len);

    /// @brief Generic test used for IPv6 lease allocation and reuse
    ///
    /// This test inserts existing_lease (if specified, may be null) into the
    /// LeaseMgr, then conducts lease allocation (pretends that client
    /// sent either Solicit or Request, depending on fake_allocation).
    /// Allocated lease is then returned (using result) for further inspection.
    ///
    /// @param alloc_engine allocation engine
    /// @param existing_lease optional lease to be inserted in the database
    /// @param addr address to be requested by client
    /// @param fake_allocation true = SOLICIT, false = REQUEST
    /// @param exp_result expected result
    /// @param result [out] allocated lease
    void testReuseLease6(const AllocEnginePtr& alloc_engine,
                         Lease6Ptr& existing_lease,
                         const std::string& addr,
                         const bool fake_allocation,
                         ExpectedResult exp_result,
                         Lease6Ptr& result);

    /// @brief Creates a declined IPv6 lease with specified expiration time
    ///
    /// expired parameter controls probation period. Positive value
    /// means that the lease will expire in X seconds. Negative means
    /// that the lease expired X seconds ago. 0 means it expires now.
    /// Probation period is a parameter that specifies for how long
    /// a lease will stay unavailable after decline.
    ///
    /// @param addr address of the lease
    /// @param probation_period expressed in seconds
    /// @param expired number of seconds when the lease will expire
    ///
    /// @return generated lease
    Lease6Ptr generateDeclinedLease(const std::string& addr,
                                    time_t probation_period,
                                    int32_t expired);

    /// @brief checks if bogus hint can be ignored and the allocation succeeds
    ///
    /// This test checks if the allocation with a hing that is out of the blue
    /// can succeed. The invalid hint should be ignored completely.
    ///
    /// @param type Lease type
    /// @param hint hint (as sent by a client)
    /// @param expected_pd_len (used in validation)
    void allocBogusHint6(Lease::Type type, asiolink::IOAddress hint,
                         uint8_t expected_pd_len);

    /// @brief Utility function that creates a host reservation (duid)
    ///
    /// @param add_to_host_mgr true if the reservation should be added
    /// @param type specifies reservation type
    /// @param addr specifies reserved address or prefix
    /// @param prefix_len prefix length (should be 128 for addresses)
    ///
    /// @return created Host object.
    HostPtr createHost6(bool add_to_host_mgr,
                        IPv6Resrv::Type type,
                        const asiolink::IOAddress& addr,
                        uint8_t prefix_len) {
        HostPtr host(new Host(&duid_->getDuid()[0], duid_->getDuid().size(), Host::IDENT_DUID,
                              SUBNET_ID_UNUSED, subnet_->getID(), asiolink::IOAddress("0.0.0.0")));
        IPv6Resrv resv(type, addr, prefix_len);
        host->addReservation(resv);

        if (add_to_host_mgr) {
            addHost(host);
        }

        return (host);
    }

    /// @brief Add a host reservation to the current configuration
    ///
    /// Adds the given host reservation to the current configuration by
    /// casting it to non-const.  We do it this way rather than adding it to
    /// staging and then committing as that wipes out the current configuration
    /// such as subnets.
    ///
    /// @param host host reservation to add
    void addHost(HostPtr& host) {
        SrvConfigPtr cfg = boost::const_pointer_cast<SrvConfig>(CfgMgr::instance().getCurrentCfg());
        cfg->getCfgHosts()->add(host);
    }

    /// @brief Utility function that creates a host reservation (hwaddr)
    ///
    /// @param add_to_host_mgr true if the reservation should be added
    /// @param type specifies reservation type
    /// @param hwaddr hardware address to be reserved to
    /// @param addr specifies reserved address or prefix
    /// @param prefix_len prefix length (should be 128 for addresses)
    ///
    /// @return created Host object.
    HostPtr createHost6HWAddr(bool add_to_host_mgr,
                              IPv6Resrv::Type type,
                              HWAddrPtr& hwaddr,
                              const asiolink::IOAddress& addr,
                              uint8_t prefix_len);

    /// @brief Utility function that decrements cltt of a persisted lease
    ///
    /// This function is used to simulate the passage of time by decrementing
    /// the lease's cltt, currently by 1.  It fetches the desired lease from the
    /// lease manager, decrements the cltt, then updates the lease in the lease
    /// manager.  Next, it refetches the lease and verifies the update took place.
    ///
    /// @param[in][out] lease pointer reference to the lease to modify.  Upon
    /// return it will point to the newly updated lease.
    void rollbackPersistedCltt(Lease6Ptr& lease) {
        ASSERT_TRUE(lease) << "rollbackPersistedCltt lease is empty";

        // Fetch it, so we can update it.
        Lease6Ptr from_mgr = LeaseMgrFactory::instance().getLease6(lease->type_, lease->addr_);
        ASSERT_TRUE(from_mgr) << "rollbackPersistedCltt: lease not found?";

        // Decrement cltt then update it in the manager.
        --from_mgr->cltt_;
        ASSERT_NO_THROW(LeaseMgrFactory::instance().updateLease6(from_mgr));

        // Fetch it fresh.
        lease = LeaseMgrFactory::instance().getLease6(lease->type_, lease->addr_);

        // Make sure it stuck.
        ASSERT_EQ(lease->cltt_, from_mgr->cltt_);
    }

    virtual ~AllocEngine6Test() {
        factory_.destroy();
    }

    DuidPtr duid_;            ///< client-identifier (value used in tests)
    HWAddrPtr hwaddr_;        ///< client's hardware address
    uint32_t iaid_;           ///< IA identifier (value used in tests)
    Subnet6Ptr subnet_;       ///< subnet6 (used in tests)
    Pool6Ptr pool_;           ///< NA pool belonging to subnet_
    Pool6Ptr pd_pool_;        ///< PD pool belonging to subnet_
    std::string hostname_;    ///< Hostname
    bool fqdn_fwd_;           ///< Perform forward update for a lease.
    bool fqdn_rev_;           ///< Perform reverse update for a lease.
    LeaseMgrFactory factory_; ///< pointer to LeaseMgr factory
    ClientClasses cc_;        ///< client classes
};

/// @brief Used in Allocation Engine tests for IPv4
class AllocEngine4Test : public ::testing::Test {
public:

    /// @brief Specified expected result of a given operation
    enum ExpectedResult {
        SHOULD_PASS,
        SHOULD_FAIL
    };

    /// @brief Default constructor
    ///
    /// Sets clientid_, hwaddr_, subnet_, pool_ fields to example values
    /// used in many tests, initializes cfg_mgr configuration and creates
    /// lease database.
    ///
    /// It also re-initializes the Host Manager.
    AllocEngine4Test();

    /// @brief checks if Lease4 matches expected configuration
    ///
    /// @param lease lease to be checked
    void checkLease4(const Lease4Ptr& lease) {
        // Check that is belongs to the right subnet
        EXPECT_EQ(lease->subnet_id_, subnet_->getID());
        EXPECT_TRUE(subnet_->inRange(lease->addr_));
        EXPECT_TRUE(subnet_->inPool(Lease::TYPE_V4, lease->addr_));

        // Check that it has proper parameters
        if (subnet_->getValid().getMin() == subnet_->getValid().getMax()) {
            EXPECT_EQ(subnet_->getValid(), lease->valid_lft_);
        } else {
            EXPECT_LE(subnet_->getValid().getMin(), lease->valid_lft_);
            EXPECT_GE(subnet_->getValid().getMax(), lease->valid_lft_);
        }
        if (lease->client_id_ && !clientid_) {
            ADD_FAILURE() << "Lease4 has a client-id, while it should have none.";
        } else if (!lease->client_id_ && clientid_) {
            ADD_FAILURE() << "Lease4 has no client-id, but it was expected to have one.";
        } else if (lease->client_id_ && clientid_) {
            EXPECT_TRUE(*lease->client_id_ == *clientid_);
        }
        EXPECT_TRUE(*lease->hwaddr_ == *hwaddr_);
        EXPECT_EQ(0, lease->reuseable_valid_lft_);
        /// @todo: check cltt
    }

    /// @brief Generic test used for IPv4 lease allocation and reuse
    ///
    /// This test inserts existing_lease (if specified, may be null) into the
    /// LeaseMgr, then conducts lease allocation (pretends that client
    /// sent either Discover or Request, depending on fake_allocation).
    /// Allocated lease is then returned (using result) for further inspection.
    ///
    /// @param alloc_engine allocation engine
    /// @param existing_lease optional lease to be inserted in the database
    /// @param addr address to be requested by client
    /// @param fake_allocation true = DISCOVER, false = REQUEST
    /// @param exp_result expected result
    /// @param result [out] allocated lease
    void testReuseLease4(const AllocEnginePtr& alloc_engine,
                         Lease4Ptr& existing_lease,
                         const std::string& addr,
                         const bool fake_allocation,
                         ExpectedResult exp_result,
                         Lease4Ptr& result);

    /// @brief Creates a declined IPv4 lease with specified expiration time
    ///
    /// expired parameter controls probation period. Positive value
    /// means that the lease will expire in X seconds. Negative means
    /// that the lease expired X seconds ago. 0 means it expires now.
    /// Probation period is a parameter that specifies for how long
    /// a lease will stay unavailable after decline.
    ///
    /// @param addr address of the lease
    /// @param probation_period expressed in seconds
    /// @param expired number of seconds when the lease will expire
    ///
    /// @return generated lease
    Lease4Ptr generateDeclinedLease(const std::string& addr,
                                    time_t probation_period,
                                    int32_t expired);

    /// @brief Create a subnet with a specified pool of addresses.
    ///
    /// @param pool_start First address in the pool.
    /// @param pool_end Last address in the pool.
    void initSubnet(const asiolink::IOAddress& pool_start,
                    const asiolink::IOAddress& pool_end);

    /// @brief Default constructor
    virtual ~AllocEngine4Test() {
        factory_.destroy();
    }

    ClientIdPtr clientid_;            ///< Client-identifier (value used in tests)
    ClientIdPtr clientid2_;           ///< Alternative client-identifier.
    HWAddrPtr hwaddr_;                ///< Hardware address (value used in tests)
    HWAddrPtr hwaddr2_;               ///< Alternative hardware address.
    Subnet4Ptr subnet_;               ///< Subnet4 (used in tests)
    Pool4Ptr pool_;                   ///< Pool belonging to subnet_
    LeaseMgrFactory factory_;         ///< Pointer to LeaseMgr factory
    AllocEngine::ClientContext4 ctx_; ///< Context information passed to various
    ClientClasses cc_;                ///< Client classes
                                      ///< allocation engine functions.
};

} // namespace test
} // namespace dhcp
} // namespace isc

#endif
