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 )