File indexing completed on 2024-05-12 05:26:26

0001 #include <QTest>
0002 
0003 #include <QString>
0004 #include <QSignalSpy>
0005 
0006 #include "resource.h"
0007 #include "store.h"
0008 #include "resourcecontrol.h"
0009 #include "commands.h"
0010 #include "resourceconfig.h"
0011 #include "log.h"
0012 #include "modelresult.h"
0013 #include "test.h"
0014 #include "applicationdomaintype.h"
0015 #include "queryrunner.h"
0016 #include "adaptorfactoryregistry.h"
0017 #include "fulltextindex.h"
0018 
0019 #include <KMime/Message>
0020 #include <KCalendarCore/Event>
0021 #include <KCalendarCore/ICalFormat>
0022 
0023 using namespace Sink;
0024 using namespace Sink::ApplicationDomain;
0025 
0026 /**
0027  * Test of the query system using the dummy resource.
0028  *
0029  * This test requires the dummy resource installed.
0030  */
0031 class QueryTest : public QObject
0032 {
0033     Q_OBJECT
0034 private slots:
0035     void initTestCase()
0036     {
0037         qRegisterMetaType<QList<QPersistentModelIndex>>("QList<QPersistentModelIndex>");
0038         qRegisterMetaType<QAbstractItemModel::LayoutChangeHint>("QAbstractItemModel::LayoutChangeHint");
0039         Sink::Test::initTest();
0040         auto factory = Sink::ResourceFactory::load("sink.dummy");
0041         QVERIFY(factory);
0042         ResourceConfig::addResource("sink.dummy.instance1", "sink.dummy");
0043         ResourceConfig::configureResource("sink.dummy.instance1", {{"populate", true}});
0044         VERIFYEXEC(Sink::Store::removeDataFromDisk(QByteArray("sink.dummy.instance1")));
0045     }
0046 
0047     void cleanup()
0048     {
0049         VERIFYEXEC(Sink::Store::removeDataFromDisk(QByteArray("sink.dummy.instance1")));
0050     }
0051 
0052     void init()
0053     {
0054         qDebug();
0055         qDebug() << "-----------------------------------------";
0056         qDebug();
0057     }
0058 
0059     void testSerialization()
0060     {
0061 
0062         auto type = QByteArray("type");
0063         auto sort = QByteArray("sort");
0064 
0065         Sink::QueryBase::Filter filter;
0066         filter.ids << "id";
0067         filter.propertyFilter.insert({"foo"}, QVariant::fromValue(QByteArray("bar")));
0068 
0069         Sink::Query query;
0070         query.setFilter(filter);
0071         query.setType(type);
0072         query.setSortProperty(sort);
0073 
0074         QByteArray data;
0075         {
0076             QDataStream stream(&data, QIODevice::WriteOnly);
0077             stream << query;
0078         }
0079 
0080         Sink::Query deserializedQuery;
0081         {
0082             QDataStream stream(&data, QIODevice::ReadOnly);
0083             stream >> deserializedQuery;
0084         }
0085 
0086         QCOMPARE(deserializedQuery.type(), type);
0087         QCOMPARE(deserializedQuery.sortProperty(), sort);
0088         QCOMPARE(deserializedQuery.getFilter().ids, filter.ids);
0089         QCOMPARE(deserializedQuery.getFilter().propertyFilter.keys(), filter.propertyFilter.keys());
0090         QCOMPARE(deserializedQuery.getFilter().propertyFilter, filter.propertyFilter);
0091     }
0092 
0093     void testNoResources()
0094     {
0095         // Test
0096         Sink::Query query;
0097         query.resourceFilter("foobar");
0098         query.setFlags(Query::LiveQuery);
0099 
0100         // We fetch before the data is available and rely on the live query mechanism to deliver the actual data
0101         auto model = Sink::Store::loadModel<Mail>(query);
0102         QTRY_VERIFY(model->data(QModelIndex(), Sink::Store::ChildrenFetchedRole).toBool());
0103         QCOMPARE(model->rowCount(), 0);
0104     }
0105 
0106 
0107     void testSingle()
0108     {
0109         // Setup
0110         auto mail = Mail("sink.dummy.instance1");
0111         mail.setExtractedMessageId("test1");
0112         VERIFYEXEC(Sink::Store::create<Mail>(mail));
0113 
0114         // Test
0115         Sink::Query query;
0116         query.resourceFilter("sink.dummy.instance1");
0117         query.setFlags(Query::LiveQuery);
0118 
0119         // We fetch before the data is available and rely on the live query mechanism to deliver the actual data
0120         auto model = Sink::Store::loadModel<Mail>(query);
0121         QTRY_COMPARE(model->rowCount(), 1);
0122     }
0123 
0124     void testSingleWithDelay()
0125     {
0126         // Setup
0127         auto mail = Mail("sink.dummy.instance1");
0128         mail.setExtractedMessageId("test1");
0129         VERIFYEXEC(Sink::Store::create<Mail>(mail));
0130 
0131         // Test
0132         Sink::Query query;
0133         query.resourceFilter("sink.dummy.instance1");
0134 
0135         // Ensure all local data is processed
0136         VERIFYEXEC(Sink::ResourceControl::flushMessageQueue(QByteArrayList() << "sink.dummy.instance1"));
0137 
0138         // We fetch after the data is available and don't rely on the live query mechanism to deliver the actual data
0139         auto model = Sink::Store::loadModel<Mail>(query);
0140 
0141         QTRY_VERIFY(model->data(QModelIndex(), Sink::Store::ChildrenFetchedRole).toBool());
0142         QCOMPARE(model->rowCount(), 1);
0143     }
0144 
0145     void testFilter()
0146     {
0147         // Setup
0148         {
0149             Mail mail("sink.dummy.instance1");
0150             mail.setExtractedMessageId("test1");
0151             mail.setFolder("folder1");
0152             VERIFYEXEC(Sink::Store::create<Mail>(mail));
0153         }
0154         {
0155             Mail mail("sink.dummy.instance1");
0156             mail.setExtractedMessageId("test2");
0157             mail.setFolder("folder2");
0158             VERIFYEXEC(Sink::Store::create<Mail>(mail));
0159         }
0160 
0161         // Test
0162         Sink::Query query;
0163         query.resourceFilter("sink.dummy.instance1");
0164         query.setFlags(Query::LiveQuery);
0165         query.filter<Mail::Folder>("folder1");
0166 
0167         // We fetch before the data is available and rely on the live query mechanism to deliver the actual data
0168         auto model = Sink::Store::loadModel<Mail>(query);
0169         QTRY_COMPARE(model->rowCount(), 1);
0170 
0171         auto mail = model->index(0, 0, QModelIndex()).data(Sink::Store::DomainObjectRole).value<Mail::Ptr>();
0172         {
0173             mail->setFolder("folder2");
0174             VERIFYEXEC(Sink::Store::modify<Mail>(*mail));
0175         }
0176         QTRY_COMPARE(model->rowCount(), 0);
0177 
0178         {
0179             mail->setFolder("folder1");
0180             VERIFYEXEC(Sink::Store::modify<Mail>(*mail));
0181         }
0182         QTRY_COMPARE(model->rowCount(), 1);
0183     }
0184 
0185     void testById()
0186     {
0187         QByteArray id;
0188         // Setup
0189         {
0190             Mail mail("sink.dummy.instance1");
0191             mail.setExtractedMessageId("test1");
0192             VERIFYEXEC(Sink::Store::create<Mail>(mail));
0193             mail.setExtractedMessageId("test2");
0194             VERIFYEXEC(Sink::Store::create<Mail>(mail));
0195 
0196             Sink::Query query;
0197             query.resourceFilter("sink.dummy.instance1");
0198 
0199             // Ensure all local data is processed
0200             Sink::Store::synchronize(query).exec().waitForFinished();
0201 
0202             // We fetch before the data is available and rely on the live query mechanism to deliver the actual data
0203             auto model = Sink::Store::loadModel<Mail>(query);
0204             QTRY_VERIFY(model->data(QModelIndex(), Sink::Store::ChildrenFetchedRole).toBool());
0205             QVERIFY(model->rowCount() >= 1);
0206             id = model->index(0, 0).data(Sink::Store::DomainObjectRole).value<Mail::Ptr>()->identifier();
0207         }
0208 
0209         // Test
0210         {
0211             Sink::Query query;
0212             query.resourceFilter("sink.dummy.instance1");
0213             query.filter(id);
0214             auto model = Sink::Store::loadModel<Mail>(query);
0215             QTRY_VERIFY(model->data(QModelIndex(), Sink::Store::ChildrenFetchedRole).toBool());
0216             QCOMPARE(model->rowCount(), 1);
0217         }
0218 
0219         {
0220             Sink::Query query;
0221             query.resourceFilter("sink.dummy.instance1");
0222             //Try a non-existing id
0223             query.filter("{87fcea5e-8d2e-408e-bb8d-b27b9dcf5e92}");
0224             auto model = Sink::Store::loadModel<Mail>(query);
0225             QTRY_VERIFY(model->data(QModelIndex(), Sink::Store::ChildrenFetchedRole).toBool());
0226             QCOMPARE(model->rowCount(), 0);
0227         }
0228     }
0229 
0230     void testFolder()
0231     {
0232         // Setup
0233         {
0234             Folder folder("sink.dummy.instance1");
0235             VERIFYEXEC(Sink::Store::create<Folder>(folder));
0236         }
0237 
0238         // Test
0239         Sink::Query query;
0240         query.resourceFilter("sink.dummy.instance1");
0241         query.setFlags(Query::LiveQuery);
0242 
0243         // We fetch before the data is available and rely on the live query mechanism to deliver the actual data
0244         auto model = Sink::Store::loadModel<Folder>(query);
0245         QTRY_COMPARE(model->rowCount(), 1);
0246         auto folderEntity = model->index(0, 0).data(Sink::Store::DomainObjectRole).value<Folder::Ptr>();
0247         QVERIFY(!folderEntity->identifier().isEmpty());
0248     }
0249 
0250     void testFolderTree()
0251     {
0252         // Setup
0253         {
0254             auto folder = ApplicationDomainType::createEntity<Folder>("sink.dummy.instance1");
0255             VERIFYEXEC(Sink::Store::create<Folder>(folder));
0256             auto subfolder = ApplicationDomainType::createEntity<Folder>("sink.dummy.instance1");
0257             subfolder.setParent(folder.identifier());
0258             VERIFYEXEC(Sink::Store::create<Folder>(subfolder));
0259             // Ensure all local data is processed
0260             VERIFYEXEC(Sink::ResourceControl::flushMessageQueue(QByteArrayList() << "sink.dummy.instance1"));
0261         }
0262 
0263         // Test
0264         Sink::Query query;
0265         query.resourceFilter("sink.dummy.instance1");
0266         query.requestTree<Folder::Parent>();
0267 
0268         // Ensure all local data is processed
0269         VERIFYEXEC(Sink::ResourceControl::flushMessageQueue(QByteArrayList() << "sink.dummy.instance1"));
0270 
0271         // We fetch after the data is available and don't rely on the live query mechanism to deliver the actual data
0272         auto model = Sink::Store::loadModel<Folder>(query);
0273         QTRY_VERIFY(model->data(QModelIndex(), Sink::Store::ChildrenFetchedRole).toBool());
0274         QCOMPARE(model->rowCount(), 1);
0275         QCOMPARE(model->rowCount(model->index(0, 0)), 1);
0276     }
0277 
0278     void testIncrementalFolderTree()
0279     {
0280         // Setup
0281         auto folder = ApplicationDomainType::createEntity<Folder>("sink.dummy.instance1");
0282         VERIFYEXEC(Sink::Store::create<Folder>(folder));
0283         // Ensure all local data is processed
0284         VERIFYEXEC(Sink::ResourceControl::flushMessageQueue("sink.dummy.instance1"));
0285 
0286         // Test
0287         Sink::Query query{Sink::Query::LiveQuery};
0288         query.resourceFilter("sink.dummy.instance1");
0289         query.requestTree<Folder::Parent>();
0290 
0291         auto model = Sink::Store::loadModel<Folder>(query);
0292         QTRY_VERIFY(model->data(QModelIndex(), Sink::Store::ChildrenFetchedRole).toBool());
0293         QCOMPARE(model->rowCount(), 1);
0294 
0295         auto subfolder = ApplicationDomainType::createEntity<Folder>("sink.dummy.instance1");
0296         subfolder.setParent(folder.identifier());
0297         VERIFYEXEC(Sink::Store::create<Folder>(subfolder));
0298         VERIFYEXEC(Sink::ResourceControl::flushMessageQueue("sink.dummy.instance1"));
0299 
0300         //Ensure the folder appears
0301         QTRY_COMPARE(model->rowCount(model->index(0, 0)), 1);
0302 
0303         //...and dissapears again after removal
0304         VERIFYEXEC(Sink::Store::remove<Folder>(subfolder));
0305         VERIFYEXEC(Sink::ResourceControl::flushMessageQueue("sink.dummy.instance1"));
0306         QTRY_COMPARE(model->rowCount(model->index(0, 0)), 0);
0307     }
0308 
0309     void testMailByMessageId()
0310     {
0311         // Setup
0312         {
0313             Mail mail("sink.dummy.instance1");
0314             mail.setExtractedMessageId("test1");
0315             mail.setProperty("sender", "doe@example.org");
0316             Sink::Store::create<Mail>(mail).exec().waitForFinished();
0317         }
0318 
0319         {
0320             Mail mail("sink.dummy.instance1");
0321             mail.setExtractedMessageId("test2");
0322             mail.setProperty("sender", "doe@example.org");
0323             Sink::Store::create<Mail>(mail).exec().waitForFinished();
0324         }
0325 
0326         // Test
0327         Sink::Query query;
0328         query.resourceFilter("sink.dummy.instance1");
0329         query.filter<Mail::MessageId>("test1");
0330 
0331         // Ensure all local data is processed
0332         VERIFYEXEC(Sink::ResourceControl::flushMessageQueue(QByteArrayList() << "sink.dummy.instance1"));
0333 
0334         // We fetch before the data is available and rely on the live query mechanism to deliver the actual data
0335         auto model = Sink::Store::loadModel<Mail>(query);
0336         QTRY_VERIFY(model->data(QModelIndex(), Sink::Store::ChildrenFetchedRole).toBool());
0337         QCOMPARE(model->rowCount(), 1);
0338     }
0339 
0340     void testMailByFolder()
0341     {
0342         // Setup
0343         Folder::Ptr folderEntity;
0344         {
0345             Folder folder("sink.dummy.instance1");
0346             Sink::Store::create<Folder>(folder).exec().waitForFinished();
0347 
0348             Sink::Query query;
0349             query.resourceFilter("sink.dummy.instance1");
0350 
0351             // Ensure all local data is processed
0352             VERIFYEXEC(Sink::ResourceControl::flushMessageQueue(QByteArrayList() << "sink.dummy.instance1"));
0353 
0354             auto model = Sink::Store::loadModel<Folder>(query);
0355             QTRY_VERIFY(model->data(QModelIndex(), Sink::Store::ChildrenFetchedRole).toBool());
0356             QCOMPARE(model->rowCount(), 1);
0357 
0358             folderEntity = model->index(0, 0).data(Sink::Store::DomainObjectRole).value<Folder::Ptr>();
0359             QVERIFY(!folderEntity->identifier().isEmpty());
0360 
0361             Mail mail("sink.dummy.instance1");
0362             mail.setExtractedMessageId("test1");
0363             mail.setFolder(folderEntity->identifier());
0364             Sink::Store::create<Mail>(mail).exec().waitForFinished();
0365         }
0366 
0367         // Test
0368         Sink::Query query;
0369         query.resourceFilter("sink.dummy.instance1");
0370         query.filter<Mail::Folder>(*folderEntity);
0371 
0372         // Ensure all local data is processed
0373         VERIFYEXEC(Sink::ResourceControl::flushMessageQueue(QByteArrayList() << "sink.dummy.instance1"));
0374 
0375         // We fetch before the data is available and rely on the live query mechanism to deliver the actual data
0376         auto model = Sink::Store::loadModel<Mail>(query);
0377         QTRY_VERIFY(model->data(QModelIndex(), Sink::Store::ChildrenFetchedRole).toBool());
0378         QCOMPARE(model->rowCount(), 1);
0379     }
0380 
0381     /*
0382      * Filter by two properties to make sure that we also use a non-index based filter.
0383      */
0384     void testMailByMessageIdAndFolder()
0385     {
0386         // Setup
0387         Folder::Ptr folderEntity;
0388         {
0389             Folder folder("sink.dummy.instance1");
0390             Sink::Store::create<Folder>(folder).exec().waitForFinished();
0391 
0392             Sink::Query query;
0393             query.resourceFilter("sink.dummy.instance1");
0394 
0395             // Ensure all local data is processed
0396             VERIFYEXEC(Sink::ResourceControl::flushMessageQueue(QByteArrayList() << "sink.dummy.instance1"));
0397 
0398             auto model = Sink::Store::loadModel<Folder>(query);
0399             QTRY_VERIFY(model->data(QModelIndex(), Sink::Store::ChildrenFetchedRole).toBool());
0400             QCOMPARE(model->rowCount(), 1);
0401 
0402             folderEntity = model->index(0, 0).data(Sink::Store::DomainObjectRole).value<Folder::Ptr>();
0403             QVERIFY(!folderEntity->identifier().isEmpty());
0404 
0405             Mail mail("sink.dummy.instance1");
0406             mail.setExtractedMessageId("test1");
0407             mail.setFolder(folderEntity->identifier());
0408             Sink::Store::create<Mail>(mail).exec().waitForFinished();
0409 
0410             Mail mail1("sink.dummy.instance1");
0411             mail1.setExtractedMessageId("test1");
0412             mail1.setFolder("foobar");
0413             Sink::Store::create<Mail>(mail1).exec().waitForFinished();
0414 
0415             Mail mail2("sink.dummy.instance1");
0416             mail2.setExtractedMessageId("test2");
0417             mail2.setFolder(folderEntity->identifier());
0418             Sink::Store::create<Mail>(mail2).exec().waitForFinished();
0419         }
0420 
0421         // Test
0422         Sink::Query query;
0423         query.resourceFilter("sink.dummy.instance1");
0424         query.filter<Mail::Folder>(*folderEntity);
0425         query.filter<Mail::MessageId>("test1");
0426 
0427         // Ensure all local data is processed
0428         VERIFYEXEC(Sink::ResourceControl::flushMessageQueue(QByteArrayList() << "sink.dummy.instance1"));
0429 
0430         // We fetch before the data is available and rely on the live query mechanism to deliver the actual data
0431         auto model = Sink::Store::loadModel<Mail>(query);
0432         QTRY_VERIFY(model->data(QModelIndex(), Sink::Store::ChildrenFetchedRole).toBool());
0433         QCOMPARE(model->rowCount(), 1);
0434     }
0435 
0436     void testMailByFolderSortedByDate()
0437     {
0438         // Setup
0439         Folder::Ptr folderEntity;
0440         const auto date = QDateTime(QDate(2015, 7, 7), QTime(12, 0));
0441         {
0442             Folder folder("sink.dummy.instance1");
0443             Sink::Store::create<Folder>(folder).exec().waitForFinished();
0444 
0445             Sink::Query query;
0446             query.resourceFilter("sink.dummy.instance1");
0447 
0448             // Ensure all local data is processed
0449             VERIFYEXEC(Sink::ResourceControl::flushMessageQueue(QByteArrayList() << "sink.dummy.instance1"));
0450 
0451             auto model = Sink::Store::loadModel<Folder>(query);
0452             QTRY_VERIFY(model->data(QModelIndex(), Sink::Store::ChildrenFetchedRole).toBool());
0453             QCOMPARE(model->rowCount(), 1);
0454 
0455             folderEntity = model->index(0, 0).data(Sink::Store::DomainObjectRole).value<Folder::Ptr>();
0456             QVERIFY(!folderEntity->identifier().isEmpty());
0457 
0458             {
0459                 Mail mail("sink.dummy.instance1");
0460                 mail.setExtractedMessageId("testSecond");
0461                 mail.setFolder(folderEntity->identifier());
0462                 mail.setExtractedDate(date.addDays(-1));
0463                 Sink::Store::create<Mail>(mail).exec().waitForFinished();
0464             }
0465             {
0466                 Mail mail("sink.dummy.instance1");
0467                 mail.setExtractedMessageId("testLatest");
0468                 mail.setFolder(folderEntity->identifier());
0469                 mail.setExtractedDate(date);
0470                 Sink::Store::create<Mail>(mail).exec().waitForFinished();
0471             }
0472             {
0473                 Mail mail("sink.dummy.instance1");
0474                 mail.setExtractedMessageId("testLast");
0475                 mail.setFolder(folderEntity->identifier());
0476                 mail.setExtractedDate(date.addDays(-2));
0477                 Sink::Store::create<Mail>(mail).exec().waitForFinished();
0478             }
0479         }
0480 
0481         // Test
0482         Sink::Query query;
0483         query.resourceFilter("sink.dummy.instance1");
0484         query.filter<Mail::Folder>(*folderEntity);
0485         query.sort<Mail::Date>();
0486         query.limit(1);
0487         query.setFlags(Query::LiveQuery);
0488         query.reduce<ApplicationDomain::Mail::ThreadId>(Query::Reduce::Selector::max<ApplicationDomain::Mail::Date>())
0489             .count("count")
0490             .collect<ApplicationDomain::Mail::Unread>("unreadCollected")
0491             .collect<ApplicationDomain::Mail::Important>("importantCollected");
0492 
0493         // Ensure all local data is processed
0494         VERIFYEXEC(Sink::ResourceControl::flushMessageQueue(QByteArrayList() << "sink.dummy.instance1"));
0495 
0496         auto model = Sink::Store::loadModel<Mail>(query);
0497         QTRY_VERIFY(model->data(QModelIndex(), Sink::Store::ChildrenFetchedRole).toBool());
0498         // The model is not sorted, but the limited set is sorted, so we can only test for the latest result.
0499         QCOMPARE(model->rowCount(), 1);
0500         QCOMPARE(model->index(0, 0).data(Sink::Store::DomainObjectRole).value<Mail::Ptr>()->getProperty("messageId").toByteArray(), QByteArray("testLatest"));
0501 
0502         model->fetchMore(QModelIndex());
0503         QTRY_VERIFY(model->data(QModelIndex(), Sink::Store::ChildrenFetchedRole).toBool());
0504         QCOMPARE(model->rowCount(), 2);
0505         // We can't make any assumptions about the order of the indexes
0506         // QCOMPARE(model->index(1, 0).data(Sink::Store::DomainObjectRole).value<Mail::Ptr>()->getProperty("messageId").toByteArray(), QByteArray("testSecond"));
0507 
0508         //New revisions always go through
0509         {
0510             Mail mail("sink.dummy.instance1");
0511             mail.setExtractedMessageId("testInjected");
0512             mail.setFolder(folderEntity->identifier());
0513             mail.setExtractedDate(date.addDays(-2));
0514             Sink::Store::create<Mail>(mail).exec().waitForFinished();
0515         }
0516         QTRY_COMPARE(model->rowCount(), 3);
0517 
0518         //Ensure we can continue fetching after the incremental update
0519         model->fetchMore(QModelIndex());
0520         QTRY_VERIFY(model->data(QModelIndex(), Sink::Store::ChildrenFetchedRole).toBool());
0521         QCOMPARE(model->rowCount(), 4);
0522 
0523         //Ensure we have fetched all
0524         model->fetchMore(QModelIndex());
0525         QTRY_VERIFY(model->data(QModelIndex(), Sink::Store::ChildrenFetchedRole).toBool());
0526         QCOMPARE(model->rowCount(), 4);
0527     }
0528 
0529     void testReactToNewResource()
0530     {
0531         Sink::Query query;
0532         query.setFlags(Query::LiveQuery);
0533         auto model = Sink::Store::loadModel<Folder>(query);
0534         QTRY_COMPARE(model->rowCount(QModelIndex()), 0);
0535 
0536         auto res = DummyResource::create("");
0537         VERIFYEXEC(Sink::Store::create(res));
0538         auto folder = Folder::create(res.identifier());
0539         VERIFYEXEC(Sink::Store::create(folder));
0540         QTRY_COMPARE(model->rowCount(QModelIndex()), 1);
0541 
0542         VERIFYEXEC(Sink::Store::remove(res));
0543     }
0544 
0545     void testAccountFilter()
0546     {
0547         using namespace Sink;
0548         using namespace Sink::ApplicationDomain;
0549 
0550         //Setup
0551         QString accountName("name");
0552         QString accountIcon("icon");
0553         auto account1 = ApplicationDomainType::createEntity<SinkAccount>();
0554         account1.setAccountType("maildir");
0555         account1.setName(accountName);
0556         account1.setIcon(accountIcon);
0557         VERIFYEXEC(Store::create(account1));
0558 
0559         auto account2 = ApplicationDomainType::createEntity<SinkAccount>();
0560         account2.setAccountType("maildir");
0561         account2.setName(accountName);
0562         account2.setIcon(accountIcon);
0563         VERIFYEXEC(Store::create(account2));
0564 
0565         auto resource1 = ApplicationDomainType::createEntity<SinkResource>();
0566         resource1.setResourceType("sink.dummy");
0567         resource1.setAccount(account1);
0568         Store::create(resource1).exec().waitForFinished();
0569 
0570         auto resource2 = ApplicationDomainType::createEntity<SinkResource>();
0571         resource2.setResourceType("sink.dummy");
0572         resource2.setAccount(account2);
0573         Store::create(resource2).exec().waitForFinished();
0574 
0575         {
0576             Folder folder1(resource1.identifier());
0577             VERIFYEXEC(Sink::Store::create<Folder>(folder1));
0578             Folder folder2(resource2.identifier());
0579             VERIFYEXEC(Sink::Store::create<Folder>(folder2));
0580         }
0581         VERIFYEXEC(Sink::ResourceControl::flushMessageQueue(QByteArrayList() << resource1.identifier()));
0582         VERIFYEXEC(Sink::ResourceControl::flushMessageQueue(QByteArrayList() << resource2.identifier()));
0583 
0584         // Test
0585         Sink::Query query;
0586         query.resourceFilter<SinkResource::Account>(account1);
0587 
0588         auto folders = Sink::Store::read<Folder>(query);
0589         QCOMPARE(folders.size(), 1);
0590     }
0591 
0592     void testSubquery()
0593     {
0594         // Setup
0595         auto folder1 = Folder::createEntity<Folder>("sink.dummy.instance1");
0596         folder1.setSpecialPurpose(QByteArrayList() << "purpose1");
0597         VERIFYEXEC(Sink::Store::create<Folder>(folder1));
0598 
0599         auto folder2 = Folder::createEntity<Folder>("sink.dummy.instance1");
0600         folder2.setSpecialPurpose(QByteArrayList() << "purpose2");
0601         VERIFYEXEC(Sink::Store::create<Folder>(folder2));
0602 
0603         {
0604             auto mail = Mail::createEntity<Mail>("sink.dummy.instance1");
0605             mail.setExtractedMessageId("mail1");
0606             mail.setFolder(folder1);
0607             VERIFYEXEC(Sink::Store::create(mail));
0608         }
0609         {
0610             auto mail = Mail::createEntity<Mail>("sink.dummy.instance1");
0611             mail.setExtractedMessageId("mail2");
0612             mail.setFolder(folder2);
0613             VERIFYEXEC(Sink::Store::create(mail));
0614         }
0615 
0616         // Ensure all local data is processed
0617         VERIFYEXEC(Sink::ResourceControl::flushMessageQueue(QByteArrayList() << "sink.dummy.instance1"));
0618 
0619         //Setup two folders with a mail each, ensure we only get the mail from the folder that matches the folder filter.
0620         Query query;
0621         query.filter<Mail::Folder>(Sink::Query().containsFilter<Folder::SpecialPurpose>("purpose1"));
0622         query.request<Mail::MessageId>();
0623 
0624         auto mails = Sink::Store::read<Mail>(query);
0625         QCOMPARE(mails.size(), 1);
0626         QCOMPARE(mails.first().getMessageId(), QByteArray("mail1"));
0627     }
0628 
0629     void testLiveSubquery()
0630     {
0631         // Setup
0632         auto folder1 = Folder::createEntity<Folder>("sink.dummy.instance1");
0633         folder1.setSpecialPurpose(QByteArrayList() << "purpose1");
0634         VERIFYEXEC(Sink::Store::create<Folder>(folder1));
0635 
0636         auto folder2 = Folder::createEntity<Folder>("sink.dummy.instance1");
0637         folder2.setSpecialPurpose(QByteArrayList() << "purpose2");
0638         VERIFYEXEC(Sink::Store::create<Folder>(folder2));
0639 
0640         {
0641             auto mail = Mail::createEntity<Mail>("sink.dummy.instance1");
0642             mail.setExtractedMessageId("mail1");
0643             mail.setFolder(folder1);
0644             VERIFYEXEC(Sink::Store::create(mail));
0645         }
0646         {
0647             auto mail = Mail::createEntity<Mail>("sink.dummy.instance1");
0648             mail.setExtractedMessageId("mail2");
0649             mail.setFolder(folder2);
0650             VERIFYEXEC(Sink::Store::create(mail));
0651         }
0652 
0653         // Ensure all local data is processed
0654         VERIFYEXEC(Sink::ResourceControl::flushMessageQueue(QByteArrayList() << "sink.dummy.instance1"));
0655 
0656         //Setup two folders with a mail each, ensure we only get the mail from the folder that matches the folder filter.
0657         Query query;
0658         query.filter<Mail::Folder>(Sink::Query().containsFilter<Folder::SpecialPurpose>("purpose1"));
0659         query.request<Mail::MessageId>();
0660         query.setFlags(Query::LiveQuery);
0661 
0662         auto model = Sink::Store::loadModel<Mail>(query);
0663         QTRY_COMPARE(model->rowCount(), 1);
0664 
0665         //This folder should not make it through the query
0666         {
0667             auto mail = Mail::createEntity<Mail>("sink.dummy.instance1");
0668             mail.setExtractedMessageId("mail3");
0669             mail.setFolder(folder2);
0670             VERIFYEXEC(Sink::Store::create(mail));
0671         }
0672 
0673         //But this one should
0674         {
0675             auto mail = Mail::createEntity<Mail>("sink.dummy.instance1");
0676             mail.setExtractedMessageId("mail4");
0677             mail.setFolder(folder1);
0678             VERIFYEXEC(Sink::Store::create(mail));
0679         }
0680         QTRY_COMPARE(model->rowCount(), 2);
0681 
0682     }
0683 
0684     void testResourceSubQuery()
0685     {
0686         using namespace Sink;
0687         using namespace Sink::ApplicationDomain;
0688 
0689         //Setup
0690         auto resource1 = ApplicationDomainType::createEntity<SinkResource>();
0691         resource1.setResourceType("sink.dummy");
0692         resource1.setCapabilities(QByteArrayList() << "cap1");
0693         VERIFYEXEC(Store::create(resource1));
0694 
0695         auto resource2 = ApplicationDomainType::createEntity<SinkResource>();
0696         resource2.setCapabilities(QByteArrayList() << "cap2");
0697         resource2.setResourceType("sink.dummy");
0698         VERIFYEXEC(Store::create(resource2));
0699 
0700         VERIFYEXEC(Sink::Store::create<Folder>(Folder{resource1.identifier()}));
0701         VERIFYEXEC(Sink::Store::create<Folder>(Folder{resource2.identifier()}));
0702 
0703         VERIFYEXEC(Sink::ResourceControl::flushMessageQueue(resource1.identifier()));
0704         VERIFYEXEC(Sink::ResourceControl::flushMessageQueue(resource2.identifier()));
0705 
0706         // We fetch before the data is available and rely on the live query mechanism to deliver the actual data
0707         auto folders = Sink::Store::read<Folder>(Sink::Query{}.resourceContainsFilter<SinkResource::Capabilities>("cap1"));
0708         QCOMPARE(folders.size(), 1);
0709 
0710         //TODO this should be part of the regular cleanup between tests
0711         VERIFYEXEC(Store::remove(resource1));
0712         VERIFYEXEC(Store::remove(resource2));
0713     }
0714 
0715     void testFilteredLiveResourceSubQuery()
0716     {
0717         using namespace Sink;
0718         using namespace Sink::ApplicationDomain;
0719 
0720         //Setup
0721         auto resource1 = ApplicationDomainType::createEntity<SinkResource>();
0722         resource1.setResourceType("sink.dummy");
0723         resource1.setCapabilities(QByteArrayList() << "cap1");
0724         VERIFYEXEC(Store::create(resource1));
0725         VERIFYEXEC(Store::create<Folder>(Folder{resource1.identifier()}));
0726         VERIFYEXEC(ResourceControl::flushMessageQueue(resource1.identifier()));
0727 
0728         auto model = Sink::Store::loadModel<Folder>(Query{Query::LiveQuery}.resourceContainsFilter<SinkResource::Capabilities>("cap1"));
0729         QTRY_COMPARE(model->rowCount(), 1);
0730 
0731         auto resource2 = ApplicationDomainType::createEntity<SinkResource>();
0732         resource2.setCapabilities(QByteArrayList() << "cap2");
0733         resource2.setResourceType("sink.dummy");
0734         VERIFYEXEC(Store::create(resource2));
0735         VERIFYEXEC(Store::create<Folder>(Folder{resource2.identifier()}));
0736         VERIFYEXEC(ResourceControl::flushMessageQueue(resource2.identifier()));
0737 
0738         //The new resource should be filtered and thus not make it in here
0739         QCOMPARE(model->rowCount(), 1);
0740 
0741         //TODO this should be part of the regular cleanup between tests
0742         VERIFYEXEC(Store::remove(resource1));
0743         VERIFYEXEC(Store::remove(resource2));
0744     }
0745 
0746     void testLivequeryUnmatchInThread()
0747     {
0748         // Setup
0749         auto folder1 = Folder::createEntity<Folder>("sink.dummy.instance1");
0750         VERIFYEXEC(Sink::Store::create<Folder>(folder1));
0751 
0752         auto folder2 = Folder::createEntity<Folder>("sink.dummy.instance1");
0753         VERIFYEXEC(Sink::Store::create<Folder>(folder2));
0754 
0755         auto mail1 = Mail::createEntity<Mail>("sink.dummy.instance1");
0756         mail1.setExtractedMessageId("mail1");
0757         mail1.setFolder(folder1);
0758         VERIFYEXEC(Sink::Store::create(mail1));
0759         // Ensure all local data is processed
0760         VERIFYEXEC(Sink::ResourceControl::flushMessageQueue("sink.dummy.instance1"));
0761 
0762         //Setup two folders with a mail each, ensure we only get the mail from the folder that matches the folder filter.
0763         Query query;
0764         query.setId("testLivequeryUnmatch");
0765         query.filter<Mail::Folder>(folder1);
0766         query.reduce<Mail::ThreadId>(Query::Reduce::Selector::max<Mail::Date>()).count("count").collect<Mail::Sender>("senders");
0767         query.sort<Mail::Date>();
0768         query.setFlags(Query::LiveQuery);
0769         auto model = Sink::Store::loadModel<Mail>(query);
0770         QTRY_COMPARE(model->rowCount(), 1);
0771 
0772         //After the modifcation the mail should have vanished.
0773         {
0774 
0775             mail1.setFolder(folder2);
0776             VERIFYEXEC(Sink::Store::modify(mail1));
0777         }
0778         VERIFYEXEC(Sink::ResourceControl::flushMessageQueue("sink.dummy.instance1"));
0779         QTRY_COMPARE(model->rowCount(), 0);
0780     }
0781 
0782     void testLivequeryFilterUnrelated()
0783     {
0784         // Setup
0785         auto folder1 = Folder::createEntity<Folder>("sink.dummy.instance1");
0786         VERIFYEXEC(Sink::Store::create<Folder>(folder1));
0787 
0788         auto mail1 = Mail::createEntity<Mail>("sink.dummy.instance1");
0789         mail1.setExtractedMessageId("mail1");
0790         mail1.setFolder(folder1);
0791         VERIFYEXEC(Sink::Store::create(mail1));
0792         VERIFYEXEC(Sink::ResourceControl::flushMessageQueue("sink.dummy.instance1"));
0793 
0794         Query query;
0795         query.setId("testLivequeryUnmatch");
0796         query.filter(mail1.identifier());
0797         query.setFlags(Query::LiveQuery);
0798         auto model = Sink::Store::loadModel<Mail>(query);
0799         QTRY_COMPARE(model->rowCount(), 1);
0800 
0801         //Create another mail and make sure it doesn't show up in the query
0802         auto mail2 = Mail::createEntity<Mail>("sink.dummy.instance1");
0803         mail2.setExtractedMessageId("mail2");
0804         mail2.setFolder(folder1);
0805         VERIFYEXEC(Sink::Store::create(mail2));
0806         VERIFYEXEC(Sink::ResourceControl::flushMessageQueue("sink.dummy.instance1"));
0807 
0808         QCOMPARE(model->rowCount(), 1);
0809 
0810         //A removal should still make it though
0811         {
0812             VERIFYEXEC(Sink::Store::remove(mail1));
0813         }
0814         VERIFYEXEC(Sink::ResourceControl::flushMessageQueue("sink.dummy.instance1"));
0815         QTRY_COMPARE(model->rowCount(), 0);
0816     }
0817 
0818 
0819     void testLivequeryRemoveOneInThread()
0820     {
0821         // Setup
0822         auto folder1 = Folder::createEntity<Folder>("sink.dummy.instance1");
0823         VERIFYEXEC(Sink::Store::create<Folder>(folder1));
0824 
0825         auto mail1 = Mail::createEntity<Mail>("sink.dummy.instance1");
0826         mail1.setExtractedMessageId("mail1");
0827         mail1.setFolder(folder1);
0828         VERIFYEXEC(Sink::Store::create(mail1));
0829         auto mail2 = Mail::createEntity<Mail>("sink.dummy.instance1");
0830         mail2.setExtractedMessageId("mail2");
0831         mail2.setFolder(folder1);
0832         VERIFYEXEC(Sink::Store::create(mail2));
0833         // Ensure all local data is processed
0834         VERIFYEXEC(Sink::ResourceControl::flushMessageQueue("sink.dummy.instance1"));
0835 
0836         //Setup two folders with a mail each, ensure we only get the mail from the folder that matches the folder filter.
0837         Query query;
0838         query.setId("testLivequeryUnmatch");
0839         query.reduce<Mail::Folder>(Query::Reduce::Selector::max<Mail::Date>()).count("count").collect<Mail::Sender>("senders");
0840         query.sort<Mail::Date>();
0841         query.setFlags(Query::LiveQuery);
0842         auto model = Sink::Store::loadModel<Mail>(query);
0843         QTRY_COMPARE(model->rowCount(), 1);
0844         QCOMPARE(model->data(model->index(0, 0, QModelIndex{}), Sink::Store::DomainObjectRole).value<Mail::Ptr>()->getProperty("count").toInt(), 2);
0845 
0846         //After the removal, the thread size should be reduced by one
0847         {
0848 
0849             VERIFYEXEC(Sink::Store::remove(mail1));
0850         }
0851         VERIFYEXEC(Sink::ResourceControl::flushMessageQueue("sink.dummy.instance1"));
0852         QTRY_COMPARE(model->rowCount(), 1);
0853         QTRY_COMPARE(model->data(model->index(0, 0, QModelIndex{}), Sink::Store::DomainObjectRole).value<Mail::Ptr>()->getProperty("count").toInt(), 1);
0854 
0855         //After the second removal, the thread should be gone
0856         {
0857             VERIFYEXEC(Sink::Store::remove(mail2));
0858         }
0859         VERIFYEXEC(Sink::ResourceControl::flushMessageQueue("sink.dummy.instance1"));
0860         QTRY_COMPARE(model->rowCount(), 0);
0861     }
0862 
0863     void testDontUpdateNonLiveQuery()
0864     {
0865         // Setup
0866         auto folder1 = Folder::createEntity<Folder>("sink.dummy.instance1");
0867         VERIFYEXEC(Sink::Store::create<Folder>(folder1));
0868 
0869         auto mail1 = Mail::createEntity<Mail>("sink.dummy.instance1");
0870         mail1.setExtractedMessageId("mail1");
0871         mail1.setFolder(folder1);
0872         mail1.setUnread(false);
0873         VERIFYEXEC(Sink::Store::create(mail1));
0874 
0875         // Ensure all local data is processed
0876         VERIFYEXEC(Sink::ResourceControl::flushMessageQueue("sink.dummy.instance1"));
0877 
0878         Query query;
0879         //Not a live query
0880         query.setFlags(Query::Flags{});
0881         query.setId("testNoLiveQuery");
0882         query.filter<Mail::Folder>(folder1);
0883         query.reduce<Mail::ThreadId>(Query::Reduce::Selector::max<Mail::Date>()).count("count").collect<Mail::Sender>("senders");
0884         query.sort<Mail::Date>();
0885         query.request<Mail::Unread>();
0886         QVERIFY(!query.liveQuery());
0887 
0888         auto model = Sink::Store::loadModel<Mail>(query);
0889         QTRY_COMPARE(model->rowCount(), 1);
0890 
0891         //After the modifcation the mail should have vanished.
0892         {
0893             mail1.setUnread(true);
0894             VERIFYEXEC(Sink::Store::modify(mail1));
0895         }
0896         VERIFYEXEC(Sink::ResourceControl::flushMessageQueue("sink.dummy.instance1"));
0897         QTRY_COMPARE(model->rowCount(), 1);
0898         auto mail = model->data(model->index(0, 0, QModelIndex{}), Sink::Store::DomainObjectRole).value<Mail::Ptr>();
0899         QTest::qWait(100);
0900         QCOMPARE(mail->getUnread(), false);
0901     }
0902 
0903     void testLivequeryModifcationUpdateInThread()
0904     {
0905         // Setup
0906         auto folder1 = Folder::createEntity<Folder>("sink.dummy.instance1");
0907         VERIFYEXEC(Sink::Store::create<Folder>(folder1));
0908 
0909         auto folder2 = Folder::createEntity<Folder>("sink.dummy.instance1");
0910         VERIFYEXEC(Sink::Store::create<Folder>(folder2));
0911 
0912         auto mail1 = Mail::createEntity<Mail>("sink.dummy.instance1");
0913         mail1.setExtractedMessageId("mail1");
0914         mail1.setFolder(folder1);
0915         mail1.setUnread(false);
0916         VERIFYEXEC(Sink::Store::create(mail1));
0917 
0918         // Ensure all local data is processed
0919         VERIFYEXEC(Sink::ResourceControl::flushMessageQueue("sink.dummy.instance1"));
0920 
0921         Query query;
0922         query.setId("testLivequeryUnmatch");
0923         query.filter<Mail::Folder>(folder1);
0924         query.reduce<Mail::ThreadId>(Query::Reduce::Selector::max<Mail::Date>()).count("count").collect<Mail::Folder>("folders");
0925         query.sort<Mail::Date>();
0926         query.setFlags(Query::LiveQuery);
0927         query.request<Mail::Unread>();
0928 
0929         auto model = Sink::Store::loadModel<Mail>(query);
0930         QTRY_COMPARE(model->rowCount(), 1);
0931 
0932         //After the modifcation the mail should have vanished.
0933         {
0934             mail1.setUnread(true);
0935             VERIFYEXEC(Sink::Store::modify(mail1));
0936         }
0937         VERIFYEXEC(Sink::ResourceControl::flushMessageQueue("sink.dummy.instance1"));
0938         QTRY_COMPARE(model->rowCount(), 1);
0939         auto mail = model->data(model->index(0, 0, QModelIndex{}), Sink::Store::DomainObjectRole).value<Mail::Ptr>();
0940         QTRY_COMPARE(mail->getUnread(), true);
0941         QCOMPARE(mail->getProperty("count").toInt(), 1);
0942         QCOMPARE(mail->getProperty("folders").toList().size(), 1);
0943     }
0944 
0945     void testReductionUpdate()
0946     {
0947         // Setup
0948         auto folder1 = Folder::createEntity<Folder>("sink.dummy.instance1");
0949         VERIFYEXEC(Sink::Store::create<Folder>(folder1));
0950 
0951         auto folder2 = Folder::createEntity<Folder>("sink.dummy.instance1");
0952         VERIFYEXEC(Sink::Store::create<Folder>(folder2));
0953 
0954         QDateTime now{QDate{2017, 2, 3}, QTime{10, 0, 0}};
0955         QDateTime later{QDate{2017, 2, 3}, QTime{11, 0, 0}};
0956 
0957         auto mail1 = Mail::createEntity<Mail>("sink.dummy.instance1");
0958         mail1.setExtractedMessageId("mail1");
0959         mail1.setFolder(folder1);
0960         mail1.setUnread(false);
0961         mail1.setExtractedDate(now);
0962         VERIFYEXEC(Sink::Store::create(mail1));
0963 
0964         // Ensure all local data is processed
0965         VERIFYEXEC(Sink::ResourceControl::flushMessageQueue("sink.dummy.instance1"));
0966 
0967         Query query;
0968         query.setId("testLivequeryUnmatch");
0969         query.setFlags(Query::LiveQuery);
0970         query.filter<Mail::Folder>(folder1);
0971         query.reduce<Mail::Folder>(Query::Reduce::Selector::max<Mail::Date>()).count("count").collect<Mail::Folder>("folders");
0972         query.sort<Mail::Date>();
0973         query.request<Mail::Unread>();
0974         query.request<Mail::MessageId>();
0975 
0976         auto model = Sink::Store::loadModel<Mail>(query);
0977         QTRY_VERIFY(model->data(QModelIndex(), Sink::Store::ChildrenFetchedRole).toBool());
0978         QCOMPARE(model->rowCount(), 1);
0979 
0980         QSignalSpy insertedSpy(model.data(), &QAbstractItemModel::rowsInserted);
0981         QSignalSpy removedSpy(model.data(), &QAbstractItemModel::rowsRemoved);
0982         QSignalSpy changedSpy(model.data(), &QAbstractItemModel::dataChanged);
0983         QSignalSpy layoutChangedSpy(model.data(), &QAbstractItemModel::layoutChanged);
0984         QSignalSpy resetSpy(model.data(), &QAbstractItemModel::modelReset);
0985 
0986         //The leader should change to mail2 after the modification
0987         {
0988             auto mail2 = Mail::createEntity<Mail>("sink.dummy.instance1");
0989             mail2.setExtractedMessageId("mail2");
0990             mail2.setFolder(folder1);
0991             mail2.setUnread(false);
0992             mail2.setExtractedDate(later);
0993             VERIFYEXEC(Sink::Store::create(mail2));
0994         }
0995 
0996         VERIFYEXEC(Sink::ResourceControl::flushMessageQueue("sink.dummy.instance1"));
0997         QTRY_COMPARE(model->rowCount(), 1);
0998         auto mail = model->data(model->index(0, 0, QModelIndex{}), Sink::Store::DomainObjectRole).value<Mail::Ptr>();
0999         QTRY_COMPARE(mail->getMessageId(), QByteArray{"mail2"});
1000         QCOMPARE(mail->getProperty("count").toInt(), 2);
1001         QCOMPARE(mail->getProperty("folders").toList().size(), 2);
1002 
1003         //This should eventually be just one modification instead of remove + add (See datastorequery reduce component)
1004         QCOMPARE(insertedSpy.size(), 1);
1005         QCOMPARE(removedSpy.size(), 1);
1006         QCOMPARE(changedSpy.size(), 0);
1007         QCOMPARE(layoutChangedSpy.size(), 0);
1008         QCOMPARE(resetSpy.size(), 0);
1009     }
1010 
1011     void testFilteredReductionUpdate()
1012     {
1013         // Setup
1014         auto folder1 = Folder::createEntity<Folder>("sink.dummy.instance1");
1015         VERIFYEXEC(Sink::Store::create<Folder>(folder1));
1016 
1017         auto folder2 = Folder::createEntity<Folder>("sink.dummy.instance1");
1018         VERIFYEXEC(Sink::Store::create<Folder>(folder2));
1019 
1020         // Ensure all local data is processed
1021         VERIFYEXEC(Sink::ResourceControl::flushMessageQueue("sink.dummy.instance1"));
1022 
1023         Query query;
1024         query.setId("testFilteredReductionUpdate");
1025         query.setFlags(Query::LiveQuery);
1026         query.filter<Mail::Folder>(folder1);
1027         query.reduce<Mail::Folder>(Query::Reduce::Selector::max<Mail::Date>()).count("count").collect<Mail::Folder>("folders");
1028         query.sort<Mail::Date>();
1029 
1030         auto model = Sink::Store::loadModel<Mail>(query);
1031         QTRY_VERIFY(model->data(QModelIndex(), Sink::Store::ChildrenFetchedRole).toBool());
1032         QCOMPARE(model->rowCount(), 0);
1033 
1034         QSignalSpy insertedSpy(model.data(), &QAbstractItemModel::rowsInserted);
1035         QSignalSpy removedSpy(model.data(), &QAbstractItemModel::rowsRemoved);
1036         QSignalSpy changedSpy(model.data(), &QAbstractItemModel::dataChanged);
1037         QSignalSpy layoutChangedSpy(model.data(), &QAbstractItemModel::layoutChanged);
1038         QSignalSpy resetSpy(model.data(), &QAbstractItemModel::modelReset);
1039 
1040         //Ensure we don't end up with a mail in the thread that was filtered
1041         //This tests the case of an otherwise emtpy thread on purpose.
1042         {
1043             auto mail = Mail::createEntity<Mail>("sink.dummy.instance1");
1044             mail.setExtractedMessageId("filtered");
1045             mail.setFolder(folder2);
1046             mail.setExtractedDate(QDateTime{QDate{2017, 2, 3}, QTime{11, 0, 0}});
1047             VERIFYEXEC(Sink::Store::create(mail));
1048         }
1049 
1050         VERIFYEXEC(Sink::ResourceControl::flushMessageQueue("sink.dummy.instance1"));
1051         QCOMPARE(model->rowCount(), 0);
1052 
1053         //Ensure the non-filtered still get through.
1054         {
1055             auto mail = Mail::createEntity<Mail>("sink.dummy.instance1");
1056             mail.setExtractedMessageId("not-filtered");
1057             mail.setFolder(folder1);
1058             mail.setExtractedDate(QDateTime{QDate{2017, 2, 3}, QTime{11, 0, 0}});
1059             VERIFYEXEC(Sink::Store::create(mail));
1060         }
1061         VERIFYEXEC(Sink::ResourceControl::flushMessageQueue("sink.dummy.instance1"));
1062         QTRY_COMPARE(model->rowCount(), 1);
1063     }
1064 
1065     /*
1066      * Two messages in the same thread. The first get's filtered, the second one makes it.
1067      */
1068     void testFilteredReductionUpdateInSameThread()
1069     {
1070         // Setup
1071         auto folder1 = Folder::createEntity<Folder>("sink.dummy.instance1");
1072         VERIFYEXEC(Sink::Store::create<Folder>(folder1));
1073 
1074         auto folder2 = Folder::createEntity<Folder>("sink.dummy.instance1");
1075         VERIFYEXEC(Sink::Store::create<Folder>(folder2));
1076 
1077         // Ensure all local data is processed
1078         VERIFYEXEC(Sink::ResourceControl::flushMessageQueue("sink.dummy.instance1"));
1079 
1080         Query query;
1081         query.setId("testFilteredReductionUpdate");
1082         query.setFlags(Query::LiveQuery);
1083         query.filter<Mail::Folder>(folder1);
1084         query.reduce<Mail::MessageId>(Query::Reduce::Selector::max<Mail::Date>()).count("count");
1085 
1086         auto model = Sink::Store::loadModel<Mail>(query);
1087         QTRY_VERIFY(model->data(QModelIndex(), Sink::Store::ChildrenFetchedRole).toBool());
1088         QCOMPARE(model->rowCount(), 0);
1089 
1090         //The first message will be filtered (but would be aggreagted together with the message that passes)
1091         {
1092             auto mail = Mail::createEntity<Mail>("sink.dummy.instance1");
1093             mail.setExtractedMessageId("aggregatedId");
1094             mail.setFolder(folder2);
1095             VERIFYEXEC(Sink::Store::create(mail));
1096 
1097             //Ensure that we can deal with a modification to the filtered message
1098             mail.setUnread(true);
1099             VERIFYEXEC(Sink::Store::modify(mail));
1100         }
1101 
1102         VERIFYEXEC(Sink::ResourceControl::flushMessageQueue("sink.dummy.instance1"));
1103         QCOMPARE(model->rowCount(), 0);
1104 
1105         //Ensure the non-filtered still gets through.
1106         {
1107             auto mail = Mail::createEntity<Mail>("sink.dummy.instance1");
1108             mail.setExtractedMessageId("aggregatedId");
1109             mail.setFolder(folder1);
1110             VERIFYEXEC(Sink::Store::create(mail));
1111 
1112             //Ensure that we can deal with a modification to the filtered message
1113             mail.setUnread(true);
1114             VERIFYEXEC(Sink::Store::modify(mail));
1115         }
1116         VERIFYEXEC(Sink::ResourceControl::flushMessageQueue("sink.dummy.instance1"));
1117         QTRY_COMPARE(model->rowCount(), 1);
1118         QCOMPARE(model->data(model->index(0, 0, QModelIndex{}), Sink::Store::DomainObjectRole).value<Mail::Ptr>()->getProperty("count").toInt(), 1);
1119 
1120         //Ensure another entity still results in a modification
1121         {
1122             auto mail = Mail::createEntity<Mail>("sink.dummy.instance1");
1123             mail.setExtractedMessageId("aggregatedId");
1124             mail.setFolder(folder1);
1125             VERIFYEXEC(Sink::Store::create(mail));
1126         }
1127         VERIFYEXEC(Sink::ResourceControl::flushMessageQueue("sink.dummy.instance1"));
1128         QTRY_COMPARE(model->data(model->index(0, 0, QModelIndex{}), Sink::Store::DomainObjectRole).value<Mail::Ptr>()->getProperty("count").toInt(), 2);
1129     }
1130 
1131     void testBloom()
1132     {
1133         // Setup
1134         auto folder1 = Folder::createEntity<Folder>("sink.dummy.instance1");
1135         VERIFYEXEC(Sink::Store::create<Folder>(folder1));
1136 
1137         auto folder2 = Folder::createEntity<Folder>("sink.dummy.instance1");
1138         VERIFYEXEC(Sink::Store::create<Folder>(folder2));
1139 
1140         auto mail1 = Mail::createEntity<Mail>("sink.dummy.instance1");
1141         mail1.setExtractedMessageId("mail1");
1142         mail1.setFolder(folder1);
1143         VERIFYEXEC(Sink::Store::create(mail1));
1144 
1145         // Ensure all local data is processed
1146         VERIFYEXEC(Sink::ResourceControl::flushMessageQueue("sink.dummy.instance1"));
1147 
1148         {
1149             auto mail = Mail::createEntity<Mail>("sink.dummy.instance1");
1150             mail.setExtractedMessageId("mail2");
1151             mail.setFolder(folder1);
1152             VERIFYEXEC(Sink::Store::create(mail));
1153         }
1154         {
1155             auto mail = Mail::createEntity<Mail>("sink.dummy.instance1");
1156             mail.setExtractedMessageId("mail3");
1157             mail.setFolder(folder2);
1158             VERIFYEXEC(Sink::Store::create(mail));
1159         }
1160         VERIFYEXEC(Sink::ResourceControl::flushMessageQueue("sink.dummy.instance1"));
1161 
1162         Query query;
1163         query.resourceFilter("sink.dummy.instance1");
1164         query.setId("testFilterCreationInThread");
1165         query.filter(mail1.identifier());
1166         query.bloom<Mail::Folder>();
1167         query.request<Mail::Folder>();
1168 
1169         auto model = Sink::Store::loadModel<Mail>(query);
1170         QTRY_VERIFY(model->data(QModelIndex(), Sink::Store::ChildrenFetchedRole).toBool());
1171         QCOMPARE(model->rowCount(), 2);
1172     }
1173 
1174     //Live query bloom filter
1175     void testLivequeryFilterCreationInThread()
1176     {
1177         // Setup
1178         auto folder1 = Folder::createEntity<Folder>("sink.dummy.instance1");
1179         VERIFYEXEC(Sink::Store::create<Folder>(folder1));
1180 
1181         auto folder2 = Folder::createEntity<Folder>("sink.dummy.instance1");
1182         VERIFYEXEC(Sink::Store::create<Folder>(folder2));
1183 
1184         auto mail1 = Mail::createEntity<Mail>("sink.dummy.instance1");
1185         mail1.setExtractedMessageId("mail1");
1186         mail1.setFolder(folder1);
1187         mail1.setUnread(true);
1188         VERIFYEXEC(Sink::Store::create(mail1));
1189 
1190         // Ensure all local data is processed
1191         VERIFYEXEC(Sink::ResourceControl::flushMessageQueue("sink.dummy.instance1"));
1192 
1193         Query query;
1194         query.setId("testFilterCreationInThread");
1195         query.resourceFilter("sink.dummy.instance1");
1196         query.filter(mail1.identifier());
1197         query.bloom<Mail::Folder>();
1198         query.sort<Mail::Date>();
1199         query.setFlags(Query::LiveQuery);
1200         query.request<Mail::Unread>();
1201         query.request<Mail::Folder>();
1202 
1203         auto model = Sink::Store::loadModel<Mail>(query);
1204         QTRY_VERIFY(model->data(QModelIndex(), Sink::Store::ChildrenFetchedRole).toBool());
1205         QCOMPARE(model->rowCount(), 1);
1206 
1207         QSignalSpy insertedSpy(model.data(), &QAbstractItemModel::rowsInserted);
1208         QSignalSpy removedSpy(model.data(), &QAbstractItemModel::rowsRemoved);
1209         QSignalSpy changedSpy(model.data(), &QAbstractItemModel::dataChanged);
1210         QSignalSpy layoutChangedSpy(model.data(), &QAbstractItemModel::layoutChanged);
1211         QSignalSpy resetSpy(model.data(), &QAbstractItemModel::modelReset);
1212 
1213         //This modification should make it through
1214         {
1215             //This should not trigger an entity already in model warning
1216             mail1.setUnread(false);
1217             VERIFYEXEC(Sink::Store::modify(mail1));
1218         }
1219 
1220         //This mail should make it through
1221         {
1222             auto mail = Mail::createEntity<Mail>("sink.dummy.instance1");
1223             mail.setExtractedMessageId("mail2");
1224             mail.setFolder(folder1);
1225             VERIFYEXEC(Sink::Store::create(mail));
1226         }
1227 
1228         //This mail shouldn't make it through
1229         {
1230             auto mail = Mail::createEntity<Mail>("sink.dummy.instance1");
1231             mail.setExtractedMessageId("mail3");
1232             mail.setFolder(folder2);
1233             VERIFYEXEC(Sink::Store::create(mail));
1234         }
1235         VERIFYEXEC(Sink::ResourceControl::flushMessageQueue("sink.dummy.instance1"));
1236 
1237         QTRY_COMPARE(model->rowCount(), 2);
1238         QTest::qWait(100);
1239         QCOMPARE(model->rowCount(), 2);
1240 
1241         //From mail2
1242         QCOMPARE(insertedSpy.size(), 1);
1243         QCOMPARE(removedSpy.size(), 0);
1244         //From the modification
1245         QCOMPARE(changedSpy.size(), 1);
1246         QCOMPARE(layoutChangedSpy.size(), 0);
1247         QCOMPARE(resetSpy.size(), 0);
1248     }
1249 
1250     //Live query reduction
1251     void testLivequeryThreadleaderChange()
1252     {
1253         // Setup
1254         auto folder1 = Folder::createEntity<Folder>("sink.dummy.instance1");
1255         VERIFYEXEC(Sink::Store::create<Folder>(folder1));
1256 
1257         auto folder2 = Folder::createEntity<Folder>("sink.dummy.instance1");
1258         VERIFYEXEC(Sink::Store::create<Folder>(folder2));
1259 
1260         QDateTime earlier{QDate{2017, 2, 3}, QTime{9, 0, 0}};
1261         QDateTime now{QDate{2017, 2, 3}, QTime{10, 0, 0}};
1262         QDateTime later{QDate{2017, 2, 3}, QTime{11, 0, 0}};
1263 
1264         auto mail1 = Mail::createEntity<Mail>("sink.dummy.instance1");
1265         mail1.setExtractedMessageId("mail1");
1266         mail1.setFolder(folder1);
1267         mail1.setExtractedDate(now);
1268         VERIFYEXEC(Sink::Store::create(mail1));
1269 
1270         // Ensure all local data is processed
1271         VERIFYEXEC(Sink::ResourceControl::flushMessageQueue("sink.dummy.instance1"));
1272 
1273         Query query;
1274         query.setId("testLivequeryThreadleaderChange");
1275         query.setFlags(Query::LiveQuery);
1276         query.reduce<Mail::Folder>(Query::Reduce::Selector::max<Mail::Date>()).count("count").collect<Mail::Folder>("folders");
1277         query.sort<Mail::Date>();
1278         query.request<Mail::MessageId>();
1279 
1280         auto model = Sink::Store::loadModel<Mail>(query);
1281         QTRY_VERIFY(model->data(QModelIndex(), Sink::Store::ChildrenFetchedRole).toBool());
1282         QCOMPARE(model->rowCount(), 1);
1283 
1284         QSignalSpy insertedSpy(model.data(), &QAbstractItemModel::rowsInserted);
1285         QSignalSpy removedSpy(model.data(), &QAbstractItemModel::rowsRemoved);
1286         QSignalSpy changedSpy(model.data(), &QAbstractItemModel::dataChanged);
1287         QSignalSpy layoutChangedSpy(model.data(), &QAbstractItemModel::layoutChanged);
1288         QSignalSpy resetSpy(model.data(), &QAbstractItemModel::modelReset);
1289 
1290         //The leader shouldn't change to mail2 after the modification
1291         {
1292             auto mail2 = Mail::createEntity<Mail>("sink.dummy.instance1");
1293             mail2.setExtractedMessageId("mail2");
1294             mail2.setFolder(folder1);
1295             mail2.setExtractedDate(earlier);
1296             VERIFYEXEC(Sink::Store::create(mail2));
1297         }
1298 
1299         VERIFYEXEC(Sink::ResourceControl::flushMessageQueue("sink.dummy.instance1"));
1300         QTRY_COMPARE(model->rowCount(), 1);
1301         {
1302             auto mail = model->data(model->index(0, 0, QModelIndex{}), Sink::Store::DomainObjectRole).value<Mail::Ptr>();
1303             QTRY_COMPARE(mail->getMessageId(), QByteArray{"mail1"});
1304             QTRY_COMPARE(mail->getProperty("count").toInt(), 2);
1305             QCOMPARE(mail->getProperty("folders").toList().size(), 2);
1306         }
1307 
1308 
1309         QCOMPARE(insertedSpy.size(), 0);
1310         QCOMPARE(removedSpy.size(), 0);
1311         QCOMPARE(changedSpy.size(), 1);
1312         QCOMPARE(layoutChangedSpy.size(), 0);
1313         QCOMPARE(resetSpy.size(), 0);
1314 
1315         //The leader should change to mail3 after the modification
1316         {
1317             auto mail3 = Mail::createEntity<Mail>("sink.dummy.instance1");
1318             mail3.setExtractedMessageId("mail3");
1319             mail3.setFolder(folder1);
1320             mail3.setExtractedDate(later);
1321             VERIFYEXEC(Sink::Store::create(mail3));
1322         }
1323 
1324         VERIFYEXEC(Sink::ResourceControl::flushMessageQueue("sink.dummy.instance1"));
1325         QTRY_COMPARE(model->rowCount(), 1);
1326         {
1327             auto mail = model->data(model->index(0, 0, QModelIndex{}), Sink::Store::DomainObjectRole).value<Mail::Ptr>();
1328             QTRY_COMPARE(mail->getMessageId(), QByteArray{"mail3"});
1329             QCOMPARE(mail->getProperty("count").toInt(), 3);
1330             QCOMPARE(mail->getProperty("folders").toList().size(), 3);
1331         }
1332 
1333         //This should eventually be just one modification instead of remove + add (See datastorequery reduce component)
1334         QCOMPARE(insertedSpy.size(), 1);
1335         QCOMPARE(removedSpy.size(), 1);
1336         QCOMPARE(changedSpy.size(), 1);
1337         QCOMPARE(layoutChangedSpy.size(), 0);
1338         QCOMPARE(resetSpy.size(), 0);
1339 
1340         //Nothing should change on third mail in separate folder
1341         {
1342             auto mail = Mail::createEntity<Mail>("sink.dummy.instance1");
1343             mail.setExtractedMessageId("mail4");
1344             mail.setFolder(folder2);
1345             mail.setExtractedDate(now);
1346             VERIFYEXEC(Sink::Store::create(mail));
1347         }
1348 
1349         VERIFYEXEC(Sink::ResourceControl::flushMessageQueue("sink.dummy.instance1"));
1350         QTRY_COMPARE(model->rowCount(), 2);
1351 
1352         //This should eventually be just one modification instead of remove + add (See datastorequery reduce component)
1353         QCOMPARE(insertedSpy.size(), 2);
1354         QCOMPARE(removedSpy.size(), 1);
1355         QCOMPARE(changedSpy.size(), 1);
1356         QCOMPARE(layoutChangedSpy.size(), 0);
1357         QCOMPARE(resetSpy.size(), 0);
1358     }
1359 
1360     /*
1361      * Ensure that we handle the situation properly if the thread-leader doesn't match a property filter.
1362      */
1363     void testFilteredThreadLeader()
1364     {
1365         // Setup
1366         auto folder1 = Folder::createEntity<Folder>("sink.dummy.instance1");
1367         VERIFYEXEC(Sink::Store::create<Folder>(folder1));
1368 
1369         auto folder2 = Folder::createEntity<Folder>("sink.dummy.instance1");
1370         VERIFYEXEC(Sink::Store::create<Folder>(folder2));
1371 
1372         QDateTime earlier{QDate{2017, 2, 3}, QTime{9, 0, 0}};
1373         QDateTime now{QDate{2017, 2, 3}, QTime{10, 0, 0}};
1374         QDateTime later{QDate{2017, 2, 3}, QTime{11, 0, 0}};
1375 
1376         auto createMail = [] (const QByteArray &messageid, const Folder &folder, const QDateTime &date, bool important) {
1377             auto mail = Mail::createEntity<Mail>("sink.dummy.instance1");
1378             mail.setExtractedSubject(messageid);
1379             mail.setExtractedMessageId(messageid);
1380             mail.setFolder(folder);
1381             mail.setExtractedDate(date);
1382             mail.setImportant(important);
1383             return mail;
1384         };
1385 
1386         VERIFYEXEC(Sink::Store::create(createMail("mail1", folder1, now, false)));
1387         VERIFYEXEC(Sink::Store::create(createMail("mail2", folder1, earlier, false)));
1388         VERIFYEXEC(Sink::Store::create(createMail("mail3", folder1, later, true)));
1389 
1390         // Ensure all local data is processed
1391         VERIFYEXEC(Sink::ResourceControl::flushMessageQueue("sink.dummy.instance1"));
1392 
1393         Query query;
1394         query.setId("testLivequeryThreadleaderChange");
1395         query.setFlags(Query::LiveQuery);
1396         query.reduce<Mail::Folder>(Query::Reduce::Selector::max<Mail::Date>())
1397             .count()
1398             .collect<Mail::Folder>()
1399             .select<Mail::Subject>(Query::Reduce::Selector::Min, "subjectSelected");
1400         query.sort<Mail::Date>();
1401         query.request<Mail::MessageId>();
1402         query.request<Mail::Subject>();
1403         query.filter<Mail::Important>(false);
1404 
1405         auto model = Sink::Store::loadModel<Mail>(query);
1406         QTRY_VERIFY(model->data(QModelIndex(), Sink::Store::ChildrenFetchedRole).toBool());
1407 
1408         QCOMPARE(model->rowCount(), 1);
1409 
1410         {
1411             auto mail = model->data(model->index(0, 0, QModelIndex{}), Sink::Store::DomainObjectRole).value<Mail::Ptr>();
1412             QCOMPARE(mail->getMessageId(), QByteArray{"mail1"});
1413             QCOMPARE(mail->count(), 2);
1414             QCOMPARE(mail->getCollectedProperty<Mail::Folder>().size(), 2);
1415             QCOMPARE(mail->getProperty("subjectSelected").toString(), QString{"mail2"});
1416         }
1417     }
1418 
1419     void testQueryRunnerDontMissUpdates()
1420     {
1421         // Setup
1422         auto folder1 = Folder::createEntity<Folder>("sink.dummy.instance1");
1423         VERIFYEXEC(Sink::Store::create<Folder>(folder1));
1424 
1425         QDateTime now{QDate{2017, 2, 3}, QTime{10, 0, 0}};
1426 
1427         auto createMail = [] (const QByteArray &messageid, const Folder &folder, const QDateTime &date, bool important) {
1428             auto mail = Mail::createEntity<Mail>("sink.dummy.instance1");
1429             mail.setExtractedMessageId(messageid);
1430             mail.setFolder(folder);
1431             mail.setExtractedDate(date);
1432             mail.setImportant(important);
1433             return mail;
1434         };
1435 
1436         VERIFYEXEC(Sink::Store::create(createMail("mail1", folder1, now, false)));
1437 
1438         // Ensure all local data is processed
1439         VERIFYEXEC(Sink::ResourceControl::flushMessageQueue("sink.dummy.instance1"));
1440 
1441         Query query;
1442         query.setFlags(Query::LiveQuery);
1443 
1444         Sink::ResourceContext resourceContext{"sink.dummy.instance1", "sink.dummy", Sink::AdaptorFactoryRegistry::instance().getFactories("sink.dummy")};
1445         Sink::Log::Context logCtx;
1446         auto runner = new QueryRunner<Mail>(query, resourceContext, ApplicationDomain::getTypeName<Mail>(), logCtx);
1447         runner->delayNextQuery();
1448 
1449         auto emitter = runner->emitter();
1450         QList<Mail::Ptr> added;
1451         emitter->onAdded([&](Mail::Ptr mail) {
1452             added << mail;
1453         });
1454 
1455         emitter->fetch();
1456         VERIFYEXEC(Sink::Store::create(createMail("mail2", folder1, now, false)));
1457         QTRY_COMPARE(added.size(), 2);
1458 
1459         runner->delayNextQuery();
1460         VERIFYEXEC(Sink::Store::create(createMail("mail3", folder1, now, false)));
1461         //The second revision update is supposed to come in while the initial revision update is still in the query.
1462         //So wait a bit to make sure the query is currently runnning.
1463         QTest::qWait(500);
1464         VERIFYEXEC(Sink::Store::create(createMail("mail4", folder1, now, false)));
1465         QTRY_COMPARE(added.size(), 4);
1466     }
1467 
1468     /*
1469      * This test excercises the scenario where a fetchMore is triggered after
1470      * the revision is already updated in storage, but the incremental query was not run yet.
1471      * This resulted in lost modification updates.
1472      * It also exercised the lower bound protection, because we delay the update, and thus the resource will already have cleaned up.
1473      */
1474     void testQueryRunnerDontMissUpdatesWithFetchMore()
1475     {
1476         // Setup
1477         auto folder1 = Folder::createEntity<Folder>("sink.dummy.instance1");
1478         folder1.setName("name1");
1479         VERIFYEXEC(Sink::Store::create<Folder>(folder1));
1480         VERIFYEXEC(Sink::ResourceControl::flushMessageQueue("sink.dummy.instance1"));
1481 
1482         Query query;
1483         query.setFlags(Query::LiveQuery);
1484 
1485         Sink::ResourceContext resourceContext{"sink.dummy.instance1", "sink.dummy", Sink::AdaptorFactoryRegistry::instance().getFactories("sink.dummy")};
1486         Sink::Log::Context logCtx;
1487         auto runner = new QueryRunner<Folder>(query, resourceContext, ApplicationDomain::getTypeName<Folder>(), logCtx);
1488 
1489         auto emitter = runner->emitter();
1490         QList<Folder::Ptr> added;
1491         emitter->onAdded([&](Folder::Ptr folder) {
1492             added << folder;
1493         });
1494         QList<Folder::Ptr> modified;
1495         emitter->onModified([&](Folder::Ptr folder) {
1496             modified << folder;
1497         });
1498         QList<Folder::Ptr> removed;
1499         emitter->onRemoved([&](Folder::Ptr folder) {
1500             removed << folder;
1501         });
1502 
1503         emitter->fetch();
1504         QTRY_COMPARE(added.size(), 1);
1505         QCOMPARE(modified.size(), 0);
1506         QCOMPARE(removed.size(), 0);
1507 
1508         runner->ignoreRevisionChanges();
1509 
1510         folder1.setName("name2");
1511         VERIFYEXEC(Sink::Store::modify<Folder>(folder1));
1512         VERIFYEXEC(Sink::ResourceControl::flushMessageQueue("sink.dummy.instance1"));
1513 
1514         emitter->fetch();
1515 
1516         runner->triggerRevisionChange();
1517 
1518         QTRY_COMPARE(added.size(), 1);
1519         QTRY_COMPARE(modified.size(), 1);
1520         QCOMPARE(removed.size(), 0);
1521 
1522         runner->ignoreRevisionChanges();
1523         VERIFYEXEC(Sink::Store::remove<Folder>(folder1));
1524         VERIFYEXEC(Sink::ResourceControl::flushMessageQueue("sink.dummy.instance1"));
1525         runner->triggerRevisionChange();
1526         QTRY_COMPARE(removed.size(), 1);
1527     }
1528 
1529     /*
1530      * This test is here to ensure we don't crash if we call removeFromDisk with a running query.
1531      */
1532     void testRemoveFromDiskWithRunningQuery()
1533     {
1534         // FIXME: we currently crash
1535         QSKIP("Skipping because this produces a crash.");
1536         {
1537             // Setup
1538             Folder::Ptr folderEntity;
1539             const auto date = QDateTime(QDate(2015, 7, 7), QTime(12, 0));
1540             {
1541                 Folder folder("sink.dummy.instance1");
1542                 Sink::Store::create<Folder>(folder).exec().waitForFinished();
1543 
1544                 Sink::Query query;
1545                 query.resourceFilter("sink.dummy.instance1");
1546 
1547                 // Ensure all local data is processed
1548                 VERIFYEXEC(Sink::ResourceControl::flushMessageQueue(QByteArrayList() << "sink.dummy.instance1"));
1549 
1550                 auto model = Sink::Store::loadModel<Folder>(query);
1551                 QTRY_VERIFY(model->data(QModelIndex(), Sink::Store::ChildrenFetchedRole).toBool());
1552                 QCOMPARE(model->rowCount(), 1);
1553 
1554                 folderEntity = model->index(0, 0).data(Sink::Store::DomainObjectRole).value<Folder::Ptr>();
1555                 QVERIFY(!folderEntity->identifier().isEmpty());
1556 
1557                 //Add enough data so the query takes long enough that we remove the data from disk whlie the query is ongoing.
1558                 for (int i = 0; i < 100; i++) {
1559                     Mail mail("sink.dummy.instance1");
1560                     mail.setExtractedMessageId("test" + QByteArray::number(i));
1561                     mail.setFolder(folderEntity->identifier());
1562                     mail.setExtractedDate(date.addDays(i));
1563                     Sink::Store::create<Mail>(mail).exec().waitForFinished();
1564                 }
1565             }
1566 
1567             // Test
1568             Sink::Query query;
1569             query.resourceFilter("sink.dummy.instance1");
1570             query.filter<Mail::Folder>(*folderEntity);
1571             query.sort<Mail::Date>();
1572             query.setFlags(Query::LiveQuery);
1573             query.reduce<ApplicationDomain::Mail::ThreadId>(Query::Reduce::Selector::max<ApplicationDomain::Mail::Date>())
1574                 .count("count")
1575                 .collect<ApplicationDomain::Mail::Unread>("unreadCollected")
1576                 .collect<ApplicationDomain::Mail::Important>("importantCollected");
1577 
1578             // Ensure all local data is processed
1579             VERIFYEXEC(Sink::ResourceControl::flushMessageQueue(QByteArrayList() << "sink.dummy.instance1"));
1580 
1581             auto model = Sink::Store::loadModel<Mail>(query);
1582         }
1583 
1584         //FIXME: this will result in a crash in the above still running query.
1585         VERIFYEXEC(Sink::Store::removeDataFromDisk(QByteArray("sink.dummy.instance1")));
1586     }
1587 
1588     void testMailFulltext()
1589     {
1590         QByteArray id1;
1591         QByteArray id2;
1592         // Setup
1593         {
1594             {
1595                 auto msg = KMime::Message::Ptr::create();
1596                 msg->subject()->from7BitString("Subject To Search");
1597                 msg->setBody("This is the searchable body bar. unique sender2");
1598                 msg->from()->from7BitString("\"The Sender\"<sender@example.org>");
1599                 msg->to()->from7BitString("\"Foo Bar\"<foo-bar@example.org>");
1600                 msg->assemble();
1601 
1602                 auto mail = ApplicationDomainType::createEntity<Mail>("sink.dummy.instance1");
1603                 mail.setExtractedMessageId("test1");
1604                 mail.setFolder("folder1");
1605                 mail.setMimeMessage(msg->encodedContent());
1606                 VERIFYEXEC(Sink::Store::create<Mail>(mail));
1607                 id1 = mail.identifier();
1608             }
1609             {
1610                 auto msg = KMime::Message::Ptr::create();
1611                 msg->subject()->from7BitString("Stuff to Search");
1612                 msg->setBody("Body foo bar");
1613                 msg->from()->from7BitString("\"Another Sender2\"<sender2@unique.com>");
1614                 msg->assemble();
1615                 auto mail = ApplicationDomainType::createEntity<Mail>("sink.dummy.instance1");
1616                 mail.setExtractedMessageId("test2");
1617                 mail.setFolder("folder2");
1618                 mail.setMimeMessage(msg->encodedContent());
1619                 VERIFYEXEC(Sink::Store::create<Mail>(mail));
1620                 id2 = mail.identifier();
1621             }
1622             VERIFYEXEC(Sink::ResourceControl::flushMessageQueue("sink.dummy.instance1"));
1623             {
1624                 FulltextIndex index("sink.dummy.instance1", Sink::Storage::DataStore::ReadOnly);
1625                 qInfo() << QString("Found document 1 with terms: ") + index.getIndexContent(id1).terms.join(", ");
1626                 qInfo() << QString("Found document 2 with terms: ") + index.getIndexContent(id2).terms.join(", ");
1627             }
1628         }
1629 
1630         // Test
1631         // Default search
1632         {
1633             Sink::Query query;
1634             query.resourceFilter("sink.dummy.instance1");
1635             query.filter<Mail::Subject>(QueryBase::Comparator(QString("Subject To Search"), QueryBase::Comparator::Fulltext));
1636             const auto list = Sink::Store::read<Mail>(query);
1637             QCOMPARE(list.size(), 1);
1638             QCOMPARE(list.first().identifier(), id1);
1639         }
1640         // Phrase search
1641         {
1642             Sink::Query query;
1643             query.resourceFilter("sink.dummy.instance1");
1644             query.filter<Mail::Subject>(QueryBase::Comparator(QString("\"Subject To Search\""), QueryBase::Comparator::Fulltext));
1645             const auto list = Sink::Store::read<Mail>(query);
1646             QCOMPARE(list.size(), 1);
1647             QCOMPARE(list.first().identifier(), id1);
1648         }
1649         {
1650             Sink::Query query;
1651             query.resourceFilter("sink.dummy.instance1");
1652             query.filter<Mail::Subject>(QueryBase::Comparator(QString("\"Stuff to Search\""), QueryBase::Comparator::Fulltext));
1653             const auto list = Sink::Store::read<Mail>(query);
1654             QCOMPARE(list.size(), 1);
1655         }
1656         //Operators
1657         {
1658             Sink::Query query;
1659             query.resourceFilter("sink.dummy.instance1");
1660             query.filter<Mail::Subject>(QueryBase::Comparator(QString("subject AND search"), QueryBase::Comparator::Fulltext));
1661             const auto list = Sink::Store::read<Mail>(query);
1662             QCOMPARE(list.size(), 1);
1663             QCOMPARE(list.first().identifier(), id1);
1664         }
1665         {
1666             Sink::Query query;
1667             query.resourceFilter("sink.dummy.instance1");
1668             query.filter<Mail::Subject>(QueryBase::Comparator(QString("subject OR search"), QueryBase::Comparator::Fulltext));
1669             QCOMPARE(Sink::Store::read<Mail>(query).size(), 2);
1670         }
1671         //Case-insensitive
1672         {
1673             Sink::Query query;
1674             query.resourceFilter("sink.dummy.instance1");
1675             query.filter<Mail::Subject>(QueryBase::Comparator(QString("Subject"), QueryBase::Comparator::Fulltext));
1676             const auto list = Sink::Store::read<Mail>(query);
1677             QCOMPARE(list.size(), 1);
1678             QCOMPARE(list.first().identifier(), id1);
1679         }
1680         //Case-insensitive
1681         {
1682             Sink::Query query;
1683             query.resourceFilter("sink.dummy.instance1");
1684             query.filter<Mail::Subject>(QueryBase::Comparator(QString("subject"), QueryBase::Comparator::Fulltext));
1685             const auto list = Sink::Store::read<Mail>(query);
1686             QCOMPARE(list.size(), 1);
1687             QCOMPARE(list.first().identifier(), id1);
1688         }
1689         //Partial match
1690         {
1691             Sink::Query query;
1692             query.resourceFilter("sink.dummy.instance1");
1693             query.filter<Mail::Subject>(QueryBase::Comparator(QString("subj"), QueryBase::Comparator::Fulltext));
1694             const auto list = Sink::Store::read<Mail>(query);
1695             QCOMPARE(list.size(), 1);
1696             QCOMPARE(list.first().identifier(), id1);
1697         }
1698         //Filter by body
1699         {
1700             Sink::Query query;
1701             query.resourceFilter("sink.dummy.instance1");
1702             query.filter<Mail::MimeMessage>(QueryBase::Comparator(QString("searchable"), QueryBase::Comparator::Fulltext));
1703             const auto list = Sink::Store::read<Mail>(query);
1704             QCOMPARE(list.size(), 1);
1705             QCOMPARE(list.first().identifier(), id1);
1706         }
1707         //Filter by folder
1708         {
1709             Sink::Query query;
1710             query.resourceFilter("sink.dummy.instance1");
1711             query.filter<Mail::Subject>(QueryBase::Comparator(QString("Subject"), QueryBase::Comparator::Fulltext));
1712             query.filter<Mail::Folder>("folder1");
1713             const auto list = Sink::Store::read<Mail>(query);
1714             QCOMPARE(list.size(), 1);
1715             QCOMPARE(list.first().identifier(), id1);
1716         }
1717         //Filter by folder
1718         {
1719             Sink::Query query;
1720             query.resourceFilter("sink.dummy.instance1");
1721             query.filter<Mail::Subject>(QueryBase::Comparator(QString("Subject"), QueryBase::Comparator::Fulltext));
1722             query.filter<Mail::Folder>("folder2");
1723             QCOMPARE(Sink::Store::read<Mail>(query).size(), 0);
1724         }
1725         //Filter by sender
1726         {
1727             Sink::Query query;
1728             query.resourceFilter("sink.dummy.instance1");
1729             query.filter({}, Sink::QueryBase::Comparator(QString("sender"), Sink::QueryBase::Comparator::Fulltext));
1730             const auto list = Sink::Store::read<Mail>(query);
1731             QCOMPARE(list.size(), 2);
1732         }
1733         //Filter by sender
1734         {
1735             Sink::Query query;
1736             query.resourceFilter("sink.dummy.instance1");
1737             query.filter({}, Sink::QueryBase::Comparator(QString("Sender"), Sink::QueryBase::Comparator::Fulltext));
1738             const auto list = Sink::Store::read<Mail>(query);
1739             QCOMPARE(list.size(), 2);
1740         }
1741         //Filter by sender
1742         {
1743             Sink::Query query;
1744             query.resourceFilter("sink.dummy.instance1");
1745             query.filter({}, Sink::QueryBase::Comparator(QString("sender@example"), Sink::QueryBase::Comparator::Fulltext));
1746             const auto list = Sink::Store::read<Mail>(query);
1747             QCOMPARE(list.size(), 1);
1748             QCOMPARE(list.first().identifier(), id1);
1749         }
1750         //Filter by sender
1751         {
1752             Sink::Query query;
1753             query.resourceFilter("sink.dummy.instance1");
1754             query.filter({}, Sink::QueryBase::Comparator(QString("The Sender"), Sink::QueryBase::Comparator::Fulltext));
1755             const auto list = Sink::Store::read<Mail>(query);
1756             QCOMPARE(list.size(), 1);
1757         }
1758 
1759         //Filter by sender
1760         {
1761             Sink::Query query;
1762             query.resourceFilter("sink.dummy.instance1");
1763             query.filter({}, Sink::QueryBase::Comparator(QString("sender2@unique.com"), Sink::QueryBase::Comparator::Fulltext));
1764             const auto list = Sink::Store::read<Mail>(query);
1765             QCOMPARE(list.size(), 1);
1766             QCOMPARE(list.first().identifier(), id2);
1767         }
1768 
1769         //Filter by recipient
1770         {
1771             Sink::Query query;
1772             query.resourceFilter("sink.dummy.instance1");
1773             query.filter({}, Sink::QueryBase::Comparator(QString("foo-bar@example.org"), Sink::QueryBase::Comparator::Fulltext));
1774             const auto list = Sink::Store::read<Mail>(query);
1775             QCOMPARE(list.size(), 1);
1776             QCOMPARE(list.first().identifier(), id1);
1777         }
1778 
1779         //Filter by recipient
1780         {
1781             Sink::Query query;
1782             query.resourceFilter("sink.dummy.instance1");
1783             query.filter({}, Sink::QueryBase::Comparator(QString("foo-bar@example.com"), Sink::QueryBase::Comparator::Fulltext));
1784             QCOMPARE(Sink::Store::read<Mail>(query).size(), 0);
1785         }
1786 
1787         //Filter by subject field
1788         {
1789             Sink::Query query;
1790             query.resourceFilter("sink.dummy.instance1");
1791             query.filter({}, QueryBase::Comparator(QString("subject:\"Subject To Search\""), QueryBase::Comparator::Fulltext));
1792             const auto list = Sink::Store::read<Mail>(query);
1793             QCOMPARE(list.size(), 1);
1794             QCOMPARE(list.first().identifier(), id1);
1795         }
1796         //Ensure the query searches the right field
1797         {
1798             Sink::Query query;
1799             query.resourceFilter("sink.dummy.instance1");
1800             query.filter({}, QueryBase::Comparator(QString("sender:\"Subject To Search\""), QueryBase::Comparator::Fulltext));
1801             const auto list = Sink::Store::read<Mail>(query);
1802             QCOMPARE(list.size(), 0);
1803         }
1804     }
1805 
1806     void testUTF8MailFulltext()
1807     {
1808         QByteArray id1;
1809         // Setup
1810         {
1811             {
1812                 auto msg = KMime::Message::Ptr::create();
1813                 msg->subject()->fromUnicodeString("sübject", "utf8");
1814                 msg->setBody("büdi");
1815                 msg->from()->fromUnicodeString("\"John Düderli\"<john@doe.com>", "utf8");
1816                 msg->assemble();
1817 
1818                 auto mail = ApplicationDomainType::createEntity<Mail>("sink.dummy.instance1");
1819                 mail.setExtractedMessageId("test1");
1820                 mail.setFolder("folder1");
1821                 mail.setMimeMessage(msg->encodedContent());
1822                 VERIFYEXEC(Sink::Store::create<Mail>(mail));
1823                 id1 = mail.identifier();
1824             }
1825             VERIFYEXEC(Sink::ResourceControl::flushMessageQueue("sink.dummy.instance1"));
1826             {
1827                 FulltextIndex index("sink.dummy.instance1", Sink::Storage::DataStore::ReadOnly);
1828                 qInfo() << QString("found document 1 with terms: ") + index.getIndexContent(id1).terms.join(", ");
1829             }
1830         }
1831         {
1832             Sink::Query query;
1833             query.resourceFilter("sink.dummy.instance1");
1834             query.filter({}, Sink::QueryBase::Comparator(QString("sübject"), Sink::QueryBase::Comparator::Fulltext));
1835             const auto list = Sink::Store::read<Mail>(query);
1836             QCOMPARE(list.size(), 1);
1837             QCOMPARE(list.first().identifier(), id1);
1838         }
1839         {
1840             Sink::Query query;
1841             query.resourceFilter("sink.dummy.instance1");
1842             query.filter({}, Sink::QueryBase::Comparator(QString("büdi"), Sink::QueryBase::Comparator::Fulltext));
1843             const auto list = Sink::Store::read<Mail>(query);
1844             QCOMPARE(list.size(), 1);
1845             QCOMPARE(list.first().identifier(), id1);
1846         }
1847         {
1848             Sink::Query query;
1849             query.resourceFilter("sink.dummy.instance1");
1850             query.filter({}, Sink::QueryBase::Comparator(QString("düderli"), Sink::QueryBase::Comparator::Fulltext));
1851             const auto list = Sink::Store::read<Mail>(query);
1852             QCOMPARE(list.size(), 1);
1853             QCOMPARE(list.first().identifier(), id1);
1854         }
1855     }
1856 
1857     void testLiveMailFulltext()
1858     {
1859         Sink::Query query;
1860         query.setFlags(Query::LiveQuery);
1861         query.resourceFilter("sink.dummy.instance1");
1862         query.filter<Mail::Subject>(QueryBase::Comparator(QString("Live Subject To Search"), QueryBase::Comparator::Fulltext));
1863 
1864         auto model = Sink::Store::loadModel<Mail>(query);
1865         QTRY_VERIFY(model->data(QModelIndex(), Sink::Store::ChildrenFetchedRole).toBool());
1866         QCOMPARE(model->rowCount(), 0);
1867         Mail mailToModify;
1868         {
1869             {
1870                 auto msg = KMime::Message::Ptr::create();
1871                 msg->subject()->from7BitString("Not a match");
1872                 msg->setBody("This is the searchable body bar. unique sender1");
1873                 msg->from()->from7BitString("\"The Sender\"<sender@example.org>");
1874                 msg->to()->from7BitString("\"Foo Bar\"<foo-bar@example.org>");
1875                 msg->assemble();
1876 
1877                 auto mail = ApplicationDomainType::createEntity<Mail>("sink.dummy.instance1");
1878                 mail.setExtractedMessageId("test1");
1879                 mail.setFolder("folder1");
1880                 mail.setMimeMessage(msg->encodedContent());
1881                 VERIFYEXEC(Sink::Store::create<Mail>(mail));
1882             }
1883             {
1884                 auto msg = KMime::Message::Ptr::create();
1885                 msg->subject()->from7BitString("Live Subject To Search");
1886                 msg->setBody("This is the searchable body bar. unique sender2");
1887                 msg->from()->from7BitString("\"The Sender\"<sender@example.org>");
1888                 msg->to()->from7BitString("\"Foo Bar\"<foo-bar@example.org>");
1889                 msg->assemble();
1890 
1891                 auto mail = ApplicationDomainType::createEntity<Mail>("sink.dummy.instance1");
1892                 mail.setExtractedMessageId("test1");
1893                 mail.setFolder("folder1");
1894                 mail.setMimeMessage(msg->encodedContent());
1895                 mail.setUnread(true);
1896                 VERIFYEXEC(Sink::Store::create<Mail>(mail));
1897                 mailToModify = mail;
1898             }
1899 
1900             VERIFYEXEC(Sink::ResourceControl::flushMessageQueue("sink.dummy.instance1"));
1901         }
1902         QTRY_COMPARE(model->rowCount(), 1);
1903         //Test a modification that shouldn't affect the result
1904         {
1905             QSignalSpy insertedSpy(model.data(), &QAbstractItemModel::rowsInserted);
1906             QSignalSpy removedSpy(model.data(), &QAbstractItemModel::rowsRemoved);
1907             QSignalSpy changedSpy(model.data(), &QAbstractItemModel::dataChanged);
1908             QSignalSpy layoutChangedSpy(model.data(), &QAbstractItemModel::layoutChanged);
1909             QSignalSpy resetSpy(model.data(), &QAbstractItemModel::modelReset);
1910 
1911             mailToModify.setUnread(false);
1912             VERIFYEXEC(Sink::Store::modify(mailToModify));
1913 
1914             QTRY_COMPARE(changedSpy.size(), 1);
1915             QCOMPARE(insertedSpy.size(), 0);
1916             QCOMPARE(removedSpy.size(), 0);
1917             QCOMPARE(layoutChangedSpy.size(), 0);
1918             QCOMPARE(resetSpy.size(), 0);
1919         }
1920         //Test a modification that should affect the result
1921         {
1922             QSignalSpy insertedSpy(model.data(), &QAbstractItemModel::rowsInserted);
1923             QSignalSpy removedSpy(model.data(), &QAbstractItemModel::rowsRemoved);
1924             QSignalSpy changedSpy(model.data(), &QAbstractItemModel::dataChanged);
1925             QSignalSpy layoutChangedSpy(model.data(), &QAbstractItemModel::layoutChanged);
1926             QSignalSpy resetSpy(model.data(), &QAbstractItemModel::modelReset);
1927 
1928             auto msg = KMime::Message::Ptr::create();
1929             msg->subject()->from7BitString("No longer a match");
1930             msg->setBody("This is the searchable body bar. unique sender2");
1931             msg->from()->from7BitString("\"The Sender\"<sender@example.org>");
1932             msg->to()->from7BitString("\"Foo Bar\"<foo-bar@example.org>");
1933             msg->assemble();
1934 
1935             mailToModify.setMimeMessage(msg->encodedContent());
1936             VERIFYEXEC(Sink::Store::modify(mailToModify));
1937 
1938             QTRY_COMPARE(removedSpy.size(), 1);
1939             QCOMPARE(changedSpy.size(), 0);
1940             QCOMPARE(insertedSpy.size(), 0);
1941             QCOMPARE(layoutChangedSpy.size(), 0);
1942             QCOMPARE(resetSpy.size(), 0);
1943         }
1944         QCOMPARE(model->rowCount(), 0);
1945     }
1946 
1947     void testLiveMailFulltextThreaded()
1948     {
1949         Sink::Query query;
1950         query.setFlags(Query::LiveQuery);
1951         query.resourceFilter("sink.dummy.instance1");
1952         //Rely on partial matching
1953         query.filter<Mail::Subject>(QueryBase::Comparator(QString("LiveSubject"), QueryBase::Comparator::Fulltext));
1954         query.reduce<Mail::Folder>(Query::Reduce::Selector::max<Mail::Date>()).count("count").collect<Mail::Sender>("senders");
1955 
1956         auto model = Sink::Store::loadModel<Mail>(query);
1957         QTRY_VERIFY(model->data(QModelIndex(), Sink::Store::ChildrenFetchedRole).toBool());
1958         QCOMPARE(model->rowCount(), 0);
1959         Mail mail1;
1960         Mail mail2;
1961         Mail mail3;
1962         {
1963             {
1964                 auto msg = KMime::Message::Ptr::create();
1965                 msg->subject()->from7BitString("Not a match");
1966                 msg->setBody("This is the searchable body bar. unique sender1");
1967                 msg->from()->from7BitString("\"The Sender\"<sender@example.org>");
1968                 msg->to()->from7BitString("\"Foo Bar\"<foo-bar@example.org>");
1969                 msg->assemble();
1970 
1971                 auto mail = ApplicationDomainType::createEntity<Mail>("sink.dummy.instance1");
1972                 mail.setExtractedMessageId("test1");
1973                 mail.setFolder("folder1");
1974                 mail.setMimeMessage(msg->encodedContent());
1975                 mail.setUnread(true);
1976                 VERIFYEXEC(Sink::Store::create<Mail>(mail));
1977                 mail1 = mail;
1978             }
1979             {
1980                 auto msg = KMime::Message::Ptr::create();
1981                 msg->subject()->from7BitString("LiveSubjectToSearch");
1982                 msg->setBody("This is the searchable body bar. unique sender2");
1983                 msg->from()->from7BitString("\"The Sender\"<sender@example.org>");
1984                 msg->to()->from7BitString("\"Foo Bar\"<foo-bar@example.org>");
1985                 msg->assemble();
1986 
1987                 auto mail = ApplicationDomainType::createEntity<Mail>("sink.dummy.instance1");
1988                 mail.setExtractedMessageId("test2");
1989                 mail.setFolder("folder1");
1990                 mail.setMimeMessage(msg->encodedContent());
1991                 mail.setUnread(true);
1992                 VERIFYEXEC(Sink::Store::create<Mail>(mail));
1993                 mail2 = mail;
1994             }
1995             {
1996                 auto msg = KMime::Message::Ptr::create();
1997                 msg->subject()->from7BitString("LiveSubjectToSearch");
1998                 msg->setBody("This is the searchable body bar. unique sender2");
1999                 msg->from()->from7BitString("\"The Sender\"<sender@example.org>");
2000                 msg->to()->from7BitString("\"Foo Bar\"<foo-bar@example.org>");
2001                 msg->assemble();
2002 
2003                 auto mail = ApplicationDomainType::createEntity<Mail>("sink.dummy.instance1");
2004                 mail.setExtractedMessageId("test3");
2005                 mail.setFolder("folder2");
2006                 mail.setMimeMessage(msg->encodedContent());
2007                 mail.setUnread(true);
2008                 VERIFYEXEC(Sink::Store::create<Mail>(mail));
2009                 mail3 = mail;
2010             }
2011 
2012             VERIFYEXEC(Sink::ResourceControl::flushMessageQueue("sink.dummy.instance1"));
2013         }
2014         QTRY_COMPARE(model->rowCount(), 2);
2015         //Test a modification that shouldn't affect the result
2016         {
2017             QSignalSpy insertedSpy(model.data(), &QAbstractItemModel::rowsInserted);
2018             QSignalSpy removedSpy(model.data(), &QAbstractItemModel::rowsRemoved);
2019             QSignalSpy changedSpy(model.data(), &QAbstractItemModel::dataChanged);
2020             QSignalSpy layoutChangedSpy(model.data(), &QAbstractItemModel::layoutChanged);
2021             QSignalSpy resetSpy(model.data(), &QAbstractItemModel::modelReset);
2022 
2023             mail2.setUnread(false);
2024             VERIFYEXEC(Sink::Store::modify(mail2));
2025 
2026             QTRY_COMPARE(changedSpy.size(), 1);
2027             QCOMPARE(insertedSpy.size(), 0);
2028             QCOMPARE(removedSpy.size(), 0);
2029             QCOMPARE(layoutChangedSpy.size(), 0);
2030             QCOMPARE(resetSpy.size(), 0);
2031         }
2032 
2033         //Test a modification that shouldn't affect the result
2034         {
2035             QSignalSpy insertedSpy(model.data(), &QAbstractItemModel::rowsInserted);
2036             QSignalSpy removedSpy(model.data(), &QAbstractItemModel::rowsRemoved);
2037             QSignalSpy changedSpy(model.data(), &QAbstractItemModel::dataChanged);
2038             QSignalSpy layoutChangedSpy(model.data(), &QAbstractItemModel::layoutChanged);
2039             QSignalSpy resetSpy(model.data(), &QAbstractItemModel::modelReset);
2040 
2041             mail1.setUnread(false);
2042             VERIFYEXEC(Sink::Store::modify(mail1));
2043 
2044             QTRY_COMPARE(changedSpy.size(), 1);
2045             QCOMPARE(insertedSpy.size(), 0);
2046             QCOMPARE(removedSpy.size(), 0);
2047             QCOMPARE(layoutChangedSpy.size(), 0);
2048             QCOMPARE(resetSpy.size(), 0);
2049         }
2050 
2051         {
2052             QSignalSpy insertedSpy(model.data(), &QAbstractItemModel::rowsInserted);
2053             QSignalSpy removedSpy(model.data(), &QAbstractItemModel::rowsRemoved);
2054             QSignalSpy changedSpy(model.data(), &QAbstractItemModel::dataChanged);
2055             QSignalSpy layoutChangedSpy(model.data(), &QAbstractItemModel::layoutChanged);
2056             QSignalSpy resetSpy(model.data(), &QAbstractItemModel::modelReset);
2057 
2058             mail3.setUnread(false);
2059             VERIFYEXEC(Sink::Store::modify(mail3));
2060 
2061             QTRY_COMPARE(changedSpy.size(), 1);
2062             QCOMPARE(insertedSpy.size(), 0);
2063             QCOMPARE(removedSpy.size(), 0);
2064             QCOMPARE(layoutChangedSpy.size(), 0);
2065             QCOMPARE(resetSpy.size(), 0);
2066         }
2067 
2068         //Test a modification that should affect the result
2069         {
2070             QSignalSpy insertedSpy(model.data(), &QAbstractItemModel::rowsInserted);
2071             QSignalSpy removedSpy(model.data(), &QAbstractItemModel::rowsRemoved);
2072             QSignalSpy changedSpy(model.data(), &QAbstractItemModel::dataChanged);
2073             QSignalSpy layoutChangedSpy(model.data(), &QAbstractItemModel::layoutChanged);
2074             QSignalSpy resetSpy(model.data(), &QAbstractItemModel::modelReset);
2075 
2076             auto msg = KMime::Message::Ptr::create();
2077             msg->subject()->from7BitString("No longer a match");
2078             msg->setBody("This is the searchable body bar. unique sender2");
2079             msg->from()->from7BitString("\"The Sender\"<sender@example.org>");
2080             msg->to()->from7BitString("\"Foo Bar\"<foo-bar@example.org>");
2081             msg->assemble();
2082 
2083             mail2.setMimeMessage(msg->encodedContent());
2084             VERIFYEXEC(Sink::Store::modify(mail2));
2085 
2086             QTRY_COMPARE(removedSpy.size(), 1);
2087             QCOMPARE(changedSpy.size(), 0);
2088             QCOMPARE(insertedSpy.size(), 0);
2089             QCOMPARE(layoutChangedSpy.size(), 0);
2090             QCOMPARE(resetSpy.size(), 0);
2091         }
2092         QCOMPARE(model->rowCount(), 1);
2093     }
2094 
2095     void mailsWithDates()
2096     {
2097         {
2098             Mail mail("sink.dummy.instance1");
2099             mail.setExtractedDate(QDateTime::fromString("2018-05-23T13:49:41Z", Qt::ISODate));
2100             mail.setExtractedMessageId("message1");
2101             VERIFYEXEC(Sink::Store::create<Mail>(mail));
2102         }
2103         {
2104             Mail mail("sink.dummy.instance1");
2105             mail.setExtractedDate(QDateTime::fromString("2018-05-23T13:50:00Z", Qt::ISODate));
2106             mail.setExtractedMessageId("message2");
2107             VERIFYEXEC(Sink::Store::create<Mail>(mail));
2108         }
2109         {
2110             Mail mail("sink.dummy.instance1");
2111             mail.setExtractedDate(QDateTime::fromString("2018-05-27T13:50:00Z", Qt::ISODate));
2112             mail.setExtractedMessageId("message3");
2113             VERIFYEXEC(Sink::Store::create<Mail>(mail));
2114         }
2115         {
2116             Mail mail("sink.dummy.instance1");
2117             mail.setExtractedMessageId("message4");
2118             VERIFYEXEC(Sink::Store::create<Mail>(mail));
2119         }
2120         {
2121             Mail mail("sink.dummy.instance1");
2122             mail.setExtractedDate(QDateTime::fromString("2078-05-23T13:49:41Z", Qt::ISODate));
2123             mail.setExtractedMessageId("message5");
2124             VERIFYEXEC(Sink::Store::create<Mail>(mail));
2125         }
2126         VERIFYEXEC(Sink::ResourceControl::flushMessageQueue("sink.dummy.instance1"));
2127     }
2128 
2129     void testMailDate()
2130     {
2131         mailsWithDates();
2132 
2133         {
2134             Sink::Query query;
2135             query.resourceFilter("sink.dummy.instance1");
2136             query.filter<Mail::Date>(QDateTime::fromString("2018-05-23T13:49:41Z", Qt::ISODate));
2137             auto model = Sink::Store::loadModel<Mail>(query);
2138             QTRY_VERIFY(model->data(QModelIndex(), Sink::Store::ChildrenFetchedRole).toBool());
2139             QCOMPARE(model->rowCount(), 1);
2140         }
2141 
2142         {
2143             Sink::Query query;
2144             query.resourceFilter("sink.dummy.instance1");
2145             query.filter<Mail::Date>(QDateTime::fromString("2018-05-27T13:49:41Z", Qt::ISODate));
2146             auto model = Sink::Store::loadModel<Mail>(query);
2147             QTRY_VERIFY(model->data(QModelIndex(), Sink::Store::ChildrenFetchedRole).toBool());
2148             QCOMPARE(model->rowCount(), 0);
2149         }
2150 
2151         {
2152             Sink::Query query;
2153             query.resourceFilter("sink.dummy.instance1");
2154             query.filter<Mail::Date>(QDateTime::fromString("2018-05-27T13:50:00Z", Qt::ISODate));
2155             auto model = Sink::Store::loadModel<Mail>(query);
2156             QTRY_VERIFY(model->data(QModelIndex(), Sink::Store::ChildrenFetchedRole).toBool());
2157             QCOMPARE(model->rowCount(), 1);
2158         }
2159 
2160     }
2161 
2162     void testMailRange()
2163     {
2164         mailsWithDates();
2165 
2166         {
2167             Sink::Query query;
2168             query.resourceFilter("sink.dummy.instance1");
2169             query.filter<Mail::Date>(QueryBase::Comparator(QVariantList{QDateTime::fromString("2018-05-23T13:49:41Z", Qt::ISODate), QDateTime::fromString("2018-05-23T13:49:41Z", Qt::ISODate)}, QueryBase::Comparator::Within));
2170             auto model = Sink::Store::loadModel<Mail>(query);
2171             QTRY_VERIFY(model->data(QModelIndex(), Sink::Store::ChildrenFetchedRole).toBool());
2172             QCOMPARE(model->rowCount(), 1);
2173         }
2174 
2175         {
2176             Sink::Query query;
2177             query.resourceFilter("sink.dummy.instance1");
2178             query.filter<Mail::Date>(QueryBase::Comparator(QVariantList{QDateTime::fromString("2018-05-22T13:49:41Z", Qt::ISODate), QDateTime::fromString("2018-05-25T13:49:41Z", Qt::ISODate)}, QueryBase::Comparator::Within));
2179             auto model = Sink::Store::loadModel<Mail>(query);
2180             QTRY_VERIFY(model->data(QModelIndex(), Sink::Store::ChildrenFetchedRole).toBool());
2181             QCOMPARE(model->rowCount(), 2);
2182         }
2183 
2184         {
2185             Sink::Query query;
2186             query.resourceFilter("sink.dummy.instance1");
2187             query.filter<Mail::Date>(QueryBase::Comparator(QVariantList{QDateTime::fromString("2018-05-22T13:49:41Z", Qt::ISODate), QDateTime::fromString("2018-05-30T13:49:41Z", Qt::ISODate)}, QueryBase::Comparator::Within));
2188             auto model = Sink::Store::loadModel<Mail>(query);
2189             QTRY_VERIFY(model->data(QModelIndex(), Sink::Store::ChildrenFetchedRole).toBool());
2190             QCOMPARE(model->rowCount(), 3);
2191         }
2192 
2193         {
2194             Sink::Query query;
2195             query.resourceFilter("sink.dummy.instance1");
2196             query.filter<Mail::Date>(QueryBase::Comparator(QVariantList{QDateTime::fromString("2018-05-22T13:49:41Z", Qt::ISODate), QDateTime::fromString("2080-05-30T13:49:41Z", Qt::ISODate)}, QueryBase::Comparator::Within));
2197             auto model = Sink::Store::loadModel<Mail>(query);
2198             QTRY_VERIFY(model->data(QModelIndex(), Sink::Store::ChildrenFetchedRole).toBool());
2199             //This query also finds the mail without date, because we assign a default date of current utc
2200             QCOMPARE(model->rowCount(), 5);
2201         }
2202     }
2203 
2204     void testOverlap()
2205     {
2206         auto createEvent = [] (const QString &start, const QString &end) {
2207             auto icalEvent = KCalendarCore::Event::Ptr::create();
2208             icalEvent->setSummary("test");
2209             icalEvent->setDtStart(QDateTime::fromString(start, Qt::ISODate));
2210             icalEvent->setDtEnd(QDateTime::fromString(end, Qt::ISODate));
2211 
2212             Event event("sink.dummy.instance1");
2213             event.setIcal(KCalendarCore::ICalFormat().toICalString(icalEvent).toUtf8());
2214             VERIFYEXEC(Sink::Store::create(event));
2215         };
2216 
2217         createEvent("2018-05-23T12:00:00Z", "2018-05-23T13:00:00Z");
2218         createEvent("2018-05-23T13:00:00Z", "2018-05-23T14:00:00Z");
2219         createEvent("2018-05-23T14:00:00Z", "2018-05-23T15:00:00Z");
2220         createEvent("2018-05-24T12:00:00Z", "2018-05-24T14:00:00Z");
2221         //Long event that spans multiple buckets
2222         createEvent("2018-05-30T22:00:00",  "2019-04-25T03:00:00");
2223         VERIFYEXEC(Sink::ResourceControl::flushMessageQueue("sink.dummy.instance1"));
2224 
2225         auto findInRange = [] (const QString &start, const QString &end) {
2226             Sink::Query query;
2227             query.resourceFilter("sink.dummy.instance1");
2228             query.filter<Event::StartTime, Event::EndTime>(QueryBase::Comparator(
2229                 QVariantList{ QDateTime::fromString(start, Qt::ISODate),
2230                     QDateTime::fromString(end, Qt::ISODate) },
2231                 QueryBase::Comparator::Overlap));
2232             return Sink::Store::read<Event>(query);
2233         };
2234 
2235         //Find all
2236         QCOMPARE(findInRange("2018-05-22T12:00:00Z", "2018-05-30T13:00:00Z").size(), 4);
2237         //Find none on day without events
2238         QCOMPARE(findInRange("2018-05-22T12:00:00Z", "2018-05-22T13:00:00Z").size(), 0);
2239         //Find none on day with events
2240         QCOMPARE(findInRange("2018-05-24T10:00:00Z", "2018-05-24T11:00:00Z").size(), 0);
2241         //Find on same day
2242         QCOMPARE(findInRange("2018-05-23T12:30:00Z", "2018-05-23T12:31:00Z").size(), 1);
2243         //Find on different days
2244         QCOMPARE(findInRange("2018-05-22T12:30:00Z", "2018-05-23T12:00:00Z").size(), 1);
2245         QCOMPARE(findInRange("2018-05-23T14:30:00Z", "2018-05-23T16:00:00Z").size(), 1);
2246 
2247         //Find long range event
2248         QCOMPARE(findInRange("2018-07-23T14:30:00Z", "2018-10-23T16:00:00Z").size(), 1);
2249     }
2250 
2251     void testOverlapLive()
2252     {
2253         auto createEvent = [] (const QString &start, const QString &end) {
2254             auto icalEvent = KCalendarCore::Event::Ptr::create();
2255             icalEvent->setSummary("test");
2256             icalEvent->setDtStart(QDateTime::fromString(start, Qt::ISODate));
2257             icalEvent->setDtEnd(QDateTime::fromString(end, Qt::ISODate));
2258 
2259             Event event = Event::createEntity<Event>("sink.dummy.instance1");
2260             event.setIcal(KCalendarCore::ICalFormat().toICalString(icalEvent).toUtf8());
2261             VERIFYEXEC_RET(Sink::Store::create(event), {});
2262             return event;
2263         };
2264 
2265         createEvent("2018-05-23T12:00:00Z", "2018-05-23T13:00:00Z");
2266         createEvent("2018-05-23T13:00:00Z", "2018-05-23T14:00:00Z");
2267         createEvent("2018-05-23T14:00:00Z", "2018-05-23T15:00:00Z");
2268         createEvent("2018-05-24T12:00:00Z", "2018-05-24T14:00:00Z");
2269         VERIFYEXEC(Sink::ResourceControl::flushMessageQueue("sink.dummy.instance1"));
2270 
2271         {
2272             Sink::Query query;
2273             query.resourceFilter("sink.dummy.instance1");
2274             query.setFlags(Query::LiveQuery);
2275             query.filter<Event::StartTime, Event::EndTime>(QueryBase::Comparator(
2276                 QVariantList{ QDateTime::fromString("2018-05-22T12:00:00Z", Qt::ISODate),
2277                     QDateTime::fromString("2018-05-30T13:00:00Z", Qt::ISODate) },
2278                 QueryBase::Comparator::Overlap));
2279             auto model = Sink::Store::loadModel<Event>(query);
2280             QTRY_VERIFY(model->data(QModelIndex(), Sink::Store::ChildrenFetchedRole).toBool());
2281             QCOMPARE(model->rowCount(), 4);
2282 
2283             auto event1 = createEvent("2018-05-23T12:00:00Z", "2018-05-23T13:00:00Z");
2284             auto event2 = createEvent("2018-05-31T12:00:00Z", "2018-05-31T13:00:00Z");
2285 
2286             QTRY_COMPARE(model->rowCount(), 5);
2287 
2288             VERIFYEXEC(Sink::Store::remove(event1));
2289             VERIFYEXEC(Sink::Store::remove(event2));
2290 
2291             QTRY_COMPARE(model->rowCount(), 4);
2292         }
2293     }
2294 
2295     void testRecurringEvents()
2296     {
2297         auto icalEvent = KCalendarCore::Event::Ptr::create();
2298         icalEvent->setSummary("test");
2299         icalEvent->setDtStart(QDateTime::fromString("2018-05-10T13:00:00Z", Qt::ISODate));
2300         icalEvent->setDtEnd(QDateTime::fromString("2018-05-10T14:00:00Z", Qt::ISODate));
2301         icalEvent->recurrence()->setWeekly(3);
2302 
2303         Event event = Event::createEntity<Event>("sink.dummy.instance1");
2304         event.setIcal(KCalendarCore::ICalFormat().toICalString(icalEvent).toUtf8());
2305         VERIFYEXEC(Sink::Store::create(event));
2306         VERIFYEXEC(Sink::ResourceControl::flushMessageQueue("sink.dummy.instance1"));
2307 
2308         Sink::Query query;
2309         query.resourceFilter("sink.dummy.instance1");
2310         query.setFlags(Query::LiveQuery);
2311         query.filter<Event::StartTime, Event::EndTime>(QueryBase::Comparator(
2312             QVariantList{ QDateTime::fromString("2018-05-15T12:00:00Z", Qt::ISODate),
2313                 QDateTime::fromString("2018-05-30T13:00:00Z", Qt::ISODate) },
2314             QueryBase::Comparator::Overlap));
2315         auto model = Sink::Store::loadModel<Event>(query);
2316         QTRY_VERIFY(model->data(QModelIndex(), Sink::Store::ChildrenFetchedRole).toBool());
2317         QCOMPARE(model->rowCount(), 1);
2318 
2319         VERIFYEXEC(Sink::Store::remove(event));
2320         QTRY_COMPARE(model->rowCount(), 0);
2321     }
2322 
2323     void testRecurringEventsWithExceptions()
2324     {
2325         {
2326             auto icalEvent = KCalendarCore::Event::Ptr::create();
2327             icalEvent->setSummary("test");
2328             icalEvent->setDtStart(QDateTime::fromString("2018-05-10T13:00:00Z", Qt::ISODate));
2329             icalEvent->setDtEnd(QDateTime::fromString("2018-05-10T14:00:00Z", Qt::ISODate));
2330             icalEvent->recurrence()->setWeekly(3);
2331 
2332             Event event = Event::createEntity<Event>("sink.dummy.instance1");
2333             event.setIcal(KCalendarCore::ICalFormat().toICalString(icalEvent).toUtf8());
2334             VERIFYEXEC(Sink::Store::create(event));
2335         }
2336 
2337 
2338         //Exception
2339         {
2340             auto icalEvent = KCalendarCore::Event::Ptr::create();
2341             icalEvent->setSummary("test");
2342             icalEvent->setRecurrenceId(QDateTime::fromString("2018-05-17T13:00:00Z", Qt::ISODate));
2343             icalEvent->setDtStart(QDateTime::fromString("2018-07-10T13:00:00Z", Qt::ISODate));
2344             icalEvent->setDtEnd(QDateTime::fromString("2018-07-10T14:00:00Z", Qt::ISODate));
2345 
2346             Event event = Event::createEntity<Event>("sink.dummy.instance1");
2347             event.setIcal(KCalendarCore::ICalFormat().toICalString(icalEvent).toUtf8());
2348             VERIFYEXEC(Sink::Store::create(event));
2349         }
2350 
2351         VERIFYEXEC(Sink::ResourceControl::flushMessageQueue("sink.dummy.instance1"));
2352 
2353         {
2354             Sink::Query query;
2355             query.resourceFilter("sink.dummy.instance1");
2356             query.filter<Event::StartTime, Event::EndTime>(QueryBase::Comparator(
2357                 QVariantList{ QDateTime::fromString("2018-05-15T12:00:00Z", Qt::ISODate),
2358                     QDateTime::fromString("2018-05-30T13:00:00Z", Qt::ISODate) },
2359                 QueryBase::Comparator::Overlap));
2360             auto model = Sink::Store::loadModel<Event>(query);
2361             QTRY_VERIFY(model->data(QModelIndex(), Sink::Store::ChildrenFetchedRole).toBool());
2362             QCOMPARE(model->rowCount(), 2);
2363         }
2364         {
2365             Sink::Query query;
2366             query.resourceFilter("sink.dummy.instance1");
2367             query.filter<Event::StartTime, Event::EndTime>(QueryBase::Comparator(
2368                 QVariantList{ QDateTime::fromString("2018-07-15T12:00:00Z", Qt::ISODate),
2369                     QDateTime::fromString("2018-07-30T13:00:00Z", Qt::ISODate) },
2370                 QueryBase::Comparator::Overlap));
2371             auto model = Sink::Store::loadModel<Event>(query);
2372             QTRY_VERIFY(model->data(QModelIndex(), Sink::Store::ChildrenFetchedRole).toBool());
2373             QCOMPARE(model->rowCount(), 1);
2374         }
2375     }
2376 
2377 
2378     void testQueryUpdate()
2379     {
2380         // Setup
2381         {
2382             Mail mail("sink.dummy.instance1");
2383             mail.setExtractedMessageId("test1");
2384             mail.setFolder("folder1");
2385             VERIFYEXEC(Sink::Store::create<Mail>(mail));
2386         }
2387         {
2388             Mail mail("sink.dummy.instance1");
2389             mail.setExtractedMessageId("test2");
2390             mail.setFolder("folder2");
2391             VERIFYEXEC(Sink::Store::create<Mail>(mail));
2392         }
2393 
2394         // Test
2395         Sink::Query query;
2396         query.resourceFilter("sink.dummy.instance1");
2397         query.setFlags(Query::LiveQuery);
2398         query.filter<Mail::Folder>("folder1");
2399 
2400         auto model = Sink::Store::loadModel<Mail>(query);
2401         QTRY_COMPARE(model->rowCount(), 1);
2402 
2403         {
2404             Sink::Query newQuery;
2405             newQuery.resourceFilter("sink.dummy.instance1");
2406 
2407             Sink::Store::updateModel<Mail>(newQuery, model);
2408             QTRY_VERIFY(model->data(QModelIndex(), Sink::Store::ChildrenFetchedRole).toBool());
2409             QCOMPARE(model->rowCount(), 2);
2410         }
2411         {
2412 
2413             Sink::Query newQuery;
2414             newQuery.resourceFilter("sink.dummy.instance1");
2415             newQuery.filter<Mail::Folder>("folder2");
2416 
2417             Sink::Store::updateModel<Mail>(newQuery, model);
2418             QTRY_VERIFY(model->data(QModelIndex(), Sink::Store::ChildrenFetchedRole).toBool());
2419             QCOMPARE(model->rowCount(), 1);
2420             QCOMPARE(model->data(model->index(0, 0, QModelIndex{}), Sink::Store::DomainObjectRole).value<Mail::Ptr>()->getMessageId(), "test2");
2421         }
2422         {
2423 
2424             Sink::Query newQuery;
2425             newQuery.resourceFilter("sink.dummy.instance1");
2426             newQuery.filter<Mail::Folder>("folder1");
2427 
2428             Sink::Store::updateModel<Mail>(newQuery, model);
2429             QTRY_VERIFY(model->data(QModelIndex(), Sink::Store::ChildrenFetchedRole).toBool());
2430             QCOMPARE(model->rowCount(), 1);
2431             QCOMPARE(model->data(model->index(0, 0, QModelIndex{}), Sink::Store::DomainObjectRole).value<Mail::Ptr>()->getMessageId(), "test1");
2432         }
2433         //Quickly run two queries without waiting for the first to complete.
2434         {
2435             {
2436 
2437                 Sink::Query newQuery;
2438                 newQuery.resourceFilter("sink.dummy.instance1");
2439                 newQuery.filter<Mail::Folder>("folder2");
2440 
2441                 Sink::Store::updateModel<Mail>(newQuery, model);
2442             }
2443 
2444             Sink::Query newQuery;
2445             newQuery.resourceFilter("sink.dummy.instance1");
2446             newQuery.filter<Mail::Folder>("folder1");
2447 
2448             Sink::Store::updateModel<Mail>(newQuery, model);
2449             QTRY_VERIFY(model->data(QModelIndex(), Sink::Store::ChildrenFetchedRole).toBool());
2450             QCOMPARE(model->rowCount(), 1);
2451             QCOMPARE(model->data(model->index(0, 0, QModelIndex{}), Sink::Store::DomainObjectRole).value<Mail::Ptr>()->getMessageId(), "test1");
2452         }
2453     }
2454 
2455 };
2456 
2457 QTEST_MAIN(QueryTest)
2458 #include "querytest.moc"