// Copyright (C) 2010  Internet Systems Consortium, Inc. ("ISC")
// Copyright (C) 2011  CZ NIC
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.

#include <exceptions/exceptions.h>

#include <dns/name.h>
#include <dns/rdata.h>
#include <dns/rdataclass.h>
#include <dns/rrclass.h>
#include <dns/rrsetlist.h>
#include <dns/rrttl.h>
#include <dns/masterload.h>

#include <datasrc/memory_datasrc.h>

#include <gtest/gtest.h>

using namespace isc::dns;
using namespace isc::dns::rdata;
using namespace isc::datasrc;

namespace {
// Commonly used result codes (Who should write the prefix all the time)
using result::SUCCESS;
using result::EXIST;

class MemoryDataSrcTest : public ::testing::Test {
protected:
    MemoryDataSrcTest() : rrclass(RRClass::IN())
    {}
    RRClass rrclass;
    MemoryDataSrc memory_datasrc;
};

TEST_F(MemoryDataSrcTest, add_find_Zone) {
    // test add zone
    // Bogus zone (NULL)
    EXPECT_THROW(memory_datasrc.addZone(ZonePtr()), isc::InvalidParameter);

    // add zones with different names one by one
    EXPECT_EQ(result::SUCCESS, memory_datasrc.addZone(
                  ZonePtr(new MemoryZone(RRClass::IN(), Name("a")))));
    EXPECT_EQ(result::SUCCESS, memory_datasrc.addZone(
                  ZonePtr(new MemoryZone(RRClass::CH(), Name("b")))));
    EXPECT_EQ(result::SUCCESS, memory_datasrc.addZone(
                  ZonePtr(new MemoryZone(RRClass::IN(), Name("c")))));
    // add zones with the same name suffix
    EXPECT_EQ(result::SUCCESS, memory_datasrc.addZone(
                  ZonePtr(new MemoryZone(RRClass::CH(),
                                         Name("x.d.e.f")))));
    EXPECT_EQ(result::SUCCESS, memory_datasrc.addZone(
                  ZonePtr(new MemoryZone(RRClass::CH(),
                                         Name("o.w.y.d.e.f")))));
    EXPECT_EQ(result::SUCCESS, memory_datasrc.addZone(
                  ZonePtr(new MemoryZone(RRClass::CH(),
                                         Name("p.w.y.d.e.f")))));
    EXPECT_EQ(result::SUCCESS, memory_datasrc.addZone(
                  ZonePtr(new MemoryZone(RRClass::IN(),
                                         Name("q.w.y.d.e.f")))));
    // add super zone and its subzone
    EXPECT_EQ(result::SUCCESS, memory_datasrc.addZone(
                  ZonePtr(new MemoryZone(RRClass::CH(), Name("g.h")))));
    EXPECT_EQ(result::SUCCESS, memory_datasrc.addZone(
                  ZonePtr(new MemoryZone(RRClass::IN(), Name("i.g.h")))));
    EXPECT_EQ(result::SUCCESS, memory_datasrc.addZone(
                  ZonePtr(new MemoryZone(RRClass::IN(),
                                         Name("z.d.e.f")))));
    EXPECT_EQ(result::SUCCESS, memory_datasrc.addZone(
                  ZonePtr(new MemoryZone(RRClass::IN(),
                                         Name("j.z.d.e.f")))));

    // different zone class isn't allowed.
    EXPECT_EQ(result::EXIST, memory_datasrc.addZone(
                  ZonePtr(new MemoryZone(RRClass::CH(),
                                         Name("q.w.y.d.e.f")))));

    // names are compared in a case insensitive manner.
    EXPECT_EQ(result::EXIST, memory_datasrc.addZone(
                  ZonePtr(new MemoryZone(RRClass::IN(),
                                         Name("Q.W.Y.d.E.f")))));

    // test find zone
    EXPECT_EQ(result::SUCCESS, memory_datasrc.findZone(Name("a")).code);
    EXPECT_EQ(Name("a"),
              memory_datasrc.findZone(Name("a")).zone->getOrigin());

    EXPECT_EQ(result::SUCCESS,
              memory_datasrc.findZone(Name("j.z.d.e.f")).code);
    EXPECT_EQ(Name("j.z.d.e.f"),
              memory_datasrc.findZone(Name("j.z.d.e.f")).zone->getOrigin());

    // NOTFOUND
    EXPECT_EQ(result::NOTFOUND, memory_datasrc.findZone(Name("d.e.f")).code);
    EXPECT_EQ(ConstZonePtr(), memory_datasrc.findZone(Name("d.e.f")).zone);

    EXPECT_EQ(result::NOTFOUND,
              memory_datasrc.findZone(Name("w.y.d.e.f")).code);
    EXPECT_EQ(ConstZonePtr(),
              memory_datasrc.findZone(Name("w.y.d.e.f")).zone);

    // there's no exact match.  the result should be the longest match,
    // and the code should be PARTIALMATCH.
    EXPECT_EQ(result::PARTIALMATCH,
              memory_datasrc.findZone(Name("j.g.h")).code);
    EXPECT_EQ(Name("g.h"),
              memory_datasrc.findZone(Name("g.h")).zone->getOrigin());

    EXPECT_EQ(result::PARTIALMATCH,
              memory_datasrc.findZone(Name("z.i.g.h")).code);
    EXPECT_EQ(Name("i.g.h"),
              memory_datasrc.findZone(Name("z.i.g.h")).zone->getOrigin());
}

TEST_F(MemoryDataSrcTest, getZoneCount) {
    EXPECT_EQ(0, memory_datasrc.getZoneCount());
    memory_datasrc.addZone(
                  ZonePtr(new MemoryZone(rrclass, Name("example.com"))));
    EXPECT_EQ(1, memory_datasrc.getZoneCount());

    // duplicate add.  counter shouldn't change
    memory_datasrc.addZone(
                  ZonePtr(new MemoryZone(rrclass, Name("example.com"))));
    EXPECT_EQ(1, memory_datasrc.getZoneCount());

    // add one more
    memory_datasrc.addZone(
                  ZonePtr(new MemoryZone(rrclass, Name("example.org"))));
    EXPECT_EQ(2, memory_datasrc.getZoneCount());
}

/// \brief Test fixture for the MemoryZone class
class MemoryZoneTest : public ::testing::Test {
public:
    MemoryZoneTest() :
        class_(RRClass::IN()),
        origin_("example.org"),
        ns_name_("ns.example.org"),
        cname_name_("cname.example.org"),
        dname_name_("dname.example.org"),
        child_ns_name_("child.example.org"),
        child_glue_name_("ns.child.example.org"),
        grandchild_ns_name_("grand.child.example.org"),
        grandchild_glue_name_("ns.grand.child.example.org"),
        child_dname_name_("dname.child.example.org"),
        zone_(class_, origin_),
        rr_out_(new RRset(Name("example.com"), class_, RRType::A(),
            RRTTL(300))),
        rr_ns_(new RRset(origin_, class_, RRType::NS(), RRTTL(300))),
        rr_ns_a_(new RRset(ns_name_, class_, RRType::A(), RRTTL(300))),
        rr_ns_aaaa_(new RRset(ns_name_, class_, RRType::AAAA(), RRTTL(300))),
        rr_a_(new RRset(origin_, class_, RRType::A(), RRTTL(300))),
        rr_cname_(new RRset(cname_name_, class_, RRType::CNAME(), RRTTL(300))),
        rr_cname_a_(new RRset(cname_name_, class_, RRType::A(), RRTTL(300))),
        rr_dname_(new RRset(dname_name_, class_, RRType::DNAME(), RRTTL(300))),
        rr_dname_a_(new RRset(dname_name_, class_, RRType::A(),
            RRTTL(300))),
        rr_dname_ns_(new RRset(dname_name_, class_, RRType::NS(), RRTTL(300))),
        rr_dname_apex_(new RRset(origin_, class_, RRType::DNAME(),
            RRTTL(300))),
        rr_child_ns_(new RRset(child_ns_name_, class_, RRType::NS(),
                               RRTTL(300))),
        rr_child_glue_(new RRset(child_glue_name_, class_, RRType::A(),
                              RRTTL(300))),
        rr_grandchild_ns_(new RRset(grandchild_ns_name_, class_, RRType::NS(),
                                    RRTTL(300))),
        rr_grandchild_glue_(new RRset(grandchild_glue_name_, class_,
                                      RRType::AAAA(), RRTTL(300))),
        rr_child_dname_(new RRset(child_dname_name_, class_, RRType::DNAME(),
            RRTTL(300)))
    {
    }
    // Some data to test with
    const RRClass class_;
    const Name origin_, ns_name_, cname_name_, dname_name_, child_ns_name_,
        child_glue_name_, grandchild_ns_name_, grandchild_glue_name_,
        child_dname_name_;
    // The zone to torture by tests
    MemoryZone zone_;

    /*
     * Some RRsets to put inside the zone.
     * They are empty, but the MemoryZone does not have a reason to look
     * inside anyway. We will check it finds them and does not change
     * the pointer.
     */
    ConstRRsetPtr
        // Out of zone RRset
        rr_out_,
        // NS of example.org
        rr_ns_,
        // A of ns.example.org
        rr_ns_a_,
        // AAAA of ns.example.org
        rr_ns_aaaa_,
        // A of example.org
        rr_a_;
    RRsetPtr rr_cname_;         // CNAME in example.org (RDATA will be added)
    ConstRRsetPtr rr_cname_a_; // for mixed CNAME + A case
    RRsetPtr rr_dname_;         // DNAME in example.org (RDATA will be added)
    ConstRRsetPtr rr_dname_a_; // for mixed DNAME + A case
    ConstRRsetPtr rr_dname_ns_; // for mixed DNAME + NS case
    ConstRRsetPtr rr_dname_apex_; // for mixed DNAME + NS case in the apex
    ConstRRsetPtr rr_child_ns_; // NS of a child domain (for delegation)
    ConstRRsetPtr rr_child_glue_; // glue RR of the child domain
    ConstRRsetPtr rr_grandchild_ns_; // NS below a zone cut (unusual)
    ConstRRsetPtr rr_grandchild_glue_; // glue RR below a deeper zone cut
    ConstRRsetPtr rr_child_dname_; // A DNAME under NS

    /**
     * \brief Test one find query to the zone.
     *
     * Asks a query to the zone and checks it does not throw and returns
     * expected results. It returns nothing, it just signals failures
     * to GTEST.
     *
     * \param name The name to ask for.
     * \param rrtype The RRType to ask of.
     * \param result The expected code of the result.
     * \param check_answer Should a check against equality of the answer be
     *     done?
     * \param answer The expected rrset, if any should be returned.
     * \param zone Check different MemoryZone object than zone_ (if NULL,
     *     uses zone_)
     */
    void findTest(const Name& name, const RRType& rrtype, Zone::Result result,
                  bool check_answer = true,
                  const ConstRRsetPtr& answer = ConstRRsetPtr(),
                  RRsetList* target = NULL,
                  MemoryZone* zone = NULL,
                  Zone::FindOptions options = Zone::FIND_DEFAULT)
    {
        if (!zone) {
            zone = &zone_;
        }
        // The whole block is inside, because we need to check the result and
        // we can't assign to FindResult
        EXPECT_NO_THROW({
                Zone::FindResult find_result(zone->find(name, rrtype, target,
                                                        options));
                // Check it returns correct answers
                EXPECT_EQ(result, find_result.code);
                if (check_answer) {
                    EXPECT_EQ(answer, find_result.rrset);
                }
            });
    }
};

/**
 * \brief Test MemoryZone::MemoryZone constructor.
 *
 * Takes the created zone and checks its properties they are the same
 * as passed parameters.
 */
TEST_F(MemoryZoneTest, constructor) {
    ASSERT_EQ(class_, zone_.getClass());
    ASSERT_EQ(origin_, zone_.getOrigin());
}
/**
 * \brief Test adding.
 *
 * We test that it throws at the correct moments and the correct exceptions.
 * And we test the return value.
 */
TEST_F(MemoryZoneTest, add) {
    // This one does not belong to this zone
    EXPECT_THROW(zone_.add(rr_out_), MemoryZone::OutOfZone);
    // Test null pointer
    EXPECT_THROW(zone_.add(ConstRRsetPtr()), MemoryZone::NullRRset);

    // Now put all the data we have there. It should throw nothing
    EXPECT_NO_THROW(EXPECT_EQ(SUCCESS, zone_.add(rr_ns_)));
    EXPECT_NO_THROW(EXPECT_EQ(SUCCESS, zone_.add(rr_ns_a_)));
    EXPECT_NO_THROW(EXPECT_EQ(SUCCESS, zone_.add(rr_ns_aaaa_)));
    EXPECT_NO_THROW(EXPECT_EQ(SUCCESS, zone_.add(rr_a_)));

    // Try putting there something twice, it should be rejected
    EXPECT_NO_THROW(EXPECT_EQ(EXIST, zone_.add(rr_ns_)));
    EXPECT_NO_THROW(EXPECT_EQ(EXIST, zone_.add(rr_ns_a_)));
}

TEST_F(MemoryZoneTest, addMultipleCNAMEs) {
    rr_cname_->addRdata(generic::CNAME("canonical1.example.org."));
    rr_cname_->addRdata(generic::CNAME("canonical2.example.org."));
    EXPECT_THROW(zone_.add(rr_cname_), MemoryZone::AddError);
}

TEST_F(MemoryZoneTest, addCNAMEThenOther) {
    rr_cname_->addRdata(generic::CNAME("canonical.example.org."));
    EXPECT_EQ(SUCCESS, zone_.add(rr_cname_));
    EXPECT_THROW(zone_.add(rr_cname_a_), MemoryZone::AddError);
}

TEST_F(MemoryZoneTest, addOtherThenCNAME) {
    rr_cname_->addRdata(generic::CNAME("canonical.example.org."));
    EXPECT_EQ(SUCCESS, zone_.add(rr_cname_a_));
    EXPECT_THROW(zone_.add(rr_cname_), MemoryZone::AddError);
}

TEST_F(MemoryZoneTest, findCNAME) {
    // install CNAME RR
    rr_cname_->addRdata(generic::CNAME("canonical.example.org."));
    EXPECT_EQ(SUCCESS, zone_.add(rr_cname_));

    // Find A RR of the same.  Should match the CNAME
    findTest(cname_name_, RRType::NS(), Zone::CNAME, true, rr_cname_);

    // Find the CNAME itself.  Should result in normal SUCCESS
    findTest(cname_name_, RRType::CNAME(), Zone::SUCCESS, true, rr_cname_);
}

TEST_F(MemoryZoneTest, findCNAMEUnderZoneCut) {
    // There's nothing special when we find a CNAME under a zone cut
    // (with FIND_GLUE_OK).  The behavior is different from BIND 9,
    // so we test this case explicitly.
    EXPECT_EQ(SUCCESS, zone_.add(rr_child_ns_));
    RRsetPtr rr_cname_under_cut_(new RRset(Name("cname.child.example.org"),
                                           class_, RRType::CNAME(),
                                           RRTTL(300)));
    EXPECT_EQ(SUCCESS, zone_.add(rr_cname_under_cut_));
    findTest(Name("cname.child.example.org"), RRType::AAAA(),
             Zone::CNAME, true, rr_cname_under_cut_, NULL, NULL,
             Zone::FIND_GLUE_OK);
}

// Two DNAMEs at single domain are disallowed by RFC 2672, section 3)
// Having a CNAME there is disallowed too, but it is tested by
// addOtherThenCNAME and addCNAMEThenOther.
TEST_F(MemoryZoneTest, addMultipleDNAMEs) {
    rr_dname_->addRdata(generic::DNAME("dname1.example.org."));
    rr_dname_->addRdata(generic::DNAME("dname2.example.org."));
    EXPECT_THROW(zone_.add(rr_dname_), MemoryZone::AddError);
}

/*
 * These two tests ensure that we can't have DNAME and NS at the same
 * node with the exception of the apex of zone (forbidden by RFC 2672)
 */
TEST_F(MemoryZoneTest, addDNAMEThenNS) {
    EXPECT_NO_THROW(EXPECT_EQ(SUCCESS, zone_.add(rr_dname_)));
    EXPECT_THROW(zone_.add(rr_dname_ns_), MemoryZone::AddError);
}

TEST_F(MemoryZoneTest, addNSThenDNAME) {
    EXPECT_NO_THROW(EXPECT_EQ(SUCCESS, zone_.add(rr_dname_ns_)));
    EXPECT_THROW(zone_.add(rr_dname_), MemoryZone::AddError);
}

// It is allowed to have NS and DNAME at apex
TEST_F(MemoryZoneTest, DNAMEAndNSAtApex) {
    EXPECT_NO_THROW(EXPECT_EQ(SUCCESS, zone_.add(rr_dname_apex_)));
    EXPECT_NO_THROW(EXPECT_EQ(SUCCESS, zone_.add(rr_ns_)));

    // The NS should be possible to be found, below should be DNAME, not
    // delegation
    findTest(origin_, RRType::NS(), Zone::SUCCESS, true, rr_ns_);
    findTest(child_ns_name_, RRType::A(), Zone::DNAME, true, rr_dname_apex_);
}

TEST_F(MemoryZoneTest, NSAndDNAMEAtApex) {
    EXPECT_NO_THROW(EXPECT_EQ(SUCCESS, zone_.add(rr_ns_)));
    EXPECT_NO_THROW(EXPECT_EQ(SUCCESS, zone_.add(rr_dname_apex_)));
}

// TODO: Test (and implement) adding data under DNAME. That is forbidden by
// 2672 as well.

// Search under a DNAME record. It should return the DNAME
TEST_F(MemoryZoneTest, findBelowDNAME) {
    rr_dname_->addRdata(generic::DNAME("target.example.org."));
    EXPECT_NO_THROW(EXPECT_EQ(SUCCESS, zone_.add(rr_dname_)));
    findTest(Name("below.dname.example.org"), RRType::A(), Zone::DNAME, true,
        rr_dname_);
}

// Search at the domain with DNAME. It should act as DNAME isn't there, DNAME
// influences only the data below (see RFC 2672, section 3)
TEST_F(MemoryZoneTest, findAtDNAME) {
    rr_dname_->addRdata(generic::DNAME("target.example.org."));
    EXPECT_NO_THROW(EXPECT_EQ(SUCCESS, zone_.add(rr_dname_)));
    EXPECT_NO_THROW(EXPECT_EQ(SUCCESS, zone_.add(rr_dname_a_)));

    findTest(dname_name_, RRType::A(), Zone::SUCCESS, true, rr_dname_a_);
    findTest(dname_name_, RRType::DNAME(), Zone::SUCCESS, true, rr_dname_);
    findTest(dname_name_, RRType::TXT(), Zone::NXRRSET, true);
}

// Try searching something that is both under NS and DNAME, without and with
// GLUE_OK mode (it should stop at the NS and DNAME respectively).
TEST_F(MemoryZoneTest, DNAMEUnderNS) {
    zone_.add(rr_child_ns_);
    zone_.add(rr_child_dname_);

    Name lowName("below.dname.child.example.org.");

    findTest(lowName, RRType::A(), Zone::DELEGATION, true, rr_child_ns_);
    findTest(lowName, RRType::A(), Zone::DNAME, true, rr_child_dname_, NULL,
        NULL, Zone::FIND_GLUE_OK);
}

// Test adding child zones and zone cut handling
TEST_F(MemoryZoneTest, delegationNS) {
    // add in-zone data
    EXPECT_NO_THROW(EXPECT_EQ(SUCCESS, zone_.add(rr_ns_)));

    // install a zone cut
    EXPECT_NO_THROW(EXPECT_EQ(SUCCESS, zone_.add(rr_child_ns_)));

    // below the zone cut
    findTest(Name("www.child.example.org"), RRType::A(), Zone::DELEGATION,
             true, rr_child_ns_);

    // at the zone cut
    findTest(Name("child.example.org"), RRType::A(), Zone::DELEGATION,
             true, rr_child_ns_);
    findTest(Name("child.example.org"), RRType::NS(), Zone::DELEGATION,
             true, rr_child_ns_);

    // finding NS for the apex (origin) node.  This must not be confused
    // with delegation due to the existence of an NS RR.
    findTest(origin_, RRType::NS(), Zone::SUCCESS, true, rr_ns_);

    // unusual case of "nested delegation": the highest cut should be used.
    EXPECT_NO_THROW(EXPECT_EQ(SUCCESS, zone_.add(rr_grandchild_ns_)));
    findTest(Name("www.grand.child.example.org"), RRType::A(),
             Zone::DELEGATION, true, rr_child_ns_); // note: !rr_grandchild_ns_
}

TEST_F(MemoryZoneTest, findAny) {
    EXPECT_NO_THROW(EXPECT_EQ(SUCCESS, zone_.add(rr_a_)));
    EXPECT_NO_THROW(EXPECT_EQ(SUCCESS, zone_.add(rr_ns_)));
    EXPECT_NO_THROW(EXPECT_EQ(SUCCESS, zone_.add(rr_child_glue_)));

    // origin
    RRsetList origin_rrsets;
    findTest(origin_, RRType::ANY(), Zone::SUCCESS, true,
             ConstRRsetPtr(), &origin_rrsets);
    EXPECT_EQ(2, origin_rrsets.size());
    EXPECT_EQ(rr_a_, origin_rrsets.findRRset(RRType::A(), RRClass::IN()));
    EXPECT_EQ(rr_ns_, origin_rrsets.findRRset(RRType::NS(), RRClass::IN()));

    // out zone name
    RRsetList out_rrsets;
    findTest(Name("example.com"), RRType::ANY(), Zone::NXDOMAIN, true,
             ConstRRsetPtr(), &out_rrsets);
    EXPECT_EQ(0, out_rrsets.size());

    RRsetList glue_child_rrsets;
    findTest(child_glue_name_, RRType::ANY(), Zone::SUCCESS, true,
                ConstRRsetPtr(), &glue_child_rrsets);
    EXPECT_EQ(rr_child_glue_, glue_child_rrsets.findRRset(RRType::A(),
                                                     RRClass::IN()));
    EXPECT_EQ(1, glue_child_rrsets.size());

    // TODO: test NXRRSET case after rbtree non-terminal logic has
    // been implemented

    // add zone cut
    EXPECT_NO_THROW(EXPECT_EQ(SUCCESS, zone_.add(rr_child_ns_)));

    // zone cut
    RRsetList child_rrsets;
    findTest(child_ns_name_, RRType::ANY(), Zone::DELEGATION, true,
             rr_child_ns_, &child_rrsets);
    EXPECT_EQ(0, child_rrsets.size());

    // glue for this zone cut
    RRsetList new_glue_child_rrsets;
    findTest(child_glue_name_, RRType::ANY(), Zone::DELEGATION, true,
                rr_child_ns_, &new_glue_child_rrsets);
    EXPECT_EQ(0, new_glue_child_rrsets.size());
}

TEST_F(MemoryZoneTest, glue) {
    // install zone data:
    // a zone cut
    EXPECT_NO_THROW(EXPECT_EQ(SUCCESS, zone_.add(rr_child_ns_)));
    // glue for this cut
    EXPECT_NO_THROW(EXPECT_EQ(SUCCESS, zone_.add(rr_child_glue_)));
    // a nested zone cut (unusual)
    EXPECT_NO_THROW(EXPECT_EQ(SUCCESS, zone_.add(rr_grandchild_ns_)));
    // glue under the deeper zone cut
    EXPECT_NO_THROW(EXPECT_EQ(SUCCESS, zone_.add(rr_grandchild_glue_)));

    // by default glue is hidden due to the zone cut
    findTest(child_glue_name_, RRType::A(), Zone::DELEGATION, true,
             rr_child_ns_);


    // If we do it in the "glue OK" mode, we should find the exact match.
    findTest(child_glue_name_, RRType::A(), Zone::SUCCESS, true,
             rr_child_glue_, NULL, NULL, Zone::FIND_GLUE_OK);

    // glue OK + NXRRSET case
    findTest(child_glue_name_, RRType::AAAA(), Zone::NXRRSET, true,
             ConstRRsetPtr(), NULL, NULL, Zone::FIND_GLUE_OK);

    // glue OK + NXDOMAIN case
    findTest(Name("www.child.example.org"), RRType::A(), Zone::DELEGATION,
             true, rr_child_ns_, NULL, NULL, Zone::FIND_GLUE_OK);

    // TODO:
    // glue name would match a wildcard under a zone cut: wildcard match
    // shouldn't happen under a cut and result must be PARTIALMATCH
    // (This case cannot be tested yet)

    // nested cut case.  The glue should be found.
    findTest(grandchild_glue_name_, RRType::AAAA(), Zone::SUCCESS,
             true, rr_grandchild_glue_, NULL, NULL, Zone::FIND_GLUE_OK);

    // A non-existent name in nested cut.  This should result in delegation
    // at the highest zone cut.
    findTest(Name("www.grand.child.example.org"), RRType::TXT(),
             Zone::DELEGATION, true, rr_child_ns_, NULL, NULL,
             Zone::FIND_GLUE_OK);
}

/**
 * \brief Test searching.
 *
 * Check it finds or does not find correctly and does not throw exceptions.
 * \todo This doesn't do any kind of CNAME and so on. If it isn't
 *     directly there, it just tells it doesn't exist.
 */
TEST_F(MemoryZoneTest, find) {
    // Fill some data inside
    // Now put all the data we have there. It should throw nothing
    EXPECT_NO_THROW(EXPECT_EQ(SUCCESS, zone_.add(rr_ns_)));
    EXPECT_NO_THROW(EXPECT_EQ(SUCCESS, zone_.add(rr_ns_a_)));
    EXPECT_NO_THROW(EXPECT_EQ(SUCCESS, zone_.add(rr_ns_aaaa_)));
    EXPECT_NO_THROW(EXPECT_EQ(SUCCESS, zone_.add(rr_a_)));

    // These two should be successful
    findTest(origin_, RRType::NS(), Zone::SUCCESS, true, rr_ns_);
    findTest(ns_name_, RRType::A(), Zone::SUCCESS, true, rr_ns_a_);

    // These domain exist but don't have the provided RRType
    findTest(origin_, RRType::AAAA(), Zone::NXRRSET);
    findTest(ns_name_, RRType::NS(), Zone::NXRRSET);

    // These domains don't exist (and one is out of the zone)
    findTest(Name("nothere.example.org"), RRType::A(), Zone::NXDOMAIN);
    findTest(Name("example.net"), RRType::A(), Zone::NXDOMAIN);
}

TEST_F(MemoryZoneTest, load) {
    // Put some data inside the zone
    EXPECT_NO_THROW(EXPECT_EQ(result::SUCCESS, zone_.add(rr_ns_)));
    // Loading with different origin should fail
    EXPECT_THROW(zone_.load(TEST_DATA_DIR "/root.zone"), MasterLoadError);
    // See the original data is still there, survived the exception
    findTest(origin_, RRType::NS(), Zone::SUCCESS, true, rr_ns_);
    // Create correct zone
    MemoryZone rootzone(class_, Name("."));
    // Try putting something inside
    EXPECT_NO_THROW(EXPECT_EQ(result::SUCCESS, rootzone.add(rr_ns_aaaa_)));
    // Load the zone. It should overwrite/remove the above RRset
    EXPECT_NO_THROW(rootzone.load(TEST_DATA_DIR "/root.zone"));

    // Now see there are some rrsets (we don't look inside, though)
    findTest(Name("."), RRType::SOA(), Zone::SUCCESS, false, ConstRRsetPtr(),
        NULL, &rootzone);
    findTest(Name("."), RRType::NS(), Zone::SUCCESS, false, ConstRRsetPtr(),
        NULL, &rootzone);
    findTest(Name("a.root-servers.net."), RRType::A(), Zone::SUCCESS, false,
        ConstRRsetPtr(), NULL, &rootzone);
    // But this should no longer be here
    findTest(ns_name_, RRType::AAAA(), Zone::NXDOMAIN, true, ConstRRsetPtr(),
        NULL, &rootzone);

    // Try loading zone that is wrong in a different way
    EXPECT_THROW(zone_.load(TEST_DATA_DIR "/duplicate_rrset.zone"),
        MasterLoadError);
}

TEST_F(MemoryZoneTest, swap) {
    // build one zone with some data
    MemoryZone zone1(class_, origin_);
    EXPECT_EQ(result::SUCCESS, zone1.add(rr_ns_));
    EXPECT_EQ(result::SUCCESS, zone1.add(rr_ns_aaaa_));

    // build another zone of a different RR class with some other data
    const Name other_origin("version.bind");
    ASSERT_NE(origin_, other_origin); // make sure these two are different
    MemoryZone zone2(RRClass::CH(), other_origin);
    EXPECT_EQ(result::SUCCESS,
              zone2.add(RRsetPtr(new RRset(Name("version.bind"),
                                           RRClass::CH(), RRType::TXT(),
                                           RRTTL(0)))));

    zone1.swap(zone2);
    EXPECT_EQ(other_origin, zone1.getOrigin());
    EXPECT_EQ(origin_, zone2.getOrigin());
    EXPECT_EQ(RRClass::CH(), zone1.getClass());
    EXPECT_EQ(RRClass::IN(), zone2.getClass());
    // make sure the zone data is swapped, too
    findTest(origin_, RRType::NS(), Zone::NXDOMAIN, false, ConstRRsetPtr(),
             NULL, &zone1);
    findTest(other_origin, RRType::TXT(), Zone::SUCCESS, false,
             ConstRRsetPtr(), NULL, &zone1);
    findTest(origin_, RRType::NS(), Zone::SUCCESS, false, ConstRRsetPtr(),
             NULL, &zone2);
    findTest(other_origin, RRType::TXT(), Zone::NXDOMAIN, false,
             ConstRRsetPtr(), NULL, &zone2);
}

TEST_F(MemoryZoneTest, getFileName) {
    // for an empty zone the file name should also be empty.
    EXPECT_TRUE(zone_.getFileName().empty());

    // if loading a zone fails the file name shouldn't be set.
    EXPECT_THROW(zone_.load(TEST_DATA_DIR "/root.zone"), MasterLoadError);
    EXPECT_TRUE(zone_.getFileName().empty());

    // after a successful load, the specified file name should be set
    MemoryZone rootzone(class_, Name("."));
    EXPECT_NO_THROW(rootzone.load(TEST_DATA_DIR "/root.zone"));
    EXPECT_EQ(TEST_DATA_DIR "/root.zone", rootzone.getFileName());
    // overriding load, which will fail
    EXPECT_THROW(rootzone.load(TEST_DATA_DIR "/duplicate_rrset.zone"),
                 MasterLoadError);
    // the file name should be intact.
    EXPECT_EQ(TEST_DATA_DIR "/root.zone", rootzone.getFileName());

    // After swap, file names should also be swapped.
    zone_.swap(rootzone);
    EXPECT_EQ(TEST_DATA_DIR "/root.zone", zone_.getFileName());
    EXPECT_TRUE(rootzone.getFileName().empty());
}

}