IMAP: Fix interaction of UNSELECT and [CLOSED]
[trojita.git] / tests / Imap / test_Imap_Tasks_ObtainSynchronizedMailbox.cpp
blobe655638ce0de5c3e00064374966c5c003f37659c
1 /* Copyright (C) 2006 - 2014 Jan Kundrát <jkt@flaska.net>
3 This file is part of the Trojita Qt IMAP e-mail client,
4 http://trojita.flaska.net/
6 This program is free software; you can redistribute it and/or
7 modify it under the terms of the GNU General Public License as
8 published by the Free Software Foundation; either version 2 of
9 the License or (at your option) version 3 or any later version
10 accepted by the membership of KDE e.V. (or its successor approved
11 by the membership of KDE e.V.), which shall act as a proxy
12 defined in Section 14 of version 3 of the license.
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License for more details.
19 You should have received a copy of the GNU General Public License
20 along with this program. If not, see <http://www.gnu.org/licenses/>.
23 #include "test_Imap_Tasks_ObtainSynchronizedMailbox.h"
24 #include "Utils/headless_test.h"
25 #include "Utils/FakeCapabilitiesInjector.h"
26 #include "Streams/FakeSocket.h"
27 #include "Imap/Model/ItemRoles.h"
28 #include "Imap/Model/MailboxTree.h"
29 #include "Imap/Model/MsgListModel.h"
30 #include "Imap/Model/ThreadingMsgListModel.h"
31 #include "Imap/Tasks/ObtainSynchronizedMailboxTask.h"
34 void ImapModelObtainSynchronizedMailboxTest::init()
36 LibMailboxSync::init();
38 using namespace Imap::Mailbox;
39 MsgListModel *msgListModelA = new MsgListModel(model, model);
40 ThreadingMsgListModel *threadingA = new ThreadingMsgListModel(msgListModelA);
41 threadingA->setSourceModel(msgListModelA);
42 MsgListModel *msgListModelB = new MsgListModel(model, model);
43 ThreadingMsgListModel *threadingB = new ThreadingMsgListModel(msgListModelB);
44 threadingB->setSourceModel(msgListModelB);
47 /** Verify syncing of an empty mailbox with just the EXISTS response
49 Verify that we can synchronize a mailbox which is empty even if the server
50 ommits most of the required replies and sends us just the EXISTS one. Also
51 check the cache for valid state.
53 void ImapModelObtainSynchronizedMailboxTest::testSyncEmptyMinimal()
55 model->setProperty( "trojita-imap-noop-period", 10 );
57 // Ask the model to sync stuff
58 QCOMPARE( model->rowCount( msgListA ), 0 );
59 cClient(t.mk("SELECT a\r\n"));
61 // Try to feed it with absolute minimum data
62 cServer(QByteArray("* 0 exists\r\n") + t.last("OK done\r\n"));
64 // Verify that we indeed received what we wanted
65 QCOMPARE( model->rowCount( msgListA ), 0 );
66 Imap::Mailbox::TreeItemMsgList* list = dynamic_cast<Imap::Mailbox::TreeItemMsgList*>( static_cast<Imap::Mailbox::TreeItem*>( msgListA.internalPointer() ) );
67 Q_ASSERT( list );
68 QVERIFY( list->fetched() );
69 //QTest::qWait( 100 );
70 QCoreApplication::processEvents();
71 cEmpty();
73 // Now, let's try to re-sync it once again; the difference is that our cache now has "something"
74 model->resyncMailbox( idxA );
75 QCoreApplication::processEvents();
77 // Verify that it indeed caused a re-synchronization
78 list = dynamic_cast<Imap::Mailbox::TreeItemMsgList*>( static_cast<Imap::Mailbox::TreeItem*>( msgListA.internalPointer() ) );
79 Q_ASSERT( list );
80 QVERIFY( list->loading() );
81 cClient(t.mk("SELECT a\r\n"));
82 cServer(QByteArray("* 0 exists\r\n") + t.last("OK done\r\n"));
83 cEmpty();
85 // Check the cache
86 Imap::Mailbox::SyncState syncState = model->cache()->mailboxSyncState( QLatin1String("a") );
87 QCOMPARE( syncState.exists(), 0u );
88 QCOMPARE( syncState.flags(), QStringList() );
89 // No messages, and hence we know that none of them could possibly be recent or unseen or whatever
90 QCOMPARE( syncState.isUsableForNumbers(), true );
91 QCOMPARE( syncState.isUsableForSyncing(), false );
92 QCOMPARE( syncState.permanentFlags(), QStringList() );
93 QCOMPARE( syncState.recent(), 0u );
94 QCOMPARE( syncState.uidNext(), 0u );
95 QCOMPARE( syncState.uidValidity(), 0u );
96 QCOMPARE( syncState.unSeenCount(), 0u );
97 QCOMPARE( syncState.unSeenOffset(), 0u );
99 // No errors
100 QVERIFY( errorSpy->isEmpty() );
103 /** Verify syncing of a non-empty mailbox with just the EXISTS response
105 The interesting bits are what happens when the server cheats and returns just a very limited subset of mailbox metadata,
106 just the EXISTS response in particular.
108 void ImapModelObtainSynchronizedMailboxTest::testSyncEmptyMinimalNonEmpty()
110 model->setProperty("trojita-imap-noop-period", 10);
112 // Ask the model to sync stuff
113 QCOMPARE(model->rowCount(msgListA), 0);
114 cClient(t.mk("SELECT a\r\n"));
116 // Try to feed it with absolute minimum data
117 cServer(QByteArray("* 1 exists\r\n") + t.last("OK done\r\n"));
118 cClient(t.mk("UID SEARCH ALL\r\n"));
119 cServer("* SEARCH 666\r\n" + t.last("OK searched\r\n"));
120 cClient(t.mk("FETCH 1 (FLAGS)\r\n"));
121 cServer("* 1 FETCH (FLAGS ())\r\n" + t.last("OK fetched\r\n"));
122 cEmpty();
124 // Verify that we indeed received what we wanted
125 QCOMPARE(model->rowCount(msgListA), 1);
126 Imap::Mailbox::TreeItemMsgList* list = dynamic_cast<Imap::Mailbox::TreeItemMsgList*>(static_cast<Imap::Mailbox::TreeItem*>(msgListA.internalPointer()));
127 Q_ASSERT(list);
128 QVERIFY(list->fetched());
129 QCoreApplication::processEvents();
130 cEmpty();
132 // Now, let's try to re-sync it once again; the difference is that our cache now has "something"
133 model->resyncMailbox(idxA);
134 QCoreApplication::processEvents();
136 // Verify that it indeed caused a re-synchronization
137 list = dynamic_cast<Imap::Mailbox::TreeItemMsgList*>(static_cast<Imap::Mailbox::TreeItem*>(msgListA.internalPointer()));
138 Q_ASSERT(list);
139 QVERIFY(list->loading());
140 cClient(t.mk("SELECT a\r\n"));
141 cServer(QByteArray("* 1 exists\r\n") + t.last("OK done\r\n"));
142 // We still don't know anything else but EXISTS, so this is a full sync again
143 cClient(t.mk("UID SEARCH ALL\r\n"));
144 cServer("* SEARCH 666\r\n" + t.last("OK searched\r\n"));
145 cClient(t.mk("FETCH 1 (FLAGS)\r\n"));
146 cServer("* 1 FETCH (FLAGS ())\r\n" + t.last("OK fetched\r\n"));
147 cEmpty();
149 // Check the cache
150 Imap::Mailbox::SyncState syncState = model->cache()->mailboxSyncState(QLatin1String("a"));
151 QCOMPARE(syncState.exists(), 1u);
152 QCOMPARE(syncState.flags(), QStringList());
153 // The flags are already synced, though
154 QCOMPARE(syncState.isUsableForNumbers(), true);
155 QCOMPARE(syncState.isUsableForSyncing(), false);
156 QCOMPARE(syncState.permanentFlags(), QStringList());
157 QCOMPARE(syncState.recent(), 0u);
158 QCOMPARE(syncState.uidNext(), 667u);
159 QCOMPARE(syncState.uidValidity(), 0u);
160 QCOMPARE(syncState.unSeenCount(), 1u);
161 QCOMPARE(syncState.unSeenOffset(), 0u);
163 // No errors
164 QVERIFY( errorSpy->isEmpty() );
167 /** @short Verify synchronization of an empty mailbox against a compliant IMAP4rev1 server
169 This test verifies that we handle a compliant server's responses when we sync an empty mailbox.
170 A check of the state of the cache after is completed, too.
172 void ImapModelObtainSynchronizedMailboxTest::testSyncEmptyNormal()
174 // Ask the model to sync stuff
175 QCOMPARE( model->rowCount( msgListA ), 0 );
176 cClient(t.mk("SELECT a\r\n"));
178 // Try to feed it with absolute minimum data
179 cServer(QByteArray("* FLAGS (\\Answered \\Flagged \\Deleted \\Seen \\Draft)\r\n"
180 "* OK [PERMANENTFLAGS (\\Answered \\Flagged \\Deleted \\Seen \\Draft \\*)] Flags permitted.\r\n"
181 "* 0 EXISTS\r\n"
182 "* 0 RECENT\r\n"
183 "* OK [UIDVALIDITY 666] UIDs valid\r\n"
184 "* OK [UIDNEXT 3] Predicted next UID\r\n")
185 + t.last("OK [READ-WRITE] Select completed.\r\n"));
187 // Verify that we indeed received what we wanted
188 QCOMPARE( model->rowCount( msgListA ), 0 );
189 Imap::Mailbox::TreeItemMsgList* list = dynamic_cast<Imap::Mailbox::TreeItemMsgList*>( static_cast<Imap::Mailbox::TreeItem*>( msgListA.internalPointer() ) );
190 Q_ASSERT( list );
191 QVERIFY( list->fetched() );
192 cEmpty();
194 // Check the cache
195 Imap::Mailbox::SyncState syncState = model->cache()->mailboxSyncState( QLatin1String("a") );
196 QCOMPARE( syncState.exists(), 0u );
197 QCOMPARE( syncState.flags(), QStringList() << QLatin1String("\\Answered") <<
198 QLatin1String("\\Flagged") << QLatin1String("\\Deleted") <<
199 QLatin1String("\\Seen") << QLatin1String("\\Draft") );
200 QCOMPARE( syncState.isUsableForNumbers(), true );
201 QCOMPARE( syncState.isUsableForSyncing(), true );
202 QCOMPARE( syncState.permanentFlags(), QStringList() << QLatin1String("\\Answered") <<
203 QLatin1String("\\Flagged") << QLatin1String("\\Deleted") <<
204 QLatin1String("\\Seen") << QLatin1String("\\Draft") << QLatin1String("\\*") );
205 QCOMPARE( syncState.recent(), 0u );
206 QCOMPARE( syncState.uidNext(), 3u );
207 QCOMPARE( syncState.uidValidity(), 666u );
208 QCOMPARE( syncState.unSeenCount(), 0u );
209 QCOMPARE(syncState.unSeenOffset(), 0u);
211 // Now, let's try to re-sync it once again; the difference is that our cache now has "something"
212 // and that we feed it with a rather limited set of responses
213 model->resyncMailbox( idxA );
214 QCoreApplication::processEvents();
216 // Verify that it indeed caused a re-synchronization
217 list = dynamic_cast<Imap::Mailbox::TreeItemMsgList*>( static_cast<Imap::Mailbox::TreeItem*>( msgListA.internalPointer() ) );
218 Q_ASSERT( list );
219 QVERIFY( list->loading() );
220 cClient(t.mk("SELECT a\r\n"));
221 cServer(QByteArray("* 0 exists\r\n* NO a random no in inserted here\r\n") + t.last("OK done\r\n"));
222 cEmpty();
224 // Check the cache; now it should be almost empty
225 syncState = model->cache()->mailboxSyncState( QLatin1String("a") );
226 QCOMPARE( syncState.exists(), 0u );
227 QCOMPARE( syncState.flags(), QStringList() );
228 QCOMPARE( syncState.isUsableForNumbers(), true );
229 QCOMPARE( syncState.isUsableForSyncing(), false );
230 QCOMPARE( syncState.permanentFlags(), QStringList() );
231 QCOMPARE( syncState.recent(), 0u );
233 // No errors
234 QVERIFY( errorSpy->isEmpty() );
237 /** @short Initial sync of a mailbox that contains some messages */
238 void ImapModelObtainSynchronizedMailboxTest::testSyncWithMessages()
240 existsA = 33;
241 uidValidityA = 333666;
242 for ( uint i = 1; i <= existsA; ++i ) {
243 uidMapA.append( i * 1.3 );
245 uidNextA = qMax( 666u, uidMapA.last() );
246 helperSyncAWithMessagesEmptyState();
247 helperVerifyUidMapA();
250 /** @short Go back to a selected mailbox after some time, the mailbox doesn't have any modifications */
251 void ImapModelObtainSynchronizedMailboxTest::testResyncNoArrivals()
253 existsA = 42;
254 uidValidityA = 1337;
255 for ( uint i = 1; i <= existsA; ++i ) {
256 uidMapA.append( 6 + i * 3.6 );
258 uidNextA = qMax( 666u, uidMapA.last() );
259 helperSyncAWithMessagesEmptyState();
260 helperSyncBNoMessages();
261 helperSyncAWithMessagesNoArrivals();
262 helperSyncBNoMessages();
263 helperSyncAWithMessagesNoArrivals();
264 helperVerifyUidMapA();
265 helperOneFlagUpdate( idxA.child( 0,0 ).child( 10, 0 ) );
268 /** @short Test new message arrivals happening on each resync */
269 void ImapModelObtainSynchronizedMailboxTest::testResyncOneNew()
271 existsA = 17;
272 uidValidityA = 800500;
273 for ( uint i = 1; i <= existsA; ++i ) {
274 uidMapA.append( 3 + i * 1.3 );
276 uidNextA = qMax( 666u, uidMapA.last() );
277 helperSyncAWithMessagesEmptyState();
278 helperSyncBNoMessages();
279 helperSyncASomeNew( 1 );
280 helperVerifyUidMapA();
281 helperSyncBNoMessages();
282 helperSyncASomeNew( 3 );
283 helperVerifyUidMapA();
286 /** @short Test inconsistency in the local cache where UIDNEXT got decreased without UIDVALIDITY change */
287 void ImapModelObtainSynchronizedMailboxTest::testDecreasedUidNext()
289 // Initial state
290 existsA = 3;
291 uidValidityA = 333666;
292 for ( uint i = 1; i <= existsA; ++i ) {
293 uidMapA.append(i);
295 // Make sure the UID really gets decreeased
296 Q_ASSERT(uidNextA < uidMapA.last() + 1);
297 uidNextA = uidMapA.last() + 1;
298 helperSyncAWithMessagesEmptyState();
299 helperVerifyUidMapA();
300 helperSyncBNoMessages();
302 // Now perform the nasty change...
303 --existsA;
304 uidMapA.removeLast();
305 --uidNextA;
307 // ...and resync again, this should scream loud, but not crash
308 QCOMPARE( model->rowCount( msgListA ), 3 );
309 model->switchToMailbox( idxA );
310 QCoreApplication::processEvents();
311 QCoreApplication::processEvents();
312 helperSyncAFullSync();
316 Test that going from an empty mailbox to a bigger one works correctly, especially that the untagged
317 EXISTS response which belongs to the SELECT gets picked up by the new mailbox and not the old one
319 void ImapModelObtainSynchronizedMailboxTest::testSyncTwoLikeCyrus()
321 // Ask the model to sync stuff
322 QCOMPARE( model->rowCount( msgListB ), 0 );
323 cClient(t.mk("SELECT b\r\n"));
325 cServer(QByteArray("* 0 EXISTS\r\n"
326 "* 0 RECENT\r\n"
327 "* FLAGS (\\Answered \\Flagged \\Draft \\Deleted \\Seen)\r\n"
328 "* OK [PERMANENTFLAGS (\\Answered \\Flagged \\Draft \\Deleted \\Seen \\*)] Ok\r\n"
329 "* OK [UIDVALIDITY 1290594339] Ok\r\n"
330 "* OK [UIDNEXT 1] Ok\r\n"
331 "* OK [HIGHESTMODSEQ 1] Ok\r\n"
332 "* OK [URLMECH INTERNAL] Ok\r\n")
333 + t.last("OK [READ-WRITE] Completed\r\n"));
335 // Verify that we indeed received what we wanted
336 Imap::Mailbox::TreeItemMsgList* listB = dynamic_cast<Imap::Mailbox::TreeItemMsgList*>( static_cast<Imap::Mailbox::TreeItem*>( msgListB.internalPointer() ) );
337 Q_ASSERT( listB );
338 QVERIFY( listB->fetched() );
340 QCOMPARE( model->rowCount( msgListB ), 0 );
341 cEmpty();
342 QVERIFY( errorSpy->isEmpty() );
344 QCOMPARE( model->rowCount( msgListA ), 0 );
345 cClient(t.mk("SELECT a\r\n"));
347 cServer(QByteArray("* 1 EXISTS\r\n"
348 "* 0 RECENT\r\n"
349 "* FLAGS (\\Answered \\Flagged \\Draft \\Deleted \\Seen hasatt hasnotd)\r\n"
350 "* OK [PERMANENTFLAGS (\\Answered \\Flagged \\Draft \\Deleted \\Seen hasatt hasnotd \\*)] Ok\r\n"
351 "* OK [UIDVALIDITY 1290593745] Ok\r\n"
352 "* OK [UIDNEXT 2] Ok\r\n"
353 "* OK [HIGHESTMODSEQ 9] Ok\r\n"
354 "* OK [URLMECH INTERNAL] Ok\r\n")
355 + t.last("OK [READ-WRITE] Completed"));
356 Imap::Mailbox::TreeItemMsgList* listA = dynamic_cast<Imap::Mailbox::TreeItemMsgList*>( static_cast<Imap::Mailbox::TreeItem*>( msgListA.internalPointer() ) );
357 Q_ASSERT( listA );
358 QVERIFY( ! listA->fetched() );
359 cEmpty();
360 QVERIFY( errorSpy->isEmpty() );
363 void ImapModelObtainSynchronizedMailboxTest::testSyncTwoInParallel()
365 // This will select both mailboxes, one after another
366 QCOMPARE( model->rowCount( msgListA ), 0 );
367 QCOMPARE( model->rowCount( msgListB ), 0 );
368 cClient(t.mk("SELECT a\r\n"));
369 cServer(QByteArray("* 1 EXISTS\r\n"
370 "* 0 RECENT\r\n"
371 "* FLAGS (\\Answered \\Flagged \\Draft \\Deleted \\Seen hasatt hasnotd)\r\n"
372 "* OK [PERMANENTFLAGS (\\Answered \\Flagged \\Draft \\Deleted \\Seen hasatt hasnotd \\*)] Ok\r\n"
373 "* OK [UIDVALIDITY 1290593745] Ok\r\n"
374 "* OK [UIDNEXT 2] Ok\r\n"
375 "* OK [HIGHESTMODSEQ 9] Ok\r\n"
376 "* OK [URLMECH INTERNAL] Ok\r\n")
377 + t.last("OK [READ-WRITE] Completed\r\n"));
378 cClient(t.mk("UID SEARCH ALL\r\n"));
379 QCOMPARE( model->rowCount( msgListA ), 1 );
380 QCOMPARE( model->rowCount( msgListB ), 0 );
381 QCoreApplication::processEvents();
382 QCoreApplication::processEvents();
383 QCoreApplication::processEvents();
384 QCOMPARE( model->rowCount( msgListA ), 1 );
385 QCOMPARE( model->rowCount( msgListB ), 0 );
386 // ...none of them are synced yet
388 cServer(QByteArray("* SEARCH 1\r\n") + t.last("OK Completed (1 msgs in 0.000 secs)\r\n"));
389 QCOMPARE( model->rowCount( msgListA ), 1 );
390 // the first one should contain a message now
391 cClient(t.mk("FETCH 1 (FLAGS)\r\n"));
392 // without the tagged OK -- se below
393 cServer(QByteArray("* 1 FETCH (FLAGS (\\Seen hasatt hasnotd))\r\n"));
394 QModelIndex msg1A = model->index( 0, 0, msgListA );
396 // Requesting message data should delay entering the second mailbox.
397 // That's why we had to delay the tagged OK for FLAGS.
398 QCOMPARE( model->data( msg1A, Imap::Mailbox::RoleMessageMessageId ), QVariant() );
399 cServer(t.last("OK Completed (0.000 sec)\r\n"));
400 QCoreApplication::processEvents();
401 QCoreApplication::processEvents();
402 QCOMPARE( model->rowCount( msgListA ), 1 );
403 QCoreApplication::processEvents();
404 QCoreApplication::processEvents();
406 cClient(t.mk("UID FETCH 1 (" FETCH_METADATA_ITEMS ")\r\n"));
407 // let's try to get around without specifying ENVELOPE and BODYSTRUCTURE
408 cServer(QByteArray("* 1 FETCH (UID 1 RFC822.SIZE 13021)\r\n") + t.last("OK Completed\r\n"));
409 cClient(t.mk(QByteArray("SELECT b\r\n")));
410 cEmpty();
411 QVERIFY( errorSpy->isEmpty() );
414 /** @short Test whether a change in the UIDVALIDITY results in a complete resync */
415 void ImapModelObtainSynchronizedMailboxTest::testResyncUidValidity()
417 existsA = 42;
418 uidValidityA = 1337;
419 for ( uint i = 1; i <= existsA; ++i ) {
420 uidMapA.append( 6 + i * 3.6 );
422 uidNextA = qMax( 666u, uidMapA.last() );
423 helperSyncAWithMessagesEmptyState();
425 // Change UIDVALIDITY
426 uidValidityA = 333666;
427 helperSyncBNoMessages();
429 QCOMPARE( model->rowCount( msgListA ), 42 );
430 model->switchToMailbox( idxA );
431 QCoreApplication::processEvents();
432 QCoreApplication::processEvents();
434 helperSyncAFullSync();
437 void ImapModelObtainSynchronizedMailboxTest::testFlagReSyncBenchmark()
439 existsA = 100000;
440 uidValidityA = 333;
441 for (uint i = 1; i <= existsA; ++i) {
442 uidMapA << i;
444 uidNextA = existsA + 2;
445 helperSyncAWithMessagesEmptyState();
447 QBENCHMARK {
448 helperSyncBNoMessages();
449 helperSyncAWithMessagesNoArrivals();
453 /** @short Make sure that calling Model::resyncMailbox() preloads data from the cache */
454 void ImapModelObtainSynchronizedMailboxTest::testReloadReadsFromCache()
456 Imap::Mailbox::SyncState sync;
457 sync.setExists(3);
458 sync.setUidValidity(666);
459 sync.setUidNext(15);
460 QList<uint> uidMap;
461 uidMap << 6 << 9 << 10;
462 model->cache()->setMailboxSyncState("a", sync);
463 model->cache()->setUidMapping("a", uidMap);
464 model->resyncMailbox(idxA);
465 cClient(t.mk("SELECT a\r\n"));
466 cServer("* 3 EXISTS\r\n"
467 "* OK [UIDVALIDITY 666] .\r\n"
468 "* OK [UIDNEXT 15] .\r\n");
469 cServer(t.last("OK selected\r\n"));
470 cClient(t.mk("FETCH 1:3 (FLAGS)\r\n"));
471 cServer("* 1 FETCH (FLAGS (\\SEEN x))\r\n"
472 "* 2 FETCH (FLAGS (y))\r\n"
473 "* 3 FETCH (FLAGS (\\seen z))\r\n");
474 cServer(t.last("OK fetch\r\n"));
475 cEmpty();
476 sync.setUnSeenCount(1);
477 sync.setRecent(0);
478 QCOMPARE(model->cache()->mailboxSyncState("a"), sync);
479 QCOMPARE(static_cast<int>(model->cache()->mailboxSyncState("a").exists()), uidMap.size());
480 QCOMPARE(model->cache()->uidMapping("a"), uidMap);
481 QCOMPARE(model->cache()->msgFlags("a", 6), QStringList() << "\\Seen" << "x");
482 QCOMPARE(model->cache()->msgFlags("a", 9), QStringList() << "y");
483 QCOMPARE(model->cache()->msgFlags("a", 10), QStringList() << "\\Seen" << "z");
484 justKeepTask();
487 /** @short Test synchronization of a mailbox with on-disk cache being up-to-date, but no data in memory */
488 void ImapModelObtainSynchronizedMailboxTest::testCacheNoChange()
490 Imap::Mailbox::SyncState sync;
491 sync.setExists(3);
492 sync.setUidValidity(666);
493 sync.setUidNext(15);
494 sync.setUnSeenCount(3);
495 sync.setRecent(0);
496 QList<uint> uidMap;
497 uidMap << 6 << 9 << 10;
498 model->cache()->setMailboxSyncState("a", sync);
499 model->cache()->setUidMapping("a", uidMap);
500 QCOMPARE(model->rowCount(msgListA), 0);
501 cClient(t.mk("SELECT a\r\n"));
502 cServer("* 3 EXISTS\r\n"
503 "* OK [UIDVALIDITY 666] .\r\n"
504 "* OK [UIDNEXT 15] .\r\n");
505 cServer(t.last("OK selected\r\n"));
506 cClient(t.mk("FETCH 1:3 (FLAGS)\r\n"));
507 cServer("* 1 FETCH (FLAGS (x))\r\n"
508 "* 2 FETCH (FLAGS (y))\r\n"
509 "* 3 FETCH (FLAGS (z))\r\n");
510 cServer(t.last("OK fetch\r\n"));
511 cEmpty();
512 QCOMPARE(model->cache()->mailboxSyncState("a"), sync);
513 QCOMPARE(static_cast<int>(model->cache()->mailboxSyncState("a").exists()), uidMap.size());
514 QCOMPARE(model->cache()->uidMapping("a"), uidMap);
515 QCOMPARE(model->cache()->msgFlags("a", 6), QStringList() << "x");
516 QCOMPARE(model->cache()->msgFlags("a", 9), QStringList() << "y");
517 QCOMPARE(model->cache()->msgFlags("a", 10), QStringList() << "z");
518 justKeepTask();
521 /** @short Test UIDVALIDITY changes since the last cached state */
522 void ImapModelObtainSynchronizedMailboxTest::testCacheUidValidity()
524 Imap::Mailbox::SyncState sync;
525 sync.setExists(3);
526 sync.setUidValidity(333);
527 sync.setUidNext(15);
528 QList<uint> uidMap;
529 uidMap << 6 << 9 << 10;
531 // Fill the cache with some values which shall make sense in the "previous state"
532 model->cache()->setMailboxSyncState("a", sync);
533 model->cache()->setUidMapping("a", uidMap);
534 // Don't forget about the flags
535 model->cache()->setMsgFlags("a", 1, QStringList() << "f1");
536 model->cache()->setMsgFlags("a", 6, QStringList() << "f6");
537 // And even message metadata
538 Imap::Mailbox::AbstractCache::MessageDataBundle bundle;
539 bundle.envelope = Imap::Message::Envelope(QDateTime::currentDateTime(), QLatin1String("subj"),
540 QList<Imap::Message::MailAddress>(), QList<Imap::Message::MailAddress>(),
541 QList<Imap::Message::MailAddress>(), QList<Imap::Message::MailAddress>(),
542 QList<Imap::Message::MailAddress>(), QList<Imap::Message::MailAddress>(),
543 QList<QByteArray>(), QByteArray());
544 bundle.uid = 1;
545 model->cache()->setMessageMetadata("a", 1, bundle);
546 bundle.uid = 6;
547 model->cache()->setMessageMetadata("a", 6, bundle);
548 // And of course also message parts
549 model->cache()->setMsgPart("a", 1, "1", "blah");
550 model->cache()->setMsgPart("a", 6, "1", "blah");
552 QCOMPARE(model->rowCount(msgListA), 0);
553 cClient(t.mk("SELECT a\r\n"));
554 cServer("* 3 EXISTS\r\n"
555 "* OK [UIDVALIDITY 666] .\r\n"
556 "* OK [UIDNEXT 15] .\r\n");
557 cServer(t.last("OK selected\r\n"));
559 // The UIDVALIDTY change should be already discovered
560 QCOMPARE(model->cache()->msgFlags("a", 1), QStringList());
561 QCOMPARE(model->cache()->msgFlags("a", 3), QStringList());
562 QCOMPARE(model->cache()->msgFlags("a", 6), QStringList());
563 QCOMPARE(model->cache()->messageMetadata("a", 1), Imap::Mailbox::AbstractCache::MessageDataBundle());
564 QCOMPARE(model->cache()->messageMetadata("a", 3), Imap::Mailbox::AbstractCache::MessageDataBundle());
565 QCOMPARE(model->cache()->messageMetadata("a", 6), Imap::Mailbox::AbstractCache::MessageDataBundle());
566 QCOMPARE(model->cache()->messagePart("a", 1, "1"), QByteArray());
567 QCOMPARE(model->cache()->messagePart("a", 3, "1"), QByteArray());
568 QCOMPARE(model->cache()->messagePart("a", 6, "1"), QByteArray());
570 cClient(t.mk("UID SEARCH ALL\r\n"));
571 cServer(QByteArray("* SEARCH 6 9 10\r\n") + t.last("OK uid search\r\n"));
572 cClient(t.mk("FETCH 1:3 (FLAGS)\r\n"));
573 cServer("* 1 FETCH (FLAGS (x))\r\n"
574 "* 2 FETCH (FLAGS (y))\r\n"
575 "* 3 FETCH (FLAGS (z))\r\n");
576 cServer(t.last("OK fetch\r\n"));
577 cEmpty();
578 sync.setUidValidity(666);
579 sync.setUnSeenCount(3);
580 sync.setRecent(0);
581 QCOMPARE(model->cache()->mailboxSyncState("a"), sync);
582 QCOMPARE(static_cast<int>(model->cache()->mailboxSyncState("a").exists()), uidMap.size());
583 QCOMPARE(model->cache()->uidMapping("a"), uidMap);
584 QCOMPARE(model->cache()->msgFlags("a", 6), QStringList() << "x");
585 QCOMPARE(model->cache()->msgFlags("a", 9), QStringList() << "y");
586 QCOMPARE(model->cache()->msgFlags("a", 10), QStringList() << "z");
587 justKeepTask();
590 /** @short Test synchronization of a mailbox with on-disk cache when one new message arrives */
591 void ImapModelObtainSynchronizedMailboxTest::testCacheArrivals()
593 Imap::Mailbox::SyncState sync;
594 sync.setExists(3);
595 sync.setUidValidity(666);
596 sync.setUidNext(15);
597 QList<uint> uidMap;
598 uidMap << 6 << 9 << 10;
599 model->cache()->setMailboxSyncState("a", sync);
600 model->cache()->setUidMapping("a", uidMap);
601 QCOMPARE(model->rowCount(msgListA), 0);
602 cClient(t.mk("SELECT a\r\n"));
603 cServer("* 4 EXISTS\r\n"
604 "* OK [UIDVALIDITY 666] .\r\n"
605 "* OK [UIDNEXT 16] .\r\n");
606 cServer(t.last("OK selected\r\n"));
607 cClient(t.mk("UID SEARCH UID 15:*\r\n"));
608 cServer("* SEARCH 42\r\n");
609 cServer(t.last("OK uids\r\n"));
610 uidMap << 42;
611 sync.setUidNext(43);
612 sync.setExists(4);
613 cClient(t.mk("FETCH 1:4 (FLAGS)\r\n"));
614 cServer("* 1 FETCH (FLAGS (x))\r\n"
615 "* 2 FETCH (FLAGS (y))\r\n"
616 "* 3 FETCH (FLAGS (z))\r\n"
617 "* 4 FETCH (FLAGS (fn))\r\n");
618 cServer(t.last("OK fetch\r\n"));
619 cEmpty();
620 sync.setUnSeenCount(4);
621 sync.setRecent(0);
622 QCOMPARE(model->cache()->mailboxSyncState("a"), sync);
623 QCOMPARE(static_cast<int>(model->cache()->mailboxSyncState("a").exists()), uidMap.size());
624 QCOMPARE(model->cache()->uidMapping("a"), uidMap);
625 QCOMPARE(model->cache()->msgFlags("a", 6), QStringList() << "x");
626 QCOMPARE(model->cache()->msgFlags("a", 9), QStringList() << "y");
627 QCOMPARE(model->cache()->msgFlags("a", 10), QStringList() << "z");
628 QCOMPARE(model->cache()->msgFlags("a", 42), QStringList() << "fn");
629 justKeepTask();
632 void ImapModelObtainSynchronizedMailboxTest::testCacheArrivalRaceDuringUid()
634 helperCacheArrivalRaceDuringUid(WITHOUT_ESEARCH);
637 void ImapModelObtainSynchronizedMailboxTest::testCacheArrivalRaceDuringUid_ESearch()
639 helperCacheArrivalRaceDuringUid(WITH_ESEARCH);
642 /** @short Number of messages grows twice
644 The first increment is when compared to the original state, the second one after the UID SEARCH is issued,
645 but before its response arrives.
647 void ImapModelObtainSynchronizedMailboxTest::helperCacheArrivalRaceDuringUid(const ESearchMode esearch)
649 if (esearch == WITH_ESEARCH) {
650 FakeCapabilitiesInjector injector(model);
651 injector.injectCapability("ESEARCH");
653 Imap::Mailbox::SyncState sync;
654 sync.setExists(3);
655 sync.setUidValidity(666);
656 sync.setUidNext(15);
657 QList<uint> uidMap;
658 uidMap << 6 << 9 << 10;
659 model->cache()->setMailboxSyncState("a", sync);
660 model->cache()->setUidMapping("a", uidMap);
661 QCOMPARE(model->rowCount(msgListA), 0);
662 cClient(t.mk("SELECT a\r\n"));
663 cServer("* 4 EXISTS\r\n"
664 "* OK [UIDVALIDITY 666] .\r\n"
665 "* OK [UIDNEXT 16] .\r\n");
666 cServer(t.last("OK selected\r\n"));
667 if (esearch == WITH_ESEARCH) {
668 cClient(t.mk("UID SEARCH RETURN (ALL) UID 15:*\r\n"));
669 cServer(QByteArray("* 5 EXISTS\r\n* ESEARCH (TAG ") + t.last() + ") UID ALL 42:43\r\n");
670 } else {
671 cClient(t.mk("UID SEARCH UID 15:*\r\n"));
672 cServer("* 5 EXISTS\r\n* SEARCH 42 43\r\n");
674 cServer(t.last("OK uids\r\n"));
675 uidMap << 42 << 43;
676 sync.setUidNext(44);
677 sync.setExists(5);
678 sync.setUnSeenCount(5);
679 sync.setRecent(0);
680 cClient(t.mk("FETCH 1:5 (FLAGS)\r\n"));
681 cServer("* 1 FETCH (FLAGS (x))\r\n"
682 "* 2 FETCH (FLAGS (y))\r\n"
683 "* 3 FETCH (FLAGS (z))\r\n"
684 "* 4 FETCH (FLAGS (fn))\r\n"
685 "* 5 FETCH (FLAGS (a))\r\n");
686 cServer(t.last("OK fetch\r\n"));
687 cEmpty();
688 QCOMPARE(model->cache()->mailboxSyncState("a"), sync);
689 QCOMPARE(static_cast<int>(model->cache()->mailboxSyncState("a").exists()), uidMap.size());
690 QCOMPARE(model->cache()->uidMapping("a"), uidMap);
691 QCOMPARE(model->cache()->msgFlags("a", 6), QStringList() << "x");
692 QCOMPARE(model->cache()->msgFlags("a", 9), QStringList() << "y");
693 QCOMPARE(model->cache()->msgFlags("a", 10), QStringList() << "z");
694 QCOMPARE(model->cache()->msgFlags("a", 42), QStringList() << "fn");
695 QCOMPARE(model->cache()->msgFlags("a", 43), QStringList() << "a");
696 justKeepTask();
699 /** @short Number of messages grows twice
701 The first increment is when compared to the original state, the second one after the UID SEARCH is issued, after its untagged
702 response arrives, but before the tagged OK.
704 void ImapModelObtainSynchronizedMailboxTest::testCacheArrivalRaceDuringUid2()
706 Imap::Mailbox::SyncState sync;
707 sync.setExists(3);
708 sync.setUidValidity(666);
709 sync.setUidNext(15);
710 QList<uint> uidMap;
711 uidMap << 6 << 9 << 10;
712 model->cache()->setMailboxSyncState("a", sync);
713 model->cache()->setUidMapping("a", uidMap);
714 QCOMPARE(model->rowCount(msgListA), 0);
715 cClient(t.mk("SELECT a\r\n"));
716 cServer("* 4 EXISTS\r\n"
717 "* OK [UIDVALIDITY 666] .\r\n"
718 "* OK [UIDNEXT 16] .\r\n");
719 cServer(t.last("OK selected\r\n"));
720 cClient(t.mk("UID SEARCH UID 15:*\r\n"));
721 cServer("* SEARCH 42\r\n* 5 EXISTS\r\n");
722 cServer(t.last("OK uids\r\n"));
723 uidMap << 42;
724 sync.setUidNext(43);
725 sync.setExists(5);
726 sync.setUnSeenCount(5);
727 sync.setRecent(0);
728 QByteArray newArrivalsQuery = t.mk("UID FETCH 43:* (FLAGS)\r\n");
729 QByteArray newArrivalsResponse = t.last("OK uid fetch\r\n");
730 QByteArray preexistingFlagsQuery = t.mk("FETCH 1:5 (FLAGS)\r\n");
731 QByteArray preexistingFlagsResponse = t.last("OK fetch\r\n");
732 cClient(newArrivalsQuery + preexistingFlagsQuery);
733 cServer("* 5 FETCH (UID 66 FLAGS (a))\r\n");
734 cServer("* 1 FETCH (FLAGS (x))\r\n"
735 "* 2 FETCH (FLAGS (y))\r\n"
736 "* 3 FETCH (FLAGS (z))\r\n"
737 "* 4 FETCH (FLAGS (fn))\r\n"
738 "* 5 FETCH (FLAGS (a))\r\n");
739 cServer(newArrivalsResponse + preexistingFlagsResponse);
740 uidMap << 66;
741 sync.setUidNext(67);
742 cEmpty();
743 QCOMPARE(model->cache()->mailboxSyncState("a"), sync);
744 QCOMPARE(static_cast<int>(model->cache()->mailboxSyncState("a").exists()), uidMap.size());
745 QCOMPARE(model->cache()->uidMapping("a"), uidMap);
746 QCOMPARE(model->cache()->msgFlags("a", 6), QStringList() << "x");
747 QCOMPARE(model->cache()->msgFlags("a", 9), QStringList() << "y");
748 QCOMPARE(model->cache()->msgFlags("a", 10), QStringList() << "z");
749 QCOMPARE(model->cache()->msgFlags("a", 42), QStringList() << "fn");
750 // UID 43 did not exist at any time after all
751 QCOMPARE(model->cache()->msgFlags("a", 43), QStringList());
752 QCOMPARE(model->cache()->msgFlags("a", 66), QStringList() << "a");
753 justKeepTask();
755 /** @short New message arrives when syncing flags */
756 void ImapModelObtainSynchronizedMailboxTest::testCacheArrivalRaceDuringFlags()
758 Imap::Mailbox::SyncState sync;
759 sync.setExists(3);
760 sync.setUidValidity(666);
761 sync.setUidNext(15);
762 QList<uint> uidMap;
763 uidMap << 6 << 9 << 10;
764 model->cache()->setMailboxSyncState("a", sync);
765 model->cache()->setUidMapping("a", uidMap);
766 QCOMPARE(model->rowCount(msgListA), 0);
767 cClient(t.mk("SELECT a\r\n"));
768 cServer("* 4 EXISTS\r\n"
769 "* OK [UIDVALIDITY 666] .\r\n"
770 "* OK [UIDNEXT 16] .\r\n");
771 cServer(t.last("OK selected\r\n"));
772 cClient(t.mk("UID SEARCH UID 15:*\r\n"));
773 cServer("* SEARCH 42\r\n");
774 cServer(t.last("OK uids\r\n"));
775 cClient(t.mk("FETCH 1:4 (FLAGS)\r\n"));
776 cServer("* 1 FETCH (FLAGS (x))\r\n"
777 "* 2 FETCH (FLAGS (y))\r\n"
778 "* 5 EXISTS\r\n"
779 "* 3 FETCH (FLAGS (z))\r\n"
780 "* 4 FETCH (FLAGS (fn))\r\n"
781 // notice that we're adding unsolicited data for a new message here
782 "* 5 FETCH (FLAGS (blah))\r\n");
783 // The new arrival shall not be present in the *persistent cache* at this point -- that's not an atomic update (yet)
784 QCOMPARE(model->cache()->mailboxSyncState("a"), sync);
785 QByteArray fetchTermination = t.last("OK fetch\r\n");
786 cClient(t.mk("UID FETCH 43:* (FLAGS)\r\n"));
787 cServer(fetchTermination);
788 cServer("* 5 FETCH (FLAGS (gah) UID 60)\r\n" + t.last("OK new discovery\r\n"));
789 sync.setExists(5);
790 sync.setUidNext(61);
791 sync.setUnSeenCount(5);
792 sync.setRecent(0);
793 uidMap << 42 << 60;
794 cEmpty();
795 // At this point, the cache shall be up-to-speed again
796 QCOMPARE(model->cache()->mailboxSyncState("a"), sync);
797 QCOMPARE(static_cast<int>(model->cache()->mailboxSyncState("a").exists()), uidMap.size());
798 QCOMPARE(model->cache()->uidMapping("a"), uidMap);
799 QCOMPARE(model->cache()->msgFlags("a", 6), QStringList() << "x");
800 QCOMPARE(model->cache()->msgFlags("a", 9), QStringList() << "y");
801 QCOMPARE(model->cache()->msgFlags("a", 10), QStringList() << "z");
802 QCOMPARE(model->cache()->msgFlags("a", 42), QStringList() << "fn");
803 QCOMPARE(model->cache()->msgFlags("a", 60), QStringList() << "gah");
804 justKeepTask();
809 void ImapModelObtainSynchronizedMailboxTest::testCacheExpunges()
811 helperCacheExpunges(WITHOUT_ESEARCH);
814 void ImapModelObtainSynchronizedMailboxTest::testCacheExpunges_ESearch()
816 helperCacheExpunges(WITH_ESEARCH);
819 /** @short Test synchronization of a mailbox with on-disk cache when one message got deleted */
820 void ImapModelObtainSynchronizedMailboxTest::helperCacheExpunges(const ESearchMode esearch)
822 if (esearch == WITH_ESEARCH) {
823 FakeCapabilitiesInjector injector(model);
824 injector.injectCapability("ESEARCH");
826 Imap::Mailbox::SyncState sync;
827 sync.setExists(6);
828 sync.setUidValidity(666);
829 sync.setUidNext(15);
830 QList<uint> uidMap;
831 uidMap << 6 << 9 << 10 << 11 << 12 << 14;
832 model->cache()->setMailboxSyncState("a", sync);
833 model->cache()->setUidMapping("a", uidMap);
834 model->cache()->setMsgFlags("a", 9, QStringList() << "foo");
835 QCOMPARE(model->rowCount(msgListA), 0);
836 cClient(t.mk("SELECT a\r\n"));
837 cServer("* 5 EXISTS\r\n"
838 "* OK [UIDVALIDITY 666] .\r\n"
839 "* OK [UIDNEXT 15] .\r\n");
840 cServer(t.last("OK selected\r\n"));
841 if (esearch == WITH_ESEARCH) {
842 cClient(t.mk("UID SEARCH RETURN (ALL) ALL\r\n"));
843 cServer(QByteArray("* ESEARCH (TAG ") + t.last() + ") UID ALL 6,10:12,14\r\n");
844 } else {
845 cClient(t.mk("UID SEARCH ALL\r\n"));
846 cServer("* SEARCH 6 10 11 12 14\r\n");
848 cServer(t.last("OK uids\r\n"));
849 uidMap.removeAt(1);
850 sync.setExists(5);
851 sync.setUnSeenCount(5);
852 sync.setRecent(0);
853 cClient(t.mk("FETCH 1:5 (FLAGS)\r\n"));
854 cServer("* 1 FETCH (FLAGS (x))\r\n"
855 "* 2 FETCH (FLAGS (z))\r\n"
856 "* 3 FETCH (FLAGS (a))\r\n"
857 "* 4 FETCH (FLAGS (b))\r\n"
858 "* 5 FETCH (FLAGS (c))\r\n"
860 cServer(t.last("OK fetch\r\n"));
861 cEmpty();
862 QCOMPARE(model->cache()->mailboxSyncState("a"), sync);
863 QCOMPARE(static_cast<int>(model->cache()->mailboxSyncState("a").exists()), uidMap.size());
864 QCOMPARE(model->cache()->uidMapping("a"), uidMap);
865 QCOMPARE(model->cache()->msgFlags("a", 6), QStringList() << "x");
866 QCOMPARE(model->cache()->msgFlags("a", 10), QStringList() << "z");
867 QCOMPARE(model->cache()->msgFlags("a", 11), QStringList() << "a");
868 QCOMPARE(model->cache()->msgFlags("a", 12), QStringList() << "b");
869 QCOMPARE(model->cache()->msgFlags("a", 14), QStringList() << "c");
870 // Flags for the deleted message shall be gone
871 QCOMPARE(model->cache()->msgFlags("a", 9), QStringList());
872 justKeepTask();
875 /** @short Test two expunges, once during normal sync and then once again during the UID syncing */
876 void ImapModelObtainSynchronizedMailboxTest::testCacheExpungesDuringUid()
878 Imap::Mailbox::SyncState sync;
879 sync.setExists(6);
880 sync.setUidValidity(666);
881 sync.setUidNext(15);
882 QList<uint> uidMap;
883 uidMap << 6 << 9 << 10 << 11 << 12 << 14;
884 model->cache()->setMailboxSyncState("a", sync);
885 model->cache()->setUidMapping("a", uidMap);
886 model->cache()->setMsgFlags("a", 9, QStringList() << "foo");
887 QCOMPARE(model->rowCount(msgListA), 0);
888 cClient(t.mk("SELECT a\r\n"));
889 cServer("* 5 EXISTS\r\n"
890 "* OK [UIDVALIDITY 666] .\r\n"
891 "* OK [UIDNEXT 15] .\r\n");
892 cServer(t.last("OK selected\r\n"));
893 cClient(t.mk("UID SEARCH ALL\r\n"));
894 cServer("* 4 EXPUNGE\r\n* SEARCH 6 10 11 14\r\n");
895 cServer(t.last("OK uids\r\n"));
896 uidMap.removeAt(1);
897 uidMap.removeAt(3);
898 sync.setExists(4);
899 sync.setUnSeenCount(4);
900 sync.setRecent(0);
901 cClient(t.mk("FETCH 1:4 (FLAGS)\r\n"));
902 cServer("* 1 FETCH (FLAGS (x))\r\n"
903 "* 2 FETCH (FLAGS (z))\r\n"
904 "* 3 FETCH (FLAGS (a))\r\n"
905 "* 4 FETCH (FLAGS (c))\r\n"
907 cServer(t.last("OK fetch\r\n"));
908 cEmpty();
909 QCOMPARE(model->cache()->mailboxSyncState("a"), sync);
910 QCOMPARE(static_cast<int>(model->cache()->mailboxSyncState("a").exists()), uidMap.size());
911 QCOMPARE(model->cache()->uidMapping("a"), uidMap);
912 QCOMPARE(model->cache()->msgFlags("a", 6), QStringList() << "x");
913 QCOMPARE(model->cache()->msgFlags("a", 10), QStringList() << "z");
914 QCOMPARE(model->cache()->msgFlags("a", 11), QStringList() << "a");
915 QCOMPARE(model->cache()->msgFlags("a", 14), QStringList() << "c");
916 // Flags for the deleted messages shall be gone
917 QCOMPARE(model->cache()->msgFlags("a", 9), QStringList());
918 QCOMPARE(model->cache()->msgFlags("a", 12), QStringList());
919 justKeepTask();
922 /** @short Test two expunges, once during normal sync and then once again immediately after the UID syncing */
923 void ImapModelObtainSynchronizedMailboxTest::testCacheExpungesDuringUid2()
925 Imap::Mailbox::SyncState sync;
926 sync.setExists(6);
927 sync.setUidValidity(666);
928 sync.setUidNext(15);
929 QList<uint> uidMap;
930 uidMap << 6 << 9 << 10 << 11 << 12 << 14;
931 model->cache()->setMailboxSyncState("a", sync);
932 model->cache()->setUidMapping("a", uidMap);
933 model->cache()->setMsgFlags("a", 9, QStringList() << "foo");
934 QCOMPARE(model->rowCount(msgListA), 0);
935 cClient(t.mk("SELECT a\r\n"));
936 cServer("* 5 EXISTS\r\n"
937 "* OK [UIDVALIDITY 666] .\r\n"
938 "* OK [UIDNEXT 15] .\r\n");
939 cServer(t.last("OK selected\r\n"));
940 cClient(t.mk("UID SEARCH ALL\r\n"));
941 cServer("* SEARCH 6 10 11 12 14\r\n* 4 EXPUNGE\r\n");
942 cServer(t.last("OK uids\r\n"));
943 uidMap.removeAt(1);
944 uidMap.removeAt(3);
945 sync.setExists(4);
946 sync.setUnSeenCount(4);
947 sync.setRecent(0);
948 cClient(t.mk("FETCH 1:4 (FLAGS)\r\n"));
949 cServer("* 1 FETCH (FLAGS (x))\r\n"
950 "* 2 FETCH (FLAGS (z))\r\n"
951 "* 3 FETCH (FLAGS (a))\r\n"
952 "* 4 FETCH (FLAGS (c))\r\n"
954 cServer(t.last("OK fetch\r\n"));
955 cEmpty();
956 QCOMPARE(model->cache()->mailboxSyncState("a"), sync);
957 QCOMPARE(static_cast<int>(model->cache()->mailboxSyncState("a").exists()), uidMap.size());
958 QCOMPARE(model->cache()->uidMapping("a"), uidMap);
959 QCOMPARE(model->cache()->msgFlags("a", 6), QStringList() << "x");
960 QCOMPARE(model->cache()->msgFlags("a", 10), QStringList() << "z");
961 QCOMPARE(model->cache()->msgFlags("a", 11), QStringList() << "a");
962 QCOMPARE(model->cache()->msgFlags("a", 14), QStringList() << "c");
963 // Flags for the deleted messages shall be gone
964 QCOMPARE(model->cache()->msgFlags("a", 9), QStringList());
965 QCOMPARE(model->cache()->msgFlags("a", 12), QStringList());
966 justKeepTask();
969 /** @short Test two expunges, once during normal sync and then once again before the UID syncing */
970 void ImapModelObtainSynchronizedMailboxTest::testCacheExpungesDuringSelect()
972 Imap::Mailbox::SyncState sync;
973 sync.setExists(6);
974 sync.setUidValidity(666);
975 sync.setUidNext(15);
976 QList<uint> uidMap;
977 uidMap << 6 << 9 << 10 << 11 << 12 << 14;
978 model->cache()->setMailboxSyncState("a", sync);
979 model->cache()->setUidMapping("a", uidMap);
980 model->cache()->setMsgFlags("a", 9, QStringList() << "foo");
981 QCOMPARE(model->rowCount(msgListA), 0);
982 cClient(t.mk("SELECT a\r\n"));
983 cServer("* 5 EXISTS\r\n"
984 "* OK [UIDVALIDITY 666] .\r\n"
985 "* OK [UIDNEXT 15] .\r\n"
986 "* 4 EXPUNGE\r\n");
987 cServer(t.last("OK selected\r\n"));
988 cClient(t.mk("UID SEARCH ALL\r\n"));
989 cServer("* SEARCH 6 10 11 14\r\n");
990 cServer(t.last("OK uids\r\n"));
991 uidMap.removeAt(1);
992 uidMap.removeAt(3);
993 sync.setExists(4);
994 cClient(t.mk("FETCH 1:4 (FLAGS)\r\n"));
995 cServer("* 1 FETCH (FLAGS (x))\r\n"
996 "* 2 FETCH (FLAGS (z))\r\n"
997 "* 3 FETCH (FLAGS (a))\r\n"
998 "* 4 FETCH (FLAGS (c))\r\n"
1000 cServer(t.last("OK fetch\r\n"));
1001 cEmpty();
1002 sync.setUnSeenCount(4);
1003 sync.setRecent(0);
1004 QCOMPARE(model->cache()->mailboxSyncState("a"), sync);
1005 QCOMPARE(static_cast<int>(model->cache()->mailboxSyncState("a").exists()), uidMap.size());
1006 QCOMPARE(model->cache()->uidMapping("a"), uidMap);
1007 QCOMPARE(model->cache()->msgFlags("a", 6), QStringList() << "x");
1008 QCOMPARE(model->cache()->msgFlags("a", 10), QStringList() << "z");
1009 QCOMPARE(model->cache()->msgFlags("a", 11), QStringList() << "a");
1010 QCOMPARE(model->cache()->msgFlags("a", 14), QStringList() << "c");
1011 // Flags for the deleted messages shall be gone
1012 QCOMPARE(model->cache()->msgFlags("a", 9), QStringList());
1013 QCOMPARE(model->cache()->msgFlags("a", 12), QStringList());
1014 justKeepTask();
1017 /** @short Test two expunges, once during normal sync and then once again when syncing flags */
1018 void ImapModelObtainSynchronizedMailboxTest::testCacheExpungesDuringFlags()
1020 Imap::Mailbox::SyncState sync;
1021 sync.setExists(6);
1022 sync.setUidValidity(666);
1023 sync.setUidNext(15);
1024 sync.setUnSeenCount(6);
1025 sync.setRecent(0);
1026 QList<uint> uidMap;
1027 uidMap << 6 << 9 << 10 << 11 << 12 << 14;
1028 model->cache()->setMailboxSyncState("a", sync);
1029 model->cache()->setUidMapping("a", uidMap);
1030 model->cache()->setMsgFlags("a", 9, QStringList() << "foo");
1031 QCOMPARE(model->rowCount(msgListA), 0);
1032 cClient(t.mk("SELECT a\r\n"));
1033 cServer("* 5 EXISTS\r\n"
1034 "* OK [UIDVALIDITY 666] .\r\n"
1035 "* OK [UIDNEXT 15] .\r\n");
1036 cServer(t.last("OK selected\r\n"));
1037 cClient(t.mk("UID SEARCH ALL\r\n"));
1038 cServer("* SEARCH 6 10 11 12 14\r\n");
1039 cServer(t.last("OK uids\r\n"));
1040 uidMap.removeAt(1);
1041 sync.setExists(5);
1042 cClient(t.mk("FETCH 1:5 (FLAGS)\r\n"));
1043 cServer("* 1 FETCH (FLAGS (x))\r\n"
1044 "* 2 FETCH (FLAGS (z))\r\n"
1045 "* 4 EXPUNGE\r\n"
1046 "* 3 FETCH (FLAGS (a))\r\n"
1047 "* 4 FETCH (FLAGS (c))\r\n"
1049 cServer(t.last("OK fetch\r\n"));
1050 uidMap.removeAt(3);
1051 sync.setExists(4);
1052 sync.setUnSeenCount(4);
1053 cEmpty();
1054 QCOMPARE(model->cache()->mailboxSyncState("a"), sync);
1055 QCOMPARE(static_cast<int>(model->cache()->mailboxSyncState("a").exists()), uidMap.size());
1056 QCOMPARE(model->cache()->uidMapping("a"), uidMap);
1057 QCOMPARE(model->cache()->msgFlags("a", 6), QStringList() << "x");
1058 QCOMPARE(model->cache()->msgFlags("a", 10), QStringList() << "z");
1059 QCOMPARE(model->cache()->msgFlags("a", 11), QStringList() << "a");
1060 QCOMPARE(model->cache()->msgFlags("a", 14), QStringList() << "c");
1061 // Flags for the deleted messages shall be gone
1062 QCOMPARE(model->cache()->msgFlags("a", 9), QStringList());
1063 QCOMPARE(model->cache()->msgFlags("a", 12), QStringList());
1064 justKeepTask();
1067 /** @short The mailbox contains one new message which gets deleted before we got a chance to ask for its UID */
1068 void ImapModelObtainSynchronizedMailboxTest::testCacheArrivalsImmediatelyDeleted()
1070 Imap::Mailbox::SyncState sync;
1071 sync.setExists(3);
1072 sync.setUidValidity(666);
1073 sync.setUidNext(15);
1074 sync.setUnSeenCount(3);
1075 sync.setRecent(0);
1076 QList<uint> uidMap;
1077 uidMap << 6 << 9 << 10;
1078 model->cache()->setMailboxSyncState("a", sync);
1079 model->cache()->setUidMapping("a", uidMap);
1080 QCOMPARE(model->rowCount(msgListA), 0);
1081 cClient(t.mk("SELECT a\r\n"));
1082 cServer("* 4 EXISTS\r\n"
1083 "* OK [UIDVALIDITY 666] .\r\n"
1084 "* OK [UIDNEXT 16] .\r\n");
1085 cServer(t.last("OK selected\r\n"));
1086 cClient(t.mk("UID SEARCH UID 15:*\r\n"));
1087 cServer("* 4 EXPUNGE\r\n* SEARCH \r\n");
1088 cServer(t.last("OK uids\r\n"));
1089 // We know that at least one message has arrived, so the UIDNEXT *must* have changed.
1090 sync.setUidNext(16);
1091 sync.setExists(3);
1092 cClient(t.mk("FETCH 1:3 (FLAGS)\r\n"));
1093 cServer("* 1 FETCH (FLAGS (x))\r\n"
1094 "* 2 FETCH (FLAGS (y))\r\n"
1095 "* 3 FETCH (FLAGS (z))\r\n");
1096 cServer(t.last("OK fetch\r\n"));
1097 cEmpty();
1098 QCOMPARE(model->cache()->mailboxSyncState("a"), sync);
1099 QCOMPARE(static_cast<int>(model->cache()->mailboxSyncState("a").exists()), uidMap.size());
1100 QCOMPARE(model->cache()->uidMapping("a"), uidMap);
1101 QCOMPARE(model->cache()->msgFlags("a", 6), QStringList() << "x");
1102 QCOMPARE(model->cache()->msgFlags("a", 9), QStringList() << "y");
1103 QCOMPARE(model->cache()->msgFlags("a", 10), QStringList() << "z");
1104 QCOMPARE(model->cache()->msgFlags("a", 15), QStringList());
1105 QCOMPARE(model->cache()->msgFlags("a", 16), QStringList());
1106 justKeepTask();
1109 /** @short The mailbox contains one new message. One of the old ones gets deleted while fetching UIDs. */
1110 void ImapModelObtainSynchronizedMailboxTest::testCacheArrivalsOldDeleted()
1112 Imap::Mailbox::SyncState sync;
1113 sync.setExists(3);
1114 sync.setUidValidity(666);
1115 sync.setUidNext(15);
1116 sync.setUnSeenCount(3);
1117 sync.setRecent(0);
1118 QList<uint> uidMap;
1119 uidMap << 6 << 9 << 10;
1120 model->cache()->setMailboxSyncState("a", sync);
1121 model->cache()->setUidMapping("a", uidMap);
1122 QCOMPARE(model->rowCount(msgListA), 0);
1123 cClient(t.mk("SELECT a\r\n"));
1124 cServer("* 4 EXISTS\r\n"
1125 "* OK [UIDVALIDITY 666] .\r\n"
1126 "* OK [UIDNEXT 16] .\r\n");
1127 cServer(t.last("OK selected\r\n"));
1128 cClient(t.mk("UID SEARCH UID 15:*\r\n"));
1129 cServer("* 3 EXPUNGE\r\n* SEARCH 33\r\n");
1130 cServer(t.last("OK uids\r\n"));
1131 sync.setUidNext(34);
1132 uidMap.removeAt(2);
1133 uidMap << 33;
1134 sync.setExists(3);
1135 cClient(t.mk("FETCH 1:3 (FLAGS)\r\n"));
1136 cServer("* 1 FETCH (FLAGS (x))\r\n"
1137 "* 2 FETCH (FLAGS (y))\r\n"
1138 "* 3 FETCH (FLAGS (blah))\r\n");
1139 cServer(t.last("OK fetch\r\n"));
1140 cEmpty();
1141 QCOMPARE(model->cache()->mailboxSyncState("a"), sync);
1142 QCOMPARE(static_cast<int>(model->cache()->mailboxSyncState("a").exists()), uidMap.size());
1143 QCOMPARE(model->cache()->uidMapping("a"), uidMap);
1144 QCOMPARE(model->cache()->msgFlags("a", 6), QStringList() << "x");
1145 QCOMPARE(model->cache()->msgFlags("a", 9), QStringList() << "y");
1146 QCOMPARE(model->cache()->msgFlags("a", 33), QStringList() << "blah");
1147 QCOMPARE(model->cache()->msgFlags("a", 10), QStringList());
1148 QCOMPARE(model->cache()->msgFlags("a", 15), QStringList());
1149 QCOMPARE(model->cache()->msgFlags("a", 16), QStringList());
1150 justKeepTask();
1153 /** @short Mailbox is said to contain some new messages. While performing the sync, many events occur. */
1154 void ImapModelObtainSynchronizedMailboxTest::testCacheArrivalsThenDynamic()
1156 Imap::Mailbox::SyncState sync;
1157 sync.setExists(10);
1158 sync.setUidValidity(666);
1159 sync.setUidNext(100);
1160 QList<uint> uidMap;
1161 for (uint i = 1; i <= sync.exists(); ++i)
1162 uidMap << i;
1163 model->cache()->setMailboxSyncState("a", sync);
1164 model->cache()->setUidMapping("a", uidMap);
1165 QCOMPARE(model->rowCount(msgListA), 0);
1166 cClient(t.mk("SELECT a\r\n"));
1167 // The sync indicates that there are five more messages and nothing else
1168 cServer("* 15 EXISTS\r\n"
1169 "* OK [UIDVALIDITY 666] .\r\n"
1170 "* OK [UIDNEXT 105] .\r\n");
1171 cServer(t.last("OK selected\r\n"));
1172 cServer(
1173 // One more message arrives
1174 "* 16 EXISTS\r\n"
1175 // The last of the previously known messages gets deleted
1176 "* 10 EXPUNGE\r\n"
1177 // The very first arrival (which happened between the disconnect and this sync) goes away
1178 "* 10 EXPUNGE\r\n"
1180 cClient(t.mk("UID SEARCH UID 100:*\r\n"));
1181 // One of the old messages get deleted
1182 cServer("* 3 EXPUNGE\r\n");
1183 cServer("* SEARCH 102 103 104 105 106\r\n");
1184 cServer(t.last("OK uids\r\n"));
1185 // That's the last of the previously known, the first "10 EXPUNGE"
1186 uidMap.removeAt(9);
1187 // the second "10 EXPUNGE" is not reflected here at all, as it's for the new arrivals
1188 // This one is for the "3 EXPUNGE"
1189 uidMap.removeAt(2);
1190 uidMap << 102 << 103 << 104 << 105 << 106;
1191 sync.setUidNext(107);
1192 sync.setExists(13);
1193 cClient(t.mk("FETCH 1:13 (FLAGS)\r\n"));
1194 cServer("* 2 EXPUNGE\r\n");
1195 uidMap.removeAt(1);
1196 cServer("* 12 EXPUNGE\r\n");
1197 uidMap.removeAt(11);
1198 sync.setExists(11);
1199 cServer("* 1 FETCH (FLAGS (x))\r\n"
1200 "* 2 FETCH (FLAGS (y))\r\n"
1201 "* 3 FETCH (FLAGS (z))\r\n"
1202 "* 4 FETCH (FLAGS (a))\r\n"
1203 "* 5 FETCH (FLAGS (b))\r\n"
1204 "* 6 FETCH (FLAGS (c))\r\n"
1205 "* 7 FETCH (FLAGS (d))\r\n"
1206 "* 8 FETCH (FLAGS (x102))\r\n"
1207 "* 9 FETCH (FLAGS (x103))\r\n"
1208 "* 10 FETCH (FLAGS (x104))\r\n"
1209 "* 11 FETCH (FLAGS (x105))\r\n");
1210 // Add two, delete the last of them
1211 cServer("* 13 EXISTS\r\n* 13 EXPUNGE\r\n");
1212 sync.setExists(12);
1213 cServer(t.last("OK fetch\r\n"));
1214 cClient(t.mk("UID FETCH 107:* (FLAGS)\r\n"));
1215 cServer("* 12 FETCH (UID 109 FLAGS (last))\r\n");
1216 uidMap << 109;
1217 cServer(t.last("OK uid fetch flags done\r\n"));
1218 cEmpty();
1219 sync.setUidNext(110);
1220 sync.setUnSeenCount(12);
1221 sync.setRecent(0);
1222 QCOMPARE(model->cache()->mailboxSyncState("a"), sync);
1223 QCOMPARE(static_cast<int>(model->cache()->mailboxSyncState("a").exists()), uidMap.size());
1224 QCOMPARE(model->cache()->uidMapping("a"), uidMap);
1225 // Known UIDs at this point: 1, 4, 5, 6, 7, 8, 9, 102, 103, 104, 105, 109
1226 QCOMPARE(model->cache()->msgFlags("a", 1), QStringList() << "x");
1227 QCOMPARE(model->cache()->msgFlags("a", 4), QStringList() << "y");
1228 QCOMPARE(model->cache()->msgFlags("a", 5), QStringList() << "z");
1229 QCOMPARE(model->cache()->msgFlags("a", 6), QStringList() << "a");
1230 QCOMPARE(model->cache()->msgFlags("a", 7), QStringList() << "b");
1231 QCOMPARE(model->cache()->msgFlags("a", 8), QStringList() << "c");
1232 QCOMPARE(model->cache()->msgFlags("a", 9), QStringList() << "d");
1233 QCOMPARE(model->cache()->msgFlags("a", 102), QStringList() << "x102");
1234 QCOMPARE(model->cache()->msgFlags("a", 103), QStringList() << "x103");
1235 QCOMPARE(model->cache()->msgFlags("a", 104), QStringList() << "x104");
1236 QCOMPARE(model->cache()->msgFlags("a", 105), QStringList() << "x105");
1237 QCOMPARE(model->cache()->msgFlags("a", 109), QStringList() << "last");
1238 QCOMPARE(model->cache()->msgFlags("a", 2), QStringList());
1239 QCOMPARE(model->cache()->msgFlags("a", 3), QStringList());
1240 QCOMPARE(model->cache()->msgFlags("a", 10), QStringList());
1241 QCOMPARE(model->cache()->msgFlags("a", 100), QStringList());
1242 QCOMPARE(model->cache()->msgFlags("a", 101), QStringList());
1243 QCOMPARE(model->cache()->msgFlags("a", 106), QStringList());
1244 QCOMPARE(model->cache()->msgFlags("a", 107), QStringList());
1245 QCOMPARE(model->cache()->msgFlags("a", 108), QStringList());
1246 QCOMPARE(model->cache()->msgFlags("a", 110), QStringList());
1247 justKeepTask();
1250 /** @short Mailbox is said to have lost some messages. While performing the sync, many events occur. */
1251 void ImapModelObtainSynchronizedMailboxTest::testCacheDeletionsThenDynamic()
1253 Imap::Mailbox::SyncState sync;
1254 sync.setExists(10);
1255 sync.setUidValidity(666);
1256 sync.setUidNext(100);
1257 QList<uint> uidMap;
1258 for (uint i = 1; i <= sync.exists(); ++i)
1259 uidMap << i;
1260 model->cache()->setMailboxSyncState("a", sync);
1261 model->cache()->setUidMapping("a", uidMap);
1262 QCOMPARE(model->rowCount(msgListA), 0);
1263 cClient(t.mk("SELECT a\r\n"));
1264 // The sync indicates that there are five more messages and nothing else
1265 cServer("* 5 EXISTS\r\n"
1266 "* OK [UIDVALIDITY 666] .\r\n"
1267 "* OK [UIDNEXT 100] .\r\n");
1268 cServer(t.last("OK selected\r\n"));
1269 // Let's say that the server's idea about the UIDs is now (1 3 4 6 9)
1270 cServer(
1271 // Five more messages arrives; that means that the UIDNEXT is now at least on 105
1272 "* 10 EXISTS\r\n"
1273 // The last of the previously known messages gets deleted
1274 "* 5 EXPUNGE\r\n"
1275 // Right now, the UIDs are (1 3 4 6 ? ? ? ? ?)
1276 // And the first of the new arrivals will be gone, too.
1277 "* 5 EXPUNGE\r\n"
1278 // That makes four new messages when compared to the original state.
1279 // Right now, the UIDs are (1 3 4 6 ? ? ? ?)
1281 cClient(t.mk("UID SEARCH ALL\r\n"));
1282 // One of the old messages get deleted (UID 4)
1283 cServer("* 3 EXPUNGE\r\n");
1284 // Right now, the UIDs are (1 3 6 ? ? ? ?)
1285 cServer("* SEARCH 1 3 6 101 102 103 104\r\n");
1286 cServer(t.last("OK uids\r\n"));
1288 uidMap.clear();
1289 uidMap << 1 << 3 << 6 << 101 << 102 << 103 << 104;
1290 sync.setUidNext(105);
1291 sync.setExists(7);
1292 cClient(t.mk("FETCH 1:7 (FLAGS)\r\n"));
1293 cServer("* 2 EXPUNGE\r\n");
1294 uidMap.removeAt(1);
1295 sync.setExists(6);
1296 cServer("* 1 FETCH (FLAGS (f1))\r\n"
1297 "* 2 FETCH (FLAGS (f6))\r\n"
1298 "* 3 FETCH (FLAGS (f101))\r\n"
1299 "* 4 FETCH (FLAGS (f102))\r\n"
1300 "* 5 FETCH (FLAGS (f103))\r\n"
1301 "* 6 FETCH (FLAGS (f104))\r\n");
1302 // Add two, delete the last of them
1303 cServer("* 8 EXISTS\r\n* 8 EXPUNGE\r\n");
1304 sync.setExists(7);
1305 cServer(t.last("OK fetch\r\n"));
1306 cClient(t.mk("UID FETCH 105:* (FLAGS)\r\n"));
1307 cServer("* 7 FETCH (UID 109 FLAGS (last))\r\n");
1308 uidMap << 109;
1309 cServer(t.last("OK uid fetch flags done\r\n"));
1310 cEmpty();
1311 sync.setUidNext(110);
1312 sync.setUnSeenCount(7);
1313 sync.setRecent(0);
1314 QCOMPARE(model->cache()->mailboxSyncState("a"), sync);
1315 QCOMPARE(static_cast<int>(model->cache()->mailboxSyncState("a").exists()), uidMap.size());
1316 QCOMPARE(model->cache()->uidMapping("a"), uidMap);
1317 // UIDs: 1 6 101 102 103 104 109
1318 QCOMPARE(model->cache()->msgFlags("a", 1), QStringList() << "f1");
1319 QCOMPARE(model->cache()->msgFlags("a", 6), QStringList() << "f6");
1320 QCOMPARE(model->cache()->msgFlags("a", 101), QStringList() << "f101");
1321 QCOMPARE(model->cache()->msgFlags("a", 102), QStringList() << "f102");
1322 QCOMPARE(model->cache()->msgFlags("a", 103), QStringList() << "f103");
1323 QCOMPARE(model->cache()->msgFlags("a", 104), QStringList() << "f104");
1324 QCOMPARE(model->cache()->msgFlags("a", 109), QStringList() << "last");
1325 for (int i=2; i < 6; ++i)
1326 QCOMPARE(model->cache()->msgFlags("a", i), QStringList());
1327 for (int i=7; i < 101; ++i)
1328 QCOMPARE(model->cache()->msgFlags("a", i), QStringList());
1329 for (int i=105; i < 109; ++i)
1330 QCOMPARE(model->cache()->msgFlags("a", i), QStringList());
1331 for (int i=110; i < 120; ++i)
1332 QCOMPARE(model->cache()->msgFlags("a", i), QStringList());
1333 justKeepTask();
1336 /** @short Test what happens when HIGHESTMODSEQ says there are no changes */
1337 void ImapModelObtainSynchronizedMailboxTest::testCondstoreNoChanges()
1339 FakeCapabilitiesInjector injector(model);
1340 injector.injectCapability("CONDSTORE");
1341 Imap::Mailbox::SyncState sync;
1342 sync.setExists(3);
1343 sync.setUidValidity(666);
1344 sync.setUidNext(15);
1345 sync.setHighestModSeq(33);
1346 sync.setUnSeenCount(3);
1347 sync.setRecent(0);
1348 QList<uint> uidMap;
1349 uidMap << 6 << 9 << 10;
1350 model->cache()->setMailboxSyncState("a", sync);
1351 model->cache()->setUidMapping("a", uidMap);
1352 model->cache()->setMsgFlags("a", 6, QStringList() << "x");
1353 model->cache()->setMsgFlags("a", 9, QStringList() << "y");
1354 model->cache()->setMsgFlags("a", 10, QStringList() << "z");
1355 model->resyncMailbox(idxA);
1356 cClient(t.mk("SELECT a (CONDSTORE)\r\n"));
1357 cServer("* 3 EXISTS\r\n"
1358 "* OK [UIDVALIDITY 666] .\r\n"
1359 "* OK [UIDNEXT 15] .\r\n"
1360 "* OK [HIGHESTMODSEQ 33] .\r\n"
1362 cServer(t.last("OK selected\r\n"));
1363 cEmpty();
1364 QCOMPARE(model->cache()->mailboxSyncState("a"), sync);
1365 QCOMPARE(static_cast<int>(model->cache()->mailboxSyncState("a").exists()), uidMap.size());
1366 QCOMPARE(model->cache()->uidMapping("a"), uidMap);
1367 QCOMPARE(model->cache()->msgFlags("a", 6), QStringList() << "x");
1368 QCOMPARE(model->cache()->msgFlags("a", 9), QStringList() << "y");
1369 QCOMPARE(model->cache()->msgFlags("a", 10), QStringList() << "z");
1370 justKeepTask();
1373 /** @short Test changed HIGHESTMODSEQ */
1374 void ImapModelObtainSynchronizedMailboxTest::testCondstoreChangedFlags()
1376 FakeCapabilitiesInjector injector(model);
1377 injector.injectCapability("CONDSTORE");
1378 Imap::Mailbox::SyncState sync;
1379 sync.setExists(3);
1380 sync.setUidValidity(666);
1381 sync.setUidNext(15);
1382 sync.setHighestModSeq(33);
1383 sync.setUnSeenCount(3);
1384 sync.setRecent(0);
1385 QList<uint> uidMap;
1386 uidMap << 6 << 9 << 10;
1387 model->cache()->setMailboxSyncState("a", sync);
1388 model->cache()->setUidMapping("a", uidMap);
1389 model->cache()->setMsgFlags("a", 6, QStringList() << "x");
1390 model->cache()->setMsgFlags("a", 9, QStringList() << "y");
1391 model->cache()->setMsgFlags("a", 10, QStringList() << "z");
1392 model->resyncMailbox(idxA);
1393 cClient(t.mk("SELECT a (CONDSTORE)\r\n"));
1394 cServer("* 3 EXISTS\r\n"
1395 "* OK [UIDVALIDITY 666] .\r\n"
1396 "* OK [UIDNEXT 15] .\r\n"
1397 "* OK [HIGHESTMODSEQ 666] .\r\n"
1399 cServer(t.last("OK selected\r\n"));
1400 cClient(t.mk("FETCH 1:3 (FLAGS) (CHANGEDSINCE 33)\r\n"));
1401 cServer("* 3 FETCH (FLAGS (f101 \\seen))\r\n");
1402 cServer(t.last("OK fetched\r\n"));
1403 cEmpty();
1404 sync.setHighestModSeq(666);
1405 sync.setUnSeenCount(2);
1406 QCOMPARE(model->cache()->mailboxSyncState("a"), sync);
1407 QCOMPARE(static_cast<int>(model->cache()->mailboxSyncState("a").exists()), uidMap.size());
1408 QCOMPARE(model->cache()->uidMapping("a"), uidMap);
1409 QCOMPARE(model->cache()->msgFlags("a", 6), QStringList() << "x");
1410 QCOMPARE(model->cache()->msgFlags("a", 9), QStringList() << "y");
1411 QCOMPARE(model->cache()->msgFlags("a", 10), QStringList() << "\\Seen" << "f101");
1412 justKeepTask();
1415 /** @short Test constant HIGHESTMODSEQ and more EXISTS */
1416 void ImapModelObtainSynchronizedMailboxTest::testCondstoreErrorExists()
1418 FakeCapabilitiesInjector injector(model);
1419 injector.injectCapability("CONDSTORE");
1420 Imap::Mailbox::SyncState sync;
1421 sync.setExists(3);
1422 sync.setUidValidity(666);
1423 sync.setUidNext(15);
1424 sync.setHighestModSeq(33);
1425 sync.setUnSeenCount(3);
1426 sync.setRecent(0);
1427 QList<uint> uidMap;
1428 uidMap << 6 << 9 << 10;
1429 model->cache()->setMailboxSyncState("a", sync);
1430 model->cache()->setUidMapping("a", uidMap);
1431 model->cache()->setMsgFlags("a", 6, QStringList() << "x");
1432 model->cache()->setMsgFlags("a", 9, QStringList() << "y");
1433 model->cache()->setMsgFlags("a", 10, QStringList() << "z");
1434 model->resyncMailbox(idxA);
1435 cClient(t.mk("SELECT a (CONDSTORE)\r\n"));
1436 cServer("* 4 EXISTS\r\n"
1437 "* OK [UIDVALIDITY 666] .\r\n"
1438 "* OK [UIDNEXT 15] .\r\n"
1439 "* OK [HIGHESTMODSEQ 33] .\r\n"
1441 // yes, it's buggy. The goal here is to make sure that even an increased EXISTS is enough
1442 // to disable CHANGEDSINCE
1443 cServer(t.last("OK selected\r\n"));
1444 cClient(t.mk("UID SEARCH ALL\r\n"));
1445 cServer(QByteArray("* SEARCH 6 9 10 15\r\n") + t.last("OK uids\r\n"));
1446 cClient(t.mk("FETCH 1:4 (FLAGS)\r\n"));
1447 cServer("* 1 FETCH (FLAGS (x))\r\n"
1448 "* 2 FETCH (FLAGS (y))\r\n"
1449 "* 3 FETCH (FLAGS (z))\r\n"
1450 "* 4 FETCH (FLAGS (blah))\r\n");
1451 cServer(t.last("OK fetch\r\n"));
1452 cEmpty();
1453 uidMap << 15;
1454 sync.setUidNext(16);
1455 sync.setExists(4);
1456 sync.setUnSeenCount(4);
1457 QCOMPARE(model->cache()->mailboxSyncState("a"), sync);
1458 QCOMPARE(static_cast<int>(model->cache()->mailboxSyncState("a").exists()), uidMap.size());
1459 QCOMPARE(model->cache()->uidMapping("a"), uidMap);
1460 QCOMPARE(model->cache()->msgFlags("a", 6), QStringList() << "x");
1461 QCOMPARE(model->cache()->msgFlags("a", 9), QStringList() << "y");
1462 QCOMPARE(model->cache()->msgFlags("a", 10), QStringList() << "z");
1463 QCOMPARE(model->cache()->msgFlags("a", 15), QStringList() << "blah");
1464 justKeepTask();
1467 /** @short Test constant HIGHESTMODSEQ but changed UIDNEXT */
1468 void ImapModelObtainSynchronizedMailboxTest::testCondstoreErrorUidNext()
1470 FakeCapabilitiesInjector injector(model);
1471 injector.injectCapability("CONDSTORE");
1472 Imap::Mailbox::SyncState sync;
1473 sync.setExists(3);
1474 sync.setUidValidity(666);
1475 sync.setUidNext(15);
1476 sync.setHighestModSeq(33);
1477 sync.setUnSeenCount(3);
1478 sync.setRecent(0);
1479 QList<uint> uidMap;
1480 uidMap << 6 << 9 << 10;
1481 model->cache()->setMailboxSyncState("a", sync);
1482 model->cache()->setUidMapping("a", uidMap);
1483 model->cache()->setMsgFlags("a", 6, QStringList() << "x");
1484 model->cache()->setMsgFlags("a", 9, QStringList() << "y");
1485 model->cache()->setMsgFlags("a", 10, QStringList() << "z");
1486 model->resyncMailbox(idxA);
1487 cClient(t.mk("SELECT a (CONDSTORE)\r\n"));
1488 cServer("* 3 EXISTS\r\n"
1489 "* OK [UIDVALIDITY 666] .\r\n"
1490 "* OK [UIDNEXT 16] .\r\n"
1491 "* OK [HIGHESTMODSEQ 33] .\r\n"
1493 cServer(t.last("OK selected\r\n"));
1494 cClient(t.mk("UID SEARCH ALL\r\n"));
1495 cServer(QByteArray("* SEARCH 6 9 10\r\n") + t.last("OK uids\r\n"));
1496 cClient(t.mk("FETCH 1:3 (FLAGS)\r\n"));
1497 cServer("* 1 FETCH (FLAGS (x))\r\n"
1498 "* 2 FETCH (FLAGS (y))\r\n"
1499 "* 3 FETCH (FLAGS (z \\Recent))\r\n");
1500 cServer(t.last("OK fetch\r\n"));
1501 cEmpty();
1502 sync.setUidNext(16);
1503 sync.setRecent(1);
1504 QCOMPARE(model->cache()->mailboxSyncState("a"), sync);
1505 QCOMPARE(static_cast<int>(model->cache()->mailboxSyncState("a").exists()), uidMap.size());
1506 QCOMPARE(model->cache()->uidMapping("a"), uidMap);
1507 QCOMPARE(model->cache()->msgFlags("a", 6), QStringList() << "x");
1508 QCOMPARE(model->cache()->msgFlags("a", 9), QStringList() << "y");
1509 QCOMPARE(model->cache()->msgFlags("a", 10), QStringList() << "\\Recent" << "z");
1510 justKeepTask();
1513 /** @short Changed UIDVALIDITY shall always lead to full sync, no matter what HIGHESTMODSEQ says */
1514 void ImapModelObtainSynchronizedMailboxTest::testCondstoreUidValidity()
1516 FakeCapabilitiesInjector injector(model);
1517 injector.injectCapability("CONDSTORE");
1518 Imap::Mailbox::SyncState sync;
1519 sync.setExists(3);
1520 sync.setUidValidity(666);
1521 sync.setUidNext(15);
1522 sync.setHighestModSeq(33);
1523 sync.setUnSeenCount(3);
1524 sync.setRecent(0);
1525 QList<uint> uidMap;
1526 uidMap << 6 << 9 << 10;
1527 model->cache()->setMailboxSyncState("a", sync);
1528 model->cache()->setUidMapping("a", uidMap);
1529 model->cache()->setMsgFlags("a", 6, QStringList() << "x");
1530 model->cache()->setMsgFlags("a", 9, QStringList() << "y");
1531 model->cache()->setMsgFlags("a", 10, QStringList() << "z");
1532 model->resyncMailbox(idxA);
1533 cClient(t.mk("SELECT a (CONDSTORE)\r\n"));
1534 cServer("* 3 EXISTS\r\n"
1535 "* OK [UIDVALIDITY 333] .\r\n"
1536 "* OK [UIDNEXT 15] .\r\n"
1537 "* OK [HIGHESTMODSEQ 33] .\r\n"
1539 cServer(t.last("OK selected\r\n"));
1540 cClient(t.mk("UID SEARCH ALL\r\n"));
1541 cServer(QByteArray("* SEARCH 6 9 10\r\n") + t.last("OK uids\r\n"));
1542 cClient(t.mk("FETCH 1:3 (FLAGS)\r\n"));
1543 cServer("* 1 FETCH (FLAGS (x))\r\n"
1544 "* 2 FETCH (FLAGS (y))\r\n"
1545 "* 3 FETCH (FLAGS (z))\r\n");
1546 cServer(t.last("OK fetch\r\n"));
1547 cEmpty();
1548 sync.setUidValidity(333);
1549 QCOMPARE(model->cache()->mailboxSyncState("a"), sync);
1550 QCOMPARE(static_cast<int>(model->cache()->mailboxSyncState("a").exists()), uidMap.size());
1551 QCOMPARE(model->cache()->uidMapping("a"), uidMap);
1552 QCOMPARE(model->cache()->msgFlags("a", 6), QStringList() << "x");
1553 QCOMPARE(model->cache()->msgFlags("a", 9), QStringList() << "y");
1554 QCOMPARE(model->cache()->msgFlags("a", 10), QStringList() << "z");
1555 justKeepTask();
1558 /** @short Test decreased HIGHESTMODSEQ */
1559 void ImapModelObtainSynchronizedMailboxTest::testCondstoreDecreasedHighestModSeq()
1561 FakeCapabilitiesInjector injector(model);
1562 injector.injectCapability("CONDSTORE");
1563 Imap::Mailbox::SyncState sync;
1564 sync.setExists(3);
1565 sync.setUidValidity(666);
1566 sync.setUidNext(15);
1567 sync.setHighestModSeq(33);
1568 sync.setUnSeenCount(3);
1569 sync.setRecent(0);
1570 QList<uint> uidMap;
1571 uidMap << 6 << 9 << 10;
1572 model->cache()->setMailboxSyncState("a", sync);
1573 model->cache()->setUidMapping("a", uidMap);
1574 model->cache()->setMsgFlags("a", 6, QStringList() << "x");
1575 model->cache()->setMsgFlags("a", 9, QStringList() << "y");
1576 model->cache()->setMsgFlags("a", 10, QStringList() << "z");
1577 model->resyncMailbox(idxA);
1578 cClient(t.mk("SELECT a (CONDSTORE)\r\n"));
1579 cServer("* 3 EXISTS\r\n"
1580 "* OK [UIDVALIDITY 666] .\r\n"
1581 "* OK [UIDNEXT 15] .\r\n"
1582 "* OK [HIGHESTMODSEQ 1] .\r\n"
1584 cServer(t.last("OK selected\r\n"));
1585 cClient(t.mk("FETCH 1:3 (FLAGS)\r\n"));
1586 cServer("* 1 FETCH (FLAGS (x1))\r\n"
1587 "* 2 FETCH (FLAGS (x2))\r\n"
1588 "* 3 FETCH (FLAGS (x3))\r\n");
1589 cServer(t.last("OK fetched\r\n"));
1590 cEmpty();
1591 sync.setHighestModSeq(1);
1592 QCOMPARE(model->cache()->mailboxSyncState("a"), sync);
1593 QCOMPARE(static_cast<int>(model->cache()->mailboxSyncState("a").exists()), uidMap.size());
1594 QCOMPARE(model->cache()->uidMapping("a"), uidMap);
1595 QCOMPARE(model->cache()->msgFlags("a", 6), QStringList() << "x1");
1596 QCOMPARE(model->cache()->msgFlags("a", 9), QStringList() << "x2");
1597 QCOMPARE(model->cache()->msgFlags("a", 10), QStringList() << "x3");
1598 justKeepTask();
1602 /** @short Test that we deal with discrepancy between the EXISTS and the number of UIDs in the cache
1603 and that FLAGS are completely re-fetched even in presence of the HIGHESTMODSEQ */
1604 void ImapModelObtainSynchronizedMailboxTest::testCacheDiscrepancyExistsUidsConstantHMS()
1606 helperCacheDiscrepancyExistsUids(true);
1609 /** @short Test that we deal with discrepancy between the EXISTS and the number of UIDs in the cache
1610 and that FLAGS are fetched even if the HIGHESTMODSEQ remains the same */
1611 void ImapModelObtainSynchronizedMailboxTest::testCacheDiscrepancyExistsUidsDifferentHMS()
1613 helperCacheDiscrepancyExistsUids(false);
1616 void ImapModelObtainSynchronizedMailboxTest::helperCacheDiscrepancyExistsUids(bool constantHighestModSeq)
1618 FakeCapabilitiesInjector injector(model);
1619 injector.injectCapability("QRESYNC");
1621 uidMapA << 5 << 6;
1622 existsA = 2;
1623 uidNextA = 10;
1624 uidValidityA = 123;
1626 helperSyncAWithMessagesEmptyState();
1627 helperCheckCache();
1629 helperSyncBNoMessages();
1631 injector.injectCapability("ESEARCH");
1632 model->switchToMailbox(idxA);
1634 Imap::Mailbox::SyncState sync;
1635 sync.setExists(3);
1636 sync.setUidValidity(666);
1637 sync.setUidNext(10);
1638 sync.setHighestModSeq(111);
1639 QList<uint> uidMap;
1640 uidMap << 5 << 6 << 7 << 8;
1641 model->cache()->setMailboxSyncState(QLatin1String("a"), sync);
1642 model->cache()->setUidMapping(QLatin1String("a"), uidMap);
1643 model->resyncMailbox(idxA);
1644 cClient(t.mk("SELECT a (QRESYNC (666 111 (3 7)))\r\n"));
1645 cServer("* OK [CLOSED] Previous mailbox closed\r\n"
1646 "* 3 EXISTS\r\n"
1647 "* 1 RECENT\r\n"
1648 "* OK [UNSEEN 5] x\r\n"
1649 "* OK [UIDVALIDITY 666] x\r\n"
1650 "* OK [UIDNEXT 10] x\r\n"
1651 "* OK [HIGHESTMODSEQ " + QByteArray::number(constantHighestModSeq ? 111 : 112) + "] x\r\n"
1652 "* VANISHED (EARLIER) 8\r\n" +
1653 t.last("OK selected\r\n"));
1654 cClient(t.mk("UID SEARCH RETURN (ALL) ALL\r\n"));
1655 cServer("* ESEARCH (TAG \"" + t.last() + "\") UID ALL 5:7\r\n");
1656 cServer(t.last("OK fetched\r\n"));
1657 // This test makes sure that the flags are synced after the UID mapping vs. EXISTS discrepancy is detected.
1658 // Otherwise we might get some nonsense like all message marked as read, etc.
1659 cClient(t.mk("FETCH 1:3 (FLAGS)\r\n"));
1660 cServer("* 1 FETCH (FLAGS (a))\r\n"
1661 "* 2 FETCH (FLAGS (\\Seen))\r\n"
1662 "* 3 FETCH (FLAGS (c))\r\n" +
1663 t.last("OK fetched\r\n"));
1664 QCOMPARE(idxA.data(Imap::Mailbox::RoleUnreadMessageCount).toInt(), 2);
1665 cEmpty();
1666 justKeepTask();
1669 void ImapModelObtainSynchronizedMailboxTest::helperTestQresyncNoChanges(ModeForHelperTestQresyncNoChanges mode)
1671 FakeCapabilitiesInjector injector(model);
1672 injector.injectCapability("QRESYNC");
1673 Imap::Mailbox::SyncState sync;
1674 sync.setExists(3);
1675 sync.setUidValidity(666);
1676 sync.setUidNext(15);
1677 sync.setHighestModSeq(33);
1678 sync.setUnSeenCount(3);
1679 sync.setRecent(0);
1680 QList<uint> uidMap;
1681 uidMap << 6 << 9 << 10;
1682 model->cache()->setMailboxSyncState("a", sync);
1683 model->cache()->setUidMapping("a", uidMap);
1684 model->cache()->setMsgFlags("a", 6, QStringList() << "x");
1685 model->cache()->setMsgFlags("a", 9, QStringList() << "y");
1686 model->cache()->setMsgFlags("a", 10, QStringList() << "z");
1687 model->resyncMailbox(idxA);
1688 cClient(t.mk("SELECT a (QRESYNC (666 33 (2 9)))\r\n"));
1689 if (mode == EXTRA_ENABLED) {
1690 cServer("* ENABLED CONDSTORE QRESYNC\r\n");
1692 cServer("* 3 EXISTS\r\n"
1693 "* OK [UIDVALIDITY 666] .\r\n"
1694 "* OK [UIDNEXT 15] .\r\n"
1695 "* OK [HIGHESTMODSEQ 33] .\r\n"
1697 cServer(t.last("OK selected\r\n"));
1698 cEmpty();
1699 QCOMPARE(model->cache()->mailboxSyncState("a"), sync);
1700 QCOMPARE(static_cast<int>(model->cache()->mailboxSyncState("a").exists()), uidMap.size());
1701 QCOMPARE(model->cache()->uidMapping("a"), uidMap);
1702 QCOMPARE(model->cache()->msgFlags("a", 6), QStringList() << "x");
1703 QCOMPARE(model->cache()->msgFlags("a", 9), QStringList() << "y");
1704 QCOMPARE(model->cache()->msgFlags("a", 10), QStringList() << "z");
1705 // deactivate envelope preloading
1706 LibMailboxSync::setModelNetworkPolicy(model, Imap::Mailbox::NETWORK_EXPENSIVE);
1707 requestAndCheckSubject(0, "subject 6");
1708 justKeepTask();
1711 /** @short Test QRESYNC when there are no changes */
1712 void ImapModelObtainSynchronizedMailboxTest::testQresyncNoChanges()
1714 helperTestQresyncNoChanges(JUST_QRESYNC);
1717 /** @short Test QRESYNC reporting changed flags */
1718 void ImapModelObtainSynchronizedMailboxTest::testQresyncChangedFlags()
1720 FakeCapabilitiesInjector injector(model);
1721 injector.injectCapability("QRESYNC");
1722 Imap::Mailbox::SyncState sync;
1723 sync.setExists(3);
1724 sync.setUidValidity(666);
1725 sync.setUidNext(15);
1726 sync.setHighestModSeq(33);
1727 sync.setUnSeenCount(3);
1728 sync.setRecent(0);
1729 QList<uint> uidMap;
1730 uidMap << 6 << 9 << 10;
1731 model->cache()->setMailboxSyncState("a", sync);
1732 model->cache()->setUidMapping("a", uidMap);
1733 model->cache()->setMsgFlags("a", 6, QStringList() << "x");
1734 model->cache()->setMsgFlags("a", 9, QStringList() << "y");
1735 model->cache()->setMsgFlags("a", 10, QStringList() << "z");
1736 model->resyncMailbox(idxA);
1737 cClient(t.mk("SELECT a (QRESYNC (666 33 (2 9)))\r\n"));
1738 cServer("* 3 EXISTS\r\n"
1739 "* OK [UIDVALIDITY 666] .\r\n"
1740 "* OK [UIDNEXT 15] .\r\n"
1741 "* OK [HIGHESTMODSEQ 36] .\r\n"
1742 "* 2 FETCH (UID 9 FLAGS (x2 \\Seen))\r\n"
1744 cServer(t.last("OK selected\r\n"));
1745 cEmpty();
1746 sync.setHighestModSeq(36);
1747 sync.setUnSeenCount(2);
1748 QCOMPARE(model->cache()->mailboxSyncState("a"), sync);
1749 QCOMPARE(static_cast<int>(model->cache()->mailboxSyncState("a").exists()), uidMap.size());
1750 QCOMPARE(model->cache()->uidMapping("a"), uidMap);
1751 QCOMPARE(model->cache()->msgFlags("a", 6), QStringList() << "x");
1752 QCOMPARE(model->cache()->msgFlags("a", 9), QStringList() << "\\Seen" << "x2");
1753 QCOMPARE(model->cache()->msgFlags("a", 10), QStringList() << "z");
1755 // make sure that we've picked up possible flag change about unread messages
1756 QCOMPARE(idxA.data(Imap::Mailbox::RoleUnreadMessageCount).toInt(), 2);
1758 // deactivate envelope preloading
1759 LibMailboxSync::setModelNetworkPolicy(model, Imap::Mailbox::NETWORK_EXPENSIVE);
1760 requestAndCheckSubject(0, "subject 6");
1761 justKeepTask();
1764 /** @short Test QRESYNC using VANISHED EARLIER */
1765 void ImapModelObtainSynchronizedMailboxTest::testQresyncVanishedEarlier()
1767 FakeCapabilitiesInjector injector(model);
1768 injector.injectCapability("QRESYNC");
1769 Imap::Mailbox::SyncState sync;
1770 sync.setExists(3);
1771 sync.setUidValidity(666);
1772 sync.setUidNext(15);
1773 sync.setHighestModSeq(33);
1774 sync.setUnSeenCount(3);
1775 sync.setRecent(0);
1776 QList<uint> uidMap;
1777 uidMap << 6 << 9 << 10;
1778 model->cache()->setMailboxSyncState("a", sync);
1779 model->cache()->setUidMapping("a", uidMap);
1780 model->cache()->setMsgFlags("a", 6, QStringList() << "x");
1781 model->cache()->setMsgFlags("a", 9, QStringList() << "y");
1782 model->cache()->setMsgFlags("a", 10, QStringList() << "z");
1783 model->resyncMailbox(idxA);
1784 cClient(t.mk("SELECT a (QRESYNC (666 33 (2 9)))\r\n"));
1785 cServer("* 2 EXISTS\r\n"
1786 "* OK [UIDVALIDITY 666] .\r\n"
1787 "* OK [UIDNEXT 15] .\r\n"
1788 "* OK [HIGHESTMODSEQ 36] .\r\n"
1789 "* VANISHED (EARLIER) 1:5,9,11:13\r\n"
1791 cServer(t.last("OK selected\r\n"));
1792 cEmpty();
1793 sync.setHighestModSeq(36);
1794 sync.setExists(2);
1795 sync.setUnSeenCount(2);
1796 uidMap.removeOne(9);
1797 QCOMPARE(model->cache()->mailboxSyncState("a"), sync);
1798 QCOMPARE(static_cast<int>(model->cache()->mailboxSyncState("a").exists()), uidMap.size());
1799 QCOMPARE(model->cache()->uidMapping("a"), uidMap);
1800 QCOMPARE(model->cache()->msgFlags("a", 6), QStringList() << "x");
1801 QCOMPARE(model->cache()->msgFlags("a", 9), QStringList());
1802 QCOMPARE(model->cache()->msgFlags("a", 10), QStringList() << "z");
1803 justKeepTask();
1806 /** @short Test QRESYNC when UIDVALIDITY changes */
1807 void ImapModelObtainSynchronizedMailboxTest::testQresyncUidValidity()
1809 FakeCapabilitiesInjector injector(model);
1810 injector.injectCapability("QRESYNC");
1811 Imap::Mailbox::SyncState sync;
1812 sync.setExists(3);
1813 sync.setUidValidity(666);
1814 sync.setUidNext(15);
1815 sync.setHighestModSeq(33);
1816 sync.setUnSeenCount(3);
1817 sync.setRecent(0);
1818 QList<uint> uidMap;
1819 uidMap << 6 << 9 << 10;
1820 model->cache()->setMailboxSyncState("a", sync);
1821 model->cache()->setUidMapping("a", uidMap);
1822 model->cache()->setMsgFlags("a", 6, QStringList() << "x");
1823 model->cache()->setMsgFlags("a", 9, QStringList() << "y");
1824 model->cache()->setMsgFlags("a", 10, QStringList() << "z");
1825 model->resyncMailbox(idxA);
1826 cClient(t.mk("SELECT a (QRESYNC (666 33 (2 9)))\r\n"));
1827 cServer("* 3 EXISTS\r\n"
1828 "* OK [UIDVALIDITY 333] .\r\n"
1829 "* OK [UIDNEXT 15] .\r\n"
1830 "* OK [HIGHESTMODSEQ 33] .\r\n"
1832 cServer(t.last("OK selected\r\n"));
1833 cClient(t.mk("UID SEARCH ALL\r\n"));
1834 cServer(QByteArray("* SEARCH 6 9 10\r\n") + t.last("OK uids\r\n"));
1835 cClient(t.mk("FETCH 1:3 (FLAGS)\r\n"));
1836 cServer("* 1 FETCH (FLAGS (x))\r\n"
1837 "* 2 FETCH (FLAGS (\\Seen))\r\n"
1838 "* 3 FETCH (FLAGS (z))\r\n");
1839 cServer(t.last("OK fetch\r\n"));
1840 cEmpty();
1841 sync.setUidValidity(333);
1842 sync.setUnSeenCount(2);
1843 QCOMPARE(model->cache()->mailboxSyncState("a"), sync);
1844 QCOMPARE(static_cast<int>(model->cache()->mailboxSyncState("a").exists()), uidMap.size());
1845 QCOMPARE(model->cache()->uidMapping("a"), uidMap);
1846 QCOMPARE(model->cache()->msgFlags("a", 6), QStringList() << "x");
1847 QCOMPARE(model->cache()->msgFlags("a", 9), QStringList() << "\\Seen");
1848 QCOMPARE(model->cache()->msgFlags("a", 10), QStringList() << "z");
1849 QCOMPARE(idxA.data(Imap::Mailbox::RoleUnreadMessageCount).toInt(), 2);
1850 justKeepTask();
1853 /** @short Test QRESYNC reporting changed flags */
1854 void ImapModelObtainSynchronizedMailboxTest::testQresyncNoModseqChangedFlags()
1856 FakeCapabilitiesInjector injector(model);
1857 injector.injectCapability("QRESYNC");
1858 Imap::Mailbox::SyncState sync;
1859 sync.setExists(3);
1860 sync.setUidValidity(666);
1861 sync.setUidNext(15);
1862 sync.setHighestModSeq(33);
1863 sync.setUnSeenCount(3);
1864 sync.setRecent(0);
1865 QList<uint> uidMap;
1866 uidMap << 6 << 9 << 10;
1867 model->cache()->setMailboxSyncState("a", sync);
1868 model->cache()->setUidMapping("a", uidMap);
1869 model->cache()->setMsgFlags("a", 6, QStringList() << "x");
1870 model->cache()->setMsgFlags("a", 9, QStringList() << "y");
1871 model->cache()->setMsgFlags("a", 10, QStringList() << "z");
1872 model->resyncMailbox(idxA);
1873 cClient(t.mk("SELECT a (QRESYNC (666 33 (2 9)))\r\n"));
1874 cServer("* 3 EXISTS\r\n"
1875 "* OK [UIDVALIDITY 666] .\r\n"
1876 "* OK [UIDNEXT 15] .\r\n"
1877 "* OK [NOMODSEQ] .\r\n"
1879 cServer(t.last("OK selected\r\n"));
1880 cClient(t.mk("FETCH 1:3 (FLAGS)\r\n"));
1881 cServer("* 1 FETCH (FLAGS (x1))\r\n"
1882 "* 2 FETCH (FLAGS (x2))\r\n"
1883 "* 3 FETCH (FLAGS (x3))\r\n"
1884 + t.last("OK flags\r\n"));
1885 cEmpty();
1886 sync.setHighestModSeq(0);
1887 QCOMPARE(model->cache()->mailboxSyncState("a"), sync);
1888 QCOMPARE(static_cast<int>(model->cache()->mailboxSyncState("a").exists()), uidMap.size());
1889 QCOMPARE(model->cache()->uidMapping("a"), uidMap);
1890 QCOMPARE(model->cache()->msgFlags("a", 6), QStringList() << "x1");
1891 QCOMPARE(model->cache()->msgFlags("a", 9), QStringList() << "x2");
1892 QCOMPARE(model->cache()->msgFlags("a", 10), QStringList() << "x3");
1893 justKeepTask();
1896 /** @short Test that EXISTS change with unchanged HIGHESTMODSEQ cancels QRESYNC */
1897 void ImapModelObtainSynchronizedMailboxTest::testQresyncErrorExists()
1899 FakeCapabilitiesInjector injector(model);
1900 injector.injectCapability("QRESYNC");
1901 Imap::Mailbox::SyncState sync;
1902 sync.setExists(3);
1903 sync.setUidValidity(666);
1904 sync.setUidNext(15);
1905 sync.setHighestModSeq(33);
1906 sync.setUnSeenCount(3);
1907 sync.setRecent(0);
1908 QList<uint> uidMap;
1909 uidMap << 6 << 9 << 10;
1910 model->cache()->setMailboxSyncState("a", sync);
1911 model->cache()->setUidMapping("a", uidMap);
1912 model->cache()->setMsgFlags("a", 6, QStringList() << "x");
1913 model->cache()->setMsgFlags("a", 9, QStringList() << "y");
1914 model->cache()->setMsgFlags("a", 10, QStringList() << "z");
1915 model->resyncMailbox(idxA);
1916 cClient(t.mk("SELECT a (QRESYNC (666 33 (2 9)))\r\n"));
1917 cServer("* 4 EXISTS\r\n"
1918 "* OK [UIDVALIDITY 666] .\r\n"
1919 "* OK [UIDNEXT 15] .\r\n"
1920 "* OK [HIGHESTMODSEQ 33] .\r\n"
1922 cServer(t.last("OK selected\r\n"));
1923 cClient(t.mk("UID SEARCH ALL\r\n"));
1924 cServer("* SEARCH 6 9 10 12\r\n" + t.last("OK search\r\n"));
1925 cClient(t.mk("FETCH 1:4 (FLAGS)\r\n"));
1926 cServer("* 1 FETCH (FLAGS (x1))\r\n"
1927 "* 2 FETCH (FLAGS (\\seen x2))\r\n"
1928 "* 3 FETCH (FLAGS (x3 \\seen))\r\n"
1929 "* 4 FETCH (FLAGS (x4))\r\n"
1930 + t.last("OK flags\r\n"));
1931 cEmpty();
1932 sync.setExists(4);
1933 sync.setUnSeenCount(2);
1934 sync.setHighestModSeq(0);
1935 uidMap << 12;
1936 QCOMPARE(model->cache()->mailboxSyncState("a"), sync);
1937 QCOMPARE(static_cast<int>(model->cache()->mailboxSyncState("a").exists()), uidMap.size());
1938 QCOMPARE(model->cache()->uidMapping("a"), uidMap);
1939 QCOMPARE(model->cache()->msgFlags("a", 6), QStringList() << "x1");
1940 QCOMPARE(model->cache()->msgFlags("a", 9), QStringList() << "\\Seen" << "x2");
1941 QCOMPARE(model->cache()->msgFlags("a", 10), QStringList() << "\\Seen" << "x3");
1942 QCOMPARE(model->cache()->msgFlags("a", 12), QStringList() << "x4");
1943 QCOMPARE(model->cache()->msgFlags("a", 15), QStringList());
1944 justKeepTask();
1947 /** @short Test that UIDNEXT change with unchanged HIGHESTMODSEQ cancels QRESYNC */
1948 void ImapModelObtainSynchronizedMailboxTest::testQresyncErrorUidNext()
1950 FakeCapabilitiesInjector injector(model);
1951 injector.injectCapability("QRESYNC");
1952 Imap::Mailbox::SyncState sync;
1953 sync.setExists(3);
1954 sync.setUidValidity(666);
1955 sync.setUidNext(15);
1956 sync.setHighestModSeq(33);
1957 sync.setUnSeenCount(3);
1958 sync.setRecent(0);
1959 QList<uint> uidMap;
1960 uidMap << 6 << 9 << 10;
1961 model->cache()->setMailboxSyncState("a", sync);
1962 model->cache()->setUidMapping("a", uidMap);
1963 model->cache()->setMsgFlags("a", 6, QStringList() << "x");
1964 model->cache()->setMsgFlags("a", 9, QStringList() << "y");
1965 model->cache()->setMsgFlags("a", 10, QStringList() << "z");
1966 model->resyncMailbox(idxA);
1967 cClient(t.mk("SELECT a (QRESYNC (666 33 (2 9)))\r\n"));
1968 cServer("* 3 EXISTS\r\n"
1969 "* OK [UIDVALIDITY 666] .\r\n"
1970 "* OK [UIDNEXT 20] .\r\n"
1971 "* OK [HIGHESTMODSEQ 33] .\r\n"
1973 cServer(t.last("OK selected\r\n"));
1974 cClient(t.mk("UID SEARCH ALL\r\n"));
1975 cServer("* SEARCH 6 9 10\r\n" + t.last("OK search\r\n"));
1976 cClient(t.mk("FETCH 1:3 (FLAGS)\r\n"));
1977 cServer("* 1 FETCH (FLAGS (x1))\r\n"
1978 "* 2 FETCH (FLAGS (x2))\r\n"
1979 "* 3 FETCH (FLAGS (x3))\r\n"
1980 + t.last("OK flags\r\n"));
1981 cEmpty();
1982 sync.setUidNext(20);
1983 sync.setHighestModSeq(0);
1984 QCOMPARE(model->cache()->mailboxSyncState("a"), sync);
1985 QCOMPARE(static_cast<int>(model->cache()->mailboxSyncState("a").exists()), uidMap.size());
1986 QCOMPARE(model->cache()->uidMapping("a"), uidMap);
1987 QCOMPARE(model->cache()->msgFlags("a", 6), QStringList() << "x1");
1988 QCOMPARE(model->cache()->msgFlags("a", 9), QStringList() << "x2");
1989 QCOMPARE(model->cache()->msgFlags("a", 10), QStringList() << "x3");
1990 justKeepTask();
1993 /** @short Test QRESYNC when something has arrived and the server haven't told us about that */
1994 void ImapModelObtainSynchronizedMailboxTest::testQresyncUnreportedNewArrivals()
1996 FakeCapabilitiesInjector injector(model);
1997 injector.injectCapability("QRESYNC");
1998 Imap::Mailbox::SyncState sync;
1999 sync.setExists(3);
2000 sync.setUidValidity(666);
2001 sync.setUidNext(15);
2002 sync.setHighestModSeq(33);
2003 sync.setUnSeenCount(3);
2004 sync.setRecent(0);
2005 QList<uint> uidMap;
2006 uidMap << 6 << 9 << 10;
2007 model->cache()->setMailboxSyncState("a", sync);
2008 model->cache()->setUidMapping("a", uidMap);
2009 model->cache()->setMsgFlags("a", 6, QStringList() << "x");
2010 model->cache()->setMsgFlags("a", 9, QStringList() << "y");
2011 model->cache()->setMsgFlags("a", 10, QStringList() << "z");
2012 model->resyncMailbox(idxA);
2013 cClient(t.mk("SELECT a (QRESYNC (666 33 (2 9)))\r\n"));
2014 cServer("* 4 EXISTS\r\n"
2015 "* OK [UIDVALIDITY 666] .\r\n"
2016 "* OK [UIDNEXT 20] .\r\n"
2017 "* OK [HIGHESTMODSEQ 34] .\r\n"
2019 QCOMPARE(model->rowCount(msgListA), 4);
2020 QCOMPARE(msgListA.child(3, 0).data(Imap::Mailbox::RoleMessageUid).toUInt(), 0u);
2021 cServer(t.last("OK selected\r\n"));
2022 cClient(t.mk("UID FETCH 15:* (FLAGS)\r\n"));
2023 cServer("* 4 FETCH (FLAGS (x4 \\seen) UID 16)\r\n" + t.last("OK uid fetch flags\r\n"));
2024 cEmpty();
2025 sync.setExists(4);
2026 sync.setUidNext(20);
2027 uidMap << 16;
2028 sync.setHighestModSeq(34);
2029 QCOMPARE(model->cache()->mailboxSyncState("a"), sync);
2030 QCOMPARE(static_cast<int>(model->cache()->mailboxSyncState("a").exists()), uidMap.size());
2031 QCOMPARE(model->cache()->uidMapping("a"), uidMap);
2032 QCOMPARE(model->cache()->msgFlags("a", 6), QStringList() << "x");
2033 QCOMPARE(model->cache()->msgFlags("a", 9), QStringList() << "y");
2034 QCOMPARE(model->cache()->msgFlags("a", 10), QStringList() << "z");
2035 QCOMPARE(model->cache()->msgFlags("a", 16), QStringList() << "\\Seen" << "x4");
2036 justKeepTask();
2039 /** @short Test QRESYNC when something has arrived and the server reported UID of that stuff on its own */
2040 void ImapModelObtainSynchronizedMailboxTest::testQresyncReportedNewArrivals()
2042 FakeCapabilitiesInjector injector(model);
2043 injector.injectCapability("QRESYNC");
2044 Imap::Mailbox::SyncState sync;
2045 sync.setExists(3);
2046 sync.setUidValidity(666);
2047 sync.setUidNext(15);
2048 sync.setHighestModSeq(33);
2049 sync.setUnSeenCount(3);
2050 sync.setRecent(0);
2051 QList<uint> uidMap;
2052 uidMap << 6 << 9 << 10;
2053 model->cache()->setMailboxSyncState("a", sync);
2054 model->cache()->setUidMapping("a", uidMap);
2055 model->cache()->setMsgFlags("a", 6, QStringList() << "x");
2056 model->cache()->setMsgFlags("a", 9, QStringList() << "y");
2057 model->cache()->setMsgFlags("a", 10, QStringList() << "z");
2058 model->resyncMailbox(idxA);
2059 cClient(t.mk("SELECT a (QRESYNC (666 33 (2 9)))\r\n"));
2060 cServer("* 4 EXISTS\r\n"
2061 "* OK [UIDVALIDITY 666] .\r\n"
2062 "* OK [UIDNEXT 20] .\r\n"
2063 "* OK [HIGHESTMODSEQ 34] .\r\n"
2064 "* 4 FETCH (FLAGS (x4) UID 16)\r\n"
2066 QCOMPARE(model->rowCount(msgListA), 4);
2067 QCOMPARE(msgListA.child(3, 0).data(Imap::Mailbox::RoleMessageUid).toUInt(), 16u);
2068 cServer(t.last("OK selected\r\n"));
2069 cEmpty();
2070 sync.setExists(4);
2071 sync.setUnSeenCount(4);
2072 sync.setUidNext(20);
2073 uidMap << 16;
2074 sync.setHighestModSeq(34);
2075 QCOMPARE(model->cache()->mailboxSyncState("a"), sync);
2076 QCOMPARE(static_cast<int>(model->cache()->mailboxSyncState("a").exists()), uidMap.size());
2077 QCOMPARE(model->cache()->uidMapping("a"), uidMap);
2078 QCOMPARE(model->cache()->msgFlags("a", 6), QStringList() << "x");
2079 QCOMPARE(model->cache()->msgFlags("a", 9), QStringList() << "y");
2080 QCOMPARE(model->cache()->msgFlags("a", 10), QStringList() << "z");
2081 // deactivate envelope preloading
2082 LibMailboxSync::setModelNetworkPolicy(model, Imap::Mailbox::NETWORK_EXPENSIVE);
2083 requestAndCheckSubject(0, "subject 6");
2084 justKeepTask();
2087 /** @short QRESYNC where the number of messaged actually removed by the VANISHED EARLIER is equal to number of new arrivals */
2088 void ImapModelObtainSynchronizedMailboxTest::testQresyncDeletionsNewArrivals()
2090 FakeCapabilitiesInjector injector(model);
2091 injector.injectCapability("QRESYNC");
2092 Imap::Mailbox::SyncState sync;
2093 sync.setExists(5);
2094 sync.setUidValidity(666);
2095 sync.setUidNext(6);
2096 sync.setHighestModSeq(10);
2097 sync.setUnSeenCount(5);
2098 sync.setRecent(0);
2099 QList<uint> uidMap;
2100 uidMap << 1 << 2 << 3 << 4 << 5;
2101 model->cache()->setMailboxSyncState("a", sync);
2102 model->cache()->setUidMapping("a", uidMap);
2103 model->cache()->setMsgFlags("a", 1, QStringList() << "1");
2104 model->cache()->setMsgFlags("a", 2, QStringList() << "2");
2105 model->cache()->setMsgFlags("a", 3, QStringList() << "3");
2106 model->cache()->setMsgFlags("a", 4, QStringList() << "4");
2107 model->cache()->setMsgFlags("a", 5, QStringList() << "5");
2108 model->resyncMailbox(idxA);
2109 cClient(t.mk("SELECT a (QRESYNC (666 10 (3,5 3,5)))\r\n"));
2110 cServer("* 5 EXISTS\r\n"
2111 "* OK [UIDVALIDITY 666] .\r\n"
2112 "* OK [UIDNEXT 10] .\r\n"
2113 "* OK [HIGHESTMODSEQ 34] .\r\n"
2114 "* VANISHED (EARLIER) 1:3\r\n"
2115 "* 3 FETCH (UID 6 FLAGS (6))\r\n"
2116 "* 4 FETCH (UID 7 FLAGS (7))\r\n"
2117 "* 5 FETCH (UID 8 FLAGS (8))\r\n"
2119 uidMap.removeOne(1);
2120 uidMap.removeOne(2);
2121 uidMap.removeOne(3);
2122 uidMap << 6 << 7 << 8;
2123 QCOMPARE(model->rowCount(msgListA), 5);
2124 cServer(t.last("OK selected\r\n"));
2125 cEmpty();
2126 sync.setUidNext(10);
2127 sync.setHighestModSeq(34);
2128 QCOMPARE(model->cache()->mailboxSyncState("a"), sync);
2129 QCOMPARE(static_cast<int>(model->cache()->mailboxSyncState("a").exists()), uidMap.size());
2130 QCOMPARE(model->cache()->uidMapping("a"), uidMap);
2131 // these were removed
2132 QCOMPARE(model->cache()->msgFlags("a", 1), QStringList());
2133 QCOMPARE(model->cache()->msgFlags("a", 2), QStringList());
2134 QCOMPARE(model->cache()->msgFlags("a", 3), QStringList());
2135 // these are still present
2136 QCOMPARE(model->cache()->msgFlags("a", 4), QStringList() << "4");
2137 QCOMPARE(model->cache()->msgFlags("a", 5), QStringList() << "5");
2138 // and these are the new arrivals
2139 QCOMPARE(model->cache()->msgFlags("a", 6), QStringList() << "6");
2140 QCOMPARE(model->cache()->msgFlags("a", 7), QStringList() << "7");
2141 QCOMPARE(model->cache()->msgFlags("a", 8), QStringList() << "8");
2142 justKeepTask();
2145 /** @short Test that extra SEARCH responses don't cause asserts */
2146 void ImapModelObtainSynchronizedMailboxTest::testSpuriousSearch()
2148 QCOMPARE(model->rowCount(msgListA), 0);
2149 cClient(t.mk("SELECT a\r\n"));
2150 cServer(QByteArray("* 0 exists\r\n"));
2153 ExpectSingleErrorHere blocker(this);
2154 cServer("* SEARCH \r\n");
2158 /** @short Test how unexpected ESEARCH operates */
2159 void ImapModelObtainSynchronizedMailboxTest::testSpuriousESearch()
2161 QCOMPARE(model->rowCount(msgListA), 0);
2162 cClient(t.mk("SELECT a\r\n"));
2163 cServer(QByteArray("* 0 exists\r\n"));
2166 ExpectSingleErrorHere blocker(this);
2167 cServer("* ESEARCH (TAG \"\") UID \r\n");
2171 /** @short Mailbox synchronization without the UIDNEXT -- this is what Courier 4.5.0 is happy to return */
2172 void ImapModelObtainSynchronizedMailboxTest::testSyncNoUidnext()
2174 QCOMPARE(model->rowCount(msgListA), 0);
2175 cClient(t.mk("SELECT a\r\n"));
2176 cServer("* 3 EXISTS\r\n"
2177 "* 0 RECENT\r\n"
2178 "* OK [UIDVALIDITY 1336643053] Ok\r\n"
2179 "* OK [MYRIGHTS \"acdilrsw\"] ACL\r\n"
2180 + t.last("OK [READ-WRITE] Ok\r\n"));
2181 QCOMPARE(model->rowCount(msgListA), 3);
2182 cClient(t.mk("UID SEARCH ALL\r\n"));
2183 cServer("* SEARCH 1212 1214 1215\r\n");
2184 cServer(t.last("OK search\r\n"));
2185 cClient(t.mk("FETCH 1:3 (FLAGS)\r\n"));
2186 cServer("* 1 FETCH (FLAGS (uid1212))\r\n"
2187 "* 2 FETCH (FLAGS (uid1214 \\seen))\r\n"
2188 "* 3 FETCH (FLAGS (uid1215))\r\n"
2189 + t.last("OK fetch\r\n"));
2190 cEmpty();
2191 justKeepTask();
2193 // Verify the cache
2194 Imap::Mailbox::SyncState sync;
2195 sync.setExists(3);
2196 sync.setUidValidity(1336643053);
2197 sync.setRecent(0);
2198 // The UIDNEXT shall be updated automatically
2199 sync.setUidNext(1216);
2200 // unseen count is computed, too
2201 sync.setUnSeenCount(2);
2202 QList<uint> uidMap;
2203 uidMap << 1212 << 1214 << 1215;
2205 QCOMPARE(model->cache()->mailboxSyncState("a"), sync);
2206 QCOMPARE(static_cast<int>(model->cache()->mailboxSyncState("a").exists()), uidMap.size());
2207 QCOMPARE(model->cache()->uidMapping("a"), uidMap);
2208 QCOMPARE(model->cache()->msgFlags("a", 1212), QStringList() << "uid1212");
2209 QCOMPARE(model->cache()->msgFlags("a", 1214), QStringList() << "\\Seen" << "uid1214");
2210 QCOMPARE(model->cache()->msgFlags("a", 1215), QStringList() << "uid1215");
2212 // Switch away from this mailbox
2213 helperSyncBNoMessages();
2215 // Make sure that we catch UIDNEXT missing by purging the cache
2216 model->cache()->setMsgPart("a", 1212, QByteArray(), "foo");
2218 // Now go back to mailbox A
2219 model->resyncMailbox(idxA);
2220 cClient(t.mk("SELECT a\r\n"));
2221 cServer("* 3 EXISTS\r\n"
2222 "* 0 RECENT\r\n"
2223 "* OK [UIDVALIDITY 1336643053] .\r\n"
2224 "* OK [MYRIGHTS \"acdilrsw\"] ACL\r\n"
2226 QCOMPARE(model->rowCount(msgListA), 3);
2227 cServer(t.last("OK selected\r\n"));
2228 // The UIDNEXT is missing -> resyncing the UIDs again
2229 cClient(t.mk("UID SEARCH ALL\r\n"));
2230 cServer("* SEARCH 1212 1214 1215\r\n");
2231 cServer(t.last("OK search\r\n"));
2232 cClient(t.mk("FETCH 1:3 (FLAGS)\r\n"));
2233 cServer("* 1 FETCH (FLAGS (uid1212))\r\n"
2234 "* 2 FETCH (FLAGS (\\sEEN uid1214))\r\n"
2235 "* 3 FETCH (FLAGS (uid1215))\r\n"
2236 + t.last("OK fetch\r\n"));
2237 cEmpty();
2238 justKeepTask();
2240 QCOMPARE(model->cache()->mailboxSyncState("a"), sync);
2241 QCOMPARE(static_cast<int>(model->cache()->mailboxSyncState("a").exists()), uidMap.size());
2242 QCOMPARE(model->cache()->uidMapping("a"), uidMap);
2244 // Missing UIDNEXT is a violation of the IMAP protocol specification, we treat that like a severe error and fall back to
2245 // a full synchronization which means that any cached data is discarded
2246 QCOMPARE(model->cache()->messagePart("a", 1212, QByteArray()), QByteArray());
2249 /** @short Test that we can open a mailbox using just the cached data when offline */
2250 void ImapModelObtainSynchronizedMailboxTest::testOfflineOpening()
2252 LibMailboxSync::setModelNetworkPolicy(model, Imap::Mailbox::NETWORK_OFFLINE);
2253 cClient(t.mk("LOGOUT\r\n"));
2254 cServer(t.last("OK logged out\r\n"));
2256 // prepare the cache
2257 Imap::Mailbox::SyncState sync;
2258 sync.setExists(3);
2259 sync.setUidValidity(333);
2260 sync.setRecent(0);
2261 sync.setUidNext(666);
2262 QList<uint> uidMap;
2263 uidMap << 10 << 20 << 30;
2264 model->cache()->setMailboxSyncState("a", sync);
2265 model->cache()->setUidMapping("a", uidMap);
2266 Imap::Mailbox::AbstractCache::MessageDataBundle msg10, msg20;
2267 msg10.uid = 10;
2268 msg10.envelope.subject = "msg10";
2269 msg20.uid = 20;
2270 msg20.envelope.subject = "msg20";
2272 // Prepare the body structure for this message
2273 int start = 0;
2274 Imap::Responses::Fetch fetchResponse(666, QByteArray(" (BODYSTRUCTURE (\"text\" \"plain\" (\"chaRset\" \"UTF-8\" "
2275 "\"format\" \"flowed\") NIL NIL \"8bit\" 362 15 NIL NIL NIL))\r\n"),
2276 start);
2277 msg10.serializedBodyStructure = dynamic_cast<const Imap::Responses::RespData<QByteArray>&>(*(fetchResponse.data["x-trojita-bodystructure"])).data;
2278 msg20.serializedBodyStructure = msg10.serializedBodyStructure;
2280 model->cache()->setMessageMetadata("a", 10, msg10);
2281 model->cache()->setMessageMetadata("a", 20, msg20);
2283 // Check that stuff works
2284 QCOMPARE(model->rowCount(msgListA), 0);
2285 QCoreApplication::processEvents();
2286 QCOMPARE(model->rowCount(msgListA), 3);
2287 checkCachedSubject(0, "msg10");
2288 checkCachedSubject(1, "msg20");
2289 checkCachedSubject(2, "");
2290 QCOMPARE(msgListA.child(2, 0).data(Imap::Mailbox::RoleIsFetched).toBool(), false);
2292 QCOMPARE(model->taskModel()->rowCount(), 0);
2294 QCOMPARE(model->cache()->mailboxSyncState("a"), sync);
2295 QCOMPARE(static_cast<int>(model->cache()->mailboxSyncState("a").exists()), uidMap.size());
2296 QCOMPARE(model->cache()->uidMapping("a"), uidMap);
2299 /** @short Check that ENABLE QRESYNC always gets sent prior to SELECT QRESYNC
2301 See Redmine #611 for details.
2303 void ImapModelObtainSynchronizedMailboxTest::testQresyncEnabling()
2305 using namespace Imap::Mailbox;
2307 LibMailboxSync::setModelNetworkPolicy(model, Imap::Mailbox::NETWORK_OFFLINE);
2308 cClient(t.mk("LOGOUT\r\n"));
2309 cServer(t.last("OK logged out\r\n"));
2311 // this one is important, otherwise the index would get invalidated "too fast"
2312 taskFactoryUnsafe->fakeListChildMailboxes = false;
2313 // we need to tap into the whole connection establishing process; otherwise KeepMailboxOpenTask asserts on line 80
2314 taskFactoryUnsafe->fakeOpenConnectionTask = false;
2315 factory->setInitialState(Imap::CONN_STATE_CONNECTED_PRETLS_PRECAPS);
2316 t.reset();
2318 LibMailboxSync::setModelNetworkPolicy(model, Imap::Mailbox::NETWORK_ONLINE);
2319 QCoreApplication::processEvents();
2320 cServer("* OK [CAPABILITY IMAP4rev1] hi there\r\n");
2321 QCOMPARE(model->rowCount(QModelIndex()), 26);
2322 idxA = model->index(1, 0, QModelIndex());
2323 QVERIFY(idxA.isValid());
2324 QCOMPARE(idxA.data(RoleMailboxName).toString(), QString("a"));
2325 msgListA = idxA.child(0, 0);
2326 QVERIFY(idxA.isValid());
2327 QCOMPARE(model->rowCount(msgListA), 0);
2328 cEmpty();
2329 model->setImapUser(QLatin1String("user"));
2330 model->setImapPassword(QLatin1String("pw"));
2331 cClient(t.mk("LOGIN user pw\r\n"));
2332 cServer(t.last("OK [CAPABILITY IMAP4rev1 ENABLE QRESYNC UNSELECT] logged in\r\n"));
2334 QByteArray idCmd = t.mk("ENABLE QRESYNC\r\n");
2335 QByteArray idRes = t.last("OK enabled\r\n");
2336 QByteArray listCmd = t.mk("LIST \"\" \"%\"\r\n");
2337 QByteArray listResp = t.last("OK listed\r\n");
2338 QByteArray selectCmd = t.mk("SELECT a\r\n");
2339 QByteArray selectResp = t.last("OK selected\r\n");
2341 cClient(idCmd + listCmd + selectCmd);
2342 cServer(idRes + "* LIST (\\HasNoChildren) \".\" \"a\"\r\n" + listResp);
2343 cServer(selectResp);
2344 cClient(t.mk("UNSELECT\r\n"));
2345 cServer(t.last("OK whatever\r\n"));
2346 cEmpty();
2349 /** @short VANISHED EARLIER which refers to meanwhile-arrived-and-deleted messages on an empty mailbox */
2350 void ImapModelObtainSynchronizedMailboxTest::testQresyncSpuriousVanishedEarlier()
2352 FakeCapabilitiesInjector injector(model);
2353 injector.injectCapability("ESEARCH");
2354 injector.injectCapability("QRESYNC");
2355 Imap::Mailbox::SyncState sync;
2356 sync.setExists(0);
2357 sync.setUidValidity(1309542826);
2358 sync.setUidNext(252);
2359 sync.setHighestModSeq(10);
2360 // and just for fun: introduce garbage to the cache, muhehe
2361 sync.setUnSeenCount(10);
2362 sync.setRecent(10);
2363 QList<uint> uidMap;
2364 model->cache()->setMailboxSyncState("a", sync);
2365 model->cache()->setUidMapping("a", uidMap);
2366 model->resyncMailbox(idxA);
2367 cClient(t.mk("SELECT a (QRESYNC (1309542826 10))\r\n"));
2368 cServer("* 0 EXISTS\r\n"
2369 "* OK [UIDVALIDITY 1309542826] UIDs valid\r\n"
2370 "* OK [UIDNEXT 256] Predicted next UID\r\n"
2371 "* OK [HIGHESTMODSEQ 22] Highest\r\n"
2372 "* VANISHED (EARLIER) 252:255\r\n"
2374 QCOMPARE(model->rowCount(msgListA), 0);
2375 cServer(t.last("OK selected\r\n"));
2376 cEmpty();
2377 sync.setUidNext(256);
2378 sync.setHighestModSeq(22);
2379 sync.setUnSeenCount(0);
2380 sync.setRecent(0);
2381 QCOMPARE(model->cache()->mailboxSyncState("a"), sync);
2382 QCOMPARE(static_cast<int>(model->cache()->mailboxSyncState("a").exists()), 0);
2383 QCOMPARE(model->cache()->uidMapping("a"), uidMap);
2384 justKeepTask();
2387 /** @short QRESYNC synchronization of a formerly empty mailbox which now contains some messages
2389 This was reported by paalsteek as being broken.
2391 void ImapModelObtainSynchronizedMailboxTest::testQresyncAfterEmpty()
2393 FakeCapabilitiesInjector injector(model);
2394 injector.injectCapability("ESEARCH");
2395 injector.injectCapability("QRESYNC");
2396 Imap::Mailbox::SyncState sync;
2397 sync.setUidValidity(1336686200);
2398 sync.setExists(0);
2399 sync.setUidNext(1);
2400 sync.setHighestModSeq(1);
2401 model->cache()->setMailboxSyncState("a", sync);
2402 model->cache()->setUidMapping("a", QList<uint>());
2403 model->resyncMailbox(idxA);
2404 cClient(t.mk("SELECT a (QRESYNC (1336686200 1))\r\n"));
2405 cServer("* 6 EXISTS\r\n"
2406 "* OK [UIDVALIDITY 1336686200] UIDs valid\r\n"
2407 "* OK [UIDNEXT 7] Predicted next UID\r\n"
2408 "* OK [HIGHESTMODSEQ 3] Highest\r\n"
2409 "* 1 FETCH (MODSEQ (2) UID 1 FLAGS (\\Seen \\Recent))\r\n"
2410 "* 2 FETCH (MODSEQ (2) UID 2 FLAGS (\\Seen \\Recent))\r\n"
2411 "* 3 FETCH (MODSEQ (2) UID 3 FLAGS (\\Seen \\Recent))\r\n"
2412 "* 4 FETCH (MODSEQ (2) UID 4 FLAGS (\\Seen \\Recent))\r\n"
2413 "* 5 FETCH (MODSEQ (2) UID 5 FLAGS (\\Seen \\Recent))\r\n"
2414 "* 6 FETCH (MODSEQ (2) UID 6 FLAGS (\\Seen \\Recent))\r\n"
2415 + t.last("OK [READ-WRITE] Select completed.\r\n")
2417 existsA = 6;
2418 uidNextA = 7;
2419 uidValidityA = 1336686200;
2420 for (uint i = 1; i <= existsA; ++i)
2421 uidMapA << i;
2422 helperCheckCache();
2423 cEmpty();
2424 justKeepTask();
2427 /** @short Test QRESYNC/CONDSTORE initial sync on Devocot which reports NOMODSEQ followed by HIGHESTMODSEQ 1
2429 This is apparently a real-world issue.
2431 void ImapModelObtainSynchronizedMailboxTest::testCondstoreQresyncNomodseqHighestmodseq()
2433 FakeCapabilitiesInjector injector(model);
2434 injector.injectCapability("ESEARCH");
2435 injector.injectCapability("CONDSTORE");
2436 injector.injectCapability("QRESYNC");
2437 model->resyncMailbox(idxA);
2438 cClient(t.mk("SELECT a (CONDSTORE)\r\n"));
2439 cServer("* FLAGS (\\Answered \\Flagged \\Deleted \\Seen \\Draft Junk NonJunk $Forwarded)\r\n"
2440 "* OK [PERMANENTFLAGS (\\Answered \\Flagged \\Deleted \\Seen \\Draft Junk NonJunk $Forwarded \\*)] Flags permitted.\r\n"
2441 "* 3 EXISTS\r\n"
2442 "* 0 RECENT\r\n"
2443 "* OK [UIDVALIDITY 666] .\r\n"
2444 "* OK [UIDNEXT 15] .\r\n"
2445 "* OK [NOMODSEQ] .\r\n"
2447 cServer(t.last("OK [READ-WRITE] selected\r\n"));
2448 cClient(t.mk("UID SEARCH RETURN (ALL) ALL\r\n"));
2449 cServer("* ESEARCH (TAG \"" + t.last() + "\") UID ALL 1:3\r\n");
2450 cServer(t.last("OK [HIGHESTMODSEQ 1] Searched\r\n"));
2451 cClient(t.mk("FETCH 1:3 (FLAGS)\r\n"));
2452 cServer("* 1 FETCH (MODSEQ (1) UID 1 FLAGS (\\Seen))\r\n"
2453 "* 2 FETCH (MODSEQ (1) UID 2 FLAGS ())\r\n"
2454 "* 3 FETCH (MODSEQ (1) UID 3 FLAGS (\\Answered))\r\n"
2455 + t.last("OK flags fetched\r\n"));
2457 existsA = 3;
2458 uidNextA = 15;
2459 uidValidityA = 666;
2460 Imap::Mailbox::SyncState state;
2461 state.setExists(existsA);
2462 state.setUidNext(uidNextA);
2463 state.setUidValidity(uidValidityA);
2464 state.setRecent(0);
2465 state.setUnSeenCount(2);
2466 state.setFlags(QString::fromUtf8("\\Answered \\Flagged \\Deleted \\Seen \\Draft Junk NonJunk $Forwarded").split(QLatin1Char(' ')));
2467 state.setPermanentFlags(QString::fromUtf8("\\Answered \\Flagged \\Deleted \\Seen \\Draft Junk NonJunk $Forwarded \\*").split(QLatin1Char(' ')));
2468 state.setHighestModSeq(1);
2469 uidMapA << 1 << 2 << 3;
2471 helperCheckCache();
2472 helperVerifyUidMapA();
2473 QCOMPARE(model->cache()->mailboxSyncState("a"), state);
2474 QCOMPARE(static_cast<int>(model->cache()->mailboxSyncState("a").exists()), uidMapA.size());
2475 QCOMPARE(model->cache()->uidMapping("a"), uidMapA);
2476 QCOMPARE(model->cache()->msgFlags("a", 1), QStringList() << QLatin1String("\\Seen"));
2477 QCOMPARE(model->cache()->msgFlags("a", 2), QStringList());
2478 QCOMPARE(model->cache()->msgFlags("a", 3), QStringList() << QLatin1String("\\Answered"));
2480 cEmpty();
2481 justKeepTask();
2484 /** @short Bug #329204 -- spurious * ENABLED untagged responses from Kolab's IMAP servers */
2485 void ImapModelObtainSynchronizedMailboxTest::testQresyncExtraEnabled()
2487 helperTestQresyncNoChanges(EXTRA_ENABLED);
2490 /** @short Check that we can recover when a SELECT ends up in a BAD or NO response */
2491 void ImapModelObtainSynchronizedMailboxTest::testSelectRetryNoBad()
2493 FakeCapabilitiesInjector injector(model);
2494 injector.injectCapability("ESEARCH");
2495 injector.injectCapability("CONDSTORE");
2496 injector.injectCapability("QRESYNC");
2498 // Our first attempt fails with a NO response
2499 model->resyncMailbox(idxA);
2500 cClient(t.mk("SELECT a (CONDSTORE)\r\n"));
2501 cServer(t.last("NO go away\r\n"));
2502 cEmpty();
2503 checkNoTasks();
2505 // The server is even more creative now and returns a tagged BAD for increased fun factor
2506 model->resyncMailbox(idxA);
2507 cClient(t.mk("SELECT a (CONDSTORE)\r\n"));
2508 cServer(t.last("BAD pwned\r\n"));
2509 cEmpty();
2510 checkNoTasks();
2512 // But we're very persistent and never give up
2513 model->resyncMailbox(idxA);
2514 cClient(t.mk("SELECT a (CONDSTORE)\r\n"));
2515 cServer("* FLAGS (\\Answered \\Flagged \\Deleted \\Seen \\Draft Junk NonJunk $Forwarded)\r\n"
2516 "* OK [PERMANENTFLAGS (\\Answered \\Flagged \\Deleted \\Seen \\Draft Junk NonJunk $Forwarded \\*)] Flags permitted.\r\n"
2517 "* 0 EXISTS\r\n"
2518 "* OK [UIDVALIDITY 666] .\r\n"
2519 "* OK [UIDNEXT 15] .\r\n"
2520 "* OK [HIGHESTMODSEQ 333] .\r\n"
2521 + t.last("OK [READ-WRITE] feels better, doesn't it\r\n"));
2522 cEmpty();
2523 justKeepTask();
2525 // Now this is strange -- reselecting fails.
2526 // The whole point why we're doing this is to test failed-A -> B transtitions.
2527 model->resyncMailbox(idxA);
2528 cClient(t.mk("SELECT a (QRESYNC (666 333))\r\n"));
2529 cServer(t.last("BAD pwned\r\n"));
2530 cEmpty();
2531 checkNoTasks();
2533 // Let's see if we will have any luck with the other mailbox
2534 model->resyncMailbox(idxB);
2535 cClient(t.mk("SELECT b (CONDSTORE)\r\n"));
2536 cServer("* FLAGS (\\Answered \\Flagged \\Deleted \\Seen \\Draft Junk NonJunk $Forwarded)\r\n"
2537 "* OK [PERMANENTFLAGS (\\Answered \\Flagged \\Deleted \\Seen \\Draft Junk NonJunk $Forwarded \\*)] Flags permitted.\r\n"
2538 "* 0 EXISTS\r\n"
2539 "* OK [UIDVALIDITY 666] .\r\n"
2540 "* OK [UIDNEXT 15] .\r\n"
2541 "* OK [HIGHESTMODSEQ 333] .\r\n"
2542 + t.last("OK [READ-WRITE] feels better, doesn't it\r\n"));
2543 cEmpty();
2544 justKeepTask();
2547 /** @short Check that mailbox switchover which never completes and subsequent model destruction does not lead to segfault
2549 https://bugs.kde.org/show_bug.cgi?id=336090
2551 void ImapModelObtainSynchronizedMailboxTest::testDanglingSelect()
2553 helperTestQresyncNoChanges(JUST_QRESYNC);
2554 model->resyncMailbox(idxB);
2555 cClient(t.mk("SELECT b\r\n"));
2556 cEmpty();
2559 /** @short Test that we can detect broken servers which do not issue an untagged OK with the [CLOSED] response code */
2560 void ImapModelObtainSynchronizedMailboxTest::testQresyncNoClosed()
2562 helperTestQresyncNoChanges(JUST_QRESYNC);
2563 model->resyncMailbox(idxB);
2564 cClient(t.mk("SELECT b\r\n"));
2566 // The IMAP code should complain about a lack of the [CLOSED] response code
2567 ExpectSingleErrorHere blocker(this);
2568 cServer(t.last("OK selected\r\n"));
2572 /** @short Check that everything gets delivered to the older mailbox in absence of QRESYNC */
2573 void ImapModelObtainSynchronizedMailboxTest::testNoQresyncOutOfBounds()
2575 existsA = 3;
2576 uidValidityA = 6;
2577 uidMapA << 1 << 7 << 9;
2578 uidNextA = 16;
2579 helperSyncAWithMessagesEmptyState();
2580 model->resyncMailbox(idxB);
2581 QCOMPARE(msgListA.model()->rowCount(msgListA), 3);
2582 cClient(t.mk("SELECT b\r\n"));
2584 // This refers to message #4 in a mailbox which only has three messages, hence a failure.
2585 ExpectSingleErrorHere blocker(this);
2586 cServer("* 4 FETCH (UID 666 FLAGS())\r\n")
2590 /** @short Check that the responses are consumed by the older mailbox */
2591 void ImapModelObtainSynchronizedMailboxTest::testQresyncClosedHandover()
2593 Imap::Mailbox::SyncState sync;
2594 helperQresyncAInitial(sync);
2595 QStringList okFlags = QStringList() << "z";
2596 QCOMPARE(model->cache()->msgFlags("a", 10), okFlags);
2598 // OK, we're done. Now the actual test -- open another mailbox
2599 model->resyncMailbox(idxB);
2600 cClient(t.mk("SELECT b\r\n"));
2601 // this one should be eaten, but ignored
2602 cServer("* 3 FETCH (FLAGS ())\r\n");
2603 QCOMPARE(msgListA.child(2, 0).data(Imap::Mailbox::RoleMessageFlags).toStringList(), okFlags);
2604 QCOMPARE(model->cache()->msgFlags("a", 10), okFlags);
2605 cServer("* 4 EXISTS\r\n");
2606 cServer("* 4 FETCH (UID 333666333 FLAGS (PWNED))\r\n");
2607 cEmpty();
2608 // "4" shouldn't be in there either, of course
2609 QCOMPARE(model->cache()->mailboxSyncState("a"), sync);
2610 QCOMPARE(model->rowCount(msgListA), 3);
2611 cEmpty();
2612 cServer("* OK [CLOSED] Previous mailbox closed\r\n"
2613 "* 0 EXISTS\r\n"
2614 + t.last("OK selected\r\n"));
2615 justKeepTask();
2616 cEmpty();
2619 /** @short In absence of QRESYNC, all responses are delivered directly to the new ObtainSynchronizedMailboxTask */
2620 void ImapModelObtainSynchronizedMailboxTest::testNoClosedRouting()
2622 existsA = 3;
2623 uidValidityA = 6;
2624 uidMapA << 1 << 7 << 9;
2625 uidNextA = 16;
2626 helperSyncAWithMessagesEmptyState();
2627 model->resyncMailbox(idxB);
2628 cClient(t.mk("SELECT b\r\n"));
2629 cServer("* 1 EXISTS\r\n" + t.last("OK selected\r\n"));
2630 cClient(t.mk("UID SEARCH ALL\r\n"));
2631 cServer("* SEARCH 123\r\n" + t.last("OK uids\r\n"));
2632 cClient(t.mk("FETCH 1 (FLAGS)\r\n"));
2633 cServer("* 1 FETCH (FLAGS ())\r\n" + t.last("OK flags\r\n"));
2634 cEmpty();
2635 justKeepTask();
2638 #define REINIT_INDEXES_AFTER_LIST_CYCLE \
2639 model->rowCount(QModelIndex()); \
2640 QCoreApplication::processEvents(); \
2641 QCoreApplication::processEvents(); \
2642 QCOMPARE(model->rowCount(QModelIndex()), 26); \
2643 idxA = model->index(1, 0, QModelIndex()); \
2644 idxB = model->index(2, 0, QModelIndex()); \
2645 QCOMPARE(model->data(idxA, Qt::DisplayRole), QVariant(QLatin1String("a"))); \
2646 QCOMPARE(model->data(idxB, Qt::DisplayRole), QVariant(QLatin1String("b"))); \
2647 msgListA = model->index(0, 0, idxA); \
2648 msgListB = model->index(0, 0, idxB);
2650 /** @short Check that an UNSELECT resets that flag which expects a [CLOSED] */
2651 void ImapModelObtainSynchronizedMailboxTest::testUnselectClosed()
2653 FakeCapabilitiesInjector injector(model);
2654 injector.injectCapability("UNSELECT");
2655 Imap::Mailbox::SyncState sync;
2656 helperQresyncAInitial(sync);
2658 model->reloadMailboxList();
2659 REINIT_INDEXES_AFTER_LIST_CYCLE
2660 cClient(t.mk("UNSELECT\r\n"));
2661 cServer(t.last("OK unselected\r\n"));
2662 cEmpty();
2664 model->resyncMailbox(idxA);
2665 cClient(t.mk("SELECT a (QRESYNC (666 33 (2 9)))\r\n"));
2666 cServer("* 3 EXISTS\r\n"
2667 "* OK [UIDVALIDITY 666] .\r\n"
2668 "* OK [UIDNEXT 15] .\r\n"
2669 "* OK [HIGHESTMODSEQ 33] .\r\n"
2671 cServer(t.last("OK selected\r\n"));
2673 justKeepTask();
2674 cEmpty();
2677 TROJITA_HEADLESS_TEST( ImapModelObtainSynchronizedMailboxTest )