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