// Copyright (C) 2019-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/. #include #include #include #include #include #include using namespace isc; using namespace isc::db; namespace { /// @brief Test fixture class for testing @c AuditEntry. class AuditEntryTest : public ::testing::Test { public: /// @brief Constructor. AuditEntryTest() : fixed_time_(now()) { } /// @brief Returns current time. static boost::posix_time::ptime now() { return (boost::posix_time::microsec_clock::local_time()); } /// @brief Returns always the same time value. /// /// The value is initialized when the test it started. boost::posix_time::ptime fixedTime() const { return (fixed_time_); } /// @brief Checks if the given time value is "close" to the /// current time. /// /// This is useful in tests when the @c AuditEntry class sets the /// modification time to a default value (in its constructor). /// Because the test doesn't know the exact value to which the /// modification time is set, it merely checks that this value /// is earlier than current time and within the range of 1s. /// /// @param t time value to be checked. bool almostEqualTime(const boost::posix_time::ptime& t) const { auto current = now(); // The provided value must be a valid time. if (t.is_not_a_date_time()) { ADD_FAILURE() << "provided value is not a date time"; return (false); } // It must be earlier than current time. if (t > current) { ADD_FAILURE() << "provided time value is later than current time"; return (false); } // The difference must be lower than 1 second. boost::posix_time::time_duration dur = current - t; return (dur.total_milliseconds() < 1000); } /// @brief Fixed time value initialized in the constructor. /// /// This is used in tests that require the exact time values. boost::posix_time::ptime fixed_time_; }; // Checks that the modification time value can be cast to a number. TEST_F(AuditEntryTest, modificationType) { EXPECT_EQ(0, static_cast(AuditEntry::ModificationType::CREATE)); EXPECT_EQ(1, static_cast(AuditEntry::ModificationType::UPDATE)); EXPECT_EQ(2, static_cast(AuditEntry::ModificationType::DELETE)); } // Checks that the audit entry can be created. TEST_F(AuditEntryTest, create) { AuditEntryPtr audit_entry; { SCOPED_TRACE("create with modification time"); ASSERT_NO_THROW(audit_entry = AuditEntry::create ("dhcp4_subnet", 10, AuditEntry::ModificationType::DELETE, fixedTime(), 123, "deleted subnet 10")); EXPECT_EQ("dhcp4_subnet", audit_entry->getObjectType()); EXPECT_EQ(10, audit_entry->getObjectId()); EXPECT_EQ(AuditEntry::ModificationType::DELETE, audit_entry->getModificationType()); EXPECT_EQ(fixedTime(), audit_entry->getModificationTime()); EXPECT_EQ(123, audit_entry->getRevisionId()); EXPECT_EQ("deleted subnet 10", audit_entry->getLogMessage()); } { SCOPED_TRACE("create with default modification time"); ASSERT_NO_THROW(audit_entry = AuditEntry::create ("dhcp4_option", 123, AuditEntry::ModificationType::CREATE, 234, "")); EXPECT_EQ("dhcp4_option", audit_entry->getObjectType()); EXPECT_EQ(123, audit_entry->getObjectId()); EXPECT_EQ(AuditEntry::ModificationType::CREATE, audit_entry->getModificationType()); EXPECT_TRUE(almostEqualTime(audit_entry->getModificationTime())); EXPECT_EQ(234, audit_entry->getRevisionId()); EXPECT_TRUE(audit_entry->getLogMessage().empty()); } } // Checks that invalid values for the audit entry are rejected. TEST_F(AuditEntryTest, createFailures) { { SCOPED_TRACE("empty object type"); EXPECT_THROW(AuditEntry("", 10, AuditEntry::ModificationType::DELETE, fixedTime(), 123, "deleted subnet 10"), BadValue); } { SCOPED_TRACE("not a date time"); EXPECT_THROW(AuditEntry("dhcp4_subnet", 10, AuditEntry::ModificationType::DELETE, boost::posix_time::ptime(), 123, "deleted subnet 10"), BadValue); } } /// @brief Test fixture class for testing @c AuditEntryCollection. class AuditEntryCollectionTest : public AuditEntryTest { public: /// @brief Constructor. /// /// Creates a collection of audit entries used in the tests. AuditEntryCollectionTest() : AuditEntryTest(), audit_entries_() { createTestAuditEntries(); } /// @brief Returns a time value being being a specified number of /// seconds later or earlier than the time returned by @c fixedTime. /// /// @param secs offset in seconds since the @c fixedTime output. If /// the parameter is negative, the returned time is earlier than the /// fixed time. Otherwise, it is later than fixed time. boost::posix_time::ptime diffTime(const long secs) { if (secs < 0) { return (fixedTime() - boost::posix_time::seconds(-secs)); } return (fixedTime() + boost::posix_time::seconds(secs)); } /// @brief Creates an @c AuditEntry instance and inserts it to /// the @c audit_entries_ collection. /// /// @tparam Args types of the arguments to be passed to the @c AuditEntry /// constructors. /// @param args arguments to be passed to the @c AuditEntry constructors. template void create(Args&& ...args) { audit_entries_.insert(boost::make_shared(args...)); } /// @brief Creates a collection of @c AuditEntry objects to be used by /// the tests. void createTestAuditEntries() { create("dhcp4_subnet", 10, AuditEntry::ModificationType::CREATE, diffTime(-5), 100, "added subnet 10"); create("dhcp4_shared_network", 1, AuditEntry::ModificationType::CREATE, diffTime(-5), 110, "added shared network 1"); create("dhcp4_shared_network", 120, AuditEntry::ModificationType::UPDATE, diffTime(-8), 90, "updated shared network 120"); create("dhcp4_subnet", 120, AuditEntry::ModificationType::DELETE, diffTime(8), 130, "deleted subnet 120"); create("dhcp4_subnet", 1000, AuditEntry::ModificationType::CREATE, diffTime(4), 120, "created subnet 1000"); create("dhcp4_option", 15, AuditEntry::ModificationType::UPDATE, diffTime(16), 140, "updated option 15"); } /// @brief Checks if the returned results range contains an @c AuditEntry /// with a given object type and identifier. /// /// @param object_type expected object type. /// @param object_id expected object id. /// @param begin beginning of the results range to be examined. /// @param end end of the results range to be examined. template bool includes(const std::string& object_type, const uint64_t object_id, Iterator begin, Iterator end) { // Iterate over the results range and look for the entry. for (auto it = begin; it != end; ++it) { if (((*it)->getObjectType() == object_type) && ((*it)->getObjectId() == object_id)) { // Entry found. return (true); } } // Entry not found. return (false); } /// @brief Checks if the returned results range contains an @c AuditEntry /// with a given object and modification types, and object identifier. /// /// @param object_type expected object type. /// @param object_id expected object id. /// @param modification_type expected modification type. /// @param begin beginning of the results range to be examined. /// @param end end of the results range to be examined. template bool includes(const std::string& object_type, const uint64_t object_id, const AuditEntry::ModificationType& modification_type, Iterator begin, Iterator end) { // Iterate over the results range and look for the entry. for (auto it = begin; it != end; ++it) { if (((*it)->getObjectType() == object_type) && ((*it)->getObjectId() == object_id) && ((*it)->getModificationType() == modification_type)) { // Entry found. return (true); } } // Entry not found. return (false); } /// @brief Audit entries used in the tests. AuditEntryCollection audit_entries_; }; // Checks that entries can be found by object type. TEST_F(AuditEntryCollectionTest, getByObjectType) { auto const& object_type_idx = audit_entries_.get(); // Search for "dhcp4_subnet" objects. auto range = object_type_idx.equal_range("dhcp4_subnet"); ASSERT_EQ(3, std::distance(range.first, range.second)); EXPECT_TRUE(includes("dhcp4_subnet", 10, range.first, range.second)); EXPECT_TRUE(includes("dhcp4_subnet", 120, range.first, range.second)); EXPECT_TRUE(includes("dhcp4_subnet", 1000, range.first, range.second)); // Search for "dhcp4_shared_network" objects. range = object_type_idx.equal_range("dhcp4_shared_network"); ASSERT_EQ(2, std::distance(range.first, range.second)); EXPECT_TRUE(includes("dhcp4_shared_network", 1, range.first, range.second)); EXPECT_TRUE(includes("dhcp4_shared_network", 120, range.first, range.second)); // Search for "dhcp4_option" objects. range = object_type_idx.equal_range("dhcp4_option"); ASSERT_EQ(1, std::distance(range.first, range.second)); EXPECT_TRUE(includes("dhcp4_option", 15, range.first, range.second)); } // Checks that entries can be found by modification time. TEST_F(AuditEntryCollectionTest, getByModificationTime) { auto const& mod_time_idx = audit_entries_.get(); // Search for objects later than fixed time - 10s. auto lb = mod_time_idx.lower_bound(diffTime(-10)); ASSERT_EQ(6, std::distance(lb, mod_time_idx.end())); EXPECT_TRUE(includes("dhcp4_subnet", 10, lb, mod_time_idx.end())); EXPECT_TRUE(includes("dhcp4_subnet", 120, lb, mod_time_idx.end())); EXPECT_TRUE(includes("dhcp4_subnet", 1000, lb, mod_time_idx.end())); EXPECT_TRUE(includes("dhcp4_shared_network", 1, lb, mod_time_idx.end())); EXPECT_TRUE(includes("dhcp4_shared_network", 120, lb, mod_time_idx.end())); EXPECT_TRUE(includes("dhcp4_option", 15, lb, mod_time_idx.end())); // Search for objects later than fixed time - 7s. lb = mod_time_idx.lower_bound(diffTime(-7)); ASSERT_EQ(5, std::distance(lb, mod_time_idx.end())); EXPECT_TRUE(includes("dhcp4_subnet", 10, lb, mod_time_idx.end())); EXPECT_TRUE(includes("dhcp4_subnet", 120, lb, mod_time_idx.end())); EXPECT_TRUE(includes("dhcp4_subnet", 1000, lb, mod_time_idx.end())); EXPECT_TRUE(includes("dhcp4_shared_network", 1, lb, mod_time_idx.end())); EXPECT_TRUE(includes("dhcp4_option", 15, lb, mod_time_idx.end())); // Search for objects later than fixed time - 1s. lb = mod_time_idx.lower_bound(diffTime(-1)); ASSERT_EQ(3, std::distance(lb, mod_time_idx.end())); EXPECT_TRUE(includes("dhcp4_subnet", 120, lb, mod_time_idx.end())); EXPECT_TRUE(includes("dhcp4_subnet", 1000, lb, mod_time_idx.end())); EXPECT_TRUE(includes("dhcp4_option", 15, lb, mod_time_idx.end())); // Search for objects later than fixed time + 6s. lb = mod_time_idx.lower_bound(diffTime(6)); ASSERT_EQ(2, std::distance(lb, mod_time_idx.end())); EXPECT_TRUE(includes("dhcp4_subnet", 120, lb, mod_time_idx.end())); EXPECT_TRUE(includes("dhcp4_option", 15, lb, mod_time_idx.end())); // Search for objects later than fixed time + 10s. lb = mod_time_idx.lower_bound(diffTime(10)); ASSERT_EQ(1, std::distance(lb, mod_time_idx.end())); EXPECT_TRUE(includes("dhcp4_option", 15, lb, mod_time_idx.end())); // Search for objects later than fixed time + 20s. lb = mod_time_idx.lower_bound(diffTime(20)); // None found. ASSERT_EQ(0, std::distance(lb, mod_time_idx.end())); } // Checks that entries can be found by modification time and id. TEST_F(AuditEntryCollectionTest, getByModificationTimeAndId) { auto const& mod_time_idx = audit_entries_.get(); // Search for objects later than added added subnet 10. auto mod = boost::make_tuple(diffTime(-5), 100 + 1); auto lb = mod_time_idx.lower_bound(mod); ASSERT_EQ(4, std::distance(lb, mod_time_idx.end())); EXPECT_TRUE(includes("dhcp4_subnet", 120, lb, mod_time_idx.end())); EXPECT_TRUE(includes("dhcp4_subnet", 1000, lb, mod_time_idx.end())); EXPECT_TRUE(includes("dhcp4_shared_network", 1, lb, mod_time_idx.end())); EXPECT_TRUE(includes("dhcp4_option", 15, lb, mod_time_idx.end())); // Check the order is time first, id after. create("dhcp4_subnet", 1000, AuditEntry::ModificationType::UPDATE, diffTime(-8), 200, "updated subnet 1000"); lb = mod_time_idx.lower_bound(mod); ASSERT_EQ(4, std::distance(lb, mod_time_idx.end())); EXPECT_TRUE(includes("dhcp4_subnet", 120, lb, mod_time_idx.end())); EXPECT_TRUE(includes("dhcp4_subnet", 1000, lb, mod_time_idx.end())); EXPECT_TRUE(includes("dhcp4_shared_network", 1, lb, mod_time_idx.end())); EXPECT_TRUE(includes("dhcp4_option", 15, lb, mod_time_idx.end())); } // Checks that entries can be found by object id. TEST_F(AuditEntryCollectionTest, getByObjectId) { auto const& object_id_idx = audit_entries_.get(); // Search for object id 10. auto range = object_id_idx.equal_range(10); ASSERT_EQ(1, std::distance(range.first, range.second)); EXPECT_TRUE(includes("dhcp4_subnet", 10, range.first, range.second)); // Add another entry. create("dhcp4_subnet", 10, AuditEntry::ModificationType::UPDATE, diffTime(0), 111, "updated subnet 10"); // Now search should return two entries. range = object_id_idx.equal_range(10); ASSERT_EQ(2, std::distance(range.first, range.second)); EXPECT_TRUE(includes("dhcp4_subnet", 10, AuditEntry::ModificationType::CREATE, range.first, range.second)); EXPECT_TRUE(includes("dhcp4_subnet", 10, AuditEntry::ModificationType::UPDATE, range.first, range.second)); } }