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() ) );
68 QVERIFY( list
->fetched() );
69 //QTest::qWait( 100 );
70 QCoreApplication::processEvents();
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() ) );
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"));
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 );
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"));
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()));
128 QVERIFY(list
->fetched());
129 QCoreApplication::processEvents();
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()));
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"));
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);
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"
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() ) );
191 QVERIFY( list
->fetched() );
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() ) );
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"));
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 );
234 QVERIFY( errorSpy
->isEmpty() );
237 /** @short Initial sync of a mailbox that contains some messages */
238 void ImapModelObtainSynchronizedMailboxTest::testSyncWithMessages()
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()
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()
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()
291 uidValidityA
= 333666;
292 for ( uint i
= 1; i
<= existsA
; ++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...
304 uidMapA
.removeLast();
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"
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() ) );
338 QVERIFY( listB
->fetched() );
340 QCOMPARE( model
->rowCount( msgListB
), 0 );
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"
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() ) );
358 QVERIFY( ! listA
->fetched() );
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"
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")));
411 QVERIFY( errorSpy
->isEmpty() );
414 /** @short Test whether a change in the UIDVALIDITY results in a complete resync */
415 void ImapModelObtainSynchronizedMailboxTest::testResyncUidValidity()
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()
441 for (uint i
= 1; i
<= existsA
; ++i
) {
444 uidNextA
= existsA
+ 2;
445 helperSyncAWithMessagesEmptyState();
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
;
458 sync
.setUidValidity(666);
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"));
476 sync
.setUnSeenCount(1);
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");
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
;
492 sync
.setUidValidity(666);
494 sync
.setUnSeenCount(3);
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"));
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");
521 /** @short Test UIDVALIDITY changes since the last cached state */
522 void ImapModelObtainSynchronizedMailboxTest::testCacheUidValidity()
524 Imap::Mailbox::SyncState sync
;
526 sync
.setUidValidity(333);
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());
545 model
->cache()->setMessageMetadata("a", 1, bundle
);
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"));
578 sync
.setUidValidity(666);
579 sync
.setUnSeenCount(3);
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");
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
;
595 sync
.setUidValidity(666);
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"));
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"));
620 sync
.setUnSeenCount(4);
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");
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
;
655 sync
.setUidValidity(666);
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");
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"));
678 sync
.setUnSeenCount(5);
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"));
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");
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
;
708 sync
.setUidValidity(666);
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"));
726 sync
.setUnSeenCount(5);
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
);
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");
755 /** @short New message arrives when syncing flags */
756 void ImapModelObtainSynchronizedMailboxTest::testCacheArrivalRaceDuringFlags()
758 Imap::Mailbox::SyncState sync
;
760 sync
.setUidValidity(666);
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"
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"));
791 sync
.setUnSeenCount(5);
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");
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
;
828 sync
.setUidValidity(666);
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");
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"));
851 sync
.setUnSeenCount(5);
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"));
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());
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
;
880 sync
.setUidValidity(666);
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"));
899 sync
.setUnSeenCount(4);
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"));
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());
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
;
927 sync
.setUidValidity(666);
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"));
946 sync
.setUnSeenCount(4);
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"));
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());
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
;
974 sync
.setUidValidity(666);
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"
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"));
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"));
1002 sync
.setUnSeenCount(4);
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());
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
;
1022 sync
.setUidValidity(666);
1023 sync
.setUidNext(15);
1024 sync
.setUnSeenCount(6);
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"));
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"
1046 "* 3 FETCH (FLAGS (a))\r\n"
1047 "* 4 FETCH (FLAGS (c))\r\n"
1049 cServer(t
.last("OK fetch\r\n"));
1052 sync
.setUnSeenCount(4);
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());
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
;
1072 sync
.setUidValidity(666);
1073 sync
.setUidNext(15);
1074 sync
.setUnSeenCount(3);
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);
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"));
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());
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
;
1114 sync
.setUidValidity(666);
1115 sync
.setUidNext(15);
1116 sync
.setUnSeenCount(3);
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);
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"));
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());
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
;
1158 sync
.setUidValidity(666);
1159 sync
.setUidNext(100);
1161 for (uint i
= 1; i
<= sync
.exists(); ++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"));
1173 // One more message arrives
1175 // The last of the previously known messages gets deleted
1177 // The very first arrival (which happened between the disconnect and this sync) goes away
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"
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"
1190 uidMap
<< 102 << 103 << 104 << 105 << 106;
1191 sync
.setUidNext(107);
1193 cClient(t
.mk("FETCH 1:13 (FLAGS)\r\n"));
1194 cServer("* 2 EXPUNGE\r\n");
1196 cServer("* 12 EXPUNGE\r\n");
1197 uidMap
.removeAt(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");
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");
1217 cServer(t
.last("OK uid fetch flags done\r\n"));
1219 sync
.setUidNext(110);
1220 sync
.setUnSeenCount(12);
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());
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
;
1255 sync
.setUidValidity(666);
1256 sync
.setUidNext(100);
1258 for (uint i
= 1; i
<= sync
.exists(); ++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)
1271 // Five more messages arrives; that means that the UIDNEXT is now at least on 105
1273 // The last of the previously known messages gets deleted
1275 // Right now, the UIDs are (1 3 4 6 ? ? ? ? ?)
1276 // And the first of the new arrivals will be gone, too.
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"));
1289 uidMap
<< 1 << 3 << 6 << 101 << 102 << 103 << 104;
1290 sync
.setUidNext(105);
1292 cClient(t
.mk("FETCH 1:7 (FLAGS)\r\n"));
1293 cServer("* 2 EXPUNGE\r\n");
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");
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");
1309 cServer(t
.last("OK uid fetch flags done\r\n"));
1311 sync
.setUidNext(110);
1312 sync
.setUnSeenCount(7);
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());
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
;
1343 sync
.setUidValidity(666);
1344 sync
.setUidNext(15);
1345 sync
.setHighestModSeq(33);
1346 sync
.setUnSeenCount(3);
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"));
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");
1373 /** @short Test changed HIGHESTMODSEQ */
1374 void ImapModelObtainSynchronizedMailboxTest::testCondstoreChangedFlags()
1376 FakeCapabilitiesInjector
injector(model
);
1377 injector
.injectCapability("CONDSTORE");
1378 Imap::Mailbox::SyncState sync
;
1380 sync
.setUidValidity(666);
1381 sync
.setUidNext(15);
1382 sync
.setHighestModSeq(33);
1383 sync
.setUnSeenCount(3);
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"));
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");
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
;
1422 sync
.setUidValidity(666);
1423 sync
.setUidNext(15);
1424 sync
.setHighestModSeq(33);
1425 sync
.setUnSeenCount(3);
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"));
1454 sync
.setUidNext(16);
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");
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
;
1474 sync
.setUidValidity(666);
1475 sync
.setUidNext(15);
1476 sync
.setHighestModSeq(33);
1477 sync
.setUnSeenCount(3);
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"));
1502 sync
.setUidNext(16);
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");
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
;
1520 sync
.setUidValidity(666);
1521 sync
.setUidNext(15);
1522 sync
.setHighestModSeq(33);
1523 sync
.setUnSeenCount(3);
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"));
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");
1558 /** @short Test decreased HIGHESTMODSEQ */
1559 void ImapModelObtainSynchronizedMailboxTest::testCondstoreDecreasedHighestModSeq()
1561 FakeCapabilitiesInjector
injector(model
);
1562 injector
.injectCapability("CONDSTORE");
1563 Imap::Mailbox::SyncState sync
;
1565 sync
.setUidValidity(666);
1566 sync
.setUidNext(15);
1567 sync
.setHighestModSeq(33);
1568 sync
.setUnSeenCount(3);
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"));
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");
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");
1626 helperSyncAWithMessagesEmptyState();
1629 helperSyncBNoMessages();
1631 injector
.injectCapability("ESEARCH");
1632 model
->switchToMailbox(idxA
);
1634 Imap::Mailbox::SyncState sync
;
1636 sync
.setUidValidity(666);
1637 sync
.setUidNext(10);
1638 sync
.setHighestModSeq(111);
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"
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);
1669 void ImapModelObtainSynchronizedMailboxTest::helperTestQresyncNoChanges(ModeForHelperTestQresyncNoChanges mode
)
1671 FakeCapabilitiesInjector
injector(model
);
1672 injector
.injectCapability("QRESYNC");
1673 Imap::Mailbox::SyncState sync
;
1675 sync
.setUidValidity(666);
1676 sync
.setUidNext(15);
1677 sync
.setHighestModSeq(33);
1678 sync
.setUnSeenCount(3);
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"));
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");
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
;
1724 sync
.setUidValidity(666);
1725 sync
.setUidNext(15);
1726 sync
.setHighestModSeq(33);
1727 sync
.setUnSeenCount(3);
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"));
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");
1764 /** @short Test QRESYNC using VANISHED EARLIER */
1765 void ImapModelObtainSynchronizedMailboxTest::testQresyncVanishedEarlier()
1767 FakeCapabilitiesInjector
injector(model
);
1768 injector
.injectCapability("QRESYNC");
1769 Imap::Mailbox::SyncState sync
;
1771 sync
.setUidValidity(666);
1772 sync
.setUidNext(15);
1773 sync
.setHighestModSeq(33);
1774 sync
.setUnSeenCount(3);
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"));
1793 sync
.setHighestModSeq(36);
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");
1806 /** @short Test QRESYNC when UIDVALIDITY changes */
1807 void ImapModelObtainSynchronizedMailboxTest::testQresyncUidValidity()
1809 FakeCapabilitiesInjector
injector(model
);
1810 injector
.injectCapability("QRESYNC");
1811 Imap::Mailbox::SyncState sync
;
1813 sync
.setUidValidity(666);
1814 sync
.setUidNext(15);
1815 sync
.setHighestModSeq(33);
1816 sync
.setUnSeenCount(3);
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"));
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);
1853 /** @short Test QRESYNC reporting changed flags */
1854 void ImapModelObtainSynchronizedMailboxTest::testQresyncNoModseqChangedFlags()
1856 FakeCapabilitiesInjector
injector(model
);
1857 injector
.injectCapability("QRESYNC");
1858 Imap::Mailbox::SyncState sync
;
1860 sync
.setUidValidity(666);
1861 sync
.setUidNext(15);
1862 sync
.setHighestModSeq(33);
1863 sync
.setUnSeenCount(3);
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"));
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");
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
;
1903 sync
.setUidValidity(666);
1904 sync
.setUidNext(15);
1905 sync
.setHighestModSeq(33);
1906 sync
.setUnSeenCount(3);
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"));
1933 sync
.setUnSeenCount(2);
1934 sync
.setHighestModSeq(0);
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());
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
;
1954 sync
.setUidValidity(666);
1955 sync
.setUidNext(15);
1956 sync
.setHighestModSeq(33);
1957 sync
.setUnSeenCount(3);
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"));
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");
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
;
2000 sync
.setUidValidity(666);
2001 sync
.setUidNext(15);
2002 sync
.setHighestModSeq(33);
2003 sync
.setUnSeenCount(3);
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"));
2026 sync
.setUidNext(20);
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");
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
;
2046 sync
.setUidValidity(666);
2047 sync
.setUidNext(15);
2048 sync
.setHighestModSeq(33);
2049 sync
.setUnSeenCount(3);
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"));
2071 sync
.setUnSeenCount(4);
2072 sync
.setUidNext(20);
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");
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
;
2094 sync
.setUidValidity(666);
2096 sync
.setHighestModSeq(10);
2097 sync
.setUnSeenCount(5);
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"));
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");
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"
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"));
2194 Imap::Mailbox::SyncState sync
;
2196 sync
.setUidValidity(1336643053);
2198 // The UIDNEXT shall be updated automatically
2199 sync
.setUidNext(1216);
2200 // unseen count is computed, too
2201 sync
.setUnSeenCount(2);
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"
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"));
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
;
2259 sync
.setUidValidity(333);
2261 sync
.setUidNext(666);
2263 uidMap
<< 10 << 20 << 30;
2264 model
->cache()->setMailboxSyncState("a", sync
);
2265 model
->cache()->setUidMapping("a", uidMap
);
2266 Imap::Mailbox::AbstractCache::MessageDataBundle msg10
, msg20
;
2268 msg10
.envelope
.subject
= "msg10";
2270 msg20
.envelope
.subject
= "msg20";
2272 // Prepare the body structure for this message
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"),
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
);
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);
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"));
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
;
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);
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"));
2377 sync
.setUidNext(256);
2378 sync
.setHighestModSeq(22);
2379 sync
.setUnSeenCount(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
);
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);
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")
2419 uidValidityA
= 1336686200;
2420 for (uint i
= 1; i
<= existsA
; ++i
)
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"
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"));
2460 Imap::Mailbox::SyncState state
;
2461 state
.setExists(existsA
);
2462 state
.setUidNext(uidNextA
);
2463 state
.setUidValidity(uidValidityA
);
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;
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"));
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"));
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"));
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"
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"));
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"));
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"
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"));
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"));
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()
2577 uidMapA
<< 1 << 7 << 9;
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");
2608 // "4" shouldn't be in there either, of course
2609 QCOMPARE(model
->cache()->mailboxSyncState("a"), sync
);
2610 QCOMPARE(model
->rowCount(msgListA
), 3);
2612 cServer("* OK [CLOSED] Previous mailbox closed\r\n"
2614 + t
.last("OK selected\r\n"));
2619 /** @short In absence of QRESYNC, all responses are delivered directly to the new ObtainSynchronizedMailboxTask */
2620 void ImapModelObtainSynchronizedMailboxTest::testNoClosedRouting()
2624 uidMapA
<< 1 << 7 << 9;
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"));
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"));
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"));
2677 TROJITA_HEADLESS_TEST( ImapModelObtainSynchronizedMailboxTest
)