File indexing completed on 2024-06-16 05:01:54

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_Idle.h"
0025 #include "Streams/FakeSocket.h"
0026 #include "Imap/Tasks/IdleLauncher.h"
0027 #include "Imap/Model/ItemRoles.h"
0028 #include "Imap/Tasks/KeepMailboxOpenTask.h"
0029 #include "Utils/FakeCapabilitiesInjector.h"
0030 
0031 /** @short Wait for the IDLE to arrive, or a timeout
0032 
0033 The motivation here is to avoid being too fast, as reported in Redmine#275
0034 */
0035 #define waitForIdle() \
0036 { \
0037     QTest::qWait(40); \
0038     QByteArray written = SOCK->writtenStuff(); \
0039     int times = 0; \
0040     while (written.isEmpty() && times < 4) { \
0041         QTest::qWait(5); \
0042         written = SOCK->writtenStuff(); \
0043         ++times; \
0044     } \
0045     QCOMPARE(written, t.mk("IDLE\r\n")); \
0046 }
0047 
0048 /** @short Test a NO reply to IDLE command */
0049 void ImapModelIdleTest::testIdleNo()
0050 {
0051     model->setProperty("trojita-imap-idle-delayedEnter", QVariant(30));
0052     FakeCapabilitiesInjector injector(model);
0053     injector.injectCapability(QStringLiteral("IDLE"));
0054     existsA = 3;
0055     uidValidityA = 6;
0056     uidMapA << 1 << 7 << 9;
0057     uidNextA = 16;
0058     helperSyncAWithMessagesEmptyState();
0059     QVERIFY(SOCK->writtenStuff().isEmpty());
0060     waitForIdle();
0061     SOCK->fakeReading(t.last("NO you can't idle now\r\n"));
0062     QTest::qWait(40);
0063     QVERIFY(SOCK->writtenStuff().isEmpty());
0064     helperSyncBNoMessages();
0065     QVERIFY(errorSpy->isEmpty());
0066 }
0067 
0068 /** @short Test what happens when IDLE terminates by an OK, but without our "DONE" input */
0069 void ImapModelIdleTest::testIdleImmediateReturn()
0070 {
0071     model->setProperty("trojita-imap-idle-delayedEnter", QVariant(30));
0072     FakeCapabilitiesInjector injector(model);
0073     injector.injectCapability(QStringLiteral("IDLE"));
0074     existsA = 3;
0075     uidValidityA = 6;
0076     uidMapA << 1 << 7 << 9;
0077     uidNextA = 16;
0078     helperSyncAWithMessagesEmptyState();
0079     QVERIFY(SOCK->writtenStuff().isEmpty());
0080     waitForIdle();
0081     SOCK->fakeReading(QByteArray("+ blah\r\n") + t.last("OK done\r\n"));
0082     waitForIdle();
0083 }
0084 
0085 /** @short Test automatic IDLE renewal */
0086 void ImapModelIdleTest::testIdleRenewal()
0087 {
0088     model->setProperty("trojita-imap-idle-delayedEnter", QVariant(30));
0089     model->setProperty("trojita-imap-idle-renewal", QVariant(10));
0090     FakeCapabilitiesInjector injector(model);
0091     injector.injectCapability(QStringLiteral("IDLE"));
0092     existsA = 3;
0093     uidValidityA = 6;
0094     uidMapA << 1 << 7 << 9;
0095     uidNextA = 16;
0096     helperSyncAWithMessagesEmptyState();
0097     QVERIFY(SOCK->writtenStuff().isEmpty());
0098     waitForIdle();
0099     SOCK->fakeReading(QByteArray("+ blah\r\n"));
0100     QTest::qWait(50);
0101     QCOMPARE( SOCK->writtenStuff(), QByteArray("DONE\r\n") );
0102     SOCK->fakeReading(t.last("OK done\r\n"));
0103     waitForIdle();
0104 }
0105 
0106 /** @short Test that IDLE gets immediately interrupted by any Task */
0107 void ImapModelIdleTest::testIdleBreakTask()
0108 {
0109     model->setProperty("trojita-imap-idle-delayedEnter", QVariant(30));
0110     // Intentionally leave trojita-imap-idle-renewal at its rather high default value
0111     FakeCapabilitiesInjector injector(model);
0112     injector.injectCapability(QStringLiteral("IDLE"));
0113     existsA = 3;
0114     uidValidityA = 6;
0115     uidMapA << 1 << 7 << 9;
0116     uidNextA = 16;
0117     helperSyncAWithMessagesEmptyState();
0118     QVERIFY(SOCK->writtenStuff().isEmpty());
0119     waitForIdle();
0120     SOCK->fakeReading(QByteArray("+ blah\r\n"));
0121     QCOMPARE( msgListA.model()->index(0, 0, msgListA).data(Imap::Mailbox::RoleMessageFrom).toString(), QString() );
0122     QCoreApplication::processEvents();
0123     QCoreApplication::processEvents();
0124     QCoreApplication::processEvents();
0125     QCoreApplication::processEvents();
0126     QCOMPARE(SOCK->writtenStuff(), QByteArray(QByteArray("DONE\r\n") + t.mk("UID FETCH 1,7,9 (" FETCH_METADATA_ITEMS ")\r\n")));
0127     SOCK->fakeReading(t.last("OK done\r\n"));
0128     QTest::qWait(40);
0129     QVERIFY(SOCK->writtenStuff().isEmpty());
0130 }
0131 
0132 /** @short Test automatic IDLE renewal when server gets really slow to respond */
0133 void ImapModelIdleTest::testIdleSlowResponses()
0134 {
0135     model->setProperty("trojita-imap-idle-delayedEnter", QVariant(30));
0136     model->setProperty("trojita-imap-idle-renewal", QVariant(10));
0137     FakeCapabilitiesInjector injector(model);
0138     injector.injectCapability(QStringLiteral("IDLE"));
0139     existsA = 3;
0140     uidValidityA = 6;
0141     uidMapA << 1 << 7 << 9;
0142     uidNextA = 16;
0143     helperSyncAWithMessagesEmptyState();
0144     QVERIFY(SOCK->writtenStuff().isEmpty());
0145 
0146     waitForIdle();
0147     // Check what happens if it takes the server a lot of time to issue the initial continuation
0148     QTest::qWait(70);
0149     SOCK->fakeReading(QByteArray("+ blah\r\n"));
0150     QCoreApplication::processEvents();
0151     QCoreApplication::processEvents();
0152     QCOMPARE( SOCK->writtenStuff(), QByteArray("DONE\r\n") );
0153     SOCK->fakeReading(t.last("OK done\r\n"));
0154 
0155     waitForIdle();
0156     SOCK->fakeReading(QByteArray("+ blah\r\n"));
0157     // The client is fast enough...
0158     QTest::qWait(40);
0159     QCOMPARE( SOCK->writtenStuff(), QByteArray("DONE\r\n") );
0160     // ...but the server is taking its time
0161     QTest::qWait(70);
0162     QVERIFY(SOCK->writtenStuff().isEmpty());
0163     SOCK->fakeReading(t.last("OK done\r\n"));
0164     QCoreApplication::processEvents();
0165     QCoreApplication::processEvents();
0166     QVERIFY(SOCK->writtenStuff().isEmpty());
0167     //QCOMPARE( SOCK->writtenStuff(), t.mk("DONE\r\n") );
0168 }
0169 
0170 /** @short Test that the automatic IDLE renewal gets disabled when IDLE finishes */
0171 void ImapModelIdleTest::testIdleNoPerpetuateRenewal()
0172 {
0173     // we shouldn't enter IDLE automatically
0174     model->setProperty("trojita-imap-idle-delayedEnter", QVariant(1000 * 1000 ));
0175     model->setProperty("trojita-imap-idle-renewal", QVariant(10));
0176     FakeCapabilitiesInjector injector(model);
0177     injector.injectCapability(QStringLiteral("IDLE"));
0178     existsA = 3;
0179     uidValidityA = 6;
0180     uidMapA << 1 << 7 << 9;
0181     uidNextA = 16;
0182     helperSyncAWithMessagesEmptyState();
0183     QVERIFY(SOCK->writtenStuff().isEmpty());
0184 
0185     // Force manual trigger of the IDLE
0186     model->findTaskResponsibleFor(idxA)->idleLauncher->slotEnterIdleNow();
0187     QCoreApplication::processEvents();
0188     QCoreApplication::processEvents();
0189     // we check it immediately, ie. no via the waitForIdle()
0190     QCOMPARE( SOCK->writtenStuff(), t.mk("IDLE\r\n") );
0191     SOCK->fakeReading(t.last("NO you can't idle now\r\n"));
0192     // ...make sure it won't try to "break long IDLE"
0193     QTest::qWait(30);
0194     // switch away
0195     helperSyncBNoMessages();
0196     QVERIFY(errorSpy->isEmpty());
0197 
0198     // Now go back to mailbox A
0199     model->switchToMailbox(idxA);
0200     helperSyncAWithMessagesNoArrivals();
0201 
0202     // Force manual trigger of the IDLE
0203     model->findTaskResponsibleFor(idxA)->idleLauncher->slotEnterIdleNow();
0204     QCoreApplication::processEvents();
0205     QCoreApplication::processEvents();
0206     QCOMPARE( SOCK->writtenStuff(), t.mk("IDLE\r\n") );
0207     SOCK->fakeReading(QByteArray("+ blah\r\n"));
0208     QCoreApplication::processEvents();
0209     QCoreApplication::processEvents();
0210 
0211     // so we're in regular IDLE and want to break it
0212     QCOMPARE( msgListA.model()->index(0, 0, msgListA).data(Imap::Mailbox::RoleMessageFrom).toString(), QString() );
0213     QCoreApplication::processEvents();
0214     QCoreApplication::processEvents();
0215     QCoreApplication::processEvents();
0216     QCoreApplication::processEvents();
0217     QCOMPARE(SOCK->writtenStuff(), QByteArray(QByteArray("DONE\r\n") + t.mk("UID FETCH 1,7,9 (" FETCH_METADATA_ITEMS ")\r\n")));
0218     SOCK->fakeReading(t.last("OK done\r\n"));
0219     // Make sure we won't try to "renew" it automatically...
0220     QTest::qWait(30);
0221     QVERIFY(SOCK->writtenStuff().isEmpty());
0222 }
0223 
0224 
0225 /** @short Test that the tagged OK for terminating IDLE gets correctly handled when changing mailboxes */
0226 void ImapModelIdleTest::testIdleMailboxChange()
0227 {
0228     // we shouldn't enter IDLE automatically
0229     model->setProperty("trojita-imap-idle-delayedEnter", QVariant(1000 * 1000 ));
0230     FakeCapabilitiesInjector injector(model);
0231     injector.injectCapability(QStringLiteral("IDLE"));
0232     existsA = 3;
0233     uidValidityA = 6;
0234     uidMapA << 1 << 7 << 9;
0235     uidNextA = 16;
0236     helperSyncAWithMessagesEmptyState();
0237     QVERIFY(SOCK->writtenStuff().isEmpty());
0238 
0239     // Force manual trigger of the IDLE
0240     model->findTaskResponsibleFor(idxA)->idleLauncher->slotEnterIdleNow();
0241     QCoreApplication::processEvents();
0242     QCoreApplication::processEvents();
0243     // we check it immediately, ie. no via the waitForIdle()
0244     QCOMPARE( SOCK->writtenStuff(), t.mk("IDLE\r\n") );
0245     SOCK->fakeReading("+ idling\r\n");
0246     // save the IDLE termination response for later
0247     QByteArray respIdleDone = t.last("OK idle terminated\r\n");
0248     QCoreApplication::processEvents();
0249     QCoreApplication::processEvents();
0250 
0251     // Now switch away
0252     // can't use the helperSyncBNoMessages() as we do have to check the IDLE explicitly here
0253     model->switchToMailbox( idxB );
0254     QCoreApplication::processEvents();
0255     QCoreApplication::processEvents();
0256     QCOMPARE(SOCK->writtenStuff(), QByteArray("DONE\r\n"));
0257     // be sure that we wait for the tagged termination of the IDLE command
0258     for (int i = 0; i < 100; ++i) {
0259         QCoreApplication::processEvents();
0260     }
0261     SOCK->fakeReading(respIdleDone);
0262     QCoreApplication::processEvents();
0263     QCoreApplication::processEvents();
0264     QCoreApplication::processEvents();
0265     QCoreApplication::processEvents();
0266     QCOMPARE(SOCK->writtenStuff(), t.mk("SELECT b\r\n"));
0267     SOCK->fakeReading(QByteArray("* 0 exists\r\n")
0268                       + t.last("ok completed\r\n"));
0269     QCoreApplication::processEvents();
0270     QCoreApplication::processEvents();
0271     QCoreApplication::processEvents();
0272     QCoreApplication::processEvents();
0273 
0274     QVERIFY(errorSpy->isEmpty());
0275 
0276     QTest::qWait(30);
0277     QVERIFY(SOCK->writtenStuff().isEmpty());
0278 }
0279 
0280 
0281 QTEST_GUILESS_MAIN( ImapModelIdleTest )