Warning, file /pim/trojita/tests/Imap/test_Imap_SelectedMailboxUpdates.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 <algorithm>
0024 #include <QtTest>
0025 #include "test_Imap_SelectedMailboxUpdates.h"
0026 #include "Imap/Model/DummyNetworkWatcher.h"
0027 #include "Imap/Model/ItemRoles.h"
0028 #include "Imap/Model/MemoryCache.h"
0029 #include "Imap/Parser/Uids.h"
0030 #include "Streams/FakeSocket.h"
0031 #include "Imap/data.h"
0032 
0033 /** @short Test that we survive a new message arrival and its subsequent removal in rapid sequence
0034 
0035 The code won't notice that the message got expunged immediately (simply because it has no idea of a
0036 next response when processing the first EXISTS), will ask for UID and FLAGS, but the message gets
0037 deleted (and EXPUNGE sent) before the server receives the UID FETCH command. This leads to us having
0038 a flawed idea of UIDNEXT, unless the server provides a hint via the * OK [UIDNEXT xyz] response.
0039 
0040 It's a question whether or not to at least increment the UIDNEXT by one when the response doesn't
0041 arrive. Doing that would probably break when the server employs an Outlook-workaround for IDLE with
0042 a fake EXISTS/EXPUNGE responses.
0043 */
0044 void ImapModelSelectedMailboxUpdatesTest::helperTestExpungeImmediatelyAfterArrival(bool sendUidNext)
0045 {
0046     existsA = 3;
0047     uidValidityA = 6;
0048     uidMapA << 1 << 7 << 9;
0049     uidNextA = 16;
0050     helperSyncAWithMessagesEmptyState();
0051     helperCheckCache();
0052     cServer("* 1 FETCH (FLAGS ())\r\n");
0053     QCOMPARE(idxA.data(Imap::Mailbox::RoleUnreadMessageCount).toInt(), 1);
0054     cEmpty();
0055 
0056     auto oldState = model->cache()->mailboxSyncState(("a"));
0057     auto oldUidMap = model->cache()->uidMapping(QStringLiteral("a"));
0058     QCOMPARE(static_cast<uint>(oldUidMap.size()), oldState.exists());
0059     QSignalSpy numbersWatcher(model, SIGNAL(messageCountPossiblyChanged(QModelIndex)));
0060     cServer(QString::fromUtf8("* %1 EXISTS\r\n* %1 EXPUNGE\r\n").arg(QString::number(existsA + 1)).toUtf8());
0061     cClient(QString(t.mk("UID FETCH %1:* (FLAGS)\r\n")).arg(QString::number(qMax(uidMapA.last() + 1, uidNextA))).toUtf8());
0062     QCOMPARE(model->cache()->mailboxSyncState(QLatin1String("a")), oldState);
0063     QCOMPARE(model->cache()->uidMapping(QLatin1String("a")), oldUidMap);
0064     QCOMPARE(numbersWatcher.size(), 2);
0065     QCOMPARE(numbersWatcher[0][0].toModelIndex(), QModelIndex(idxA));
0066     QCOMPARE(numbersWatcher[1][0].toModelIndex(), QModelIndex(idxA));
0067     QCOMPARE(idxA.data(Imap::Mailbox::RoleUnreadMessageCount).toInt(), 1);
0068 
0069     // Add message with this UID to our internal list
0070     uint addedUid = 33;
0071     uidNextA = addedUid + 1;
0072 
0073     QByteArray uidUpdateResponse = sendUidNext ? QStringLiteral("* OK [UIDNEXT %1] courtesy of the server\r\n").arg(
0074             QString::number(uidNextA)).toUtf8() : QByteArray();
0075 
0076     // ...but because it got deleted, here we go
0077     cServer(t.last("OK empty fetch\r\n") + uidUpdateResponse);
0078     cEmpty();
0079     QVERIFY(errorSpy->isEmpty());
0080 
0081     helperCheckCache( ! sendUidNext );
0082     helperVerifyUidMapA();
0083 }
0084 
0085 void ImapModelSelectedMailboxUpdatesTest::testUnsolicitedFetch()
0086 {
0087     existsA = 2;
0088     uidValidityA = 666;
0089     uidMapA << 3 << 9;
0090     uidNextA = 33;
0091     helperSyncAWithMessagesEmptyState();
0092     helperCheckCache();
0093     cEmpty();
0094 
0095     auto oldState = model->cache()->mailboxSyncState(("a"));
0096     auto oldUidMap = model->cache()->uidMapping(QStringLiteral("a"));
0097     QCOMPARE(static_cast<uint>(oldUidMap.size()), oldState.exists());
0098     cServer(QString::fromUtf8("* %1 EXISTS\r\n* %1 FETCH (FLAGS (\\Seen \\Recent $NotJunk NotJunk))\r\n").arg(QString::number(existsA + 1)).toUtf8());
0099     cClient(QString(t.mk("UID FETCH %1:* (FLAGS)\r\n")).arg(
0100                  QString::number(qMax(uidMapA.last() + 1, uidNextA))).toUtf8());
0101     QCOMPARE(model->cache()->mailboxSyncState(QLatin1String("a")), oldState);
0102     QCOMPARE(model->cache()->uidMapping(QLatin1String("a")), oldUidMap);
0103 
0104     // Add message with this UID to our internal list
0105     uint addedUid = 42;
0106     ++existsA;
0107     uidMapA << addedUid;
0108     uidNextA = addedUid + 1;
0109 
0110     cServer(QString("* %1 FETCH (FLAGS (\\Seen \\Recent $NotJunk NotJunk) UID %2)\r\n").arg(
0111             QString::number(existsA), QString::number(addedUid)).toUtf8() +
0112                        t.last("OK flags returned\r\n"));
0113     cEmpty();
0114     QVERIFY(errorSpy->isEmpty());
0115 
0116     helperCheckCache();
0117     helperVerifyUidMapA();
0118 }
0119 
0120 /** @short Test a rapid EXISTS/EXPUNGE sequence
0121 
0122 @see helperTestExpungeImmediatelyAfterArrival for details
0123 */
0124 void ImapModelSelectedMailboxUpdatesTest::testExpungeImmediatelyAfterArrival()
0125 {
0126     helperTestExpungeImmediatelyAfterArrival(false);
0127 }
0128 
0129 /** @short Test a rapid EXISTS/EXPUNGE sequence with an added UIDNEXT response
0130 
0131 @see helperTestExpungeImmediatelyAfterArrival for details
0132 */
0133 void ImapModelSelectedMailboxUpdatesTest::testExpungeImmediatelyAfterArrivalWithUidNext()
0134 {
0135     helperTestExpungeImmediatelyAfterArrival(true);
0136 }
0137 
0138 /** @short Test generic traffic to an opened mailbox without asking for message data too soon */
0139 void ImapModelSelectedMailboxUpdatesTest::testGenericTraffic()
0140 {
0141     helperGenericTraffic(false);
0142 }
0143 
0144 /** @short Test generic traffic to an opened mailbox when we ask for message metadata as soon as they arrive */
0145 void ImapModelSelectedMailboxUpdatesTest::testGenericTrafficWithEnvelopes()
0146 {
0147     helperGenericTraffic(true);
0148 }
0149 
0150 void ImapModelSelectedMailboxUpdatesTest::helperGenericTraffic(bool askForEnvelopes)
0151 {
0152     // At first, sync to an empty state
0153     uidNextA = 12;
0154     uidValidityA = 333;
0155     helperSyncANoMessagesCompleteState();
0156 
0157     // Fake delivery of A, B and C
0158     helperGenericTrafficFirstArrivals(askForEnvelopes);
0159 
0160     // Now add one more message, the D
0161     helperGenericTrafficArrive2(askForEnvelopes);
0162 
0163     // Remove B
0164     helperDeleteOneMessage(1, QStringList() << QStringLiteral("A") << QStringLiteral("C") << QStringLiteral("D"));
0165 
0166     // Remove D
0167     helperDeleteOneMessage(2, QStringList() << QStringLiteral("A") << QStringLiteral("C"));
0168 
0169     // Remove A
0170     helperDeleteOneMessage(0, QStringList() << QStringLiteral("C"));
0171 
0172     // Remove C
0173     helperDeleteOneMessage(0, QStringList());
0174 
0175     // Add completely different messages, A, B, C and D
0176     helperGenericTrafficArrive3(askForEnvelopes);
0177 
0178     // remove C and B
0179     helperDeleteOneMessage(2, QStringList() << QStringLiteral("A") << QStringLiteral("B") << QStringLiteral("D"));
0180     helperDeleteOneMessage(1, QStringList() << QStringLiteral("A") << QStringLiteral("D"));
0181 
0182     // Add E, F and G
0183     helperGenericTrafficArrive4(askForEnvelopes);
0184 
0185     // Delete D and F
0186     helperDeleteTwoMessages(3, 1, QStringList() << QStringLiteral("A") << QStringLiteral("E") << QStringLiteral("G"));
0187 
0188     // Delete G and A
0189     helperDeleteTwoMessages(2, 0, QStringList() << QStringLiteral("E"));
0190 }
0191 
0192 /** @short Test an arrival of three brand new messages to an already synced mailbox
0193 
0194 This function assumes that mailbox A is already openened and is active and synced. It will fake an arrival
0195 of three brand new messages, A, B and C. This arrival will get noticed and processed.
0196  */
0197 void ImapModelSelectedMailboxUpdatesTest::helperGenericTrafficFirstArrivals(bool askForEnvelopes)
0198 {
0199     auto oldState = model->cache()->mailboxSyncState(("a"));
0200     auto oldUidMap = model->cache()->uidMapping(QStringLiteral("a"));
0201     QCOMPARE(static_cast<uint>(oldUidMap.size()), oldState.exists());
0202     // Fake delivery of A, B and C
0203     cServer(QByteArray("* 3 EXISTS\r\n* 3 RECENT\r\n"));
0204     // This should trigger a request for flags
0205     cClient(t.mk("UID FETCH 12:* (FLAGS)\r\n"));
0206     QCOMPARE(model->cache()->mailboxSyncState(QLatin1String("a")), oldState);
0207     QCOMPARE(model->cache()->uidMapping(QLatin1String("a")), oldUidMap);
0208 
0209     // The messages sgould be there already
0210     QVERIFY(msgListA.model()->index(0, 0, msgListA).isValid());
0211     QModelIndex msgB = msgListA.model()->index(1, 0, msgListA);
0212     QVERIFY(msgB.isValid());
0213     QVERIFY(msgListA.model()->index(2, 0, msgListA).isValid());
0214     QVERIFY( ! msgListA.model()->index(3, 0, msgListA).isValid());
0215     // We shouldn't have the UID yet
0216     QCOMPARE(msgB.data(Imap::Mailbox::RoleMessageUid).toUInt(), 0u);
0217 
0218     // Verify various message counts
0219     // This one is parsed from RECENT
0220     QCOMPARE(idxA.data(Imap::Mailbox::RoleRecentMessageCount).toInt(), 3);
0221     // This one is easy, too
0222     QCOMPARE(idxA.data(Imap::Mailbox::RoleTotalMessageCount).toInt(), 3);
0223     // We have to wait for FLAGS for this one
0224     QCOMPARE(idxA.data(Imap::Mailbox::RoleUnreadMessageCount).toInt(), 0);
0225 
0226     if ( askForEnvelopes ) {
0227         // In the meanwhile, ask for ENVELOPE etc -- but it shouldn't be there yet
0228         QVERIFY( ! msgB.data(Imap::Mailbox::RoleMessageFrom).isValid() );
0229     }
0230 
0231     // FLAGS arrive, bringing the UID with them
0232     cServer(QByteArray("* 1 FETCH (UID 43 FLAGS (\\Recent))\r\n"
0233                                   "* 2 FETCH (UID 44 FLAGS (\\Recent))\r\n"
0234                                   "* 3 FETCH (UID 45 FLAGS (\\Recent))\r\n") +
0235                        t.last("OK fetched\r\n"));
0236     // The UIDs shall be known at this point
0237     QCOMPARE(msgB.data(Imap::Mailbox::RoleMessageUid).toUInt(), 44u);
0238     // The unread message count should be correct now, too
0239     QCOMPARE(idxA.data(Imap::Mailbox::RoleUnreadMessageCount).toInt(), 3);
0240     if ( askForEnvelopes ) {
0241         // The ENVELOPE and related fields should be requested now
0242         cClient(t.mk("UID FETCH 43:44 (" FETCH_METADATA_ITEMS ")\r\n"));
0243     }
0244 
0245     existsA = 3;
0246     uidNextA = 46;
0247     uidMapA << 43 << 44 << 45;
0248     helperCheckCache();
0249     helperVerifyUidMapA();
0250 
0251     // Yes, we're counting to one more than what actually is here; that's because we want to prevent a possible out-of-bounds access
0252     for ( int i = 0; i < static_cast<int>(existsA) + 1; ++i )
0253         msgListA.model()->index(i, 0, msgListA).data(Imap::Mailbox::RoleMessageSubject);
0254 
0255     QModelIndex uid43 = msgListA.model()->index(0, 0, msgListA);
0256     Q_ASSERT(uid43.isValid());
0257     QCOMPARE(uid43.data(Imap::Mailbox::RoleMessageUid).toUInt(), 43u);
0258 
0259     if ( askForEnvelopes ) {
0260         // Envelopes for A and B already got requested
0261         // We have to preserve the previous command, too
0262         QByteArray completionForFirstFetch = t.last("OK fetched\r\n");
0263         cClient(t.mk("UID FETCH 45 (" FETCH_METADATA_ITEMS ")\r\n"));
0264         // Simulate out-of-order execution here -- the completion responses for both commands arrive only
0265         // after the actual data for both of them got pushed
0266 
0267         // Also test the header parsing
0268         QByteArray headerData("List-Post: <mailto:gentoo-dev@lists.gentoo.org>\r\n"
0269                               "References: <20121031120002.5C37D5807C@linuxized.com> "
0270                               "<CAKmKYaDZtfZ9wzKML8WgJ=evVhteyOG0RVfsASpBGViwncsaiQ@mail.gmail.com>\r\n"
0271                               " <50911AE6.8060402@gmail.com>\r\n"
0272                               "\r\n");
0273         cServer("* 1 FETCH (BODY[HEADER.FIELDS (List-Post References fail)]" + asLiteral(headerData) + ")\r\n");
0274 
0275         cServer(helperCreateTrivialEnvelope(1, 43, QLatin1String("A")) +
0276                            helperCreateTrivialEnvelope(2, 44, QLatin1String("B")) +
0277                            helperCreateTrivialEnvelope(3, 45, QLatin1String("C")) +
0278                            completionForFirstFetch +
0279                            t.last("OK fetched\r\n"));
0280 
0281 
0282         QList<QByteArray> receivedReferences = uid43.data(Imap::Mailbox::RoleMessageHeaderReferences).value<QList<QByteArray> >();
0283         QCOMPARE(receivedReferences,
0284                  QList<QByteArray>() << "20121031120002.5C37D5807C@linuxized.com"
0285                  << "CAKmKYaDZtfZ9wzKML8WgJ=evVhteyOG0RVfsASpBGViwncsaiQ@mail.gmail.com" << "50911AE6.8060402@gmail.com");
0286         QCOMPARE(uid43.data(Imap::Mailbox::RoleMessageHeaderListPost).toList(),
0287                  QVariantList() << QUrl("mailto:gentoo-dev@lists.gentoo.org"));
0288         QCOMPARE(uid43.data(Imap::Mailbox::RoleMessageHeaderListPostNo).toBool(), false);
0289     } else {
0290         // Requesting all envelopes at once
0291         cClient(t.mk("UID FETCH 43:45 (" FETCH_METADATA_ITEMS ")\r\n"));
0292 
0293         // test header parsing as well
0294         QByteArray headerData("List-Post: NO (disabled)\r\n\r\n");
0295         cServer("* 1 FETCH (BODY[HEADER.FIELDS (References List-Post)]" + asLiteral(headerData) + ")\r\n");
0296 
0297         cServer(helperCreateTrivialEnvelope(1, 43, QLatin1String("A")) +
0298                            helperCreateTrivialEnvelope(2, 44, QLatin1String("B")) +
0299                            helperCreateTrivialEnvelope(3, 45, QLatin1String("C")) +
0300                            t.last("OK [UIDNEXT 46] fetched\r\n"));
0301         QCOMPARE(uid43.data(Imap::Mailbox::RoleMessageHeaderReferences).value<QList<QByteArray> >(), QList<QByteArray>());
0302         QCOMPARE(uid43.data(Imap::Mailbox::RoleMessageHeaderListPost).toList(), QVariantList());
0303         QCOMPARE(uid43.data(Imap::Mailbox::RoleMessageHeaderListPostNo).toBool(), true);
0304     }
0305     helperCheckSubjects(QStringList() << QStringLiteral("A") << QStringLiteral("B") << QStringLiteral("C"));
0306     cEmpty();
0307     justKeepTask();
0308     QVERIFY( errorSpy->isEmpty() );
0309 }
0310 
0311 /** @short Fake delivery of D to a mailbox which already contains A, B and C */
0312 void  ImapModelSelectedMailboxUpdatesTest::helperGenericTrafficArrive2(bool askForEnvelopes)
0313 {
0314     auto oldState = model->cache()->mailboxSyncState(("a"));
0315     auto oldUidMap = model->cache()->uidMapping(QStringLiteral("a"));
0316     QCOMPARE(static_cast<uint>(oldUidMap.size()), oldState.exists());
0317     // Fake delivery of D
0318     cServer(QByteArray("* 4 EXISTS\r\n* 4 RECENT\r\n"));
0319     // This should trigger a request for flags
0320     cClient(t.mk("UID FETCH 46:* (FLAGS)\r\n"));
0321     // The messages sgould be there already
0322     QVERIFY(msgListA.model()->index(0, 0, msgListA).isValid());
0323     QModelIndex msgD = msgListA.model()->index(3, 0, msgListA);
0324     QVERIFY(msgD.isValid());
0325     QVERIFY( ! msgListA.model()->index(4, 0, msgListA).isValid());
0326     // We shouldn't have the UID yet
0327     QCOMPARE(msgD.data(Imap::Mailbox::RoleMessageUid).toUInt(), 0u);
0328 
0329     // Verify various message counts
0330     // This one is parsed from RECENT
0331     QCOMPARE(idxA.data(Imap::Mailbox::RoleRecentMessageCount).toInt(), 4);
0332     // This one is easy, too
0333     QCOMPARE(idxA.data(Imap::Mailbox::RoleTotalMessageCount).toInt(), 4);
0334     // this one isn't available yet
0335     QCOMPARE(idxA.data(Imap::Mailbox::RoleUnreadMessageCount).toInt(), 3);
0336 
0337     if ( askForEnvelopes ) {
0338         // In the meanwhile, ask for ENVELOPE etc -- but it shouldn't be there yet
0339         QVERIFY( ! msgD.data(Imap::Mailbox::RoleMessageFrom).isValid() );
0340     }
0341     QCOMPARE(model->cache()->mailboxSyncState(QLatin1String("a")), oldState);
0342     QCOMPARE(model->cache()->uidMapping(QLatin1String("a")), oldUidMap);
0343 
0344     // FLAGS arrive, bringing the UID with them
0345     cServer(QByteArray("* 4 FETCH (UID 46 FLAGS (\\Recent))\r\n") +
0346                        t.last("OK fetched\r\n"));
0347     // The UIDs shall be known at this point
0348     QCOMPARE(msgD.data(Imap::Mailbox::RoleMessageUid).toUInt(), 46u);
0349     // The unread message count should be correct now, too
0350     QCOMPARE(idxA.data(Imap::Mailbox::RoleUnreadMessageCount).toInt(), 4);
0351 
0352     if ( askForEnvelopes ) {
0353         QCoreApplication::processEvents();
0354         // The ENVELOPE and related fields should be requested now
0355         cClient(t.mk("UID FETCH 46 (" FETCH_METADATA_ITEMS ")\r\n"));
0356     }
0357 
0358     existsA = 4;
0359     uidNextA = 47;
0360     uidMapA << 46;
0361     helperCheckCache();
0362     helperVerifyUidMapA();
0363 
0364     // Request the subject once again
0365     msgListA.model()->index(3, 0, msgListA).data(Imap::Mailbox::RoleMessageSubject);
0366     // In contrast to helperGenericTrafficFirstArrivals, we won't query "message #5",ie. one more than how many are
0367     // actually there. The main motivation is trying to behave in a different manner than before. Hope this helps.
0368 
0369     if ( askForEnvelopes ) {
0370         // Envelope for D already got requested -> nothing to do here
0371     } else {
0372         // The D got requested by an explicit query above
0373         cClient(t.mk("UID FETCH 46 (" FETCH_METADATA_ITEMS ")\r\n"));
0374     }
0375     cServer(helperCreateTrivialEnvelope(4, 46, QLatin1String("D")) + t.last("OK fetched\r\n"));
0376     helperCheckSubjects(QStringList() << QStringLiteral("A") << QStringLiteral("B") << QStringLiteral("C") << QStringLiteral("D"));
0377     cEmpty();
0378     QVERIFY( errorSpy->isEmpty() );
0379 }
0380 
0381 /** @short Check subjects of all messages in a given mailbox
0382 
0383 This function will check subjects of all mailboxes in the mailbox A against a list of subjects specified in @arg subjects.
0384 */
0385 void ImapModelSelectedMailboxUpdatesTest::helperCheckSubjects(const QStringList &subjects)
0386 {
0387     for ( int i = 0; i < subjects.size(); ++i ) {
0388         QModelIndex index = msgListA.model()->index(i, 0, msgListA);
0389         QVERIFY(index.isValid());
0390         QCOMPARE(index.data(Imap::Mailbox::RoleMessageSubject).toString(), subjects[i]);
0391     }
0392     // Me sure that there are no more messages
0393     QVERIFY( ! msgListA.model()->index(subjects.size(), 0, msgListA).isValid() );
0394 }
0395 
0396 /** @short Test what happens when the server tells us that one message got deleted */
0397 void ImapModelSelectedMailboxUpdatesTest::helperDeleteOneMessage(const uint seq, const QStringList &remainingSubjects)
0398 {
0399     // Fake deleting one message
0400     --existsA;
0401     uidMapA.remove(seq);
0402     Q_ASSERT(remainingSubjects.size() == static_cast<int>(existsA));
0403     Q_ASSERT(uidMapA.size() == static_cast<int>(existsA));
0404     cServer(QString::fromUtf8("* %1 EXPUNGE\r\n* %2 RECENT\r\n").arg(QString::number(seq+1), QString::number(existsA)).toUtf8());
0405 
0406     // Verify the model's idea about the current state. The cache is updated immediately.
0407     helperCheckSubjects(remainingSubjects);
0408     helperCheckCache();
0409     helperVerifyUidMapA();
0410 }
0411 
0412 /** @short Test what happens when the server tells us that one message got deleted */
0413 void ImapModelSelectedMailboxUpdatesTest::helperDeleteTwoMessages(const uint seq1, const uint seq2, const QStringList &remainingSubjects)
0414 {
0415     // Fake deleting one message
0416     existsA -= 2;
0417     uidMapA.remove(seq1);
0418     uidMapA.remove(seq2);
0419     Q_ASSERT(remainingSubjects.size() == static_cast<int>(existsA));
0420     Q_ASSERT(uidMapA.size() == static_cast<int>(existsA));
0421     cServer(QString::fromUtf8("* %1 EXPUNGE\r\n* %2 EXPUNGE\r\n* %3 RECENT\r\n").arg(QString::number(seq1+1), QString::number(seq2+1), QString::number(existsA)).toUtf8());
0422 
0423     // Verify the model's idea about the current state. The cache is updated immediately.
0424     helperCheckSubjects(remainingSubjects);
0425     helperCheckCache();
0426     helperVerifyUidMapA();
0427 }
0428 
0429 /** @short Fake delivery of a completely new set of messages, the A, B, C and D */
0430 void ImapModelSelectedMailboxUpdatesTest::helperGenericTrafficArrive3(bool askForEnvelopes)
0431 {
0432     auto oldState = model->cache()->mailboxSyncState(("a"));
0433     auto oldUidMap = model->cache()->uidMapping(QStringLiteral("a"));
0434     QCOMPARE(static_cast<uint>(oldUidMap.size()), oldState.exists());
0435     // Fake the announcement of the delivery
0436     cServer(QByteArray("* 4 EXISTS\r\n* 4 RECENT\r\n"));
0437     // This should trigger a request for flags
0438     cClient(t.mk("UID FETCH 47:* (FLAGS)\r\n"));
0439 
0440     // The messages should be there already
0441     QVERIFY(msgListA.model()->index(0, 0, msgListA).isValid());
0442     QModelIndex msgD = msgListA.model()->index(3, 0, msgListA);
0443     QVERIFY(msgD.isValid());
0444     QVERIFY( ! msgListA.model()->index(4, 0, msgListA).isValid());
0445     // We shouldn't have the UID yet
0446     QCOMPARE(msgD.data(Imap::Mailbox::RoleMessageUid).toUInt(), 0u);
0447 
0448     // Verify various message counts
0449     // This one is parsed from RECENT
0450     QCOMPARE(idxA.data(Imap::Mailbox::RoleRecentMessageCount).toInt(), 4);
0451     // This one is easy, too
0452     QCOMPARE(idxA.data(Imap::Mailbox::RoleTotalMessageCount).toInt(), 4);
0453     // this one isn't available yet
0454     QCOMPARE(idxA.data(Imap::Mailbox::RoleUnreadMessageCount).toInt(), 0);
0455 
0456     if ( askForEnvelopes ) {
0457         // In the meanwhile, ask for ENVELOPE etc -- but it shouldn't be there yet
0458         QVERIFY( ! msgD.data(Imap::Mailbox::RoleMessageFrom).isValid() );
0459     }
0460     QCOMPARE(model->cache()->mailboxSyncState(QLatin1String("a")), oldState);
0461     QCOMPARE(model->cache()->uidMapping(QLatin1String("a")), oldUidMap);
0462 
0463     // FLAGS arrive, bringing the UID with them
0464     cServer(QByteArray("* 1 FETCH (UID 47 FLAGS (\\Recent))\r\n"
0465                                   "* 2 FETCH (UID 48 FLAGS (\\Recent))\r\n") );
0466     // Try to insert a pause here; this could be used to properly play with preloading in future...
0467     for ( int i = 0; i < 10; ++i)
0468         QCoreApplication::processEvents();
0469     cServer(QByteArray("* 3 FETCH (UID 49 FLAGS (\\Recent))\r\n"
0470                                   "* 4 FETCH (UID 50 FLAGS (\\Recent))\r\n") +
0471                        t.last("OK fetched\r\n"));
0472     // The UIDs shall be known at this point
0473     QCOMPARE(msgD.data(Imap::Mailbox::RoleMessageUid).toUInt(), 50u);
0474     // The unread message count should be correct now, too
0475     QCOMPARE(idxA.data(Imap::Mailbox::RoleUnreadMessageCount).toInt(), 4);
0476 
0477     if ( askForEnvelopes ) {
0478         // The ENVELOPE and related fields should be requested now
0479         cClient(t.mk("UID FETCH 47:50 (" FETCH_METADATA_ITEMS ")\r\n"));
0480     }
0481 
0482     existsA = 4;
0483     uidNextA = 51;
0484     uidMapA.clear();
0485     uidMapA << 47 << 48 << 49 << 50;
0486     helperCheckCache();
0487     helperVerifyUidMapA();
0488 
0489     if ( askForEnvelopes ) {
0490         // Envelopes already requested -> nothing to do here
0491     } else {
0492         // Not requested yet -> do it now
0493         QVERIFY( ! msgD.data(Imap::Mailbox::RoleMessageFrom).isValid() );
0494         cClient(t.mk("UID FETCH 47:50 (" FETCH_METADATA_ITEMS ")\r\n"));
0495     }
0496     // This is common for both cases; the data should finally arrive
0497     cServer(helperCreateTrivialEnvelope(1, 47, QLatin1String("A")) +
0498                        helperCreateTrivialEnvelope(2, 48, QLatin1String("B")) +
0499                        helperCreateTrivialEnvelope(3, 49, QLatin1String("C")) +
0500                        helperCreateTrivialEnvelope(4, 50, QLatin1String("D")) +
0501                        t.last("OK fetched\r\n"));
0502     helperCheckSubjects(QStringList() << QStringLiteral("A") << QStringLiteral("B") << QStringLiteral("C") << QStringLiteral("D"));
0503 
0504     // Verify UIDd and cache stuff once again to make sure reading data doesn't mess anything up
0505     helperCheckCache();
0506     helperVerifyUidMapA();
0507     cEmpty();
0508     QVERIFY( errorSpy->isEmpty() );
0509 }
0510 
0511 /** @short Fake delivery of three new messages, E, F and G, to a mailbox with A and D */
0512 void ImapModelSelectedMailboxUpdatesTest::helperGenericTrafficArrive4(bool askForEnvelopes)
0513 {
0514     auto oldState = model->cache()->mailboxSyncState(("a"));
0515     auto oldUidMap = model->cache()->uidMapping(QStringLiteral("a"));
0516     // Fake the announcement of the delivery
0517     cServer(QByteArray("* 5 EXISTS\r\n* 5 RECENT\r\n"));
0518     // This should trigger a request for flags
0519     cClient(t.mk("UID FETCH 51:* (FLAGS)\r\n"));
0520 
0521     QModelIndex msgE = msgListA.model()->index(2, 0, msgListA);
0522     QVERIFY(msgE.isValid());
0523     // We shouldn't have the UID yet
0524     QCOMPARE(msgE.data(Imap::Mailbox::RoleMessageUid).toUInt(), 0u);
0525 
0526     // Verify various message counts
0527     // This one is parsed from RECENT
0528     QCOMPARE(idxA.data(Imap::Mailbox::RoleRecentMessageCount).toInt(), 5);
0529     // This one is easy, too
0530     QCOMPARE(idxA.data(Imap::Mailbox::RoleTotalMessageCount).toInt(), 5);
0531     // this one isn't available yet
0532     QCOMPARE(idxA.data(Imap::Mailbox::RoleUnreadMessageCount).toInt(), 2);
0533 
0534     if ( askForEnvelopes ) {
0535         // In the meanwhile, ask for ENVELOPE etc -- but it shouldn't be there yet
0536         QVERIFY( ! msgE.data(Imap::Mailbox::RoleMessageFrom).isValid() );
0537     }
0538     QCOMPARE(model->cache()->mailboxSyncState(QLatin1String("a")), oldState);
0539     QCOMPARE(model->cache()->uidMapping(QLatin1String("a")), oldUidMap);
0540 
0541     // FLAGS arrive, bringing the UID with them
0542     cServer(QByteArray("* 3 FETCH (UID 52 FLAGS (\\Recent))\r\n"
0543                                   "* 4 FETCH (UID 53 FLAGS (\\Recent))\r\n"
0544                                   "* 5 FETCH (UID 63 FLAGS (\\Recent))\r\n") +
0545                        t.last("OK fetched\r\n"));
0546     // The UIDs shall be known at this point
0547     QCOMPARE(msgE.data(Imap::Mailbox::RoleMessageUid).toUInt(), 52u);
0548     // The unread message count should be correct now, too
0549     QCOMPARE(idxA.data(Imap::Mailbox::RoleUnreadMessageCount).toInt(), 5);
0550 
0551     if ( askForEnvelopes ) {
0552         QCoreApplication::processEvents();
0553         // The ENVELOPE and related fields should be requested now
0554         cClient(t.mk("UID FETCH 52 (" FETCH_METADATA_ITEMS ")\r\n"));
0555     }
0556 
0557     existsA = 5;
0558     uidNextA = 64;
0559     uidMapA << 52 << 53 << 63;
0560     helperCheckCache();
0561     helperVerifyUidMapA();
0562 
0563     if ( askForEnvelopes ) {
0564         // Envelopes already requested -> nothing to do here
0565     } else {
0566         // Not requested yet -> do it now
0567         QVERIFY( ! msgE.data(Imap::Mailbox::RoleMessageFrom).isValid() );
0568         cClient(t.mk("UID FETCH 52:53,63 (" FETCH_METADATA_ITEMS ")\r\n"));
0569     }
0570     // This is common for both cases; the data should finally arrive
0571     cServer(helperCreateTrivialEnvelope(3, 52, QLatin1String("E")) +
0572                        helperCreateTrivialEnvelope(4, 53, QLatin1String("F")) +
0573                        helperCreateTrivialEnvelope(5, 63, QLatin1String("G")) +
0574                        t.last("OK fetched\r\n"));
0575     cEmpty();
0576     helperCheckSubjects(QStringList() << QStringLiteral("A") << QStringLiteral("D") << QStringLiteral("E") << QStringLiteral("F") << QStringLiteral("G"));
0577     if (askForEnvelopes) {
0578         // Well, this subject checking led to a fetch() being issued on all of these messages (through the ::data()).
0579         // The subjects and a lot of other metadata items were already known for all of them, but the INTERNALDATE in particular
0580         // was still missing. This means that this message was not considered "fetched".
0581         cClient(t.mk("UID FETCH 53,63 (" FETCH_METADATA_ITEMS ")\r\n"));
0582         // and yeah, we're cheating because I'm lazy; the data ain't here
0583         cServer(t.last("OK fetched\r\n"));
0584     } else {
0585         cEmpty();
0586     }
0587 
0588     // Verify UIDd and cache stuff once again to make sure reading data doesn't mess anything up
0589     helperCheckCache();
0590     helperVerifyUidMapA();
0591     cEmpty();
0592     QVERIFY( errorSpy->isEmpty() );
0593 }
0594 
0595 /** @short Check the UIDs in the mailbox against the uidMapA list */
0596 #define helperCheckUidMapFromModel() \
0597 { \
0598     QCOMPARE(model->rowCount(msgListA), uidMapA.size()); \
0599     Imap::Uids actual; \
0600     for (int i = 0; i < uidMapA.size(); ++i) { \
0601         actual << msgListA.model()->index(i, 0, msgListA).data(Imap::Mailbox::RoleMessageUid).toUInt(); \
0602     } \
0603     QCOMPARE(actual, uidMapA); \
0604 } \
0605 
0606 void ImapModelSelectedMailboxUpdatesTest::testVanishedUpdates()
0607 {
0608     initialMessages(10);
0609 
0610     // Deleting a message in the middle of the range
0611     cServer("* VANISHED 9\r\n");
0612     uidMapA.remove(uidMapA.indexOf(9));
0613     --existsA;
0614     helperCheckUidMapFromModel();
0615     helperCheckCache();
0616 
0617     // Deleting the last one
0618     cServer("* VANISHED 10\r\n");
0619     uidMapA.remove(uidMapA.indexOf(10));
0620     --existsA;
0621     helperCheckUidMapFromModel();
0622     helperCheckCache();
0623 
0624     // Dleting three at the very start
0625     cServer("* VANISHED 1:3\r\n");
0626     uidMapA.remove(uidMapA.indexOf(1));
0627     uidMapA.remove(uidMapA.indexOf(2));
0628     uidMapA.remove(uidMapA.indexOf(3));
0629     existsA -= 3;
0630     helperCheckUidMapFromModel();
0631     helperCheckCache();
0632 
0633     QCOMPARE(uidMapA, Imap::Uids() << 4 << 5 << 6 << 7 << 8);
0634 
0635     // A new arrival...
0636     cServer("* 6 EXISTS\r\n");
0637     cClient(t.mk("UID FETCH 11:* (FLAGS)\r\n"));
0638     cServer("* 6 FETCH (UID 11 FLAGS (x))\r\n" + t.last("OK fetched\r\n"));
0639     uidMapA << 11;
0640     ++existsA;
0641     uidNextA = 12;
0642     helperCheckUidMapFromModel();
0643     helperCheckCache();
0644 
0645     // Yet another arrival which, unfortunately, gets removed immediately
0646     cServer("* 7 EXISTS\r\n* VANISHED 12\r\n");
0647     cClient(t.mk("UID FETCH 12:* (FLAGS)\r\n"));
0648     cServer(t.last("OK fetched\r\n"));
0649     uidNextA = 13;
0650     helperCheckUidMapFromModel();
0651     helperCheckCache();
0652 
0653     // Two messages arrive, the server sends UID for the last of them and the first one gets expunged
0654     cServer("* 8 EXISTS\r\n");
0655     // the on-disk cache is trashed, that's by design because it would otherwise have to contain zeros
0656     uidMapA << 0 << 0;
0657     auto oldUidMap = uidMapA;
0658     existsA += 2;
0659     helperCheckUidMapFromModel();
0660     uidMapA.clear();
0661     // FIXME: don't call this, existsA != uidMapA.size
0662     // helperCheckCache();
0663     uidMapA = oldUidMap;
0664     cServer("* 8 FETCH (UID 14 FLAGS ())\r\n");
0665     uidMapA[7] = 14;
0666     uidNextA = 15;
0667     helperCheckUidMapFromModel();
0668     // FIXME: don't call this -- early call to cEmpty()
0669     // helperCheckCache();
0670     cServer("* VANISHED 13\r\n");
0671     uidMapA.remove(6);
0672     --existsA;
0673     helperCheckUidMapFromModel();
0674     // FIXME: don't call this: early call to cEmpty()
0675     //helperCheckCache();
0676     cClient(t.mk("UID FETCH 13:* (FLAGS)\r\n"));
0677     cServer("* 7 FETCH (UID 14 FLAGS (x))\r\n" + t.last("OK fetched\r\n"));
0678     helperCheckUidMapFromModel();
0679     helperCheckCache();
0680 
0681     cEmpty();
0682 }
0683 
0684 inline bool isOdd(const uint i)
0685 {
0686     return i & 1;
0687 }
0688 
0689 inline bool isEven(const uint i)
0690 {
0691     return ! isOdd(i);
0692 }
0693 
0694 void ImapModelSelectedMailboxUpdatesTest::testVanishedWithNonExisting()
0695 {
0696     initialMessages(10);
0697     cServer("* VANISHED 0,2,4,6,6,6,8,2,0,10,12\r\n");
0698     uidMapA.erase(std::remove_if(uidMapA.begin(), uidMapA.end(), isEven), uidMapA.end());
0699     existsA = uidMapA.size();
0700     helperCheckUidMapFromModel();
0701     helperCheckCache();
0702 
0703     // This one has been already reported
0704     cServer("* VANISHED 2\r\n");
0705     helperCheckUidMapFromModel();
0706     helperCheckCache();
0707 
0708     // This one wasn't there yet at all
0709     cServer("* VANISHED 666\r\n");
0710     helperCheckUidMapFromModel();
0711     helperCheckCache();
0712 
0713     cEmpty();
0714 }
0715 
0716 /** @short Test what happens when the server informs about new message arrivals twice in a row */
0717 void ImapModelSelectedMailboxUpdatesTest::testMultipleArrivals()
0718 {
0719     initialMessages(1);
0720     auto oldState = model->cache()->mailboxSyncState(("a"));
0721     auto oldUidMap = model->cache()->uidMapping(QStringLiteral("a"));
0722     QModelIndex index = model->taskModel()->index(0, 0);
0723     QPersistentModelIndex keepIndex = index.model()->index(0, 0, index);
0724     QVERIFY(keepIndex.isValid());
0725     QCOMPARE(keepIndex.data(Imap::Mailbox::RoleTaskIsVisible), QVariant(false));
0726     cServer("* 2 EXISTS\r\n* 3 EXISTS\r\n");
0727     QCOMPARE(keepIndex.data(Imap::Mailbox::RoleTaskIsVisible), QVariant(true));
0728     QCOMPARE(model->cache()->mailboxSyncState(QLatin1String("a")), oldState);
0729     QCOMPARE(model->cache()->uidMapping(QLatin1String("a")), oldUidMap);
0730     QByteArray req1 = t.mk("UID FETCH 2:* (FLAGS)\r\n");
0731     QByteArray resp1 = t.last("OK fetched\r\n");
0732     // The UIDs are still unknown at this point, and at the same time the client pessimistically assumes that the first command
0733     // can easily arrive to the server before it sent the second EXISTS, which is why it has no other choice but repeat
0734     // essentially the same command once again.
0735     QByteArray req2 = t.mk("UID FETCH 2:* (FLAGS)\r\n");
0736     QByteArray resp2 = t.last("OK fetched\r\n");
0737     cClient(req1 + req2);
0738     QCOMPARE(model->cache()->mailboxSyncState(QLatin1String("a")), oldState);
0739     QCOMPARE(model->cache()->uidMapping(QLatin1String("a")), oldUidMap);
0740     // The server will, however, try to be smart in this case and will send the responses back just one time.
0741     // It's OK to do so per the relevant standards and Trojita won't care.
0742     cServer("* 2 FETCH (UID 2 FLAGS (m2))\r\n"
0743             "* 3 FETCH (UID 3 FLAGS (m3))\r\n")
0744     QCOMPARE(model->cache()->mailboxSyncState(QLatin1String("a")), oldState);
0745     QCOMPARE(model->cache()->uidMapping(QLatin1String("a")), oldUidMap);
0746     cServer(resp1);
0747     QCOMPARE(model->cache()->mailboxSyncState(QLatin1String("a")), oldState);
0748     QCOMPARE(model->cache()->uidMapping(QLatin1String("a")), oldUidMap);
0749     QCOMPARE(keepIndex.data(Imap::Mailbox::RoleTaskIsVisible), QVariant(true));
0750     cServer(resp2);
0751     QCOMPARE(keepIndex.data(Imap::Mailbox::RoleTaskIsVisible), QVariant(false));
0752     uidMapA << 2 << 3;
0753     existsA = 3;
0754     uidNextA = 4;
0755     helperCheckUidMapFromModel();
0756     helperCheckCache();
0757     QCOMPARE(static_cast<int>(model->cache()->mailboxSyncState("a").exists()), uidMapA.size());
0758     QCOMPARE(model->cache()->uidMapping("a"), uidMapA);
0759     // flags for UID 1 arre determined deep inside the test helpers, better not check that
0760     QCOMPARE(model->cache()->msgFlags("a", 2), QStringList() << "m2");
0761     QCOMPARE(model->cache()->msgFlags("a", 3), QStringList() << "m3");
0762     cEmpty();
0763 }
0764 
0765 /** @short Similar to testMultipleArrivals, but also check that a request to go to another task is delayed until everything is synced again */
0766 void ImapModelSelectedMailboxUpdatesTest::testMultipleArrivalsBlockingFurtherActivity()
0767 {
0768     initialMessages(1);
0769     cServer("* 2 EXISTS\r\n* 3 EXISTS\r\n");
0770     QByteArray req1 = t.mk("UID FETCH 2:* (FLAGS)\r\n");
0771     QByteArray resp1 = t.last("OK fetched\r\n");
0772     // The UIDs are still unknown at this point, and at the same time the client pessimistically assumes that the first command
0773     // can easily arrive to the server before it sent the second EXISTS, which is why it has no other choice but repeat
0774     // essentially the same command once again.
0775     QByteArray req2 = t.mk("UID FETCH 2:* (FLAGS)\r\n");
0776     QByteArray resp2 = t.last("OK fetched\r\n");
0777 
0778     // Issue a request to go to another mailbox; it will be queued until the responses are finished
0779     QCOMPARE(model->rowCount(msgListB), 0);
0780 
0781     cClient(req1 + req2);
0782     // The server will, however, try to be smart in this case and will send the responses back just one time.
0783     // It's OK to do so per the relevant standards and Trojita won't care.
0784     cServer("* 2 FETCH (UID 2 FLAGS (m2))\r\n"
0785             "* 3 FETCH (UID 3 FLAGS (m3))\r\n"
0786             + resp1 + resp2);
0787     uidMapA << 2 << 3;
0788     existsA = 3;
0789     uidNextA = 4;
0790     helperCheckUidMapFromModel();
0791     QCOMPARE(static_cast<int>(model->cache()->mailboxSyncState("a").exists()), uidMapA.size());
0792     QCOMPARE(model->cache()->uidMapping("a"), uidMapA);
0793     // flags for UID 1 arre determined deep inside the test helpers, better not check that
0794     QCOMPARE(model->cache()->msgFlags("a", 2), QStringList() << "m2");
0795     QCOMPARE(model->cache()->msgFlags("a", 3), QStringList() << "m3");
0796 
0797     // Only now the second SELECT shall be queued
0798     cClient(t.mk("SELECT b\r\n"));
0799     cServer(t.last("OK selected\r\n"));
0800     helperCheckCache();
0801     cEmpty();
0802 }
0803 
0804 /** @short Make sure that a UIDVALIDITY update which does not actually change is value is handled properly */
0805 void ImapModelSelectedMailboxUpdatesTest::testInnocentUidValidityChange()
0806 {
0807     initialMessages(6);
0808     cServer("* OK [UIDVALIDITY " + QByteArray::number(uidValidityA) + "] foo\r\n");
0809     cEmpty();
0810     QVERIFY(model->isNetworkOnline());
0811 }
0812 
0813 /** @short Check that an unexpected change to the UIDVALIDITY while a mailbox is open is handled */
0814 void ImapModelSelectedMailboxUpdatesTest::testUnexpectedUidValidityChange()
0815 {
0816     initialMessages(6);
0817     {
0818         ExpectSingleErrorHere blocker(this);
0819         cServer("* OK [UIDVALIDITY " + QByteArray::number(uidValidityA + 333) + "] foo\r\n");
0820     }
0821     cClient(t.mk("LOGOUT\r\n"));
0822     QVERIFY(!model->isNetworkAvailable());
0823 }
0824 
0825 void ImapModelSelectedMailboxUpdatesTest::testHighestModseqFlags()
0826 {
0827     initialMessages(1);
0828     cServer("* 1 FETCH (UID " + QByteArray::number(uidMapA[0]) + " MODSEQ (666) FLAGS ())\r\n");
0829     helperCheckCache();
0830     cServer("* 1 FETCH (UID " + QByteArray::number(uidMapA[0]) + " MODSEQ (668) FLAGS ())\r\n");
0831     helperCheckCache();
0832     cEmpty();
0833 }
0834 
0835 /** @short Make sure that we handle newly arriving messages correctly even if metadata or part fetch is in progress
0836 
0837 See bug 329757 for details -- the old version asserted that the TreeItemMsgList was already marked as fetched, which
0838 is no longer true when new messages arrive.
0839 */
0840 void ImapModelSelectedMailboxUpdatesTest::testFetchAndConcurrentArrival()
0841 {
0842     using namespace Imap::Mailbox;
0843     model->setProperty("trojita-imap-delayed-fetch-part", 0);
0844 
0845     initialMessages(1);
0846     QModelIndex msg1 = msgListA.model()->index(0, 0, msgListA);
0847     QVERIFY(msg1.isValid());
0848     QCOMPARE(model->rowCount(msg1), 0);
0849 
0850     // Check new arrivals while we're fetching the BODYSTRUCTURE
0851     cClient(t.mk("UID FETCH 1 (" FETCH_METADATA_ITEMS ")\r\n"));
0852     cServer("* 2 EXISTS\r\n"
0853             + helperCreateTrivialEnvelope(1, 1, QLatin1String("new"))
0854             + "* 3 EXISTS\r\n"
0855             + t.last("OK fetched\r\n"));
0856 
0857     // Well, the current code is slightly substandard here; we *could* remember the number of messages for which
0858     // we're already asked, and increment our lowest-UID estimate by one for each of them. However, this will not be
0859     // absolutely safe (we might still get the duplicates because it's UID FETCH, not FETCH, and we're using UIDs as
0860     // offsets, not sequence numbers), so I'm not doing this right now.
0861     {
0862         auto req1 = t.mk("UID FETCH 2:* (FLAGS)\r\n");
0863         auto resp1 = t.last("OK fetched\r\n");
0864         auto req2 = t.mk("UID FETCH 2:* (FLAGS)\r\n");
0865         auto resp2 = t.last("OK fetched\r\n");
0866         // so yup, these are two identical commands
0867         cClient(req1 + req2);
0868         cServer("* 2 FETCH (UID 2 FLAGS ())\r\n"
0869                 "* 3 FETCH (UID 3 FLAGS ())\r\n"
0870                 + resp1 + resp2);
0871         cEmpty();
0872     }
0873 
0874     // Now check what happens when that number is incremented *once again* while our request for part data is in flight
0875     QVERIFY(msg1.parent().data(RoleIsFetched).toBool());
0876     QVERIFY(msg1.data(RoleMessageSubject).isValid());
0877     QVERIFY(!msg1.data(RoleIsFetched).toBool()); // not fully fetched yet, though
0878     QModelIndex msg1p1 = msg1.model()->index(0, 0, msg1);
0879     QVERIFY(msg1p1.isValid());
0880     QCOMPARE(msg1p1.data(RolePartData).toByteArray(), QByteArray());
0881     cClient(t.mk("UID FETCH 1 (BODY.PEEK[1])\r\n"));
0882     cServer("* 4 EXISTS\r\n"
0883             "* 1 FETCH (UID 1 BODY[1] ahoj)\r\n"
0884             "* 5 EXISTS\r\n"
0885             + t.last("OK fetched\r\n"));
0886     QCOMPARE(msg1p1.data(RolePartData).toByteArray(), QByteArray("ahoj"));
0887 
0888     // Again, two identical responses -- see above
0889     {
0890         auto req1 = t.mk("UID FETCH 4:* (FLAGS)\r\n");
0891         auto resp1 = t.last("OK fetched\r\n");
0892         auto req2 = t.mk("UID FETCH 4:* (FLAGS)\r\n");
0893         auto resp2 = t.last("OK fetched\r\n");
0894         cClient(req1 + req2);
0895         cServer("* 4 FETCH (UID 4 FLAGS ())\r\n"
0896                 "* 5 FETCH (UID 5 FLAGS ())\r\n"
0897                 + resp1 + resp2);
0898         cEmpty();
0899     }
0900 
0901     justKeepTask();
0902     cEmpty();
0903 }
0904 
0905 /** @short GMail sends the flags spontaneously, and because it doesn't do \Recent, the flag set is empty */
0906 void ImapModelSelectedMailboxUpdatesTest::testGMailSpontaneousFlagsAndNoRecent()
0907 {
0908     initialMessages(1);
0909     QSignalSpy numbersWatcher(model, SIGNAL(messageCountPossiblyChanged(QModelIndex)));
0910     cServer("* 2 EXISTS\r\n");
0911     QCOMPARE(numbersWatcher.size(), 1);
0912     QCOMPARE(numbersWatcher[0][0].toModelIndex(), QModelIndex(idxA));
0913     numbersWatcher.clear();
0914     QCOMPARE(idxA.data(Imap::Mailbox::RoleUnreadMessageCount).toInt(), 0);
0915     cServer("* 2 FETCH (UID 5000 MODSEQ (4201) FLAGS ())\r\n");
0916     QCOMPARE(numbersWatcher.size(), 1);
0917     QCOMPARE(numbersWatcher[0][0].toModelIndex(), QModelIndex(idxA));
0918     numbersWatcher.clear();
0919     QCOMPARE(idxA.data(Imap::Mailbox::RoleUnreadMessageCount).toInt(), 1);
0920     // Yes, what we do is suboptimal -- it would be better to postone the UID and FLAGS discovery for a tiny moment
0921     // of time in order to allow for GMail's preemptive FETCH which does contain both UID and the FLAGS...
0922     cClient(t.mk("UID FETCH 2:* (FLAGS)\r\n"));
0923     cServer("* 2 FETCH (UID 5000 MODSEQ (4201) FLAGS ())\r\n"
0924             + t.last("OK fetched\r\n"));
0925 
0926     // Now let's try what happens when that new arrival is actually something we've seen before
0927     cServer("* 3 EXISTS\r\n");
0928     QCOMPARE(numbersWatcher.size(), 1);
0929     QCOMPARE(numbersWatcher[0][0].toModelIndex(), QModelIndex(idxA));
0930     numbersWatcher.clear();
0931     QCOMPARE(idxA.data(Imap::Mailbox::RoleUnreadMessageCount).toInt(), 1);
0932     cServer("* 3 FETCH (UID 6000 MODSEQ (333) FLAGS (\\Seen))\r\n");
0933     QCOMPARE(numbersWatcher.size(), 1);
0934     QCOMPARE(numbersWatcher[0][0].toModelIndex(), QModelIndex(idxA));
0935     numbersWatcher.clear();
0936     QCOMPARE(idxA.data(Imap::Mailbox::RoleUnreadMessageCount).toInt(), 1);
0937     cClient(t.mk("UID FETCH 5001:* (FLAGS)\r\n"));
0938     cServer("* 3 FETCH (UID 6000 MODSEQ (333) FLAGS (\\Seen))\r\n"
0939             + t.last("OK fetched\r\n"));
0940 
0941     justKeepTask();
0942     cEmpty();
0943 }
0944 
0945 /** @short Recalculating the message numbers in face of expunges of possibly unknown messages */
0946 void ImapModelSelectedMailboxUpdatesTest::testFlagsRecalcOnExpunge()
0947 {
0948     initialMessages(2);
0949     QSignalSpy numbersWatcher(model, SIGNAL(messageCountPossiblyChanged(QModelIndex)));
0950     cServer("* 1 FETCH (FLAGS ())\r\n");
0951     QCOMPARE(numbersWatcher.size(), 1);
0952     QCOMPARE(numbersWatcher[0][0].toModelIndex(), QModelIndex(idxA));
0953     numbersWatcher.clear();
0954     QCOMPARE(idxA.data(Imap::Mailbox::RoleUnreadMessageCount).toInt(), 1);
0955     cServer("* 3 EXISTS\r\n");
0956     QCOMPARE(numbersWatcher.size(), 1);
0957     QCOMPARE(numbersWatcher[0][0].toModelIndex(), QModelIndex(idxA));
0958     numbersWatcher.clear();
0959     QCOMPARE(idxA.data(Imap::Mailbox::RoleUnreadMessageCount).toInt(), 1);
0960     cClient(t.mk("UID FETCH 3:* (FLAGS)\r\n"));
0961     cServer("* 3 EXPUNGE\r\n");
0962     QCOMPARE(numbersWatcher.size(), 1);
0963     QCOMPARE(numbersWatcher[0][0].toModelIndex(), QModelIndex(idxA));
0964     numbersWatcher.clear();
0965     QCOMPARE(idxA.data(Imap::Mailbox::RoleUnreadMessageCount).toInt(), 1);
0966     cServer(t.last("OK fetched\r\n"));
0967     justKeepTask();
0968     cEmpty();
0969 }
0970 
0971 /** @short Servers reporting UID 0 are buggy, full stop */
0972 void ImapModelSelectedMailboxUpdatesTest::testUid0()
0973 {
0974     initialMessages(2);
0975     {
0976         ExpectSingleErrorHere blocker(this);
0977         cServer("* 3 EXISTS\r\n* 3 FETCH (UID 0)\r\n");
0978     }
0979 }
0980 
0981 /** @short Check that marking of all messages as read doesn't report dataChanged about messages with UID 0 */
0982 void ImapModelSelectedMailboxUpdatesTest::testMarkAllConcurrentArrival()
0983 {
0984     initialMessages(1);
0985     cServer("* 1 FETCH (FLAGS ())\r\n");
0986     justKeepTask();
0987     cEmpty();
0988 
0989     QSignalSpy changedSpy(model, SIGNAL(dataChanged(QModelIndex,QModelIndex)));
0990     connect(model, &QAbstractItemModel::dataChanged, this, &ImapModelSelectedMailboxUpdatesTest::helperDataChangedUidNonZero);
0991     QSignalSpy messageCountChangedSpy(model, SIGNAL(messageCountPossiblyChanged(QModelIndex)));
0992     model->markMailboxAsRead(idxA);
0993     cClient(t.mk("STORE 1:* +FLAGS.SILENT \\Seen\r\n"));
0994     cServer("* 2 EXISTS\r\n" + t.last("OK marked\r\n"));
0995 
0996     QCOMPARE(changedSpy.size(), 5);
0997     // 1st pair of signals is for TreeItemMsgList (and also TreeItemMailbox because it uses data from that layer) due to the EXISTS
0998     QCOMPARE(changedSpy[0][0].toModelIndex(), QModelIndex(msgListA));
0999     QCOMPARE(changedSpy[0][1].toModelIndex(), QModelIndex(msgListA));
1000     QCOMPARE(changedSpy[1][0].toModelIndex(), QModelIndex(idxA));
1001     QCOMPARE(changedSpy[1][1].toModelIndex(), QModelIndex(idxA));
1002     // now report each changed message
1003     QCOMPARE(changedSpy[2][0].toModelIndex(), QModelIndex(msgListA.model()->index(0, 0, msgListA)));
1004     QCOMPARE(changedSpy[2][1].toModelIndex(), QModelIndex(msgListA.model()->index(0, 0, msgListA)));
1005     // and now a pair of signals for the (list, mailbox) pair due to the mass-update of indexes
1006     QCOMPARE(changedSpy[3][0].toModelIndex(), QModelIndex(msgListA));
1007     QCOMPARE(changedSpy[3][1].toModelIndex(), QModelIndex(msgListA));
1008     QCOMPARE(changedSpy[4][0].toModelIndex(), QModelIndex(idxA));
1009     QCOMPARE(changedSpy[4][1].toModelIndex(), QModelIndex(idxA));
1010     // Two bits, one for the initial EXISTS, second for the flag update
1011     QCOMPARE(messageCountChangedSpy.size(), 2);
1012     QCOMPARE(messageCountChangedSpy[0][0].toModelIndex(), QModelIndex(idxA));
1013     QCOMPARE(messageCountChangedSpy[1][0].toModelIndex(), QModelIndex(idxA));
1014 
1015     // This is a crude thing, but the point is that the cache should not have been called with an update to an unknown message
1016     QCOMPARE(model->cache()->msgFlags(QLatin1String("a"), 0), QStringList());
1017 
1018     cClient(t.mk("UID FETCH 2:* (FLAGS)\r\n"));
1019     cServer("* 2 FETCH (UID 1002 FLAGS (foo))\r\n" + t.last("OK done\r\n"));
1020     QCOMPARE(model->cache()->msgFlags(QLatin1String("a"), 0), QStringList());
1021     QCOMPARE(model->cache()->msgFlags(QLatin1String("a"), 1002), QStringList() << QLatin1String("foo"));
1022     justKeepTask();
1023     cEmpty();
1024 }
1025 
1026 /** @short Helper for testMarkAllConcurrentArrival */
1027 void ImapModelSelectedMailboxUpdatesTest::helperDataChangedUidNonZero(const QModelIndex &a, const QModelIndex &b)
1028 {
1029     QVERIFY(a.isValid());
1030     QVERIFY(b.isValid());
1031     if (a.parent() == msgListA) {
1032         QVERIFY(a.data(Imap::Mailbox::RoleMessageUid).toUInt() != 0);
1033         QVERIFY(b.data(Imap::Mailbox::RoleMessageUid).toUInt() != 0);
1034     }
1035 }
1036 
1037 void ImapModelSelectedMailboxUpdatesTest::testLogoutClosed()
1038 {
1039     Imap::Mailbox::DummyNetworkWatcher nm(nullptr, model);
1040     model->switchToMailbox(idxA);
1041     cClient(t.mk("SELECT a\r\n"));
1042     cServer("* 0 EXISTS\r\n"
1043             + t.last("OK selected\r\n"));
1044     model->switchToMailbox(idxB);
1045     cClient(t.mk("SELECT b\r\n"));
1046     auto okSelectedB = t.last("OK selected\r\n");
1047     nm.setNetworkOffline();
1048     cClient(t.mk("LOGOUT\r\n"));
1049     cServer("* OK [CLOSED] Previous mailbox closed.\r\n");
1050     cServer("* 1 EXISTS\r\n"
1051             + okSelectedB);
1052     cEmpty();
1053     cServer("* BYE closing now\r\n");
1054 }
1055 
1056 /** @short Make sure that incremental upload of message metadata bits works, too */
1057 void ImapModelSelectedMailboxUpdatesTest::testFetchMsgMetadataPerPartes()
1058 {
1059     initialMessages(1);
1060     cServer("* 1 FETCH (FLAGS ())\r\n");
1061     justKeepTask();
1062     cEmpty();
1063 
1064     auto msg1 = msgListA.model()->index(0, 0, msgListA);
1065     QVERIFY(msg1.isValid());
1066 
1067     QSignalSpy insertionSpy(model, SIGNAL(rowsInserted(QModelIndex,int,int)));
1068 
1069     QCOMPARE(model->rowCount(msg1), 0);
1070     cClient(t.mk("UID FETCH 1 (" FETCH_METADATA_ITEMS ")\r\n"));
1071     QCOMPARE(msg1.data(Imap::Mailbox::RoleIsFetched).toBool(), false);
1072     cServer("* 1 FETCH (INTERNALDATE \"15-Jan-2013 12:17:06 +0000\")\r\n");
1073     QCOMPARE(msg1.data(Imap::Mailbox::RoleIsFetched).toBool(), false);
1074     cServer("* 1 FETCH (ENVELOPE (NIL \"pwn\" NIL NIL NIL NIL NIL NIL NIL NIL))\r\n");
1075     QCOMPARE(msg1.data(Imap::Mailbox::RoleIsFetched).toBool(), false);
1076     cServer("* 1 FETCH (BODYSTRUCTURE (" + bsPlaintext + "))\r\n");
1077     QCOMPARE(msg1.data(Imap::Mailbox::RoleIsFetched).toBool(), false);
1078     QCOMPARE(insertionSpy.size(), 1);
1079     cServer("* 1 FETCH (RFC822.SIZE 666)\r\n");
1080     QCOMPARE(msg1.data(Imap::Mailbox::RoleIsFetched).toBool(), true);
1081     cServer(t.last("OK fetched\r\n"));
1082     QCOMPARE(msg1.data(Imap::Mailbox::RoleIsFetched).toBool(), true);
1083     cServer("* 1 FETCH (BODYSTRUCTURE (" + bsPlaintext + "))\r\n");
1084     cEmpty();
1085     QCOMPARE(insertionSpy.size(), 1);
1086     justKeepTask();
1087 }
1088 
1089 class MonitoringCache : public Imap::Mailbox::MemoryCache {
1090 public:
1091     void setMessageMetadata(const QString &mailbox, const uint uid, const MessageDataBundle &metadata) override
1092     {
1093         msgMetadataLog.emplace_back(uid);
1094         MemoryCache::setMessageMetadata(mailbox, uid, metadata);
1095     }
1096     std::vector<uint> msgMetadataLog;
1097 };
1098 
1099 /** @short Do we survive duplicate unsolicited BODYSTRUCTURE responses? */
1100 void ImapModelSelectedMailboxUpdatesTest::testFetchMsgDuplicateBodystructure()
1101 {
1102     auto cacheLog = std::make_shared<MonitoringCache>();
1103     model->setCache(cacheLog);
1104     initialMessages(1);
1105     cServer("* 1 FETCH (FLAGS ())\r\n");
1106     justKeepTask();
1107     cEmpty();
1108 
1109     auto msg1 = msgListA.model()->index(0, 0, msgListA);
1110     QVERIFY(msg1.isValid());
1111 
1112     QCOMPARE(model->rowCount(msg1), 0);
1113     cClient(t.mk("UID FETCH 1 (" FETCH_METADATA_ITEMS ")\r\n"));
1114     QCOMPARE(msg1.data(Imap::Mailbox::RoleIsFetched).toBool(), false);
1115     QCOMPARE(cacheLog->msgMetadataLog.size(), size_t(0));
1116     cServer("* 1 FETCH (BODYSTRUCTURE (" + bsPlaintext + "))\r\n");
1117     QCOMPARE(msg1.data(Imap::Mailbox::RoleIsFetched).toBool(), false);
1118     QCOMPARE(model->rowCount(msg1), 1);
1119     QCOMPARE(cacheLog->msgMetadataLog.size(), size_t(0));
1120     // Now send the duplicate data. We shouldn't assert.
1121     cServer("* 1 FETCH (BODYSTRUCTURE (" + bsPlaintext + "))\r\n");
1122     QCOMPARE(msg1.data(Imap::Mailbox::RoleIsFetched).toBool(), false);
1123     QCOMPARE(model->rowCount(msg1), 1);
1124     QCOMPARE(cacheLog->msgMetadataLog.size(), size_t(0));
1125     cServer(t.last("OK fetched\r\n"));
1126     QCOMPARE(msg1.data(Imap::Mailbox::RoleIsFetched).toBool(), false);
1127     QCOMPARE(model->rowCount(msg1), 1);
1128     cServer("* 1 FETCH (RFC822.SIZE 1234 INTERNALDATE \"15-Jan-2013 12:17:06 +0000\" "
1129             "ENVELOPE (NIL \"pwn\" NIL NIL NIL NIL NIL NIL NIL NIL))\r\n");
1130     QCOMPARE(cacheLog->msgMetadataLog.size(), size_t(1));
1131     QCOMPARE(msg1.data(Imap::Mailbox::RoleIsFetched).toBool(), true);
1132     cServer("* 1 FETCH (BODYSTRUCTURE (" + bsPlaintext + "))\r\n");
1133     QCOMPARE(cacheLog->msgMetadataLog.size(), size_t(1));
1134     cEmpty();
1135     justKeepTask();
1136 
1137 }
1138 
1139 QTEST_GUILESS_MAIN( ImapModelSelectedMailboxUpdatesTest )