File indexing completed on 2024-11-10 04:40:13

0001 /*
0002     SPDX-FileCopyrightText: 2008 Volker Krause <vkrause@kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "itemsync.h"
0008 #include "agentinstance.h"
0009 #include "agentmanager.h"
0010 #include "collection.h"
0011 #include "control.h"
0012 #include "item.h"
0013 #include "itemcreatejob.h"
0014 #include "itemdeletejob.h"
0015 #include "itemfetchjob.h"
0016 #include "itemfetchscope.h"
0017 #include "monitor.h"
0018 #include "qtest_akonadi.h"
0019 #include "resourceselectjob_p.h"
0020 
0021 #include <KRandom>
0022 
0023 #include <QObject>
0024 #include <QSignalSpy>
0025 
0026 #include <memory>
0027 
0028 using namespace Akonadi;
0029 
0030 Q_DECLARE_METATYPE(KJob *)
0031 Q_DECLARE_METATYPE(ItemSync::TransactionMode)
0032 using namespace std::chrono_literals;
0033 class ItemsyncTest : public QObject
0034 {
0035     Q_OBJECT
0036 private:
0037     Item::List fetchItems(const Collection &col)
0038     {
0039         qDebug() << "fetching items from collection" << col.remoteId() << col.name();
0040         auto fetch = new ItemFetchJob(col, this);
0041         fetch->fetchScope().fetchFullPayload();
0042         fetch->fetchScope().fetchAllAttributes();
0043         fetch->fetchScope().setCacheOnly(true); // resources are switched off anyway
0044         if (!fetch->exec()) {
0045             []() {
0046                 QFAIL("Failed to fetch items!");
0047             }();
0048         }
0049         return fetch->items();
0050     }
0051 
0052     static void createItems(const Collection &col, int itemCount)
0053     {
0054         for (int i = 0; i < itemCount; ++i) {
0055             Item item(QStringLiteral("application/octet-stream"));
0056             item.setRemoteId(QStringLiteral("rid") + QString::number(i));
0057             item.setGid(QStringLiteral("gid") + QString::number(i));
0058             item.setPayload<QByteArray>("payload1");
0059             auto job = new ItemCreateJob(item, col);
0060             AKVERIFYEXEC(job);
0061         }
0062     }
0063 
0064     static Item duplicateItem(const Item &item, const Collection &col)
0065     {
0066         Item duplicate = item;
0067         duplicate.setId(-1);
0068         auto job = new ItemCreateJob(duplicate, col);
0069         [job]() {
0070             AKVERIFYEXEC(job);
0071         }();
0072         return job->item();
0073     }
0074 
0075     static Item modifyItem(Item item)
0076     {
0077         static int counter = 0;
0078         item.setFlag(QByteArray("\\READ") + QByteArray::number(counter));
0079         counter++;
0080         return item;
0081     }
0082 
0083     std::unique_ptr<Monitor> createCollectionMonitor(const Collection &col)
0084     {
0085         auto monitor = std::make_unique<Monitor>();
0086         monitor->setCollectionMonitored(col);
0087         if (!AkonadiTest::akWaitForSignal(monitor.get(), &Monitor::monitorReady)) {
0088             QTest::qFail("Failed to wait for monitor", __FILE__, __LINE__);
0089             return nullptr;
0090         }
0091 
0092         return monitor;
0093     }
0094 
0095 private Q_SLOTS:
0096     void initTestCase()
0097     {
0098         AkonadiTest::checkTestIsIsolated();
0099         Control::start();
0100         AkonadiTest::setAllResourcesOffline();
0101         qRegisterMetaType<KJob *>();
0102         qRegisterMetaType<ItemSync::TransactionMode>();
0103     }
0104 
0105     void testFullSync()
0106     {
0107         const Collection col = Collection(AkonadiTest::collectionIdFromPath(QStringLiteral("res1/foo")));
0108         QVERIFY(col.isValid());
0109         Item::List origItems = fetchItems(col);
0110 
0111         // Since the item sync affects the knut resource we ensure we actually managed to load all items
0112         // This needs to be adjusted should the testdataset change
0113         QCOMPARE(origItems.size(), 15);
0114 
0115         Akonadi::Monitor monitor;
0116         monitor.setCollectionMonitored(col);
0117         QSignalSpy deletedSpy(&monitor, &Monitor::itemRemoved);
0118         QVERIFY(deletedSpy.isValid());
0119         QSignalSpy addedSpy(&monitor, &Monitor::itemAdded);
0120         QVERIFY(addedSpy.isValid());
0121         QSignalSpy changedSpy(&monitor, &Monitor::itemChanged);
0122         QVERIFY(changedSpy.isValid());
0123 
0124         auto syncer = new ItemSync(col);
0125         syncer->setTransactionMode(ItemSync::SingleTransaction);
0126         QSignalSpy transactionSpy(syncer, &ItemSync::transactionCommitted);
0127         QVERIFY(transactionSpy.isValid());
0128         syncer->setFullSyncItems(origItems);
0129         AKVERIFYEXEC(syncer);
0130         QCOMPARE(transactionSpy.count(), 1);
0131 
0132         Item::List resultItems = fetchItems(col);
0133         QCOMPARE(resultItems.count(), origItems.count());
0134         QTest::qWait(100);
0135         QCOMPARE(deletedSpy.count(), 0);
0136         QCOMPARE(addedSpy.count(), 0);
0137         QCOMPARE(changedSpy.count(), 0);
0138     }
0139 
0140     void testFullStreamingSync_data()
0141     {
0142         QTest::addColumn<ItemSync::TransactionMode>("transactionMode");
0143         QTest::addColumn<bool>("goToEventLoopAfterAddingItems");
0144 
0145         QTest::newRow("single transaction, no eventloop") << ItemSync::SingleTransaction << false;
0146         QTest::newRow("multi transaction, no eventloop") << ItemSync::MultipleTransactions << false;
0147         QTest::newRow("single transaction, with eventloop") << ItemSync::SingleTransaction << true;
0148         QTest::newRow("multi transaction, with eventloop") << ItemSync::MultipleTransactions << true;
0149     }
0150 
0151     void testFullStreamingSync()
0152     {
0153         QFETCH(ItemSync::TransactionMode, transactionMode);
0154         QFETCH(bool, goToEventLoopAfterAddingItems);
0155 
0156         const Collection col = Collection(AkonadiTest::collectionIdFromPath(QStringLiteral("res1/foo")));
0157         QVERIFY(col.isValid());
0158         Item::List origItems = fetchItems(col);
0159         QCOMPARE(origItems.size(), 15);
0160 
0161         auto monitor = createCollectionMonitor(col);
0162         QSignalSpy deletedSpy(monitor.get(), &Monitor::itemRemoved);
0163         QSignalSpy addedSpy(monitor.get(), &Monitor::itemAdded);
0164         QSignalSpy changedSpy(monitor.get(), &Monitor::itemChanged);
0165 
0166         auto syncer = new ItemSync(col);
0167         QSignalSpy transactionSpy(syncer, &ItemSync::transactionCommitted);
0168         QVERIFY(transactionSpy.isValid());
0169         syncer->setTransactionMode(transactionMode);
0170         syncer->setBatchSize(1);
0171         syncer->setAutoDelete(false);
0172         syncer->setStreamingEnabled(true);
0173         QSignalSpy spy(syncer, &KJob::result);
0174         QVERIFY(spy.isValid());
0175         syncer->setTotalItems(origItems.count());
0176         QTest::qWait(0);
0177         QCOMPARE(spy.count(), 0);
0178 
0179         for (int i = 0; i < origItems.count(); ++i) {
0180             Item::List l;
0181             // Modify to trigger a changed signal
0182             l << modifyItem(origItems[i]);
0183             syncer->setFullSyncItems(l);
0184             if (goToEventLoopAfterAddingItems) {
0185                 QTest::qWait(0);
0186             }
0187             if (i < origItems.count() - 1) {
0188                 QCOMPARE(spy.count(), 0);
0189             }
0190         }
0191         syncer->deliveryDone();
0192         QTRY_COMPARE(spy.count(), 1);
0193         KJob *job = spy.at(0).at(0).value<KJob *>();
0194         QCOMPARE(job, syncer);
0195         QCOMPARE(job->error(), 0);
0196         if (transactionMode == ItemSync::SingleTransaction) {
0197             QCOMPARE(transactionSpy.count(), 1);
0198         }
0199         if (transactionMode == ItemSync::MultipleTransactions) {
0200             QCOMPARE(transactionSpy.count(), origItems.count());
0201         }
0202 
0203         Item::List resultItems = fetchItems(col);
0204         QCOMPARE(resultItems.count(), origItems.count());
0205 
0206         delete syncer;
0207         QTest::qWait(100);
0208         QTRY_COMPARE(deletedSpy.count(), 0);
0209         QTRY_COMPARE(addedSpy.count(), 0);
0210         QTRY_COMPARE(changedSpy.count(), origItems.count());
0211     }
0212 
0213     void testIncrementalSync()
0214     {
0215         {
0216             auto select = new ResourceSelectJob(QStringLiteral("akonadi_knut_resource_0"));
0217             AKVERIFYEXEC(select);
0218         }
0219 
0220         const Collection col = Collection(AkonadiTest::collectionIdFromPath(QStringLiteral("res1/foo")));
0221         QVERIFY(col.isValid());
0222         Item::List origItems = fetchItems(col);
0223         QCOMPARE(origItems.size(), 15);
0224 
0225         auto monitor = createCollectionMonitor(col);
0226         QSignalSpy deletedSpy(monitor.get(), &Monitor::itemRemoved);
0227         QSignalSpy addedSpy(monitor.get(), &Monitor::itemAdded);
0228         QSignalSpy changedSpy(monitor.get(), &Monitor::itemChanged);
0229 
0230         {
0231             auto syncer = new ItemSync(col);
0232             QSignalSpy transactionSpy(syncer, &ItemSync::transactionCommitted);
0233             QVERIFY(transactionSpy.isValid());
0234             syncer->setTransactionMode(ItemSync::SingleTransaction);
0235             syncer->setIncrementalSyncItems(origItems, Item::List());
0236             AKVERIFYEXEC(syncer);
0237             QCOMPARE(transactionSpy.count(), 1);
0238         }
0239 
0240         QTest::qWait(100);
0241         QTRY_COMPARE(deletedSpy.count(), 0);
0242         QCOMPARE(addedSpy.count(), 0);
0243         QTRY_COMPARE(changedSpy.count(), 0);
0244         deletedSpy.clear();
0245         addedSpy.clear();
0246         changedSpy.clear();
0247 
0248         Item::List resultItems = fetchItems(col);
0249         QCOMPARE(resultItems.count(), origItems.count());
0250 
0251         Item::List delItems;
0252         delItems << resultItems.takeFirst();
0253 
0254         Item itemWithOnlyRemoteId;
0255         itemWithOnlyRemoteId.setRemoteId(resultItems.front().remoteId());
0256         delItems << itemWithOnlyRemoteId;
0257         resultItems.takeFirst();
0258 
0259         // This item will not be removed since it isn't existing locally
0260         Item itemWithRandomRemoteId;
0261         itemWithRandomRemoteId.setRemoteId(KRandom::randomString(100));
0262         delItems << itemWithRandomRemoteId;
0263 
0264         {
0265             auto syncer = new ItemSync(col);
0266             syncer->setTransactionMode(ItemSync::SingleTransaction);
0267             QSignalSpy transactionSpy(syncer, &ItemSync::transactionCommitted);
0268             QVERIFY(transactionSpy.isValid());
0269             syncer->setIncrementalSyncItems(resultItems, delItems);
0270             AKVERIFYEXEC(syncer);
0271             QCOMPARE(transactionSpy.count(), 1);
0272         }
0273 
0274         Item::List resultItems2 = fetchItems(col);
0275         QCOMPARE(resultItems2.count(), resultItems.count());
0276 
0277         QTest::qWait(100);
0278         QTRY_COMPARE(deletedSpy.count(), 2);
0279         QCOMPARE(addedSpy.count(), 0);
0280         QTRY_COMPARE(changedSpy.count(), 0);
0281 
0282         {
0283             auto select = new ResourceSelectJob(QStringLiteral(""));
0284             AKVERIFYEXEC(select);
0285         }
0286     }
0287 
0288     void testIncrementalStreamingSync()
0289     {
0290         const Collection col = Collection(AkonadiTest::collectionIdFromPath(QStringLiteral("res1/foo")));
0291         QVERIFY(col.isValid());
0292         Item::List origItems = fetchItems(col);
0293 
0294         auto monitor = createCollectionMonitor(col);
0295         QSignalSpy deletedSpy(monitor.get(), &Monitor::itemRemoved);
0296         QSignalSpy addedSpy(monitor.get(), &Monitor::itemAdded);
0297         QSignalSpy changedSpy(monitor.get(), &Monitor::itemChanged);
0298 
0299         auto syncer = new ItemSync(col);
0300         syncer->setTransactionMode(ItemSync::SingleTransaction);
0301         QSignalSpy transactionSpy(syncer, &ItemSync::transactionCommitted);
0302         QVERIFY(transactionSpy.isValid());
0303         syncer->setAutoDelete(false);
0304         QSignalSpy spy(syncer, &KJob::result);
0305         QVERIFY(spy.isValid());
0306         syncer->setStreamingEnabled(true);
0307         QTest::qWait(0);
0308         QCOMPARE(spy.count(), 0);
0309 
0310         for (int i = 0; i < origItems.count(); ++i) {
0311             Item::List l;
0312             // Modify to trigger a changed signal
0313             l << modifyItem(origItems[i]);
0314             syncer->setIncrementalSyncItems(l, Item::List());
0315             if (i < origItems.count() - 1) {
0316                 QTest::qWait(0); // enter the event loop so itemsync actually can do something
0317             }
0318             QCOMPARE(spy.count(), 0);
0319         }
0320         syncer->deliveryDone();
0321         QTRY_COMPARE(spy.count(), 1);
0322         KJob *job = spy.at(0).at(0).value<KJob *>();
0323         QCOMPARE(job, syncer);
0324         QCOMPARE(job->error(), 0);
0325         QCOMPARE(transactionSpy.count(), 1);
0326 
0327         Item::List resultItems = fetchItems(col);
0328         QCOMPARE(resultItems.count(), origItems.count());
0329 
0330         delete syncer;
0331 
0332         QTest::qWait(100);
0333         QCOMPARE(deletedSpy.count(), 0);
0334         QCOMPARE(addedSpy.count(), 0);
0335         QTRY_COMPARE(changedSpy.count(), origItems.size());
0336     }
0337 
0338     void testEmptyIncrementalSync()
0339     {
0340         const Collection col = Collection(AkonadiTest::collectionIdFromPath(QStringLiteral("res1/foo")));
0341         QVERIFY(col.isValid());
0342         Item::List origItems = fetchItems(col);
0343 
0344         auto monitor = createCollectionMonitor(col);
0345         QSignalSpy deletedSpy(monitor.get(), &Monitor::itemRemoved);
0346         QSignalSpy addedSpy(monitor.get(), &Monitor::itemAdded);
0347         QSignalSpy changedSpy(monitor.get(), &Monitor::itemChanged);
0348 
0349         auto syncer = new ItemSync(col);
0350         syncer->setTransactionMode(ItemSync::SingleTransaction);
0351         QSignalSpy transactionSpy(syncer, &ItemSync::transactionCommitted);
0352         QVERIFY(transactionSpy.isValid());
0353         syncer->setIncrementalSyncItems(Item::List(), Item::List());
0354         AKVERIFYEXEC(syncer);
0355         // It would be better if we didn't have a transaction at all, but so far the transaction is still created
0356         QCOMPARE(transactionSpy.count(), 1);
0357 
0358         Item::List resultItems = fetchItems(col);
0359         QCOMPARE(resultItems.count(), origItems.count());
0360 
0361         QTest::qWait(100);
0362         QCOMPARE(deletedSpy.count(), 0);
0363         QCOMPARE(addedSpy.count(), 0);
0364         QCOMPARE(changedSpy.count(), 0);
0365     }
0366 
0367     void testIncrementalStreamingSyncBatchProcessing()
0368     {
0369         const Collection col = Collection(AkonadiTest::collectionIdFromPath(QStringLiteral("res1/foo")));
0370         QVERIFY(col.isValid());
0371         Item::List origItems = fetchItems(col);
0372 
0373         auto monitor = createCollectionMonitor(col);
0374         QSignalSpy deletedSpy(monitor.get(), &Monitor::itemRemoved);
0375         QSignalSpy addedSpy(monitor.get(), &Monitor::itemAdded);
0376         QSignalSpy changedSpy(monitor.get(), &Monitor::itemChanged);
0377 
0378         auto syncer = new ItemSync(col);
0379         QSignalSpy transactionSpy(syncer, &ItemSync::transactionCommitted);
0380         QVERIFY(transactionSpy.isValid());
0381         QSignalSpy spy(syncer, &KJob::result);
0382         QVERIFY(spy.isValid());
0383         syncer->setStreamingEnabled(true);
0384         syncer->setTransactionMode(ItemSync::MultipleTransactions);
0385         QTest::qWait(0);
0386         QCOMPARE(spy.count(), 0);
0387 
0388         for (int i = 0; i < syncer->batchSize(); ++i) {
0389             Item::List l;
0390             // Modify to trigger a changed signal
0391             l << modifyItem(origItems[i]);
0392             syncer->setIncrementalSyncItems(l, Item::List());
0393             if (i < (syncer->batchSize() - 1)) {
0394                 QTest::qWait(0); // enter the event loop so itemsync actually can do something
0395             }
0396             QCOMPARE(spy.count(), 0);
0397         }
0398         QTest::qWait(100);
0399         // this should process one batch of batchSize() items
0400         QTRY_COMPARE(changedSpy.count(), syncer->batchSize());
0401         QCOMPARE(transactionSpy.count(), 1); // one per batch
0402 
0403         for (int i = syncer->batchSize(); i < origItems.count(); ++i) {
0404             Item::List l;
0405             // Modify to trigger a changed signal
0406             l << modifyItem(origItems[i]);
0407             syncer->setIncrementalSyncItems(l, Item::List());
0408             if (i < origItems.count() - 1) {
0409                 QTest::qWait(0); // enter the event loop so itemsync actually can do something
0410             }
0411             QCOMPARE(spy.count(), 0);
0412         }
0413 
0414         syncer->deliveryDone();
0415         QTRY_COMPARE(spy.count(), 1);
0416         QCOMPARE(transactionSpy.count(), 2); // one per batch
0417         QTest::qWait(100);
0418 
0419         Item::List resultItems = fetchItems(col);
0420         QCOMPARE(resultItems.count(), origItems.count());
0421 
0422         QTest::qWait(100);
0423         QCOMPARE(deletedSpy.count(), 0);
0424         QCOMPARE(addedSpy.count(), 0);
0425         QTRY_COMPARE(changedSpy.count(), resultItems.count());
0426     }
0427 
0428     void testGidMerge()
0429     {
0430         Collection col(AkonadiTest::collectionIdFromPath(QStringLiteral("res3")));
0431         {
0432             Item item(QStringLiteral("application/octet-stream"));
0433             item.setRemoteId(QStringLiteral("rid1"));
0434             item.setGid(QStringLiteral("gid1"));
0435             item.setPayload<QByteArray>("payload1");
0436             auto job = new ItemCreateJob(item, col);
0437             AKVERIFYEXEC(job);
0438         }
0439         {
0440             Item item(QStringLiteral("application/octet-stream"));
0441             item.setRemoteId(QStringLiteral("rid2"));
0442             item.setGid(QStringLiteral("gid2"));
0443             item.setPayload<QByteArray>("payload1");
0444             auto job = new ItemCreateJob(item, col);
0445             AKVERIFYEXEC(job);
0446         }
0447         Item modifiedItem(QStringLiteral("application/octet-stream"));
0448         modifiedItem.setRemoteId(QStringLiteral("rid3"));
0449         modifiedItem.setGid(QStringLiteral("gid2"));
0450         modifiedItem.setPayload<QByteArray>("payload2");
0451 
0452         auto syncer = new ItemSync(col);
0453         syncer->setTransactionMode(ItemSync::MultipleTransactions);
0454         syncer->setIncrementalSyncItems(Item::List() << modifiedItem, Item::List());
0455         AKVERIFYEXEC(syncer);
0456 
0457         Item::List resultItems = fetchItems(col);
0458         QCOMPARE(resultItems.count(), 3);
0459 
0460         Item item;
0461         item.setGid(QStringLiteral("gid2"));
0462         auto fetchJob = new ItemFetchJob(item);
0463         fetchJob->fetchScope().fetchFullPayload();
0464         AKVERIFYEXEC(fetchJob);
0465         QCOMPARE(fetchJob->items().size(), 2);
0466         QCOMPARE(fetchJob->items().first().payload<QByteArray>(), QByteArray("payload2"));
0467         QCOMPARE(fetchJob->items().first().remoteId(), QString::fromLatin1("rid3"));
0468         QCOMPARE(fetchJob->items().at(1).payload<QByteArray>(), QByteArray("payload1"));
0469         QCOMPARE(fetchJob->items().at(1).remoteId(), QStringLiteral("rid2"));
0470     }
0471 
0472     /*
0473      * This test verifies that ItemSync doesn't prematurely emit its result if a job inside a transaction fails.
0474      * ItemSync is supposed to continue the sync but simply ignoring all delivered data.
0475      */
0476     void testFailingJob()
0477     {
0478         const Collection col = Collection(AkonadiTest::collectionIdFromPath(QStringLiteral("res1/foo")));
0479         QVERIFY(col.isValid());
0480         Item::List origItems = fetchItems(col);
0481 
0482         auto syncer = new ItemSync(col);
0483         QSignalSpy transactionSpy(syncer, &ItemSync::transactionCommitted);
0484         QVERIFY(transactionSpy.isValid());
0485         QSignalSpy spy(syncer, &KJob::result);
0486         QVERIFY(spy.isValid());
0487         syncer->setStreamingEnabled(true);
0488         syncer->setTransactionMode(ItemSync::MultipleTransactions);
0489         QTest::qWait(0);
0490         QCOMPARE(spy.count(), 0);
0491 
0492         for (int i = 0; i < syncer->batchSize(); ++i) {
0493             Item::List l;
0494             // Modify to trigger a changed signal
0495             Item item = modifyItem(origItems[i]);
0496             // item.setRemoteId(QByteArray("foo"));
0497             item.setRemoteId(QString());
0498             item.setId(-1);
0499             l << item;
0500             syncer->setIncrementalSyncItems(l, Item::List());
0501             if (i < (syncer->batchSize() - 1)) {
0502                 QTest::qWait(0); // enter the event loop so itemsync actually can do something
0503             }
0504             QCOMPARE(spy.count(), 0);
0505         }
0506         QTest::qWait(100);
0507         QTRY_COMPARE(spy.count(), 0);
0508 
0509         for (int i = syncer->batchSize(); i < origItems.count(); ++i) {
0510             Item::List l;
0511             // Modify to trigger a changed signal
0512             l << modifyItem(origItems[i]);
0513             syncer->setIncrementalSyncItems(l, Item::List());
0514             if (i < origItems.count() - 1) {
0515                 QTest::qWait(0); // enter the event loop so itemsync actually can do something
0516             }
0517             QCOMPARE(spy.count(), 0);
0518         }
0519 
0520         syncer->deliveryDone();
0521         QTRY_COMPARE(spy.count(), 1);
0522     }
0523 
0524     /*
0525      * This test verifies that ItemSync doesn't prematurely emit its result if a job inside a transaction fails, due to a duplicate.
0526      * This case used to break the TransactionSequence.
0527      * ItemSync is supposed to continue the sync but simply ignoring all delivered data.
0528      */
0529     void testFailingDueToDuplicateItem()
0530     {
0531         const Collection col = Collection(AkonadiTest::collectionIdFromPath(QStringLiteral("res1/foo")));
0532         QVERIFY(col.isValid());
0533         Item::List origItems = fetchItems(col);
0534 
0535         // Create a duplicate that will trigger an error during the first batch
0536         Item dupe = duplicateItem(origItems.at(0), col);
0537         origItems = fetchItems(col);
0538 
0539         auto syncer = new ItemSync(col);
0540         QSignalSpy transactionSpy(syncer, &ItemSync::transactionCommitted);
0541         QVERIFY(transactionSpy.isValid());
0542         QSignalSpy spy(syncer, &KJob::result);
0543         QVERIFY(spy.isValid());
0544         syncer->setStreamingEnabled(true);
0545         syncer->setTransactionMode(ItemSync::MultipleTransactions);
0546         QTest::qWait(0);
0547         QCOMPARE(spy.count(), 0);
0548 
0549         for (int i = 0; i < syncer->batchSize(); ++i) {
0550             Item::List l;
0551             // Modify to trigger a changed signal
0552             l << modifyItem(origItems[i]);
0553             syncer->setIncrementalSyncItems(l, Item::List());
0554             if (i < (syncer->batchSize() - 1)) {
0555                 QTest::qWait(0); // enter the event loop so itemsync actually can do something
0556             }
0557             QCOMPARE(spy.count(), 0);
0558         }
0559         QTest::qWait(100);
0560         // Ensure the job hasn't finished yet due to the errors
0561         QTRY_COMPARE(spy.count(), 0);
0562 
0563         for (int i = syncer->batchSize(); i < origItems.count(); ++i) {
0564             Item::List l;
0565             // Modify to trigger a changed signal
0566             l << modifyItem(origItems[i]);
0567             syncer->setIncrementalSyncItems(l, Item::List());
0568             if (i < origItems.count() - 1) {
0569                 QTest::qWait(0); // enter the event loop so itemsync actually can do something
0570             }
0571             QCOMPARE(spy.count(), 0);
0572         }
0573 
0574         syncer->deliveryDone();
0575         QTRY_COMPARE(spy.count(), 1);
0576 
0577         // cleanup
0578         auto del = new ItemDeleteJob(dupe, this);
0579         AKVERIFYEXEC(del);
0580     }
0581 
0582     void testFullSyncFailingDueToDuplicateItem()
0583     {
0584         const Collection col = Collection(AkonadiTest::collectionIdFromPath(QStringLiteral("res1/foo")));
0585         QVERIFY(col.isValid());
0586         Item::List origItems = fetchItems(col);
0587         // Create a duplicate that will trigger an error during the first batch
0588         Item dupe = duplicateItem(origItems.at(0), col);
0589         origItems = fetchItems(col);
0590 
0591         auto monitor = createCollectionMonitor(col);
0592         QSignalSpy deletedSpy(monitor.get(), &Monitor::itemRemoved);
0593         QSignalSpy addedSpy(monitor.get(), &Monitor::itemAdded);
0594         QSignalSpy changedSpy(monitor.get(), &Monitor::itemChanged);
0595 
0596         auto syncer = new ItemSync(col);
0597         syncer->setTransactionMode(ItemSync::SingleTransaction);
0598         QSignalSpy transactionSpy(syncer, &ItemSync::transactionCommitted);
0599         QVERIFY(transactionSpy.isValid());
0600         syncer->setFullSyncItems(origItems);
0601         QVERIFY(!syncer->exec());
0602         QCOMPARE(transactionSpy.count(), 1);
0603 
0604         Item::List resultItems = fetchItems(col);
0605         QCOMPARE(resultItems.count(), origItems.count());
0606         QTest::qWait(100);
0607         // QCOMPARE(deletedSpy.count(), 1); // ## is this correct?
0608         // QCOMPARE(addedSpy.count(), 1); // ## is this correct?
0609         QCOMPARE(changedSpy.count(), 0);
0610 
0611         // cleanup
0612         auto del = new ItemDeleteJob(dupe, this);
0613         AKVERIFYEXEC(del);
0614     }
0615 
0616     void testFullSyncManyItems()
0617     {
0618         // Given a collection with 1000 items
0619         const Collection col = Collection(AkonadiTest::collectionIdFromPath(QStringLiteral("res2/foo2")));
0620         QVERIFY(col.isValid());
0621 
0622         auto monitor = createCollectionMonitor(col);
0623         QSignalSpy addedSpy(monitor.get(), &Monitor::itemAdded);
0624 
0625         const int itemCount = 1000;
0626         createItems(col, itemCount);
0627         QTRY_COMPARE(addedSpy.count(), itemCount);
0628         addedSpy.clear();
0629 
0630         const Item::List origItems = fetchItems(col);
0631         QCOMPARE(origItems.size(), itemCount);
0632 
0633         QSignalSpy deletedSpy(monitor.get(), &Monitor::itemRemoved);
0634         QSignalSpy changedSpy(monitor.get(), &Monitor::itemChanged);
0635 
0636         QBENCHMARK {
0637             auto syncer = new ItemSync(col);
0638             syncer->setTransactionMode(ItemSync::SingleTransaction);
0639             QSignalSpy transactionSpy(syncer, &ItemSync::transactionCommitted);
0640             QVERIFY(transactionSpy.isValid());
0641             syncer->setFullSyncItems(origItems);
0642 
0643             AKVERIFYEXEC(syncer);
0644             QCOMPARE(transactionSpy.count(), 1);
0645         }
0646 
0647         const Item::List resultItems = fetchItems(col);
0648         QCOMPARE(resultItems.count(), origItems.count());
0649         QTest::qWait(100);
0650         QCOMPARE(deletedSpy.count(), 0);
0651         QCOMPARE(addedSpy.count(), 0);
0652         QCOMPARE(changedSpy.count(), 0);
0653 
0654         // delete all items; QBENCHMARK leads to the whole method being called more than once
0655         auto job = new ItemDeleteJob(resultItems);
0656         AKVERIFYEXEC(job);
0657     }
0658 
0659     void testUserCancel()
0660     {
0661         // Given a collection with 100 items
0662         const Collection col = Collection(AkonadiTest::collectionIdFromPath(QStringLiteral("res2/foo2")));
0663         QVERIFY(col.isValid());
0664 
0665         const Item::List itemsToDelete = fetchItems(col);
0666         if (!itemsToDelete.isEmpty()) {
0667             auto deleteJob = new ItemDeleteJob(itemsToDelete);
0668             AKVERIFYEXEC(deleteJob);
0669         }
0670 
0671         const int itemCount = 100;
0672         createItems(col, itemCount);
0673         const Item::List origItems = fetchItems(col);
0674         QCOMPARE(origItems.size(), itemCount);
0675 
0676         // and an ItemSync running
0677         auto syncer = new ItemSync(col);
0678         syncer->setTransactionMode(ItemSync::SingleTransaction);
0679         syncer->setFullSyncItems(origItems);
0680 
0681         // When the user cancels the ItemSync
0682         QTimer::singleShot(10ms, syncer, &ItemSync::rollback);
0683 
0684         // Then the itemsync should finish at some point, and not crash
0685         QVERIFY(!syncer->exec());
0686         QCOMPARE(syncer->errorString(), QStringLiteral("User canceled operation."));
0687 
0688         // Cleanup
0689         auto job = new ItemDeleteJob(origItems);
0690         AKVERIFYEXEC(job);
0691     }
0692 };
0693 
0694 QTEST_AKONADIMAIN(ItemsyncTest)
0695 
0696 #include "itemsynctest.moc"