File indexing completed on 2024-12-22 05:00:55

0001 /*
0002    SPDX-FileCopyrightText: 2018 Daniel Vrátil <dvratil@kde.org>
0003 
0004    SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "../unifiedmailboxmanager.h"
0008 #include "../common.h"
0009 #include "../unifiedmailbox.h"
0010 
0011 #include <KConfigGroup>
0012 #include <KSharedConfig>
0013 
0014 #include <Akonadi/CollectionCreateJob>
0015 #include <Akonadi/CollectionDeleteJob>
0016 #include <Akonadi/CollectionFetchJob>
0017 #include <Akonadi/CollectionFetchScope>
0018 #include <Akonadi/CollectionModifyJob>
0019 #include <Akonadi/ItemCreateJob>
0020 #include <Akonadi/ItemDeleteJob>
0021 #include <Akonadi/ItemModifyJob>
0022 #include <Akonadi/ItemMoveJob>
0023 #include <Akonadi/SpecialCollectionAttribute>
0024 #include <akonadi/qtest_akonadi.h>
0025 
0026 #include <QTest>
0027 
0028 #include <chrono>
0029 #include <memory>
0030 
0031 using namespace std::chrono;
0032 using namespace std::chrono_literals;
0033 
0034 namespace
0035 {
0036 #define AKVERIFY_RET(statement, ret)                                                                                                                           \
0037     do {                                                                                                                                                       \
0038         if (!QTest::qVerify(static_cast<bool>(statement), #statement, "", __FILE__, __LINE__)) {                                                               \
0039             return ret;                                                                                                                                        \
0040         }                                                                                                                                                      \
0041     } while (false)
0042 
0043 #define AKCOMPARE_RET(actual, expected, ret)                                                                                                                   \
0044     do {                                                                                                                                                       \
0045         if (!QTest::qCompare(actual, expected, #actual, #expected, __FILE__, __LINE__)) {                                                                      \
0046             return ret;                                                                                                                                        \
0047         }                                                                                                                                                      \
0048     } while (false)
0049 
0050 Akonadi::Collection collectionForId(qint64 id)
0051 {
0052     auto fetch = new Akonadi::CollectionFetchJob(Akonadi::Collection(id), Akonadi::CollectionFetchJob::Base);
0053     fetch->fetchScope().fetchAttribute<Akonadi::SpecialCollectionAttribute>();
0054     AKVERIFY_RET(fetch->exec(), {});
0055     const auto cols = fetch->collections();
0056     AKCOMPARE_RET(cols.count(), 1, {});
0057     return cols.first();
0058 }
0059 
0060 Akonadi::Collection collectionForRid(const QString &rid)
0061 {
0062     auto fetch = new Akonadi::CollectionFetchJob(Akonadi::Collection::root(), Akonadi::CollectionFetchJob::Recursive);
0063     fetch->fetchScope().fetchAttribute<Akonadi::SpecialCollectionAttribute>();
0064     fetch->fetchScope().setAncestorRetrieval(Akonadi::CollectionFetchScope::All);
0065     AKVERIFY_RET(fetch->exec(), {});
0066     const auto cols = fetch->collections();
0067     auto colIt = std::find_if(cols.cbegin(), cols.cend(), [&rid](const Akonadi::Collection &col) {
0068         return col.remoteId() == rid;
0069     });
0070     AKVERIFY_RET(colIt != cols.cend(), {});
0071     return *colIt;
0072 }
0073 
0074 // A kingdom and a horse for std::optional!
0075 std::unique_ptr<UnifiedMailbox> createUnifiedMailbox(const QString &id, const QString &name, const QStringList &sourceRids)
0076 {
0077     auto mailbox = std::make_unique<UnifiedMailbox>();
0078     mailbox->setId(id);
0079     mailbox->setName(name);
0080     mailbox->setIcon(QStringLiteral("dummy-icon"));
0081     for (const auto &srcRid : sourceRids) {
0082         const auto srcCol = collectionForRid(srcRid);
0083         AKVERIFY_RET(srcCol.isValid(), {});
0084         mailbox->addSourceCollection(srcCol.id());
0085     }
0086     return mailbox;
0087 }
0088 
0089 class EntityDeleter
0090 {
0091 public:
0092     ~EntityDeleter()
0093     {
0094         while (!cols.isEmpty()) {
0095             if (!(new Akonadi::CollectionDeleteJob(cols.takeFirst()))->exec()) {
0096                 QFAIL("Failed to cleanup collection!");
0097             }
0098         }
0099         while (!items.isEmpty()) {
0100             if (!(new Akonadi::ItemDeleteJob(items.takeFirst()))->exec()) {
0101                 QFAIL("Failed to cleanup Item");
0102             }
0103         }
0104     }
0105 
0106     EntityDeleter &operator<<(const Akonadi::Collection &col)
0107     {
0108         cols.push_back(col);
0109         return *this;
0110     }
0111 
0112     EntityDeleter &operator<<(const Akonadi::Item &item)
0113     {
0114         items.push_back(item);
0115         return *this;
0116     }
0117 
0118 private:
0119     Akonadi::Collection::List cols;
0120     Akonadi::Item::List items;
0121 };
0122 
0123 Akonadi::Collection createCollection(const QString &name, const Akonadi::Collection &parent, EntityDeleter &deleter)
0124 {
0125     Akonadi::Collection col;
0126     col.setName(name);
0127     col.setParentCollection(parent);
0128     col.setVirtual(true);
0129     auto createCol = new Akonadi::CollectionCreateJob(col);
0130     AKVERIFY_RET(createCol->exec(), {});
0131     col = createCol->collection();
0132     if (col.isValid()) {
0133         deleter << col;
0134     }
0135     return col;
0136 }
0137 } // namespace
0138 
0139 class UnifiedMailboxManagerTest : public QObject
0140 {
0141     Q_OBJECT
0142 
0143 private Q_SLOTS:
0144 
0145     void initTestCase()
0146     {
0147         AkonadiTest::checkTestIsIsolated();
0148     }
0149 
0150     void testCreateDefaultBoxes()
0151     {
0152         // Setup
0153         auto kcfg = KSharedConfig::openConfig(QString::fromUtf8(QTest::currentTestFunction()));
0154         const auto boxesGroup = kcfg->group(QStringLiteral("UnifiedMailboxes"));
0155         UnifiedMailboxManager manager(kcfg);
0156 
0157         // Make sure the config is empty
0158         QVERIFY(boxesGroup.groupList().empty());
0159 
0160         // Call loadBoxes and wait for it to finish
0161         bool loadingDone = false;
0162         manager.loadBoxes([&loadingDone]() {
0163             loadingDone = true;
0164         });
0165         QTRY_VERIFY_WITH_TIMEOUT(loadingDone, milliseconds(10s).count());
0166 
0167         // Check all the three boxes were created
0168         bool success;
0169         const auto verifyBox = [&manager, &success](const QString &id, int numSources) {
0170             success = false;
0171             auto boxIt = std::find_if(manager.begin(), manager.end(), [&id](const UnifiedMailboxManager::Entry &e) {
0172                 return e.second->id() == id;
0173             });
0174             QVERIFY(boxIt != manager.end());
0175             const auto &box = boxIt->second;
0176             const auto sourceCollections = box->sourceCollections();
0177             QCOMPARE(sourceCollections.size(), numSources);
0178             for (auto source : sourceCollections) {
0179                 auto col = collectionForId(source);
0180                 QVERIFY(col.isValid());
0181                 QVERIFY(col.hasAttribute<Akonadi::SpecialCollectionAttribute>());
0182                 QCOMPARE(col.attribute<Akonadi::SpecialCollectionAttribute>()->collectionType(), id.toLatin1());
0183             }
0184             success = true;
0185         };
0186         verifyBox(Common::InboxBoxId, 2);
0187         QVERIFY(success);
0188         verifyBox(Common::SentBoxId, 2);
0189         QVERIFY(success);
0190         verifyBox(Common::DraftsBoxId, 1);
0191         QVERIFY(success);
0192 
0193         // Check boxes were written to config - we don't check the contents of
0194         // the group, testing UnifiedMailbox serialization is done in other tests
0195         QVERIFY(boxesGroup.groupList().size() == 3);
0196         QVERIFY(boxesGroup.hasGroup(Common::InboxBoxId));
0197         QVERIFY(boxesGroup.hasGroup(Common::SentBoxId));
0198         QVERIFY(boxesGroup.hasGroup(Common::DraftsBoxId));
0199     }
0200 
0201     void testAddingNewMailbox()
0202     {
0203         // Setup
0204         auto kcfg = KSharedConfig::openConfig(QString::fromUtf8(QTest::currentTestFunction()));
0205         const auto boxesGroup = kcfg->group(QStringLiteral("UnifiedMailboxes"));
0206         UnifiedMailboxManager manager(kcfg);
0207         Akonadi::ChangeRecorder &recorder = manager.changeRecorder();
0208 
0209         // Nothing should be monitored as of now
0210         QVERIFY(recorder.collectionsMonitored().isEmpty());
0211 
0212         // Create a new unified mailbox and passit to the manager
0213         auto mailbox = createUnifiedMailbox(QStringLiteral("Test1"), QStringLiteral("Test 1"), {QStringLiteral("res1_inbox")});
0214         QVERIFY(mailbox);
0215         const auto sourceCol = mailbox->sourceCollections().values().first();
0216         manager.insertBox(std::move(mailbox));
0217 
0218         // Now manager should have one unified mailbox and monitor all of its
0219         // source collections
0220         QCOMPARE(std::distance(manager.begin(), manager.end()), 1l);
0221         QCOMPARE(recorder.collectionsMonitored().size(), 1);
0222         QCOMPARE(recorder.collectionsMonitored().at(0).id(), sourceCol);
0223         QVERIFY(manager.unifiedMailboxForSource(sourceCol) != nullptr);
0224 
0225         // But nothing should bne written in the config yet
0226         QVERIFY(!boxesGroup.groupList().contains(QLatin1StringView("Test1")));
0227 
0228         // Now write to the config file and check it's actually there - we don't test
0229         // the contents of the group, UnifiedMailbox serialization has its own test
0230         manager.saveBoxes();
0231         QVERIFY(boxesGroup.hasGroup(QLatin1StringView("Test1")));
0232     }
0233 
0234     void testRemoveMailbox()
0235     {
0236         // Setup
0237         auto kcfg = KSharedConfig::openConfig(QString::fromUtf8(QTest::currentTestFunction()));
0238         auto boxesGroup = kcfg->group(QStringLiteral("UnifiedMailboxes"));
0239         auto mailbox = createUnifiedMailbox(QStringLiteral("Test1"), QStringLiteral("Test 1"), {QStringLiteral("res1_foo"), QStringLiteral("res2_foo")});
0240         QVERIFY(mailbox);
0241         auto group = boxesGroup.group(mailbox->id());
0242         mailbox->save(group);
0243 
0244         UnifiedMailboxManager manager(kcfg);
0245         Akonadi::ChangeRecorder &recorder = manager.changeRecorder();
0246 
0247         // Nothing should be monitored right now
0248         QVERIFY(recorder.collectionsMonitored().isEmpty());
0249 
0250         // Load the config
0251         bool loadingDone = false;
0252         manager.loadBoxes([&loadingDone]() {
0253             loadingDone = true;
0254         });
0255         QTRY_VERIFY_WITH_TIMEOUT(loadingDone, milliseconds(10s).count());
0256 
0257         // Now the box should be loaded and its source collections monitored
0258         QCOMPARE(std::distance(manager.begin(), manager.end()), 1l);
0259         QCOMPARE(recorder.collectionsMonitored().count(), 2);
0260         const auto srcCols = mailbox->sourceCollections().values();
0261         QCOMPARE(srcCols.count(), 2);
0262         QVERIFY(recorder.collectionsMonitored().contains(Akonadi::Collection(srcCols[0])));
0263         QVERIFY(recorder.collectionsMonitored().contains(Akonadi::Collection(srcCols[1])));
0264 
0265         // Now remove the box
0266         manager.removeBox(mailbox->id());
0267 
0268         // Manager should have no boxes and no source collections should be monitored
0269         QVERIFY(manager.begin() == manager.end());
0270         QVERIFY(recorder.collectionsMonitored().isEmpty());
0271 
0272         // But the box still exists in the config
0273         QVERIFY(boxesGroup.hasGroup(mailbox->id()));
0274 
0275         // Save the new state
0276         manager.saveBoxes();
0277 
0278         // And now it should be gone from the config file as well
0279         QVERIFY(!boxesGroup.hasGroup(mailbox->id()));
0280     }
0281 
0282     void testDiscoverBoxCollections()
0283     {
0284         // Setup
0285         auto kcfg = KSharedConfig::openConfig(QString::fromUtf8(QTest::currentTestFunction()));
0286         auto boxesGroup = kcfg->group(QStringLiteral("UnifiedMailboxes"));
0287         UnifiedMailboxManager manager(kcfg);
0288         EntityDeleter deleter;
0289         const auto inbox = createUnifiedMailbox(Common::InboxBoxId, QStringLiteral("Inbox"), {QStringLiteral("res1_inbox"), QStringLiteral("res2_inbox")});
0290         auto boxGroup = boxesGroup.group(inbox->id());
0291         inbox->save(boxGroup);
0292         const auto sentBox = createUnifiedMailbox(Common::SentBoxId, QStringLiteral("Sent"), {QStringLiteral("res1_sent"), QStringLiteral("res2_sent")});
0293         boxGroup = boxesGroup.group(sentBox->id());
0294         sentBox->save(boxGroup);
0295 
0296         const Akonadi::Collection parentCol = collectionForRid(Common::AgentIdentifier);
0297         QVERIFY(parentCol.isValid());
0298 
0299         const auto inboxBoxCol = createCollection(Common::InboxBoxId, parentCol, deleter);
0300         QVERIFY(inboxBoxCol.isValid());
0301 
0302         const auto sentBoxCol = createCollection(Common::SentBoxId, parentCol, deleter);
0303         QVERIFY(sentBoxCol.isValid());
0304 
0305         // Load from config
0306         bool loadingDone = false;
0307         manager.loadBoxes([&loadingDone]() {
0308             loadingDone = true;
0309         });
0310         QTRY_VERIFY_WITH_TIMEOUT(loadingDone, milliseconds(10s).count());
0311 
0312         // Now the boxes should be loaded and we should be able to access them
0313         // by IDs of collections that represent them. The collections should also
0314         // be set for each box.
0315         auto box = manager.unifiedMailboxFromCollection(inboxBoxCol);
0316         QVERIFY(box != nullptr);
0317         QCOMPARE(box->collectionId(), inboxBoxCol.id());
0318 
0319         box = manager.unifiedMailboxFromCollection(sentBoxCol);
0320         QVERIFY(box != nullptr);
0321         QCOMPARE(box->collectionId(), sentBoxCol.id());
0322     }
0323 
0324     void testItemAddedToSourceCollection()
0325     {
0326         // Setup
0327         auto kcfg = KSharedConfig::openConfig(QString::fromUtf8(QTest::currentTestFunction()));
0328         UnifiedMailboxManager manager(kcfg);
0329         EntityDeleter deleter;
0330 
0331         const auto parentCol = collectionForRid(Common::AgentIdentifier);
0332         QVERIFY(parentCol.isValid());
0333 
0334         const auto inboxBoxCol = createCollection(Common::InboxBoxId, parentCol, deleter);
0335         QVERIFY(inboxBoxCol.isValid());
0336 
0337         // Load boxes - config is empty so this will create the default Boxes and
0338         // assign the Inboxes from Knuts to it
0339         bool loadingDone = true;
0340         manager.loadBoxes([&loadingDone]() {
0341             loadingDone = true;
0342         });
0343         QTRY_VERIFY_WITH_TIMEOUT(loadingDone, milliseconds(10s).count());
0344 
0345         // Now discover collections for the created boxes
0346         loadingDone = false;
0347         manager.discoverBoxCollections([&loadingDone]() {
0348             loadingDone = true;
0349         });
0350         QTRY_VERIFY_WITH_TIMEOUT(loadingDone, milliseconds(10s).count());
0351 
0352         // Get one of the source collections for Inbox
0353         const auto inboxSourceCol = collectionForRid(QStringLiteral("res1_inbox"));
0354         QVERIFY(inboxSourceCol.isValid());
0355 
0356         // Setup up a monitor to to be notified when an item gets linked into
0357         // the unified mailbox collection
0358         Akonadi::Monitor monitor;
0359         monitor.setCollectionMonitored(inboxBoxCol);
0360         QSignalSpy itemLinkedSignalSpy(&monitor, &Akonadi::Monitor::itemsLinked);
0361         QVERIFY(QSignalSpy(&monitor, &Akonadi::Monitor::monitorReady).wait());
0362 
0363         // Add a new Item into the source collection
0364         Akonadi::Item item;
0365         item.setMimeType(QStringLiteral("application/octet-stream"));
0366         item.setParentCollection(inboxSourceCol);
0367         item.setPayload(QByteArray{"Hello world!"});
0368         auto createItem = new Akonadi::ItemCreateJob(item, inboxSourceCol, this);
0369         AKVERIFYEXEC(createItem);
0370         item = createItem->item();
0371         deleter << item;
0372 
0373         // Then wait for ItemLinked notification as the Manager has linked the new Item
0374         // to the dest collection
0375         QTRY_COMPARE(itemLinkedSignalSpy.size(), 1);
0376         const auto linkedItems = itemLinkedSignalSpy.at(0).at(0).value<Akonadi::Item::List>();
0377         QCOMPARE(linkedItems.size(), 1);
0378         QCOMPARE(linkedItems.at(0), item);
0379         const auto linkedCol = itemLinkedSignalSpy.at(0).at(1).value<Akonadi::Collection>();
0380         QCOMPARE(linkedCol, inboxBoxCol);
0381     }
0382 
0383     void testItemMovedFromSourceCollection()
0384     {
0385         // Setup
0386         auto kcfg = KSharedConfig::openConfig(QString::fromUtf8(QTest::currentTestFunction()));
0387         UnifiedMailboxManager manager(kcfg);
0388         EntityDeleter deleter;
0389 
0390         const auto parentCol = collectionForRid(Common::AgentIdentifier);
0391         QVERIFY(parentCol.isValid());
0392 
0393         const auto inboxBoxCol = createCollection(Common::InboxBoxId, parentCol, deleter);
0394         QVERIFY(inboxBoxCol.isValid());
0395 
0396         // Load boxes - config is empty so this will create the default Boxes and
0397         // assign the Inboxes from Knuts to it
0398         bool loadingDone = true;
0399         manager.loadBoxes([&loadingDone]() {
0400             loadingDone = true;
0401         });
0402         QTRY_VERIFY_WITH_TIMEOUT(loadingDone, milliseconds(10s).count());
0403 
0404         // Now discover collections for the created boxes
0405         loadingDone = false;
0406         manager.discoverBoxCollections([&loadingDone]() {
0407             loadingDone = true;
0408         });
0409         QTRY_VERIFY_WITH_TIMEOUT(loadingDone, milliseconds(10s).count());
0410 
0411         // Get one of the source collections for Inbox
0412         const auto inboxSourceCol = collectionForRid(QStringLiteral("res1_inbox"));
0413         QVERIFY(inboxSourceCol.isValid());
0414 
0415         // Setup up a monitor to to be notified when an item gets linked into
0416         // the unified mailbox collection
0417         Akonadi::Monitor monitor;
0418         monitor.setCollectionMonitored(inboxBoxCol);
0419         QSignalSpy itemLinkedSignalSpy(&monitor, &Akonadi::Monitor::itemsLinked);
0420         QSignalSpy itemUnlinkedSignalSpy(&monitor, &Akonadi::Monitor::itemsUnlinked);
0421         QVERIFY(QSignalSpy(&monitor, &Akonadi::Monitor::monitorReady).wait());
0422 
0423         // Add a new Item into the source collection
0424         Akonadi::Item item;
0425         item.setMimeType(QStringLiteral("application/octet-stream"));
0426         item.setParentCollection(inboxSourceCol);
0427         item.setPayload(QByteArray{"Hello world!"});
0428         auto createItem = new Akonadi::ItemCreateJob(item, inboxSourceCol, this);
0429         AKVERIFYEXEC(createItem);
0430         item = createItem->item();
0431         deleter << item;
0432 
0433         // Waity for the item to be linked
0434         QTRY_COMPARE(itemLinkedSignalSpy.size(), 1);
0435 
0436         const auto destinationCol = collectionForRid(QStringLiteral("res1_foo"));
0437         QVERIFY(destinationCol.isValid());
0438 
0439         // Now move the Item to an unmonitored collection
0440         auto move = new Akonadi::ItemMoveJob(item, destinationCol, this);
0441         AKVERIFYEXEC(move);
0442 
0443         QTRY_COMPARE(itemUnlinkedSignalSpy.size(), 1);
0444         const auto unlinkedItems = itemUnlinkedSignalSpy.at(0).at(0).value<Akonadi::Item::List>();
0445         QCOMPARE(unlinkedItems.size(), 1);
0446         QCOMPARE(unlinkedItems.first(), item);
0447         const auto unlinkedCol = itemUnlinkedSignalSpy.at(0).at(1).value<Akonadi::Collection>();
0448         QCOMPARE(unlinkedCol, inboxBoxCol);
0449     }
0450 
0451     void testItemMovedBetweenSourceCollections()
0452     {
0453         // Setup
0454         auto kcfg = KSharedConfig::openConfig(QString::fromUtf8(QTest::currentTestFunction()));
0455         UnifiedMailboxManager manager(kcfg);
0456         EntityDeleter deleter;
0457 
0458         const auto parentCol = collectionForRid(Common::AgentIdentifier);
0459         QVERIFY(parentCol.isValid());
0460 
0461         const auto inboxBoxCol = createCollection(Common::InboxBoxId, parentCol, deleter);
0462         QVERIFY(inboxBoxCol.isValid());
0463 
0464         const auto draftsBoxCol = createCollection(Common::DraftsBoxId, parentCol, deleter);
0465         QVERIFY(draftsBoxCol.isValid());
0466 
0467         // Load boxes - config is empty so this will create the default Boxes and
0468         // assign the Inboxes from Knuts to it
0469         bool loadingDone = true;
0470         manager.loadBoxes([&loadingDone]() {
0471             loadingDone = true;
0472         });
0473         QTRY_VERIFY_WITH_TIMEOUT(loadingDone, milliseconds(10s).count());
0474 
0475         // Now discover collections for the created boxes
0476         loadingDone = false;
0477         manager.discoverBoxCollections([&loadingDone]() {
0478             loadingDone = true;
0479         });
0480         QTRY_VERIFY_WITH_TIMEOUT(loadingDone, milliseconds(10s).count());
0481 
0482         // Get one of the source collections for Inbox and Drafts
0483         const auto inboxSourceCol = collectionForRid(QStringLiteral("res1_inbox"));
0484         QVERIFY(inboxSourceCol.isValid());
0485         const auto draftsSourceCol = collectionForRid(QStringLiteral("res1_drafts"));
0486         QVERIFY(draftsSourceCol.isValid());
0487 
0488         // Setup up a monitor to to be notified when an item gets linked into
0489         // the unified mailbox collection
0490         Akonadi::Monitor monitor;
0491         monitor.setCollectionMonitored(inboxBoxCol);
0492         monitor.setCollectionMonitored(draftsBoxCol);
0493         QSignalSpy itemLinkedSignalSpy(&monitor, &Akonadi::Monitor::itemsLinked);
0494         QSignalSpy itemUnlinkedSignalSpy(&monitor, &Akonadi::Monitor::itemsUnlinked);
0495         QVERIFY(QSignalSpy(&monitor, &Akonadi::Monitor::monitorReady).wait());
0496 
0497         // Add a new Item into the source Inbox collection
0498         Akonadi::Item item;
0499         item.setMimeType(QStringLiteral("application/octet-stream"));
0500         item.setParentCollection(inboxSourceCol);
0501         item.setPayload(QByteArray{"Hello world!"});
0502         auto createItem = new Akonadi::ItemCreateJob(item, inboxSourceCol, this);
0503         AKVERIFYEXEC(createItem);
0504         item = createItem->item();
0505         deleter << item;
0506 
0507         // Waity for the item to be linked
0508         QTRY_COMPARE(itemLinkedSignalSpy.size(), 1);
0509         itemLinkedSignalSpy.clear();
0510 
0511         // Now move the Item to another Unified mailbox's source collection
0512         auto move = new Akonadi::ItemMoveJob(item, draftsSourceCol, this);
0513         AKVERIFYEXEC(move);
0514 
0515         QTRY_COMPARE(itemUnlinkedSignalSpy.size(), 1);
0516         const auto unlinkedItems = itemUnlinkedSignalSpy.at(0).at(0).value<Akonadi::Item::List>();
0517         QCOMPARE(unlinkedItems.size(), 1);
0518         QCOMPARE(unlinkedItems.first(), item);
0519         const auto unlinkedCol = itemUnlinkedSignalSpy.at(0).at(1).value<Akonadi::Collection>();
0520         QCOMPARE(unlinkedCol, inboxBoxCol);
0521 
0522         QTRY_COMPARE(itemLinkedSignalSpy.size(), 1);
0523         const auto linkedItems = itemLinkedSignalSpy.at(0).at(0).value<Akonadi::Item::List>();
0524         QCOMPARE(linkedItems.size(), 1);
0525         QCOMPARE(linkedItems.first(), item);
0526         const auto linkedCol = itemLinkedSignalSpy.at(0).at(1).value<Akonadi::Collection>();
0527         QCOMPARE(linkedCol, draftsBoxCol);
0528     }
0529 
0530     void testSourceCollectionRemoved()
0531     {
0532         // Setup
0533         auto kcfg = KSharedConfig::openConfig(QString::fromUtf8(QTest::currentTestFunction()));
0534         UnifiedMailboxManager manager(kcfg);
0535         auto &changeRecorder = manager.changeRecorder();
0536         QSignalSpy crRemovedSpy(&changeRecorder, &Akonadi::Monitor::collectionRemoved);
0537         EntityDeleter deleter;
0538 
0539         const auto parentCol = collectionForRid(Common::AgentIdentifier);
0540         QVERIFY(parentCol.isValid());
0541 
0542         const auto inboxBoxCol = createCollection(Common::InboxBoxId, parentCol, deleter);
0543         QVERIFY(inboxBoxCol.isValid());
0544 
0545         // Load boxes - config is empty so this will create the default Boxes and
0546         // assign the Inboxes from Knuts to it
0547         bool loadingDone = true;
0548         manager.loadBoxes([&loadingDone]() {
0549             loadingDone = true;
0550         });
0551         QTRY_VERIFY_WITH_TIMEOUT(loadingDone, milliseconds(10s).count());
0552 
0553         // Now discover collections for the created boxes
0554         loadingDone = false;
0555         manager.discoverBoxCollections([&loadingDone]() {
0556             loadingDone = true;
0557         });
0558         QTRY_VERIFY_WITH_TIMEOUT(loadingDone, milliseconds(10s).count());
0559 
0560         auto inboxSourceCol = collectionForRid(QStringLiteral("res1_inbox"));
0561         QVERIFY(inboxSourceCol.isValid());
0562         auto delJob = new Akonadi::CollectionDeleteJob(inboxSourceCol, this);
0563         AKVERIFYEXEC(delJob);
0564 
0565         // Wait for the change recorder to be notified
0566         QVERIFY(crRemovedSpy.wait());
0567         crRemovedSpy.clear();
0568         // and then wait a little bit more to give the Manager time to process the event
0569         QTest::qWait(0);
0570 
0571         auto inboxBox = manager.unifiedMailboxFromCollection(inboxBoxCol);
0572         QVERIFY(inboxBox);
0573         QVERIFY(!inboxBox->sourceCollections().contains(inboxSourceCol.id()));
0574         QVERIFY(!changeRecorder.collectionsMonitored().contains(inboxSourceCol));
0575         QVERIFY(!manager.unifiedMailboxForSource(inboxSourceCol.id()));
0576 
0577         // Lets removed the other source collection now, that should remove the unified box completely
0578         inboxSourceCol = collectionForRid(QStringLiteral("res2_inbox"));
0579         QVERIFY(inboxSourceCol.isValid());
0580         delJob = new Akonadi::CollectionDeleteJob(inboxSourceCol, this);
0581         AKVERIFYEXEC(delJob);
0582 
0583         // Wait for the change recorder once again
0584         QVERIFY(crRemovedSpy.wait());
0585         QTest::qWait(0);
0586 
0587         QVERIFY(!manager.unifiedMailboxFromCollection(inboxBoxCol));
0588         QVERIFY(!changeRecorder.collectionsMonitored().contains(inboxSourceCol));
0589         QVERIFY(!manager.unifiedMailboxForSource(inboxSourceCol.id()));
0590     }
0591 
0592     void testSpecialSourceCollectionCreated()
0593     {
0594         // TODO: this does not work yet: we only monitor collections that we are
0595         // interested in, we don't monitor other collections
0596     }
0597 
0598     void testSpecialSourceCollectionDemoted()
0599     {
0600         // Setup
0601         auto kcfg = KSharedConfig::openConfig(QString::fromUtf8(QTest::currentTestFunction()));
0602         UnifiedMailboxManager manager(kcfg);
0603         auto &changeRecorder = manager.changeRecorder();
0604         QSignalSpy crChangedSpy(&changeRecorder, qOverload<const Akonadi::Collection &, const QSet<QByteArray> &>(&Akonadi::Monitor::collectionChanged));
0605         EntityDeleter deleter;
0606 
0607         const auto parentCol = collectionForRid(Common::AgentIdentifier);
0608         QVERIFY(parentCol.isValid());
0609 
0610         const auto sentBoxCol = createCollection(Common::SentBoxId, parentCol, deleter);
0611         QVERIFY(sentBoxCol.isValid());
0612 
0613         // Load boxes - config is empty so this will create the default Boxes and
0614         // assign the Inboxes from Knuts to it
0615         bool loadingDone = true;
0616         manager.loadBoxes([&loadingDone]() {
0617             loadingDone = true;
0618         });
0619         QTRY_VERIFY_WITH_TIMEOUT(loadingDone, milliseconds(10s).count());
0620 
0621         // Now discover collections for the created boxes
0622         loadingDone = false;
0623         manager.discoverBoxCollections([&loadingDone]() {
0624             loadingDone = true;
0625         });
0626         QTRY_VERIFY_WITH_TIMEOUT(loadingDone, milliseconds(10s).count());
0627 
0628         auto sentSourceCol = collectionForRid(QStringLiteral("res1_sent"));
0629         QVERIFY(sentSourceCol.isValid());
0630         sentSourceCol.removeAttribute<Akonadi::SpecialCollectionAttribute>();
0631         auto modify = new Akonadi::CollectionModifyJob(sentSourceCol, this);
0632         AKVERIFYEXEC(modify);
0633 
0634         // Wait for the change recorder to be notified
0635         QVERIFY(crChangedSpy.wait());
0636         crChangedSpy.clear();
0637         // and then wait a little bit more to give the Manager time to process the event
0638         QTest::qWait(0);
0639 
0640         auto sourceBox = manager.unifiedMailboxFromCollection(sentBoxCol);
0641         QVERIFY(sourceBox);
0642         QVERIFY(!sourceBox->sourceCollections().contains(sentSourceCol.id()));
0643         QVERIFY(!changeRecorder.collectionsMonitored().contains(sentSourceCol));
0644         QVERIFY(!manager.unifiedMailboxForSource(sentSourceCol.id()));
0645 
0646         // Lets demote the other source collection now, that should remove the unified box completely
0647         sentSourceCol = collectionForRid(QStringLiteral("res2_sent"));
0648         QVERIFY(sentSourceCol.isValid());
0649         sentSourceCol.attribute<Akonadi::SpecialCollectionAttribute>()->setCollectionType("drafts");
0650         modify = new Akonadi::CollectionModifyJob(sentSourceCol, this);
0651         AKVERIFYEXEC(modify);
0652 
0653         // Wait for the change recorder once again
0654         QVERIFY(crChangedSpy.wait());
0655         QTest::qWait(0);
0656 
0657         // There's no more Sent unified box
0658         QVERIFY(!manager.unifiedMailboxFromCollection(sentBoxCol));
0659 
0660         // The collection is still monitored: it belongs to the Drafts special box now!
0661         QVERIFY(changeRecorder.collectionsMonitored().contains(sentSourceCol));
0662         QVERIFY(manager.unifiedMailboxForSource(sentSourceCol.id()));
0663     }
0664 };
0665 
0666 QTEST_AKONADIMAIN(UnifiedMailboxManagerTest)
0667 
0668 #include "unifiedmailboxmanagertest.moc"