// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC") // // 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 #include #include #include #include #include #include using namespace isc::datasrc; using namespace std; using namespace boost; using namespace isc::dns; namespace { /* * A connection with minimum implementation, keeping the original * "NotImplemented" methods. */ class NopConnection : public DatabaseConnection { public: virtual std::pair getZone(const Name& name) const { if (name == Name("example.org")) { return (std::pair(true, 42)); } else if (name == Name("null.example.org")) { return (std::pair(true, 13)); } else if (name == Name("empty.example.org")) { return (std::pair(true, 0)); } else if (name == Name("bad.example.org")) { return (std::pair(true, -1)); } else { return (std::pair(false, 0)); } } }; /* * A virtual database connection that pretends it contains single zone -- * example.org. * * It has the same getZone method as NopConnection, but it provides * implementation of the optional functionality. */ class MockConnection : public NopConnection { private: class MockIteratorContext : public IteratorContext { private: int step; public: MockIteratorContext() : step(0) { } virtual bool getNext(string& name, string& rtype, int& ttl, string& data) { switch (step ++) { case 0: name = "example.org"; rtype = "SOA"; ttl = 300; data = "ns1.example.org. admin.example.org. " "1234 3600 1800 2419200 7200"; return (true); case 1: name = "x.example.org"; rtype = "A"; ttl = 300; data = "192.0.2.1"; return (true); case 2: name = "x.example.org"; rtype = "A"; ttl = 300; data = "192.0.2.2"; return (true); case 3: name = "x.example.org"; rtype = "AAAA"; ttl = 300; data = "2001:db8::1"; return (true); case 4: name = "x.example.org"; rtype = "AAAA"; ttl = 300; data = "2001:db8::2"; return (true); default: ADD_FAILURE() << "Request past the end of iterator context"; case 5: return (false); } } }; class EmptyIteratorContext : public IteratorContext { public: virtual bool getNext(string&, string&, int&, string&) { return (false); } }; class BadIteratorContext : public IteratorContext { private: int step; public: BadIteratorContext() : step(0) { } virtual bool getNext(string& name, string& rtype, int& ttl, string& data) { switch (step ++) { case 0: name = "x.example.org"; rtype = "A"; ttl = 300; data = "192.0.2.1"; return (true); case 1: name = "x.example.org"; rtype = "A"; ttl = 301; data = "192.0.2.2"; return (true); default: ADD_FAILURE() << "Request past the end of iterator context"; case 2: return (false); } } }; public: virtual IteratorContextPtr getIteratorContext(const Name&, int id) const { if (id == 42) { return (IteratorContextPtr(new MockIteratorContext())); } else if (id == 13) { return (IteratorContextPtr()); } else if (id == 0) { return (IteratorContextPtr(new EmptyIteratorContext())); } else if (id == -1) { return (IteratorContextPtr(new BadIteratorContext())); } else { isc_throw(isc::Unexpected, "Unknown zone ID"); } } }; // This tests the default getIteratorContext behaviour, throwing NotImplemented TEST(DatabaseConnectionTest, getIteratorContext) { // The parameters don't matter EXPECT_THROW(NopConnection().getIteratorContext(Name("."), 1), isc::NotImplemented); } class DatabaseClientTest : public ::testing::Test { public: DatabaseClientTest() { createClient(); } /* * We initialize the client from a function, so we can call it multiple * times per test. */ void createClient() { current_connection_ = new MockConnection(); client_.reset(new DatabaseClient(auto_ptr( current_connection_))); } // Will be deleted by client_, just keep the current value for comparison. MockConnection* current_connection_; auto_ptr client_; /** * Check the zone finder is a valid one and references the zone ID and * connection available here. */ void checkZoneFinder(const DataSourceClient::FindResult& zone) { ASSERT_NE(ZoneFinderPtr(), zone.zone_finder) << "No zone finder"; shared_ptr finder( dynamic_pointer_cast(zone.zone_finder)); ASSERT_NE(shared_ptr(), finder) << "Wrong type of finder"; EXPECT_EQ(42, finder->zone_id()); EXPECT_EQ(current_connection_, &finder->connection()); } }; TEST_F(DatabaseClientTest, zoneNotFound) { DataSourceClient::FindResult zone(client_->findZone(Name("example.com"))); EXPECT_EQ(result::NOTFOUND, zone.code); } TEST_F(DatabaseClientTest, exactZone) { DataSourceClient::FindResult zone(client_->findZone(Name("example.org"))); EXPECT_EQ(result::SUCCESS, zone.code); checkZoneFinder(zone); } TEST_F(DatabaseClientTest, superZone) { DataSourceClient::FindResult zone(client_->findZone(Name( "sub.example.org"))); EXPECT_EQ(result::PARTIALMATCH, zone.code); checkZoneFinder(zone); } TEST_F(DatabaseClientTest, noConnException) { EXPECT_THROW(DatabaseClient(auto_ptr()), isc::InvalidParameter); } // If the zone doesn't exist, exception is thrown TEST_F(DatabaseClientTest, noZoneIterator) { EXPECT_THROW(client_->getIterator(Name("example.com")), DataSourceError); } // If the zone doesn't exist and iteration is not implemented, it still throws // the exception it doesn't exist TEST_F(DatabaseClientTest, noZoneNotImplementedIterator) { EXPECT_THROW(DatabaseClient(auto_ptr( new NopConnection())).getIterator(Name("example.com")), DataSourceError); } TEST_F(DatabaseClientTest, notImplementedIterator) { EXPECT_THROW(DatabaseClient(auto_ptr( new NopConnection())).getIterator(Name("example.org")), isc::NotImplemented); } // Pretend a bug in the connection and pass NULL as the context // Should not crash, but gracefully throw TEST_F(DatabaseClientTest, nullIteratorContext) { EXPECT_THROW(client_->getIterator(Name("null.example.org")), isc::Unexpected); } // It doesn't crash or anything if the zone is completely empty TEST_F(DatabaseClientTest, emptyIterator) { ZoneIteratorPtr it(client_->getIterator(Name("empty.example.org"))); EXPECT_EQ(ConstRRsetPtr(), it->getNextRRset()); // This is past the end, it should throw EXPECT_THROW(it->getNextRRset(), isc::Unexpected); } // Iterate trough a zone TEST_F(DatabaseClientTest, iterator) { ZoneIteratorPtr it(client_->getIterator(Name("example.org"))); ConstRRsetPtr rrset(it->getNextRRset()); ASSERT_NE(ConstRRsetPtr(), rrset); EXPECT_EQ(Name("example.org"), rrset->getName()); EXPECT_EQ(RRClass::IN(), rrset->getClass()); EXPECT_EQ(RRType::SOA(), rrset->getType()); EXPECT_EQ(RRTTL(300), rrset->getTTL()); RdataIteratorPtr rit(rrset->getRdataIterator()); ASSERT_FALSE(rit->isLast()); rit->next(); EXPECT_TRUE(rit->isLast()); rrset = it->getNextRRset(); ASSERT_NE(ConstRRsetPtr(), rrset); EXPECT_EQ(Name("x.example.org"), rrset->getName()); EXPECT_EQ(RRClass::IN(), rrset->getClass()); EXPECT_EQ(RRType::A(), rrset->getType()); EXPECT_EQ(RRTTL(300), rrset->getTTL()); rit = rrset->getRdataIterator(); ASSERT_FALSE(rit->isLast()); EXPECT_EQ("192.0.2.1", rit->getCurrent().toText()); rit->next(); ASSERT_FALSE(rit->isLast()); EXPECT_EQ("192.0.2.2", rit->getCurrent().toText()); rit->next(); EXPECT_TRUE(rit->isLast()); rrset = it->getNextRRset(); ASSERT_NE(ConstRRsetPtr(), rrset); EXPECT_EQ(Name("x.example.org"), rrset->getName()); EXPECT_EQ(RRClass::IN(), rrset->getClass()); EXPECT_EQ(RRType::AAAA(), rrset->getType()); EXPECT_EQ(RRTTL(300), rrset->getTTL()); EXPECT_EQ(ConstRRsetPtr(), it->getNextRRset()); rit = rrset->getRdataIterator(); ASSERT_FALSE(rit->isLast()); EXPECT_EQ("2001:db8::1", rit->getCurrent().toText()); rit->next(); ASSERT_FALSE(rit->isLast()); EXPECT_EQ("2001:db8::2", rit->getCurrent().toText()); rit->next(); EXPECT_TRUE(rit->isLast()); } // This has inconsistent TTL in the set (the rest, like nonsense in // the data is handled in rdata itself). TEST_F(DatabaseClientTest, badIterator) { ZoneIteratorPtr it(client_->getIterator(Name("bad.example.org"))); EXPECT_THROW(it->getNextRRset(), DataSourceError); } }