File indexing completed on 2024-06-16 05:01:56
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 <QtTest> 0024 #include "test_Imap_Tasks_ListChildMailboxes.h" 0025 #include "Common/MetaTypes.h" 0026 #include "Streams/FakeSocket.h" 0027 #include "Imap/Model/ItemRoles.h" 0028 #include "Imap/Model/MailboxFinder.h" 0029 #include "Imap/Model/MailboxModel.h" 0030 #include "Imap/Model/MemoryCache.h" 0031 #include "Imap/Model/Model.h" 0032 #include "Imap/Tasks/Fake_ListChildMailboxesTask.h" 0033 0034 void ImapModelListChildMailboxesTest::init() 0035 { 0036 m_fakeListCommand = false; 0037 LibMailboxSync::init(); 0038 0039 LibMailboxSync::setModelNetworkPolicy(model, Imap::Mailbox::NETWORK_ONLINE); 0040 QCoreApplication::processEvents(); 0041 t.reset(); 0042 } 0043 0044 void ImapModelListChildMailboxesTest::testSimpleListing() 0045 { 0046 QCOMPARE(model->rowCount(QModelIndex()), 1); 0047 cClient(t.mk("LIST \"\" \"%\"\r\n")); 0048 cServer("* LIST (\\HasNoChildren) \".\" \"b\"\r\n" 0049 "* LIST (\\HasChildren) \".\" \"a\"\r\n" 0050 "* LIST (\\Noselect \\HasChildren) \".\" \"xyz\"\r\n" 0051 "* LIST (\\HasNoChildren) \".\" \"INBOX\"\r\n" 0052 + t.last("OK List done.\r\n")); 0053 QCOMPARE(model->rowCount( QModelIndex() ), 5); 0054 // the first one will be "list of messages" 0055 QModelIndex idxInbox = model->index(1, 0, QModelIndex()); 0056 QModelIndex idxA = model->index(2, 0, QModelIndex()); 0057 QModelIndex idxB = model->index(3, 0, QModelIndex()); 0058 QModelIndex idxXyz = model->index(4, 0, QModelIndex()); 0059 QCOMPARE(model->data(idxInbox, Qt::DisplayRole), QVariant(QLatin1String("INBOX"))); 0060 QCOMPARE(model->data(idxA, Qt::DisplayRole), QVariant(QLatin1String("a"))); 0061 QCOMPARE(model->data(idxB, Qt::DisplayRole), QVariant(QLatin1String("b"))); 0062 QCOMPARE(model->data(idxXyz, Qt::DisplayRole), QVariant(QLatin1String("xyz"))); 0063 cEmpty(); 0064 QCOMPARE(model->rowCount(idxInbox), 1); // just the "list of messages" 0065 QCOMPARE(model->rowCount(idxB), 1); // just the "list of messages" 0066 cEmpty(); 0067 model->rowCount(idxA); 0068 model->rowCount(idxXyz); 0069 QByteArray c1 = t.mk("LIST \"\" \"a.%\"\r\n"); 0070 QByteArray r1 = t.last("OK listed\r\n"); 0071 QByteArray c2 = t.mk("LIST \"\" \"xyz.%\"\r\n"); 0072 QByteArray r2 = t.last("OK listed\r\n"); 0073 cClient(c1 + c2); 0074 cServer("* LIST (\\HasNoChildren) \".\" \"a.aa\"\r\n" 0075 "* LIST (\\HasNoChildren) \".\" \"a.ab\"\r\n" 0076 + r1 + 0077 "* LIST (\\HasNoChildren) \".\" \"xyz.a\"\r\n" 0078 "* LIST (\\HasNoChildren) \".\" \"xyz.c\"\r\n" 0079 + r2); 0080 cEmpty(); 0081 QCOMPARE(model->rowCount(idxA), 3); 0082 QCOMPARE(model->rowCount(idxXyz), 3); 0083 cEmpty(); 0084 } 0085 0086 void ImapModelListChildMailboxesTest::testFailingList() 0087 { 0088 QCOMPARE(model->rowCount(QModelIndex()), 1); 0089 cClient(t.mk("LIST \"\" \"%\"\r\n")); 0090 QCOMPARE(model->data(QModelIndex(), Imap::Mailbox::RoleIsFetched).toBool(), false); 0091 QCOMPARE(model->data(QModelIndex(), Imap::Mailbox::RoleIsUnavailable).toBool(), false); 0092 cServer(t.last("NO no joy today\r\n")); 0093 QCOMPARE(model->data(QModelIndex(), Imap::Mailbox::RoleIsFetched).toBool(), false); 0094 QCOMPARE(model->data(QModelIndex(), Imap::Mailbox::RoleIsUnavailable).toBool(), true); 0095 QCOMPARE(model->rowCount(QModelIndex()), 1); 0096 cEmpty(); 0097 model->reloadMailboxList(); 0098 cClient(t.mk("LIST \"\" \"%\"\r\n")); 0099 QCOMPARE(model->data(QModelIndex(), Imap::Mailbox::RoleIsFetched).toBool(), false); 0100 QCOMPARE(model->data(QModelIndex(), Imap::Mailbox::RoleIsUnavailable).toBool(), false); 0101 cServer(t.last("OK listed\r\n")); 0102 QCOMPARE(model->data(QModelIndex(), Imap::Mailbox::RoleIsFetched).toBool(), true); 0103 QCOMPARE(model->data(QModelIndex(), Imap::Mailbox::RoleIsUnavailable).toBool(), false); 0104 QCOMPARE(model->rowCount(QModelIndex()), 1); 0105 cEmpty(); 0106 } 0107 0108 void ImapModelListChildMailboxesTest::testFakeListing() 0109 { 0110 taskFactoryUnsafe->fakeListChildMailboxes = true; 0111 taskFactoryUnsafe->fakeListChildMailboxesMap[ QLatin1String("") ] = QStringList() << QStringLiteral("a") << QStringLiteral("b"); 0112 taskFactoryUnsafe->fakeListChildMailboxesMap[ QStringLiteral("a") ] = QStringList() << QStringLiteral("aa") << QStringLiteral("ab"); 0113 model->rowCount( QModelIndex() ); 0114 QCoreApplication::processEvents(); 0115 QCoreApplication::processEvents(); 0116 QCOMPARE( model->rowCount( QModelIndex() ), 3 ); 0117 QModelIndex idxA = model->index( 1, 0, QModelIndex() ); 0118 QModelIndex idxB = model->index( 2, 0, QModelIndex() ); 0119 QCOMPARE( model->data( idxA, Qt::DisplayRole ), QVariant(QLatin1String("a")) ); 0120 QCOMPARE( model->data( idxB, Qt::DisplayRole ), QVariant(QLatin1String("b")) ); 0121 model->rowCount( idxA ); 0122 model->rowCount( idxB ); 0123 QCoreApplication::processEvents(); 0124 QCoreApplication::processEvents(); 0125 QCOMPARE( model->rowCount( idxA ), 3 ); 0126 QCOMPARE( model->rowCount( idxB ), 1 ); 0127 QVERIFY( SOCK->writtenStuff().isEmpty() ); 0128 cleanup(); init(); 0129 } 0130 0131 void ImapModelListChildMailboxesTest::testBackslashes() 0132 { 0133 model->rowCount(QModelIndex()); 0134 cClient(t.mk("LIST \"\" \"%\"\r\n")); 0135 cServer("* LIST (\\HasNoChildren) \"/\" Drafts\r\n" 0136 "* LIST (\\HasNoChildren) \"/\" {13}\r\nMail\\Papelera\r\n" 0137 "* LIST () \"/\" INBOX\r\n" 0138 + t.last("OK LIST completed\r\n")); 0139 QCOMPARE(model->rowCount(QModelIndex()), 4); 0140 QModelIndex withBackSlash = model->index(3, 0, QModelIndex()); 0141 QVERIFY(withBackSlash.isValid()); 0142 QCOMPARE(withBackSlash.data(Imap::Mailbox::RoleMailboxName).toString(), QString::fromUtf8("Mail\\Papelera")); 0143 QCOMPARE(withBackSlash.data(Imap::Mailbox::RoleUnreadMessageCount).toInt(), 0); 0144 cClient(t.mk("STATUS \"Mail\\\\Papelera\" (MESSAGES UNSEEN RECENT)\r\n")); 0145 cServer("* STATUS {13}\r\nMail\\Papelera (MESSAGES 1 RECENT 1 UNSEEN 1)\r\n" + t.last("OK status done\r\n")); 0146 QCOMPARE(withBackSlash.data(Imap::Mailbox::RoleUnreadMessageCount).toInt(), 1); 0147 cEmpty(); 0148 } 0149 0150 /** @short Check that cached mailboxes do not send away STATUS when they are going to be immediately replaced anyway */ 0151 void ImapModelListChildMailboxesTest::testNoStatusForCachedItems() 0152 { 0153 using namespace Imap::Mailbox; 0154 0155 // Remember two mailboxes 0156 model->cache()->setChildMailboxes(QString(), QList<MailboxMetadata>() 0157 << MailboxMetadata(QStringLiteral("a"), QStringLiteral("."), QStringList()) 0158 << MailboxMetadata(QStringLiteral("b"), QStringLiteral("."), QStringList()) 0159 ); 0160 // ... and the numbers for the first of them 0161 SyncState s; 0162 s.setExists(10); 0163 s.setRecent(1); 0164 s.setUnSeenCount(2); 0165 QVERIFY(s.isUsableForNumbers()); 0166 model->cache()->setMailboxSyncState(QStringLiteral("b"), s); 0167 0168 // touch the network 0169 QCOMPARE(model->rowCount(QModelIndex()), 1); 0170 cClient(t.mk("LIST \"\" \"%\"\r\n")); 0171 // ...but the data gets refreshed from cache immediately 0172 QCOMPARE(model->rowCount(QModelIndex()), 3); 0173 0174 // just settings up indexes 0175 idxA = model->index(1, 0, QModelIndex()); 0176 QVERIFY(idxA.isValid()); 0177 QCOMPARE(idxA.data(RoleMailboxName).toString(), QString::fromUtf8("a")); 0178 idxB = model->index(2, 0, QModelIndex()); 0179 QVERIFY(idxB.isValid()); 0180 QCOMPARE(idxB.data(RoleMailboxName).toString(), QString::fromUtf8("b")); 0181 0182 // Request message counts; this should not lead to network activity even though the actual number 0183 // is not stored in the cache for mailbox "a", simply because the whole mailbox will get replaced 0184 // after the LIST finishes anyway 0185 QCOMPARE(idxA.data(RoleTotalMessageCount), QVariant()); 0186 QCOMPARE(idxA.data(RoleMailboxNumbersFetched).toBool(), false); 0187 QCOMPARE(idxB.data(RoleTotalMessageCount).toInt(), 10); 0188 QCOMPARE(idxB.data(RoleMailboxNumbersFetched).toBool(), false); 0189 cEmpty(); 0190 0191 cServer("* LIST (\\HasNoChildren) \".\" a\r\n" 0192 "* LIST (\\HasNoChildren) \".\" b\r\n" 0193 + t.last("OK listed\r\n")); 0194 cEmpty(); 0195 0196 // The mailboxes are replaced now -> rebuild the indexes 0197 QVERIFY(!idxA.isValid()); 0198 QVERIFY(!idxB.isValid()); 0199 idxA = model->index(1, 0, QModelIndex()); 0200 QVERIFY(idxA.isValid()); 0201 QCOMPARE(idxA.data(RoleMailboxName).toString(), QString::fromUtf8("a")); 0202 idxB = model->index(2, 0, QModelIndex()); 0203 QVERIFY(idxB.isValid()); 0204 QCOMPARE(idxB.data(RoleMailboxName).toString(), QString::fromUtf8("b")); 0205 0206 // Ask for the numbers again 0207 QCOMPARE(idxA.data(RoleTotalMessageCount), QVariant()); 0208 QCOMPARE(idxB.data(RoleTotalMessageCount), QVariant()); 0209 QByteArray c1 = t.mk("STATUS a (MESSAGES UNSEEN RECENT)\r\n"); 0210 QByteArray r1 = t.last("OK status\r\n"); 0211 QByteArray c2 = t.mk("STATUS b (MESSAGES UNSEEN RECENT)\r\n"); 0212 QByteArray r2 = t.last("OK status\r\n"); 0213 cClient(c1 + c2); 0214 cServer("* STATUS a (MESSAGES 1 RECENT 2 UNSEEN 3)\r\n" 0215 "* STATUS b (MESSAGES 666 RECENT 33 UNSEEN 2)\r\n"); 0216 QCOMPARE(idxA.data(RoleTotalMessageCount).toInt(), 1); 0217 QCOMPARE(idxA.data(RoleMailboxNumbersFetched).toBool(), true); 0218 QCOMPARE(idxB.data(RoleTotalMessageCount).toInt(), 666); 0219 QCOMPARE(idxB.data(RoleMailboxNumbersFetched).toBool(), true); 0220 QCOMPARE(model->cache()->mailboxSyncState(QLatin1String("a")).unSeenCount(), 3u); 0221 QCOMPARE(model->cache()->mailboxSyncState(QLatin1String("a")).recent(), 2u); 0222 // the "EXISTS" is missing, though 0223 QVERIFY(!model->cache()->mailboxSyncState(QLatin1String("a")).isUsableForNumbers()); 0224 QCOMPARE(model->cache()->mailboxSyncState(QLatin1String("b")).unSeenCount(), 2u); 0225 QCOMPARE(model->cache()->mailboxSyncState(QLatin1String("b")).recent(), 33u); 0226 // The mailbox state for mailbox "b" must be preserved 0227 QCOMPARE(model->cache()->mailboxSyncState(QLatin1String("b")).exists(), 10u); 0228 QVERIFY(model->cache()->mailboxSyncState(QLatin1String("b")).isUsableForNumbers()); 0229 cServer(r2 + r1); 0230 cEmpty(); 0231 } 0232 0233 /** @short Concurrent listing at several levels of the nesting 0234 0235 https://bugs.kde.org/show_bug.cgi?id=364314 0236 */ 0237 void ImapModelListChildMailboxesTest::testAutoExpanding() 0238 { 0239 Imap::Mailbox::MailboxModel mm(nullptr, model); 0240 Imap::Mailbox::MailboxFinder finder(nullptr, &mm); 0241 0242 auto resetWatchedMailboxes = [&finder]() { 0243 finder.addMailbox(QStringLiteral("a")); 0244 finder.addMailbox(QStringLiteral("a.A")); 0245 finder.addMailbox(QStringLiteral("a.A.1")); 0246 }; 0247 0248 resetWatchedMailboxes(); 0249 connect(&mm, &QAbstractItemModel::layoutChanged, this, resetWatchedMailboxes); 0250 connect(&mm, &QAbstractItemModel::rowsRemoved, this, resetWatchedMailboxes); 0251 connect(&mm, &QAbstractItemModel::modelReset, this, resetWatchedMailboxes); 0252 0253 cClient(t.mk("LIST \"\" \"%\"\r\n")); 0254 cServer("* LIST (\\HasChildren) \".\" a\r\n" 0255 + t.last("OK listed\r\n")); 0256 cClient(t.mk("LIST \"\" \"a.%\"\r\n")); 0257 cServer("* LIST (\\HasChildren) \".\" a.A\r\n" 0258 + t.last("OK listed\r\n")); 0259 cClient(t.mk("LIST \"\" \"a.A.%\"\r\n")); 0260 cServer("* LIST (\\HasNoChildren) \".\" a.A.1\r\n" 0261 + t.last("OK listed\r\n")); 0262 0263 LibMailboxSync::setModelNetworkPolicy(model, Imap::Mailbox::NETWORK_OFFLINE); 0264 LibMailboxSync::setModelNetworkPolicy(model, Imap::Mailbox::NETWORK_ONLINE); 0265 t.reset(); 0266 0267 cClient(t.mk("LIST \"\" \"%\"\r\n")); 0268 cServer("* LIST (\\HasChildren) \".\" a\r\n" 0269 + t.last("OK listed\r\n")); 0270 auto q1 = t.mk("LIST \"\" \"a.%\"\r\n"); 0271 auto r1 = t.last("OK listed\r\n"); 0272 auto q2 = t.mk("LIST \"\" \"a.A.%\"\r\n"); 0273 auto r2 = t.last("OK listed\r\n"); 0274 cClient(q1 + q2); 0275 cServer("* LIST (\\HasChildren) \".\" a.A\r\n" 0276 "* LIST (\\HasNoChildren) \".\" a.A.1\r\n" 0277 + r1 + r2); 0278 0279 auto idx_a = mm.index(0, 0, QModelIndex()); 0280 QVERIFY(idx_a.isValid()); 0281 QCOMPARE(idx_a.data(Imap::Mailbox::RoleMailboxName).toString(), QString::fromUtf8("a")); 0282 QCOMPARE(mm.rowCount(idx_a), 1); // KDE bug 364314, "a.A.1" must not show up beneath "a" 0283 auto idx_a_A = mm.index(0, 0, idx_a); 0284 QVERIFY(idx_a_A.isValid()); 0285 QCOMPARE(idx_a_A.data(Imap::Mailbox::RoleMailboxName).toString(), QString::fromUtf8("a.A")); 0286 QCOMPARE(mm.rowCount(idx_a_A), 1); 0287 0288 // ...and because the q1's response invalidated q2's root, it gets re-requested 0289 cClient(t.mk("LIST \"\" \"a.A.%\"\r\n")); 0290 cServer("* LIST (\\HasNoChildren) \".\" a.A.1\r\n" 0291 + t.last("OK listed\r\n")); 0292 QCOMPARE(mm.rowCount(idx_a), 1); 0293 QCOMPARE(mm.rowCount(idx_a_A), 1); 0294 cEmpty(); 0295 } 0296 0297 0298 QTEST_GUILESS_MAIN( ImapModelListChildMailboxesTest )