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

0001 /*
0002  * SPDX-FileCopyrightText: 2015 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/akonadilivequeryhelpers.h"
0010 
0011 #include <functional>
0012 
0013 #include <KCalendarCore/Todo>
0014 
0015 #include "akonadi/akonadiserializer.h"
0016 
0017 #include "testlib/akonadifakedata.h"
0018 #include "testlib/gencollection.h"
0019 #include "testlib/gentodo.h"
0020 #include "testlib/testhelpers.h"
0021 
0022 using namespace Testlib;
0023 
0024 using namespace std::placeholders;
0025 
0026 static QString titleFromItem(const Akonadi::Item &item)
0027 {
0028     if (item.hasPayload<KCalendarCore::Todo::Ptr>()) {
0029         const auto todo = item.payload<KCalendarCore::Todo::Ptr>();
0030         return todo->summary();
0031     } else {
0032         return QString();
0033     }
0034 }
0035 
0036 class AkonadiLiveQueryHelpersTest : public QObject
0037 {
0038     Q_OBJECT
0039 
0040 private:
0041     Akonadi::LiveQueryHelpers::Ptr createHelpers(AkonadiFakeData &data)
0042     {
0043         return Akonadi::LiveQueryHelpers::Ptr(new Akonadi::LiveQueryHelpers(createSerializer(), createStorage(data)));
0044     }
0045 
0046     Akonadi::StorageInterface::Ptr createStorage(AkonadiFakeData &data)
0047     {
0048         return Akonadi::StorageInterface::Ptr(data.createStorage());
0049     }
0050 
0051     Akonadi::SerializerInterface::Ptr createSerializer()
0052     {
0053         return Akonadi::SerializerInterface::Ptr(new Akonadi::Serializer);
0054     }
0055 
0056 private slots:
0057     void shouldFetchAllCollections()
0058     {
0059         // GIVEN
0060         auto data = AkonadiFakeData();
0061         auto helpers = createHelpers(data);
0062 
0063         // Three top level collections (any content, tasks and notes)
0064         data.createCollection(GenCollection().withId(42).withRootAsParent().withName(QStringLiteral("42")));
0065         data.createCollection(GenCollection().withId(43).withRootAsParent().withName(QStringLiteral("43")).withTaskContent());
0066         data.createCollection(GenCollection().withId(44).withRootAsParent().withName(QStringLiteral("44")).withNoteContent());
0067 
0068         // Three children under each of the top level for each content type
0069         data.createCollection(GenCollection().withId(45).withParent(42).withName(QStringLiteral("45")));
0070         data.createCollection(GenCollection().withId(46).withParent(42).withName(QStringLiteral("46")).withTaskContent());
0071         data.createCollection(GenCollection().withId(47).withParent(42).withName(QStringLiteral("47")).withNoteContent());
0072         data.createCollection(GenCollection().withId(48).withParent(43).withName(QStringLiteral("48")));
0073         data.createCollection(GenCollection().withId(49).withParent(43).withName(QStringLiteral("49")).withTaskContent());
0074         data.createCollection(GenCollection().withId(50).withParent(43).withName(QStringLiteral("50")).withNoteContent());
0075         data.createCollection(GenCollection().withId(51).withParent(44).withName(QStringLiteral("51")));
0076         data.createCollection(GenCollection().withId(52).withParent(44).withName(QStringLiteral("52")).withTaskContent());
0077         data.createCollection(GenCollection().withId(53).withParent(44).withName(QStringLiteral("53")).withNoteContent());
0078 
0079         // The list which will be filled by the fetch function
0080         auto collections = Akonadi::Collection::List();
0081         auto add = [&collections] (const Akonadi::Collection &collection) {
0082             collections.append(collection);
0083         };
0084 
0085         // WHEN
0086         auto fetch = helpers->fetchAllCollections(nullptr);
0087         fetch(add);
0088         TestHelpers::waitForEmptyJobQueue();
0089 
0090         auto result = QStringList();
0091         std::transform(collections.constBegin(), collections.constEnd(),
0092                        std::back_inserter(result),
0093                        std::bind(&Akonadi::Collection::displayName, _1));
0094         result.sort();
0095 
0096         // THEN
0097         auto expected = QStringList();
0098         expected << QStringLiteral("43") << QStringLiteral("46") << QStringLiteral("49") << QStringLiteral("52");
0099 
0100         expected.sort();
0101         QCOMPARE(result, expected);
0102 
0103         // WHEN (should not crash when the helpers object is deleted)
0104         helpers.clear();
0105         collections.clear();
0106         fetch(add);
0107         TestHelpers::waitForEmptyJobQueue();
0108 
0109         // THEN
0110         result.clear();
0111         std::transform(collections.constBegin(), collections.constEnd(),
0112                        std::back_inserter(result),
0113                        std::bind(&Akonadi::Collection::displayName, _1));
0114         result.sort();
0115         QCOMPARE(result, expected);
0116     }
0117 
0118     void shouldFetchCollectionsForRoot_data()
0119     {
0120         QTest::addColumn<Akonadi::Collection>("root");
0121 
0122         QTest::newRow("all collections from root") << Akonadi::Collection::root();
0123         QTest::newRow("all collections from 'all branch'") << Akonadi::Collection(42);
0124         QTest::newRow("all collections from 'task branch'") << Akonadi::Collection(43);
0125         QTest::newRow("all collections from 'note branch'") << Akonadi::Collection(44);
0126     }
0127 
0128     void shouldFetchCollectionsForRoot()
0129     {
0130         // GIVEN
0131         auto data = AkonadiFakeData();
0132         auto helpers = createHelpers(data);
0133 
0134         // Three top level collections (any content, tasks and notes)
0135         data.createCollection(GenCollection().withId(42).withRootAsParent().withName(QStringLiteral("42")));
0136         data.createCollection(GenCollection().withId(43).withRootAsParent().withName(QStringLiteral("43")).withTaskContent());
0137         data.createCollection(GenCollection().withId(44).withRootAsParent().withName(QStringLiteral("44")).withNoteContent());
0138 
0139         // Three children under each of the top level for each content type
0140         data.createCollection(GenCollection().withId(45).withParent(42).withName(QStringLiteral("45")));
0141         data.createCollection(GenCollection().withId(46).withParent(42).withName(QStringLiteral("46")).withTaskContent());
0142         data.createCollection(GenCollection().withId(47).withParent(42).withName(QStringLiteral("47")).withNoteContent());
0143         data.createCollection(GenCollection().withId(48).withParent(43).withName(QStringLiteral("48")));
0144         data.createCollection(GenCollection().withId(49).withParent(43).withName(QStringLiteral("49")).withTaskContent());
0145         data.createCollection(GenCollection().withId(50).withParent(43).withName(QStringLiteral("50")).withNoteContent());
0146         data.createCollection(GenCollection().withId(51).withParent(44).withName(QStringLiteral("51")));
0147         data.createCollection(GenCollection().withId(52).withParent(44).withName(QStringLiteral("52")).withTaskContent());
0148         data.createCollection(GenCollection().withId(53).withParent(44).withName(QStringLiteral("53")).withNoteContent());
0149 
0150         // The list which will be filled by the fetch function
0151         auto collections = Akonadi::Collection::List();
0152         auto add = [&collections] (const Akonadi::Collection &collection) {
0153             collections.append(collection);
0154         };
0155 
0156         // WHEN
0157         QFETCH(Akonadi::Collection, root);
0158         auto fetch = helpers->fetchCollections(root, nullptr);
0159         fetch(add);
0160         TestHelpers::waitForEmptyJobQueue();
0161 
0162         auto result = QStringList();
0163         std::transform(collections.constBegin(), collections.constEnd(),
0164                        std::back_inserter(result),
0165                        std::bind(&Akonadi::Collection::displayName, _1));
0166         result.sort();
0167 
0168         // THEN
0169         auto expected = QStringList();
0170 
0171         if (root == Akonadi::Collection::root()) {
0172             expected << QStringLiteral("42") << QStringLiteral("43") << QStringLiteral("44");
0173         } else {
0174             const qint64 baseId = root.id() == 42 ? 45
0175                                 : root.id() == 43 ? 48
0176                                 : root.id() == 44 ? 51
0177                                 : -1;
0178             QVERIFY(baseId > 0);
0179 
0180             expected << QString::number(baseId + 1);
0181         }
0182 
0183         expected.sort();
0184         QCOMPARE(result, expected);
0185 
0186         // WHEN (should not crash when the helpers object is deleted)
0187         helpers.clear();
0188         collections.clear();
0189         fetch(add);
0190         TestHelpers::waitForEmptyJobQueue();
0191 
0192         // THEN
0193         result.clear();
0194         std::transform(collections.constBegin(), collections.constEnd(),
0195                        std::back_inserter(result),
0196                        std::bind(&Akonadi::Collection::displayName, _1));
0197         result.sort();
0198         QCOMPARE(result, expected);
0199     }
0200 
0201     void shouldFetchItems()
0202     {
0203         // GIVEN
0204         auto data = AkonadiFakeData();
0205         auto helpers = createHelpers(data);
0206 
0207         // Two top level collections, one with no particular content, one with tasks
0208         data.createCollection(GenCollection().withId(42).withRootAsParent().withName(QStringLiteral("42")));
0209         data.createCollection(GenCollection().withId(43).withRootAsParent().withName(QStringLiteral("43")).withTaskContent());
0210 
0211         // One note collection as child of the first one
0212         data.createCollection(GenCollection().withId(44).withParent(42).withName(QStringLiteral("44")).withNoteContent());
0213 
0214         // One task collection as child of the note collection
0215         data.createCollection(GenCollection().withId(45).withParent(44).withName(QStringLiteral("45")).withTaskContent());
0216 
0217         // One task in the first collection
0218         data.createItem(GenTodo().withId(42).withParent(42).withTitle(QStringLiteral("42")));
0219 
0220         // Two tasks in all the other collections
0221         data.createItem(GenTodo().withId(43).withParent(43).withTitle(QStringLiteral("43")));
0222         data.createItem(GenTodo().withId(44).withParent(43).withTitle(QStringLiteral("44")));
0223         data.createItem(GenTodo().withId(45).withParent(44).withTitle(QStringLiteral("45")));
0224         data.createItem(GenTodo().withId(46).withParent(44).withTitle(QStringLiteral("46")));
0225         data.createItem(GenTodo().withId(47).withParent(45).withTitle(QStringLiteral("47")));
0226         data.createItem(GenTodo().withId(48).withParent(45).withTitle(QStringLiteral("48")));
0227 
0228         // The list which will be filled by the fetch function
0229         auto items = Akonadi::Item::List();
0230         auto add = [&items] (const Akonadi::Item &item) {
0231             items.append(item);
0232         };
0233 
0234         // WHEN
0235         auto fetch = helpers->fetchItems(nullptr);
0236         fetch(add);
0237         TestHelpers::waitForEmptyJobQueue();
0238 
0239         auto result = QStringList();
0240         std::transform(items.constBegin(), items.constEnd(),
0241                        std::back_inserter(result),
0242                        titleFromItem);
0243         result.sort();
0244 
0245         // THEN
0246         auto expected = QStringList();
0247         expected << QStringLiteral("43") << QStringLiteral("44") << QStringLiteral("47") << QStringLiteral("48");
0248         expected.sort();
0249         QCOMPARE(result, expected);
0250 
0251         // WHEN (should not crash when the helpers object is deleted)
0252         helpers.clear();
0253         items.clear();
0254         fetch(add);
0255         TestHelpers::waitForEmptyJobQueue();
0256 
0257         // THEN
0258         result.clear();
0259         std::transform(items.constBegin(), items.constEnd(),
0260                        std::back_inserter(result),
0261                        titleFromItem);
0262         result.sort();
0263         QCOMPARE(result, expected);
0264     }
0265 
0266     void shouldFetchItemsByCollection_data()
0267     {
0268         QTest::addColumn<Akonadi::Collection>("collection");
0269 
0270         QTest::newRow("first collection") << Akonadi::Collection(42);
0271         QTest::newRow("second collection") << Akonadi::Collection(43);
0272     }
0273 
0274     void shouldFetchItemsByCollection()
0275     {
0276         // GIVEN
0277         auto data = AkonadiFakeData();
0278         auto helpers = createHelpers(data);
0279 
0280         // Two top level collections with tasks
0281         data.createCollection(GenCollection().withId(42).withRootAsParent().withName(QStringLiteral("42")).withTaskContent());
0282         data.createCollection(GenCollection().withId(43).withRootAsParent().withName(QStringLiteral("43")).withTaskContent());
0283 
0284         // Two items in each collection
0285         data.createItem(GenTodo().withId(42).withParent(42).withTitle(QStringLiteral("42")));
0286         data.createItem(GenTodo().withId(43).withParent(42).withTitle(QStringLiteral("43")));
0287         data.createItem(GenTodo().withId(44).withParent(43).withTitle(QStringLiteral("44")));
0288         data.createItem(GenTodo().withId(45).withParent(43).withTitle(QStringLiteral("45")));
0289 
0290         // The list which will be filled by the fetch function
0291         auto items = Akonadi::Item::List();
0292         auto add = [&items] (const Akonadi::Item &item) {
0293             items.append(item);
0294         };
0295 
0296         // WHEN
0297         QFETCH(Akonadi::Collection, collection);
0298         auto fetch = helpers->fetchItems(collection, nullptr);
0299         fetch(add);
0300         TestHelpers::waitForEmptyJobQueue();
0301 
0302         auto result = QStringList();
0303         std::transform(items.constBegin(), items.constEnd(),
0304                        std::back_inserter(result),
0305                        titleFromItem);
0306         result.sort();
0307 
0308         // THEN
0309         auto expected = QStringList();
0310 
0311         switch (collection.id()) {
0312         case 42:
0313             expected << QStringLiteral("42") << QStringLiteral("43");
0314             break;
0315         case 43:
0316             expected << QStringLiteral("44") << QStringLiteral("45");
0317             break;
0318         }
0319         QVERIFY(!expected.isEmpty());
0320 
0321         expected.sort();
0322         QCOMPARE(result, expected);
0323 
0324         // WHEN (should not crash when the helpers object is deleted)
0325         helpers.clear();
0326         items.clear();
0327         fetch(add);
0328         TestHelpers::waitForEmptyJobQueue();
0329 
0330         // THEN
0331         result.clear();
0332         std::transform(items.constBegin(), items.constEnd(),
0333                        std::back_inserter(result),
0334                        titleFromItem);
0335         result.sort();
0336         QCOMPARE(result, expected);
0337     }
0338 
0339     void shouldFetchItemsForContext_data()
0340     {
0341         QTest::addColumn<Domain::Context::Ptr>("context");
0342 
0343         auto context1 = Domain::Context::Ptr::create();
0344         context1->setProperty("todoUid", "ctx-42");
0345         auto context2 = Domain::Context::Ptr::create();
0346         context2->setProperty("todoUid", "ctx-43");
0347 
0348         QTest::newRow("first") << context1;
0349         QTest::newRow("second") << context2;
0350     }
0351 
0352     void shouldFetchItemsForContext()
0353     {
0354         // GIVEN
0355         auto data = AkonadiFakeData();
0356         auto helpers = createHelpers(data);
0357 
0358         // Two top level collections with tasks
0359         data.createCollection(GenCollection().withId(42).withRootAsParent().withName(QStringLiteral("42")).withTaskContent());
0360         data.createCollection(GenCollection().withId(43).withRootAsParent().withName(QStringLiteral("43")).withTaskContent());
0361 
0362         // Two contexts
0363         data.createItem(GenTodo().withId(142).withUid("ctx-42").asContext());
0364         data.createItem(GenTodo().withId(143).withUid("ctx-43").asContext());
0365 
0366         // Four items in each collection, one with no context, one with the first context,
0367         // one with the second context, last one with both contexts
0368         data.createItem(GenTodo().withId(42).withParent(42).withContexts({}).withTitle(QStringLiteral("42")));
0369         data.createItem(GenTodo().withId(43).withParent(42).withContexts({"ctx-42"}).withTitle(QStringLiteral("43")));
0370         data.createItem(GenTodo().withId(44).withParent(42).withContexts({"ctx-43"}).withTitle(QStringLiteral("44")));
0371         data.createItem(GenTodo().withId(45).withParent(42).withContexts({"ctx-42", "ctx-43"}).withTitle(QStringLiteral("45")));
0372         data.createItem(GenTodo().withId(46).withParent(43).withContexts({}).withTitle(QStringLiteral("46")));
0373         data.createItem(GenTodo().withId(47).withParent(43).withContexts({"ctx-42"}).withTitle(QStringLiteral("47")));
0374         data.createItem(GenTodo().withId(48).withParent(43).withContexts({"ctx-43"}).withTitle(QStringLiteral("48")));
0375         data.createItem(GenTodo().withId(49).withParent(43).withContexts({"ctx-42", "ctx-43"}).withTitle(QStringLiteral("49")));
0376 
0377         // The list which will be filled by the fetch function
0378         auto items = Akonadi::Item::List();
0379         auto add = [&items] (const Akonadi::Item &item) {
0380             items.append(item);
0381         };
0382 
0383         // WHEN
0384         QFETCH(Domain::Context::Ptr, context);
0385         auto fetch = helpers->fetchItemsForContext(context, nullptr);
0386         fetch(add);
0387         TestHelpers::waitForEmptyJobQueue();
0388 
0389         auto result = QStringList();
0390         std::transform(items.constBegin(), items.constEnd(),
0391                        std::back_inserter(result),
0392                        titleFromItem);
0393         result.sort();
0394 
0395         // THEN
0396         auto expected = QStringList();
0397 
0398         if (context->property("todoUid") == "ctx-42")
0399             expected << QStringLiteral("43") << QStringLiteral("45") << QStringLiteral("47") << QStringLiteral("49");
0400         else if (context->property("todoUid") == "ctx-43")
0401             expected << QStringLiteral("44") << QStringLiteral("45") << QStringLiteral("48") << QStringLiteral("49");
0402         QVERIFY(!expected.isEmpty());
0403 
0404         expected.sort();
0405         QCOMPARE(result, expected);
0406 
0407         // WHEN (should not crash when the helpers object is deleted)
0408         helpers.clear();
0409         items.clear();
0410         fetch(add);
0411         TestHelpers::waitForEmptyJobQueue();
0412 
0413         // THEN
0414         result.clear();
0415         std::transform(items.constBegin(), items.constEnd(),
0416                        std::back_inserter(result),
0417                        titleFromItem);
0418         result.sort();
0419         QCOMPARE(result, expected);
0420     }
0421 
0422     void shouldFetchSiblings_data()
0423     {
0424         QTest::addColumn<Akonadi::Item>("item");
0425 
0426         QTest::newRow("item in first collection") << Akonadi::Item(43);
0427         QTest::newRow("item in second collection") << Akonadi::Item(48);
0428     }
0429 
0430     void shouldFetchSiblings()
0431     {
0432         // GIVEN
0433         auto data = AkonadiFakeData();
0434         auto helpers = createHelpers(data);
0435 
0436         // Two top level collections (one with notes, one with tasks)
0437         data.createCollection(GenCollection().withId(42).withRootAsParent().withName(QStringLiteral("42")).withTaskContent());
0438         data.createCollection(GenCollection().withId(43).withRootAsParent().withName(QStringLiteral("43")).withNoteContent());
0439 
0440         // Four items in each collection
0441         data.createItem(GenTodo().withId(42).withParent(42).withTitle(QStringLiteral("42")));
0442         data.createItem(GenTodo().withId(43).withParent(42).withTitle(QStringLiteral("43")));
0443         data.createItem(GenTodo().withId(44).withParent(42).withTitle(QStringLiteral("44")));
0444         data.createItem(GenTodo().withId(45).withParent(42).withTitle(QStringLiteral("45")));
0445         data.createItem(GenTodo().withId(46).withParent(43).withTitle(QStringLiteral("46")));
0446         data.createItem(GenTodo().withId(47).withParent(43).withTitle(QStringLiteral("47")));
0447         data.createItem(GenTodo().withId(48).withParent(43).withTitle(QStringLiteral("48")));
0448         data.createItem(GenTodo().withId(49).withParent(43).withTitle(QStringLiteral("49")));
0449 
0450         // The list which will be filled by the fetch function
0451         auto items = Akonadi::Item::List();
0452         auto add = [&items] (const Akonadi::Item &item) {
0453             items.append(item);
0454         };
0455 
0456         // WHEN
0457         QFETCH(Akonadi::Item, item);
0458         auto fetch = helpers->fetchSiblings(item, nullptr);
0459         fetch(add);
0460         TestHelpers::waitForEmptyJobQueue();
0461 
0462         auto result = QStringList();
0463         std::transform(items.constBegin(), items.constEnd(),
0464                        std::back_inserter(result),
0465                        titleFromItem);
0466         result.sort();
0467 
0468         // THEN
0469         auto expected = QStringList();
0470 
0471         switch (item.id()) {
0472         case 43:
0473             expected << QStringLiteral("42") << QStringLiteral("43") << QStringLiteral("44") << QStringLiteral("45");
0474             break;
0475         case 48:
0476             expected << QStringLiteral("46") << QStringLiteral("47") << QStringLiteral("48") << QStringLiteral("49");
0477             break;
0478         }
0479         QVERIFY(!expected.isEmpty());
0480 
0481         expected.sort();
0482         QCOMPARE(result, expected);
0483 
0484         // WHEN (should not crash when the helpers object is deleted)
0485         helpers.clear();
0486         items.clear();
0487         fetch(add);
0488         TestHelpers::waitForEmptyJobQueue();
0489 
0490         // THEN
0491         result.clear();
0492         std::transform(items.constBegin(), items.constEnd(),
0493                        std::back_inserter(result),
0494                        titleFromItem);
0495         result.sort();
0496         QCOMPARE(result, expected);
0497     }
0498 };
0499 
0500 ZANSHIN_TEST_MAIN(AkonadiLiveQueryHelpersTest)
0501 
0502 #include "akonadilivequeryhelperstest.moc"