Warning, file /pim/trojita/tests/Imap/test_Imap_Tasks_ObtainSynchronizedMailbox.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).

0001 /* Copyright (C) 2006 - 2014 Jan Kundrát <jkt@flaska.net>
0002 
0003    This file is part of the Trojita Qt IMAP e-mail client,
0004    http://trojita.flaska.net/
0005 
0006    This program is free software; you can redistribute it and/or
0007    modify it under the terms of the GNU General Public License as
0008    published by the Free Software Foundation; either version 2 of
0009    the License or (at your option) version 3 or any later version
0010    accepted by the membership of KDE e.V. (or its successor approved
0011    by the membership of KDE e.V.), which shall act as a proxy
0012    defined in Section 14 of version 3 of the license.
0013 
0014    This program is distributed in the hope that it will be useful,
0015    but WITHOUT ANY WARRANTY; without even the implied warranty of
0016    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0017    GNU General Public License for more details.
0018 
0019    You should have received a copy of the GNU General Public License
0020    along with this program.  If not, see <http://www.gnu.org/licenses/>.
0021 */
0022 
0023 #include "test_Imap_Tasks_ObtainSynchronizedMailbox.h"
0024 #include "Utils/FakeCapabilitiesInjector.h"
0025 #include "Streams/FakeSocket.h"
0026 #include "Imap/Model/ItemRoles.h"
0027 #include "Imap/Model/MailboxTree.h"
0028 #include "Imap/Model/MsgListModel.h"
0029 #include "Imap/Model/ThreadingMsgListModel.h"
0030 #include "Imap/Tasks/ObtainSynchronizedMailboxTask.h"
0031 
0032 
0033 void ImapModelObtainSynchronizedMailboxTest::init()
0034 {
0035     LibMailboxSync::init();
0036 
0037     using namespace Imap::Mailbox;
0038     MsgListModel *msgListModelA = new MsgListModel(model, model);
0039     ThreadingMsgListModel *threadingA = new ThreadingMsgListModel(msgListModelA);
0040     threadingA->setSourceModel(msgListModelA);
0041     MsgListModel *msgListModelB = new MsgListModel(model, model);
0042     ThreadingMsgListModel *threadingB = new ThreadingMsgListModel(msgListModelB);
0043     threadingB->setSourceModel(msgListModelB);
0044 }
0045 
0046 /** Verify syncing of an empty mailbox with just the EXISTS response
0047 
0048 Verify that we can synchronize a mailbox which is empty even if the server
0049 ommits most of the required replies and sends us just the EXISTS one. Also
0050 check the cache for valid state.
0051 */
0052 void ImapModelObtainSynchronizedMailboxTest::testSyncEmptyMinimal()
0053 {
0054     model->setProperty( "trojita-imap-noop-period", 10 );
0055 
0056     // Ask the model to sync stuff
0057     QCOMPARE( model->rowCount( msgListA ), 0 );
0058     cClient(t.mk("SELECT a\r\n"));
0059 
0060     // Try to feed it with absolute minimum data
0061     cServer(QByteArray("* 0 exists\r\n") + t.last("OK done\r\n"));
0062 
0063     // Verify that we indeed received what we wanted
0064     QCOMPARE( model->rowCount( msgListA ), 0 );
0065     Imap::Mailbox::TreeItemMsgList* list = dynamic_cast<Imap::Mailbox::TreeItemMsgList*>( static_cast<Imap::Mailbox::TreeItem*>( msgListA.internalPointer() ) );
0066     Q_ASSERT( list );
0067     QVERIFY( list->fetched() );
0068     //QTest::qWait( 100 );
0069     QCoreApplication::processEvents();
0070     cEmpty();
0071 
0072     // Now, let's try to re-sync it once again; the difference is that our cache now has "something"
0073     model->resyncMailbox( idxA );
0074     QCoreApplication::processEvents();
0075 
0076     // Verify that it indeed caused a re-synchronization
0077     list = dynamic_cast<Imap::Mailbox::TreeItemMsgList*>( static_cast<Imap::Mailbox::TreeItem*>( msgListA.internalPointer() ) );
0078     Q_ASSERT( list );
0079     QVERIFY( list->loading() );
0080     cClient(t.mk("SELECT a\r\n"));
0081     cServer(QByteArray("* 0 exists\r\n") + t.last("OK done\r\n"));
0082     cEmpty();
0083 
0084     // Check the cache
0085     Imap::Mailbox::SyncState syncState = model->cache()->mailboxSyncState( QStringLiteral("a") );
0086     QCOMPARE( syncState.exists(), 0u );
0087     QCOMPARE( syncState.flags(), QStringList() );
0088     // No messages, and hence we know that none of them could possibly be recent or unseen or whatever
0089     QCOMPARE( syncState.isUsableForNumbers(), true );
0090     QCOMPARE( syncState.isUsableForSyncing(), false );
0091     QCOMPARE( syncState.permanentFlags(), QStringList() );
0092     QCOMPARE( syncState.recent(), 0u );
0093     QCOMPARE( syncState.uidNext(), 0u );
0094     QCOMPARE( syncState.uidValidity(), 0u );
0095     QCOMPARE( syncState.unSeenCount(), 0u );
0096     QCOMPARE( syncState.unSeenOffset(), 0u );
0097 
0098     // No errors
0099     QVERIFY( errorSpy->isEmpty() );
0100 }
0101 
0102 /** Verify syncing of a non-empty mailbox with just the EXISTS response
0103 
0104 The interesting bits are what happens when the server cheats and returns just a very limited subset of mailbox metadata,
0105 just the EXISTS response in particular.
0106 */
0107 void ImapModelObtainSynchronizedMailboxTest::testSyncEmptyMinimalNonEmpty()
0108 {
0109     model->setProperty("trojita-imap-noop-period", 10);
0110 
0111     // Ask the model to sync stuff
0112     QCOMPARE(model->rowCount(msgListA), 0);
0113     cClient(t.mk("SELECT a\r\n"));
0114 
0115     // Try to feed it with absolute minimum data
0116     cServer(QByteArray("* 1 exists\r\n") + t.last("OK done\r\n"));
0117     cClient(t.mk("UID SEARCH ALL\r\n"));
0118     cServer("* SEARCH 666\r\n" + t.last("OK searched\r\n"));
0119     cClient(t.mk("FETCH 1 (FLAGS)\r\n"));
0120     cServer("* 1 FETCH (FLAGS ())\r\n" + t.last("OK fetched\r\n"));
0121     cEmpty();
0122 
0123     // Verify that we indeed received what we wanted
0124     QCOMPARE(model->rowCount(msgListA), 1);
0125     Imap::Mailbox::TreeItemMsgList* list = dynamic_cast<Imap::Mailbox::TreeItemMsgList*>(static_cast<Imap::Mailbox::TreeItem*>(msgListA.internalPointer()));
0126     Q_ASSERT(list);
0127     QVERIFY(list->fetched());
0128     QCoreApplication::processEvents();
0129     cEmpty();
0130 
0131     // Now, let's try to re-sync it once again; the difference is that our cache now has "something"
0132     model->resyncMailbox(idxA);
0133     QCoreApplication::processEvents();
0134 
0135     // Verify that it indeed caused a re-synchronization
0136     list = dynamic_cast<Imap::Mailbox::TreeItemMsgList*>(static_cast<Imap::Mailbox::TreeItem*>(msgListA.internalPointer()));
0137     Q_ASSERT(list);
0138     QVERIFY(list->loading());
0139     cClient(t.mk("SELECT a\r\n"));
0140     cServer(QByteArray("* 1 exists\r\n") + t.last("OK done\r\n"));
0141     // We still don't know anything else but EXISTS, so this is a full sync again
0142     cClient(t.mk("UID SEARCH ALL\r\n"));
0143     cServer("* SEARCH 666\r\n" + t.last("OK searched\r\n"));
0144     cClient(t.mk("FETCH 1 (FLAGS)\r\n"));
0145     cServer("* 1 FETCH (FLAGS ())\r\n" + t.last("OK fetched\r\n"));
0146     cEmpty();
0147 
0148     // Check the cache
0149     Imap::Mailbox::SyncState syncState = model->cache()->mailboxSyncState(QStringLiteral("a"));
0150     QCOMPARE(syncState.exists(), 1u);
0151     QCOMPARE(syncState.flags(), QStringList());
0152     // The flags are already synced, though
0153     QCOMPARE(syncState.isUsableForNumbers(), true);
0154     QCOMPARE(syncState.isUsableForSyncing(), false);
0155     QCOMPARE(syncState.permanentFlags(), QStringList());
0156     QCOMPARE(syncState.recent(), 0u);
0157     QCOMPARE(syncState.uidNext(), 667u);
0158     QCOMPARE(syncState.uidValidity(), 0u);
0159     QCOMPARE(syncState.unSeenCount(), 1u);
0160     QCOMPARE(syncState.unSeenOffset(), 0u);
0161 
0162     // No errors
0163     QVERIFY( errorSpy->isEmpty() );
0164 }
0165 
0166 /** @short Verify synchronization of an empty mailbox against a compliant IMAP4rev1 server
0167 
0168 This test verifies that we handle a compliant server's responses when we sync an empty mailbox.
0169 A check of the state of the cache after is completed, too.
0170 */
0171 void ImapModelObtainSynchronizedMailboxTest::testSyncEmptyNormal()
0172 {
0173     // Ask the model to sync stuff
0174     QCOMPARE( model->rowCount( msgListA ), 0 );
0175     cClient(t.mk("SELECT a\r\n"));
0176 
0177     // Try to feed it with absolute minimum data
0178     cServer(QByteArray("* FLAGS (\\Answered \\Flagged \\Deleted \\Seen \\Draft)\r\n"
0179                        "* OK [PERMANENTFLAGS (\\Answered \\Flagged \\Deleted \\Seen \\Draft \\*)] Flags permitted.\r\n"
0180                        "* 0 EXISTS\r\n"
0181                        "* 0 RECENT\r\n"
0182                        "* OK [UIDVALIDITY 666] UIDs valid\r\n"
0183                        "* OK [UIDNEXT 3] Predicted next UID\r\n")
0184             + t.last("OK [READ-WRITE] Select completed.\r\n"));
0185 
0186     // Verify that we indeed received what we wanted
0187     QCOMPARE( model->rowCount( msgListA ), 0 );
0188     Imap::Mailbox::TreeItemMsgList* list = dynamic_cast<Imap::Mailbox::TreeItemMsgList*>( static_cast<Imap::Mailbox::TreeItem*>( msgListA.internalPointer() ) );
0189     Q_ASSERT( list );
0190     QVERIFY( list->fetched() );
0191     cEmpty();
0192 
0193     // Check the cache
0194     Imap::Mailbox::SyncState syncState = model->cache()->mailboxSyncState( QStringLiteral("a") );
0195     QCOMPARE( syncState.exists(), 0u );
0196     QCOMPARE( syncState.flags(), QStringList() << QLatin1String("\\Answered") <<
0197               QLatin1String("\\Flagged") << QLatin1String("\\Deleted") <<
0198               QLatin1String("\\Seen") << QLatin1String("\\Draft") );
0199     QCOMPARE( syncState.isUsableForNumbers(), true );
0200     QCOMPARE( syncState.isUsableForSyncing(), true );
0201     QCOMPARE( syncState.permanentFlags(), QStringList() << QLatin1String("\\Answered") <<
0202               QLatin1String("\\Flagged") << QLatin1String("\\Deleted") <<
0203               QLatin1String("\\Seen") << QLatin1String("\\Draft") << QLatin1String("\\*") );
0204     QCOMPARE( syncState.recent(), 0u );
0205     QCOMPARE( syncState.uidNext(), 3u );
0206     QCOMPARE( syncState.uidValidity(), 666u );
0207     QCOMPARE( syncState.unSeenCount(), 0u );
0208     QCOMPARE(syncState.unSeenOffset(), 0u);
0209 
0210     // Now, let's try to re-sync it once again; the difference is that our cache now has "something"
0211     // and that we feed it with a rather limited set of responses
0212     model->resyncMailbox( idxA );
0213     QCoreApplication::processEvents();
0214 
0215     // Verify that it indeed caused a re-synchronization
0216     list = dynamic_cast<Imap::Mailbox::TreeItemMsgList*>( static_cast<Imap::Mailbox::TreeItem*>( msgListA.internalPointer() ) );
0217     Q_ASSERT( list );
0218     QVERIFY( list->loading() );
0219     cClient(t.mk("SELECT a\r\n"));
0220     cServer(QByteArray("* 0 exists\r\n* NO a random no in inserted here\r\n") + t.last("OK done\r\n"));
0221     cEmpty();
0222 
0223     // Check the cache; now it should be almost empty
0224     syncState = model->cache()->mailboxSyncState( QStringLiteral("a") );
0225     QCOMPARE( syncState.exists(), 0u );
0226     QCOMPARE( syncState.flags(), QStringList() );
0227     QCOMPARE( syncState.isUsableForNumbers(), true );
0228     QCOMPARE( syncState.isUsableForSyncing(), false );
0229     QCOMPARE( syncState.permanentFlags(), QStringList() );
0230     QCOMPARE( syncState.recent(), 0u );
0231 
0232     // No errors
0233     QVERIFY( errorSpy->isEmpty() );
0234 }
0235 
0236 /** @short Initial sync of a mailbox that contains some messages */
0237 void ImapModelObtainSynchronizedMailboxTest::testSyncWithMessages()
0238 {
0239     existsA = 33;
0240     uidValidityA = 333666;
0241     for ( uint i = 1; i <= existsA; ++i ) {
0242         uidMapA.append( i * 1.3 );
0243     }
0244     uidNextA = qMax( 666u, uidMapA.last() );
0245     helperSyncAWithMessagesEmptyState();
0246     helperVerifyUidMapA();
0247 }
0248 
0249 /** @short Go back to a selected mailbox after some time, the mailbox doesn't have any modifications */
0250 void ImapModelObtainSynchronizedMailboxTest::testResyncNoArrivals()
0251 {
0252     existsA = 42;
0253     uidValidityA = 1337;
0254     for ( uint i = 1; i <= existsA; ++i ) {
0255         uidMapA.append( 6 + i * 3.6 );
0256     }
0257     uidNextA = qMax( 666u, uidMapA.last() );
0258     helperSyncAWithMessagesEmptyState();
0259     helperSyncBNoMessages();
0260     helperSyncAWithMessagesNoArrivals();
0261     helperSyncBNoMessages();
0262     helperSyncAWithMessagesNoArrivals();
0263     helperVerifyUidMapA();
0264     QModelIndex index = idxA.model()->index(0, 0, idxA);
0265     helperOneFlagUpdate( index.model()->index(10, 0, index) );
0266 }
0267 
0268 /** @short Test new message arrivals happening on each resync */
0269 void ImapModelObtainSynchronizedMailboxTest::testResyncOneNew()
0270 {
0271     existsA = 17;
0272     uidValidityA = 800500;
0273     for ( uint i = 1; i <= existsA; ++i ) {
0274         uidMapA.append( 3 + i * 1.3 );
0275     }
0276     uidNextA = qMax( 666u, uidMapA.last() );
0277     helperSyncAWithMessagesEmptyState();
0278     helperSyncBNoMessages();
0279     helperSyncASomeNew( 1 );
0280     helperVerifyUidMapA();
0281     helperSyncBNoMessages();
0282     helperSyncASomeNew( 3 );
0283     helperVerifyUidMapA();
0284 }
0285 
0286 /** @short Test inconsistency in the local cache where UIDNEXT got decreased without UIDVALIDITY change */
0287 void ImapModelObtainSynchronizedMailboxTest::testDecreasedUidNext()
0288 {
0289     // Initial state
0290     existsA = 3;
0291     uidValidityA = 333666;
0292     for ( uint i = 1; i <= existsA; ++i ) {
0293         uidMapA.append(i);
0294     }
0295     // Make sure the UID really gets decreeased
0296     Q_ASSERT(uidNextA < uidMapA.last() + 1);
0297     uidNextA = uidMapA.last() + 1;
0298     helperSyncAWithMessagesEmptyState();
0299     helperVerifyUidMapA();
0300     helperSyncBNoMessages();
0301 
0302     // Now perform the nasty change...
0303     --existsA;
0304     uidMapA.pop_back();
0305     --uidNextA;
0306 
0307     // ...and resync again, this should scream loud, but not crash
0308     QCOMPARE( model->rowCount( msgListA ), 3 );
0309     model->switchToMailbox( idxA );
0310     QCoreApplication::processEvents();
0311     QCoreApplication::processEvents();
0312     helperSyncAFullSync();
0313 }
0314 
0315 /** @short Synchronization when the server doesn't report UIDNEXT at all */
0316 void ImapModelObtainSynchronizedMailboxTest::helperMissingUidNext(const MessageNumberChange mode)
0317 {
0318     Imap::Mailbox::SyncState oldState;
0319     oldState.setExists(3);
0320     oldState.setUidValidity(666);
0321     oldState.setUidNext(10); // because Trojita's code computes this during points in time the view is fully synced
0322     oldState.setUnSeenCount(0);
0323     oldState.setRecent(0);
0324     model->cache()->setMailboxSyncState(QStringLiteral("a"), oldState);
0325     model->cache()->setUidMapping(QStringLiteral("a"), Imap::Uids() << 4 << 6 << 7);
0326 
0327     QSignalSpy rowsInserted(model, SIGNAL(rowsInserted(QModelIndex,int,int)));
0328     QSignalSpy rowsRemoved(model, SIGNAL(rowsRemoved(QModelIndex,int,int)));
0329 
0330     model->switchToMailbox(idxA);
0331     cClient(t.mk("SELECT a\r\n"));
0332     // prepopulation from cache should be ignored
0333     QCOMPARE(rowsInserted.size(), 1);
0334     QCOMPARE(rowsInserted[0], QVariantList() << QModelIndex(msgListA) << 0 << 2);
0335     rowsInserted.clear();
0336     QVERIFY(rowsRemoved.isEmpty());
0337     switch (mode) {
0338     case MessageNumberChange::LESS:
0339         cServer("* 2 EXISTS\r\n");
0340         break;
0341     case MessageNumberChange::MORE:
0342         cServer("* 4 EXISTS\r\n");
0343         break;
0344     case MessageNumberChange::SAME:
0345         cServer("* 3 EXISTS\r\n");
0346         break;
0347     }
0348     cServer("* 0 RECENT\r\n"
0349             "* OK [UIDVALIDITY 666] UIDs valid\r\n"
0350             + t.last("OK selected\r\n"));
0351     QVERIFY(rowsRemoved.isEmpty());
0352     QVERIFY(rowsInserted.isEmpty());
0353     cClient(t.mk("UID SEARCH ALL\r\n"));
0354     // the actual UID data is garbage and not needed
0355     switch (mode) {
0356     case MessageNumberChange::LESS:
0357         cServer("* SEARCH 5 9\r\n");
0358         break;
0359     case MessageNumberChange::MORE:
0360         cServer("* SEARCH 5 8 7 9\r\n");
0361         break;
0362     case MessageNumberChange::SAME:
0363         cServer("* SEARCH 5 8 9\r\n");
0364         break;
0365     }
0366     cServer(t.last("OK search\r\n"));
0367     switch (mode) {
0368     case MessageNumberChange::LESS:
0369         cClient(t.mk("FETCH 1:2 (FLAGS)\r\n"));
0370         break;
0371     case MessageNumberChange::MORE:
0372         cClient(t.mk("FETCH 1:4 (FLAGS)\r\n"));
0373         break;
0374     case MessageNumberChange::SAME:
0375         cClient(t.mk("FETCH 1:3 (FLAGS)\r\n"));
0376         break;
0377     }
0378     cServer("* 1 FETCH (FLAGS (\\Seen))\r\n"
0379             "* 2 FETCH (FLAGS (\\Seen))\r\n");
0380     switch (mode) {
0381     case MessageNumberChange::LESS:
0382         oldState.setExists(2);
0383         break;
0384     case MessageNumberChange::MORE:
0385         cServer("* 3 FETCH (FLAGS (\\Seen))\r\n"
0386                 "* 4 FETCH (FLAGS (\\Seen))\r\n");
0387         oldState.setExists(4);
0388         break;
0389     case MessageNumberChange::SAME:
0390         cServer("* 3 FETCH (FLAGS (\\Seen))\r\n");
0391         break;
0392     }
0393     cServer(t.last("OK fetch\r\n"));
0394     cEmpty();
0395     justKeepTask();
0396     QCOMPARE(model->cache()->mailboxSyncState(QLatin1String("a")), oldState);
0397 }
0398 
0399 void ImapModelObtainSynchronizedMailboxTest::testMisingUidNextLess()
0400 {
0401     helperMissingUidNext(MessageNumberChange::LESS);
0402 }
0403 
0404 void ImapModelObtainSynchronizedMailboxTest::testMisingUidNextMore()
0405 {
0406     helperMissingUidNext(MessageNumberChange::MORE);
0407 }
0408 
0409 void ImapModelObtainSynchronizedMailboxTest::testMisingUidNextSame()
0410 {
0411     helperMissingUidNext(MessageNumberChange::SAME);
0412 }
0413 
0414 /**
0415 Test that going from an empty mailbox to a bigger one works correctly, especially that the untagged
0416 EXISTS response which belongs to the SELECT gets picked up by the new mailbox and not the old one
0417 */
0418 void ImapModelObtainSynchronizedMailboxTest::testSyncTwoLikeCyrus()
0419 {
0420     // Ask the model to sync stuff
0421     QCOMPARE( model->rowCount( msgListB ), 0 );
0422     cClient(t.mk("SELECT b\r\n"));
0423 
0424     cServer(QByteArray("* 0 EXISTS\r\n"
0425                        "* 0 RECENT\r\n"
0426                        "* FLAGS (\\Answered \\Flagged \\Draft \\Deleted \\Seen)\r\n"
0427                        "* OK [PERMANENTFLAGS (\\Answered \\Flagged \\Draft \\Deleted \\Seen \\*)] Ok\r\n"
0428                        "* OK [UIDVALIDITY 1290594339] Ok\r\n"
0429                        "* OK [UIDNEXT 1] Ok\r\n"
0430                        "* OK [HIGHESTMODSEQ 1] Ok\r\n"
0431                        "* OK [URLMECH INTERNAL] Ok\r\n")
0432             + t.last("OK [READ-WRITE] Completed\r\n"));
0433 
0434     // Verify that we indeed received what we wanted
0435     Imap::Mailbox::TreeItemMsgList* listB = dynamic_cast<Imap::Mailbox::TreeItemMsgList*>( static_cast<Imap::Mailbox::TreeItem*>( msgListB.internalPointer() ) );
0436     Q_ASSERT( listB );
0437     QVERIFY( listB->fetched() );
0438 
0439     QCOMPARE( model->rowCount( msgListB ), 0 );
0440     cEmpty();
0441     QVERIFY( errorSpy->isEmpty() );
0442 
0443     QCOMPARE( model->rowCount( msgListA ), 0 );
0444     cClient(t.mk("SELECT a\r\n"));
0445 
0446     cServer(QByteArray("* 1 EXISTS\r\n"
0447                        "* 0 RECENT\r\n"
0448                        "* FLAGS (\\Answered \\Flagged \\Draft \\Deleted \\Seen hasatt hasnotd)\r\n"
0449                        "* OK [PERMANENTFLAGS (\\Answered \\Flagged \\Draft \\Deleted \\Seen hasatt hasnotd \\*)] Ok\r\n"
0450                        "* OK [UIDVALIDITY 1290593745] Ok\r\n"
0451                        "* OK [UIDNEXT 2] Ok\r\n"
0452                        "* OK [HIGHESTMODSEQ 9] Ok\r\n"
0453                        "* OK [URLMECH INTERNAL] Ok\r\n")
0454             + t.last("OK [READ-WRITE] Completed"));
0455     Imap::Mailbox::TreeItemMsgList* listA = dynamic_cast<Imap::Mailbox::TreeItemMsgList*>( static_cast<Imap::Mailbox::TreeItem*>( msgListA.internalPointer() ) );
0456     Q_ASSERT( listA );
0457     QVERIFY( ! listA->fetched() );
0458     cEmpty();
0459     QVERIFY( errorSpy->isEmpty() );
0460 }
0461 
0462 void ImapModelObtainSynchronizedMailboxTest::testSyncTwoInParallel()
0463 {
0464     // This will select both mailboxes, one after another
0465     QCOMPARE( model->rowCount( msgListA ), 0 );
0466     QCOMPARE( model->rowCount( msgListB ), 0 );
0467     cClient(t.mk("SELECT a\r\n"));
0468     cServer(QByteArray("* 1 EXISTS\r\n"
0469                        "* 0 RECENT\r\n"
0470                        "* FLAGS (\\Answered \\Flagged \\Draft \\Deleted \\Seen hasatt hasnotd)\r\n"
0471                        "* OK [PERMANENTFLAGS (\\Answered \\Flagged \\Draft \\Deleted \\Seen hasatt hasnotd \\*)] Ok\r\n"
0472                        "* OK [UIDVALIDITY 1290593745] Ok\r\n"
0473                        "* OK [UIDNEXT 2] Ok\r\n"
0474                        "* OK [HIGHESTMODSEQ 9] Ok\r\n"
0475                        "* OK [URLMECH INTERNAL] Ok\r\n")
0476             + t.last("OK [READ-WRITE] Completed\r\n"));
0477     cClient(t.mk("UID SEARCH ALL\r\n"));
0478     QCOMPARE( model->rowCount( msgListA ), 1 );
0479     QCOMPARE( model->rowCount( msgListB ), 0 );
0480     QCoreApplication::processEvents();
0481     QCoreApplication::processEvents();
0482     QCoreApplication::processEvents();
0483     QCOMPARE( model->rowCount( msgListA ), 1 );
0484     QCOMPARE( model->rowCount( msgListB ), 0 );
0485     // ...none of them are synced yet
0486 
0487     cServer(QByteArray("* SEARCH 1\r\n") + t.last("OK Completed (1 msgs in 0.000 secs)\r\n"));
0488     QCOMPARE( model->rowCount( msgListA ), 1 );
0489     // the first one should contain a message now
0490     cClient(t.mk("FETCH 1 (FLAGS)\r\n"));
0491     // without the tagged OK -- se below
0492     cServer(QByteArray("* 1 FETCH (FLAGS (\\Seen hasatt hasnotd))\r\n"));
0493     QModelIndex msg1A = model->index( 0, 0, msgListA );
0494 
0495     // Requesting message data should delay entering the second mailbox.
0496     // That's why we had to delay the tagged OK for FLAGS.
0497     QCOMPARE( model->data( msg1A, Imap::Mailbox::RoleMessageMessageId ), QVariant() );
0498     cServer(t.last("OK Completed (0.000 sec)\r\n"));
0499     QCoreApplication::processEvents();
0500     QCoreApplication::processEvents();
0501     QCOMPARE( model->rowCount( msgListA ), 1 );
0502     QCoreApplication::processEvents();
0503     QCoreApplication::processEvents();
0504 
0505     cClient(t.mk("UID FETCH 1 (" FETCH_METADATA_ITEMS ")\r\n"));
0506     // let's try to get around without specifying ENVELOPE and BODYSTRUCTURE
0507     cServer(QByteArray("* 1 FETCH (UID 1 RFC822.SIZE 13021)\r\n") + t.last("OK Completed\r\n"));
0508     cClient(t.mk(QByteArray("SELECT b\r\n")));
0509     cEmpty();
0510     QVERIFY( errorSpy->isEmpty() );
0511 }
0512 
0513 /** @short Test whether a change in the UIDVALIDITY results in a complete resync */
0514 void ImapModelObtainSynchronizedMailboxTest::testResyncUidValidity()
0515 {
0516     existsA = 42;
0517     uidValidityA = 1337;
0518     for ( uint i = 1; i <= existsA; ++i ) {
0519         uidMapA.append( 6 + i * 3.6 );
0520     }
0521     uidNextA = qMax( 666u, uidMapA.last() );
0522     helperSyncAWithMessagesEmptyState();
0523 
0524     // Change UIDVALIDITY
0525     uidValidityA = 333666;
0526     helperSyncBNoMessages();
0527 
0528     QCOMPARE( model->rowCount( msgListA ), 42 );
0529     model->switchToMailbox( idxA );
0530     QCoreApplication::processEvents();
0531     QCoreApplication::processEvents();
0532 
0533     helperSyncAFullSync();
0534 }
0535 
0536 void ImapModelObtainSynchronizedMailboxTest::testFlagReSyncBenchmark()
0537 {
0538     existsA = 100000;
0539 #if defined(__has_feature)
0540 #  if  __has_feature(address_sanitizer)
0541     qDebug() << "ASAN build detected, benchmarking with fewer items";
0542     existsA = 1333;
0543 #  endif
0544 #endif
0545     uidValidityA = 333;
0546     for (uint i = 1; i <= existsA; ++i) {
0547         uidMapA << i;
0548     }
0549     uidNextA = existsA + 2;
0550     helperSyncAWithMessagesEmptyState();
0551 
0552     QBENCHMARK {
0553         helperSyncBNoMessages();
0554         helperSyncAWithMessagesNoArrivals();
0555     }
0556 }
0557 
0558 /** @short Make sure that calling Model::resyncMailbox() preloads data from the cache */
0559 void ImapModelObtainSynchronizedMailboxTest::testReloadReadsFromCache()
0560 {
0561     Imap::Mailbox::SyncState sync;
0562     sync.setExists(3);
0563     sync.setUidValidity(666);
0564     sync.setUidNext(15);
0565     Imap::Uids uidMap;
0566     uidMap << 6 << 9 << 10;
0567     model->cache()->setMailboxSyncState(QStringLiteral("a"), sync);
0568     model->cache()->setUidMapping(QStringLiteral("a"), uidMap);
0569     model->resyncMailbox(idxA);
0570     cClient(t.mk("SELECT a\r\n"));
0571     cServer("* 3 EXISTS\r\n"
0572             "* OK [UIDVALIDITY 666] .\r\n"
0573             "* OK [UIDNEXT 15] .\r\n");
0574     cServer(t.last("OK selected\r\n"));
0575     cClient(t.mk("FETCH 1:3 (FLAGS)\r\n"));
0576     cServer("* 1 FETCH (FLAGS (\\SEEN x))\r\n"
0577             "* 2 FETCH (FLAGS (y))\r\n"
0578             "* 3 FETCH (FLAGS (\\seen z))\r\n");
0579     cServer(t.last("OK fetch\r\n"));
0580     cEmpty();
0581     sync.setUnSeenCount(1);
0582     sync.setRecent(0);
0583     QCOMPARE(model->cache()->mailboxSyncState("a"), sync);
0584     QCOMPARE(static_cast<int>(model->cache()->mailboxSyncState("a").exists()), uidMap.size());
0585     QCOMPARE(model->cache()->uidMapping("a"), uidMap);
0586     QCOMPARE(model->cache()->msgFlags("a", 6), QStringList() << "\\Seen" << "x");
0587     QCOMPARE(model->cache()->msgFlags("a", 9), QStringList() << "y");
0588     QCOMPARE(model->cache()->msgFlags("a", 10), QStringList() << "\\Seen" << "z");
0589     justKeepTask();
0590 }
0591 
0592 /** @short Test synchronization of a mailbox with on-disk cache being up-to-date, but no data in memory */
0593 void ImapModelObtainSynchronizedMailboxTest::testCacheNoChange()
0594 {
0595     Imap::Mailbox::SyncState sync;
0596     sync.setExists(3);
0597     sync.setUidValidity(666);
0598     sync.setUidNext(15);
0599     sync.setUnSeenCount(3);
0600     sync.setRecent(0);
0601     Imap::Uids uidMap;
0602     uidMap << 6 << 9 << 10;
0603     model->cache()->setMailboxSyncState(QStringLiteral("a"), sync);
0604     model->cache()->setUidMapping(QStringLiteral("a"), uidMap);
0605     QCOMPARE(model->rowCount(msgListA), 0);
0606     cClient(t.mk("SELECT a\r\n"));
0607     cServer("* 3 EXISTS\r\n"
0608             "* OK [UIDVALIDITY 666] .\r\n"
0609             "* OK [UIDNEXT 15] .\r\n");
0610     cServer(t.last("OK selected\r\n"));
0611     cClient(t.mk("FETCH 1:3 (FLAGS)\r\n"));
0612     cServer("* 1 FETCH (FLAGS (x))\r\n"
0613             "* 2 FETCH (FLAGS (y))\r\n"
0614             "* 3 FETCH (FLAGS (z))\r\n");
0615     cServer(t.last("OK fetch\r\n"));
0616     cEmpty();
0617     QCOMPARE(model->cache()->mailboxSyncState("a"), sync);
0618     QCOMPARE(static_cast<int>(model->cache()->mailboxSyncState("a").exists()), uidMap.size());
0619     QCOMPARE(model->cache()->uidMapping("a"), uidMap);
0620     QCOMPARE(model->cache()->msgFlags("a", 6), QStringList() << "x");
0621     QCOMPARE(model->cache()->msgFlags("a", 9), QStringList() << "y");
0622     QCOMPARE(model->cache()->msgFlags("a", 10), QStringList() << "z");
0623     justKeepTask();
0624 }
0625 
0626 /** @short Test UIDVALIDITY changes since the last cached state */
0627 void ImapModelObtainSynchronizedMailboxTest::testCacheUidValidity()
0628 {
0629     Imap::Mailbox::SyncState sync;
0630     sync.setExists(3);
0631     sync.setUidValidity(333);
0632     sync.setUidNext(15);
0633     Imap::Uids uidMap;
0634     uidMap << 6 << 9 << 10;
0635 
0636     // Fill the cache with some values which shall make sense in the "previous state"
0637     model->cache()->setMailboxSyncState(QStringLiteral("a"), sync);
0638     model->cache()->setUidMapping(QStringLiteral("a"), uidMap);
0639     // Don't forget about the flags
0640     model->cache()->setMsgFlags(QStringLiteral("a"), 1, QStringList() << QStringLiteral("f1"));
0641     model->cache()->setMsgFlags(QStringLiteral("a"), 6, QStringList() << QStringLiteral("f6"));
0642     // And even message metadata
0643     Imap::Mailbox::AbstractCache::MessageDataBundle bundle;
0644     bundle.envelope = Imap::Message::Envelope(QDateTime::currentDateTime(), QStringLiteral("subj"),
0645                                               QList<Imap::Message::MailAddress>(), QList<Imap::Message::MailAddress>(),
0646                                               QList<Imap::Message::MailAddress>(), QList<Imap::Message::MailAddress>(),
0647                                               QList<Imap::Message::MailAddress>(), QList<Imap::Message::MailAddress>(),
0648                                               QList<QByteArray>(), QByteArray());
0649     bundle.uid = 1;
0650     model->cache()->setMessageMetadata(QStringLiteral("a"), 1, bundle);
0651     bundle.uid = 6;
0652     model->cache()->setMessageMetadata(QStringLiteral("a"), 6, bundle);
0653     // And of course also message parts
0654     model->cache()->setMsgPart(QStringLiteral("a"), 1, "1", "blah");
0655     model->cache()->setMsgPart(QStringLiteral("a"), 6, "1", "blah");
0656 
0657     QCOMPARE(model->rowCount(msgListA), 0);
0658     cClient(t.mk("SELECT a\r\n"));
0659     cServer("* 3 EXISTS\r\n"
0660             "* OK [UIDVALIDITY 666] .\r\n"
0661             "* OK [UIDNEXT 15] .\r\n");
0662     cServer(t.last("OK selected\r\n"));
0663 
0664     // The UIDVALIDTY change should be already discovered
0665     QCOMPARE(model->cache()->msgFlags("a", 1), QStringList());
0666     QCOMPARE(model->cache()->msgFlags("a", 3), QStringList());
0667     QCOMPARE(model->cache()->msgFlags("a", 6), QStringList());
0668     QCOMPARE(model->cache()->messageMetadata("a", 1), Imap::Mailbox::AbstractCache::MessageDataBundle());
0669     QCOMPARE(model->cache()->messageMetadata("a", 3), Imap::Mailbox::AbstractCache::MessageDataBundle());
0670     QCOMPARE(model->cache()->messageMetadata("a", 6), Imap::Mailbox::AbstractCache::MessageDataBundle());
0671     QCOMPARE(model->cache()->messagePart("a", 1, "1"), QByteArray());
0672     QCOMPARE(model->cache()->messagePart("a", 3, "1"), QByteArray());
0673     QCOMPARE(model->cache()->messagePart("a", 6, "1"), QByteArray());
0674 
0675     cClient(t.mk("UID SEARCH ALL\r\n"));
0676     cServer(QByteArray("* SEARCH 6 9 10\r\n") + t.last("OK uid search\r\n"));
0677     cClient(t.mk("FETCH 1:3 (FLAGS)\r\n"));
0678     cServer("* 1 FETCH (FLAGS (x))\r\n"
0679             "* 2 FETCH (FLAGS (y))\r\n"
0680             "* 3 FETCH (FLAGS (z))\r\n");
0681     cServer(t.last("OK fetch\r\n"));
0682     cEmpty();
0683     sync.setUidValidity(666);
0684     sync.setUnSeenCount(3);
0685     sync.setRecent(0);
0686     QCOMPARE(model->cache()->mailboxSyncState("a"), sync);
0687     QCOMPARE(static_cast<int>(model->cache()->mailboxSyncState("a").exists()), uidMap.size());
0688     QCOMPARE(model->cache()->uidMapping("a"), uidMap);
0689     QCOMPARE(model->cache()->msgFlags("a", 6), QStringList() << "x");
0690     QCOMPARE(model->cache()->msgFlags("a", 9), QStringList() << "y");
0691     QCOMPARE(model->cache()->msgFlags("a", 10), QStringList() << "z");
0692     justKeepTask();
0693 }
0694 
0695 /** @short Test synchronization of a mailbox with on-disk cache when one new message arrives */
0696 void ImapModelObtainSynchronizedMailboxTest::testCacheArrivals()
0697 {
0698     Imap::Mailbox::SyncState sync;
0699     sync.setExists(3);
0700     sync.setUidValidity(666);
0701     sync.setUidNext(15);
0702     Imap::Uids uidMap;
0703     uidMap << 6 << 9 << 10;
0704     model->cache()->setMailboxSyncState(QStringLiteral("a"), sync);
0705     model->cache()->setUidMapping(QStringLiteral("a"), uidMap);
0706     QCOMPARE(model->rowCount(msgListA), 0);
0707     cClient(t.mk("SELECT a\r\n"));
0708     cServer("* 4 EXISTS\r\n"
0709             "* OK [UIDVALIDITY 666] .\r\n"
0710             "* OK [UIDNEXT 16] .\r\n");
0711     cServer(t.last("OK selected\r\n"));
0712     cClient(t.mk("UID SEARCH UID 15:*\r\n"));
0713     cServer("* SEARCH 42\r\n");
0714     cServer(t.last("OK uids\r\n"));
0715     uidMap << 42;
0716     sync.setUidNext(43);
0717     sync.setExists(4);
0718     cClient(t.mk("FETCH 1:4 (FLAGS)\r\n"));
0719     cServer("* 1 FETCH (FLAGS (x))\r\n"
0720             "* 2 FETCH (FLAGS (y))\r\n"
0721             "* 3 FETCH (FLAGS (z))\r\n"
0722             "* 4 FETCH (FLAGS (fn))\r\n");
0723     cServer(t.last("OK fetch\r\n"));
0724     cEmpty();
0725     sync.setUnSeenCount(4);
0726     sync.setRecent(0);
0727     QCOMPARE(model->cache()->mailboxSyncState("a"), sync);
0728     QCOMPARE(static_cast<int>(model->cache()->mailboxSyncState("a").exists()), uidMap.size());
0729     QCOMPARE(model->cache()->uidMapping("a"), uidMap);
0730     QCOMPARE(model->cache()->msgFlags("a", 6), QStringList() << "x");
0731     QCOMPARE(model->cache()->msgFlags("a", 9), QStringList() << "y");
0732     QCOMPARE(model->cache()->msgFlags("a", 10), QStringList() << "z");
0733     QCOMPARE(model->cache()->msgFlags("a", 42), QStringList() << "fn");
0734     justKeepTask();
0735 }
0736 
0737 void ImapModelObtainSynchronizedMailboxTest::testCacheArrivalRaceDuringUid()
0738 {
0739     helperCacheArrivalRaceDuringUid(WITHOUT_ESEARCH);
0740 }
0741 
0742 void ImapModelObtainSynchronizedMailboxTest::testCacheArrivalRaceDuringUid_ESearch()
0743 {
0744     helperCacheArrivalRaceDuringUid(WITH_ESEARCH);
0745 }
0746 
0747 /** @short Number of messages grows twice
0748 
0749 The first increment is when compared to the original state, the second one after the UID SEARCH is issued,
0750 but before its response arrives.
0751 */
0752 void ImapModelObtainSynchronizedMailboxTest::helperCacheArrivalRaceDuringUid(const ESearchMode esearch)
0753 {
0754     if (esearch == WITH_ESEARCH) {
0755         FakeCapabilitiesInjector injector(model);
0756         injector.injectCapability(QStringLiteral("ESEARCH"));
0757     }
0758     Imap::Mailbox::SyncState sync;
0759     sync.setExists(3);
0760     sync.setUidValidity(666);
0761     sync.setUidNext(15);
0762     Imap::Uids uidMap;
0763     uidMap << 6 << 9 << 10;
0764     model->cache()->setMailboxSyncState(QStringLiteral("a"), sync);
0765     model->cache()->setUidMapping(QStringLiteral("a"), uidMap);
0766     QCOMPARE(model->rowCount(msgListA), 0);
0767     cClient(t.mk("SELECT a\r\n"));
0768     cServer("* 4 EXISTS\r\n"
0769             "* OK [UIDVALIDITY 666] .\r\n"
0770             "* OK [UIDNEXT 16] .\r\n");
0771     cServer(t.last("OK selected\r\n"));
0772     if (esearch == WITH_ESEARCH) {
0773         cClient(t.mk("UID SEARCH RETURN (ALL) UID 15:*\r\n"));
0774         cServer(QByteArray("* 5 EXISTS\r\n* ESEARCH (TAG ") + t.last() + ") UID ALL 42:43\r\n");
0775     } else {
0776         cClient(t.mk("UID SEARCH UID 15:*\r\n"));
0777         cServer("* 5 EXISTS\r\n* SEARCH 42 43\r\n");
0778     }
0779     cServer(t.last("OK uids\r\n"));
0780     uidMap << 42 << 43;
0781     sync.setUidNext(44);
0782     sync.setExists(5);
0783     sync.setUnSeenCount(5);
0784     sync.setRecent(0);
0785     cClient(t.mk("FETCH 1:5 (FLAGS)\r\n"));
0786     cServer("* 1 FETCH (FLAGS (x))\r\n"
0787             "* 2 FETCH (FLAGS (y))\r\n"
0788             "* 3 FETCH (FLAGS (z))\r\n"
0789             "* 4 FETCH (FLAGS (fn))\r\n"
0790             "* 5 FETCH (FLAGS (a))\r\n");
0791     cServer(t.last("OK fetch\r\n"));
0792     cEmpty();
0793     QCOMPARE(model->cache()->mailboxSyncState("a"), sync);
0794     QCOMPARE(static_cast<int>(model->cache()->mailboxSyncState("a").exists()), uidMap.size());
0795     QCOMPARE(model->cache()->uidMapping("a"), uidMap);
0796     QCOMPARE(model->cache()->msgFlags("a", 6), QStringList() << "x");
0797     QCOMPARE(model->cache()->msgFlags("a", 9), QStringList() << "y");
0798     QCOMPARE(model->cache()->msgFlags("a", 10), QStringList() << "z");
0799     QCOMPARE(model->cache()->msgFlags("a", 42), QStringList() << "fn");
0800     QCOMPARE(model->cache()->msgFlags("a", 43), QStringList() << "a");
0801     justKeepTask();
0802 }
0803 
0804 /** @short Number of messages grows twice
0805 
0806 The first increment is when compared to the original state, the second one after the UID SEARCH is issued, after its untagged
0807 response arrives, but before the tagged OK.
0808 */
0809 void ImapModelObtainSynchronizedMailboxTest::testCacheArrivalRaceDuringUid2()
0810 {
0811     Imap::Mailbox::SyncState sync;
0812     sync.setExists(3);
0813     sync.setUidValidity(666);
0814     sync.setUidNext(15);
0815     Imap::Uids uidMap;
0816     uidMap << 6 << 9 << 10;
0817     model->cache()->setMailboxSyncState(QStringLiteral("a"), sync);
0818     model->cache()->setUidMapping(QStringLiteral("a"), uidMap);
0819     QCOMPARE(model->rowCount(msgListA), 0);
0820     cClient(t.mk("SELECT a\r\n"));
0821     cServer("* 4 EXISTS\r\n"
0822             "* OK [UIDVALIDITY 666] .\r\n"
0823             "* OK [UIDNEXT 16] .\r\n");
0824     cServer(t.last("OK selected\r\n"));
0825     cClient(t.mk("UID SEARCH UID 15:*\r\n"));
0826     cServer("* SEARCH 42\r\n* 5 EXISTS\r\n");
0827     cServer(t.last("OK uids\r\n"));
0828     uidMap << 42;
0829     sync.setUidNext(43);
0830     sync.setExists(5);
0831     sync.setUnSeenCount(5);
0832     sync.setRecent(0);
0833     QByteArray newArrivalsQuery = t.mk("UID FETCH 43:* (FLAGS)\r\n");
0834     QByteArray newArrivalsResponse = t.last("OK uid fetch\r\n");
0835     QByteArray preexistingFlagsQuery = t.mk("FETCH 1:5 (FLAGS)\r\n");
0836     QByteArray preexistingFlagsResponse = t.last("OK fetch\r\n");
0837     cClient(newArrivalsQuery + preexistingFlagsQuery);
0838     cServer("* 5 FETCH (UID 66 FLAGS (a))\r\n");
0839     cServer("* 1 FETCH (FLAGS (x))\r\n"
0840             "* 2 FETCH (FLAGS (y))\r\n"
0841             "* 3 FETCH (FLAGS (z))\r\n"
0842             "* 4 FETCH (FLAGS (fn))\r\n"
0843             "* 5 FETCH (FLAGS (a))\r\n");
0844     cServer(newArrivalsResponse + preexistingFlagsResponse);
0845     uidMap << 66;
0846     sync.setUidNext(67);
0847     cEmpty();
0848     QCOMPARE(model->cache()->mailboxSyncState("a"), sync);
0849     QCOMPARE(static_cast<int>(model->cache()->mailboxSyncState("a").exists()), uidMap.size());
0850     QCOMPARE(model->cache()->uidMapping("a"), uidMap);
0851     QCOMPARE(model->cache()->msgFlags("a", 6), QStringList() << "x");
0852     QCOMPARE(model->cache()->msgFlags("a", 9), QStringList() << "y");
0853     QCOMPARE(model->cache()->msgFlags("a", 10), QStringList() << "z");
0854     QCOMPARE(model->cache()->msgFlags("a", 42), QStringList() << "fn");
0855     // UID 43 did not exist at any time after all
0856     QCOMPARE(model->cache()->msgFlags("a", 43), QStringList());
0857     QCOMPARE(model->cache()->msgFlags("a", 66), QStringList() << "a");
0858     justKeepTask();
0859 }
0860 /** @short New message arrives when syncing flags */
0861 void ImapModelObtainSynchronizedMailboxTest::testCacheArrivalRaceDuringFlags()
0862 {
0863     Imap::Mailbox::SyncState sync;
0864     sync.setExists(3);
0865     sync.setUidValidity(666);
0866     sync.setUidNext(15);
0867     Imap::Uids uidMap;
0868     uidMap << 6 << 9 << 10;
0869     model->cache()->setMailboxSyncState(QStringLiteral("a"), sync);
0870     model->cache()->setUidMapping(QStringLiteral("a"), uidMap);
0871     QCOMPARE(model->rowCount(msgListA), 0);
0872     cClient(t.mk("SELECT a\r\n"));
0873     cServer("* 4 EXISTS\r\n"
0874             "* OK [UIDVALIDITY 666] .\r\n"
0875             "* OK [UIDNEXT 16] .\r\n");
0876     cServer(t.last("OK selected\r\n"));
0877     cClient(t.mk("UID SEARCH UID 15:*\r\n"));
0878     cServer("* SEARCH 42\r\n");
0879     cServer(t.last("OK uids\r\n"));
0880     cClient(t.mk("FETCH 1:4 (FLAGS)\r\n"));
0881     cServer("* 1 FETCH (FLAGS (x))\r\n"
0882             "* 2 FETCH (FLAGS (y))\r\n"
0883             "* 5 EXISTS\r\n"
0884             "* 3 FETCH (FLAGS (z))\r\n"
0885             "* 4 FETCH (FLAGS (fn))\r\n"
0886             // notice that we're adding unsolicited data for a new message here
0887             "* 5 FETCH (FLAGS (blah))\r\n");
0888     // The new arrival shall not be present in the *persistent cache* at this point -- that's not an atomic update (yet)
0889     QCOMPARE(model->cache()->mailboxSyncState("a"), sync);
0890     QByteArray fetchTermination = t.last("OK fetch\r\n");
0891     cClient(t.mk("UID FETCH 43:* (FLAGS)\r\n"));
0892     cServer(fetchTermination);
0893     cServer("* 5 FETCH (FLAGS (gah) UID 60)\r\n" + t.last("OK new discovery\r\n"));
0894     sync.setExists(5);
0895     sync.setUidNext(61);
0896     sync.setUnSeenCount(5);
0897     sync.setRecent(0);
0898     uidMap << 42 << 60;
0899     cEmpty();
0900     // At this point, the cache shall be up-to-speed again
0901     QCOMPARE(model->cache()->mailboxSyncState("a"), sync);
0902     QCOMPARE(static_cast<int>(model->cache()->mailboxSyncState("a").exists()), uidMap.size());
0903     QCOMPARE(model->cache()->uidMapping("a"), uidMap);
0904     QCOMPARE(model->cache()->msgFlags("a", 6), QStringList() << "x");
0905     QCOMPARE(model->cache()->msgFlags("a", 9), QStringList() << "y");
0906     QCOMPARE(model->cache()->msgFlags("a", 10), QStringList() << "z");
0907     QCOMPARE(model->cache()->msgFlags("a", 42), QStringList() << "fn");
0908     QCOMPARE(model->cache()->msgFlags("a", 60), QStringList() << "gah");
0909     justKeepTask();
0910 }
0911 
0912 
0913 
0914 void ImapModelObtainSynchronizedMailboxTest::testCacheExpunges()
0915 {
0916     helperCacheExpunges(WITHOUT_ESEARCH);
0917 }
0918 
0919 void ImapModelObtainSynchronizedMailboxTest::testCacheExpunges_ESearch()
0920 {
0921     helperCacheExpunges(WITH_ESEARCH);
0922 }
0923 
0924 /** @short Test synchronization of a mailbox with on-disk cache when one message got deleted */
0925 void ImapModelObtainSynchronizedMailboxTest::helperCacheExpunges(const ESearchMode esearch)
0926 {
0927     if (esearch == WITH_ESEARCH) {
0928         FakeCapabilitiesInjector injector(model);
0929         injector.injectCapability(QStringLiteral("ESEARCH"));
0930     }
0931     Imap::Mailbox::SyncState sync;
0932     sync.setExists(6);
0933     sync.setUidValidity(666);
0934     sync.setUidNext(15);
0935     Imap::Uids uidMap;
0936     uidMap << 6 << 9 << 10 << 11 << 12 << 14;
0937     model->cache()->setMailboxSyncState(QStringLiteral("a"), sync);
0938     model->cache()->setUidMapping(QStringLiteral("a"), uidMap);
0939     model->cache()->setMsgFlags(QStringLiteral("a"), 9, QStringList() << QStringLiteral("foo"));
0940     QCOMPARE(model->rowCount(msgListA), 0);
0941     cClient(t.mk("SELECT a\r\n"));
0942     cServer("* 5 EXISTS\r\n"
0943             "* OK [UIDVALIDITY 666] .\r\n"
0944             "* OK [UIDNEXT 15] .\r\n");
0945     cServer(t.last("OK selected\r\n"));
0946     if (esearch == WITH_ESEARCH) {
0947         cClient(t.mk("UID SEARCH RETURN (ALL) ALL\r\n"));
0948         cServer(QByteArray("* ESEARCH (TAG ") + t.last() + ") UID ALL 6,10:12,14\r\n");
0949     } else {
0950         cClient(t.mk("UID SEARCH ALL\r\n"));
0951         cServer("* SEARCH 6 10 11 12 14\r\n");
0952     }
0953     cServer(t.last("OK uids\r\n"));
0954     uidMap.remove(1);
0955     sync.setExists(5);
0956     sync.setUnSeenCount(5);
0957     sync.setRecent(0);
0958     cClient(t.mk("FETCH 1:5 (FLAGS)\r\n"));
0959     cServer("* 1 FETCH (FLAGS (x))\r\n"
0960             "* 2 FETCH (FLAGS (z))\r\n"
0961             "* 3 FETCH (FLAGS (a))\r\n"
0962             "* 4 FETCH (FLAGS (b))\r\n"
0963             "* 5 FETCH (FLAGS (c))\r\n"
0964             );
0965     cServer(t.last("OK fetch\r\n"));
0966     cEmpty();
0967     QCOMPARE(model->cache()->mailboxSyncState("a"), sync);
0968     QCOMPARE(static_cast<int>(model->cache()->mailboxSyncState("a").exists()), uidMap.size());
0969     QCOMPARE(model->cache()->uidMapping("a"), uidMap);
0970     QCOMPARE(model->cache()->msgFlags("a", 6), QStringList() << "x");
0971     QCOMPARE(model->cache()->msgFlags("a", 10), QStringList() << "z");
0972     QCOMPARE(model->cache()->msgFlags("a", 11), QStringList() << "a");
0973     QCOMPARE(model->cache()->msgFlags("a", 12), QStringList() << "b");
0974     QCOMPARE(model->cache()->msgFlags("a", 14), QStringList() << "c");
0975     // Flags for the deleted message shall be gone
0976     QCOMPARE(model->cache()->msgFlags("a", 9), QStringList());
0977     justKeepTask();
0978 }
0979 
0980 /** @short Test two expunges, once during normal sync and then once again during the UID syncing */
0981 void ImapModelObtainSynchronizedMailboxTest::testCacheExpungesDuringUid()
0982 {
0983     Imap::Mailbox::SyncState sync;
0984     sync.setExists(6);
0985     sync.setUidValidity(666);
0986     sync.setUidNext(15);
0987     Imap::Uids uidMap;
0988     uidMap << 6 << 9 << 10 << 11 << 12 << 14;
0989     model->cache()->setMailboxSyncState(QStringLiteral("a"), sync);
0990     model->cache()->setUidMapping(QStringLiteral("a"), uidMap);
0991     model->cache()->setMsgFlags(QStringLiteral("a"), 9, QStringList() << QStringLiteral("foo"));
0992     QCOMPARE(model->rowCount(msgListA), 0);
0993     cClient(t.mk("SELECT a\r\n"));
0994     cServer("* 5 EXISTS\r\n"
0995             "* OK [UIDVALIDITY 666] .\r\n"
0996             "* OK [UIDNEXT 15] .\r\n");
0997     cServer(t.last("OK selected\r\n"));
0998     cClient(t.mk("UID SEARCH ALL\r\n"));
0999     cServer("* 4 EXPUNGE\r\n* SEARCH 6 10 11 14\r\n");
1000     cServer(t.last("OK uids\r\n"));
1001     uidMap.remove(1);
1002     uidMap.remove(3);
1003     sync.setExists(4);
1004     sync.setUnSeenCount(4);
1005     sync.setRecent(0);
1006     cClient(t.mk("FETCH 1:4 (FLAGS)\r\n"));
1007     cServer("* 1 FETCH (FLAGS (x))\r\n"
1008             "* 2 FETCH (FLAGS (z))\r\n"
1009             "* 3 FETCH (FLAGS (a))\r\n"
1010             "* 4 FETCH (FLAGS (c))\r\n"
1011             );
1012     cServer(t.last("OK fetch\r\n"));
1013     cEmpty();
1014     QCOMPARE(model->cache()->mailboxSyncState("a"), sync);
1015     QCOMPARE(static_cast<int>(model->cache()->mailboxSyncState("a").exists()), uidMap.size());
1016     QCOMPARE(model->cache()->uidMapping("a"), uidMap);
1017     QCOMPARE(model->cache()->msgFlags("a", 6), QStringList() << "x");
1018     QCOMPARE(model->cache()->msgFlags("a", 10), QStringList() << "z");
1019     QCOMPARE(model->cache()->msgFlags("a", 11), QStringList() << "a");
1020     QCOMPARE(model->cache()->msgFlags("a", 14), QStringList() << "c");
1021     // Flags for the deleted messages shall be gone
1022     QCOMPARE(model->cache()->msgFlags("a", 9), QStringList());
1023     QCOMPARE(model->cache()->msgFlags("a", 12), QStringList());
1024     justKeepTask();
1025 }
1026 
1027 /** @short Test two expunges, once during normal sync and then once again immediately after the UID syncing */
1028 void ImapModelObtainSynchronizedMailboxTest::testCacheExpungesDuringUid2()
1029 {
1030     Imap::Mailbox::SyncState sync;
1031     sync.setExists(6);
1032     sync.setUidValidity(666);
1033     sync.setUidNext(15);
1034     Imap::Uids uidMap;
1035     uidMap << 6 << 9 << 10 << 11 << 12 << 14;
1036     model->cache()->setMailboxSyncState(QStringLiteral("a"), sync);
1037     model->cache()->setUidMapping(QStringLiteral("a"), uidMap);
1038     model->cache()->setMsgFlags(QStringLiteral("a"), 9, QStringList() << QStringLiteral("foo"));
1039     QCOMPARE(model->rowCount(msgListA), 0);
1040     cClient(t.mk("SELECT a\r\n"));
1041     cServer("* 5 EXISTS\r\n"
1042             "* OK [UIDVALIDITY 666] .\r\n"
1043             "* OK [UIDNEXT 15] .\r\n");
1044     cServer(t.last("OK selected\r\n"));
1045     cClient(t.mk("UID SEARCH ALL\r\n"));
1046     cServer("* SEARCH 6 10 11 12 14\r\n* 4 EXPUNGE\r\n");
1047     cServer(t.last("OK uids\r\n"));
1048     uidMap.remove(1);
1049     uidMap.remove(3);
1050     sync.setExists(4);
1051     sync.setUnSeenCount(4);
1052     sync.setRecent(0);
1053     cClient(t.mk("FETCH 1:4 (FLAGS)\r\n"));
1054     cServer("* 1 FETCH (FLAGS (x))\r\n"
1055             "* 2 FETCH (FLAGS (z))\r\n"
1056             "* 3 FETCH (FLAGS (a))\r\n"
1057             "* 4 FETCH (FLAGS (c))\r\n"
1058             );
1059     cServer(t.last("OK fetch\r\n"));
1060     cEmpty();
1061     QCOMPARE(model->cache()->mailboxSyncState("a"), sync);
1062     QCOMPARE(static_cast<int>(model->cache()->mailboxSyncState("a").exists()), uidMap.size());
1063     QCOMPARE(model->cache()->uidMapping("a"), uidMap);
1064     QCOMPARE(model->cache()->msgFlags("a", 6), QStringList() << "x");
1065     QCOMPARE(model->cache()->msgFlags("a", 10), QStringList() << "z");
1066     QCOMPARE(model->cache()->msgFlags("a", 11), QStringList() << "a");
1067     QCOMPARE(model->cache()->msgFlags("a", 14), QStringList() << "c");
1068     // Flags for the deleted messages shall be gone
1069     QCOMPARE(model->cache()->msgFlags("a", 9), QStringList());
1070     QCOMPARE(model->cache()->msgFlags("a", 12), QStringList());
1071     justKeepTask();
1072 }
1073 
1074 /** @short Test two expunges, once during normal sync and then once again before the UID syncing */
1075 void ImapModelObtainSynchronizedMailboxTest::testCacheExpungesDuringSelect()
1076 {
1077     Imap::Mailbox::SyncState sync;
1078     sync.setExists(6);
1079     sync.setUidValidity(666);
1080     sync.setUidNext(15);
1081     Imap::Uids uidMap;
1082     uidMap << 6 << 9 << 10 << 11 << 12 << 14;
1083     model->cache()->setMailboxSyncState(QStringLiteral("a"), sync);
1084     model->cache()->setUidMapping(QStringLiteral("a"), uidMap);
1085     model->cache()->setMsgFlags(QStringLiteral("a"), 9, QStringList() << QStringLiteral("foo"));
1086     QCOMPARE(model->rowCount(msgListA), 0);
1087     cClient(t.mk("SELECT a\r\n"));
1088     cServer("* 5 EXISTS\r\n"
1089             "* OK [UIDVALIDITY 666] .\r\n"
1090             "* OK [UIDNEXT 15] .\r\n"
1091             "* 4 EXPUNGE\r\n");
1092     cServer(t.last("OK selected\r\n"));
1093     cClient(t.mk("UID SEARCH ALL\r\n"));
1094     cServer("* SEARCH 6 10 11 14\r\n");
1095     cServer(t.last("OK uids\r\n"));
1096     uidMap.remove(1);
1097     uidMap.remove(3);
1098     sync.setExists(4);
1099     cClient(t.mk("FETCH 1:4 (FLAGS)\r\n"));
1100     cServer("* 1 FETCH (FLAGS (x))\r\n"
1101             "* 2 FETCH (FLAGS (z))\r\n"
1102             "* 3 FETCH (FLAGS (a))\r\n"
1103             "* 4 FETCH (FLAGS (c))\r\n"
1104             );
1105     cServer(t.last("OK fetch\r\n"));
1106     cEmpty();
1107     sync.setUnSeenCount(4);
1108     sync.setRecent(0);
1109     QCOMPARE(model->cache()->mailboxSyncState("a"), sync);
1110     QCOMPARE(static_cast<int>(model->cache()->mailboxSyncState("a").exists()), uidMap.size());
1111     QCOMPARE(model->cache()->uidMapping("a"), uidMap);
1112     QCOMPARE(model->cache()->msgFlags("a", 6), QStringList() << "x");
1113     QCOMPARE(model->cache()->msgFlags("a", 10), QStringList() << "z");
1114     QCOMPARE(model->cache()->msgFlags("a", 11), QStringList() << "a");
1115     QCOMPARE(model->cache()->msgFlags("a", 14), QStringList() << "c");
1116     // Flags for the deleted messages shall be gone
1117     QCOMPARE(model->cache()->msgFlags("a", 9), QStringList());
1118     QCOMPARE(model->cache()->msgFlags("a", 12), QStringList());
1119     justKeepTask();
1120 }
1121 
1122 /** @short Test two expunges, once during normal sync and then once again when syncing flags */
1123 void ImapModelObtainSynchronizedMailboxTest::testCacheExpungesDuringFlags()
1124 {
1125     Imap::Mailbox::SyncState sync;
1126     sync.setExists(6);
1127     sync.setUidValidity(666);
1128     sync.setUidNext(15);
1129     sync.setUnSeenCount(6);
1130     sync.setRecent(0);
1131     Imap::Uids uidMap;
1132     uidMap << 6 << 9 << 10 << 11 << 12 << 14;
1133     model->cache()->setMailboxSyncState(QStringLiteral("a"), sync);
1134     model->cache()->setUidMapping(QStringLiteral("a"), uidMap);
1135     model->cache()->setMsgFlags(QStringLiteral("a"), 9, QStringList() << QStringLiteral("foo"));
1136     QCOMPARE(model->rowCount(msgListA), 0);
1137     cClient(t.mk("SELECT a\r\n"));
1138     cServer("* 5 EXISTS\r\n"
1139             "* OK [UIDVALIDITY 666] .\r\n"
1140             "* OK [UIDNEXT 15] .\r\n");
1141     cServer(t.last("OK selected\r\n"));
1142     cClient(t.mk("UID SEARCH ALL\r\n"));
1143     cServer("* SEARCH 6 10 11 12 14\r\n");
1144     cServer(t.last("OK uids\r\n"));
1145     uidMap.remove(1);
1146     sync.setExists(5);
1147     cClient(t.mk("FETCH 1:5 (FLAGS)\r\n"));
1148     cServer("* 1 FETCH (FLAGS (x))\r\n"
1149             "* 2 FETCH (FLAGS (z))\r\n"
1150             "* 4 EXPUNGE\r\n"
1151             "* 3 FETCH (FLAGS (a))\r\n"
1152             "* 4 FETCH (FLAGS (c))\r\n"
1153             );
1154     cServer(t.last("OK fetch\r\n"));
1155     uidMap.remove(3);
1156     sync.setExists(4);
1157     sync.setUnSeenCount(4);
1158     cEmpty();
1159     QCOMPARE(model->cache()->mailboxSyncState("a"), sync);
1160     QCOMPARE(static_cast<int>(model->cache()->mailboxSyncState("a").exists()), uidMap.size());
1161     QCOMPARE(model->cache()->uidMapping("a"), uidMap);
1162     QCOMPARE(model->cache()->msgFlags("a", 6), QStringList() << "x");
1163     QCOMPARE(model->cache()->msgFlags("a", 10), QStringList() << "z");
1164     QCOMPARE(model->cache()->msgFlags("a", 11), QStringList() << "a");
1165     QCOMPARE(model->cache()->msgFlags("a", 14), QStringList() << "c");
1166     // Flags for the deleted messages shall be gone
1167     QCOMPARE(model->cache()->msgFlags("a", 9), QStringList());
1168     QCOMPARE(model->cache()->msgFlags("a", 12), QStringList());
1169     justKeepTask();
1170 }
1171 
1172 /** @short The mailbox contains one new message which gets deleted before we got a chance to ask for its UID */
1173 void ImapModelObtainSynchronizedMailboxTest::testCacheArrivalsImmediatelyDeleted()
1174 {
1175     Imap::Mailbox::SyncState sync;
1176     sync.setExists(3);
1177     sync.setUidValidity(666);
1178     sync.setUidNext(15);
1179     sync.setUnSeenCount(3);
1180     sync.setRecent(0);
1181     Imap::Uids uidMap;
1182     uidMap << 6 << 9 << 10;
1183     model->cache()->setMailboxSyncState(QStringLiteral("a"), sync);
1184     model->cache()->setUidMapping(QStringLiteral("a"), uidMap);
1185     QCOMPARE(model->rowCount(msgListA), 0);
1186     cClient(t.mk("SELECT a\r\n"));
1187     cServer("* 4 EXISTS\r\n"
1188             "* OK [UIDVALIDITY 666] .\r\n"
1189             "* OK [UIDNEXT 16] .\r\n");
1190     cServer(t.last("OK selected\r\n"));
1191     cClient(t.mk("UID SEARCH UID 15:*\r\n"));
1192     cServer("* 4 EXPUNGE\r\n* SEARCH \r\n");
1193     cServer(t.last("OK uids\r\n"));
1194     // We know that at least one message has arrived, so the UIDNEXT *must* have changed.
1195     sync.setUidNext(16);
1196     sync.setExists(3);
1197     cClient(t.mk("FETCH 1:3 (FLAGS)\r\n"));
1198     cServer("* 1 FETCH (FLAGS (x))\r\n"
1199             "* 2 FETCH (FLAGS (y))\r\n"
1200             "* 3 FETCH (FLAGS (z))\r\n");
1201     cServer(t.last("OK fetch\r\n"));
1202     cEmpty();
1203     QCOMPARE(model->cache()->mailboxSyncState("a"), sync);
1204     QCOMPARE(static_cast<int>(model->cache()->mailboxSyncState("a").exists()), uidMap.size());
1205     QCOMPARE(model->cache()->uidMapping("a"), uidMap);
1206     QCOMPARE(model->cache()->msgFlags("a", 6), QStringList() << "x");
1207     QCOMPARE(model->cache()->msgFlags("a", 9), QStringList() << "y");
1208     QCOMPARE(model->cache()->msgFlags("a", 10), QStringList() << "z");
1209     QCOMPARE(model->cache()->msgFlags("a", 15), QStringList());
1210     QCOMPARE(model->cache()->msgFlags("a", 16), QStringList());
1211     justKeepTask();
1212 }
1213 
1214 /** @short The mailbox contains one new message.  One of the old ones gets deleted while fetching UIDs. */
1215 void ImapModelObtainSynchronizedMailboxTest::testCacheArrivalsOldDeleted()
1216 {
1217     Imap::Mailbox::SyncState sync;
1218     sync.setExists(3);
1219     sync.setUidValidity(666);
1220     sync.setUidNext(15);
1221     sync.setUnSeenCount(3);
1222     sync.setRecent(0);
1223     Imap::Uids uidMap;
1224     uidMap << 6 << 9 << 10;
1225     model->cache()->setMailboxSyncState(QStringLiteral("a"), sync);
1226     model->cache()->setUidMapping(QStringLiteral("a"), uidMap);
1227     QCOMPARE(model->rowCount(msgListA), 0);
1228     cClient(t.mk("SELECT a\r\n"));
1229     cServer("* 4 EXISTS\r\n"
1230             "* OK [UIDVALIDITY 666] .\r\n"
1231             "* OK [UIDNEXT 16] .\r\n");
1232     cServer(t.last("OK selected\r\n"));
1233     cClient(t.mk("UID SEARCH UID 15:*\r\n"));
1234     cServer("* 3 EXPUNGE\r\n* SEARCH 33\r\n");
1235     cServer(t.last("OK uids\r\n"));
1236     sync.setUidNext(34);
1237     uidMap.remove(2);
1238     uidMap << 33;
1239     sync.setExists(3);
1240     cClient(t.mk("FETCH 1:3 (FLAGS)\r\n"));
1241     cServer("* 1 FETCH (FLAGS (x))\r\n"
1242             "* 2 FETCH (FLAGS (y))\r\n"
1243             "* 3 FETCH (FLAGS (blah))\r\n");
1244     cServer(t.last("OK fetch\r\n"));
1245     cEmpty();
1246     QCOMPARE(model->cache()->mailboxSyncState("a"), sync);
1247     QCOMPARE(static_cast<int>(model->cache()->mailboxSyncState("a").exists()), uidMap.size());
1248     QCOMPARE(model->cache()->uidMapping("a"), uidMap);
1249     QCOMPARE(model->cache()->msgFlags("a", 6), QStringList() << "x");
1250     QCOMPARE(model->cache()->msgFlags("a", 9), QStringList() << "y");
1251     QCOMPARE(model->cache()->msgFlags("a", 33), QStringList() << "blah");
1252     QCOMPARE(model->cache()->msgFlags("a", 10), QStringList());
1253     QCOMPARE(model->cache()->msgFlags("a", 15), QStringList());
1254     QCOMPARE(model->cache()->msgFlags("a", 16), QStringList());
1255     justKeepTask();
1256 }
1257 
1258 /** @short Mailbox is said to contain some new messages. While performing the sync, many events occur. */
1259 void ImapModelObtainSynchronizedMailboxTest::testCacheArrivalsThenDynamic()
1260 {
1261     Imap::Mailbox::SyncState sync;
1262     sync.setExists(10);
1263     sync.setUidValidity(666);
1264     sync.setUidNext(100);
1265     Imap::Uids uidMap;
1266     for (uint i = 1; i <= sync.exists(); ++i)
1267         uidMap << i;
1268     model->cache()->setMailboxSyncState(QStringLiteral("a"), sync);
1269     model->cache()->setUidMapping(QStringLiteral("a"), uidMap);
1270     QCOMPARE(model->rowCount(msgListA), 0);
1271     cClient(t.mk("SELECT a\r\n"));
1272     // The sync indicates that there are five more messages and nothing else
1273     cServer("* 15 EXISTS\r\n"
1274             "* OK [UIDVALIDITY 666] .\r\n"
1275             "* OK [UIDNEXT 105] .\r\n");
1276     cServer(t.last("OK selected\r\n"));
1277     cServer(
1278         // One more message arrives
1279         "* 16 EXISTS\r\n"
1280         // The last of the previously known messages gets deleted
1281         "* 10 EXPUNGE\r\n"
1282         // The very first arrival (which happened between the disconnect and this sync) goes away
1283         "* 10 EXPUNGE\r\n"
1284             );
1285     cClient(t.mk("UID SEARCH UID 100:*\r\n"));
1286     // One of the old messages get deleted
1287     cServer("* 3 EXPUNGE\r\n");
1288     cServer("* SEARCH 102 103 104 105 106\r\n");
1289     cServer(t.last("OK uids\r\n"));
1290     // That's the last of the previously known, the first "10 EXPUNGE"
1291     uidMap.remove(9);
1292     // the second "10 EXPUNGE" is not reflected here at all, as it's for the new arrivals
1293     // This one is for the "3 EXPUNGE"
1294     uidMap.remove(2);
1295     uidMap << 102 << 103 << 104 << 105 << 106;
1296     sync.setUidNext(107);
1297     sync.setExists(13);
1298     cClient(t.mk("FETCH 1:13 (FLAGS)\r\n"));
1299     cServer("* 2 EXPUNGE\r\n");
1300     uidMap.remove(1);
1301     cServer("* 12 EXPUNGE\r\n");
1302     uidMap.remove(11);
1303     sync.setExists(11);
1304     cServer("* 1 FETCH (FLAGS (x))\r\n"
1305             "* 2 FETCH (FLAGS (y))\r\n"
1306             "* 3 FETCH (FLAGS (z))\r\n"
1307             "* 4 FETCH (FLAGS (a))\r\n"
1308             "* 5 FETCH (FLAGS (b))\r\n"
1309             "* 6 FETCH (FLAGS (c))\r\n"
1310             "* 7 FETCH (FLAGS (d))\r\n"
1311             "* 8 FETCH (FLAGS (x102))\r\n"
1312             "* 9 FETCH (FLAGS (x103))\r\n"
1313             "* 10 FETCH (FLAGS (x104))\r\n"
1314             "* 11 FETCH (FLAGS (x105))\r\n");
1315     // Add two, delete the last of them
1316     cServer("* 13 EXISTS\r\n* 13 EXPUNGE\r\n");
1317     sync.setExists(12);
1318     cServer(t.last("OK fetch\r\n"));
1319     cClient(t.mk("UID FETCH 107:* (FLAGS)\r\n"));
1320     cServer("* 12 FETCH (UID 109 FLAGS (last))\r\n");
1321     uidMap << 109;
1322     cServer(t.last("OK uid fetch flags done\r\n"));
1323     cEmpty();
1324     sync.setUidNext(110);
1325     sync.setUnSeenCount(12);
1326     sync.setRecent(0);
1327     QCOMPARE(model->cache()->mailboxSyncState("a"), sync);
1328     QCOMPARE(static_cast<int>(model->cache()->mailboxSyncState("a").exists()), uidMap.size());
1329     QCOMPARE(model->cache()->uidMapping("a"), uidMap);
1330     // Known UIDs at this point: 1, 4, 5, 6, 7, 8, 9, 102, 103, 104, 105, 109
1331     QCOMPARE(model->cache()->msgFlags("a", 1), QStringList() << "x");
1332     QCOMPARE(model->cache()->msgFlags("a", 4), QStringList() << "y");
1333     QCOMPARE(model->cache()->msgFlags("a", 5), QStringList() << "z");
1334     QCOMPARE(model->cache()->msgFlags("a", 6), QStringList() << "a");
1335     QCOMPARE(model->cache()->msgFlags("a", 7), QStringList() << "b");
1336     QCOMPARE(model->cache()->msgFlags("a", 8), QStringList() << "c");
1337     QCOMPARE(model->cache()->msgFlags("a", 9), QStringList() << "d");
1338     QCOMPARE(model->cache()->msgFlags("a", 102), QStringList() << "x102");
1339     QCOMPARE(model->cache()->msgFlags("a", 103), QStringList() << "x103");
1340     QCOMPARE(model->cache()->msgFlags("a", 104), QStringList() << "x104");
1341     QCOMPARE(model->cache()->msgFlags("a", 105), QStringList() << "x105");
1342     QCOMPARE(model->cache()->msgFlags("a", 109), QStringList() << "last");
1343     QCOMPARE(model->cache()->msgFlags("a", 2), QStringList());
1344     QCOMPARE(model->cache()->msgFlags("a", 3), QStringList());
1345     QCOMPARE(model->cache()->msgFlags("a", 10), QStringList());
1346     QCOMPARE(model->cache()->msgFlags("a", 100), QStringList());
1347     QCOMPARE(model->cache()->msgFlags("a", 101), QStringList());
1348     QCOMPARE(model->cache()->msgFlags("a", 106), QStringList());
1349     QCOMPARE(model->cache()->msgFlags("a", 107), QStringList());
1350     QCOMPARE(model->cache()->msgFlags("a", 108), QStringList());
1351     QCOMPARE(model->cache()->msgFlags("a", 110), QStringList());
1352     justKeepTask();
1353 }
1354 
1355 /** @short Mailbox is said to have lost some messages. While performing the sync, many events occur. */
1356 void ImapModelObtainSynchronizedMailboxTest::testCacheDeletionsThenDynamic()
1357 {
1358     Imap::Mailbox::SyncState sync;
1359     sync.setExists(10);
1360     sync.setUidValidity(666);
1361     sync.setUidNext(100);
1362     Imap::Uids uidMap;
1363     for (uint i = 1; i <= sync.exists(); ++i)
1364         uidMap << i;
1365     model->cache()->setMailboxSyncState(QStringLiteral("a"), sync);
1366     model->cache()->setUidMapping(QStringLiteral("a"), uidMap);
1367     QCOMPARE(model->rowCount(msgListA), 0);
1368     cClient(t.mk("SELECT a\r\n"));
1369     // The sync indicates that there are five more messages and nothing else
1370     cServer("* 5 EXISTS\r\n"
1371             "* OK [UIDVALIDITY 666] .\r\n"
1372             "* OK [UIDNEXT 100] .\r\n");
1373     cServer(t.last("OK selected\r\n"));
1374     // Let's say that the server's idea about the UIDs is now (1 3 4 6 9)
1375     cServer(
1376         // Five more messages arrives; that means that the UIDNEXT is now at least on 105
1377         "* 10 EXISTS\r\n"
1378         // The last of the previously known messages gets deleted
1379         "* 5 EXPUNGE\r\n"
1380         // Right now, the UIDs are (1 3 4 6 ? ? ? ? ?)
1381         // And the first of the new arrivals will be gone, too.
1382         "* 5 EXPUNGE\r\n"
1383         // That makes four new messages when compared to the original state.
1384         // Right now, the UIDs are (1 3 4 6 ? ? ? ?)
1385             );
1386     cClient(t.mk("UID SEARCH ALL\r\n"));
1387     // One of the old messages get deleted (UID 4)
1388     cServer("* 3 EXPUNGE\r\n");
1389     // Right now, the UIDs are (1 3 6 ? ? ? ?)
1390     cServer("* SEARCH 1 3 6 101 102 103 104\r\n");
1391     cServer(t.last("OK uids\r\n"));
1392 
1393     uidMap.clear();
1394     uidMap << 1 << 3 << 6 << 101 << 102 << 103 << 104;
1395     sync.setUidNext(105);
1396     sync.setExists(7);
1397     cClient(t.mk("FETCH 1:7 (FLAGS)\r\n"));
1398     cServer("* 2 EXPUNGE\r\n");
1399     uidMap.remove(1);
1400     sync.setExists(6);
1401     cServer("* 1 FETCH (FLAGS (f1))\r\n"
1402             "* 2 FETCH (FLAGS (f6))\r\n"
1403             "* 3 FETCH (FLAGS (f101))\r\n"
1404             "* 4 FETCH (FLAGS (f102))\r\n"
1405             "* 5 FETCH (FLAGS (f103))\r\n"
1406             "* 6 FETCH (FLAGS (f104))\r\n");
1407     // Add two, delete the last of them
1408     cServer("* 8 EXISTS\r\n* 8 EXPUNGE\r\n");
1409     sync.setExists(7);
1410     cServer(t.last("OK fetch\r\n"));
1411     cClient(t.mk("UID FETCH 105:* (FLAGS)\r\n"));
1412     cServer("* 7 FETCH (UID 109 FLAGS (last))\r\n");
1413     uidMap << 109;
1414     cServer(t.last("OK uid fetch flags done\r\n"));
1415     cEmpty();
1416     sync.setUidNext(110);
1417     sync.setUnSeenCount(7);
1418     sync.setRecent(0);
1419     QCOMPARE(model->cache()->mailboxSyncState("a"), sync);
1420     QCOMPARE(static_cast<int>(model->cache()->mailboxSyncState("a").exists()), uidMap.size());
1421     QCOMPARE(model->cache()->uidMapping("a"), uidMap);
1422     // UIDs: 1 6 101 102 103 104 109
1423     QCOMPARE(model->cache()->msgFlags("a", 1), QStringList() << "f1");
1424     QCOMPARE(model->cache()->msgFlags("a", 6), QStringList() << "f6");
1425     QCOMPARE(model->cache()->msgFlags("a", 101), QStringList() << "f101");
1426     QCOMPARE(model->cache()->msgFlags("a", 102), QStringList() << "f102");
1427     QCOMPARE(model->cache()->msgFlags("a", 103), QStringList() << "f103");
1428     QCOMPARE(model->cache()->msgFlags("a", 104), QStringList() << "f104");
1429     QCOMPARE(model->cache()->msgFlags("a", 109), QStringList() << "last");
1430     for (int i=2; i < 6; ++i)
1431         QCOMPARE(model->cache()->msgFlags("a", i), QStringList());
1432     for (int i=7; i < 101; ++i)
1433         QCOMPARE(model->cache()->msgFlags("a", i), QStringList());
1434     for (int i=105; i < 109; ++i)
1435         QCOMPARE(model->cache()->msgFlags("a", i), QStringList());
1436     for (int i=110; i < 120; ++i)
1437         QCOMPARE(model->cache()->msgFlags("a", i), QStringList());
1438     justKeepTask();
1439 }
1440 
1441 /** @short Test what happens when HIGHESTMODSEQ says there are no changes */
1442 void ImapModelObtainSynchronizedMailboxTest::testCondstoreNoChanges()
1443 {
1444     FakeCapabilitiesInjector injector(model);
1445     injector.injectCapability(QStringLiteral("CONDSTORE"));
1446     Imap::Mailbox::SyncState sync;
1447     sync.setExists(3);
1448     sync.setUidValidity(666);
1449     sync.setUidNext(15);
1450     sync.setHighestModSeq(33);
1451     sync.setUnSeenCount(3);
1452     sync.setRecent(0);
1453     Imap::Uids uidMap;
1454     uidMap << 6 << 9 << 10;
1455     model->cache()->setMailboxSyncState(QStringLiteral("a"), sync);
1456     model->cache()->setUidMapping(QStringLiteral("a"), uidMap);
1457     model->cache()->setMsgFlags(QStringLiteral("a"), 6, QStringList() << QStringLiteral("x"));
1458     model->cache()->setMsgFlags(QStringLiteral("a"), 9, QStringList() << QStringLiteral("y"));
1459     model->cache()->setMsgFlags(QStringLiteral("a"), 10, QStringList() << QStringLiteral("z"));
1460     model->resyncMailbox(idxA);
1461     cClient(t.mk("SELECT a (CONDSTORE)\r\n"));
1462     cServer("* 3 EXISTS\r\n"
1463             "* OK [UIDVALIDITY 666] .\r\n"
1464             "* OK [UIDNEXT 15] .\r\n"
1465             "* OK [HIGHESTMODSEQ 33] .\r\n"
1466             );
1467     cServer(t.last("OK selected\r\n"));
1468     cEmpty();
1469     QCOMPARE(model->cache()->mailboxSyncState("a"), sync);
1470     QCOMPARE(static_cast<int>(model->cache()->mailboxSyncState("a").exists()), uidMap.size());
1471     QCOMPARE(model->cache()->uidMapping("a"), uidMap);
1472     QCOMPARE(model->cache()->msgFlags("a", 6), QStringList() << "x");
1473     QCOMPARE(model->cache()->msgFlags("a", 9), QStringList() << "y");
1474     QCOMPARE(model->cache()->msgFlags("a", 10), QStringList() << "z");
1475     justKeepTask();
1476 }
1477 
1478 /** @short Test changed HIGHESTMODSEQ */
1479 void ImapModelObtainSynchronizedMailboxTest::testCondstoreChangedFlags()
1480 {
1481     FakeCapabilitiesInjector injector(model);
1482     injector.injectCapability(QStringLiteral("CONDSTORE"));
1483     Imap::Mailbox::SyncState sync;
1484     sync.setExists(3);
1485     sync.setUidValidity(666);
1486     sync.setUidNext(15);
1487     sync.setHighestModSeq(33);
1488     sync.setUnSeenCount(3);
1489     sync.setRecent(0);
1490     Imap::Uids uidMap;
1491     uidMap << 6 << 9 << 10;
1492     model->cache()->setMailboxSyncState(QStringLiteral("a"), sync);
1493     model->cache()->setUidMapping(QStringLiteral("a"), uidMap);
1494     model->cache()->setMsgFlags(QStringLiteral("a"), 6, QStringList() << QStringLiteral("x"));
1495     model->cache()->setMsgFlags(QStringLiteral("a"), 9, QStringList() << QStringLiteral("y"));
1496     model->cache()->setMsgFlags(QStringLiteral("a"), 10, QStringList() << QStringLiteral("z"));
1497     model->resyncMailbox(idxA);
1498     cClient(t.mk("SELECT a (CONDSTORE)\r\n"));
1499     cServer("* 3 EXISTS\r\n"
1500             "* OK [UIDVALIDITY 666] .\r\n"
1501             "* OK [UIDNEXT 15] .\r\n"
1502             "* OK [HIGHESTMODSEQ 666] .\r\n"
1503             );
1504     cServer(t.last("OK selected\r\n"));
1505     cClient(t.mk("FETCH 1:3 (FLAGS) (CHANGEDSINCE 33)\r\n"));
1506     cServer("* 3 FETCH (FLAGS (f101 \\seen))\r\n");
1507     cServer(t.last("OK fetched\r\n"));
1508     cEmpty();
1509     sync.setHighestModSeq(666);
1510     sync.setUnSeenCount(2);
1511     QCOMPARE(model->cache()->mailboxSyncState("a"), sync);
1512     QCOMPARE(static_cast<int>(model->cache()->mailboxSyncState("a").exists()), uidMap.size());
1513     QCOMPARE(model->cache()->uidMapping("a"), uidMap);
1514     QCOMPARE(model->cache()->msgFlags("a", 6), QStringList() << "x");
1515     QCOMPARE(model->cache()->msgFlags("a", 9), QStringList() << "y");
1516     QCOMPARE(model->cache()->msgFlags("a", 10), QStringList() << "\\Seen" << "f101");
1517     justKeepTask();
1518 }
1519 
1520 /** @short Test constant HIGHESTMODSEQ and more EXISTS */
1521 void ImapModelObtainSynchronizedMailboxTest::testCondstoreErrorExists()
1522 {
1523     FakeCapabilitiesInjector injector(model);
1524     injector.injectCapability(QStringLiteral("CONDSTORE"));
1525     Imap::Mailbox::SyncState sync;
1526     sync.setExists(3);
1527     sync.setUidValidity(666);
1528     sync.setUidNext(15);
1529     sync.setHighestModSeq(33);
1530     sync.setUnSeenCount(3);
1531     sync.setRecent(0);
1532     Imap::Uids uidMap;
1533     uidMap << 6 << 9 << 10;
1534     model->cache()->setMailboxSyncState(QStringLiteral("a"), sync);
1535     model->cache()->setUidMapping(QStringLiteral("a"), uidMap);
1536     model->cache()->setMsgFlags(QStringLiteral("a"), 6, QStringList() << QStringLiteral("x"));
1537     model->cache()->setMsgFlags(QStringLiteral("a"), 9, QStringList() << QStringLiteral("y"));
1538     model->cache()->setMsgFlags(QStringLiteral("a"), 10, QStringList() << QStringLiteral("z"));
1539     model->resyncMailbox(idxA);
1540     cClient(t.mk("SELECT a (CONDSTORE)\r\n"));
1541     cServer("* 4 EXISTS\r\n"
1542             "* OK [UIDVALIDITY 666] .\r\n"
1543             "* OK [UIDNEXT 15] .\r\n"
1544             "* OK [HIGHESTMODSEQ 33] .\r\n"
1545             );
1546     // yes, it's buggy. The goal here is to make sure that even an increased EXISTS is enough
1547     // to disable CHANGEDSINCE
1548     cServer(t.last("OK selected\r\n"));
1549     cClient(t.mk("UID SEARCH ALL\r\n"));
1550     cServer(QByteArray("* SEARCH 6 9 10 15\r\n") + t.last("OK uids\r\n"));
1551     cClient(t.mk("FETCH 1:4 (FLAGS)\r\n"));
1552     cServer("* 1 FETCH (FLAGS (x))\r\n"
1553             "* 2 FETCH (FLAGS (y))\r\n"
1554             "* 3 FETCH (FLAGS (z))\r\n"
1555             "* 4 FETCH (FLAGS (blah))\r\n");
1556     cServer(t.last("OK fetch\r\n"));
1557     cEmpty();
1558     uidMap << 15;
1559     sync.setUidNext(16);
1560     sync.setExists(4);
1561     sync.setUnSeenCount(4);
1562     QCOMPARE(model->cache()->mailboxSyncState("a"), sync);
1563     QCOMPARE(static_cast<int>(model->cache()->mailboxSyncState("a").exists()), uidMap.size());
1564     QCOMPARE(model->cache()->uidMapping("a"), uidMap);
1565     QCOMPARE(model->cache()->msgFlags("a", 6), QStringList() << "x");
1566     QCOMPARE(model->cache()->msgFlags("a", 9), QStringList() << "y");
1567     QCOMPARE(model->cache()->msgFlags("a", 10), QStringList() << "z");
1568     QCOMPARE(model->cache()->msgFlags("a", 15), QStringList() << "blah");
1569     justKeepTask();
1570 }
1571 
1572 /** @short Test constant HIGHESTMODSEQ but changed UIDNEXT */
1573 void ImapModelObtainSynchronizedMailboxTest::testCondstoreErrorUidNext()
1574 {
1575     FakeCapabilitiesInjector injector(model);
1576     injector.injectCapability(QStringLiteral("CONDSTORE"));
1577     Imap::Mailbox::SyncState sync;
1578     sync.setExists(3);
1579     sync.setUidValidity(666);
1580     sync.setUidNext(15);
1581     sync.setHighestModSeq(33);
1582     sync.setUnSeenCount(3);
1583     sync.setRecent(0);
1584     Imap::Uids uidMap;
1585     uidMap << 6 << 9 << 10;
1586     model->cache()->setMailboxSyncState(QStringLiteral("a"), sync);
1587     model->cache()->setUidMapping(QStringLiteral("a"), uidMap);
1588     model->cache()->setMsgFlags(QStringLiteral("a"), 6, QStringList() << QStringLiteral("x"));
1589     model->cache()->setMsgFlags(QStringLiteral("a"), 9, QStringList() << QStringLiteral("y"));
1590     model->cache()->setMsgFlags(QStringLiteral("a"), 10, QStringList() << QStringLiteral("z"));
1591     model->resyncMailbox(idxA);
1592     cClient(t.mk("SELECT a (CONDSTORE)\r\n"));
1593     cServer("* 3 EXISTS\r\n"
1594             "* OK [UIDVALIDITY 666] .\r\n"
1595             "* OK [UIDNEXT 16] .\r\n"
1596             "* OK [HIGHESTMODSEQ 33] .\r\n"
1597             );
1598     cServer(t.last("OK selected\r\n"));
1599     cClient(t.mk("UID SEARCH ALL\r\n"));
1600     cServer(QByteArray("* SEARCH 6 9 10\r\n") + t.last("OK uids\r\n"));
1601     cClient(t.mk("FETCH 1:3 (FLAGS)\r\n"));
1602     cServer("* 1 FETCH (FLAGS (x))\r\n"
1603             "* 2 FETCH (FLAGS (y))\r\n"
1604             "* 3 FETCH (FLAGS (z \\Recent))\r\n");
1605     cServer(t.last("OK fetch\r\n"));
1606     cEmpty();
1607     sync.setUidNext(16);
1608     sync.setRecent(1);
1609     QCOMPARE(model->cache()->mailboxSyncState("a"), sync);
1610     QCOMPARE(static_cast<int>(model->cache()->mailboxSyncState("a").exists()), uidMap.size());
1611     QCOMPARE(model->cache()->uidMapping("a"), uidMap);
1612     QCOMPARE(model->cache()->msgFlags("a", 6), QStringList() << "x");
1613     QCOMPARE(model->cache()->msgFlags("a", 9), QStringList() << "y");
1614     QCOMPARE(model->cache()->msgFlags("a", 10), QStringList() << "\\Recent" << "z");
1615     justKeepTask();
1616 }
1617 
1618 /** @short Changed UIDVALIDITY shall always lead to full sync, no matter what HIGHESTMODSEQ says */
1619 void ImapModelObtainSynchronizedMailboxTest::testCondstoreUidValidity()
1620 {
1621     FakeCapabilitiesInjector injector(model);
1622     injector.injectCapability(QStringLiteral("CONDSTORE"));
1623     Imap::Mailbox::SyncState sync;
1624     sync.setExists(3);
1625     sync.setUidValidity(666);
1626     sync.setUidNext(15);
1627     sync.setHighestModSeq(33);
1628     sync.setUnSeenCount(3);
1629     sync.setRecent(0);
1630     Imap::Uids uidMap;
1631     uidMap << 6 << 9 << 10;
1632     model->cache()->setMailboxSyncState(QStringLiteral("a"), sync);
1633     model->cache()->setUidMapping(QStringLiteral("a"), uidMap);
1634     model->cache()->setMsgFlags(QStringLiteral("a"), 6, QStringList() << QStringLiteral("x"));
1635     model->cache()->setMsgFlags(QStringLiteral("a"), 9, QStringList() << QStringLiteral("y"));
1636     model->cache()->setMsgFlags(QStringLiteral("a"), 10, QStringList() << QStringLiteral("z"));
1637     model->resyncMailbox(idxA);
1638     cClient(t.mk("SELECT a (CONDSTORE)\r\n"));
1639     cServer("* 3 EXISTS\r\n"
1640             "* OK [UIDVALIDITY 333] .\r\n"
1641             "* OK [UIDNEXT 15] .\r\n"
1642             "* OK [HIGHESTMODSEQ 33] .\r\n"
1643             );
1644     cServer(t.last("OK selected\r\n"));
1645     cClient(t.mk("UID SEARCH ALL\r\n"));
1646     cServer(QByteArray("* SEARCH 6 9 10\r\n") + t.last("OK uids\r\n"));
1647     cClient(t.mk("FETCH 1:3 (FLAGS)\r\n"));
1648     cServer("* 1 FETCH (FLAGS (x))\r\n"
1649             "* 2 FETCH (FLAGS (y))\r\n"
1650             "* 3 FETCH (FLAGS (z))\r\n");
1651     cServer(t.last("OK fetch\r\n"));
1652     cEmpty();
1653     sync.setUidValidity(333);
1654     QCOMPARE(model->cache()->mailboxSyncState("a"), sync);
1655     QCOMPARE(static_cast<int>(model->cache()->mailboxSyncState("a").exists()), uidMap.size());
1656     QCOMPARE(model->cache()->uidMapping("a"), uidMap);
1657     QCOMPARE(model->cache()->msgFlags("a", 6), QStringList() << "x");
1658     QCOMPARE(model->cache()->msgFlags("a", 9), QStringList() << "y");
1659     QCOMPARE(model->cache()->msgFlags("a", 10), QStringList() << "z");
1660     justKeepTask();
1661 }
1662 
1663 /** @short Test decreased HIGHESTMODSEQ */
1664 void ImapModelObtainSynchronizedMailboxTest::testCondstoreDecreasedHighestModSeq()
1665 {
1666     FakeCapabilitiesInjector injector(model);
1667     injector.injectCapability(QStringLiteral("CONDSTORE"));
1668     Imap::Mailbox::SyncState sync;
1669     sync.setExists(3);
1670     sync.setUidValidity(666);
1671     sync.setUidNext(15);
1672     sync.setHighestModSeq(33);
1673     sync.setUnSeenCount(3);
1674     sync.setRecent(0);
1675     Imap::Uids uidMap;
1676     uidMap << 6 << 9 << 10;
1677     model->cache()->setMailboxSyncState(QStringLiteral("a"), sync);
1678     model->cache()->setUidMapping(QStringLiteral("a"), uidMap);
1679     model->cache()->setMsgFlags(QStringLiteral("a"), 6, QStringList() << QStringLiteral("x"));
1680     model->cache()->setMsgFlags(QStringLiteral("a"), 9, QStringList() << QStringLiteral("y"));
1681     model->cache()->setMsgFlags(QStringLiteral("a"), 10, QStringList() << QStringLiteral("z"));
1682     model->resyncMailbox(idxA);
1683     cClient(t.mk("SELECT a (CONDSTORE)\r\n"));
1684     cServer("* 3 EXISTS\r\n"
1685             "* OK [UIDVALIDITY 666] .\r\n"
1686             "* OK [UIDNEXT 15] .\r\n"
1687             "* OK [HIGHESTMODSEQ 1] .\r\n"
1688             );
1689     cServer(t.last("OK selected\r\n"));
1690     cClient(t.mk("FETCH 1:3 (FLAGS)\r\n"));
1691     cServer("* 1 FETCH (FLAGS (x1))\r\n"
1692             "* 2 FETCH (FLAGS (x2))\r\n"
1693             "* 3 FETCH (FLAGS (x3))\r\n");
1694     cServer(t.last("OK fetched\r\n"));
1695     cEmpty();
1696     sync.setHighestModSeq(1);
1697     QCOMPARE(model->cache()->mailboxSyncState("a"), sync);
1698     QCOMPARE(static_cast<int>(model->cache()->mailboxSyncState("a").exists()), uidMap.size());
1699     QCOMPARE(model->cache()->uidMapping("a"), uidMap);
1700     QCOMPARE(model->cache()->msgFlags("a", 6), QStringList() << "x1");
1701     QCOMPARE(model->cache()->msgFlags("a", 9), QStringList() << "x2");
1702     QCOMPARE(model->cache()->msgFlags("a", 10), QStringList() << "x3");
1703     justKeepTask();
1704 }
1705 
1706 
1707 /** @short Test that we deal with discrepancy between the EXISTS and the number of UIDs in the cache
1708 and that FLAGS are completely re-fetched even in presence of the HIGHESTMODSEQ */
1709 void ImapModelObtainSynchronizedMailboxTest::testCacheDiscrepancyExistsUidsConstantHMS()
1710 {
1711     helperCacheDiscrepancyExistsUids(true);
1712 }
1713 
1714 /** @short Test that we deal with discrepancy between the EXISTS and the number of UIDs in the cache
1715 and that FLAGS are fetched even if the HIGHESTMODSEQ remains the same */
1716 void ImapModelObtainSynchronizedMailboxTest::testCacheDiscrepancyExistsUidsDifferentHMS()
1717 {
1718     helperCacheDiscrepancyExistsUids(false);
1719 }
1720 
1721 void ImapModelObtainSynchronizedMailboxTest::helperCacheDiscrepancyExistsUids(bool constantHighestModSeq)
1722 {
1723     FakeCapabilitiesInjector injector(model);
1724     injector.injectCapability(QStringLiteral("QRESYNC"));
1725 
1726     uidMapA << 5 << 6;
1727     existsA = 2;
1728     uidNextA = 10;
1729     uidValidityA = 123;
1730 
1731     helperSyncAWithMessagesEmptyState();
1732     helperCheckCache();
1733 
1734     helperSyncBNoMessages();
1735 
1736     injector.injectCapability(QStringLiteral("ESEARCH"));
1737     model->switchToMailbox(idxA);
1738 
1739     Imap::Mailbox::SyncState sync;
1740     sync.setExists(3);
1741     sync.setUidValidity(666);
1742     sync.setUidNext(10);
1743     sync.setHighestModSeq(111);
1744     Imap::Uids uidMap;
1745     uidMap << 5 << 6 << 7 << 8;
1746     model->cache()->setMailboxSyncState(QStringLiteral("a"), sync);
1747     model->cache()->setUidMapping(QStringLiteral("a"), uidMap);
1748     model->resyncMailbox(idxA);
1749     cClient(t.mk("SELECT a (QRESYNC (666 111 (3 7)))\r\n"));
1750     cServer("* OK [CLOSED] Previous mailbox closed\r\n"
1751             "* 3 EXISTS\r\n"
1752             "* 1 RECENT\r\n"
1753             "* OK [UNSEEN 5] x\r\n"
1754             "* OK [UIDVALIDITY 666] x\r\n"
1755             "* OK [UIDNEXT 10] x\r\n"
1756             "* OK [HIGHESTMODSEQ " + QByteArray::number(constantHighestModSeq ? 111 : 112) + "] x\r\n"
1757             "* VANISHED (EARLIER) 8\r\n" +
1758             t.last("OK selected\r\n"));
1759     cClient(t.mk("UID SEARCH RETURN (ALL) ALL\r\n"));
1760     cServer("* ESEARCH (TAG \"" + t.last() + "\") UID ALL 5:7\r\n");
1761     cServer(t.last("OK fetched\r\n"));
1762     // This test makes sure that the flags are synced after the UID mapping vs. EXISTS discrepancy is detected.
1763     // Otherwise we might get some nonsense like all message marked as read, etc.
1764     cClient(t.mk("FETCH 1:3 (FLAGS)\r\n"));
1765     cServer("* 1 FETCH (FLAGS (a))\r\n"
1766             "* 2 FETCH (FLAGS (\\Seen))\r\n"
1767             "* 3 FETCH (FLAGS (c))\r\n" +
1768             t.last("OK fetched\r\n"));
1769     QCOMPARE(idxA.data(Imap::Mailbox::RoleUnreadMessageCount).toInt(), 2);
1770     cEmpty();
1771     justKeepTask();
1772 }
1773 
1774 void ImapModelObtainSynchronizedMailboxTest::helperTestQresyncNoChanges(ModeForHelperTestQresyncNoChanges mode)
1775 {
1776     FakeCapabilitiesInjector injector(model);
1777     injector.injectCapability(QStringLiteral("QRESYNC"));
1778     Imap::Mailbox::SyncState sync;
1779     sync.setExists(3);
1780     sync.setUidValidity(666);
1781     sync.setUidNext(15);
1782     sync.setHighestModSeq(33);
1783     sync.setUnSeenCount(3);
1784     sync.setRecent(0);
1785     Imap::Uids uidMap;
1786     uidMap << 6 << 9 << 10;
1787     model->cache()->setMailboxSyncState(QStringLiteral("a"), sync);
1788     model->cache()->setUidMapping(QStringLiteral("a"), uidMap);
1789     model->cache()->setMsgFlags(QStringLiteral("a"), 6, QStringList() << QStringLiteral("x"));
1790     model->cache()->setMsgFlags(QStringLiteral("a"), 9, QStringList() << QStringLiteral("y"));
1791     model->cache()->setMsgFlags(QStringLiteral("a"), 10, QStringList() << QStringLiteral("z"));
1792     model->resyncMailbox(idxA);
1793     cClient(t.mk("SELECT a (QRESYNC (666 33 (2 9)))\r\n"));
1794     switch (mode) {
1795     case JUST_QRESYNC:
1796         // do nothing
1797         break;
1798     case EXTRA_ENABLED:
1799         cServer("* ENABLED CONDSTORE QRESYNC\r\n");
1800         break;
1801     case EXTRA_ENABLED_EMPTY:
1802         cServer("* ENABLED\r\n");
1803         break;
1804     }
1805     cServer("* 3 EXISTS\r\n"
1806             "* OK [UIDVALIDITY 666] .\r\n"
1807             "* OK [UIDNEXT 15] .\r\n"
1808             "* OK [HIGHESTMODSEQ 33] .\r\n"
1809             );
1810     cServer(t.last("OK selected\r\n"));
1811     cEmpty();
1812     QCOMPARE(model->cache()->mailboxSyncState("a"), sync);
1813     QCOMPARE(static_cast<int>(model->cache()->mailboxSyncState("a").exists()), uidMap.size());
1814     QCOMPARE(model->cache()->uidMapping("a"), uidMap);
1815     QCOMPARE(model->cache()->msgFlags("a", 6), QStringList() << "x");
1816     QCOMPARE(model->cache()->msgFlags("a", 9), QStringList() << "y");
1817     QCOMPARE(model->cache()->msgFlags("a", 10), QStringList() << "z");
1818     // deactivate envelope preloading
1819     LibMailboxSync::setModelNetworkPolicy(model, Imap::Mailbox::NETWORK_EXPENSIVE);
1820     requestAndCheckSubject(0, "subject 6");
1821     justKeepTask();
1822 }
1823 
1824 /** @short Test QRESYNC when there are no changes */
1825 void ImapModelObtainSynchronizedMailboxTest::testQresyncNoChanges()
1826 {
1827     helperTestQresyncNoChanges(JUST_QRESYNC);
1828 }
1829 
1830 /** @short Test QRESYNC reporting changed flags */
1831 void ImapModelObtainSynchronizedMailboxTest::testQresyncChangedFlags()
1832 {
1833     FakeCapabilitiesInjector injector(model);
1834     injector.injectCapability(QStringLiteral("QRESYNC"));
1835     Imap::Mailbox::SyncState sync;
1836     sync.setExists(3);
1837     sync.setUidValidity(666);
1838     sync.setUidNext(15);
1839     sync.setHighestModSeq(33);
1840     sync.setUnSeenCount(3);
1841     sync.setRecent(0);
1842     Imap::Uids uidMap;
1843     uidMap << 6 << 9 << 10;
1844     model->cache()->setMailboxSyncState(QStringLiteral("a"), sync);
1845     model->cache()->setUidMapping(QStringLiteral("a"), uidMap);
1846     model->cache()->setMsgFlags(QStringLiteral("a"), 6, QStringList() << QStringLiteral("x"));
1847     model->cache()->setMsgFlags(QStringLiteral("a"), 9, QStringList() << QStringLiteral("y"));
1848     model->cache()->setMsgFlags(QStringLiteral("a"), 10, QStringList() << QStringLiteral("z"));
1849     model->resyncMailbox(idxA);
1850     cClient(t.mk("SELECT a (QRESYNC (666 33 (2 9)))\r\n"));
1851     cServer("* 3 EXISTS\r\n"
1852             "* OK [UIDVALIDITY 666] .\r\n"
1853             "* OK [UIDNEXT 15] .\r\n"
1854             "* OK [HIGHESTMODSEQ 36] .\r\n"
1855             "* 2 FETCH (UID 9 FLAGS (x2 \\Seen))\r\n"
1856             );
1857     cServer(t.last("OK selected\r\n"));
1858     cEmpty();
1859     sync.setHighestModSeq(36);
1860     sync.setUnSeenCount(2);
1861     QCOMPARE(model->cache()->mailboxSyncState("a"), sync);
1862     QCOMPARE(static_cast<int>(model->cache()->mailboxSyncState("a").exists()), uidMap.size());
1863     QCOMPARE(model->cache()->uidMapping("a"), uidMap);
1864     QCOMPARE(model->cache()->msgFlags("a", 6), QStringList() << "x");
1865     QCOMPARE(model->cache()->msgFlags("a", 9), QStringList() << "\\Seen" << "x2");
1866     QCOMPARE(model->cache()->msgFlags("a", 10), QStringList() << "z");
1867 
1868     // make sure that we've picked up possible flag change about unread messages
1869     QCOMPARE(idxA.data(Imap::Mailbox::RoleUnreadMessageCount).toInt(), 2);
1870 
1871     // deactivate envelope preloading
1872     LibMailboxSync::setModelNetworkPolicy(model, Imap::Mailbox::NETWORK_EXPENSIVE);
1873     requestAndCheckSubject(0, "subject 6");
1874     justKeepTask();
1875 }
1876 
1877 /** @short Test QRESYNC using VANISHED EARLIER */
1878 void ImapModelObtainSynchronizedMailboxTest::testQresyncVanishedEarlier()
1879 {
1880     FakeCapabilitiesInjector injector(model);
1881     injector.injectCapability(QStringLiteral("QRESYNC"));
1882     Imap::Mailbox::SyncState sync;
1883     sync.setExists(3);
1884     sync.setUidValidity(666);
1885     sync.setUidNext(15);
1886     sync.setHighestModSeq(33);
1887     sync.setUnSeenCount(3);
1888     sync.setRecent(0);
1889     Imap::Uids uidMap;
1890     uidMap << 6 << 9 << 10;
1891     model->cache()->setMailboxSyncState(QStringLiteral("a"), sync);
1892     model->cache()->setUidMapping(QStringLiteral("a"), uidMap);
1893     model->cache()->setMsgFlags(QStringLiteral("a"), 6, QStringList() << QStringLiteral("x"));
1894     model->cache()->setMsgFlags(QStringLiteral("a"), 9, QStringList() << QStringLiteral("y"));
1895     model->cache()->setMsgFlags(QStringLiteral("a"), 10, QStringList() << QStringLiteral("z"));
1896     model->resyncMailbox(idxA);
1897     cClient(t.mk("SELECT a (QRESYNC (666 33 (2 9)))\r\n"));
1898     cServer("* 2 EXISTS\r\n"
1899             "* OK [UIDVALIDITY 666] .\r\n"
1900             "* OK [UIDNEXT 15] .\r\n"
1901             "* OK [HIGHESTMODSEQ 36] .\r\n"
1902             "* VANISHED (EARLIER) 1:5,9,11:13\r\n"
1903             );
1904     cServer(t.last("OK selected\r\n"));
1905     cEmpty();
1906     sync.setHighestModSeq(36);
1907     sync.setExists(2);
1908     sync.setUnSeenCount(2);
1909     uidMap.remove(uidMap.indexOf(9));
1910     QCOMPARE(model->cache()->mailboxSyncState("a"), sync);
1911     QCOMPARE(static_cast<int>(model->cache()->mailboxSyncState("a").exists()), uidMap.size());
1912     QCOMPARE(model->cache()->uidMapping("a"), uidMap);
1913     QCOMPARE(model->cache()->msgFlags("a", 6), QStringList() << "x");
1914     QCOMPARE(model->cache()->msgFlags("a", 9), QStringList());
1915     QCOMPARE(model->cache()->msgFlags("a", 10), QStringList() << "z");
1916     justKeepTask();
1917 }
1918 
1919 /** @short Test QRESYNC when UIDVALIDITY changes */
1920 void ImapModelObtainSynchronizedMailboxTest::testQresyncUidValidity()
1921 {
1922     FakeCapabilitiesInjector injector(model);
1923     injector.injectCapability(QStringLiteral("QRESYNC"));
1924     Imap::Mailbox::SyncState sync;
1925     sync.setExists(3);
1926     sync.setUidValidity(666);
1927     sync.setUidNext(15);
1928     sync.setHighestModSeq(33);
1929     sync.setUnSeenCount(3);
1930     sync.setRecent(0);
1931     Imap::Uids uidMap;
1932     uidMap << 6 << 9 << 10;
1933     model->cache()->setMailboxSyncState(QStringLiteral("a"), sync);
1934     model->cache()->setUidMapping(QStringLiteral("a"), uidMap);
1935     model->cache()->setMsgFlags(QStringLiteral("a"), 6, QStringList() << QStringLiteral("x"));
1936     model->cache()->setMsgFlags(QStringLiteral("a"), 9, QStringList() << QStringLiteral("y"));
1937     model->cache()->setMsgFlags(QStringLiteral("a"), 10, QStringList() << QStringLiteral("z"));
1938     model->resyncMailbox(idxA);
1939     cClient(t.mk("SELECT a (QRESYNC (666 33 (2 9)))\r\n"));
1940     cServer("* 3 EXISTS\r\n"
1941             "* OK [UIDVALIDITY 333] .\r\n"
1942             "* OK [UIDNEXT 15] .\r\n"
1943             "* OK [HIGHESTMODSEQ 33] .\r\n"
1944             );
1945     cServer(t.last("OK selected\r\n"));
1946     cClient(t.mk("UID SEARCH ALL\r\n"));
1947     cServer(QByteArray("* SEARCH 6 9 10\r\n") + t.last("OK uids\r\n"));
1948     cClient(t.mk("FETCH 1:3 (FLAGS)\r\n"));
1949     cServer("* 1 FETCH (FLAGS (x))\r\n"
1950             "* 2 FETCH (FLAGS (\\Seen))\r\n"
1951             "* 3 FETCH (FLAGS (z))\r\n");
1952     cServer(t.last("OK fetch\r\n"));
1953     cEmpty();
1954     sync.setUidValidity(333);
1955     sync.setUnSeenCount(2);
1956     QCOMPARE(model->cache()->mailboxSyncState("a"), sync);
1957     QCOMPARE(static_cast<int>(model->cache()->mailboxSyncState("a").exists()), uidMap.size());
1958     QCOMPARE(model->cache()->uidMapping("a"), uidMap);
1959     QCOMPARE(model->cache()->msgFlags("a", 6), QStringList() << "x");
1960     QCOMPARE(model->cache()->msgFlags("a", 9), QStringList() << "\\Seen");
1961     QCOMPARE(model->cache()->msgFlags("a", 10), QStringList() << "z");
1962     QCOMPARE(idxA.data(Imap::Mailbox::RoleUnreadMessageCount).toInt(), 2);
1963     justKeepTask();
1964 }
1965 
1966 /** @short Test QRESYNC reporting changed flags */
1967 void ImapModelObtainSynchronizedMailboxTest::testQresyncNoModseqChangedFlags()
1968 {
1969     FakeCapabilitiesInjector injector(model);
1970     injector.injectCapability(QStringLiteral("QRESYNC"));
1971     Imap::Mailbox::SyncState sync;
1972     sync.setExists(3);
1973     sync.setUidValidity(666);
1974     sync.setUidNext(15);
1975     sync.setHighestModSeq(33);
1976     sync.setUnSeenCount(3);
1977     sync.setRecent(0);
1978     Imap::Uids uidMap;
1979     uidMap << 6 << 9 << 10;
1980     model->cache()->setMailboxSyncState(QStringLiteral("a"), sync);
1981     model->cache()->setUidMapping(QStringLiteral("a"), uidMap);
1982     model->cache()->setMsgFlags(QStringLiteral("a"), 6, QStringList() << QStringLiteral("x"));
1983     model->cache()->setMsgFlags(QStringLiteral("a"), 9, QStringList() << QStringLiteral("y"));
1984     model->cache()->setMsgFlags(QStringLiteral("a"), 10, QStringList() << QStringLiteral("z"));
1985     model->resyncMailbox(idxA);
1986     cClient(t.mk("SELECT a (QRESYNC (666 33 (2 9)))\r\n"));
1987     cServer("* 3 EXISTS\r\n"
1988             "* OK [UIDVALIDITY 666] .\r\n"
1989             "* OK [UIDNEXT 15] .\r\n"
1990             "* OK [NOMODSEQ] .\r\n"
1991             );
1992     cServer(t.last("OK selected\r\n"));
1993     cClient(t.mk("FETCH 1:3 (FLAGS)\r\n"));
1994     cServer("* 1 FETCH (FLAGS (x1))\r\n"
1995             "* 2 FETCH (FLAGS (x2))\r\n"
1996             "* 3 FETCH (FLAGS (x3))\r\n"
1997             + t.last("OK flags\r\n"));
1998     cEmpty();
1999     sync.setHighestModSeq(0);
2000     QCOMPARE(model->cache()->mailboxSyncState("a"), sync);
2001     QCOMPARE(static_cast<int>(model->cache()->mailboxSyncState("a").exists()), uidMap.size());
2002     QCOMPARE(model->cache()->uidMapping("a"), uidMap);
2003     QCOMPARE(model->cache()->msgFlags("a", 6), QStringList() << "x1");
2004     QCOMPARE(model->cache()->msgFlags("a", 9), QStringList() << "x2");
2005     QCOMPARE(model->cache()->msgFlags("a", 10), QStringList() << "x3");
2006     justKeepTask();
2007 }
2008 
2009 /** @short Test that EXISTS change with unchanged HIGHESTMODSEQ cancels QRESYNC */
2010 void ImapModelObtainSynchronizedMailboxTest::testQresyncErrorExists()
2011 {
2012     FakeCapabilitiesInjector injector(model);
2013     injector.injectCapability(QStringLiteral("QRESYNC"));
2014     Imap::Mailbox::SyncState sync;
2015     sync.setExists(3);
2016     sync.setUidValidity(666);
2017     sync.setUidNext(15);
2018     sync.setHighestModSeq(33);
2019     sync.setUnSeenCount(3);
2020     sync.setRecent(0);
2021     Imap::Uids uidMap;
2022     uidMap << 6 << 9 << 10;
2023     model->cache()->setMailboxSyncState(QStringLiteral("a"), sync);
2024     model->cache()->setUidMapping(QStringLiteral("a"), uidMap);
2025     model->cache()->setMsgFlags(QStringLiteral("a"), 6, QStringList() << QStringLiteral("x"));
2026     model->cache()->setMsgFlags(QStringLiteral("a"), 9, QStringList() << QStringLiteral("y"));
2027     model->cache()->setMsgFlags(QStringLiteral("a"), 10, QStringList() << QStringLiteral("z"));
2028     model->resyncMailbox(idxA);
2029     cClient(t.mk("SELECT a (QRESYNC (666 33 (2 9)))\r\n"));
2030     cServer("* 4 EXISTS\r\n"
2031             "* OK [UIDVALIDITY 666] .\r\n"
2032             "* OK [UIDNEXT 15] .\r\n"
2033             "* OK [HIGHESTMODSEQ 33] .\r\n"
2034             );
2035     cServer(t.last("OK selected\r\n"));
2036     cClient(t.mk("UID SEARCH ALL\r\n"));
2037     cServer("* SEARCH 6 9 10 12\r\n" + t.last("OK search\r\n"));
2038     cClient(t.mk("FETCH 1:4 (FLAGS)\r\n"));
2039     cServer("* 1 FETCH (FLAGS (x1))\r\n"
2040             "* 2 FETCH (FLAGS (\\seen x2))\r\n"
2041             "* 3 FETCH (FLAGS (x3 \\seen))\r\n"
2042             "* 4 FETCH (FLAGS (x4))\r\n"
2043             + t.last("OK flags\r\n"));
2044     cEmpty();
2045     sync.setExists(4);
2046     sync.setUnSeenCount(2);
2047     sync.setHighestModSeq(0);
2048     uidMap << 12;
2049     QCOMPARE(model->cache()->mailboxSyncState("a"), sync);
2050     QCOMPARE(static_cast<int>(model->cache()->mailboxSyncState("a").exists()), uidMap.size());
2051     QCOMPARE(model->cache()->uidMapping("a"), uidMap);
2052     QCOMPARE(model->cache()->msgFlags("a", 6), QStringList() << "x1");
2053     QCOMPARE(model->cache()->msgFlags("a", 9), QStringList() << "\\Seen" << "x2");
2054     QCOMPARE(model->cache()->msgFlags("a", 10), QStringList() << "\\Seen" << "x3");
2055     QCOMPARE(model->cache()->msgFlags("a", 12), QStringList() << "x4");
2056     QCOMPARE(model->cache()->msgFlags("a", 15), QStringList());
2057     justKeepTask();
2058 }
2059 
2060 /** @short Test that UIDNEXT change with unchanged HIGHESTMODSEQ cancels QRESYNC */
2061 void ImapModelObtainSynchronizedMailboxTest::testQresyncErrorUidNext()
2062 {
2063     FakeCapabilitiesInjector injector(model);
2064     injector.injectCapability(QStringLiteral("QRESYNC"));
2065     Imap::Mailbox::SyncState sync;
2066     sync.setExists(3);
2067     sync.setUidValidity(666);
2068     sync.setUidNext(15);
2069     sync.setHighestModSeq(33);
2070     sync.setUnSeenCount(3);
2071     sync.setRecent(0);
2072     Imap::Uids uidMap;
2073     uidMap << 6 << 9 << 10;
2074     model->cache()->setMailboxSyncState(QStringLiteral("a"), sync);
2075     model->cache()->setUidMapping(QStringLiteral("a"), uidMap);
2076     model->cache()->setMsgFlags(QStringLiteral("a"), 6, QStringList() << QStringLiteral("x"));
2077     model->cache()->setMsgFlags(QStringLiteral("a"), 9, QStringList() << QStringLiteral("y"));
2078     model->cache()->setMsgFlags(QStringLiteral("a"), 10, QStringList() << QStringLiteral("z"));
2079     model->resyncMailbox(idxA);
2080     cClient(t.mk("SELECT a (QRESYNC (666 33 (2 9)))\r\n"));
2081     cServer("* 3 EXISTS\r\n"
2082             "* OK [UIDVALIDITY 666] .\r\n"
2083             "* OK [UIDNEXT 20] .\r\n"
2084             "* OK [HIGHESTMODSEQ 33] .\r\n"
2085             );
2086     cServer(t.last("OK selected\r\n"));
2087     cClient(t.mk("UID SEARCH ALL\r\n"));
2088     cServer("* SEARCH 6 9 10\r\n" + t.last("OK search\r\n"));
2089     cClient(t.mk("FETCH 1:3 (FLAGS)\r\n"));
2090     cServer("* 1 FETCH (FLAGS (x1))\r\n"
2091             "* 2 FETCH (FLAGS (x2))\r\n"
2092             "* 3 FETCH (FLAGS (x3))\r\n"
2093             + t.last("OK flags\r\n"));
2094     cEmpty();
2095     sync.setUidNext(20);
2096     sync.setHighestModSeq(0);
2097     QCOMPARE(model->cache()->mailboxSyncState("a"), sync);
2098     QCOMPARE(static_cast<int>(model->cache()->mailboxSyncState("a").exists()), uidMap.size());
2099     QCOMPARE(model->cache()->uidMapping("a"), uidMap);
2100     QCOMPARE(model->cache()->msgFlags("a", 6), QStringList() << "x1");
2101     QCOMPARE(model->cache()->msgFlags("a", 9), QStringList() << "x2");
2102     QCOMPARE(model->cache()->msgFlags("a", 10), QStringList() << "x3");
2103     justKeepTask();
2104 }
2105 
2106 /** @short Test QRESYNC when something has arrived and the server haven't told us about that */
2107 void ImapModelObtainSynchronizedMailboxTest::testQresyncUnreportedNewArrivals()
2108 {
2109     FakeCapabilitiesInjector injector(model);
2110     injector.injectCapability(QStringLiteral("QRESYNC"));
2111     Imap::Mailbox::SyncState sync;
2112     sync.setExists(3);
2113     sync.setUidValidity(666);
2114     sync.setUidNext(15);
2115     sync.setHighestModSeq(33);
2116     sync.setUnSeenCount(3);
2117     sync.setRecent(0);
2118     Imap::Uids uidMap;
2119     uidMap << 6 << 9 << 10;
2120     model->cache()->setMailboxSyncState(QStringLiteral("a"), sync);
2121     model->cache()->setUidMapping(QStringLiteral("a"), uidMap);
2122     model->cache()->setMsgFlags(QStringLiteral("a"), 6, QStringList() << QStringLiteral("x"));
2123     model->cache()->setMsgFlags(QStringLiteral("a"), 9, QStringList() << QStringLiteral("y"));
2124     model->cache()->setMsgFlags(QStringLiteral("a"), 10, QStringList() << QStringLiteral("z"));
2125     model->resyncMailbox(idxA);
2126     cClient(t.mk("SELECT a (QRESYNC (666 33 (2 9)))\r\n"));
2127     cServer("* 4 EXISTS\r\n"
2128             "* OK [UIDVALIDITY 666] .\r\n"
2129             "* OK [UIDNEXT 20] .\r\n"
2130             "* OK [HIGHESTMODSEQ 34] .\r\n"
2131             );
2132     QCOMPARE(model->rowCount(msgListA), 4);
2133     QCOMPARE(msgListA.model()->index(3, 0, msgListA).data(Imap::Mailbox::RoleMessageUid).toUInt(), 0u);
2134     cServer(t.last("OK selected\r\n"));
2135     cClient(t.mk("UID FETCH 15:* (FLAGS)\r\n"));
2136     cServer("* 4 FETCH (FLAGS (x4 \\seen) UID 16)\r\n" + t.last("OK uid fetch flags\r\n"));
2137     cEmpty();
2138     sync.setExists(4);
2139     sync.setUidNext(20);
2140     uidMap << 16;
2141     sync.setHighestModSeq(34);
2142     QCOMPARE(model->cache()->mailboxSyncState("a"), sync);
2143     QCOMPARE(static_cast<int>(model->cache()->mailboxSyncState("a").exists()), uidMap.size());
2144     QCOMPARE(model->cache()->uidMapping("a"), uidMap);
2145     QCOMPARE(model->cache()->msgFlags("a", 6), QStringList() << "x");
2146     QCOMPARE(model->cache()->msgFlags("a", 9), QStringList() << "y");
2147     QCOMPARE(model->cache()->msgFlags("a", 10), QStringList() << "z");
2148     QCOMPARE(model->cache()->msgFlags("a", 16), QStringList() << "\\Seen" << "x4");
2149     justKeepTask();
2150 }
2151 
2152 /** @short Test QRESYNC when something has arrived and the server reported UID of that stuff on its own */
2153 void ImapModelObtainSynchronizedMailboxTest::testQresyncReportedNewArrivals()
2154 {
2155     FakeCapabilitiesInjector injector(model);
2156     injector.injectCapability(QStringLiteral("QRESYNC"));
2157     Imap::Mailbox::SyncState sync;
2158     sync.setExists(3);
2159     sync.setUidValidity(666);
2160     sync.setUidNext(15);
2161     sync.setHighestModSeq(33);
2162     sync.setUnSeenCount(3);
2163     sync.setRecent(0);
2164     Imap::Uids uidMap;
2165     uidMap << 6 << 9 << 10;
2166     model->cache()->setMailboxSyncState(QStringLiteral("a"), sync);
2167     model->cache()->setUidMapping(QStringLiteral("a"), uidMap);
2168     model->cache()->setMsgFlags(QStringLiteral("a"), 6, QStringList() << QStringLiteral("x"));
2169     model->cache()->setMsgFlags(QStringLiteral("a"), 9, QStringList() << QStringLiteral("y"));
2170     model->cache()->setMsgFlags(QStringLiteral("a"), 10, QStringList() << QStringLiteral("z"));
2171     model->resyncMailbox(idxA);
2172     cClient(t.mk("SELECT a (QRESYNC (666 33 (2 9)))\r\n"));
2173     cServer("* 4 EXISTS\r\n"
2174             "* OK [UIDVALIDITY 666] .\r\n"
2175             "* OK [UIDNEXT 20] .\r\n"
2176             "* OK [HIGHESTMODSEQ 34] .\r\n"
2177             "* 4 FETCH (FLAGS (x4) UID 16)\r\n"
2178             );
2179     QCOMPARE(model->rowCount(msgListA), 4);
2180     QCOMPARE(msgListA.model()->index(3, 0, msgListA).data(Imap::Mailbox::RoleMessageUid).toUInt(), 16u);
2181     cServer(t.last("OK selected\r\n"));
2182     cEmpty();
2183     sync.setExists(4);
2184     sync.setUnSeenCount(4);
2185     sync.setUidNext(20);
2186     uidMap << 16;
2187     sync.setHighestModSeq(34);
2188     QCOMPARE(model->cache()->mailboxSyncState("a"), sync);
2189     QCOMPARE(static_cast<int>(model->cache()->mailboxSyncState("a").exists()), uidMap.size());
2190     QCOMPARE(model->cache()->uidMapping("a"), uidMap);
2191     QCOMPARE(model->cache()->msgFlags("a", 6), QStringList() << "x");
2192     QCOMPARE(model->cache()->msgFlags("a", 9), QStringList() << "y");
2193     QCOMPARE(model->cache()->msgFlags("a", 10), QStringList() << "z");
2194     // deactivate envelope preloading
2195     LibMailboxSync::setModelNetworkPolicy(model, Imap::Mailbox::NETWORK_EXPENSIVE);
2196     requestAndCheckSubject(0, "subject 6");
2197     justKeepTask();
2198 }
2199 
2200 /** @short QRESYNC where the number of messaged actually removed by the VANISHED EARLIER is equal to number of new arrivals */
2201 void ImapModelObtainSynchronizedMailboxTest::testQresyncDeletionsNewArrivals()
2202 {
2203     FakeCapabilitiesInjector injector(model);
2204     injector.injectCapability(QStringLiteral("QRESYNC"));
2205     Imap::Mailbox::SyncState sync;
2206     sync.setExists(5);
2207     sync.setUidValidity(666);
2208     sync.setUidNext(6);
2209     sync.setHighestModSeq(10);
2210     sync.setUnSeenCount(5);
2211     sync.setRecent(0);
2212     Imap::Uids uidMap;
2213     uidMap << 1 << 2 << 3 << 4 << 5;
2214     model->cache()->setMailboxSyncState(QStringLiteral("a"), sync);
2215     model->cache()->setUidMapping(QStringLiteral("a"), uidMap);
2216     model->cache()->setMsgFlags(QStringLiteral("a"), 1, QStringList() << QStringLiteral("1"));
2217     model->cache()->setMsgFlags(QStringLiteral("a"), 2, QStringList() << QStringLiteral("2"));
2218     model->cache()->setMsgFlags(QStringLiteral("a"), 3, QStringList() << QStringLiteral("3"));
2219     model->cache()->setMsgFlags(QStringLiteral("a"), 4, QStringList() << QStringLiteral("4"));
2220     model->cache()->setMsgFlags(QStringLiteral("a"), 5, QStringList() << QStringLiteral("5"));
2221     model->resyncMailbox(idxA);
2222     cClient(t.mk("SELECT a (QRESYNC (666 10 (3,5 3,5)))\r\n"));
2223     cServer("* 5 EXISTS\r\n"
2224             "* OK [UIDVALIDITY 666] .\r\n"
2225             "* OK [UIDNEXT 10] .\r\n"
2226             "* OK [HIGHESTMODSEQ 34] .\r\n"
2227             "* VANISHED (EARLIER) 1:3\r\n"
2228             "* 3 FETCH (UID 6 FLAGS (6))\r\n"
2229             "* 4 FETCH (UID 7 FLAGS (7))\r\n"
2230             "* 5 FETCH (UID 8 FLAGS (8))\r\n"
2231             );
2232     uidMap.remove(uidMap.indexOf(1));
2233     uidMap.remove(uidMap.indexOf(2));
2234     uidMap.remove(uidMap.indexOf(3));
2235     uidMap << 6 << 7 << 8;
2236     QCOMPARE(model->rowCount(msgListA), 5);
2237     cServer(t.last("OK selected\r\n"));
2238     cEmpty();
2239     sync.setUidNext(10);
2240     sync.setHighestModSeq(34);
2241     QCOMPARE(model->cache()->mailboxSyncState("a"), sync);
2242     QCOMPARE(static_cast<int>(model->cache()->mailboxSyncState("a").exists()), uidMap.size());
2243     QCOMPARE(model->cache()->uidMapping("a"), uidMap);
2244     // these were removed
2245     QCOMPARE(model->cache()->msgFlags("a", 1), QStringList());
2246     QCOMPARE(model->cache()->msgFlags("a", 2), QStringList());
2247     QCOMPARE(model->cache()->msgFlags("a", 3), QStringList());
2248     // these are still present
2249     QCOMPARE(model->cache()->msgFlags("a", 4), QStringList() << "4");
2250     QCOMPARE(model->cache()->msgFlags("a", 5), QStringList() << "5");
2251     // and these are the new arrivals
2252     QCOMPARE(model->cache()->msgFlags("a", 6), QStringList() << "6");
2253     QCOMPARE(model->cache()->msgFlags("a", 7), QStringList() << "7");
2254     QCOMPARE(model->cache()->msgFlags("a", 8), QStringList() << "8");
2255     justKeepTask();
2256 }
2257 
2258 /** @short Test that extra SEARCH responses don't cause asserts */
2259 void ImapModelObtainSynchronizedMailboxTest::testSpuriousSearch()
2260 {
2261     QCOMPARE(model->rowCount(msgListA), 0);
2262     cClient(t.mk("SELECT a\r\n"));
2263     cServer(QByteArray("* 0 exists\r\n"));
2264 
2265     {
2266         ExpectSingleErrorHere blocker(this);
2267         cServer("* SEARCH \r\n");
2268     }
2269 }
2270 
2271 /** @short Test how unexpected ESEARCH operates */
2272 void ImapModelObtainSynchronizedMailboxTest::testSpuriousESearch()
2273 {
2274     QCOMPARE(model->rowCount(msgListA), 0);
2275     cClient(t.mk("SELECT a\r\n"));
2276     cServer(QByteArray("* 0 exists\r\n"));
2277 
2278     {
2279         ExpectSingleErrorHere blocker(this);
2280         cServer("* ESEARCH (TAG \"\") UID \r\n");
2281     }
2282 }
2283 
2284 /** @short Mailbox synchronization without the UIDNEXT -- this is what Courier 4.5.0 is happy to return */
2285 void ImapModelObtainSynchronizedMailboxTest::testSyncNoUidnext()
2286 {
2287     QCOMPARE(model->rowCount(msgListA), 0);
2288     cClient(t.mk("SELECT a\r\n"));
2289     cServer("* 3 EXISTS\r\n"
2290             "* 0 RECENT\r\n"
2291             "* OK [UIDVALIDITY 1336643053] Ok\r\n"
2292             "* OK [MYRIGHTS \"acdilrsw\"] ACL\r\n"
2293             + t.last("OK [READ-WRITE] Ok\r\n"));
2294     QCOMPARE(model->rowCount(msgListA), 3);
2295     cClient(t.mk("UID SEARCH ALL\r\n"));
2296     cServer("* SEARCH 1212 1214 1215\r\n");
2297     cServer(t.last("OK search\r\n"));
2298     cClient(t.mk("FETCH 1:3 (FLAGS)\r\n"));
2299     cServer("* 1 FETCH (FLAGS (uid1212))\r\n"
2300             "* 2 FETCH (FLAGS (uid1214 \\seen))\r\n"
2301             "* 3 FETCH (FLAGS (uid1215))\r\n"
2302             + t.last("OK fetch\r\n"));
2303     cEmpty();
2304     justKeepTask();
2305 
2306     // Verify the cache
2307     Imap::Mailbox::SyncState sync;
2308     sync.setExists(3);
2309     sync.setUidValidity(1336643053);
2310     sync.setRecent(0);
2311     // The UIDNEXT shall be updated automatically
2312     sync.setUidNext(1216);
2313     // unseen count is computed, too
2314     sync.setUnSeenCount(2);
2315     Imap::Uids uidMap;
2316     uidMap << 1212 << 1214 << 1215;
2317 
2318     QCOMPARE(model->cache()->mailboxSyncState("a"), sync);
2319     QCOMPARE(static_cast<int>(model->cache()->mailboxSyncState("a").exists()), uidMap.size());
2320     QCOMPARE(model->cache()->uidMapping("a"), uidMap);
2321     QCOMPARE(model->cache()->msgFlags("a", 1212), QStringList() << "uid1212");
2322     QCOMPARE(model->cache()->msgFlags("a", 1214), QStringList() << "\\Seen" << "uid1214");
2323     QCOMPARE(model->cache()->msgFlags("a", 1215), QStringList() << "uid1215");
2324 
2325     // Switch away from this mailbox
2326     helperSyncBNoMessages();
2327 
2328     // Make sure that we catch UIDNEXT missing by purging the cache
2329     model->cache()->setMsgPart(QStringLiteral("a"), 1212, QByteArray(), "foo");
2330 
2331     // Now go back to mailbox A
2332     model->resyncMailbox(idxA);
2333     cClient(t.mk("SELECT a\r\n"));
2334     cServer("* 3 EXISTS\r\n"
2335             "* 0 RECENT\r\n"
2336             "* OK [UIDVALIDITY 1336643053] .\r\n"
2337             "* OK [MYRIGHTS \"acdilrsw\"] ACL\r\n"
2338             );
2339     QCOMPARE(model->rowCount(msgListA), 3);
2340     cServer(t.last("OK selected\r\n"));
2341     // At this point, there's no need for nuking the cache (yet).
2342     QCOMPARE(model->cache()->messagePart("a", 1212, QByteArray()), QByteArray("foo"));
2343     // The UIDNEXT is missing -> resyncing the UIDs again
2344     cClient(t.mk("UID SEARCH ALL\r\n"));
2345     cServer("* SEARCH 1212 1214 1215\r\n");
2346     cServer(t.last("OK search\r\n"));
2347     cClient(t.mk("FETCH 1:3 (FLAGS)\r\n"));
2348     cServer("* 1 FETCH (FLAGS (uid1212))\r\n"
2349             "* 2 FETCH (FLAGS (\\sEEN uid1214))\r\n"
2350             "* 3 FETCH (FLAGS (uid1215))\r\n"
2351             + t.last("OK fetch\r\n"));
2352     cEmpty();
2353     justKeepTask();
2354 
2355     QCOMPARE(model->cache()->mailboxSyncState("a"), sync);
2356     QCOMPARE(static_cast<int>(model->cache()->mailboxSyncState("a").exists()), uidMap.size());
2357     QCOMPARE(model->cache()->uidMapping("a"), uidMap);
2358 
2359     // While a missing UIDNEXT is an evil thing to do, the actual language in the RFC leaves some unfortunate
2360     // leeway in its interpretation. As much as I would love to throw away everything, Courier's users would
2361     // not be thrilled by that, so the UIDNEXT actually has to be preserved in this context.
2362     //
2363     // FIXME: It would be interesting to perturb the UIDs and detect *that*. However, it's outside of scope for now.
2364 }
2365 
2366 /** @short Test that we can open a mailbox using just the cached data when offline */
2367 void ImapModelObtainSynchronizedMailboxTest::testOfflineOpening()
2368 {
2369     LibMailboxSync::setModelNetworkPolicy(model, Imap::Mailbox::NETWORK_OFFLINE);
2370     cClient(t.mk("LOGOUT\r\n"));
2371     cServer(t.last("OK logged out\r\n"));
2372 
2373     // prepare the cache
2374     Imap::Mailbox::SyncState sync;
2375     sync.setExists(3);
2376     sync.setUidValidity(333);
2377     sync.setRecent(0);
2378     sync.setUidNext(666);
2379     Imap::Uids uidMap;
2380     uidMap << 10 << 20 << 30;
2381     model->cache()->setMailboxSyncState(QStringLiteral("a"), sync);
2382     model->cache()->setUidMapping(QStringLiteral("a"), uidMap);
2383     Imap::Mailbox::AbstractCache::MessageDataBundle msg10, msg20;
2384     msg10.uid = 10;
2385     msg10.envelope.subject = QLatin1String("msg10");
2386     msg20.uid = 20;
2387     msg20.envelope.subject = QLatin1String("msg20");
2388 
2389     // Prepare the body structure for this message
2390     int start = 0;
2391     Imap::Responses::Fetch fetchResponse(666, QByteArray(" (BODYSTRUCTURE (\"text\" \"plain\" (\"chaRset\" \"UTF-8\" "
2392                                                          "\"format\" \"flowed\") NIL NIL \"8bit\" 362 15 NIL NIL NIL))\r\n"),
2393                                          start);
2394     msg10.serializedBodyStructure = dynamic_cast<const Imap::Responses::RespData<QByteArray>&>(*(fetchResponse.data["x-trojita-bodystructure"])).data;
2395     msg20.serializedBodyStructure = msg10.serializedBodyStructure;
2396 
2397     model->cache()->setMessageMetadata(QStringLiteral("a"), 10, msg10);
2398     model->cache()->setMessageMetadata(QStringLiteral("a"), 20, msg20);
2399 
2400     // Check that stuff works
2401     QCOMPARE(model->rowCount(msgListA), 0);
2402     QCoreApplication::processEvents();
2403     QCOMPARE(model->rowCount(msgListA), 3);
2404     checkCachedSubject(0, "msg10");
2405     checkCachedSubject(1, "msg20");
2406     checkCachedSubject(2, "");
2407     QCOMPARE(msgListA.model()->index(2, 0, msgListA).data(Imap::Mailbox::RoleIsFetched).toBool(), false);
2408 
2409     QCOMPARE(model->taskModel()->rowCount(), 0);
2410 
2411     QCOMPARE(model->cache()->mailboxSyncState("a"), sync);
2412     QCOMPARE(static_cast<int>(model->cache()->mailboxSyncState("a").exists()), uidMap.size());
2413     QCOMPARE(model->cache()->uidMapping("a"), uidMap);
2414 }
2415 
2416 /** @short Check that ENABLE QRESYNC always gets sent prior to SELECT QRESYNC
2417 
2418 See Redmine #611 for details.
2419 */
2420 void ImapModelObtainSynchronizedMailboxTest::testQresyncEnabling()
2421 {
2422     using namespace Imap::Mailbox;
2423 
2424     LibMailboxSync::setModelNetworkPolicy(model, Imap::Mailbox::NETWORK_OFFLINE);
2425     cClient(t.mk("LOGOUT\r\n"));
2426     cServer(t.last("OK logged out\r\n"));
2427 
2428     // this one is important, otherwise the index would get invalidated "too fast"
2429     taskFactoryUnsafe->fakeListChildMailboxes = false;
2430     // we need to tap into the whole connection establishing process; otherwise KeepMailboxOpenTask asserts on line 80
2431     taskFactoryUnsafe->fakeOpenConnectionTask = false;
2432     factory->setInitialState(Imap::CONN_STATE_CONNECTED_PRETLS_PRECAPS);
2433     t.reset();
2434 
2435     LibMailboxSync::setModelNetworkPolicy(model, Imap::Mailbox::NETWORK_ONLINE);
2436     QCoreApplication::processEvents();
2437     cServer("* OK [CAPABILITY IMAP4rev1] hi there\r\n");
2438     QCOMPARE(model->rowCount(QModelIndex()), 26);
2439     idxA = model->index(1, 0, QModelIndex());
2440     QVERIFY(idxA.isValid());
2441     QCOMPARE(idxA.data(RoleMailboxName).toString(), QString("a"));
2442     msgListA = idxA.model()->index(0, 0, idxA);
2443     QVERIFY(idxA.isValid());
2444     QCOMPARE(model->rowCount(msgListA), 0);
2445     cEmpty();
2446     model->setImapUser(QStringLiteral("user"));
2447     model->setImapPassword(QStringLiteral("pw"));
2448     cClient(t.mk("LOGIN user pw\r\n"));
2449     cServer(t.last("OK [CAPABILITY IMAP4rev1 ENABLE QRESYNC UNSELECT] logged in\r\n"));
2450 
2451     QByteArray idCmd = t.mk("ENABLE QRESYNC\r\n");
2452     QByteArray idRes = t.last("OK enabled\r\n");
2453     QByteArray listCmd = t.mk("LIST \"\" \"%\"\r\n");
2454     QByteArray listResp = t.last("OK listed\r\n");
2455     QByteArray selectCmd = t.mk("SELECT a\r\n");
2456     QByteArray selectResp = t.last("OK selected\r\n");
2457 
2458     cClient(idCmd + listCmd + selectCmd);
2459     cServer(idRes + "* LIST (\\HasNoChildren) \".\" \"a\"\r\n" + listResp);
2460     cServer(selectResp);
2461     cClient(t.mk("UNSELECT\r\n"));
2462     cServer(t.last("OK whatever\r\n"));
2463     cEmpty();
2464 }
2465 
2466 /** @short VANISHED EARLIER which refers to meanwhile-arrived-and-deleted messages on an empty mailbox */
2467 void ImapModelObtainSynchronizedMailboxTest::testQresyncSpuriousVanishedEarlier()
2468 {
2469     FakeCapabilitiesInjector injector(model);
2470     injector.injectCapability(QStringLiteral("ESEARCH"));
2471     injector.injectCapability(QStringLiteral("QRESYNC"));
2472     Imap::Mailbox::SyncState sync;
2473     sync.setExists(0);
2474     sync.setUidValidity(1309542826);
2475     sync.setUidNext(252);
2476     sync.setHighestModSeq(10);
2477     // and just for fun: introduce garbage to the cache, muhehe
2478     sync.setUnSeenCount(10);
2479     sync.setRecent(10);
2480     Imap::Uids uidMap;
2481     model->cache()->setMailboxSyncState(QStringLiteral("a"), sync);
2482     model->cache()->setUidMapping(QStringLiteral("a"), uidMap);
2483     model->resyncMailbox(idxA);
2484     cClient(t.mk("SELECT a (QRESYNC (1309542826 10))\r\n"));
2485     cServer("* 0 EXISTS\r\n"
2486             "* OK [UIDVALIDITY 1309542826] UIDs valid\r\n"
2487             "* OK [UIDNEXT 256] Predicted next UID\r\n"
2488             "* OK [HIGHESTMODSEQ 22] Highest\r\n"
2489             "* VANISHED (EARLIER) 252:255\r\n"
2490             );
2491     QCOMPARE(model->rowCount(msgListA), 0);
2492     cServer(t.last("OK selected\r\n"));
2493     cEmpty();
2494     sync.setUidNext(256);
2495     sync.setHighestModSeq(22);
2496     sync.setUnSeenCount(0);
2497     sync.setRecent(0);
2498     QCOMPARE(model->cache()->mailboxSyncState("a"), sync);
2499     QCOMPARE(static_cast<int>(model->cache()->mailboxSyncState("a").exists()), 0);
2500     QCOMPARE(model->cache()->uidMapping("a"), uidMap);
2501     justKeepTask();
2502 }
2503 
2504 /** @short QRESYNC synchronization of a formerly empty mailbox which now contains some messages
2505 
2506 This was reported by paalsteek as being broken.
2507 */
2508 void ImapModelObtainSynchronizedMailboxTest::testQresyncAfterEmpty()
2509 {
2510     FakeCapabilitiesInjector injector(model);
2511     injector.injectCapability(QStringLiteral("ESEARCH"));
2512     injector.injectCapability(QStringLiteral("QRESYNC"));
2513     Imap::Mailbox::SyncState sync;
2514     sync.setUidValidity(1336686200);
2515     sync.setExists(0);
2516     sync.setUidNext(1);
2517     sync.setHighestModSeq(1);
2518     model->cache()->setMailboxSyncState(QStringLiteral("a"), sync);
2519     model->cache()->setUidMapping(QStringLiteral("a"), Imap::Uids());
2520     model->resyncMailbox(idxA);
2521     cClient(t.mk("SELECT a (QRESYNC (1336686200 1))\r\n"));
2522     cServer("* 6 EXISTS\r\n"
2523             "* OK [UIDVALIDITY 1336686200] UIDs valid\r\n"
2524             "* OK [UIDNEXT 7] Predicted next UID\r\n"
2525             "* OK [HIGHESTMODSEQ 3] Highest\r\n"
2526             "* 1 FETCH (MODSEQ (2) UID 1 FLAGS (\\Seen \\Recent))\r\n"
2527             "* 2 FETCH (MODSEQ (2) UID 2 FLAGS (\\Seen \\Recent))\r\n"
2528             "* 3 FETCH (MODSEQ (2) UID 3 FLAGS (\\Seen \\Recent))\r\n"
2529             "* 4 FETCH (MODSEQ (2) UID 4 FLAGS (\\Seen \\Recent))\r\n"
2530             "* 5 FETCH (MODSEQ (2) UID 5 FLAGS (\\Seen \\Recent))\r\n"
2531             "* 6 FETCH (MODSEQ (2) UID 6 FLAGS (\\Seen \\Recent))\r\n"
2532             + t.last("OK [READ-WRITE] Select completed.\r\n")
2533             );
2534     existsA = 6;
2535     uidNextA = 7;
2536     uidValidityA = 1336686200;
2537     for (uint i = 1; i <= existsA; ++i)
2538         uidMapA << i;
2539     helperCheckCache();
2540     cEmpty();
2541     justKeepTask();
2542 }
2543 
2544 /** @short Test QRESYNC/CONDSTORE initial sync on Devocot which reports NOMODSEQ followed by HIGHESTMODSEQ 1
2545 
2546 This is apparently a real-world issue.
2547 */
2548 void ImapModelObtainSynchronizedMailboxTest::testCondstoreQresyncNomodseqHighestmodseq()
2549 {
2550     FakeCapabilitiesInjector injector(model);
2551     injector.injectCapability(QStringLiteral("ESEARCH"));
2552     injector.injectCapability(QStringLiteral("CONDSTORE"));
2553     injector.injectCapability(QStringLiteral("QRESYNC"));
2554     model->resyncMailbox(idxA);
2555     cClient(t.mk("SELECT a (CONDSTORE)\r\n"));
2556     cServer("* FLAGS (\\Answered \\Flagged \\Deleted \\Seen \\Draft Junk NonJunk $Forwarded)\r\n"
2557             "* OK [PERMANENTFLAGS (\\Answered \\Flagged \\Deleted \\Seen \\Draft Junk NonJunk $Forwarded \\*)] Flags permitted.\r\n"
2558             "* 3 EXISTS\r\n"
2559             "* 0 RECENT\r\n"
2560             "* OK [UIDVALIDITY 666] .\r\n"
2561             "* OK [UIDNEXT 15] .\r\n"
2562             "* OK [NOMODSEQ] .\r\n"
2563             );
2564     cServer(t.last("OK [READ-WRITE] selected\r\n"));
2565     cClient(t.mk("UID SEARCH RETURN (ALL) ALL\r\n"));
2566     cServer("* ESEARCH (TAG \"" + t.last() + "\") UID ALL 1:3\r\n");
2567     cServer(t.last("OK [HIGHESTMODSEQ 1] Searched\r\n"));
2568     cClient(t.mk("FETCH 1:3 (FLAGS)\r\n"));
2569     cServer("* 1 FETCH (MODSEQ (1) UID 1 FLAGS (\\Seen))\r\n"
2570             "* 2 FETCH (MODSEQ (1) UID 2 FLAGS ())\r\n"
2571             "* 3 FETCH (MODSEQ (1) UID 3 FLAGS (\\Answered))\r\n"
2572             + t.last("OK flags fetched\r\n"));
2573 
2574     existsA = 3;
2575     uidNextA = 15;
2576     uidValidityA = 666;
2577     Imap::Mailbox::SyncState state;
2578     state.setExists(existsA);
2579     state.setUidNext(uidNextA);
2580     state.setUidValidity(uidValidityA);
2581     state.setRecent(0);
2582     state.setUnSeenCount(2);
2583     state.setFlags(QStringLiteral("\\Answered \\Flagged \\Deleted \\Seen \\Draft Junk NonJunk $Forwarded").split(QLatin1Char(' ')));
2584     state.setPermanentFlags(QStringLiteral("\\Answered \\Flagged \\Deleted \\Seen \\Draft Junk NonJunk $Forwarded \\*").split(QLatin1Char(' ')));
2585     state.setHighestModSeq(1);
2586     uidMapA << 1 << 2 << 3;
2587 
2588     helperCheckCache();
2589     helperVerifyUidMapA();
2590     QCOMPARE(model->cache()->mailboxSyncState("a"), state);
2591     QCOMPARE(static_cast<int>(model->cache()->mailboxSyncState("a").exists()), uidMapA.size());
2592     QCOMPARE(model->cache()->uidMapping("a"), uidMapA);
2593     QCOMPARE(model->cache()->msgFlags("a", 1), QStringList() << QLatin1String("\\Seen"));
2594     QCOMPARE(model->cache()->msgFlags("a", 2), QStringList());
2595     QCOMPARE(model->cache()->msgFlags("a", 3), QStringList() << QLatin1String("\\Answered"));
2596 
2597     cEmpty();
2598     justKeepTask();
2599 }
2600 
2601 /** @short Bug #329204 -- spurious * ENABLED untagged responses from Kolab's IMAP servers */
2602 void ImapModelObtainSynchronizedMailboxTest::testQresyncExtraEnabled()
2603 {
2604     helperTestQresyncNoChanges(EXTRA_ENABLED);
2605 }
2606 
2607 /** @short Bug #350006 -- spurious and empty * ENABLED untagged responses from Cyrus on mailbox switchover */
2608 void ImapModelObtainSynchronizedMailboxTest::testQresyncExtraEnabledEmptySwitchover()
2609 {
2610     helperTestQresyncNoChanges(EXTRA_ENABLED_EMPTY);
2611 }
2612 
2613 /** @short Check that we can recover when a SELECT ends up in a BAD or NO response */
2614 void ImapModelObtainSynchronizedMailboxTest::testSelectRetryNoBad()
2615 {
2616     FakeCapabilitiesInjector injector(model);
2617     injector.injectCapability(QStringLiteral("ESEARCH"));
2618     injector.injectCapability(QStringLiteral("CONDSTORE"));
2619     injector.injectCapability(QStringLiteral("QRESYNC"));
2620 
2621     // Our first attempt fails with a NO response
2622     model->resyncMailbox(idxA);
2623     cClient(t.mk("SELECT a (CONDSTORE)\r\n"));
2624     cServer(t.last("NO go away\r\n"));
2625     cEmpty();
2626     checkNoTasks();
2627 
2628     // The server is even more creative now and returns a tagged BAD for increased fun factor
2629     model->resyncMailbox(idxA);
2630     cClient(t.mk("SELECT a (CONDSTORE)\r\n"));
2631     cServer(t.last("BAD pwned\r\n"));
2632     cEmpty();
2633     checkNoTasks();
2634 
2635     // But we're very persistent and never give up
2636     model->resyncMailbox(idxA);
2637     cClient(t.mk("SELECT a (CONDSTORE)\r\n"));
2638     cServer("* FLAGS (\\Answered \\Flagged \\Deleted \\Seen \\Draft Junk NonJunk $Forwarded)\r\n"
2639             "* OK [PERMANENTFLAGS (\\Answered \\Flagged \\Deleted \\Seen \\Draft Junk NonJunk $Forwarded \\*)] Flags permitted.\r\n"
2640             "* 0 EXISTS\r\n"
2641             "* OK [UIDVALIDITY 666] .\r\n"
2642             "* OK [UIDNEXT 15] .\r\n"
2643             "* OK [HIGHESTMODSEQ 333] .\r\n"
2644             + t.last("OK [READ-WRITE] feels better, doesn't it\r\n"));
2645     cEmpty();
2646     justKeepTask();
2647 
2648     // Now this is strange -- reselecting fails.
2649     // The whole point why we're doing this is to test failed-A -> B transtitions.
2650     model->resyncMailbox(idxA);
2651     cClient(t.mk("SELECT a (QRESYNC (666 333))\r\n"));
2652     cServer(t.last("BAD pwned\r\n"));
2653     cEmpty();
2654     checkNoTasks();
2655 
2656     // Let's see if we will have any luck with the other mailbox
2657     model->resyncMailbox(idxB);
2658     cClient(t.mk("SELECT b (CONDSTORE)\r\n"));
2659     cServer("* FLAGS (\\Answered \\Flagged \\Deleted \\Seen \\Draft Junk NonJunk $Forwarded)\r\n"
2660             "* OK [PERMANENTFLAGS (\\Answered \\Flagged \\Deleted \\Seen \\Draft Junk NonJunk $Forwarded \\*)] Flags permitted.\r\n"
2661             "* 0 EXISTS\r\n"
2662             "* OK [UIDVALIDITY 666] .\r\n"
2663             "* OK [UIDNEXT 15] .\r\n"
2664             "* OK [HIGHESTMODSEQ 333] .\r\n"
2665             + t.last("OK [READ-WRITE] feels better, doesn't it\r\n"));
2666     cEmpty();
2667     justKeepTask();
2668 }
2669 
2670 /** @short Check that mailbox switchover which never completes and subsequent model destruction does not lead to segfault
2671 
2672 https://bugs.kde.org/show_bug.cgi?id=336090
2673 */
2674 void ImapModelObtainSynchronizedMailboxTest::testDanglingSelect()
2675 {
2676     helperTestQresyncNoChanges(JUST_QRESYNC);
2677     model->resyncMailbox(idxB);
2678     cClient(t.mk("SELECT b\r\n"));
2679     cEmpty();
2680 }
2681 
2682 /** @short Test that we can detect broken servers which do not issue an untagged OK with the [CLOSED] response code */
2683 void ImapModelObtainSynchronizedMailboxTest::testQresyncNoClosed()
2684 {
2685     helperTestQresyncNoChanges(JUST_QRESYNC);
2686     model->resyncMailbox(idxB);
2687     cClient(t.mk("SELECT b\r\n"));
2688     {
2689         // The IMAP code should complain about a lack of the [CLOSED] response code
2690         ExpectSingleErrorHere blocker(this);
2691         cServer(t.last("OK selected\r\n"));
2692     }
2693 }
2694 
2695 /** @short Check that everything gets delivered to the older mailbox in absence of QRESYNC */
2696 void ImapModelObtainSynchronizedMailboxTest::testNoQresyncOutOfBounds()
2697 {
2698     existsA = 3;
2699     uidValidityA = 6;
2700     uidMapA << 1 << 7 << 9;
2701     uidNextA = 16;
2702     helperSyncAWithMessagesEmptyState();
2703     model->resyncMailbox(idxB);
2704     QCOMPARE(msgListA.model()->rowCount(msgListA), 3);
2705     cClient(t.mk("SELECT b\r\n"));
2706     {
2707         // This refers to message #4 in a mailbox which only has three messages, hence a failure.
2708         ExpectSingleErrorHere blocker(this);
2709         cServer("* 4 FETCH (UID 666 FLAGS())\r\n")
2710     }
2711 }
2712 
2713 /** @short Check that the responses are consumed by the older mailbox */
2714 void ImapModelObtainSynchronizedMailboxTest::testQresyncClosedHandover()
2715 {
2716     Imap::Mailbox::SyncState sync;
2717     helperQresyncAInitial(sync);
2718     QStringList okFlags = QStringList() << QStringLiteral("z");
2719     QCOMPARE(model->cache()->msgFlags("a", 10), okFlags);
2720 
2721     // OK, we're done. Now the actual test -- open another mailbox
2722     model->resyncMailbox(idxB);
2723     cClient(t.mk("SELECT b\r\n"));
2724     // this one should be eaten, but ignored
2725     cServer("* 3 FETCH (FLAGS ())\r\n");
2726     QCOMPARE(msgListA.model()->index(2, 0, msgListA).data(Imap::Mailbox::RoleMessageFlags).toStringList(), okFlags);
2727     QCOMPARE(model->cache()->msgFlags("a", 10), okFlags);
2728     cServer("* 4 EXISTS\r\n");
2729     cServer("* 4 FETCH (UID 333666333 FLAGS (PWNED))\r\n");
2730     cEmpty();
2731     // "4" shouldn't be in there either, of course
2732     QCOMPARE(model->cache()->mailboxSyncState("a"), sync);
2733     QCOMPARE(model->rowCount(msgListA), 3);
2734     cEmpty();
2735     cServer("* OK [CLOSED] Previous mailbox closed\r\n"
2736             "* 0 EXISTS\r\n"
2737             + t.last("OK selected\r\n"));
2738     justKeepTask();
2739     cEmpty();
2740 }
2741 
2742 /** @short In absence of QRESYNC, all responses are delivered directly to the new ObtainSynchronizedMailboxTask */
2743 void ImapModelObtainSynchronizedMailboxTest::testNoClosedRouting()
2744 {
2745     existsA = 3;
2746     uidValidityA = 6;
2747     uidMapA << 1 << 7 << 9;
2748     uidNextA = 16;
2749     helperSyncAWithMessagesEmptyState();
2750     model->resyncMailbox(idxB);
2751     cClient(t.mk("SELECT b\r\n"));
2752     cServer("* 1 EXISTS\r\n" + t.last("OK selected\r\n"));
2753     cClient(t.mk("UID SEARCH ALL\r\n"));
2754     cServer("* SEARCH 123\r\n" + t.last("OK uids\r\n"));
2755     cClient(t.mk("FETCH 1 (FLAGS)\r\n"));
2756     cServer("* 1 FETCH (FLAGS ())\r\n" + t.last("OK flags\r\n"));
2757     cEmpty();
2758     justKeepTask();
2759 }
2760 
2761 #define REINIT_INDEXES_AFTER_LIST_CYCLE \
2762     model->rowCount(QModelIndex()); \
2763     QCoreApplication::processEvents(); \
2764     QCoreApplication::processEvents(); \
2765     QCOMPARE(model->rowCount(QModelIndex()), 26); \
2766     idxA = model->index(1, 0, QModelIndex()); \
2767     idxB = model->index(2, 0, QModelIndex()); \
2768     QCOMPARE(model->data(idxA, Qt::DisplayRole), QVariant(QLatin1String("a"))); \
2769     QCOMPARE(model->data(idxB, Qt::DisplayRole), QVariant(QLatin1String("b"))); \
2770     msgListA = model->index(0, 0, idxA); \
2771     msgListB = model->index(0, 0, idxB);
2772 
2773 /** @short Check that an UNSELECT resets that flag which expects a [CLOSED] */
2774 void ImapModelObtainSynchronizedMailboxTest::testUnselectClosed()
2775 {
2776     FakeCapabilitiesInjector injector(model);
2777     injector.injectCapability(QStringLiteral("UNSELECT"));
2778     Imap::Mailbox::SyncState sync;
2779     helperQresyncAInitial(sync);
2780 
2781     model->reloadMailboxList();
2782     REINIT_INDEXES_AFTER_LIST_CYCLE
2783     cClient(t.mk("UNSELECT\r\n"));
2784     cServer(t.last("OK unselected\r\n"));
2785     cEmpty();
2786 
2787     model->resyncMailbox(idxA);
2788     cClient(t.mk("SELECT a (QRESYNC (666 33 (2 9)))\r\n"));
2789     cServer("* 3 EXISTS\r\n"
2790             "* OK [UIDVALIDITY 666] .\r\n"
2791             "* OK [UIDNEXT 15] .\r\n"
2792             "* OK [HIGHESTMODSEQ 33] .\r\n"
2793             );
2794     cServer(t.last("OK selected\r\n"));
2795 
2796     justKeepTask();
2797     cEmpty();
2798 }
2799 
2800 /** @short Similar to testUnselectClosed, but mailbox invalidation happens during the initial sync */
2801 void ImapModelObtainSynchronizedMailboxTest::testUnselectClosedDuringSelecting()
2802 {
2803     FakeCapabilitiesInjector injector(model);
2804     injector.injectCapability(QStringLiteral("UNSELECT"));
2805     injector.injectCapability(QStringLiteral("QRESYNC"));
2806     Imap::Mailbox::SyncState sync;
2807     helperQresyncAInitial(sync);
2808 
2809     LibMailboxSync::setModelNetworkPolicy(model, Imap::Mailbox::NETWORK_OFFLINE);
2810     cClient(t.mk("LOGOUT\r\n"));
2811     cServer(t.last("OK loggedeout\r\n") + "* BYE see ya\r\n");
2812 
2813     taskFactoryUnsafe->fakeListChildMailboxes = false;
2814     LibMailboxSync::setModelNetworkPolicy(model, Imap::Mailbox::NETWORK_ONLINE);
2815     t.reset();
2816     cClient(t.mk("LIST \"\" \"%\"\r\n"));
2817     model->switchToMailbox(idxA);
2818     injector.injectCapability(QStringLiteral("UNSELECT"));
2819     injector.injectCapability(QStringLiteral("QRESYNC"));
2820     cServer("* LIST () \".\" a\r\n" + t.last("OK listed\r\n"));
2821     cClient(t.mk("SELECT a (QRESYNC (666 33 (2 9)))\r\n"));
2822 
2823     // This simulates the first sync after a reconnect.
2824     // There's no [CLOSED] because it's a new connection.
2825     cServer("* 3 EXISTS\r\n"
2826             "* OK [UIDVALIDITY 666] .\r\n"
2827             "* OK [UIDNEXT 15] .\r\n"
2828             "* OK [HIGHESTMODSEQ 33] .\r\n"
2829             + t.last("OK selected\r\n"));
2830     cClient(t.mk("UNSELECT\r\n"));
2831     cServer(t.last("OK unselected\r\n"));
2832     cEmpty();
2833 
2834     idxA = model->index(1, 0, QModelIndex());
2835     QCOMPARE(idxA.data(Imap::Mailbox::RoleMailboxName), QVariant(QLatin1String("a")));
2836 
2837     // Now perform a sync after the failed one.
2838     // There will again be no [CLOSED], bug Trojita had a bug and expected to see a [CLOSED].
2839     model->switchToMailbox(idxA);
2840     cClient(t.mk("SELECT a (QRESYNC (666 33 (2 9)))\r\n"));
2841     cServer("* 3 EXISTS\r\n"
2842             "* OK [UIDVALIDITY 666] .\r\n"
2843             "* OK [UIDNEXT 15] .\r\n"
2844             "* OK [HIGHESTMODSEQ 33] .\r\n"
2845             + t.last("OK selected\r\n"));
2846 
2847     justKeepTask();
2848     cEmpty();
2849 }
2850 
2851 /** @short Servers speaking about UID 0 are buggy, full stop */
2852 void ImapModelObtainSynchronizedMailboxTest::testUid0()
2853 {
2854     QCOMPARE(model->rowCount(msgListA), 0);
2855     cClient(t.mk("SELECT a\r\n"));
2856     cServer("* 1 EXISTS\r\n");
2857     {
2858         ExpectSingleErrorHere blocker(this);
2859         cServer("* 1 FETCH (UID 0)\r\n");
2860     }
2861 }
2862 
2863 QTEST_GUILESS_MAIN( ImapModelObtainSynchronizedMailboxTest )