1 /* Copyright (C) 2006 - 2014 Jan Kundrát <jkt@flaska.net>
3 This file is part of the Trojita Qt IMAP e-mail client,
4 http://trojita.flaska.net/
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.
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.
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/>.
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"
31 /** @short Wait for the IDLE to arrive, or a timeout
33 The motivation here is to avoid being too fast, as reported in Redmine#275
35 #define waitForIdle() \
38 QByteArray written = SOCK->writtenStuff(); \
40 while (written.isEmpty() && times < 4) { \
42 written = SOCK->writtenStuff(); \
45 QCOMPARE(written, t.mk("IDLE\r\n")); \
48 /** @short Test a NO reply to IDLE command */
49 void ImapModelIdleTest::testIdleNo()
51 model
->setProperty("trojita-imap-idle-delayedEnter", QVariant(30));
52 FakeCapabilitiesInjector
injector(model
);
53 injector
.injectCapability(QStringLiteral("IDLE"));
56 uidMapA
<< 1 << 7 << 9;
58 helperSyncAWithMessagesEmptyState();
59 QVERIFY(SOCK
->writtenStuff().isEmpty());
61 SOCK
->fakeReading(t
.last("NO you can't idle now\r\n"));
63 QVERIFY(SOCK
->writtenStuff().isEmpty());
64 helperSyncBNoMessages();
65 QVERIFY(errorSpy
->isEmpty());
68 /** @short Test what happens when IDLE terminates by an OK, but without our "DONE" input */
69 void ImapModelIdleTest::testIdleImmediateReturn()
71 model
->setProperty("trojita-imap-idle-delayedEnter", QVariant(30));
72 FakeCapabilitiesInjector
injector(model
);
73 injector
.injectCapability(QStringLiteral("IDLE"));
76 uidMapA
<< 1 << 7 << 9;
78 helperSyncAWithMessagesEmptyState();
79 QVERIFY(SOCK
->writtenStuff().isEmpty());
81 SOCK
->fakeReading(QByteArray("+ blah\r\n") + t
.last("OK done\r\n"));
85 /** @short Test automatic IDLE renewal */
86 void ImapModelIdleTest::testIdleRenewal()
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"));
94 uidMapA
<< 1 << 7 << 9;
96 helperSyncAWithMessagesEmptyState();
97 QVERIFY(SOCK
->writtenStuff().isEmpty());
99 SOCK
->fakeReading(QByteArray("+ blah\r\n"));
101 QCOMPARE( SOCK
->writtenStuff(), QByteArray("DONE\r\n") );
102 SOCK
->fakeReading(t
.last("OK done\r\n"));
106 /** @short Test that IDLE gets immediately interrupted by any Task */
107 void ImapModelIdleTest::testIdleBreakTask()
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"));
115 uidMapA
<< 1 << 7 << 9;
117 helperSyncAWithMessagesEmptyState();
118 QVERIFY(SOCK
->writtenStuff().isEmpty());
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"));
129 QVERIFY(SOCK
->writtenStuff().isEmpty());
132 /** @short Test automatic IDLE renewal when server gets really slow to respond */
133 void ImapModelIdleTest::testIdleSlowResponses()
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"));
141 uidMapA
<< 1 << 7 << 9;
143 helperSyncAWithMessagesEmptyState();
144 QVERIFY(SOCK
->writtenStuff().isEmpty());
147 // Check what happens if it takes the server a lot of time to issue the initial continuation
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"));
156 SOCK
->fakeReading(QByteArray("+ blah\r\n"));
157 // The client is fast enough...
159 QCOMPARE( SOCK
->writtenStuff(), QByteArray("DONE\r\n") );
160 // ...but the server is taking its time
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") );
170 /** @short Test that the automatic IDLE renewal gets disabled when IDLE finishes */
171 void ImapModelIdleTest::testIdleNoPerpetuateRenewal()
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"));
180 uidMapA
<< 1 << 7 << 9;
182 helperSyncAWithMessagesEmptyState();
183 QVERIFY(SOCK
->writtenStuff().isEmpty());
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"
195 helperSyncBNoMessages();
196 QVERIFY(errorSpy
->isEmpty());
198 // Now go back to mailbox A
199 model
->switchToMailbox(idxA
);
200 helperSyncAWithMessagesNoArrivals();
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();
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...
221 QVERIFY(SOCK
->writtenStuff().isEmpty());
225 /** @short Test that the tagged OK for terminating IDLE gets correctly handled when changing mailboxes */
226 void ImapModelIdleTest::testIdleMailboxChange()
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"));
234 uidMapA
<< 1 << 7 << 9;
236 helperSyncAWithMessagesEmptyState();
237 QVERIFY(SOCK
->writtenStuff().isEmpty());
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();
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();
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();
274 QVERIFY(errorSpy
->isEmpty());
277 QVERIFY(SOCK
->writtenStuff().isEmpty());
281 QTEST_GUILESS_MAIN( ImapModelIdleTest
)