diff options
Diffstat (limited to 'src/lib/d2srv/tests/nc_trans_unittests.cc')
-rw-r--r-- | src/lib/d2srv/tests/nc_trans_unittests.cc | 1279 |
1 files changed, 1279 insertions, 0 deletions
diff --git a/src/lib/d2srv/tests/nc_trans_unittests.cc b/src/lib/d2srv/tests/nc_trans_unittests.cc new file mode 100644 index 0000000..89a23b7 --- /dev/null +++ b/src/lib/d2srv/tests/nc_trans_unittests.cc @@ -0,0 +1,1279 @@ +// Copyright (C) 2013-2021 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> +#include <asiolink/asio_wrapper.h> +#include <asiolink/io_service.h> +#include <asiolink/interval_timer.h> +#include <d2srv/nc_trans.h> +#include <d2srv/testutils/nc_test_utils.h> +#include <dns/opcode.h> +#include <dns/messagerenderer.h> +#include <log/logger_support.h> +#include <log/macros.h> +#include <util/buffer.h> + +#include <boost/date_time/posix_time/posix_time.hpp> +#include <gtest/gtest.h> +#include <functional> + +using namespace std; +using namespace isc; +using namespace isc::d2; +using namespace isc::util; +using namespace boost::posix_time; + +namespace { + +/// @brief Test derivation of NameChangeTransaction for exercising state +/// model mechanics. +/// +/// This class facilitates testing by making non-public methods accessible so +/// they can be invoked directly in test routines. It implements a very +/// rudimentary state model, sufficient to test the state model mechanics +/// supplied by the base class. +class NameChangeStub : public NameChangeTransaction { +public: + + // NameChangeStub states + static const int DOING_UPDATE_ST = NCT_DERIVED_STATE_MIN + 1; + + // NameChangeStub events + static const int SEND_UPDATE_EVT = NCT_DERIVED_EVENT_MIN + 2; + + /// @brief Flag which specifies if the NameChangeStub's callback should be + /// used instead of the NameChangeTransaction's callback. + bool use_stub_callback_; + + /// @brief Constructor + /// + /// Parameters match those needed by NameChangeTransaction. + NameChangeStub(asiolink::IOServicePtr& io_service, + dhcp_ddns::NameChangeRequestPtr& ncr, + DdnsDomainPtr& forward_domain, + DdnsDomainPtr& reverse_domain, + D2CfgMgrPtr& cfg_mgr) + : NameChangeTransaction(io_service, ncr, forward_domain, + reverse_domain, cfg_mgr), + use_stub_callback_(false) { + } + + /// @brief Destructor + virtual ~NameChangeStub() { + } + + /// @brief DNSClient callback + /// Overrides the callback in NameChangeTransaction to allow testing + /// sendUpdate without incorporating execution of the state model + /// into the test. + /// It sets the DNS status update and posts IO_COMPLETED_EVT as does + /// the normal callback. + virtual void operator()(DNSClient::Status status) { + if (use_stub_callback_) { + setDnsUpdateStatus(status); + postNextEvent(IO_COMPLETED_EVT); + } else { + // For tests which need to use the real callback. + NameChangeTransaction::operator()(status); + } + } + + /// @brief Some tests require a server to be selected. This method + /// selects the first server in the forward domain without involving + /// state model execution to do so. + bool selectFwdServer() { + if (getForwardDomain()) { + initServerSelection(getForwardDomain()); + selectNextServer(); + return (getCurrentServer().get() != 0); + } + + return (false); + } + + /// @brief Empty handler used to satisfy map verification. + void dummyHandler() { + isc_throw(NameChangeTransactionError, + "dummyHandler - invalid event: " << getContextStr()); + } + + /// @brief State handler for the READY_ST. + /// + /// Serves as the starting state handler, it consumes the + /// START_EVT "transitioning" to the state, DOING_UPDATE_ST and + /// sets the next event to SEND_UPDATE_EVT. + void readyHandler() { + switch(getNextEvent()) { + case START_EVT: + transition(DOING_UPDATE_ST, SEND_UPDATE_EVT); + break; + default: + // its bogus + isc_throw(NameChangeTransactionError, + "readyHandler - invalid event: " << getContextStr()); + } + } + + /// @brief State handler for the DOING_UPDATE_ST. + /// + /// Simulates a state that starts some form of asynchronous work. + /// When next event is SEND_UPDATE_EVT it sets the status to pending + /// and signals the state model must "wait" for an event by setting + /// next event to NOP_EVT. + /// + /// When next event is IO_COMPLETED_EVT, it transitions to the state, + /// PROCESS_TRANS_OK_ST, and sets the next event to UPDATE_OK_EVT. + void doingUpdateHandler() { + switch(getNextEvent()) { + case SEND_UPDATE_EVT: + setNcrStatus(dhcp_ddns::ST_PENDING); + postNextEvent(NOP_EVT); + break; + case IO_COMPLETED_EVT: + if (getDnsUpdateStatus() == DNSClient::SUCCESS) { + setForwardChangeCompleted(true); + transition(PROCESS_TRANS_OK_ST, UPDATE_OK_EVT); + } else { + transition(PROCESS_TRANS_FAILED_ST, UPDATE_FAILED_EVT); + } + break; + default: + // its bogus + isc_throw(NameChangeTransactionError, + "doingUpdateHandler - invalid event: " + << getContextStr()); + } + } + + /// @brief State handler for the PROCESS_TRANS_OK_ST. + /// + /// This is the last state in the model. Note that it sets the + /// status to completed and next event to NOP_EVT. + void processTransDoneHandler() { + switch(getNextEvent()) { + case UPDATE_OK_EVT: + setNcrStatus(dhcp_ddns::ST_COMPLETED); + endModel(); + break; + case UPDATE_FAILED_EVT: + setNcrStatus(dhcp_ddns::ST_FAILED); + endModel(); + break; + default: + // its bogus + isc_throw(NameChangeTransactionError, + "processTransDoneHandler - invalid event: " + << getContextStr()); + } + } + + /// @brief Construct the event dictionary. + virtual void defineEvents() { + // Invoke the base call implementation first. + NameChangeTransaction::defineEvents(); + + // Define our events. + defineEvent(SEND_UPDATE_EVT, "SEND_UPDATE_EVT"); + } + + /// @brief Verify the event dictionary. + virtual void verifyEvents() { + // Invoke the base call implementation first. + NameChangeTransaction::verifyEvents(); + + // Define our events. + getEvent(SEND_UPDATE_EVT); + } + + /// @brief Construct the state dictionary. + virtual void defineStates() { + // Invoke the base call implementation first. + NameChangeTransaction::defineStates(); + + // Define our states. + defineState(READY_ST, "READY_ST", + std::bind(&NameChangeStub::readyHandler, this)); + + defineState(SELECTING_FWD_SERVER_ST, "SELECTING_FWD_SERVER_ST", + std::bind(&NameChangeStub::dummyHandler, this)); + + defineState(SELECTING_REV_SERVER_ST, "SELECTING_REV_SERVER_ST", + std::bind(&NameChangeStub::dummyHandler, this)); + + defineState(DOING_UPDATE_ST, "DOING_UPDATE_ST", + std::bind(&NameChangeStub::doingUpdateHandler, + this)); + + defineState(PROCESS_TRANS_OK_ST, "PROCESS_TRANS_OK_ST", + std::bind(&NameChangeStub:: + processTransDoneHandler, this)); + + defineState(PROCESS_TRANS_FAILED_ST, "PROCESS_TRANS_FAILED_ST", + std::bind(&NameChangeStub:: + processTransDoneHandler, this)); + } + + /// @brief Verify the event dictionary. + virtual void verifyStates() { + // Invoke the base call implementation first. + NameChangeTransaction::verifyStates(); + + // Check our states. + getStateInternal(DOING_UPDATE_ST); + } + + // Expose the protected methods to be tested. + using StateModel::runModel; + using StateModel::postNextEvent; + using StateModel::setState; + using StateModel::initDictionaries; + using NameChangeTransaction::initServerSelection; + using NameChangeTransaction::selectNextServer; + using NameChangeTransaction::getCurrentServer; + using NameChangeTransaction::getDNSClient; + using NameChangeTransaction::setNcrStatus; + using NameChangeTransaction::setDnsUpdateRequest; + using NameChangeTransaction::clearDnsUpdateRequest; + using NameChangeTransaction::clearUpdateAttempts; + using NameChangeTransaction::setDnsUpdateStatus; + using NameChangeTransaction::getDnsUpdateResponse; + using NameChangeTransaction::setDnsUpdateResponse; + using NameChangeTransaction::clearDnsUpdateResponse; + using NameChangeTransaction::getForwardChangeCompleted; + using NameChangeTransaction::getReverseChangeCompleted; + using NameChangeTransaction::setForwardChangeCompleted; + using NameChangeTransaction::setReverseChangeCompleted; + using NameChangeTransaction::setUpdateAttempts; + using NameChangeTransaction::transition; + using NameChangeTransaction::retryTransition; + using NameChangeTransaction::sendUpdate; + using NameChangeTransaction::prepNewRequest; + using NameChangeTransaction::addLeaseAddressRdata; + using NameChangeTransaction::addDhcidRdata; + using NameChangeTransaction::addPtrRdata; + using NameChangeTransaction::responseString; + using NameChangeTransaction::transactionOutcomeString; +}; + +// Declare them so Gtest can see them. +const int NameChangeStub::DOING_UPDATE_ST; +const int NameChangeStub::SEND_UPDATE_EVT; + +/// @brief Defines a pointer to a NameChangeStubPtr instance. +typedef boost::shared_ptr<NameChangeStub> NameChangeStubPtr; + +/// @brief Test fixture for testing NameChangeTransaction +/// +/// Note this class uses NameChangeStub class to exercise non-public +/// aspects of NameChangeTransaction. +class NameChangeTransactionTest : public TransactionTest { +public: + NameChangeTransactionTest() { + } + + virtual ~NameChangeTransactionTest() { + } + + + /// @brief Instantiates a NameChangeStub test transaction + /// The transaction is constructed around a predefined (i.e "canned") + /// NameChangeRequest. The request has both forward and reverse DNS + /// changes requested, and both forward and reverse domains are populated. + /// @param tsig_key_info pointer to the TSIGKeyInfo to use, defaults to + /// an empty pointer, in which case TSIG will not be used. + NameChangeStubPtr makeCannedTransaction(const TSIGKeyInfoPtr& + tsig_key_info = TSIGKeyInfoPtr()) { + // Creates IPv4 remove request, forward, and reverse domains. + setupForIPv4Transaction(dhcp_ddns::CHG_ADD, FWD_AND_REV_CHG, + tsig_key_info); + + // Now create the test transaction as would occur in update manager. + // Instantiate the transaction as would be done by update manager. + return (NameChangeStubPtr(new NameChangeStub(io_service_, ncr_, + forward_domain_, reverse_domain_, cfg_mgr_))); + } + + /// @brief Instantiates a NameChangeStub test transaction + /// The transaction is constructed around a predefined (i.e "canned") + /// NameChangeRequest. The request has both forward and reverse DNS + /// changes requested, and both forward and reverse domains are populated. + /// @param key_name value to use to create TSIG key, if blank TSIG will not + /// be used. + NameChangeStubPtr makeCannedTransaction(const std::string& key_name) { + // Creates IPv4 remove request, forward, and reverse domains. + setupForIPv4Transaction(dhcp_ddns::CHG_ADD, FWD_AND_REV_CHG, key_name); + + // Now create the test transaction as would occur in update manager. + // Instantiate the transaction as would be done by update manager. + return (NameChangeStubPtr(new NameChangeStub(io_service_, ncr_, + forward_domain_, reverse_domain_, cfg_mgr_))); + } + + /// @brief Builds and then sends an update request + /// + /// This method is used to build and send and update request. It is used + /// in conjunction with FauxServer to test various message response + /// scenarios. + /// @param name_change Transaction under test + /// @param run_time Maximum time to permit IO processing to run before + /// timing out (in milliseconds) + void doOneExchange(NameChangeStubPtr name_change, + unsigned int run_time = 500) { + // Create a valid request for the transaction. + D2UpdateMessagePtr req; + ASSERT_NO_THROW(req.reset(new D2UpdateMessage(D2UpdateMessage:: + OUTBOUND))); + ASSERT_NO_THROW(name_change->setDnsUpdateRequest(req)); + req->setZone(dns::Name("request.example.com"), dns::RRClass::ANY()); + req->setRcode(dns::Rcode(dns::Rcode::NOERROR_CODE)); + + // Set the flag to use the NameChangeStub's DNSClient callback. + name_change->use_stub_callback_ = true; + + // Invoke sendUpdate. + ASSERT_NO_THROW(name_change->sendUpdate()); + + // Update attempt count should be 1, next event should be NOP_EVT. + ASSERT_EQ(1, name_change->getUpdateAttempts()); + ASSERT_EQ(NameChangeTransaction::NOP_EVT, + name_change->getNextEvent()); + + while (name_change->getNextEvent() == NameChangeTransaction::NOP_EVT) { + int cnt = 0; + ASSERT_NO_THROW(cnt = runTimedIO(run_time)); + if (cnt == 0) { + FAIL() << "IO Service stopped unexpectedly"; + } + } + } +}; + +/// @brief Tests NameChangeTransaction construction. +/// This test verifies that: +/// 1. Construction with null NameChangeRequest +/// 2. Construction with null forward domain is not allowed when the request +/// requires forward change. +/// 3. Construction with null reverse domain is not allowed when the request +/// requires reverse change. +/// 4. Valid construction functions properly +TEST(NameChangeTransaction, construction) { + asiolink::IOServicePtr io_service(new isc::asiolink::IOService()); + D2CfgMgrPtr cfg_mgr(new D2CfgMgr()); + + const char* msg_str = + "{" + " \"change-type\" : 0 , " + " \"forward-change\" : true , " + " \"reverse-change\" : true , " + " \"fqdn\" : \"example.com.\" , " + " \"ip-address\" : \"192.168.2.1\" , " + " \"dhcid\" : \"0102030405060708\" , " + " \"lease-expires-on\" : \"20130121132405\" , " + " \"lease-length\" : 1300, " + " \"use-conflict-resolution\" : true " + "}"; + + dhcp_ddns::NameChangeRequestPtr ncr; + + dhcp_ddns::NameChangeRequestPtr empty_ncr; + DnsServerInfoStoragePtr servers; + DdnsDomainPtr forward_domain; + DdnsDomainPtr reverse_domain; + DdnsDomainPtr empty_domain; + + ASSERT_NO_THROW(ncr = dhcp_ddns::NameChangeRequest::fromJSON(msg_str)); + ASSERT_NO_THROW(forward_domain.reset(new DdnsDomain("*", servers))); + ASSERT_NO_THROW(reverse_domain.reset(new DdnsDomain("*", servers))); + + // Verify that construction with a null IOServicePtr fails. + // @todo Subject to change if multi-threading is implemented. + asiolink::IOServicePtr empty; + EXPECT_THROW(NameChangeTransaction(empty, ncr, + forward_domain, reverse_domain, cfg_mgr), + NameChangeTransactionError); + + // Verify that construction with an empty NameChangeRequest throws. + EXPECT_THROW(NameChangeTransaction(io_service, empty_ncr, + forward_domain, reverse_domain, cfg_mgr), + NameChangeTransactionError); + + // Verify that construction with an empty D2CfgMgr throws. + D2CfgMgrPtr empty_cfg; + EXPECT_THROW(NameChangeTransaction(io_service, empty_ncr, + forward_domain, reverse_domain, + empty_cfg), + NameChangeTransactionError); + + + // Verify that construction with an empty forward domain when the + // NameChangeRequest calls for a forward change throws. + EXPECT_THROW(NameChangeTransaction(io_service, ncr, + empty_domain, reverse_domain, cfg_mgr), + NameChangeTransactionError); + + // Verify that construction with an empty reverse domain when the + // NameChangeRequest calls for a reverse change throws. + EXPECT_THROW(NameChangeTransaction(io_service, ncr, + forward_domain, empty_domain, cfg_mgr), + NameChangeTransactionError); + + // Verify that a valid construction attempt works. + EXPECT_NO_THROW(NameChangeTransaction(io_service, ncr, + forward_domain, reverse_domain, + cfg_mgr)); + + // Verify that an empty forward domain is allowed when the requests does + // not include a forward change. + ncr->setForwardChange(false); + ncr->setReverseChange(true); + EXPECT_NO_THROW(NameChangeTransaction(io_service, ncr, + empty_domain, reverse_domain, + cfg_mgr)); + + // Verify that an empty reverse domain is allowed when the requests does + // not include a reverse change. + ncr->setForwardChange(true); + ncr->setReverseChange(false); + EXPECT_NO_THROW(NameChangeTransaction(io_service, ncr, + forward_domain, empty_domain, + cfg_mgr)); +} + +/// @brief General testing of member accessors. +/// Most if not all of these are also tested as a byproduct of larger tests. +TEST_F(NameChangeTransactionTest, accessors) { + NameChangeStubPtr name_change; + ASSERT_NO_THROW(name_change = makeCannedTransaction()); + + // Verify that fetching the NameChangeRequest works. + dhcp_ddns::NameChangeRequestPtr ncr = name_change->getNcr(); + ASSERT_TRUE(ncr); + + // Verify that getTransactionKey works. + EXPECT_EQ(ncr->getDhcid(), name_change->getTransactionKey()); + + // Verify that getRequestId works. + EXPECT_EQ(ncr->getRequestId(), name_change->getRequestId()); + + // Verify that NcrStatus can be set and retrieved. + EXPECT_NO_THROW(name_change->setNcrStatus(dhcp_ddns::ST_FAILED)); + EXPECT_EQ(dhcp_ddns::ST_FAILED, ncr->getStatus()); + + // Verify that the forward domain can be retrieved. + ASSERT_TRUE(name_change->getForwardDomain()); + EXPECT_EQ(forward_domain_, name_change->getForwardDomain()); + + // Verify that the reverse domain can be retrieved. + ASSERT_TRUE(name_change->getReverseDomain()); + EXPECT_EQ(reverse_domain_, name_change->getReverseDomain()); + + // Neither of these have direct setters, but are tested under server + // selection. + EXPECT_FALSE(name_change->getDNSClient()); + EXPECT_FALSE(name_change->getCurrentServer()); + + // Verify that DNS update status can be set and retrieved. + EXPECT_NO_THROW(name_change->setDnsUpdateStatus(DNSClient::TIMEOUT)); + EXPECT_EQ(DNSClient::TIMEOUT, name_change->getDnsUpdateStatus()); + + // Verify that the forward change complete flag can be set and fetched. + EXPECT_NO_THROW(name_change->setForwardChangeCompleted(true)); + EXPECT_TRUE(name_change->getForwardChangeCompleted()); + + // Verify that the reverse change complete flag can be set and fetched. + EXPECT_NO_THROW(name_change->setReverseChangeCompleted(true)); + EXPECT_TRUE(name_change->getReverseChangeCompleted()); +} + +/// @brief Tests DNS update request accessor methods. +TEST_F(NameChangeTransactionTest, dnsUpdateRequestAccessors) { + // Create a transaction. + NameChangeStubPtr name_change; + ASSERT_NO_THROW(name_change = makeCannedTransaction()); + + // Post transaction construction, there should not be an update request. + EXPECT_FALSE(name_change->getDnsUpdateRequest()); + + // Create a request. + D2UpdateMessagePtr req; + ASSERT_NO_THROW(req.reset(new D2UpdateMessage(D2UpdateMessage::OUTBOUND))); + + // Use the setter and then verify we can fetch the request. + ASSERT_NO_THROW(name_change->setDnsUpdateRequest(req)); + + // Post set, we should be able to fetch it. + ASSERT_TRUE(name_change->getDnsUpdateRequest()); + + // Should be able to clear it. + ASSERT_NO_THROW(name_change->clearDnsUpdateRequest()); + + // Should be empty again. + EXPECT_FALSE(name_change->getDnsUpdateRequest()); +} + +/// @brief Tests DNS update request accessor methods. +TEST_F(NameChangeTransactionTest, dnsUpdateResponseAccessors) { + // Create a transaction. + NameChangeStubPtr name_change; + ASSERT_NO_THROW(name_change = makeCannedTransaction()); + + // Post transaction construction, there should not be an update response. + EXPECT_FALSE(name_change->getDnsUpdateResponse()); + + // Create a response. + D2UpdateMessagePtr resp; + ASSERT_NO_THROW(resp.reset(new D2UpdateMessage(D2UpdateMessage::INBOUND))); + + // Use the setter and then verify we can fetch the response. + ASSERT_NO_THROW(name_change->setDnsUpdateResponse(resp)); + + // Post set, we should be able to fetch it. + EXPECT_TRUE(name_change->getDnsUpdateResponse()); + + // Should be able to clear it. + ASSERT_NO_THROW(name_change->clearDnsUpdateResponse()); + + // Should be empty again. + EXPECT_FALSE(name_change->getDnsUpdateResponse()); + +} + +/// @brief Tests responseString method. +TEST_F(NameChangeTransactionTest, responseString) { + // Create a transaction. + NameChangeStubPtr name_change; + ASSERT_NO_THROW(name_change = makeCannedTransaction()); + + // Make sure it is safe to call when status says success but there + // is no update response. + ASSERT_NO_THROW(name_change->setDnsUpdateStatus(DNSClient::SUCCESS)); + EXPECT_EQ("SUCCESS, rcode: update response is NULL", + name_change->responseString()); + + // Create a response. (We use an OUTBOUND message so we can set RCODE) + D2UpdateMessagePtr resp; + ASSERT_NO_THROW(resp.reset(new D2UpdateMessage(D2UpdateMessage::OUTBOUND))); + ASSERT_NO_THROW(name_change->setDnsUpdateResponse(resp)); + + // Make sure we decode Rcode when status is successful. + ASSERT_NO_THROW(resp->setRcode(dns::Rcode::NXDOMAIN())); + EXPECT_EQ("SUCCESS, rcode: NXDOMAIN", name_change->responseString()); + + // Test all of the non-success values for status. + ASSERT_NO_THROW(name_change->setDnsUpdateStatus(DNSClient::TIMEOUT)); + EXPECT_EQ("TIMEOUT", name_change->responseString()); + + ASSERT_NO_THROW(name_change->setDnsUpdateStatus(DNSClient::IO_STOPPED)); + EXPECT_EQ("IO_STOPPED", name_change->responseString()); + + ASSERT_NO_THROW(name_change->setDnsUpdateStatus(DNSClient:: + INVALID_RESPONSE)); + EXPECT_EQ("INVALID_RESPONSE", name_change->responseString()); + + ASSERT_NO_THROW(name_change->setDnsUpdateStatus(DNSClient::OTHER)); + EXPECT_EQ("OTHER", name_change->responseString()); +} + +/// @brief Tests transactionOutcomeString method. +TEST_F(NameChangeTransactionTest, transactionOutcomeString) { + // Create a transaction. + NameChangeStubPtr name_change; + dhcp_ddns::NameChangeRequestPtr ncr; + ASSERT_NO_THROW(name_change = makeCannedTransaction()); + ncr = name_change->getNcr(); + + // Check case of failed transaction in both directions + std::string exp_str("Status: Failed, Event: UNDEFINED, Forward change:" + " failed, Reverse change: failed, request: "); + exp_str += ncr->toText(); + + std::string tstring = name_change->transactionOutcomeString(); + std::cout << "tstring is: [" << tstring << "]" << std::endl; + + EXPECT_EQ(exp_str, name_change->transactionOutcomeString()); + + // Check case of success all around + name_change->setNcrStatus(dhcp_ddns::ST_COMPLETED); + name_change->setForwardChangeCompleted(true); + name_change->setReverseChangeCompleted(true); + + exp_str = "Status: Completed, Event: UNDEFINED, Forward change: completed," + " Reverse change: completed, request: " + ncr->toText(); + EXPECT_EQ(exp_str, name_change->transactionOutcomeString()); + + // Check case of success, with no forward change + name_change->setNcrStatus(dhcp_ddns::ST_COMPLETED); + ncr->setForwardChange(false); + exp_str = "Status: Completed, Event: UNDEFINED, " + " Reverse change: completed, request: " + ncr->toText(); + EXPECT_EQ(exp_str, name_change->transactionOutcomeString()); + + // Check case of success, with no reverse change + name_change->setNcrStatus(dhcp_ddns::ST_COMPLETED); + ncr->setForwardChange(true); + ncr->setReverseChange(false); + exp_str = "Status: Completed, Event: UNDEFINED, " + " Forward change: completed, request: " + ncr->toText(); + EXPECT_EQ(exp_str, name_change->transactionOutcomeString()); +} + +/// @brief Tests event and state dictionary construction and verification. +TEST_F(NameChangeTransactionTest, dictionaryCheck) { + NameChangeStubPtr name_change; + ASSERT_NO_THROW(name_change = makeCannedTransaction()); + + // Verify that the event and state dictionary validation fails prior + // dictionary construction. + ASSERT_THROW(name_change->verifyEvents(), StateModelError); + ASSERT_THROW(name_change->verifyStates(), StateModelError); + + // Construct both dictionaries. + ASSERT_NO_THROW(name_change->defineEvents()); + ASSERT_NO_THROW(name_change->defineStates()); + + // Verify both event and state dictionaries now pass validation. + ASSERT_NO_THROW(name_change->verifyEvents()); + ASSERT_NO_THROW(name_change->verifyStates()); +} + +/// @brief Tests server selection methods. +/// Each transaction has a list of one or more servers for each DNS direction +/// it is required to update. The transaction must be able to start at the +/// beginning of a server list and cycle through them one at time, as needed, +/// when a DNS exchange fails due to an IO error. This test verifies the +/// ability to iteratively select a server from the list as the current server. +TEST_F(NameChangeTransactionTest, serverSelectionTest) { + NameChangeStubPtr name_change; + ASSERT_NO_THROW(name_change = makeCannedTransaction()); + + // Verify that the forward domain and its list of servers can be retrieved. + DdnsDomainPtr& domain = name_change->getForwardDomain(); + ASSERT_TRUE(domain); + DnsServerInfoStoragePtr servers = domain->getServers(); + ASSERT_TRUE(servers); + + // Get the number of entries in the server list. + int num_servers = servers->size(); + ASSERT_TRUE(num_servers > 0); + + // Verify that we can initialize server selection. This "resets" the + // selection process to start over using the list of servers in the + // given domain. + ASSERT_NO_THROW(name_change->initServerSelection(domain)); + + // The server selection process determines the current server, + // instantiates a new DNSClient, and a DNS response message buffer. + // We need to save the values before each selection, so we can verify + // they are correct after each selection. + DnsServerInfoPtr prev_server = name_change->getCurrentServer(); + DNSClientPtr prev_client = name_change->getDNSClient(); + + // Verify response pointer is empty. + EXPECT_FALSE(name_change->getDnsUpdateResponse()); + + // Create dummy response so we can verify it is cleared at each + // new server select. + D2UpdateMessagePtr dummyResp; + dummyResp.reset(new D2UpdateMessage(D2UpdateMessage::INBOUND)); + ASSERT_NO_THROW(name_change->setDnsUpdateResponse(dummyResp)); + ASSERT_TRUE(name_change->getDnsUpdateResponse()); + + // Iteratively select through the list of servers. + int passes = 0; + while (name_change->selectNextServer()) { + // Get the new values after the selection has been made. + DnsServerInfoPtr server = name_change->getCurrentServer(); + DNSClientPtr client = name_change->getDNSClient(); + D2UpdateMessagePtr response = name_change->getDnsUpdateResponse(); + + // Verify that the new values are not empty. + EXPECT_TRUE(server); + EXPECT_TRUE(client); + + // Verify response pointer is now empty. + EXPECT_FALSE(name_change->getDnsUpdateResponse()); + + // Verify that the new values are indeed new. + EXPECT_NE(server, prev_server); + EXPECT_NE(client, prev_client); + + // Remember the selected values for the next pass. + prev_server = server; + prev_client = client; + + // Create new dummy response. + dummyResp.reset(new D2UpdateMessage(D2UpdateMessage::INBOUND)); + ASSERT_NO_THROW(name_change->setDnsUpdateResponse(dummyResp)); + ASSERT_TRUE(name_change->getDnsUpdateResponse()); + + ++passes; + } + + // Verify that the number of passes made equal the number of servers. + EXPECT_EQ (passes, num_servers); + + // Repeat the same test using the reverse domain. + // Verify that the reverse domain and its list of servers can be retrieved. + domain = name_change->getReverseDomain(); + ASSERT_TRUE(domain); + servers = domain->getServers(); + ASSERT_TRUE(servers); + + // Get the number of entries in the server list. + num_servers = servers->size(); + ASSERT_TRUE(num_servers > 0); + + // Verify that we can initialize server selection. This "resets" the + // selection process to start over using the list of servers in the + // given domain. + ASSERT_NO_THROW(name_change->initServerSelection(domain)); + + // The server selection process determines the current server, + // instantiates a new DNSClient, and resets the DNS response message buffer. + // We need to save the values before each selection, so we can verify + // they are correct after each selection. + prev_server = name_change->getCurrentServer(); + prev_client = name_change->getDNSClient(); + + // Iteratively select through the list of servers. + passes = 0; + while (name_change->selectNextServer()) { + // Get the new values after the selection has been made. + DnsServerInfoPtr server = name_change->getCurrentServer(); + DNSClientPtr client = name_change->getDNSClient(); + D2UpdateMessagePtr response = name_change->getDnsUpdateResponse(); + + // Verify that the new values are not empty. + EXPECT_TRUE(server); + EXPECT_TRUE(client); + + // Verify response pointer is now empty. + EXPECT_FALSE(name_change->getDnsUpdateResponse()); + + // Verify that the new values are indeed new. + EXPECT_NE(server, prev_server); + EXPECT_NE(client, prev_client); + + // Remember the selected values for the next pass. + prev_server = server; + prev_client = client; + + // Create new dummy response. + dummyResp.reset(new D2UpdateMessage(D2UpdateMessage::INBOUND)); + ASSERT_NO_THROW(name_change->setDnsUpdateResponse(dummyResp)); + ASSERT_TRUE(name_change->getDnsUpdateResponse()); + + ++passes; + } + + // Verify that the number of passes made equal the number of servers. + EXPECT_EQ (passes, num_servers); +} + +/// @brief Tests that the transaction will be "failed" upon model errors. +TEST_F(NameChangeTransactionTest, modelFailure) { + NameChangeStubPtr name_change; + ASSERT_NO_THROW(name_change = makeCannedTransaction()); + + // Now call runModel() with an undefined event which should not throw, + // but should result in a failed model and failed transaction. + EXPECT_NO_THROW(name_change->runModel(9999)); + + // Verify that the model reports are done but failed. + EXPECT_TRUE(name_change->isModelDone()); + EXPECT_TRUE(name_change->didModelFail()); + + // Verify that the transaction has failed. + EXPECT_EQ(dhcp_ddns::ST_FAILED, name_change->getNcrStatus()); +} + +/// @brief Tests the ability to use startTransaction to initiate the state +/// model execution, and DNSClient callback, operator(), to resume the +/// model with a update successful outcome. +TEST_F(NameChangeTransactionTest, successfulUpdateTest) { + NameChangeStubPtr name_change; + ASSERT_NO_THROW(name_change = makeCannedTransaction()); + ASSERT_TRUE(name_change->selectFwdServer()); + + EXPECT_TRUE(name_change->isModelNew()); + EXPECT_FALSE(name_change->getForwardChangeCompleted()); + + // Launch the transaction by calling startTransaction. The state model + // should run up until the "IO" operation is initiated in DOING_UPDATE_ST. + ASSERT_NO_THROW(name_change->startTransaction()); + + // Verify that the model is running but waiting, and that forward change + // completion is still false. + EXPECT_TRUE(name_change->isModelRunning()); + EXPECT_TRUE(name_change->isModelWaiting()); + EXPECT_FALSE(name_change->getForwardChangeCompleted()); + + // Simulate completion of DNSClient exchange by invoking the callback, as + // DNSClient would. This should cause the state model to progress through + // completion. + EXPECT_NO_THROW((*name_change)(DNSClient::SUCCESS)); + + // The model should have worked through to completion. + // Verify that the model is done and not failed. + EXPECT_TRUE(name_change->isModelDone()); + EXPECT_FALSE(name_change->didModelFail()); + + // Verify that NCR status is completed, and that the forward change + // was completed. + EXPECT_EQ(dhcp_ddns::ST_COMPLETED, name_change->getNcrStatus()); + EXPECT_TRUE(name_change->getForwardChangeCompleted()); +} + +/// @brief Tests the ability to use startTransaction to initiate the state +/// model execution, and DNSClient callback, operator(), to resume the +/// model with a update failure outcome. +TEST_F(NameChangeTransactionTest, failedUpdateTest) { + NameChangeStubPtr name_change; + ASSERT_NO_THROW(name_change = makeCannedTransaction()); + ASSERT_TRUE(name_change->selectFwdServer()); + + // Launch the transaction by calling startTransaction. The state model + // should run up until the "IO" operation is initiated in DOING_UPDATE_ST. + ASSERT_NO_THROW(name_change->startTransaction()); + + // Verify that the model is running but waiting, and that the forward + // change has not been completed. + EXPECT_TRUE(name_change->isModelRunning()); + EXPECT_TRUE(name_change->isModelWaiting()); + EXPECT_FALSE(name_change->getForwardChangeCompleted()); + + // Simulate completion of DNSClient exchange by invoking the callback, as + // DNSClient would. This should cause the state model to progress through + // to completion. + EXPECT_NO_THROW((*name_change)(DNSClient::TIMEOUT)); + + // The model should have worked through to completion. + // Verify that the model is done and not failed. + EXPECT_TRUE(name_change->isModelDone()); + EXPECT_FALSE(name_change->didModelFail()); + + // Verify that the NCR status is failed and that the forward change + // was not completed. + EXPECT_EQ(dhcp_ddns::ST_FAILED, name_change->getNcrStatus()); + EXPECT_FALSE(name_change->getForwardChangeCompleted()); +} + +/// @brief Tests update attempt accessors. +TEST_F(NameChangeTransactionTest, updateAttempts) { + NameChangeStubPtr name_change; + ASSERT_NO_THROW(name_change = makeCannedTransaction()); + + // Post transaction construction, update attempts should be 0. + EXPECT_EQ(0, name_change->getUpdateAttempts()); + + // Set it to a known value. + name_change->setUpdateAttempts(5); + + // Verify that the value is as expected. + EXPECT_EQ(5, name_change->getUpdateAttempts()); + + // Clear it. + name_change->clearUpdateAttempts(); + + // Verify that it was cleared as expected. + EXPECT_EQ(0, name_change->getUpdateAttempts()); +} + +/// @brief Tests retryTransition method +/// +/// Verifies that while the maximum number of update attempts has not +/// been exceeded, the method will leave the state unchanged but post a +/// SERVER_SELECTED_EVT. Once the maximum is exceeded, the method should +/// transition to the state given with a next event of SERVER_IO_ERROR_EVT. +TEST_F(NameChangeTransactionTest, retryTransition) { + // Create the transaction. + NameChangeStubPtr name_change; + ASSERT_NO_THROW(name_change = makeCannedTransaction()); + + // Define dictionaries. + ASSERT_NO_THROW(name_change->initDictionaries()); + + // Transition to a known spot. + ASSERT_NO_THROW(name_change->transition( + NameChangeStub::DOING_UPDATE_ST, + NameChangeStub::SEND_UPDATE_EVT)); + + // Verify we are at the known spot. + ASSERT_EQ(NameChangeStub::DOING_UPDATE_ST, + name_change->getCurrState()); + ASSERT_EQ(NameChangeStub::SEND_UPDATE_EVT, + name_change->getNextEvent()); + + // Verify that we have not exceeded maximum number of attempts. + ASSERT_LT(name_change->getUpdateAttempts(), + NameChangeTransaction::MAX_UPDATE_TRIES_PER_SERVER); + + // Call retryTransition. + ASSERT_NO_THROW(name_change->retryTransition( + NameChangeTransaction::PROCESS_TRANS_FAILED_ST)); + + // Since the number of update attempts is less than the maximum allowed + // we should remain in our current state but with next event of + // SERVER_SELECTED_EVT posted. + ASSERT_EQ(NameChangeStub::DOING_UPDATE_ST, + name_change->getCurrState()); + ASSERT_EQ(NameChangeTransaction::SERVER_SELECTED_EVT, + name_change->getNextEvent()); + + // Now set the number of attempts to the maximum. + name_change->setUpdateAttempts(NameChangeTransaction:: + MAX_UPDATE_TRIES_PER_SERVER); + // Call retryTransition. + ASSERT_NO_THROW(name_change->retryTransition( + NameChangeTransaction::PROCESS_TRANS_FAILED_ST)); + + // Since we have exceeded maximum attempts, we should transition to + // PROCESS_UPDATE_FAILED_ST with a next event of SERVER_IO_ERROR_EVT. + ASSERT_EQ(NameChangeTransaction::PROCESS_TRANS_FAILED_ST, + name_change->getCurrState()); + ASSERT_EQ(NameChangeTransaction::SERVER_IO_ERROR_EVT, + name_change->getNextEvent()); +} + +/// @brief Tests sendUpdate method when underlying doUpdate throws. +/// +/// DNSClient::doUpdate can throw for a variety of reasons. This tests +/// sendUpdate handling of such a throw by passing doUpdate a request +/// that will not render. +TEST_F(NameChangeTransactionTest, sendUpdateDoUpdateFailure) { + NameChangeStubPtr name_change; + ASSERT_NO_THROW(name_change = makeCannedTransaction()); + ASSERT_NO_THROW(name_change->initDictionaries()); + ASSERT_TRUE(name_change->selectFwdServer()); + + // Set the transaction's request to an empty DNS update. + D2UpdateMessagePtr req; + ASSERT_NO_THROW(req.reset(new D2UpdateMessage(D2UpdateMessage::OUTBOUND))); + ASSERT_NO_THROW(name_change->setDnsUpdateRequest(req)); + + // Verify that sendUpdate does not throw, but it should fail because + // the request won't render. + ASSERT_NO_THROW(name_change->sendUpdate()); + + // Verify that we transition to failed state and event. + ASSERT_EQ(NameChangeTransaction::PROCESS_TRANS_FAILED_ST, + name_change->getCurrState()); + ASSERT_EQ(NameChangeTransaction::UPDATE_FAILED_EVT, + name_change->getNextEvent()); +} + +/// @brief Tests sendUpdate method when underlying doUpdate times out. +TEST_F(NameChangeTransactionTest, sendUpdateTimeout) { + NameChangeStubPtr name_change; + ASSERT_NO_THROW(name_change = makeCannedTransaction()); + ASSERT_NO_THROW(name_change->initDictionaries()); + ASSERT_TRUE(name_change->selectFwdServer()); + + // Build a valid request, call sendUpdate and process the response. + // Note we have to wait for DNSClient timeout plus a bit more to allow + // DNSClient to timeout. + // The method, doOneExchange, can suffer fatal assertions which invalidate + // not only it but the invoking test as well. In other words, if the + // doOneExchange blows up the rest of test is pointless. I use + // ASSERT_NO_FATAL_FAILURE to abort the test immediately. + + D2ParamsPtr d2_params = cfg_mgr_->getD2Params(); + size_t timeout = d2_params->getDnsServerTimeout() + 100; + + ASSERT_NO_FATAL_FAILURE(doOneExchange(name_change, timeout)); + + // Verify that next event is IO_COMPLETED_EVT and DNS status is TIMEOUT. + ASSERT_EQ(NameChangeTransaction::IO_COMPLETED_EVT, + name_change->getNextEvent()); + ASSERT_EQ(DNSClient::TIMEOUT, name_change->getDnsUpdateStatus()); +} + +/// @brief Tests sendUpdate method when it receives a corrupt response from +/// the server. +TEST_F(NameChangeTransactionTest, sendUpdateCorruptResponse) { + NameChangeStubPtr name_change; + ASSERT_NO_THROW(name_change = makeCannedTransaction()); + ASSERT_NO_THROW(name_change->initDictionaries()); + ASSERT_TRUE(name_change->selectFwdServer()); + + // Create a server and start it listening. + FauxServer server(*io_service_, *(name_change->getCurrentServer())); + server.receive(FauxServer::CORRUPT_RESP); + + // Build a valid request, call sendUpdate and process the response. + ASSERT_NO_FATAL_FAILURE(doOneExchange(name_change)); + + // Verify that next event is IO_COMPLETED_EVT and DNS status is INVALID. + ASSERT_EQ(NameChangeTransaction::IO_COMPLETED_EVT, + name_change->getNextEvent()); + ASSERT_EQ(DNSClient::INVALID_RESPONSE, name_change->getDnsUpdateStatus()); +} + +/// @brief Tests sendUpdate method when the exchange succeeds. +TEST_F(NameChangeTransactionTest, sendUpdate) { + NameChangeStubPtr name_change; + ASSERT_NO_THROW(name_change = makeCannedTransaction()); + ASSERT_NO_THROW(name_change->initDictionaries()); + ASSERT_TRUE(name_change->selectFwdServer()); + + // Create a server and start it listening. + FauxServer server(*io_service_, *(name_change->getCurrentServer())); + server.receive (FauxServer::USE_RCODE, dns::Rcode::NOERROR()); + + // Build a valid request, call sendUpdate and process the response. + ASSERT_NO_FATAL_FAILURE(doOneExchange(name_change)); + + // Verify that next event is IO_COMPLETED_EVT and DNS status is SUCCESS. + ASSERT_EQ(NameChangeTransaction::IO_COMPLETED_EVT, + name_change->getNextEvent()); + ASSERT_EQ(DNSClient::SUCCESS, name_change->getDnsUpdateStatus()); + + // Verify that we have a response and it's Rcode is NOERROR, + // and the zone is as expected. + D2UpdateMessagePtr response = name_change->getDnsUpdateResponse(); + ASSERT_TRUE(response); + ASSERT_EQ(dns::Rcode::NOERROR().getCode(), response->getRcode().getCode()); + D2ZonePtr zone = response->getZone(); + EXPECT_TRUE(zone); + EXPECT_EQ("response.example.com.", zone->getName().toText()); +} + +/// @brief Tests that an unsigned response to a signed request is an error +TEST_F(NameChangeTransactionTest, tsigUnsignedResponse) { + NameChangeStubPtr name_change; + ASSERT_NO_THROW(name_change = makeCannedTransaction("key_one")); + ASSERT_NO_THROW(name_change->initDictionaries()); + ASSERT_TRUE(name_change->selectFwdServer()); + + // Create a server and start it listening. + FauxServer server(*io_service_, *(name_change->getCurrentServer())); + server.receive (FauxServer::USE_RCODE, dns::Rcode::NOERROR()); + + // Do the update. + ASSERT_NO_FATAL_FAILURE(doOneExchange(name_change)); + + // Verify that next event is IO_COMPLETED_EVT and DNS status is + // INVALID_RESPONSE. + ASSERT_EQ(NameChangeTransaction::IO_COMPLETED_EVT, + name_change->getNextEvent()); + + ASSERT_EQ(DNSClient::INVALID_RESPONSE, name_change->getDnsUpdateStatus()); + + // When TSIG errors occur, only the message header (including Rcode) is + // unpacked. In this case, it should be NOERROR but have no other + // information. + D2UpdateMessagePtr response = name_change->getDnsUpdateResponse(); + ASSERT_TRUE(response); + ASSERT_EQ(dns::Rcode::NOERROR().getCode(), response->getRcode().getCode()); + EXPECT_FALSE(response->getZone()); +} + +/// @brief Tests that a response signed with the wrong key is an error +TEST_F(NameChangeTransactionTest, tsigInvalidResponse) { + NameChangeStubPtr name_change; + ASSERT_NO_THROW(name_change = makeCannedTransaction("key_one")); + ASSERT_NO_THROW(name_change->initDictionaries()); + ASSERT_TRUE(name_change->selectFwdServer()); + + // Create a server, tell it to sign responses with a "random" key, + // then start it listening. + FauxServer server(*io_service_, *(name_change->getCurrentServer())); + server.receive (FauxServer::INVALID_TSIG, dns::Rcode::NOERROR()); + + // Do the update. + ASSERT_NO_FATAL_FAILURE(doOneExchange(name_change)); + + // Verify that next event is IO_COMPLETED_EVT and DNS status is + // INVALID_RESPONSE. + ASSERT_EQ(NameChangeTransaction::IO_COMPLETED_EVT, + name_change->getNextEvent()); + + ASSERT_EQ(DNSClient::INVALID_RESPONSE, name_change->getDnsUpdateStatus()); + + // When TSIG errors occur, only the message header (including Rcode) is + // unpacked. In this case, it should be NOERROR but have no other + // information. + D2UpdateMessagePtr response = name_change->getDnsUpdateResponse(); + ASSERT_TRUE(response); + ASSERT_EQ(dns::Rcode::NOERROR().getCode(), response->getRcode().getCode()); + EXPECT_FALSE(response->getZone()); +} + +/// @brief Tests that a signed response to an unsigned request is ok. +/// Currently our policy is to accept a signed response to an unsigned request +/// even though the spec says a server MUST not do that. +TEST_F(NameChangeTransactionTest, tsigUnexpectedSignedResponse) { + NameChangeStubPtr name_change; + ASSERT_NO_THROW(name_change = makeCannedTransaction()); + ASSERT_NO_THROW(name_change->initDictionaries()); + ASSERT_TRUE(name_change->selectFwdServer()); + + // Create a server, tell it to sign responses with a "random" key, + // then start it listening. + FauxServer server(*io_service_, *(name_change->getCurrentServer())); + server.receive (FauxServer::INVALID_TSIG, dns::Rcode::NOERROR()); + + // Perform an update without TSIG. + ASSERT_NO_FATAL_FAILURE(doOneExchange(name_change)); + + // Verify that next event is IO_COMPLETED_EVT and DNS status is SUCCESS. + ASSERT_EQ(NameChangeTransaction::IO_COMPLETED_EVT, + name_change->getNextEvent()); + + ASSERT_EQ(DNSClient::SUCCESS, name_change->getDnsUpdateStatus()); + + D2UpdateMessagePtr response = name_change->getDnsUpdateResponse(); + ASSERT_TRUE(response); + ASSERT_EQ(dns::Rcode::NOERROR().getCode(), response->getRcode().getCode()); + D2ZonePtr zone = response->getZone(); + EXPECT_TRUE(zone); + EXPECT_EQ("response.example.com.", zone->getName().toText()); +} + +/// @brief Tests that a TSIG update succeeds when client and server both use +/// the right key. Runs the test for all supported algorithms. +TEST_F(NameChangeTransactionTest, tsigAllValid) { + std::vector<std::string>algorithms; + algorithms.push_back(TSIGKeyInfo::HMAC_MD5_STR); + algorithms.push_back(TSIGKeyInfo::HMAC_SHA1_STR); + algorithms.push_back(TSIGKeyInfo::HMAC_SHA224_STR); + algorithms.push_back(TSIGKeyInfo::HMAC_SHA256_STR); + algorithms.push_back(TSIGKeyInfo::HMAC_SHA384_STR); + algorithms.push_back(TSIGKeyInfo::HMAC_SHA512_STR); + + for (int i = 0; i < algorithms.size(); ++i) { + SCOPED_TRACE (algorithms[i]); + TSIGKeyInfoPtr key; + ASSERT_NO_THROW(key.reset(new TSIGKeyInfo("test_key", + algorithms[i], + "GWG/Xfbju4O2iXGqkSu4PQ=="))); + NameChangeStubPtr name_change; + ASSERT_NO_THROW(name_change = makeCannedTransaction(key)); + ASSERT_NO_THROW(name_change->initDictionaries()); + ASSERT_TRUE(name_change->selectFwdServer()); + + // Create a server, set its TSIG key, and then start it listening. + FauxServer server(*io_service_, *(name_change->getCurrentServer())); + // Since we create a new server instance each time we need to tell + // it not reschedule receives automatically. + server.perpetual_receive_ = false; + server.setTSIGKey(key->getTSIGKey()); + server.receive (FauxServer::USE_RCODE, dns::Rcode::NOERROR()); + + // Do the update. + ASSERT_NO_FATAL_FAILURE(doOneExchange(name_change)); + + // Verify that next event is IO_COMPLETED_EVT and DNS status is SUCCESS. + ASSERT_EQ(NameChangeTransaction::IO_COMPLETED_EVT, + name_change->getNextEvent()); + + ASSERT_EQ(DNSClient::SUCCESS, name_change->getDnsUpdateStatus()); + + D2UpdateMessagePtr response = name_change->getDnsUpdateResponse(); + ASSERT_TRUE(response); + ASSERT_EQ(dns::Rcode::NOERROR().getCode(), + response->getRcode().getCode()); + D2ZonePtr zone = response->getZone(); + EXPECT_TRUE(zone); + EXPECT_EQ("response.example.com.", zone->getName().toText()); + } +} + + +/// @brief Tests the prepNewRequest method +TEST_F(NameChangeTransactionTest, prepNewRequest) { + NameChangeStubPtr name_change; + ASSERT_NO_THROW(name_change = makeCannedTransaction()); + D2UpdateMessagePtr request; + + // prepNewRequest should fail on empty domain. + ASSERT_THROW(request = name_change->prepNewRequest(DdnsDomainPtr()), + NameChangeTransactionError); + + // Verify that prepNewRequest fails on invalid zone name. + // @todo This test becomes obsolete if/when DdnsDomain enforces valid + // names as is done in dns::Name. + DdnsDomainPtr bsDomain = makeDomain(".badname",""); + ASSERT_THROW(request = name_change->prepNewRequest(bsDomain), + NameChangeTransactionError); + + // Verify that prepNewRequest properly constructs a message given + // valid input. + ASSERT_NO_THROW(request = name_change->prepNewRequest(forward_domain_)); + checkZone(request, forward_domain_->getName()); + + // The query id is random so 0 is not impossible + for (unsigned i = 0; i < 10; ++i) { + if (request->getId() == 0) { + request = name_change->prepNewRequest(forward_domain_); + } + } + + EXPECT_NE(0, request->getId()); +} + +/// @brief Tests the addLeaseAddressRData method +TEST_F(NameChangeTransactionTest, addLeaseAddressRData) { + NameChangeStubPtr name_change; + ASSERT_NO_THROW(name_change = makeCannedTransaction()); + dhcp_ddns::NameChangeRequestPtr ncr = name_change->getNcr(); + + // Verify we can add a lease RData to an valid RRset. + dns::RRsetPtr rrset(new dns::RRset(dns::Name("bs"), dns::RRClass::IN(), + name_change->getAddressRRType(), + dns::RRTTL(0))); + ASSERT_NO_THROW(name_change->addLeaseAddressRdata(rrset)); + + // Verify the Rdata was added and the value is correct. + ASSERT_EQ(1, rrset->getRdataCount()); + dns::RdataIteratorPtr rdata_it = rrset->getRdataIterator(); + ASSERT_TRUE(rdata_it); + EXPECT_EQ(ncr->getIpAddress(), rdata_it->getCurrent().toText()); + +} + +/// @brief Tests the addDhcidRData method +TEST_F(NameChangeTransactionTest, addDhcidRdata) { + NameChangeStubPtr name_change; + ASSERT_NO_THROW(name_change = makeCannedTransaction()); + dhcp_ddns::NameChangeRequestPtr ncr = name_change->getNcr(); + + // Verify we can add a lease RData to an valid RRset. + dns::RRsetPtr rrset(new dns::RRset(dns::Name("bs"), dns::RRClass::IN(), + dns::RRType::DHCID(), dns::RRTTL(0))); + ASSERT_NO_THROW(name_change->addDhcidRdata(rrset)); + + // Verify the Rdata was added and the value is correct. + ASSERT_EQ(1, rrset->getRdataCount()); + dns::RdataIteratorPtr rdata_it = rrset->getRdataIterator(); + ASSERT_TRUE(rdata_it); + + const std::vector<uint8_t>& ncr_dhcid = ncr->getDhcid().getBytes(); + util::InputBuffer buffer(ncr_dhcid.data(), ncr_dhcid.size()); + dns::rdata::in::DHCID rdata_ref(buffer, ncr_dhcid.size()); + EXPECT_EQ(0, rdata_ref.compare(rdata_it->getCurrent())); +} + +/// @brief Tests the addPtrData method +TEST_F(NameChangeTransactionTest, addPtrRdata) { + NameChangeStubPtr name_change; + ASSERT_NO_THROW(name_change = makeCannedTransaction()); + dhcp_ddns::NameChangeRequestPtr ncr = name_change->getNcr(); + + // Verify we can add a PTR RData to an valid RRset. + dns::RRsetPtr rrset (new dns::RRset(dns::Name("bs"), dns::RRClass::IN(), + dns::RRType::PTR(), dns::RRTTL(0))); + ASSERT_NO_THROW(name_change->addPtrRdata(rrset)); + + // Verify the Rdata was added and the value is correct. + ASSERT_EQ(1, rrset->getRdataCount()); + dns::RdataIteratorPtr rdata_it = rrset->getRdataIterator(); + ASSERT_TRUE(rdata_it); + + EXPECT_EQ(ncr->getFqdn(), rdata_it->getCurrent().toText()); +} + +}; // anonymous namespace |