1 /* Copyright (C) 2006 - 2014 Jan Kundrát <jkt@flaska.net>
2 
3    This file is part of the Trojita Qt IMAP e-mail client,
4    http://trojita.flaska.net/
5 
6    This program is free software; you can redistribute it and/or
7    modify it under the terms of the GNU General Public License as
8    published by the Free Software Foundation; either version 2 of
9    the License or (at your option) version 3 or any later version
10    accepted by the membership of KDE e.V. (or its successor approved
11    by the membership of KDE e.V.), which shall act as a proxy
12    defined in Section 14 of version 3 of the license.
13 
14    This program is distributed in the hope that it will be useful,
15    but WITHOUT ANY WARRANTY; without even the implied warranty of
16    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17    GNU General Public License for more details.
18 
19    You should have received a copy of the GNU General Public License
20    along with this program.  If not, see <http://www.gnu.org/licenses/>.
21 */
22 
23 #include <QtTest>
24 #include "test_Imap_Idle.h"
25 #include "Streams/FakeSocket.h"
26 #include "Imap/Tasks/IdleLauncher.h"
27 #include "Imap/Model/ItemRoles.h"
28 #include "Imap/Tasks/KeepMailboxOpenTask.h"
29 #include "Utils/FakeCapabilitiesInjector.h"
30 
31 /** @short Wait for the IDLE to arrive, or a timeout
32 
33 The motivation here is to avoid being too fast, as reported in Redmine#275
34 */
35 #define waitForIdle() \
36 { \
37     QTest::qWait(40); \
38     QByteArray written = SOCK->writtenStuff(); \
39     int times = 0; \
40     while (written.isEmpty() && times < 4) { \
41         QTest::qWait(5); \
42         written = SOCK->writtenStuff(); \
43         ++times; \
44     } \
45     QCOMPARE(written, t.mk("IDLE\r\n")); \
46 }
47 
48 /** @short Test a NO reply to IDLE command */
testIdleNo()49 void ImapModelIdleTest::testIdleNo()
50 {
51     model->setProperty("trojita-imap-idle-delayedEnter", QVariant(30));
52     FakeCapabilitiesInjector injector(model);
53     injector.injectCapability(QStringLiteral("IDLE"));
54     existsA = 3;
55     uidValidityA = 6;
56     uidMapA << 1 << 7 << 9;
57     uidNextA = 16;
58     helperSyncAWithMessagesEmptyState();
59     QVERIFY(SOCK->writtenStuff().isEmpty());
60     waitForIdle();
61     SOCK->fakeReading(t.last("NO you can't idle now\r\n"));
62     QTest::qWait(40);
63     QVERIFY(SOCK->writtenStuff().isEmpty());
64     helperSyncBNoMessages();
65     QVERIFY(errorSpy->isEmpty());
66 }
67 
68 /** @short Test what happens when IDLE terminates by an OK, but without our "DONE" input */
testIdleImmediateReturn()69 void ImapModelIdleTest::testIdleImmediateReturn()
70 {
71     model->setProperty("trojita-imap-idle-delayedEnter", QVariant(30));
72     FakeCapabilitiesInjector injector(model);
73     injector.injectCapability(QStringLiteral("IDLE"));
74     existsA = 3;
75     uidValidityA = 6;
76     uidMapA << 1 << 7 << 9;
77     uidNextA = 16;
78     helperSyncAWithMessagesEmptyState();
79     QVERIFY(SOCK->writtenStuff().isEmpty());
80     waitForIdle();
81     SOCK->fakeReading(QByteArray("+ blah\r\n") + t.last("OK done\r\n"));
82     waitForIdle();
83 }
84 
85 /** @short Test automatic IDLE renewal */
testIdleRenewal()86 void ImapModelIdleTest::testIdleRenewal()
87 {
88     model->setProperty("trojita-imap-idle-delayedEnter", QVariant(30));
89     model->setProperty("trojita-imap-idle-renewal", QVariant(10));
90     FakeCapabilitiesInjector injector(model);
91     injector.injectCapability(QStringLiteral("IDLE"));
92     existsA = 3;
93     uidValidityA = 6;
94     uidMapA << 1 << 7 << 9;
95     uidNextA = 16;
96     helperSyncAWithMessagesEmptyState();
97     QVERIFY(SOCK->writtenStuff().isEmpty());
98     waitForIdle();
99     SOCK->fakeReading(QByteArray("+ blah\r\n"));
100     QTest::qWait(50);
101     QCOMPARE( SOCK->writtenStuff(), QByteArray("DONE\r\n") );
102     SOCK->fakeReading(t.last("OK done\r\n"));
103     waitForIdle();
104 }
105 
106 /** @short Test that IDLE gets immediately interrupted by any Task */
testIdleBreakTask()107 void ImapModelIdleTest::testIdleBreakTask()
108 {
109     model->setProperty("trojita-imap-idle-delayedEnter", QVariant(30));
110     // Intentionally leave trojita-imap-idle-renewal at its rather high default value
111     FakeCapabilitiesInjector injector(model);
112     injector.injectCapability(QStringLiteral("IDLE"));
113     existsA = 3;
114     uidValidityA = 6;
115     uidMapA << 1 << 7 << 9;
116     uidNextA = 16;
117     helperSyncAWithMessagesEmptyState();
118     QVERIFY(SOCK->writtenStuff().isEmpty());
119     waitForIdle();
120     SOCK->fakeReading(QByteArray("+ blah\r\n"));
121     QCOMPARE( msgListA.child(0,0).data(Imap::Mailbox::RoleMessageFrom).toString(), QString() );
122     QCoreApplication::processEvents();
123     QCoreApplication::processEvents();
124     QCoreApplication::processEvents();
125     QCoreApplication::processEvents();
126     QCOMPARE(SOCK->writtenStuff(), QByteArray(QByteArray("DONE\r\n") + t.mk("UID FETCH 1,7,9 (" FETCH_METADATA_ITEMS ")\r\n")));
127     SOCK->fakeReading(t.last("OK done\r\n"));
128     QTest::qWait(40);
129     QVERIFY(SOCK->writtenStuff().isEmpty());
130 }
131 
132 /** @short Test automatic IDLE renewal when server gets really slow to respond */
testIdleSlowResponses()133 void ImapModelIdleTest::testIdleSlowResponses()
134 {
135     model->setProperty("trojita-imap-idle-delayedEnter", QVariant(30));
136     model->setProperty("trojita-imap-idle-renewal", QVariant(10));
137     FakeCapabilitiesInjector injector(model);
138     injector.injectCapability(QStringLiteral("IDLE"));
139     existsA = 3;
140     uidValidityA = 6;
141     uidMapA << 1 << 7 << 9;
142     uidNextA = 16;
143     helperSyncAWithMessagesEmptyState();
144     QVERIFY(SOCK->writtenStuff().isEmpty());
145 
146     waitForIdle();
147     // Check what happens if it takes the server a lot of time to issue the initial continuation
148     QTest::qWait(70);
149     SOCK->fakeReading(QByteArray("+ blah\r\n"));
150     QCoreApplication::processEvents();
151     QCoreApplication::processEvents();
152     QCOMPARE( SOCK->writtenStuff(), QByteArray("DONE\r\n") );
153     SOCK->fakeReading(t.last("OK done\r\n"));
154 
155     waitForIdle();
156     SOCK->fakeReading(QByteArray("+ blah\r\n"));
157     // The client is fast enough...
158     QTest::qWait(40);
159     QCOMPARE( SOCK->writtenStuff(), QByteArray("DONE\r\n") );
160     // ...but the server is taking its time
161     QTest::qWait(70);
162     QVERIFY(SOCK->writtenStuff().isEmpty());
163     SOCK->fakeReading(t.last("OK done\r\n"));
164     QCoreApplication::processEvents();
165     QCoreApplication::processEvents();
166     QVERIFY(SOCK->writtenStuff().isEmpty());
167     //QCOMPARE( SOCK->writtenStuff(), t.mk("DONE\r\n") );
168 }
169 
170 /** @short Test that the automatic IDLE renewal gets disabled when IDLE finishes */
testIdleNoPerpetuateRenewal()171 void ImapModelIdleTest::testIdleNoPerpetuateRenewal()
172 {
173     // we shouldn't enter IDLE automatically
174     model->setProperty("trojita-imap-idle-delayedEnter", QVariant(1000 * 1000 ));
175     model->setProperty("trojita-imap-idle-renewal", QVariant(10));
176     FakeCapabilitiesInjector injector(model);
177     injector.injectCapability(QStringLiteral("IDLE"));
178     existsA = 3;
179     uidValidityA = 6;
180     uidMapA << 1 << 7 << 9;
181     uidNextA = 16;
182     helperSyncAWithMessagesEmptyState();
183     QVERIFY(SOCK->writtenStuff().isEmpty());
184 
185     // Force manual trigger of the IDLE
186     model->findTaskResponsibleFor(idxA)->idleLauncher->slotEnterIdleNow();
187     QCoreApplication::processEvents();
188     QCoreApplication::processEvents();
189     // we check it immediately, ie. no via the waitForIdle()
190     QCOMPARE( SOCK->writtenStuff(), t.mk("IDLE\r\n") );
191     SOCK->fakeReading(t.last("NO you can't idle now\r\n"));
192     // ...make sure it won't try to "break long IDLE"
193     QTest::qWait(30);
194     // switch away
195     helperSyncBNoMessages();
196     QVERIFY(errorSpy->isEmpty());
197 
198     // Now go back to mailbox A
199     model->switchToMailbox(idxA);
200     helperSyncAWithMessagesNoArrivals();
201 
202     // Force manual trigger of the IDLE
203     model->findTaskResponsibleFor(idxA)->idleLauncher->slotEnterIdleNow();
204     QCoreApplication::processEvents();
205     QCoreApplication::processEvents();
206     QCOMPARE( SOCK->writtenStuff(), t.mk("IDLE\r\n") );
207     SOCK->fakeReading(QByteArray("+ blah\r\n"));
208     QCoreApplication::processEvents();
209     QCoreApplication::processEvents();
210 
211     // so we're in regular IDLE and want to break it
212     QCOMPARE( msgListA.child(0,0).data(Imap::Mailbox::RoleMessageFrom).toString(), QString() );
213     QCoreApplication::processEvents();
214     QCoreApplication::processEvents();
215     QCoreApplication::processEvents();
216     QCoreApplication::processEvents();
217     QCOMPARE(SOCK->writtenStuff(), QByteArray(QByteArray("DONE\r\n") + t.mk("UID FETCH 1,7,9 (" FETCH_METADATA_ITEMS ")\r\n")));
218     SOCK->fakeReading(t.last("OK done\r\n"));
219     // Make sure we won't try to "renew" it automatically...
220     QTest::qWait(30);
221     QVERIFY(SOCK->writtenStuff().isEmpty());
222 }
223 
224 
225 /** @short Test that the tagged OK for terminating IDLE gets correctly handled when changing mailboxes */
testIdleMailboxChange()226 void ImapModelIdleTest::testIdleMailboxChange()
227 {
228     // we shouldn't enter IDLE automatically
229     model->setProperty("trojita-imap-idle-delayedEnter", QVariant(1000 * 1000 ));
230     FakeCapabilitiesInjector injector(model);
231     injector.injectCapability(QStringLiteral("IDLE"));
232     existsA = 3;
233     uidValidityA = 6;
234     uidMapA << 1 << 7 << 9;
235     uidNextA = 16;
236     helperSyncAWithMessagesEmptyState();
237     QVERIFY(SOCK->writtenStuff().isEmpty());
238 
239     // Force manual trigger of the IDLE
240     model->findTaskResponsibleFor(idxA)->idleLauncher->slotEnterIdleNow();
241     QCoreApplication::processEvents();
242     QCoreApplication::processEvents();
243     // we check it immediately, ie. no via the waitForIdle()
244     QCOMPARE( SOCK->writtenStuff(), t.mk("IDLE\r\n") );
245     SOCK->fakeReading("+ idling\r\n");
246     // save the IDLE termination response for later
247     QByteArray respIdleDone = t.last("OK idle terminated\r\n");
248     QCoreApplication::processEvents();
249     QCoreApplication::processEvents();
250 
251     // Now switch away
252     // can't use the helperSyncBNoMessages() as we do have to check the IDLE explicitly here
253     model->switchToMailbox( idxB );
254     QCoreApplication::processEvents();
255     QCoreApplication::processEvents();
256     QCOMPARE(SOCK->writtenStuff(), QByteArray("DONE\r\n"));
257     // be sure that we wait for the tagged termination of the IDLE command
258     for (int i = 0; i < 100; ++i) {
259         QCoreApplication::processEvents();
260     }
261     SOCK->fakeReading(respIdleDone);
262     QCoreApplication::processEvents();
263     QCoreApplication::processEvents();
264     QCoreApplication::processEvents();
265     QCoreApplication::processEvents();
266     QCOMPARE(SOCK->writtenStuff(), t.mk("SELECT b\r\n"));
267     SOCK->fakeReading(QByteArray("* 0 exists\r\n")
268                       + t.last("ok completed\r\n"));
269     QCoreApplication::processEvents();
270     QCoreApplication::processEvents();
271     QCoreApplication::processEvents();
272     QCoreApplication::processEvents();
273 
274     QVERIFY(errorSpy->isEmpty());
275 
276     QTest::qWait(30);
277     QVERIFY(SOCK->writtenStuff().isEmpty());
278 }
279 
280 
281 QTEST_GUILESS_MAIN( ImapModelIdleTest )
282