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/>.
24 #include "LibMailboxSync.h"
25 #include "Utils/FakeCapabilitiesInjector.h"
26 #include "headless_test.h"
27 #include "Common/MetaTypes.h"
28 #include "Streams/FakeSocket.h"
29 #include "Imap/Model/Cache.h"
30 #include "Imap/Model/ItemRoles.h"
31 #include "Imap/Model/MemoryCache.h"
32 #include "Imap/Model/MailboxTree.h"
33 #include "Imap/Model/MsgListModel.h"
34 #include "Imap/Model/ThreadingMsgListModel.h"
35 #include "Imap/Tasks/KeepMailboxOpenTask.h"
37 ExpectSingleErrorHere::ExpectSingleErrorHere(LibMailboxSync
*syncer
):
40 m_syncer
->m_expectsError
= true;
43 ExpectSingleErrorHere::~ExpectSingleErrorHere()
45 if (m_syncer
->m_expectsError
) {
46 QFAIL("Expected an error from the IMAP model, but there wasn't one");
48 QCOMPARE(m_syncer
->errorSpy
->size(), 1);
49 m_syncer
->errorSpy
->removeFirst();
52 LibMailboxSync::LibMailboxSync(): m_fakeListCommand(true)
56 LibMailboxSync::~LibMailboxSync()
60 void LibMailboxSync::init()
62 m_verbose
= qgetenv("TROJITA_IMAP_DEBUG") == QByteArray("1");
63 m_expectsError
= false;
64 Imap::Mailbox::AbstractCache
* cache
= new Imap::Mailbox::MemoryCache(this);
65 factory
= new Streams::FakeSocketFactory(Imap::CONN_STATE_AUTHENTICATED
);
66 Imap::Mailbox::TaskFactoryPtr
taskFactory(new Imap::Mailbox::TestingTaskFactory());
67 taskFactoryUnsafe
= static_cast<Imap::Mailbox::TestingTaskFactory
*>(taskFactory
.get());
68 taskFactoryUnsafe
->fakeOpenConnectionTask
= true;
69 taskFactoryUnsafe
->fakeListChildMailboxes
= m_fakeListCommand
;
70 if (!fakeListChildMailboxesMap
.isEmpty()) {
71 taskFactoryUnsafe
->fakeListChildMailboxesMap
= fakeListChildMailboxesMap
;
73 taskFactoryUnsafe
->fakeListChildMailboxesMap
[ QLatin1String("") ] = QStringList() <<
74 QLatin1String("a") << QLatin1String("b") << QLatin1String("c") <<
75 QLatin1String("d") << QLatin1String("e") << QLatin1String("f") <<
76 QLatin1String("g") << QLatin1String("h") << QLatin1String("i") <<
77 QLatin1String("j") << QLatin1String("k") << QLatin1String("l") <<
78 QLatin1String("m") << QLatin1String("n") << QLatin1String("o") <<
79 QLatin1String("p") << QLatin1String("q") << QLatin1String("r") <<
80 QLatin1String("s") << QLatin1String("q") << QLatin1String("u") <<
81 QLatin1String("v") << QLatin1String("w") << QLatin1String("x") <<
82 QLatin1String("y") << QLatin1String("z");
84 model
= new Imap::Mailbox::Model(this, cache
, Imap::Mailbox::SocketFactoryPtr(factory
), std::move(taskFactory
));
85 errorSpy
= new QSignalSpy(model
, SIGNAL(imapError(QString
)));
86 netErrorSpy
= new QSignalSpy(model
, SIGNAL(networkError(QString
)));
87 connect(model
, SIGNAL(imapError(QString
)), this, SLOT(modelSignalsError(QString
)));
88 connect(model
, SIGNAL(logged(uint
,Common::LogMessage
)), this, SLOT(modelLogged(uint
,Common::LogMessage
)));
90 msgListModel
= new Imap::Mailbox::MsgListModel(this, model
);
92 threadingModel
= new Imap::Mailbox::ThreadingMsgListModel(this);
93 threadingModel
->setSourceModel(msgListModel
);
95 QCoreApplication::processEvents();
97 if (m_fakeListCommand
&& fakeListChildMailboxesMap
.isEmpty()) {
98 helperInitialListing();
102 void LibMailboxSync::modelSignalsError(const QString
&message
)
104 if (m_expectsError
) {
105 m_expectsError
= false;
108 QFAIL("Model emits an error");
112 void LibMailboxSync::modelLogged(uint parserId
, const Common::LogMessage
&message
)
117 qDebug() << "LOG" << parserId
<< message
.source
<<
118 (message
.message
.endsWith(QLatin1String("\r\n")) ?
119 message
.message
.left(message
.message
.size() - 2) : message
.message
);
122 void LibMailboxSync::helperInitialListing()
124 model
->setNetworkPolicy(Imap::Mailbox::NETWORK_ONLINE
);
125 model
->rowCount( QModelIndex() );
126 QCoreApplication::processEvents();
127 QCoreApplication::processEvents();
128 QCOMPARE( model
->rowCount( QModelIndex() ), 26 );
129 idxA
= model
->index( 1, 0, QModelIndex() );
130 idxB
= model
->index( 2, 0, QModelIndex() );
131 idxC
= model
->index( 3, 0, QModelIndex() );
132 QCOMPARE( model
->data( idxA
, Qt::DisplayRole
), QVariant(QLatin1String("a")) );
133 QCOMPARE( model
->data( idxB
, Qt::DisplayRole
), QVariant(QLatin1String("b")) );
134 QCOMPARE( model
->data( idxC
, Qt::DisplayRole
), QVariant(QLatin1String("c")) );
135 msgListA
= model
->index( 0, 0, idxA
);
136 msgListB
= model
->index( 0, 0, idxB
);
137 msgListC
= model
->index( 0, 0, idxC
);
146 void LibMailboxSync::cleanup()
150 msgListModel
->deleteLater();
152 threadingModel
->deleteLater();
154 taskFactoryUnsafe
= 0;
155 QVERIFY( errorSpy
->isEmpty() );
160 QCoreApplication::sendPostedEvents(0, QEvent::DeferredDelete
);
163 void LibMailboxSync::cleanupTestCase()
167 void LibMailboxSync::initTestCase()
169 Common::registerMetaTypes();
177 /** @short Helper: simulate sync of mailbox A that contains some messages from an empty state */
178 void LibMailboxSync::helperSyncAWithMessagesEmptyState()
180 // Ask the model to sync stuff
181 QCOMPARE( model
->rowCount( msgListA
), 0 );
182 helperSyncAFullSync();
185 /** @short Helper: perform a full sync of the mailbox A */
186 void LibMailboxSync::helperSyncAFullSync()
188 cClient(t
.mk("SELECT a\r\n"));
190 helperFakeExistsUidValidityUidNext();
192 // Verify that we indeed received what we wanted
193 Imap::Mailbox::TreeItemMsgList
* list
= dynamic_cast<Imap::Mailbox::TreeItemMsgList
*>( static_cast<Imap::Mailbox::TreeItem
*>( msgListA
.internalPointer() ) );
195 QVERIFY( ! list
->fetched() );
197 // The messages are added immediately, even when their UID is not known yet
198 QCOMPARE(static_cast<int>(list
->childrenCount(model
)), static_cast<int>(existsA
));
200 helperFakeUidSearch();
202 QCOMPARE( model
->rowCount( msgListA
), static_cast<int>( existsA
) );
203 QVERIFY( errorSpy
->isEmpty() );
208 if ( ! errorSpy
->isEmpty() )
209 qDebug() << errorSpy
->first();
210 QVERIFY( errorSpy
->isEmpty() );
214 QVERIFY( list
->fetched() );
216 // and the first mailbox is fully synced now.
219 void LibMailboxSync::helperFakeExistsUidValidityUidNext()
221 // Try to feed it with absolute minimum data
223 QTextStream
ss( &buf
);
224 ss
<< "* " << existsA
<< " EXISTS\r\n";
225 ss
<< "* OK [UIDVALIDITY " << uidValidityA
<< "] UIDs valid\r\n";
226 ss
<< "* OK [UIDNEXT " << uidNextA
<< "] Predicted next UID\r\n";
228 cServer(buf
+ t
.last("OK [READ-WRITE] Select completed.\r\n"));
231 void LibMailboxSync::helperFakeUidSearch( uint start
)
233 Q_ASSERT( start
< existsA
);
235 cClient(t
.mk("UID SEARCH ALL\r\n"));
237 cClient(t
.mk("UID SEARCH UID ") + QString::number( uidMapA
[ start
] ).toUtf8() + QByteArray(":*\r\n"));
241 QTextStream
ss( &buf
);
244 // Try to be nasty and confuse the model with out-of-order UIDs
245 Imap::Uids shuffledMap
= uidMapA
;
246 if ( shuffledMap
.size() > 2 )
247 qSwap(shuffledMap
[0], shuffledMap
[2]);
249 for ( uint i
= start
; i
< existsA
; ++i
) {
250 ss
<< " " << shuffledMap
[i
];
254 cServer(buf
+ t
.last("OK search\r\n"));
257 /** @short Helper: make the parser switch to mailbox B which is actually empty
259 This function is useful for making sure that the parser switches to another mailbox and will perform
260 a fresh SELECT when it goes back to the original mailbox.
262 void LibMailboxSync::helperSyncBNoMessages()
264 // Try to go to second mailbox
265 QCOMPARE( model
->rowCount( msgListB
), 0 );
266 model
->switchToMailbox( idxB
);
267 cClient(t
.mk("SELECT b\r\n"));
268 cServer(QByteArray("* OK [CLOSED] Closed.\r\n* 0 exists\r\n") + t
.last("ok completed\r\n"));
271 Imap::Mailbox::SyncState syncState
= model
->cache()->mailboxSyncState( QLatin1String("b") );
272 QCOMPARE( syncState
.exists(), 0u );
273 QCOMPARE( syncState
.isUsableForSyncing(), false );
274 QCOMPARE( syncState
.uidNext(), 0u );
275 QCOMPARE( syncState
.uidValidity(), 0u );
277 // Verify that we indeed received what we wanted
278 Q_ASSERT(msgListB
.isValid());
279 Imap::Mailbox::TreeItemMsgList
* list
= dynamic_cast<Imap::Mailbox::TreeItemMsgList
*>( static_cast<Imap::Mailbox::TreeItem
*>( msgListB
.internalPointer() ) );
281 QVERIFY( list
->fetched() );
284 QVERIFY( errorSpy
->isEmpty() );
287 /** @short Helper: synchronization of an empty mailbox A
289 Unlike helperSyncBNoMessages(), this function actually performs the sync with all required
290 responses like UIDVALIDITY and UIDNEXT.
292 It is the caller's responsibility to provide reasonable values for uidNextA and uidValidityA.
294 @see helperSyncBNoMessages()
296 void LibMailboxSync::helperSyncANoMessagesCompleteState()
298 QCOMPARE( model
->rowCount( msgListA
), 0 );
299 model
->switchToMailbox( idxA
);
300 cClient(t
.mk("SELECT a\r\n"));
301 cServer(QString::fromUtf8("* 0 exists\r\n* OK [uidnext %1] foo\r\n* ok [uidvalidity %2] bar\r\n"
302 ).arg(QString::number(uidNextA
), QString::number(uidValidityA
)).toUtf8()
303 + t
.last("ok completed\r\n"));
306 Imap::Mailbox::SyncState syncState
= model
->cache()->mailboxSyncState( QLatin1String("a") );
307 QCOMPARE( syncState
.exists(), 0u );
308 QCOMPARE( syncState
.isUsableForSyncing(), true );
309 QCOMPARE( syncState
.uidNext(), uidNextA
);
310 QCOMPARE( syncState
.uidValidity(), uidValidityA
);
315 helperVerifyUidMapA();
317 // Verify that we indeed received what we wanted
318 Imap::Mailbox::TreeItemMsgList
* list
= dynamic_cast<Imap::Mailbox::TreeItemMsgList
*>( static_cast<Imap::Mailbox::TreeItem
*>( msgListA
.internalPointer() ) );
320 QVERIFY( list
->fetched() );
323 QVERIFY( errorSpy
->isEmpty() );
327 /** @short Simulates what happens when mailbox A gets opened again, assuming that nothing has changed since the last time etc */
328 void LibMailboxSync::helperSyncAWithMessagesNoArrivals()
330 // assume we've got some messages from the last case
331 QCOMPARE( model
->rowCount( msgListA
), static_cast<int>(existsA
) );
332 model
->switchToMailbox( idxA
);
333 cClient(t
.mk("SELECT a\r\n"));
335 helperFakeExistsUidValidityUidNext();
337 // Verify that we indeed received what we wanted
338 Imap::Mailbox::TreeItemMsgList
* list
= dynamic_cast<Imap::Mailbox::TreeItemMsgList
*>( static_cast<Imap::Mailbox::TreeItem
*>( msgListA
.internalPointer() ) );
340 QVERIFY( list
->fetched() );
342 QCOMPARE( model
->rowCount( msgListA
), static_cast<int>(existsA
) );
343 QVERIFY( errorSpy
->isEmpty() );
348 if ( ! errorSpy
->isEmpty() )
349 qDebug() << errorSpy
->first();
350 QVERIFY( errorSpy
->isEmpty() );
354 QVERIFY( list
->fetched() );
357 /** @short Simulates fetching flags for messages 1:$exists */
358 void LibMailboxSync::helperSyncFlags()
360 QCoreApplication::processEvents();
361 QByteArray expectedFetch
= t
.mk("FETCH ") +
362 (existsA
== 1 ? QByteArray("1") : QByteArray("1:") + QString::number(existsA
).toUtf8()) +
363 QByteArray(" (FLAGS)\r\n");
364 cClient(expectedFetch
);
366 for (uint i
= 1; i
<= existsA
; ++i
) {
367 buf
+= "* " + QByteArray::number(i
) + " FETCH (FLAGS (";
376 buf
+= "\\Seen \\Answered";
379 buf
+= "\\Seen starred";
384 buf
+= "\\Seen \\Answered $NotJunk";
390 if (buf
.size() > 10*1024) {
391 // Flush the output buffer roughly every 10kB
396 // Now the ugly part -- we know that the Model has that habit of processing at most 100 responses at once and then returning
397 // from the event handler. The idea is to make sure that the GUI can run even in presence of incoming FLAGS in a 50k mailbox
398 // (or really anything else; the point is to avoid GUI blocking). Under normal circumstances, this shouldn't really matter as
399 // the event loop will take care of processing all the events, but with tests which "emulate" the event loop through
400 // repeatedly calling QCoreApplication::processEvents(), we might *very easily* leave some responses undelivered.
401 // This is where our crude hack comes into play -- we know that each message generated one response, so we just make sure that
402 // Model's responseReceived will surely runs enough times. Nasty, isn't it? Well, better than introducing a side channel from
403 // Model to signal "I really need to process more events, KTHXBYE", IMHO.
404 for (uint i
= 0; i
< (existsA
/ 100) + 1; ++i
)
405 QCoreApplication::processEvents();
407 cServer(buf
+ t
.last("OK yay\r\n"));
410 /** @short Helper: update flags for some message */
411 void LibMailboxSync::helperOneFlagUpdate( const QModelIndex
&message
)
413 cServer(QString::fromUtf8("* %1 FETCH (FLAGS (\\SeEn fOo bar))\r\n").arg( message
.row() + 1 ).toUtf8());
414 QStringList expectedFlags
;
415 expectedFlags
<< QLatin1String("\\Seen") << QLatin1String("fOo") << QLatin1String("bar");
416 expectedFlags
.sort();
417 QCOMPARE(message
.data(Imap::Mailbox::RoleMessageFlags
).toStringList(), expectedFlags
);
418 QVERIFY(errorSpy
->isEmpty());
421 void LibMailboxSync::helperSyncASomeNew( int number
)
423 QCOMPARE( model
->rowCount( msgListA
), static_cast<int>(existsA
) );
424 model
->switchToMailbox( idxA
);
425 cClient(t
.mk("SELECT a\r\n"));
427 uint oldExistsA
= existsA
;
428 for ( int i
= 0; i
< number
; ++i
) {
430 uidMapA
.append( uidNextA
);
433 helperFakeExistsUidValidityUidNext();
435 // Verify that we indeed received what we wanted
436 Imap::Mailbox::TreeItemMsgList
* list
= dynamic_cast<Imap::Mailbox::TreeItemMsgList
*>( static_cast<Imap::Mailbox::TreeItem
*>( msgListA
.internalPointer() ) );
438 QVERIFY( ! list
->fetched() );
440 helperFakeUidSearch( oldExistsA
);
441 QCoreApplication::processEvents();
442 QCoreApplication::processEvents();
443 QVERIFY( list
->fetched() );
445 QCOMPARE( model
->rowCount( msgListA
), static_cast<int>( existsA
) );
446 QVERIFY( errorSpy
->isEmpty() );
451 if ( ! errorSpy
->isEmpty() )
452 qDebug() << errorSpy
->first();
453 QVERIFY( errorSpy
->isEmpty() );
457 QVERIFY( list
->fetched() );
460 /** @short Helper: make sure that UID mapping is correct */
461 void LibMailboxSync::helperVerifyUidMapA()
463 QCOMPARE( model
->rowCount( msgListA
), static_cast<int>(existsA
) );
464 Q_ASSERT( static_cast<int>(existsA
) == uidMapA
.size() );
465 for ( int i
= 0; i
< uidMapA
.size(); ++i
) {
466 QModelIndex message
= model
->index( i
, 0, msgListA
);
467 Q_ASSERT( message
.isValid() );
468 QVERIFY( uidMapA
[i
] != 0 );
469 QCOMPARE( message
.data( Imap::Mailbox::RoleMessageUid
).toUInt(), uidMapA
[i
] );
473 /** @short Helper: verify that values recorded in the cache are valid */
474 void LibMailboxSync::helperCheckCache(bool ignoreUidNext
)
476 using namespace Imap::Mailbox
;
479 SyncState syncState
= model
->cache()->mailboxSyncState(QLatin1String("a"));
480 QCOMPARE(syncState
.exists(), existsA
);
481 QCOMPARE(syncState
.isUsableForSyncing(), true);
482 if (!ignoreUidNext
) {
483 QCOMPARE(syncState
.uidNext(), uidNextA
);
485 QCOMPARE(syncState
.uidValidity(), uidValidityA
);
486 QCOMPARE(model
->cache()->uidMapping(QLatin1String("a")), uidMapA
);
487 QCOMPARE(static_cast<uint
>(uidMapA
.size()), existsA
);
489 SyncState ssFromTree
= model
->findMailboxByName(QLatin1String("a"))->syncState
;
490 SyncState ssFromCache
= syncState
;
492 ssFromTree
.setUidNext(0);
493 ssFromCache
.setUidNext(0);
495 QCOMPARE(ssFromCache
, ssFromTree
);
497 if (model
->isNetworkAvailable()) {
498 // when offline, calling cEmpty would assert fail
502 QVERIFY(errorSpy
->isEmpty());
505 void LibMailboxSync::initialMessages(const uint exists
)
507 // Setup ten fake messages
510 for (uint i
= 1; i
<= existsA
; ++i
) {
513 uidNextA
= uidMapA
.last() + 1;
514 helperSyncAWithMessagesEmptyState();
516 for (uint i
= 1; i
<= existsA
; ++i
) {
517 QModelIndex messageIndex
= msgListA
.child(i
- 1, 0);
518 Q_ASSERT(messageIndex
.isValid());
519 QCOMPARE(messageIndex
.data(Imap::Mailbox::RoleMessageUid
).toUInt(), i
);
523 msgListModel
->setMailbox(idxA
);
524 QCoreApplication::processEvents();
525 QCoreApplication::processEvents();
528 /** @short Helper for creating a fake FETCH response with all usually fetched fields
530 This function will prepare a response mentioning a minimal set of ENVELOPE, UID, BODYSTRUCTURE etc. Please note that
531 the actual string won't be passed to the fake socket, but rather returned; this is needed because the fake socket can't accept
532 incremental data, but we have to feed it with stuff at once.
534 QByteArray
LibMailboxSync::helperCreateTrivialEnvelope(const uint seq
, const uint uid
, const QString
&subject
)
536 return QString::fromUtf8("* %1 FETCH (UID %2 RFC822.SIZE 89 ENVELOPE (NIL \"%3\" NIL NIL NIL NIL NIL NIL NIL NIL) "
537 "BODYSTRUCTURE (\"text\" \"plain\" () NIL NIL NIL 19 2 NIL NIL NIL NIL))\r\n").arg(
538 QString::number(seq
), QString::number(uid
), subject
).toUtf8();
541 /** @short Make sure that the only existing task is the KeepMailboxOpenTask and nothing else */
542 void LibMailboxSync::justKeepTask()
544 QCOMPARE(model
->taskModel()->rowCount(), 1);
545 QModelIndex parser1
= model
->taskModel()->index(0, 0);
546 QVERIFY(parser1
.isValid());
547 QCOMPARE(model
->taskModel()->rowCount(parser1
), 1);
548 QModelIndex firstTask
= parser1
.child(0, 0);
549 QVERIFY(firstTask
.isValid());
550 if (firstTask
.child(0, 0).isValid()) {
551 qDebug() << "Unexpected extra task:" << firstTask
.child(0, 0).data();
553 QVERIFY(!firstTask
.child(0, 0).isValid());
554 QCOMPARE(model
->accessParser(static_cast<Imap::Parser
*>(parser1
.internalPointer())).connState
,
555 Imap::CONN_STATE_SELECTED
);
556 Imap::Mailbox::KeepMailboxOpenTask
*keepTask
= dynamic_cast<Imap::Mailbox::KeepMailboxOpenTask
*>(static_cast<Imap::Mailbox::ImapTask
*>(firstTask
.internalPointer()));
558 QVERIFY(keepTask
->requestedEnvelopes
.isEmpty());
559 QVERIFY(keepTask
->requestedParts
.isEmpty());
560 QVERIFY(keepTask
->newArrivalsFetch
.isEmpty());
563 /** @short Check that there are no tasks associated with the single parser */
564 void LibMailboxSync::checkNoTasks()
566 QCOMPARE(model
->taskModel()->rowCount(), 1);
567 QModelIndex parser1
= model
->taskModel()->index(0, 0);
568 QVERIFY(parser1
.isValid());
569 QCOMPARE(model
->taskModel()->rowCount(parser1
), 0);
572 /** @short Find an item within a tree identified by a "path"
574 Based on a textual "path" like "1.2.3" or "6", find an index within the model which corresponds to that location.
575 Indexing is zero-based, so "6" means model->index(6, 0) while "1.2.3" actually refers to model->index(1, 0).child(2, 0).child(3, 0).
577 QModelIndex
LibMailboxSync::findIndexByPosition(const QAbstractItemModel
*model
, const QString
&where
)
579 QStringList list
= where
.split(QLatin1Char('.'));
580 Q_ASSERT(!list
.isEmpty());
581 QList
<QPair
<int,int>> items
;
582 Q_FOREACH(const QString
&item
, list
) {
583 QStringList rowColumn
= item
.split(QLatin1Char('c'));
584 Q_ASSERT(rowColumn
.size() >= 1);
585 Q_ASSERT(rowColumn
.size() <= 2);
587 int row
= rowColumn
[0].toInt(&ok
);
590 if (rowColumn
.size() == 2) {
591 column
= rowColumn
[1].toInt(&ok
);
594 items
<< qMakePair(row
, column
);
597 QModelIndex index
= QModelIndex();
598 for (auto it
= items
.constBegin(); it
!= items
.constEnd(); ++it
) {
599 index
= model
->index(it
->first
, it
->second
, index
);
600 if (it
+ 1 != items
.constEnd()) {
601 // this index is an intermediate one in the path, hence it should not really fail
602 Q_ASSERT(index
.isValid());
608 /** @short Forwarding function which allows this to work without explicitly befriending each and every test */
609 void LibMailboxSync::setModelNetworkPolicy(Imap::Mailbox::Model
*model
, const Imap::Mailbox::NetworkPolicy policy
)
611 model
->setNetworkPolicy(policy
);
614 /** @short Sync mailbox A with QRESYNC and some initial state, but nothing in memory */
615 void LibMailboxSync::helperQresyncAInitial(Imap::Mailbox::SyncState
&syncState
)
617 FakeCapabilitiesInjector
injector(model
);
618 injector
.injectCapability("QRESYNC");
619 syncState
.setExists(3);
620 syncState
.setUidValidity(666);
621 syncState
.setUidNext(15);
622 syncState
.setHighestModSeq(33);
623 syncState
.setUnSeenCount(3);
624 syncState
.setRecent(0);
626 uidMap
<< 6 << 9 << 10;
627 model
->cache()->setMailboxSyncState("a", syncState
);
628 model
->cache()->setUidMapping("a", uidMap
);
629 model
->cache()->setMsgFlags("a", 6, QStringList() << "x");
630 model
->cache()->setMsgFlags("a", 9, QStringList() << "y");
631 model
->cache()->setMsgFlags("a", 10, QStringList() << "z");
632 model
->resyncMailbox(idxA
);
633 cClient(t
.mk("SELECT a (QRESYNC (666 33 (2 9)))\r\n"));
634 cServer("* 3 EXISTS\r\n"
635 "* OK [UIDVALIDITY 666] .\r\n"
636 "* OK [UIDNEXT 15] .\r\n"
637 "* OK [HIGHESTMODSEQ 33] .\r\n"
639 cServer(t
.last("OK selected\r\n"));
646 /** @short Operator for QCOMPARE which acts on all data stored in the SyncState
648 This operator compares *everything*, including the hidden members.
650 bool operator==(const SyncState
&a
, const SyncState
&b
)
652 return a
.completelyEqualTo(b
);
660 /** @short Debug data dumper for QList<uint> */
662 char *toString(const QList
<uint
> &list
)
666 d
<< "QList<uint> (" << list
.size() << "items):";
667 Q_FOREACH(const uint item
, list
) {
670 return qstrdup(buf
.toUtf8().constData());
674 /** @short Debug data dumper for unit tests
676 Could be a bit confusing as it doesn't print out the hidden members. Therefore a simple x.setFlags(QStringList()) -- which merely
677 sets a value same as the default one -- will result in comparison failure, but this function wouldn't print the original cause.
680 char *toString(const Imap::Mailbox::SyncState
&syncState
)
685 return qstrdup(buf
.toUtf8().constData());
688 /** @short Debug data dumper for the MessageDataBundle */
690 char *toString(const Imap::Mailbox::AbstractCache::MessageDataBundle
&bundle
)
694 d
<< "UID:" << bundle
.uid
<< "Envelope:" << bundle
.envelope
<< "size:" << bundle
.size
<<
695 "bodystruct:" << bundle
.serializedBodyStructure
;
696 return qstrdup(buf
.toUtf8().constData());
700 char *toString(const NetDataRegexp
&x
)
703 return qstrdup(QByteArray("[regexp: " + x
.pattern
+ "]").constData());
705 return toString(QString::fromUtf8(x
.raw
));
711 NetDataRegexp
NetDataRegexp::fromData(const QByteArray
&data
)
713 return NetDataRegexp(data
, QByteArray(), false);
716 NetDataRegexp
NetDataRegexp::fromPattern(const QByteArray
&pattern
)
718 return NetDataRegexp(QByteArray(), pattern
, true);
721 NetDataRegexp::NetDataRegexp(const QByteArray
&raw
, const QByteArray
&pattern
, const bool isPattern
):
722 raw(raw
), pattern(pattern
), isPattern(isPattern
)
726 bool operator==(const NetDataRegexp
&a
, const NetDataRegexp
&b
)
728 Q_ASSERT(!a
.isPattern
);
729 Q_ASSERT(b
.isPattern
);
730 QByteArray rawData
= a
.raw
;
732 // QRegExp doesn't support multiline patterns
733 if (!rawData
.endsWith("\r\n")) {
734 // just a partial line
738 if (rawData
.contains("\r\n")) {
739 // multiple lines, we definitely cannot handle that
743 // ...but it would be a developer's mistake if the *pattern* included
744 if (b
.pattern
.contains("\r\n")) {
745 // that's a developer's error -- multine patterns are not support by Qt's QRegExp, and there's nothing else in Qt4
746 Q_ASSERT(!"CRLF in the regexp pattern, fix the test suite");
750 return QRegExp(QString::fromUtf8(b
.pattern
)).exactMatch(QString::fromUtf8(rawData
));