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 )