File indexing completed on 2025-01-05 04:59:50

0001 /*
0002  * SPDX-FileCopyrightText: 2017 Kevin Ottens <ervin@kde.org>
0003  SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0004 */
0005 
0006 
0007 #include <testlib/qtest_zanshin.h>
0008 
0009 #include "akonadi/akonadicachingstorage.h"
0010 #include "akonadi/akonadiserializer.h"
0011 
0012 #include "akonadi/akonadicollectionfetchjobinterface.h"
0013 #include "akonadi/akonadiitemfetchjobinterface.h"
0014 
0015 #include "testlib/akonadifakedata.h"
0016 #include "testlib/gencollection.h"
0017 #include "testlib/gentodo.h"
0018 #include "testlib/testhelpers.h"
0019 
0020 Q_DECLARE_METATYPE(Akonadi::StorageInterface::FetchDepth)
0021 
0022 using namespace Testlib;
0023 
0024 class AkonadiCachingStorageTest : public QObject
0025 {
0026     Q_OBJECT
0027 public:
0028     explicit AkonadiCachingStorageTest(QObject *parent = nullptr)
0029         : QObject(parent)
0030     {
0031         qRegisterMetaType<Akonadi::Collection>();
0032         qRegisterMetaType<Akonadi::Item>();
0033     }
0034 
0035     QStringList onlyWithSuffix(const QStringList &list, const QString &suffix)
0036     {
0037         return onlyWithSuffixes(list, {suffix});
0038     }
0039 
0040     QStringList onlyWithSuffixes(const QStringList &list, const QStringList &suffixes)
0041     {
0042         auto res = QStringList();
0043         std::copy_if(list.cbegin(), list.cend(),
0044                      std::back_inserter(res),
0045                      [suffixes](const QString &entry) {
0046                         for (const auto &suffix : suffixes) {
0047                             if (entry.endsWith(suffix))
0048                                 return true;
0049                         }
0050                         return false;
0051                      });
0052         return res;
0053     }
0054 
0055 private slots:
0056     void shouldCacheAllCollectionsPerFetchType_data()
0057     {
0058         QTest::addColumn<Akonadi::Collection>("rootCollection");
0059         QTest::addColumn<Akonadi::StorageInterface::FetchDepth>("fetchDepth");
0060         QTest::addColumn<QStringList>("expectedFetchNames");
0061         QTest::addColumn<QStringList>("expectedCachedNames");
0062 
0063         const auto allCollections = QStringList() << "42Task" << "43Task" << "44Note" << "45Stuff"
0064                                                   << "46Note" << "47Task" << "48Note" << "49Stuff"
0065                                                   << "50Stuff" << "51Task" << "52Note" << "53Stuff"
0066                                                   << "54Task" << "55Task" << "56Task"
0067                                                   << "57Note" << "58Note" << "59Note"
0068                                                   << "60Stuff" << "61Stuff" << "62Stuff";
0069 
0070         const auto taskCollections = QStringList() << "42Task" << "43Task"
0071                                                    << "46Note" << "47Task"
0072                                                    << "50Stuff" << "51Task"
0073                                                    << "54Task" << "55Task" << "56Task";
0074 
0075         QTest::newRow("rootRecursiveTask") << Akonadi::Collection::root() << Akonadi::StorageInterface::Recursive
0076                                            << onlyWithSuffix(taskCollections, "Task") << taskCollections;
0077 
0078         QTest::newRow("54RecursiveTask") << Akonadi::Collection(54) << Akonadi::StorageInterface::Recursive
0079                                          << (QStringList() << "55Task" << "56Task") << taskCollections;
0080 
0081         QTest::newRow("54FirstLevelTask") << Akonadi::Collection(54) << Akonadi::StorageInterface::FirstLevel
0082                                           << (QStringList() << "55Task") << taskCollections;
0083 
0084         QTest::newRow("54BaseTask") << Akonadi::Collection(54) << Akonadi::StorageInterface::Base
0085                                     << (QStringList() << "54Task") << taskCollections;
0086     }
0087 
0088     void shouldCacheAllCollectionsPerFetchType()
0089     {
0090         // GIVEN
0091         AkonadiFakeData data;
0092 
0093         data.createCollection(GenCollection().withId(42).withName(QStringLiteral("42Task")).withRootAsParent().withTaskContent());
0094         data.createCollection(GenCollection().withId(43).withName(QStringLiteral("43Task")).withParent(42).withTaskContent());
0095         data.createCollection(GenCollection().withId(44).withName(QStringLiteral("44Note")).withParent(42).withNoteContent());
0096         data.createCollection(GenCollection().withId(45).withName(QStringLiteral("45Stuff")).withParent(42));
0097 
0098         data.createCollection(GenCollection().withId(46).withName(QStringLiteral("46Note")).withRootAsParent().withNoteContent());
0099         data.createCollection(GenCollection().withId(47).withName(QStringLiteral("47Task")).withParent(46).withTaskContent());
0100         data.createCollection(GenCollection().withId(48).withName(QStringLiteral("48Note")).withParent(46).withNoteContent());
0101         data.createCollection(GenCollection().withId(49).withName(QStringLiteral("49Stuff")).withParent(46));
0102 
0103         data.createCollection(GenCollection().withId(50).withName(QStringLiteral("50Stuff")).withRootAsParent());
0104         data.createCollection(GenCollection().withId(51).withName(QStringLiteral("51Task")).withParent(50).withTaskContent());
0105         data.createCollection(GenCollection().withId(52).withName(QStringLiteral("52Note")).withParent(50).withNoteContent());
0106         data.createCollection(GenCollection().withId(53).withName(QStringLiteral("53Stuff")).withParent(50));
0107 
0108         data.createCollection(GenCollection().withId(54).withName(QStringLiteral("54Task")).withRootAsParent().withTaskContent());
0109         data.createCollection(GenCollection().withId(55).withName(QStringLiteral("55Task")).withParent(54).withTaskContent());
0110         data.createCollection(GenCollection().withId(56).withName(QStringLiteral("56Task")).withParent(55).withTaskContent());
0111 
0112         data.createCollection(GenCollection().withId(57).withName(QStringLiteral("57Note")).withRootAsParent().withNoteContent());
0113         data.createCollection(GenCollection().withId(58).withName(QStringLiteral("58Note")).withParent(57).withNoteContent());
0114         data.createCollection(GenCollection().withId(59).withName(QStringLiteral("59Note")).withParent(58).withNoteContent());
0115 
0116         data.createCollection(GenCollection().withId(60).withName(QStringLiteral("60Stuff")).withRootAsParent());
0117         data.createCollection(GenCollection().withId(61).withName(QStringLiteral("61Stuff")).withParent(60));
0118         data.createCollection(GenCollection().withId(62).withName(QStringLiteral("62Stuff")).withParent(61));
0119 
0120         auto cache = Akonadi::Cache::Ptr::create(Akonadi::SerializerInterface::Ptr(new Akonadi::Serializer),
0121                                                  Akonadi::MonitorInterface::Ptr(data.createMonitor()));
0122         Akonadi::CachingStorage storage(cache, Akonadi::StorageInterface::Ptr(data.createStorage()));
0123 
0124         // WHEN
0125         QFETCH(Akonadi::Collection, rootCollection);
0126         QFETCH(Akonadi::StorageInterface::FetchDepth, fetchDepth);
0127         auto job = storage.fetchCollections(rootCollection, fetchDepth, nullptr);
0128         QVERIFY2(job->kjob()->exec(), qPrintable(job->kjob()->errorString()));
0129 
0130         // THEN
0131         const auto toCollectionNames = [](const Akonadi::Collection::List &collections) {
0132             auto res = QStringList();
0133             res.reserve(collections.size());
0134             std::transform(collections.cbegin(), collections.cend(),
0135                            std::back_inserter(res),
0136                            std::mem_fn(&Akonadi::Collection::name));
0137             res.sort();
0138             return res;
0139         };
0140 
0141         QFETCH(QStringList, expectedFetchNames);
0142         QFETCH(QStringList, expectedCachedNames);
0143 
0144         {
0145             const auto collectionFetchNames = toCollectionNames(job->collections());
0146             QCOMPARE(collectionFetchNames, expectedFetchNames);
0147 
0148             const auto collections = cache->allCollections();
0149             const auto collectionCachedNames = toCollectionNames(collections);
0150             QCOMPARE(collectionCachedNames, expectedCachedNames);
0151         }
0152 
0153         // WHEN (second time shouldn't hit the original storage)
0154         data.storageBehavior().setFetchCollectionsBehavior(rootCollection.id(), AkonadiFakeStorageBehavior::EmptyFetch);
0155         data.storageBehavior().setFetchCollectionsErrorCode(rootCollection.id(), 128);
0156         job = storage.fetchCollections(rootCollection, fetchDepth, nullptr);
0157         QVERIFY2(job->kjob()->exec(), qPrintable(job->kjob()->errorString()));
0158 
0159         {
0160             const auto collectionFetchNames = toCollectionNames(job->collections());
0161             QCOMPARE(collectionFetchNames, expectedFetchNames);
0162 
0163             const auto collections = cache->allCollections();
0164             const auto collectionCachedNames = toCollectionNames(collections);
0165             QCOMPARE(collectionCachedNames, expectedCachedNames);
0166         }
0167     }
0168 
0169     void shouldCacheAllItemsPerCollection()
0170     {
0171         // GIVEN
0172         AkonadiFakeData data;
0173 
0174         data.createCollection(GenCollection().withId(42).withName(QStringLiteral("42Col")).withRootAsParent().withTaskContent());
0175         data.createCollection(GenCollection().withId(43).withName(QStringLiteral("43Col")).withRootAsParent().withTaskContent());
0176 
0177         data.createItem(GenTodo().withId(42).withTitle(QStringLiteral("42Task")).withParent(42));
0178         data.createItem(GenTodo().withId(45).withTitle(QStringLiteral("45Task")).withParent(42));
0179         data.createItem(GenTodo().withId(52).withTitle(QStringLiteral("52Task")).withParent(42));
0180 
0181         data.createItem(GenTodo().withId(44).withTitle(QStringLiteral("44Task")).withParent(43));
0182         data.createItem(GenTodo().withId(48).withTitle(QStringLiteral("48Task")).withParent(43));
0183         data.createItem(GenTodo().withId(50).withTitle(QStringLiteral("50Task")).withParent(43));
0184 
0185         auto cache = Akonadi::Cache::Ptr::create(Akonadi::SerializerInterface::Ptr(new Akonadi::Serializer),
0186                                                  Akonadi::MonitorInterface::Ptr(data.createMonitor()));
0187         Akonadi::CachingStorage storage(cache, Akonadi::StorageInterface::Ptr(data.createStorage()));
0188 
0189         // WHEN
0190         auto job = storage.fetchItems(Akonadi::Collection(42), nullptr);
0191         QVERIFY2(job->kjob()->exec(), qPrintable(job->kjob()->errorString()));
0192 
0193         // THEN
0194         const auto toItemIds = [](const Akonadi::Item::List &items) {
0195             auto res = QList<Akonadi::Item::Id>();
0196             res.reserve(items.size());
0197             std::transform(items.cbegin(), items.cend(),
0198                            std::back_inserter(res),
0199                            std::mem_fn(&Akonadi::Item::id));
0200             std::sort(res.begin(), res.end());
0201             return res;
0202         };
0203 
0204         auto expectedIds = QList<Akonadi::Item::Id>() << 42 << 45 << 52;
0205         {
0206             const auto itemFetchIds = toItemIds(job->items());
0207             QCOMPARE(itemFetchIds, expectedIds);
0208 
0209             const auto items = cache->items(Akonadi::Collection(42));
0210             const auto itemCachedIds = toItemIds(items);
0211             QCOMPARE(itemCachedIds, expectedIds);
0212         }
0213 
0214         // WHEN (second time shouldn't hit the original storage)
0215         data.storageBehavior().setFetchItemsBehavior(42, AkonadiFakeStorageBehavior::EmptyFetch);
0216         data.storageBehavior().setFetchItemsErrorCode(42, 128);
0217         job = storage.fetchItems(Akonadi::Collection(42), nullptr);
0218         QVERIFY2(job->kjob()->exec(), qPrintable(job->kjob()->errorString()));
0219 
0220         {
0221             const auto itemFetchIds = toItemIds(job->items());
0222             QCOMPARE(itemFetchIds, expectedIds);
0223 
0224             const auto items = cache->items(Akonadi::Collection(42));
0225             const auto itemCachedIds = toItemIds(items);
0226             QCOMPARE(itemCachedIds, expectedIds);
0227         }
0228     }
0229 
0230     void shouldCacheSingleItems()
0231     {
0232         // GIVEN
0233         AkonadiFakeData data;
0234 
0235         data.createCollection(GenCollection().withId(42).withName(QStringLiteral("42Col")).withRootAsParent().withTaskContent());
0236         data.createCollection(GenCollection().withId(43).withName(QStringLiteral("43Col")).withRootAsParent().withTaskContent());
0237 
0238         data.createItem(GenTodo().withId(42).withTitle(QStringLiteral("42Task")).withParent(42));
0239         data.createItem(GenTodo().withId(45).withTitle(QStringLiteral("45Task")).withParent(42));
0240         data.createItem(GenTodo().withId(52).withTitle(QStringLiteral("52Task")).withParent(42));
0241 
0242         data.createItem(GenTodo().withId(44).withTitle(QStringLiteral("44Task")).withParent(43));
0243         data.createItem(GenTodo().withId(48).withTitle(QStringLiteral("48Task")).withParent(43));
0244         data.createItem(GenTodo().withId(50).withTitle(QStringLiteral("50Task")).withParent(43));
0245 
0246         auto cache = Akonadi::Cache::Ptr::create(Akonadi::SerializerInterface::Ptr(new Akonadi::Serializer),
0247                                                  Akonadi::MonitorInterface::Ptr(data.createMonitor()));
0248         Akonadi::CachingStorage storage(cache, Akonadi::StorageInterface::Ptr(data.createStorage()));
0249 
0250         // WHEN
0251         auto job = storage.fetchItem(Akonadi::Item(44), nullptr);
0252         QVERIFY2(job->kjob()->exec(), qPrintable(job->kjob()->errorString()));
0253 
0254         // THEN
0255         const auto toItemIds = [](const Akonadi::Item::List &items) {
0256             auto res = QList<Akonadi::Item::Id>();
0257             res.reserve(items.size());
0258             std::transform(items.cbegin(), items.cend(),
0259                            std::back_inserter(res),
0260                            std::mem_fn(&Akonadi::Item::id));
0261             std::sort(res.begin(), res.end());
0262             return res;
0263         };
0264 
0265         auto expectedIds = QList<Akonadi::Item::Id>() << 44;
0266         {
0267             const auto itemFetchIds = toItemIds(job->items());
0268             QCOMPARE(itemFetchIds, expectedIds);
0269             QVERIFY(!cache->item(44).isValid());
0270         }
0271 
0272         // WHEN (if collection is populated, shouldn't hit the original storage)
0273         job = storage.fetchItems(Akonadi::Collection(43), nullptr);
0274         QVERIFY2(job->kjob()->exec(), qPrintable(job->kjob()->errorString()));
0275 
0276         data.storageBehavior().setFetchItemBehavior(44, AkonadiFakeStorageBehavior::EmptyFetch);
0277         data.storageBehavior().setFetchItemErrorCode(44, 128);
0278         job = storage.fetchItem(Akonadi::Item(44), nullptr);
0279         QVERIFY2(job->kjob()->exec(), qPrintable(job->kjob()->errorString()));
0280 
0281         {
0282             const auto itemFetchIds = toItemIds(job->items());
0283             QCOMPARE(itemFetchIds, expectedIds);
0284             QVERIFY(cache->item(44).isValid());
0285         }
0286     }
0287 };
0288 
0289 ZANSHIN_TEST_MAIN(AkonadiCachingStorageTest)
0290 
0291 #include "akonadicachingstoragetest.moc"