Warning, file /pim/trojita/tests/Utils/LibMailboxSync.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).
0001 /* Copyright (C) 2006 - 2014 Jan Kundrát <jkt@flaska.net> 0002 0003 This file is part of the Trojita Qt IMAP e-mail client, 0004 http://trojita.flaska.net/ 0005 0006 This program is free software; you can redistribute it and/or 0007 modify it under the terms of the GNU General Public License as 0008 published by the Free Software Foundation; either version 2 of 0009 the License or (at your option) version 3 or any later version 0010 accepted by the membership of KDE e.V. (or its successor approved 0011 by the membership of KDE e.V.), which shall act as a proxy 0012 defined in Section 14 of version 3 of the license. 0013 0014 This program is distributed in the hope that it will be useful, 0015 but WITHOUT ANY WARRANTY; without even the implied warranty of 0016 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 0017 GNU General Public License for more details. 0018 0019 You should have received a copy of the GNU General Public License 0020 along with this program. If not, see <http://www.gnu.org/licenses/>. 0021 */ 0022 0023 #include <QTest> 0024 #include <QRegularExpression> 0025 #include "LibMailboxSync.h" 0026 #include "Utils/FakeCapabilitiesInjector.h" 0027 #include "Common/MetaTypes.h" 0028 #include "Streams/FakeSocket.h" 0029 #include "Imap/Model/Cache.h" 0030 #include "Imap/Model/ItemRoles.h" 0031 #include "Imap/Model/MemoryCache.h" 0032 #include "Imap/Model/MailboxTree.h" 0033 #include "Imap/Model/MsgListModel.h" 0034 #include "Imap/Model/ThreadingMsgListModel.h" 0035 #include "Imap/Tasks/KeepMailboxOpenTask.h" 0036 0037 ExpectSingleErrorHere::ExpectSingleErrorHere(LibMailboxSync *syncer): 0038 m_syncer(syncer) 0039 { 0040 m_syncer->m_expectsError = true; 0041 } 0042 0043 ExpectSingleErrorHere::~ExpectSingleErrorHere() 0044 { 0045 if (m_syncer->m_expectsError) { 0046 QFAIL("Expected an error from the IMAP model, but there wasn't one"); 0047 } 0048 QCOMPARE(m_syncer->errorSpy->size(), 1); 0049 m_syncer->errorSpy->removeFirst(); 0050 } 0051 0052 LibMailboxSync::LibMailboxSync() 0053 : model(0) 0054 , msgListModel(0) 0055 , threadingModel(0) 0056 , factory(0) 0057 , taskFactoryUnsafe(0) 0058 , errorSpy(0) 0059 , netErrorSpy(0) 0060 , m_verbose(false) 0061 , m_expectsError(false) 0062 , m_fakeListCommand(true) 0063 , m_fakeOpenTask(true) 0064 , m_startTlsRequired(false) 0065 , m_initialConnectionState(Imap::CONN_STATE_AUTHENTICATED) 0066 { 0067 m_verbose = qgetenv("TROJITA_IMAP_DEBUG") == QByteArray("1"); 0068 } 0069 0070 LibMailboxSync::~LibMailboxSync() 0071 { 0072 } 0073 0074 void LibMailboxSync::setupLogging() 0075 { 0076 delete errorSpy; 0077 errorSpy = new QSignalSpy(model, SIGNAL(imapError(QString))); 0078 delete netErrorSpy; 0079 netErrorSpy = new QSignalSpy(model, SIGNAL(networkError(QString))); 0080 connect(model, &Imap::Mailbox::Model::imapError, this, &LibMailboxSync::modelSignalsError); 0081 connect(model, &Imap::Mailbox::Model::logged, this, &LibMailboxSync::modelLogged); 0082 } 0083 0084 void LibMailboxSync::init() 0085 { 0086 m_expectsError = false; 0087 auto cache = std::make_shared<Imap::Mailbox::MemoryCache>(); 0088 factory = new Streams::FakeSocketFactory(m_initialConnectionState); 0089 factory->setStartTlsRequired(m_startTlsRequired); 0090 Imap::Mailbox::TaskFactoryPtr taskFactory(new Imap::Mailbox::TestingTaskFactory()); 0091 taskFactoryUnsafe = static_cast<Imap::Mailbox::TestingTaskFactory*>(taskFactory.get()); 0092 taskFactoryUnsafe->fakeOpenConnectionTask = m_fakeOpenTask; 0093 taskFactoryUnsafe->fakeListChildMailboxes = m_fakeListCommand; 0094 if (!fakeListChildMailboxesMap.isEmpty()) { 0095 taskFactoryUnsafe->fakeListChildMailboxesMap = fakeListChildMailboxesMap; 0096 } else { 0097 taskFactoryUnsafe->fakeListChildMailboxesMap[ QLatin1String("") ] = QStringList() << 0098 QStringLiteral("a") << QStringLiteral("b") << QStringLiteral("c") << 0099 QStringLiteral("d") << QStringLiteral("e") << QStringLiteral("f") << 0100 QStringLiteral("g") << QStringLiteral("h") << QStringLiteral("i") << 0101 QStringLiteral("j") << QStringLiteral("k") << QStringLiteral("l") << 0102 QStringLiteral("m") << QStringLiteral("n") << QStringLiteral("o") << 0103 QStringLiteral("p") << QStringLiteral("q") << QStringLiteral("r") << 0104 QStringLiteral("s") << QStringLiteral("q") << QStringLiteral("u") << 0105 QStringLiteral("v") << QStringLiteral("w") << QStringLiteral("x") << 0106 QStringLiteral("y") << QStringLiteral("z"); 0107 } 0108 model = new Imap::Mailbox::Model(this, cache, Imap::Mailbox::SocketFactoryPtr(factory), std::move(taskFactory)); 0109 model->setObjectName(QStringLiteral("imapModel")); 0110 setupLogging(); 0111 0112 msgListModel = new Imap::Mailbox::MsgListModel(this, model); 0113 msgListModel->setObjectName(QStringLiteral("msgListModel")); 0114 0115 threadingModel = new Imap::Mailbox::ThreadingMsgListModel(this); 0116 threadingModel->setSourceModel(msgListModel); 0117 threadingModel->setObjectName(QStringLiteral("threadingModel")); 0118 0119 QCoreApplication::processEvents(); 0120 0121 if (m_fakeListCommand && fakeListChildMailboxesMap.isEmpty()) { 0122 helperInitialListing(); 0123 } 0124 } 0125 0126 void LibMailboxSync::modelSignalsError(const QString &message) 0127 { 0128 if (m_expectsError) { 0129 m_expectsError = false; 0130 } else { 0131 qDebug() << message; 0132 QFAIL("Model emits an error"); 0133 } 0134 } 0135 0136 void LibMailboxSync::modelLogged(uint parserId, const Common::LogMessage &message) 0137 { 0138 if (!m_verbose) 0139 return; 0140 0141 qDebug() << "LOG" << parserId << message.source << 0142 (message.message.endsWith(QLatin1String("\r\n")) ? 0143 message.message.left(message.message.size() - 2) : message.message); 0144 } 0145 0146 void LibMailboxSync::helperInitialListing() 0147 { 0148 model->setNetworkPolicy(Imap::Mailbox::NETWORK_ONLINE); 0149 model->rowCount( QModelIndex() ); 0150 QCoreApplication::processEvents(); 0151 QCoreApplication::processEvents(); 0152 QCOMPARE( model->rowCount( QModelIndex() ), 26 ); 0153 idxA = model->index( 1, 0, QModelIndex() ); 0154 idxB = model->index( 2, 0, QModelIndex() ); 0155 idxC = model->index( 3, 0, QModelIndex() ); 0156 QCOMPARE( model->data( idxA, Qt::DisplayRole ), QVariant(QLatin1String("a")) ); 0157 QCOMPARE( model->data( idxB, Qt::DisplayRole ), QVariant(QLatin1String("b")) ); 0158 QCOMPARE( model->data( idxC, Qt::DisplayRole ), QVariant(QLatin1String("c")) ); 0159 msgListA = model->index( 0, 0, idxA ); 0160 msgListB = model->index( 0, 0, idxB ); 0161 msgListC = model->index( 0, 0, idxC ); 0162 cEmpty(); 0163 t.reset(); 0164 existsA = 0; 0165 uidNextA = 0; 0166 uidValidityA = 0; 0167 uidMapA.clear(); 0168 } 0169 0170 void LibMailboxSync::cleanup() 0171 { 0172 delete model; 0173 model = 0; 0174 if (msgListModel) { 0175 msgListModel->deleteLater(); 0176 } 0177 msgListModel = 0; 0178 if (threadingModel) { 0179 threadingModel->deleteLater(); 0180 } 0181 threadingModel = 0; 0182 taskFactoryUnsafe = 0; 0183 QVERIFY(errorSpy->isEmpty()); 0184 delete errorSpy; 0185 errorSpy = 0; 0186 delete netErrorSpy; 0187 netErrorSpy = 0; 0188 QCoreApplication::sendPostedEvents(0, QEvent::DeferredDelete); 0189 } 0190 0191 void LibMailboxSync::cleanupTestCase() 0192 { 0193 } 0194 0195 void LibMailboxSync::initTestCase() 0196 { 0197 // Some of the tests (hi, QNetworkAccessManager) might want to talk to actual network bearer plugins. 0198 // We do not want that in a unit test, so let's sanitize our env a little bit. 0199 qunsetenv("DBUS_SESSION_BUS_ADDRESS"); 0200 qputenv("DBUS_SYSTEM_BUS_ADDRESS", "non-existing-pwned"); 0201 qputenv("QT_EXCLUDE_GENERIC_BEARER", "1"); 0202 0203 Common::registerMetaTypes(); 0204 model = 0; 0205 msgListModel = 0; 0206 threadingModel = 0; 0207 errorSpy = 0; 0208 netErrorSpy = 0; 0209 } 0210 0211 /** @short Helper: simulate sync of mailbox A that contains some messages from an empty state */ 0212 void LibMailboxSync::helperSyncAWithMessagesEmptyState() 0213 { 0214 // Ask the model to sync stuff 0215 QCOMPARE( model->rowCount( msgListA ), 0 ); 0216 helperSyncAFullSync(); 0217 } 0218 0219 /** @short Helper: perform a full sync of the mailbox A */ 0220 void LibMailboxSync::helperSyncAFullSync() 0221 { 0222 cClient(t.mk("SELECT a\r\n")); 0223 0224 helperFakeExistsUidValidityUidNext(); 0225 0226 // Verify that we indeed received what we wanted 0227 Imap::Mailbox::TreeItemMsgList* list = dynamic_cast<Imap::Mailbox::TreeItemMsgList*>( static_cast<Imap::Mailbox::TreeItem*>( msgListA.internalPointer() ) ); 0228 Q_ASSERT( list ); 0229 QVERIFY( ! list->fetched() ); 0230 0231 // The messages are added immediately, even when their UID is not known yet 0232 QCOMPARE(static_cast<int>(list->childrenCount(model)), static_cast<int>(existsA)); 0233 0234 helperFakeUidSearch(); 0235 0236 QCOMPARE( model->rowCount( msgListA ), static_cast<int>( existsA ) ); 0237 QVERIFY( errorSpy->isEmpty() ); 0238 0239 helperSyncFlags(); 0240 0241 // No errors 0242 if ( ! errorSpy->isEmpty() ) 0243 qDebug() << errorSpy->first(); 0244 QVERIFY( errorSpy->isEmpty() ); 0245 0246 helperCheckCache(); 0247 0248 QVERIFY( list->fetched() ); 0249 0250 // and the first mailbox is fully synced now. 0251 } 0252 0253 void LibMailboxSync::helperFakeExistsUidValidityUidNext() 0254 { 0255 // Try to feed it with absolute minimum data 0256 QByteArray buf; 0257 QTextStream ss( &buf ); 0258 ss << "* " << existsA << " EXISTS\r\n"; 0259 ss << "* OK [UIDVALIDITY " << uidValidityA << "] UIDs valid\r\n"; 0260 ss << "* OK [UIDNEXT " << uidNextA << "] Predicted next UID\r\n"; 0261 ss.flush(); 0262 cServer(buf + t.last("OK [READ-WRITE] Select completed.\r\n")); 0263 } 0264 0265 void LibMailboxSync::helperFakeUidSearch( uint start ) 0266 { 0267 Q_ASSERT( start < existsA ); 0268 if ( start == 0 ) { 0269 cClient(t.mk("UID SEARCH ALL\r\n")); 0270 } else { 0271 cClient(t.mk("UID SEARCH UID ") + QString::number( uidMapA[ start ] ).toUtf8() + QByteArray(":*\r\n")); 0272 } 0273 0274 QByteArray buf; 0275 QTextStream ss( &buf ); 0276 ss << "* SEARCH"; 0277 0278 // Try to be nasty and confuse the model with out-of-order UIDs 0279 Imap::Uids shuffledMap = uidMapA; 0280 if ( shuffledMap.size() > 2 ) 0281 qSwap(shuffledMap[0], shuffledMap[2]); 0282 0283 for ( uint i = start; i < existsA; ++i ) { 0284 ss << " " << shuffledMap[i]; 0285 } 0286 ss << "\r\n"; 0287 ss.flush(); 0288 cServer(buf + t.last("OK search\r\n")); 0289 } 0290 0291 /** @short Helper: make the parser switch to mailbox B which is actually empty 0292 0293 This function is useful for making sure that the parser switches to another mailbox and will perform 0294 a fresh SELECT when it goes back to the original mailbox. 0295 */ 0296 void LibMailboxSync::helperSyncBNoMessages() 0297 { 0298 // Try to go to second mailbox 0299 QCOMPARE( model->rowCount( msgListB ), 0 ); 0300 model->switchToMailbox( idxB ); 0301 cClient(t.mk("SELECT b\r\n")); 0302 cServer(QByteArray("* OK [CLOSED] Closed.\r\n* 0 exists\r\n") + t.last("ok completed\r\n")); 0303 0304 // Check the cache 0305 Imap::Mailbox::SyncState syncState = model->cache()->mailboxSyncState( QStringLiteral("b") ); 0306 QCOMPARE( syncState.exists(), 0u ); 0307 QCOMPARE( syncState.isUsableForSyncing(), false ); 0308 QCOMPARE( syncState.uidNext(), 0u ); 0309 QCOMPARE( syncState.uidValidity(), 0u ); 0310 0311 // Verify that we indeed received what we wanted 0312 Q_ASSERT(msgListB.isValid()); 0313 Imap::Mailbox::TreeItemMsgList* list = dynamic_cast<Imap::Mailbox::TreeItemMsgList*>( static_cast<Imap::Mailbox::TreeItem*>( msgListB.internalPointer() ) ); 0314 Q_ASSERT( list ); 0315 QVERIFY( list->fetched() ); 0316 0317 cEmpty(); 0318 QVERIFY( errorSpy->isEmpty() ); 0319 } 0320 0321 /** @short Helper: synchronization of an empty mailbox A 0322 0323 Unlike helperSyncBNoMessages(), this function actually performs the sync with all required 0324 responses like UIDVALIDITY and UIDNEXT. 0325 0326 It is the caller's responsibility to provide reasonable values for uidNextA and uidValidityA. 0327 0328 @see helperSyncBNoMessages() 0329 */ 0330 void LibMailboxSync::helperSyncANoMessagesCompleteState() 0331 { 0332 QCOMPARE( model->rowCount( msgListA ), 0 ); 0333 model->switchToMailbox( idxA ); 0334 cClient(t.mk("SELECT a\r\n")); 0335 cServer(QString::fromUtf8("* 0 exists\r\n* OK [uidnext %1] foo\r\n* ok [uidvalidity %2] bar\r\n" 0336 ).arg(QString::number(uidNextA), QString::number(uidValidityA)).toUtf8() 0337 + t.last("ok completed\r\n")); 0338 0339 // Check the cache 0340 Imap::Mailbox::SyncState syncState = model->cache()->mailboxSyncState( QStringLiteral("a") ); 0341 QCOMPARE( syncState.exists(), 0u ); 0342 QCOMPARE( syncState.isUsableForSyncing(), true ); 0343 QCOMPARE( syncState.uidNext(), uidNextA ); 0344 QCOMPARE( syncState.uidValidity(), uidValidityA ); 0345 0346 existsA = 0; 0347 uidMapA.clear(); 0348 helperCheckCache(); 0349 helperVerifyUidMapA(); 0350 0351 // Verify that we indeed received what we wanted 0352 Imap::Mailbox::TreeItemMsgList* list = dynamic_cast<Imap::Mailbox::TreeItemMsgList*>( static_cast<Imap::Mailbox::TreeItem*>( msgListA.internalPointer() ) ); 0353 Q_ASSERT( list ); 0354 QVERIFY( list->fetched() ); 0355 0356 cEmpty(); 0357 QVERIFY( errorSpy->isEmpty() ); 0358 } 0359 0360 0361 /** @short Simulates what happens when mailbox A gets opened again, assuming that nothing has changed since the last time etc */ 0362 void LibMailboxSync::helperSyncAWithMessagesNoArrivals() 0363 { 0364 // assume we've got some messages from the last case 0365 QCOMPARE( model->rowCount( msgListA ), static_cast<int>(existsA) ); 0366 model->switchToMailbox( idxA ); 0367 cClient(t.mk("SELECT a\r\n")); 0368 0369 helperFakeExistsUidValidityUidNext(); 0370 0371 // Verify that we indeed received what we wanted 0372 Imap::Mailbox::TreeItemMsgList* list = dynamic_cast<Imap::Mailbox::TreeItemMsgList*>( static_cast<Imap::Mailbox::TreeItem*>( msgListA.internalPointer() ) ); 0373 Q_ASSERT( list ); 0374 QVERIFY( list->fetched() ); 0375 0376 QCOMPARE( model->rowCount( msgListA ), static_cast<int>(existsA) ); 0377 QVERIFY( errorSpy->isEmpty() ); 0378 0379 helperSyncFlags(); 0380 0381 // No errors 0382 if ( ! errorSpy->isEmpty() ) 0383 qDebug() << errorSpy->first(); 0384 QVERIFY( errorSpy->isEmpty() ); 0385 0386 helperCheckCache(); 0387 0388 QVERIFY( list->fetched() ); 0389 } 0390 0391 /** @short Simulates fetching flags for messages 1:$exists */ 0392 void LibMailboxSync::helperSyncFlags() 0393 { 0394 QCoreApplication::processEvents(); 0395 QByteArray expectedFetch = t.mk("FETCH ") + 0396 (existsA == 1 ? QByteArray("1") : QByteArray("1:") + QString::number(existsA).toUtf8()) + 0397 QByteArray(" (FLAGS)\r\n"); 0398 cClient(expectedFetch); 0399 QByteArray buf; 0400 for (uint i = 1; i <= existsA; ++i) { 0401 buf += "* " + QByteArray::number(i) + " FETCH (FLAGS ("; 0402 switch (i % 10) { 0403 case 0: 0404 case 1: 0405 buf += "\\Seen"; 0406 break; 0407 case 2: 0408 case 3: 0409 case 4: 0410 buf += "\\Seen \\Answered"; 0411 break; 0412 case 5: 0413 buf += "\\Seen starred"; 0414 break; 0415 case 6: 0416 case 7: 0417 case 8: 0418 buf += "\\Seen \\Answered $NotJunk"; 0419 break; 0420 case 9: 0421 break; 0422 } 0423 buf += "))\r\n"; 0424 if (buf.size() > 10*1024) { 0425 // Flush the output buffer roughly every 10kB 0426 cServer(buf); 0427 buf.clear(); 0428 } 0429 } 0430 // Now the ugly part -- we know that the Model has that habit of processing at most 100 responses at once and then returning 0431 // 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 0432 // (or really anything else; the point is to avoid GUI blocking). Under normal circumstances, this shouldn't really matter as 0433 // the event loop will take care of processing all the events, but with tests which "emulate" the event loop through 0434 // repeatedly calling QCoreApplication::processEvents(), we might *very easily* leave some responses undelivered. 0435 // This is where our crude hack comes into play -- we know that each message generated one response, so we just make sure that 0436 // Model's responseReceived will surely runs enough times. Nasty, isn't it? Well, better than introducing a side channel from 0437 // Model to signal "I really need to process more events, KTHXBYE", IMHO. 0438 for (uint i = 0; i < (existsA / 100) + 1; ++i) 0439 QCoreApplication::processEvents(); 0440 0441 cServer(buf + t.last("OK yay\r\n")); 0442 } 0443 0444 /** @short Helper: update flags for some message */ 0445 void LibMailboxSync::helperOneFlagUpdate( const QModelIndex &message ) 0446 { 0447 cServer(QString::fromUtf8("* %1 FETCH (FLAGS (\\SeEn fOo bar))\r\n").arg( message.row() + 1 ).toUtf8()); 0448 QStringList expectedFlags; 0449 expectedFlags << QStringLiteral("\\Seen") << QStringLiteral("fOo") << QStringLiteral("bar"); 0450 expectedFlags.sort(); 0451 QCOMPARE(message.data(Imap::Mailbox::RoleMessageFlags).toStringList(), expectedFlags); 0452 QVERIFY(errorSpy->isEmpty()); 0453 } 0454 0455 void LibMailboxSync::helperSyncASomeNew( int number ) 0456 { 0457 QCOMPARE( model->rowCount( msgListA ), static_cast<int>(existsA) ); 0458 model->switchToMailbox( idxA ); 0459 cClient(t.mk("SELECT a\r\n")); 0460 0461 uint oldExistsA = existsA; 0462 for ( int i = 0; i < number; ++i ) { 0463 ++existsA; 0464 uidMapA.append( uidNextA ); 0465 ++uidNextA; 0466 } 0467 helperFakeExistsUidValidityUidNext(); 0468 0469 // Verify that we indeed received what we wanted 0470 Imap::Mailbox::TreeItemMsgList* list = dynamic_cast<Imap::Mailbox::TreeItemMsgList*>( static_cast<Imap::Mailbox::TreeItem*>( msgListA.internalPointer() ) ); 0471 Q_ASSERT( list ); 0472 QVERIFY( ! list->fetched() ); 0473 0474 helperFakeUidSearch( oldExistsA ); 0475 QCoreApplication::processEvents(); 0476 QCoreApplication::processEvents(); 0477 QVERIFY( list->fetched() ); 0478 0479 QCOMPARE( model->rowCount( msgListA ), static_cast<int>( existsA ) ); 0480 QVERIFY( errorSpy->isEmpty() ); 0481 0482 helperSyncFlags(); 0483 0484 // No errors 0485 if ( ! errorSpy->isEmpty() ) 0486 qDebug() << errorSpy->first(); 0487 QVERIFY( errorSpy->isEmpty() ); 0488 0489 helperCheckCache(); 0490 0491 QVERIFY( list->fetched() ); 0492 } 0493 0494 /** @short Helper: make sure that UID mapping is correct */ 0495 void LibMailboxSync::helperVerifyUidMapA() 0496 { 0497 QCOMPARE( model->rowCount( msgListA ), static_cast<int>(existsA) ); 0498 Q_ASSERT( static_cast<int>(existsA) == uidMapA.size() ); 0499 for ( int i = 0; i < uidMapA.size(); ++i ) { 0500 QModelIndex message = model->index( i, 0, msgListA ); 0501 Q_ASSERT( message.isValid() ); 0502 QVERIFY( uidMapA[i] != 0 ); 0503 QCOMPARE( message.data( Imap::Mailbox::RoleMessageUid ).toUInt(), uidMapA[i] ); 0504 } 0505 } 0506 0507 /** @short Helper: verify that values recorded in the cache are valid */ 0508 void LibMailboxSync::helperCheckCache(bool ignoreUidNext) 0509 { 0510 using namespace Imap::Mailbox; 0511 0512 // Check the cache 0513 SyncState syncState = model->cache()->mailboxSyncState(QStringLiteral("a")); 0514 QCOMPARE(syncState.exists(), existsA); 0515 QCOMPARE(syncState.isUsableForSyncing(), true); 0516 if (!ignoreUidNext) { 0517 QCOMPARE(syncState.uidNext(), uidNextA); 0518 } 0519 QCOMPARE(syncState.uidValidity(), uidValidityA); 0520 QCOMPARE(model->cache()->uidMapping(QLatin1String("a")), uidMapA); 0521 QCOMPARE(static_cast<uint>(uidMapA.size()), existsA); 0522 0523 SyncState ssFromTree = model->findMailboxByName(QStringLiteral("a"))->syncState; 0524 SyncState ssFromCache = syncState; 0525 if (ignoreUidNext) { 0526 ssFromTree.setUidNext(0); 0527 ssFromCache.setUidNext(0); 0528 } 0529 QCOMPARE(ssFromCache, ssFromTree); 0530 0531 if (model->isNetworkAvailable()) { 0532 // when offline, calling cEmpty would assert fail 0533 cEmpty(); 0534 } 0535 0536 QVERIFY(errorSpy->isEmpty()); 0537 } 0538 0539 void LibMailboxSync::initialMessages(const uint exists) 0540 { 0541 // Setup ten fake messages 0542 existsA = exists; 0543 uidValidityA = 333; 0544 for (uint i = 1; i <= existsA; ++i) { 0545 uidMapA << i; 0546 } 0547 uidNextA = uidMapA.last() + 1; 0548 helperSyncAWithMessagesEmptyState(); 0549 0550 for (uint i = 1; i <= existsA; ++i) { 0551 QModelIndex messageIndex = msgListA.model()->index(i - 1, 0, msgListA); 0552 Q_ASSERT(messageIndex.isValid()); 0553 QCOMPARE(messageIndex.data(Imap::Mailbox::RoleMessageUid).toUInt(), i); 0554 } 0555 0556 // open the mailbox 0557 msgListModel->setMailbox(idxA); 0558 QCoreApplication::processEvents(); 0559 QCoreApplication::processEvents(); 0560 } 0561 0562 /** @short Helper for creating a fake FETCH response with all usually fetched fields 0563 0564 This function will prepare a response mentioning a minimal set of ENVELOPE, UID, BODYSTRUCTURE etc. Please note that 0565 the actual string won't be passed to the fake socket, but rather returned; this is needed because the fake socket can't accept 0566 incremental data, but we have to feed it with stuff at once. 0567 */ 0568 QByteArray LibMailboxSync::helperCreateTrivialEnvelope(const uint seq, const uint uid, const QString &subject) 0569 { 0570 return QString::fromUtf8("* %1 FETCH (UID %2 RFC822.SIZE 89 ENVELOPE (NIL \"%3\" NIL NIL NIL NIL NIL NIL NIL NIL) " 0571 "BODYSTRUCTURE (\"text\" \"plain\" () NIL NIL NIL 19 2 NIL NIL NIL NIL))\r\n").arg( 0572 QString::number(seq), QString::number(uid), subject ).toUtf8(); 0573 } 0574 0575 QByteArray LibMailboxSync::helperCreateTrivialEnvelope(const uint seq, const uint uid, const QString &subject, 0576 const QString &from, const QString &bodyStructure) 0577 { 0578 auto addr = Imap::Message::MailAddress::fromNameAndMail(QString(), from); 0579 return QString::fromUtf8("* %1 FETCH (UID %2 ENVELOPE (NIL \"%3\" ((NIL NIL \"%4\" \"%5\")) NIL NIL NIL NIL NIL NIL NIL) " 0580 "BODYSTRUCTURE (%6))\r\n").arg( 0581 QString::number(seq), QString::number(uid), subject, addr.mailbox, addr.host, bodyStructure).toUtf8(); 0582 } 0583 0584 /** @short Make sure that the only existing task is the KeepMailboxOpenTask and nothing else */ 0585 void LibMailboxSync::justKeepTask() 0586 { 0587 QCOMPARE(model->taskModel()->rowCount(), 1); 0588 QModelIndex parser1 = model->taskModel()->index(0, 0); 0589 QVERIFY(parser1.isValid()); 0590 QCOMPARE(model->taskModel()->rowCount(parser1), 1); 0591 QModelIndex firstTask = parser1.model()->index(0, 0, parser1); 0592 QVERIFY(firstTask.isValid()); 0593 if (firstTask.model()->index(0, 0, firstTask).isValid()) { 0594 qDebug() << "Unexpected extra task:" << firstTask.model()->index(0, 0, firstTask).data(); 0595 } 0596 QVERIFY(!firstTask.model()->index(0, 0, firstTask).isValid()); 0597 QCOMPARE(model->accessParser(static_cast<Imap::Parser*>(parser1.internalPointer())).connState, 0598 Imap::CONN_STATE_SELECTED); 0599 Imap::Mailbox::KeepMailboxOpenTask *keepTask = dynamic_cast<Imap::Mailbox::KeepMailboxOpenTask*>(static_cast<Imap::Mailbox::ImapTask*>(firstTask.internalPointer())); 0600 QVERIFY(keepTask); 0601 QVERIFY(keepTask->requestedEnvelopes.isEmpty()); 0602 QVERIFY(keepTask->requestedParts.isEmpty()); 0603 QVERIFY(keepTask->newArrivalsFetch.isEmpty()); 0604 } 0605 0606 /** @short Check that there are no tasks associated with the single parser */ 0607 void LibMailboxSync::checkNoTasks() 0608 { 0609 QCOMPARE(model->taskModel()->rowCount(), 1); 0610 QModelIndex parser1 = model->taskModel()->index(0, 0); 0611 QVERIFY(parser1.isValid()); 0612 QCOMPARE(model->taskModel()->rowCount(parser1), 0); 0613 } 0614 0615 /** @short Find an item within a tree identified by a "path" 0616 0617 Based on a textual "path" like "1.2.3" or "6", find an index within the model which corresponds to that location. 0618 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). 0619 */ 0620 QModelIndex LibMailboxSync::findIndexByPosition(const QAbstractItemModel *model, const QString &where) 0621 { 0622 QStringList list = where.split(QLatin1Char('.')); 0623 Q_ASSERT(!list.isEmpty()); 0624 QList<QPair<int,int>> items; 0625 Q_FOREACH(const QString &item, list) { 0626 QStringList rowColumn = item.split(QLatin1Char('c')); 0627 Q_ASSERT(rowColumn.size() >= 1); 0628 Q_ASSERT(rowColumn.size() <= 2); 0629 bool ok; 0630 int row = rowColumn[0].toInt(&ok); 0631 Q_ASSERT(ok); 0632 int column = 0; 0633 if (rowColumn.size() == 2) { 0634 column = rowColumn[1].toInt(&ok); 0635 Q_ASSERT(ok); 0636 } 0637 items << qMakePair(row, column); 0638 } 0639 0640 QModelIndex index = QModelIndex(); 0641 for (auto it = items.constBegin(); it != items.constEnd(); ++it) { 0642 index = model->index(it->first, it->second, index); 0643 if (it + 1 != items.constEnd()) { 0644 // this index is an intermediate one in the path, hence it should not really fail 0645 Q_ASSERT(index.isValid()); 0646 } 0647 } 0648 return index; 0649 } 0650 0651 /** @short Forwarding function which allows this to work without explicitly befriending each and every test */ 0652 void LibMailboxSync::setModelNetworkPolicy(Imap::Mailbox::Model *model, const Imap::Mailbox::NetworkPolicy policy) 0653 { 0654 model->setNetworkPolicy(policy); 0655 } 0656 0657 /** @short Sync mailbox A with QRESYNC and some initial state, but nothing in memory */ 0658 void LibMailboxSync::helperQresyncAInitial(Imap::Mailbox::SyncState &syncState) 0659 { 0660 FakeCapabilitiesInjector injector(model); 0661 injector.injectCapability(QStringLiteral("QRESYNC")); 0662 syncState.setExists(3); 0663 syncState.setUidValidity(666); 0664 syncState.setUidNext(15); 0665 syncState.setHighestModSeq(33); 0666 syncState.setUnSeenCount(3); 0667 syncState.setRecent(0); 0668 Imap::Uids uidMap; 0669 uidMap << 6 << 9 << 10; 0670 model->cache()->setMailboxSyncState(QStringLiteral("a"), syncState); 0671 model->cache()->setUidMapping(QStringLiteral("a"), uidMap); 0672 model->cache()->setMsgFlags(QStringLiteral("a"), 6, QStringList() << QStringLiteral("x")); 0673 model->cache()->setMsgFlags(QStringLiteral("a"), 9, QStringList() << QStringLiteral("y")); 0674 model->cache()->setMsgFlags(QStringLiteral("a"), 10, QStringList() << QStringLiteral("z")); 0675 model->resyncMailbox(idxA); 0676 cClient(t.mk("SELECT a (QRESYNC (666 33 (2 9)))\r\n")); 0677 cServer("* 3 EXISTS\r\n" 0678 "* OK [UIDVALIDITY 666] .\r\n" 0679 "* OK [UIDNEXT 15] .\r\n" 0680 "* OK [HIGHESTMODSEQ 33] .\r\n" 0681 ); 0682 cServer(t.last("OK selected\r\n")); 0683 cEmpty(); 0684 } 0685 0686 namespace Imap { 0687 namespace Mailbox { 0688 0689 /** @short Operator for QCOMPARE which acts on all data stored in the SyncState 0690 0691 This operator compares *everything*, including the hidden members. 0692 */ 0693 bool operator==(const SyncState &a, const SyncState &b) 0694 { 0695 return a.completelyEqualTo(b); 0696 } 0697 0698 } 0699 } 0700 0701 namespace QTest { 0702 0703 /** @short Debug data dumper for QList<uint> */ 0704 template<> 0705 char *toString(const QList<uint> &list) 0706 { 0707 QString buf; 0708 QDebug d(&buf); 0709 d << "QList<uint> (" << list.size() << "items):"; 0710 Q_FOREACH(const uint item, list) { 0711 d << item; 0712 } 0713 return qstrdup(buf.toUtf8().constData()); 0714 } 0715 0716 0717 /** @short Debug data dumper for unit tests 0718 0719 Could be a bit confusing as it doesn't print out the hidden members. Therefore a simple x.setFlags(QStringList()) -- which merely 0720 sets a value same as the default one -- will result in comparison failure, but this function wouldn't print the original cause. 0721 */ 0722 template<> 0723 char *toString(const Imap::Mailbox::SyncState &syncState) 0724 { 0725 QString buf; 0726 QDebug d(&buf); 0727 d << syncState; 0728 return qstrdup(buf.toUtf8().constData()); 0729 } 0730 0731 /** @short Debug data dumper for the MessageDataBundle */ 0732 template<> 0733 char *toString(const Imap::Mailbox::AbstractCache::MessageDataBundle &bundle) 0734 { 0735 QString buf; 0736 QDebug d(&buf); 0737 d << "UID:" << bundle.uid << "Envelope:" << bundle.envelope << "size:" << bundle.size << 0738 "bodystruct:" << bundle.serializedBodyStructure; 0739 return qstrdup(buf.toUtf8().constData()); 0740 } 0741 0742 template<> 0743 char *toString(const NetDataRegexp &x) 0744 { 0745 if (x.isPattern) { 0746 return qstrdup(QByteArray("[regexp: " + x.pattern + "]").constData()); 0747 } else { 0748 return toString(QString::fromUtf8(x.raw)); 0749 } 0750 } 0751 0752 } 0753 0754 NetDataRegexp NetDataRegexp::fromData(const QByteArray &data) 0755 { 0756 return NetDataRegexp(data, QByteArray(), false); 0757 } 0758 0759 NetDataRegexp NetDataRegexp::fromPattern(const QByteArray &pattern) 0760 { 0761 return NetDataRegexp(QByteArray(), pattern, true); 0762 } 0763 0764 NetDataRegexp::NetDataRegexp(const QByteArray &raw, const QByteArray &pattern, const bool isPattern): 0765 raw(raw), pattern(pattern), isPattern(isPattern) 0766 { 0767 } 0768 0769 bool operator==(const NetDataRegexp &a, const NetDataRegexp &b) 0770 // This used to be a QRegExp based function and it still assumes QRegExp's limitations, even though these are removed by QRegularExpression 0771 // TODO: Using QRegularExpression's functionality, multiline patterns and raw data can be accomodated; 0772 // this may simplify some code (all the ifs below here could be removed, for starters), but this will require checking all patterns 0773 { 0774 Q_ASSERT(!a.isPattern); 0775 Q_ASSERT(b.isPattern); 0776 QByteArray rawData = a.raw; 0777 0778 // QRegExp didn't support multiline patterns 0779 if (!rawData.endsWith("\r\n")) { 0780 // just a partial line 0781 return false; 0782 } 0783 rawData.chop(2); 0784 if (rawData.contains("\r\n")) { 0785 // multiple lines, we definitely couldn't handle that 0786 return false; 0787 } 0788 0789 // ...but it would've been a developer's mistake if the *pattern* included 0790 if (b.pattern.contains("\r\n")) { 0791 // that's a developer's error -- multiline patterns were not support by Qt's QRegExp, and there was nothing else in Qt4 0792 Q_ASSERT(!"CRLF in the regexp pattern, fix the test suite"); 0793 return false; 0794 } 0795 0796 return QRegularExpression("\\A(?:" + QString::fromUtf8(b.pattern) + ")\\z").match(QString::fromUtf8(rawData)).hasMatch(); 0797 // The pattern is surrounded by start and end anchors to ensure that the match is exact 0798 } 0799 0800 /** @short A string literal formatted according to RFC 3501 */ 0801 QByteArray LibMailboxSync::asLiteral(const QByteArray &data) 0802 { 0803 return '{' + QByteArray::number(data.size()) + "}\r\n" + data; 0804 }