--- /dev/null
+#define BOOST_TEST_DYN_LINK
+#define BOOST_TEST_NO_MAIN
+#include <boost/test/unit_test.hpp>
+
+#include "negcache.hh"
+#include "dnsrecords.hh"
+#include "utility.hh"
+
+static recordsAndSignatures genRecsAndSigs(const DNSName& name, const uint16_t qtype, const string& content, bool sigs) {
+ recordsAndSignatures ret;
+
+ DNSRecord rec;
+ rec.d_name = name;
+ rec.d_type = qtype;
+ rec.d_ttl = 600;
+ rec.d_place = DNSResourceRecord::AUTHORITY;
+ rec.d_content = shared_ptr<DNSRecordContent>(DNSRecordContent::mastermake(qtype, QClass::IN, content));
+
+ ret.records.push_back(rec);
+
+ if (sigs) {
+ rec.d_type = QType::RRSIG;
+ rec.d_content = std::make_shared<RRSIGRecordContent>(QType(qtype).getName() + " 5 3 600 2100010100000000 2100010100000000 24567 dummy data");
+ ret.signatures.push_back(rec);
+ }
+
+ return ret;
+}
+
+static NegCache::NegCacheEntry genNegCacheEntry(const DNSName& name, const DNSName& auth, const struct timeval& now, const uint16_t qtype=0) {
+ NegCache::NegCacheEntry ret;
+
+ ret.d_name = name;
+ ret.d_qtype = QType(qtype);
+ ret.d_auth = auth;
+ ret.d_ttd = now.tv_sec + 600;
+ ret.authoritySOA = genRecsAndSigs(auth, QType::SOA, "ns1 hostmaster 1 2 3 4 5", true);
+ ret.DNSSECRecords = genRecsAndSigs(auth, QType::NSEC, "deadbeef", true);
+
+ return ret;
+}
+
+BOOST_AUTO_TEST_SUITE(negcache_cc)
+
+BOOST_AUTO_TEST_CASE(test_get_entry) {
+ /* Add a full name negative entry to the cache and attempt to get an entry for
+ * the A record. Should yield the full name does not exist entry
+ */
+ DNSName qname("www2.powerdns.com");
+ DNSName auth("powerdns.com");
+
+ struct timeval now;
+ Utility::gettimeofday(&now, 0);
+
+ NegCache cache;
+ cache.add(genNegCacheEntry(qname, auth, now));
+
+ BOOST_CHECK_EQUAL(cache.size(), 1);
+
+ NegCache::NegCacheEntry ne;
+ bool ret = cache.get(qname, QType(1), now, ne);
+
+ BOOST_CHECK(ret);
+ BOOST_CHECK_EQUAL(ne.d_name, qname);
+ BOOST_CHECK_EQUAL(ne.d_qtype.getName(), QType(0).getName());
+ BOOST_CHECK_EQUAL(ne.d_auth, auth);
+}
+
+BOOST_AUTO_TEST_CASE(test_get_NODATA_entry) {
+ DNSName qname("www2.powerdns.com");
+ DNSName auth("powerdns.com");
+
+ struct timeval now;
+ Utility::gettimeofday(&now, 0);
+
+ NegCache cache;
+ cache.add(genNegCacheEntry(qname, auth, now, 1));
+
+ BOOST_CHECK_EQUAL(cache.size(), 1);
+
+ NegCache::NegCacheEntry ne;
+ bool ret = cache.get(qname, QType(1), now, ne);
+
+ BOOST_CHECK(ret);
+ BOOST_CHECK_EQUAL(ne.d_name, qname);
+ BOOST_CHECK_EQUAL(ne.d_qtype.getName(), QType(1).getName());
+ BOOST_CHECK_EQUAL(ne.d_auth, auth);
+
+ NegCache::NegCacheEntry ne2;
+ ret = cache.get(qname, QType(16), now, ne2);
+ BOOST_CHECK_EQUAL(ret, false);
+}
+
+BOOST_AUTO_TEST_CASE(test_getRootNXTrust_entry) {
+ DNSName qname("com");
+ DNSName auth(".");
+
+ struct timeval now;
+ Utility::gettimeofday(&now, 0);
+
+ NegCache cache;
+ cache.add(genNegCacheEntry(qname, auth, now));
+
+ BOOST_CHECK_EQUAL(cache.size(), 1);
+
+ NegCache::NegCacheEntry ne;
+ bool ret = cache.getRootNXTrust(qname, now, ne);
+
+ BOOST_CHECK(ret);
+ BOOST_CHECK_EQUAL(ne.d_name, qname);
+ BOOST_CHECK_EQUAL(ne.d_qtype.getName(), QType(0).getName());
+ BOOST_CHECK_EQUAL(ne.d_auth, auth);
+}
+
+BOOST_AUTO_TEST_CASE(test_add_and_get_expired_entry) {
+ DNSName qname("www2.powerdns.com");
+ DNSName auth("powerdns.com");
+
+ struct timeval now;
+ Utility::gettimeofday(&now, 0);
+ now.tv_sec -= 1000;
+
+ NegCache cache;
+ cache.add(genNegCacheEntry(qname, auth, now));
+
+ BOOST_CHECK_EQUAL(cache.size(), 1);
+
+ NegCache::NegCacheEntry ne;
+
+ now.tv_sec += 1000;
+ bool ret = cache.get(qname, QType(1), now, ne);
+
+ BOOST_CHECK_EQUAL(ret, false);
+ BOOST_CHECK_EQUAL(ne.d_name, DNSName());
+ BOOST_CHECK_EQUAL(ne.d_auth, DNSName());
+ BOOST_CHECK(ne.authoritySOA.records.empty());
+}
+
+BOOST_AUTO_TEST_CASE(test_add_updated_entry) {
+ DNSName qname("www2.powerdns.com");
+ DNSName auth("powerdns.com");
+ DNSName auth2("com");
+
+ struct timeval now;
+ Utility::gettimeofday(&now, 0);
+
+ NegCache cache;
+ cache.add(genNegCacheEntry(qname, auth, now));
+ // Should override the existing entry for www2.powerdns.com
+ cache.add(genNegCacheEntry(qname, auth2, now));
+
+ BOOST_CHECK_EQUAL(cache.size(), 1);
+
+ NegCache::NegCacheEntry ne;
+ cache.get(qname, QType(1), now, ne);
+
+ BOOST_CHECK_EQUAL(ne.d_name, qname);
+ BOOST_CHECK_EQUAL(ne.d_auth, auth2);
+}
+
+BOOST_AUTO_TEST_CASE(test_prune) {
+ string qname(".powerdns.com");
+ DNSName auth("powerdns.com");
+
+ struct timeval now;
+ Utility::gettimeofday(&now, 0);
+
+ NegCache cache;
+ NegCache::NegCacheEntry ne;
+ for(int i = 0; i < 400; i++) {
+ ne = genNegCacheEntry(DNSName(std::to_string(i) + qname), auth, now);
+ cache.add(ne);
+ }
+
+ BOOST_CHECK_EQUAL(cache.size(), 400);
+
+ cache.prune(100);
+
+ BOOST_CHECK_EQUAL(cache.size(), 100);
+}
+
+BOOST_AUTO_TEST_CASE(test_wipe_single) {
+ string qname(".powerdns.com");
+ DNSName auth("powerdns.com");
+
+ struct timeval now;
+ Utility::gettimeofday(&now, 0);
+
+ NegCache cache;
+ NegCache::NegCacheEntry ne;
+ ne = genNegCacheEntry(auth, auth, now);
+ cache.add(ne);
+
+ for(int i = 0; i < 400; i++) {
+ ne = genNegCacheEntry(DNSName(std::to_string(i) + qname), auth, now);
+ cache.add(ne);
+ }
+
+ BOOST_CHECK_EQUAL(cache.size(), 401);
+
+ // Should only wipe the powerdns.com entry
+ cache.wipe(auth);
+ BOOST_CHECK_EQUAL(cache.size(), 400);
+
+ NegCache::NegCacheEntry ne2;
+ bool ret = cache.get(auth, QType(1), now, ne2);
+
+ BOOST_CHECK_EQUAL(ret, false);
+ BOOST_CHECK_EQUAL(ne2.d_auth, DNSName());
+ BOOST_CHECK_EQUAL(ne2.d_name, DNSName());
+
+ cache.wipe(DNSName("1.powerdns.com"));
+ BOOST_CHECK_EQUAL(cache.size(), 399);
+
+ NegCache::NegCacheEntry ne3;
+ ret = cache.get(auth, QType(1), now, ne3);
+
+ BOOST_CHECK_EQUAL(ret, false);
+ BOOST_CHECK_EQUAL(ne3.d_auth, DNSName());
+ BOOST_CHECK_EQUAL(ne3.d_name, DNSName());
+}
+
+BOOST_AUTO_TEST_CASE(test_wipe_subtree) {
+ string qname(".powerdns.com");
+ string qname2("powerdns.org");
+ DNSName auth("powerdns.com");
+
+ struct timeval now;
+ Utility::gettimeofday(&now, 0);
+
+ NegCache cache;
+ NegCache::NegCacheEntry ne;
+ ne = genNegCacheEntry(auth, auth, now);
+ cache.add(ne);
+
+ for(int i = 0; i < 400; i++) {
+ ne = genNegCacheEntry(DNSName(std::to_string(i) + qname), auth, now);
+ cache.add(ne);
+ ne = genNegCacheEntry(DNSName(std::to_string(i) + qname2), auth, now);
+ cache.add(ne);
+ }
+
+ BOOST_CHECK_EQUAL(cache.size(), 801);
+
+ // Should wipe all the *.powerdns.com and powerdns.com entries
+ cache.wipe(auth, true);
+ BOOST_CHECK_EQUAL(cache.size(), 400);
+}
+
+BOOST_AUTO_TEST_CASE(test_clear) {
+ string qname(".powerdns.com");
+ DNSName auth("powerdns.com");
+
+ struct timeval now;
+ Utility::gettimeofday(&now, 0);
+
+ NegCache cache;
+ NegCache::NegCacheEntry ne;
+
+ for(int i = 0; i < 400; i++) {
+ ne = genNegCacheEntry(DNSName(std::to_string(i) + qname), auth, now);
+ cache.add(ne);
+ }
+
+ BOOST_CHECK_EQUAL(cache.size(), 400);
+ cache.clear();
+ BOOST_CHECK_EQUAL(cache.size(), 0);
+}
+
+BOOST_AUTO_TEST_CASE(test_dumpToFile) {
+ NegCache cache;
+ vector<string> expected;
+ expected.push_back("www1.powerdns.com. 600 IN TYPE0 VIA powerdns.com.\n");
+ expected.push_back("www2.powerdns.com. 600 IN TYPE0 VIA powerdns.com.\n");
+
+ struct timeval now;
+ Utility::gettimeofday(&now, 0);
+
+ cache.add(genNegCacheEntry(DNSName("www1.powerdns.com"), DNSName("powerdns.com"), now));
+ cache.add(genNegCacheEntry(DNSName("www2.powerdns.com"), DNSName("powerdns.com"), now));
+
+ FILE* fp;
+ fp = tmpfile();
+ if (!fp)
+ BOOST_FAIL("Temporary file could not be opened");
+
+ cache.dumpToFile(fp);
+
+ rewind(fp);
+ char *line = nullptr;
+ size_t len = 0;
+ ssize_t read;
+
+ for (const auto& str : expected) {
+ read = getline(&line, &len, fp);
+ if (read == -1)
+ BOOST_FAIL("Unable to read a line from the temp file");
+ BOOST_CHECK_EQUAL(line, str);
+ }
+ fclose(fp);
+}
+
+BOOST_AUTO_TEST_SUITE_END()