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 }