#include "data/DBHelpers.hpp" #include "data/Types.hpp" #include "etl/Models.hpp" #include "etl/impl/ext/Successor.hpp" #include "util/Assert.hpp" #include "util/BinaryTestObject.hpp" #include "util/MockAssert.hpp" #include "util/MockBackendTestFixture.hpp" #include "util/MockLedgerCache.hpp" #include "util/MockPrometheus.hpp" #include "util/StringUtils.hpp" #include "util/TestObject.hpp" #include #include #include #include #include #include #include #include #include #include #include #include using namespace etl::impl; using namespace data; namespace { constinit auto const kSeq = 123u; constinit auto const kLedgerHash = "4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652"; auto createTestData(std::vector objects) { auto transactions = std::vector{ util::createTransaction(ripple::TxType::ttNFTOKEN_BURN), util::createTransaction(ripple::TxType::ttNFTOKEN_BURN), util::createTransaction(ripple::TxType::ttNFTOKEN_CREATE_OFFER), }; auto const header = createLedgerHeader(kLedgerHash, kSeq); return etl::model::LedgerData{ .transactions = std::move(transactions), .objects = std::move(objects), .successors = {}, .edgeKeys = {}, .header = header, .rawHeader = {}, .seq = kSeq }; } [[maybe_unused]] auto createInitialTestData(std::vector edgeKeys) { // initial data expects objects to be empty as well as non-empty edgeKeys ASSERT(not edgeKeys.empty(), "Initial data requires edgeKeys"); auto ret = createTestData({}); ret.edgeKeys = std::make_optional>(); std::ranges::transform(edgeKeys, std::back_inserter(ret.edgeKeys.value()), &uint256ToString); return ret; } } // namespace struct SuccessorExtTests : util::prometheus::WithPrometheus, MockBackendTest { protected: MockLedgerCache cache_; etl::impl::SuccessorExt ext_{backend_, cache_}; }; TEST_F(SuccessorExtTests, OnLedgerDataLogicErrorIfCacheIsNotFullButSuccessorsNotPresent) { auto const data = createTestData({}); EXPECT_CALL(cache_, isFull()).WillRepeatedly(testing::Return(false)); EXPECT_CALL(cache_, latestLedgerSequence()).WillRepeatedly(testing::Return(kSeq)); EXPECT_THROW(ext_.onLedgerData(data), std::logic_error); } TEST_F( SuccessorExtTests, OnLedgerDataLogicErrorIfCacheIsFullButLatestSeqDiffersAndSuccessorsNotPresent ) { auto const data = createTestData({}); EXPECT_CALL(cache_, isFull()).WillRepeatedly(testing::Return(true)); EXPECT_CALL(cache_, latestLedgerSequence()).WillRepeatedly(testing::Return(kSeq - 1)); EXPECT_THROW(ext_.onLedgerData(data), std::logic_error); } TEST_F( SuccessorExtTests, OnLedgerDataWithDeletedObjectButWithoutCachedPredecessorAndSuccessorAndNoBookBase ) { using namespace etl::model; auto const objKey = "B00AA769C00726371689ED66A7CF57C2502F1BF4BDFF2ACADF67A2A7B5E8960D"; auto const deletedObj = util::createObject(Object::ModType::Deleted, objKey); auto const data = createTestData({ deletedObj, util::createObject(Object::ModType::Modified), }); EXPECT_CALL(cache_, isFull()).WillRepeatedly(testing::Return(true)); EXPECT_CALL(cache_, latestLedgerSequence()).WillRepeatedly(testing::Return(kSeq)); EXPECT_CALL(cache_, getPredecessor(deletedObj.key, kSeq)) .WillRepeatedly(testing::Return(std::nullopt)); EXPECT_CALL(cache_, getSuccessor(deletedObj.key, kSeq)) .WillRepeatedly(testing::Return(std::nullopt)); EXPECT_CALL( *backend_, writeSuccessor(uint256ToString(data::kFirstKey), kSeq, uint256ToString(data::kLastKey)) ); EXPECT_CALL(cache_, getDeleted(deletedObj.key, kSeq - 1)) .WillRepeatedly(testing::Return(Blob{'0'})); ext_.onLedgerData(data); } TEST_F( SuccessorExtTests, OnLedgerDataWithCreatedObjectButWithoutCachedPredecessorAndSuccessorAndNoBookBase ) { using namespace etl::model; auto const objKey = "B00AA769C00726371689ED66A7CF57C2502F1BF4BDFF2ACADF67A2A7B5E8960D"; auto const createdObj = util::createObject(Object::ModType::Created, objKey); auto const data = createTestData({ createdObj, util::createObject(Object::ModType::Modified), }); EXPECT_CALL(cache_, isFull()).WillRepeatedly(testing::Return(true)); EXPECT_CALL(cache_, latestLedgerSequence()).WillRepeatedly(testing::Return(kSeq)); EXPECT_CALL(cache_, getPredecessor(createdObj.key, kSeq)) .WillRepeatedly(testing::Return(std::nullopt)); EXPECT_CALL(cache_, getSuccessor(createdObj.key, kSeq)) .WillRepeatedly(testing::Return(std::nullopt)); EXPECT_CALL( *backend_, writeSuccessor(uint256ToString(data::kFirstKey), kSeq, uint256ToString(createdObj.key)) ); EXPECT_CALL( *backend_, writeSuccessor(uint256ToString(createdObj.key), kSeq, uint256ToString(data::kLastKey)) ); ext_.onLedgerData(data); } TEST_F( SuccessorExtTests, OnLedgerDataWithCreatedObjectButWithoutCachedPredecessorAndSuccessorWithBookBase ) { using namespace etl::model; auto const objKey = "B00AA769C00726371689ED66A7CF57C2502F1BF4BDFF2ACADF67A2A7B5E8960D"; auto const createdObj = util::createObjectWithBookBase(Object::ModType::Created, objKey); auto const data = createTestData({ createdObj, util::createObject(Object::ModType::Modified), }); auto const bookBase = getBookBase(createdObj.key); EXPECT_CALL(cache_, isFull()).WillRepeatedly(testing::Return(true)); EXPECT_CALL(cache_, latestLedgerSequence()).WillRepeatedly(testing::Return(kSeq)); EXPECT_CALL(cache_, getPredecessor(createdObj.key, kSeq)) .WillRepeatedly(testing::Return(std::nullopt)); EXPECT_CALL(cache_, getSuccessor(createdObj.key, kSeq)) .WillRepeatedly(testing::Return(std::nullopt)); EXPECT_CALL( *backend_, writeSuccessor(uint256ToString(data::kFirstKey), kSeq, uint256ToString(createdObj.key)) ); EXPECT_CALL( *backend_, writeSuccessor(uint256ToString(createdObj.key), kSeq, uint256ToString(data::kLastKey)) ); EXPECT_CALL(cache_, get(createdObj.key, kSeq)).WillRepeatedly(testing::Return(std::nullopt)); EXPECT_CALL(cache_, getSuccessor(bookBase, kSeq)) .WillRepeatedly(testing::Return(LedgerObject{})); ext_.onLedgerData(data); } TEST_F( SuccessorExtTests, OnLedgerDataWithCreatedObjectButWithoutCachedPredecessorAndSuccessorWithBookBaseAndMatchingSuccessorInCache ) { using namespace etl::model; auto const objKey = "B00AA769C00726371689ED66A7CF57C2502F1BF4BDFF2ACADF67A2A7B5E8960D"; auto const createdObj = util::createObjectWithBookBase(Object::ModType::Created, objKey); auto const data = createTestData({ createdObj, util::createObject(Object::ModType::Modified), }); auto const bookBase = getBookBase(createdObj.key); [[maybe_unused]] testing::InSequence const inSeq; EXPECT_CALL(cache_, isFull()).WillRepeatedly(testing::Return(true)); EXPECT_CALL(cache_, latestLedgerSequence()).WillRepeatedly(testing::Return(kSeq)); EXPECT_CALL(cache_, getPredecessor(createdObj.key, kSeq)) .WillRepeatedly(testing::Return(std::nullopt)); EXPECT_CALL(cache_, getSuccessor(createdObj.key, kSeq)) .WillRepeatedly(testing::Return(std::nullopt)); EXPECT_CALL( *backend_, writeSuccessor(uint256ToString(data::kFirstKey), kSeq, uint256ToString(createdObj.key)) ); EXPECT_CALL( *backend_, writeSuccessor(uint256ToString(createdObj.key), kSeq, uint256ToString(data::kLastKey)) ); EXPECT_CALL(cache_, get(createdObj.key, kSeq)).WillRepeatedly(testing::Return(data::Blob{'0'})); EXPECT_CALL(cache_, getSuccessor(bookBase, kSeq)) .WillRepeatedly(testing::Return(LedgerObject{.key = createdObj.key, .blob = {}})); EXPECT_CALL(*backend_, writeSuccessor(uint256ToString(bookBase), kSeq, testing::_)); ext_.onLedgerData(data); } TEST_F( SuccessorExtTests, OnLedgerDataWithDeletedObjectButWithoutCachedPredecessorAndSuccessorWithBookBaseButNoCurrentObjAndNoSuccessorInCache ) { using namespace etl::model; auto const objKey = "B00AA769C00726371689ED66A7CF57C2502F1BF4BDFF2ACADF67A2A7B5E8960D"; auto const createdObj = util::createObjectWithBookBase(Object::ModType::Created, objKey); auto const deletedObj = util::createObjectWithBookBase(Object::ModType::Deleted, objKey); auto const data = createTestData({ deletedObj, util::createObject(Object::ModType::Modified), }); auto const bookBase = getBookBase(deletedObj.key); auto const oldCachedObj = createdObj.data; [[maybe_unused]] testing::InSequence const inSeq; EXPECT_CALL(cache_, isFull()).WillRepeatedly(testing::Return(true)); EXPECT_CALL(cache_, latestLedgerSequence()).WillRepeatedly(testing::Return(kSeq)); EXPECT_CALL(cache_, getPredecessor(deletedObj.key, kSeq)) .WillRepeatedly(testing::Return(std::nullopt)); EXPECT_CALL(cache_, getSuccessor(deletedObj.key, kSeq)) .WillRepeatedly(testing::Return(std::nullopt)); EXPECT_CALL( *backend_, writeSuccessor(uint256ToString(data::kFirstKey), kSeq, uint256ToString(data::kLastKey)) ); EXPECT_CALL(cache_, getDeleted(deletedObj.key, kSeq - 1)) .WillOnce(testing::Return(oldCachedObj)); EXPECT_CALL(cache_, get(deletedObj.key, kSeq)).WillOnce(testing::Return(std::nullopt)); EXPECT_CALL(cache_, getSuccessor(bookBase, kSeq)).WillOnce(testing::Return(std::nullopt)); EXPECT_CALL( *backend_, writeSuccessor(uint256ToString(bookBase), kSeq, uint256ToString(data::kLastKey)) ); ext_.onLedgerData(data); } TEST_F( SuccessorExtTests, OnLedgerDataWithDeletedObjectButWithoutCachedPredecessorAndSuccessorWithBookBaseAndCurrentObjAndSuccessorInCache ) { using namespace etl::model; auto const objKey = "B00AA769C00726371689ED66A7CF57C2502F1BF4BDFF2ACADF67A2A7B5E8960D"; auto const createdObj = util::createObjectWithBookBase(Object::ModType::Created, objKey); auto const deletedObj = util::createObjectWithBookBase(Object::ModType::Deleted, objKey); auto const data = createTestData({ deletedObj, util::createObject(Object::ModType::Modified), }); auto const bookBase = getBookBase(deletedObj.key); auto const oldCachedObj = createdObj.data; [[maybe_unused]] testing::InSequence const inSeq; EXPECT_CALL(cache_, isFull()).WillRepeatedly(testing::Return(true)); EXPECT_CALL(cache_, latestLedgerSequence()).WillRepeatedly(testing::Return(kSeq)); EXPECT_CALL(cache_, getPredecessor(deletedObj.key, kSeq)) .WillRepeatedly(testing::Return(std::nullopt)); EXPECT_CALL(cache_, getSuccessor(deletedObj.key, kSeq)) .WillRepeatedly(testing::Return(std::nullopt)); EXPECT_CALL( *backend_, writeSuccessor(uint256ToString(data::kFirstKey), kSeq, uint256ToString(data::kLastKey)) ); EXPECT_CALL(cache_, getDeleted(deletedObj.key, kSeq - 1)) .WillOnce(testing::Return(oldCachedObj)); EXPECT_CALL(cache_, get(deletedObj.key, kSeq)).WillOnce(testing::Return(data::Blob{'0'})); EXPECT_CALL(cache_, getSuccessor(bookBase, kSeq)) .WillRepeatedly(testing::Return(LedgerObject{.key = deletedObj.key, .blob = {}})); EXPECT_CALL( *backend_, writeSuccessor(uint256ToString(bookBase), kSeq, uint256ToString(deletedObj.key)) ); ext_.onLedgerData(data); } TEST_F(SuccessorExtTests, OnLedgerDataWithDeletedObjectAndWithCachedPredecessorAndSuccessor) { using namespace etl::model; auto const objKey = "B00AA769C00726371689ED66A7CF57C2502F1BF4BDFF2ACADF67A2A7B5E8960D"; auto const predKey = binaryStringToUint256( hexStringToBinaryString("B00AA769C00726371689ED66A7CF57C2502F1BF4BDFF2ACADF67A2A7B5E8960C") ); auto const succKey = binaryStringToUint256( hexStringToBinaryString("B00AA769C00726371689ED66A7CF57C2502F1BF4BDFF2ACADF67A2A7B5E8960E") ); auto const createdObj = util::createObject(Object::ModType::Created, objKey); auto const data = createTestData({ createdObj, util::createObject(Object::ModType::Modified), }); EXPECT_CALL(cache_, isFull()).WillRepeatedly(testing::Return(true)); EXPECT_CALL(cache_, latestLedgerSequence()).WillRepeatedly(testing::Return(kSeq)); EXPECT_CALL(cache_, getPredecessor(createdObj.key, kSeq)) .WillOnce(testing::Return(data::LedgerObject{.key = predKey, .blob = {}})); EXPECT_CALL(cache_, getSuccessor(createdObj.key, kSeq)) .WillOnce(testing::Return(data::LedgerObject{.key = succKey, .blob = {}})); EXPECT_CALL( *backend_, writeSuccessor(uint256ToString(predKey), kSeq, uint256ToString(createdObj.key)) ); EXPECT_CALL( *backend_, writeSuccessor(uint256ToString(createdObj.key), kSeq, uint256ToString(succKey)) ); ext_.onLedgerData(data); } TEST_F(SuccessorExtTests, OnLedgerDataWithCreatedObjectAndIncludedSuccessors) { using namespace etl::model; auto const objKey = "B00AA769C00726371689ED66A7CF57C2502F1BF4BDFF2ACADF67A2A7B5E8960D"; auto const createdObj = util::createObject(Object::ModType::Created, objKey); auto data = createTestData({ createdObj, util::createObject(Object::ModType::Modified), }); auto const succ = util::createSuccessor(); data.successors = {succ, succ, succ}; EXPECT_CALL(*backend_, writeSuccessor(auto{succ.bookBase}, kSeq, auto{succ.firstBook})) .Times(data.successors->size()); EXPECT_CALL( *backend_, writeSuccessor(auto{createdObj.predecessor}, kSeq, auto{createdObj.keyRaw}) ); EXPECT_CALL( *backend_, writeSuccessor(auto{createdObj.keyRaw}, kSeq, auto{createdObj.successor}) ); ext_.onLedgerData(data); } TEST_F(SuccessorExtTests, OnLedgerDataWithDeletedObjectAndIncludedSuccessorsWithoutFirstBook) { using namespace etl::model; auto const objKey = "B00AA769C00726371689ED66A7CF57C2502F1BF4BDFF2ACADF67A2A7B5E8960D"; auto const deletedObj = util::createObject(Object::ModType::Deleted, objKey); auto data = createTestData({ deletedObj, util::createObject(Object::ModType::Modified), }); auto succ = util::createSuccessor(); succ.firstBook = {}; // empty will be transformed into kLastKey data.successors = {succ, succ}; EXPECT_CALL( *backend_, writeSuccessor(auto{succ.bookBase}, kSeq, uint256ToString(data::kLastKey)) ) .Times(data.successors->size()); EXPECT_CALL( *backend_, writeSuccessor(auto{deletedObj.predecessor}, kSeq, auto{deletedObj.successor}) ); ext_.onLedgerData(data); } TEST_F(SuccessorExtTests, OnInitialDataWithSuccessorsButNotBookDirAndNoSuccessorsForEdgeKeys) { using namespace etl::model; auto const firstKey = ripple::uint256("B00AA769C00726371689ED66A7CF57C2502F1BF4BDFF2ACADF67A2A7B5E8960C"); auto const secondKey = ripple::uint256("B00AA769C00726371689ED66A7CF57C2502F1BF4BDFF2ACADF67A2A7B5E8960E"); auto const data = createInitialTestData({firstKey, secondKey}); auto successorChain = std::queue(); successorChain.push(firstKey); successorChain.push(secondKey); [[maybe_unused]] testing::Sequence const inSeq; EXPECT_CALL(cache_, isFull()).WillOnce(testing::Return(true)); EXPECT_CALL(cache_, getSuccessor(testing::_, kSeq)) .Times(3) .InSequence(inSeq) .WillRepeatedly([&](auto&&, auto&&) -> std::optional { if (successorChain.empty()) return std::nullopt; auto v = successorChain.front(); successorChain.pop(); return data::LedgerObject{.key = v, .blob = {'0'}}; }); EXPECT_CALL( *backend_, writeSuccessor(uint256ToString(data::kFirstKey), kSeq, uint256ToString(firstKey)) ); EXPECT_CALL( *backend_, writeSuccessor(uint256ToString(secondKey), kSeq, uint256ToString(data::kLastKey)) ); // NOLINTBEGIN(bugprone-unchecked-optional-access) for (auto const& key : *data.edgeKeys) { EXPECT_CALL(cache_, getSuccessor(*ripple::uint256::fromVoidChecked(key), kSeq)) .InSequence(inSeq) .WillOnce(testing::Return(std::nullopt)); } // NOLINTEND(bugprone-unchecked-optional-access) ext_.onInitialData(data); } TEST_F(SuccessorExtTests, OnInitialDataWithSuccessorsButNotBookDirAndSuccessorsForEdgeKeys) { using namespace etl::model; auto const firstKey = ripple::uint256("B00AA769C00726371689ED66A7CF57C2502F1BF4BDFF2ACADF67A2A7B5E8960C"); auto const secondKey = ripple::uint256("B00AA769C00726371689ED66A7CF57C2502F1BF4BDFF2ACADF67A2A7B5E8960E"); auto const data = createInitialTestData({firstKey, secondKey}); auto successorChain = std::queue(); successorChain.push(firstKey); successorChain.push(secondKey); [[maybe_unused]] testing::Sequence const inSeq; EXPECT_CALL(cache_, isFull()).WillOnce(testing::Return(true)); EXPECT_CALL(cache_, getSuccessor(testing::_, kSeq)) .Times(3) .InSequence(inSeq) .WillRepeatedly([&](auto&&, auto&&) -> std::optional { if (successorChain.empty()) return std::nullopt; auto v = successorChain.front(); successorChain.pop(); return data::LedgerObject{.key = v, .blob = {'0'}}; }); EXPECT_CALL( *backend_, writeSuccessor(uint256ToString(data::kFirstKey), kSeq, uint256ToString(firstKey)) ); EXPECT_CALL( *backend_, writeSuccessor(uint256ToString(secondKey), kSeq, uint256ToString(data::kLastKey)) ); // NOLINTBEGIN(bugprone-unchecked-optional-access) for (auto const& key : *data.edgeKeys) { EXPECT_CALL(cache_, getSuccessor(*ripple::uint256::fromVoidChecked(key), kSeq)) .InSequence(inSeq) .WillOnce(testing::Return(data::LedgerObject{.key = firstKey, .blob = {}})); EXPECT_CALL(*backend_, writeSuccessor(auto{key}, kSeq, uint256ToString(firstKey))); } // NOLINTEND(bugprone-unchecked-optional-access) ext_.onInitialData(data); } TEST_F(SuccessorExtTests, OnInitialDataWithSuccessorsAndBookDirAndSuccessorsForEdgeKeys) { using namespace etl::model; auto const firstKey = ripple::uint256("B00AA769C00726371689ED66A7CF57C2502F1BF4BDFF2ACADF67A2A7B5E8960C"); auto const secondKey = ripple::uint256("B00AA769C00726371689ED66A7CF57C2502F1BF4BDFF2ACADF67A2A7B5E8960E"); auto const data = createInitialTestData({firstKey, secondKey}); auto successorChain = std::queue(); successorChain.push(firstKey); successorChain.push(secondKey); auto const bookBaseObj = util::createObjectWithBookBase(Object::ModType::Created); auto const bookBase = getBookBase(bookBaseObj.key); [[maybe_unused]] testing::Sequence const inSeq; EXPECT_CALL(cache_, isFull()).WillOnce(testing::Return(true)); EXPECT_CALL(cache_, getSuccessor(testing::_, kSeq)) .Times(3) .InSequence(inSeq) .WillRepeatedly([&](auto&&, auto&&) -> std::optional { if (successorChain.empty()) return std::nullopt; auto v = successorChain.front(); successorChain.pop(); return data::LedgerObject{.key = v, .blob = bookBaseObj.data}; }); EXPECT_CALL( *backend_, writeSuccessor(uint256ToString(data::kFirstKey), kSeq, uint256ToString(firstKey)) ); EXPECT_CALL( *backend_, writeSuccessor(uint256ToString(secondKey), kSeq, uint256ToString(data::kLastKey)) ); EXPECT_CALL(cache_, get(bookBase, kSeq)).WillRepeatedly(testing::Return(std::nullopt)); EXPECT_CALL(cache_, getSuccessor(bookBase, kSeq)) .WillRepeatedly( testing::Return(data::LedgerObject{.key = firstKey, .blob = data::Blob{'1'}}) ); EXPECT_CALL( *backend_, writeSuccessor(uint256ToString(bookBase), kSeq, testing::_) ); // Called once because firstKey returned repeatedly above // NOLINTBEGIN(bugprone-unchecked-optional-access) for (auto const& key : *data.edgeKeys) { EXPECT_CALL(cache_, getSuccessor(*ripple::uint256::fromVoidChecked(key), kSeq)) .InSequence(inSeq) .WillOnce(testing::Return(data::LedgerObject{.key = firstKey, .blob = {'1'}})); EXPECT_CALL(*backend_, writeSuccessor(auto{key}, kSeq, uint256ToString(firstKey))) .InSequence(inSeq); } // NOLINTEND(bugprone-unchecked-optional-access) ext_.onInitialData(data); } TEST_F(SuccessorExtTests, OnInitialObjectsWithEmptyLastKey) { using namespace etl::model; auto const lastKey = std::string{}; auto const data = std::vector{ util::createObject( Object::ModType::Created, "B00AA769C00726371689ED66A7CF57C2502F1BF4BDFF2ACADF67A2A7B5E8960E" ), util::createObject( Object::ModType::Created, "B00AA769C00726371689ED66A7CF57C2502F1BF4BDFF2ACADF67A2A7B5E8960F" ), util::createObject( Object::ModType::Created, "B00AA769C00726371689ED66A7CF57C2502F1BF4BDFF2ACADF67A2A7B5E89610" ), }; std::string lk = lastKey; for (auto const& obj : data) { if (not lk.empty()) EXPECT_CALL(*backend_, writeSuccessor(std::move(lk), kSeq, uint256ToString(obj.key))); lk = uint256ToString(obj.key); } ext_.onInitialObjects(kSeq, data, lastKey); } TEST_F(SuccessorExtTests, OnInitialObjectsWithNonEmptyLastKey) { using namespace etl::model; auto const lastKey = uint256ToString( ripple::uint256("B00AA769C00726371689ED66A7CF57C2502F1BF4BDFF2ACADF67A2A7B5E8960D") ); auto const data = std::vector{ util::createObject( Object::ModType::Created, "B00AA769C00726371689ED66A7CF57C2502F1BF4BDFF2ACADF67A2A7B5E8960E" ), util::createObject( Object::ModType::Created, "B00AA769C00726371689ED66A7CF57C2502F1BF4BDFF2ACADF67A2A7B5E8960F" ), util::createObject( Object::ModType::Created, "B00AA769C00726371689ED66A7CF57C2502F1BF4BDFF2ACADF67A2A7B5E89610" ), }; std::string lk = lastKey; for (auto const& obj : data) { EXPECT_CALL(*backend_, writeSuccessor(std::move(lk), kSeq, uint256ToString(obj.key))); lk = uint256ToString(obj.key); } ext_.onInitialObjects(kSeq, data, lastKey); } struct SuccessorExtAssertTests : common::util::WithMockAssert, SuccessorExtTests {}; TEST_F(SuccessorExtAssertTests, OnLedgerDataWithDeletedObjectAssertsIfGetDeletedIsNotInCache) { using namespace etl::model; auto const objKey = "B00AA769C00726371689ED66A7CF57C2502F1BF4BDFF2ACADF67A2A7B5E8960D"; auto const deletedObj = util::createObject(Object::ModType::Deleted, objKey); auto const data = createTestData({ deletedObj, util::createObject(Object::ModType::Modified), }); EXPECT_CALL(cache_, isFull()).WillRepeatedly(testing::Return(true)); EXPECT_CALL(cache_, latestLedgerSequence()).WillRepeatedly(testing::Return(kSeq)); EXPECT_CALL(cache_, getPredecessor(deletedObj.key, kSeq)) .WillRepeatedly(testing::Return(std::nullopt)); EXPECT_CALL(cache_, getSuccessor(deletedObj.key, kSeq)) .WillRepeatedly(testing::Return(std::nullopt)); EXPECT_CALL( *backend_, writeSuccessor(uint256ToString(data::kFirstKey), kSeq, uint256ToString(data::kLastKey)) ); EXPECT_CALL(cache_, getDeleted(deletedObj.key, kSeq - 1)) .WillRepeatedly(testing::Return(std::nullopt)); EXPECT_CLIO_ASSERT_FAIL({ ext_.onLedgerData(data); }); } TEST_F( SuccessorExtAssertTests, OnLedgerDataWithCreatedObjectButWithoutCachedPredecessorAndSuccessorWithBookBaseAndBookSuccessorNotInCache ) { using namespace etl::model; auto const objKey = "B00AA769C00726371689ED66A7CF57C2502F1BF4BDFF2ACADF67A2A7B5E8960D"; auto const createdObj = util::createObjectWithBookBase(Object::ModType::Created, objKey); auto const data = createTestData({ createdObj, util::createObject(Object::ModType::Modified), }); auto const bookBase = getBookBase(createdObj.key); EXPECT_CALL(cache_, isFull()).WillRepeatedly(testing::Return(true)); EXPECT_CALL(cache_, latestLedgerSequence()).WillRepeatedly(testing::Return(kSeq)); EXPECT_CALL(cache_, getPredecessor(createdObj.key, kSeq)) .WillRepeatedly(testing::Return(std::nullopt)); EXPECT_CALL(cache_, getSuccessor(createdObj.key, kSeq)) .WillRepeatedly(testing::Return(std::nullopt)); EXPECT_CALL( *backend_, writeSuccessor(uint256ToString(data::kFirstKey), kSeq, uint256ToString(createdObj.key)) ); EXPECT_CALL( *backend_, writeSuccessor(uint256ToString(createdObj.key), kSeq, uint256ToString(data::kLastKey)) ); EXPECT_CALL(cache_, get(createdObj.key, kSeq)).WillOnce(testing::Return(data::Blob{'0'})); EXPECT_CALL(cache_, getSuccessor(bookBase, kSeq)).WillOnce(testing::Return(std::nullopt)); EXPECT_CLIO_ASSERT_FAIL({ ext_.onLedgerData(data); }); } TEST_F(SuccessorExtAssertTests, OnInitialDataNotIsFull) { using namespace etl::model; auto const data = createTestData({ util::createObject(Object::ModType::Modified), util::createObject(Object::ModType::Created), }); EXPECT_CALL(cache_, isFull()).WillOnce(testing::Return(false)); EXPECT_CLIO_ASSERT_FAIL({ ext_.onInitialData(data); }); } TEST_F(SuccessorExtAssertTests, OnInitialDataIsFullButNoEdgeKeys) { using namespace etl::model; auto data = createTestData({}); EXPECT_CALL(cache_, isFull()).WillOnce(testing::Return(true)); EXPECT_CLIO_ASSERT_FAIL({ ext_.onInitialData(data); }); } TEST_F(SuccessorExtAssertTests, OnInitialDataIsFullWithEdgeKeysButHasObjects) { using namespace etl::model; auto const firstKey = ripple::uint256("B00AA769C00726371689ED66A7CF57C2502F1BF4BDFF2ACADF67A2A7B5E8960C"); auto const secondKey = ripple::uint256("B00AA769C00726371689ED66A7CF57C2502F1BF4BDFF2ACADF67A2A7B5E8960E"); auto data = createInitialTestData({firstKey, secondKey}); data.objects = {util::createObject()}; EXPECT_CALL(cache_, isFull()).WillOnce(testing::Return(true)); EXPECT_CLIO_ASSERT_FAIL({ ext_.onInitialData(data); }); }