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 )