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 <QAuthenticator>
25 #include "test_Imap_Tasks_OpenConnection.h"
26 #include "Common/MetaTypes.h"
27 #include "Streams/FakeSocket.h"
28 #include "Streams/TrojitaZlibStatus.h"
29 #include "Imap/Model/ItemRoles.h"
30 #include "Imap/Model/MemoryCache.h"
31 #include "Imap/Model/MailboxModel.h"
32 #include "Imap/Tasks/OpenConnectionTask.h"
33
initTestCase()34 void ImapModelOpenConnectionTest::initTestCase()
35 {
36 LibMailboxSync::initTestCase();
37 qRegisterMetaType<Imap::Mailbox::ImapTask*>();
38 completedSpy = 0;
39 m_enableAutoLogin = true;
40 }
41
init()42 void ImapModelOpenConnectionTest::init()
43 {
44 reinit(TlsRequired::No);
45 }
46
reinit(const TlsRequired tlsRequired)47 void ImapModelOpenConnectionTest::reinit(const TlsRequired tlsRequired)
48 {
49 m_fakeListCommand = false;
50 m_fakeOpenTask = false;
51 m_startTlsRequired = tlsRequired == TlsRequired::Yes;
52 m_initialConnectionState = Imap::CONN_STATE_CONNECTED_PRETLS_PRECAPS;
53 LibMailboxSync::init();
54 model->setProperty("trojita-imap-id-no-versions", QVariant(true));
55 connect(model, &Imap::Mailbox::Model::authRequested, this, &ImapModelOpenConnectionTest::provideAuthDetails, Qt::QueuedConnection);
56 connect(model, &Imap::Mailbox::Model::needsSslDecision, this, &ImapModelOpenConnectionTest::acceptSsl, Qt::QueuedConnection);
57 LibMailboxSync::setModelNetworkPolicy(model, Imap::Mailbox::NETWORK_ONLINE);
58 QCoreApplication::processEvents();
59 task = new Imap::Mailbox::OpenConnectionTask(model);
60 completedSpy = new QSignalSpy(task, SIGNAL(completed(Imap::Mailbox::ImapTask*)));
61 failedSpy = new QSignalSpy(task, SIGNAL(failed(QString)));
62 authSpy = new QSignalSpy(model, SIGNAL(authRequested()));
63 connErrorSpy = new QSignalSpy(model, SIGNAL(imapError(QString)));
64 startTlsUpgradeSpy = new QSignalSpy(model, SIGNAL(requireStartTlsInFuture()));
65 authErrorSpy = new QSignalSpy(model, SIGNAL(imapAuthErrorChanged(const QString&)));
66 t.reset();
67 }
68
acceptSsl(const QList<QSslCertificate> & certificateChain,const QList<QSslError> & sslErrors)69 void ImapModelOpenConnectionTest::acceptSsl(const QList<QSslCertificate> &certificateChain, const QList<QSslError> &sslErrors)
70 {
71 model->setSslPolicy(certificateChain, sslErrors, true);
72 }
73
74 /** @short Test for explicitly obtaining capability when greeted by PREAUTH */
testPreauth()75 void ImapModelOpenConnectionTest::testPreauth()
76 {
77 cEmpty();
78 cServer("* PREAUTH foo\r\n");
79 QVERIFY(completedSpy->isEmpty());
80 cClient(t.mk("CAPABILITY\r\n"));
81 QVERIFY(completedSpy->isEmpty());
82 cServer("* CAPABILITY IMAP4rev1\r\n"
83 + t.last("OK capability completed\r\n"));
84 cEmpty();
85 QCOMPARE(completedSpy->size(), 1);
86 QVERIFY(failedSpy->isEmpty());
87 QVERIFY(authSpy->isEmpty());
88 QVERIFY(startTlsUpgradeSpy->isEmpty());
89 }
90
91 /** @short Test that we can obtain capability when embedded in PREAUTH */
testPreauthWithCapability()92 void ImapModelOpenConnectionTest::testPreauthWithCapability()
93 {
94 cEmpty();
95 cServer("* PREAUTH [CAPABILITY IMAP4rev1] foo\r\n");
96 cEmpty();
97 QCOMPARE(completedSpy->size(), 1);
98 QVERIFY(failedSpy->isEmpty());
99 QVERIFY(authSpy->isEmpty());
100 QVERIFY(startTlsUpgradeSpy->isEmpty());
101 }
102
103 /** @short What happens when the server responds with PREAUTH and we want STARTTLS? */
testPreauthWithStartTlsWanted()104 void ImapModelOpenConnectionTest::testPreauthWithStartTlsWanted()
105 {
106 reinit(TlsRequired::Yes);
107
108 cEmpty();
109 cServer("* PREAUTH hi there\r\n");
110 QCOMPARE(failedSpy->size(), 1);
111 QVERIFY(completedSpy->isEmpty());
112 QVERIFY(authSpy->isEmpty());
113 QVERIFY(startTlsUpgradeSpy->isEmpty());
114 cClient(t.mk("LOGOUT\r\n"));
115 cEmpty();
116 }
117
118 /** @short Test for obtaining capability and logging in without any STARTTLS */
testOk()119 void ImapModelOpenConnectionTest::testOk()
120 {
121 cEmpty();
122 cServer("* OK foo\r\n");
123 QVERIFY(completedSpy->isEmpty());
124 cClient(t.mk("CAPABILITY\r\n"));
125 QVERIFY(completedSpy->isEmpty());
126 cServer("* CAPABILITY IMAP4rev1\r\n"
127 + t.last("OK capability completed\r\n"));
128 QCOMPARE(authSpy->size(), 1);
129 cClient(t.mk("LOGIN luzr sikrit\r\n"));
130 cServer(t.last("OK [CAPABILITY IMAP4rev1] logged in\r\n"));
131 cEmpty();
132 QCOMPARE(completedSpy->size(), 1);
133 QVERIFY(failedSpy->isEmpty());
134 QCOMPARE(authSpy->size(), 1);
135 QVERIFY(startTlsUpgradeSpy->isEmpty());
136 QCOMPARE(authErrorSpy->size(), 0);
137 QCOMPARE(model->imapAuthError(), QString());
138 }
139
140 /** @short Test with capability inside the OK greetings, no STARTTLS */
testOkWithCapability()141 void ImapModelOpenConnectionTest::testOkWithCapability()
142 {
143 cEmpty();
144 cServer("* OK [CAPABILITY IMAP4rev1] foo\r\n");
145 QVERIFY(completedSpy->isEmpty());
146 QCOMPARE(authSpy->size(), 1);
147 cClient(t.mk("LOGIN luzr sikrit\r\n"));
148 cServer(t.last("OK [CAPABILITY IMAP4rev1] logged in\r\n"));
149 cEmpty();
150 QCOMPARE(completedSpy->size(), 1);
151 QVERIFY(failedSpy->isEmpty());
152 QCOMPARE(authSpy->size(), 1);
153 QVERIFY(startTlsUpgradeSpy->isEmpty());
154 QCOMPARE(authErrorSpy->size(), 0);
155 QCOMPARE(model->imapAuthError(), QString());
156 }
157
158 /** @short See what happens when the capability response doesn't contain IMAP4rev1 capability */
testOkMissingImap4rev1()159 void ImapModelOpenConnectionTest::testOkMissingImap4rev1()
160 {
161 {
162 ExpectSingleErrorHere blocker(this);
163 cServer("* OK [CAPABILITY pwn] foo\r\n");
164 }
165 cClient(t.mk("LOGOUT\r\n"));
166 cEmpty();
167 QVERIFY(authSpy->isEmpty());
168 QVERIFY(startTlsUpgradeSpy->isEmpty());
169 }
170
171 /** @short Test to honor embedded LOGINDISABLED */
testOkLogindisabled()172 void ImapModelOpenConnectionTest::testOkLogindisabled()
173 {
174 cEmpty();
175 cServer("* OK [CAPABILITY IMAP4rev1 starttls LoginDisabled] foo\r\n");
176 QVERIFY(completedSpy->isEmpty());
177 QVERIFY(authSpy->isEmpty());
178 cClient(t.mk("STARTTLS\r\n"));
179 cServer(t.last("OK will establish secure layer immediately\r\n"));
180 cClient("[*** STARTTLS ***]"
181 + t.mk("CAPABILITY\r\n"));
182 QVERIFY(authSpy->isEmpty());
183 QVERIFY(completedSpy->isEmpty());
184 QVERIFY(authSpy->isEmpty());
185 cServer("* CAPABILITY IMAP4rev1\r\n"
186 + t.last("OK capability completed\r\n"));
187 cClient(t.mk("LOGIN luzr sikrit\r\n"));
188 QCOMPARE(authSpy->size(), 1);
189 cServer(t.last("OK [CAPABILITY IMAP4rev1] logged in\r\n"));
190 cEmpty();
191 QCOMPARE(completedSpy->size(), 1);
192 QVERIFY(failedSpy->isEmpty());
193 QCOMPARE(authSpy->size(), 1);
194 QCOMPARE(startTlsUpgradeSpy->size(), 1);
195 QCOMPARE(authErrorSpy->size(), 0);
196 QCOMPARE(model->imapAuthError(), QString());
197 }
198
199 /** @short Test how LOGINDISABLED without a corresponding STARTTLS in the capabilities end up */
testOkLogindisabledWithoutStarttls()200 void ImapModelOpenConnectionTest::testOkLogindisabledWithoutStarttls()
201 {
202 cEmpty();
203 cServer("* OK [CAPABILITY IMAP4rev1 LoginDisabled] foo\r\n");
204 // The capabilities do not contain STARTTLS but LOGINDISABLED is in there
205 cClient(t.mk("LOGOUT\r\n"))
206 QVERIFY(completedSpy->isEmpty());
207 QVERIFY(authSpy->isEmpty());
208 QCOMPARE(failedSpy->size(), 1);
209 QVERIFY(startTlsUpgradeSpy->isEmpty());
210 }
211
212 /** @short Test for an explicit CAPABILITY retrieval and automatic STARTTLS when LOGINDISABLED */
testOkLogindisabledLater()213 void ImapModelOpenConnectionTest::testOkLogindisabledLater()
214 {
215 cEmpty();
216 cServer("* OK foo\r\n");
217 QVERIFY(completedSpy->isEmpty());
218 cClient(t.mk("CAPABILITY\r\n"));
219 QVERIFY(completedSpy->isEmpty() );
220 cServer("* CAPABILITY IMAP4rev1 starttls LoGINDISABLED\r\n"
221 + t.last("OK capability completed\r\n"));
222 QVERIFY(authSpy->isEmpty());
223 cClient(t.mk("STARTTLS\r\n"));
224 cServer(t.last("OK will establish secure layer immediately\r\n"));
225 QVERIFY(authSpy->isEmpty());
226 cClient(QByteArray("[*** STARTTLS ***]")
227 + t.mk("CAPABILITY\r\n"));
228 QVERIFY(completedSpy->isEmpty());
229 QVERIFY(authSpy->isEmpty());
230 cServer("* CAPABILITY IMAP4rev1\r\n" + t.last("OK capability completed\r\n"));
231 cClient(t.mk("LOGIN luzr sikrit\r\n"));
232 QCOMPARE(authSpy->size(), 1);
233 cServer(t.last("OK [CAPABILITY IMAP4rev1] logged in\r\n"));
234 cEmpty();
235 QCOMPARE(completedSpy->size(), 1);
236 QVERIFY(failedSpy->isEmpty());
237 QCOMPARE(authSpy->size(), 1);
238 QCOMPARE(startTlsUpgradeSpy->size(), 1);
239 QCOMPARE(authErrorSpy->size(), 0);
240 QCOMPARE(model->imapAuthError(), QString());
241 }
242
243 /** @short Test conf-requested STARTTLS when not faced with embedded capabilities in OK greetings */
testOkStartTls()244 void ImapModelOpenConnectionTest::testOkStartTls()
245 {
246 reinit(TlsRequired::Yes);
247
248 cEmpty();
249 cServer("* OK foo\r\n");
250 QVERIFY(completedSpy->isEmpty());
251 cClient(t.mk("CAPABILITY\r\n"));
252 cServer("* CAPABILITY imap4rev1 starttls\r\n"
253 + t.last("ok cap\r\n"));
254 QVERIFY(authSpy->isEmpty());
255 cClient(t.mk("STARTTLS\r\n"));
256 cServer(t.last("OK will establish secure layer immediately\r\n"));
257 QVERIFY(authSpy->isEmpty());
258 cClient("[*** STARTTLS ***]"
259 + t.mk("CAPABILITY\r\n"));
260 QVERIFY(completedSpy->isEmpty());
261 QVERIFY(authSpy->isEmpty());
262 cServer("* CAPABILITY IMAP4rev1\r\n"
263 + t.last("OK capability completed\r\n"));
264 cClient(t.mk("LOGIN luzr sikrit\r\n"));
265 QCOMPARE(authSpy->size(), 1);
266 cServer(t.last("OK [CAPABILITY IMAP4rev1] logged in\r\n"));
267 cEmpty();
268 QCOMPARE(completedSpy->size(), 1);
269 QVERIFY(failedSpy->isEmpty());
270 QCOMPARE(authSpy->size(), 1);
271 QVERIFY(startTlsUpgradeSpy->isEmpty());
272 QCOMPARE(authErrorSpy->size(), 0);
273 QCOMPARE(model->imapAuthError(), QString());
274 }
275
276 /** @short Test that an untagged CAPABILITY after LOGIN prevents an extra CAPABILITY command */
testCapabilityAfterLogin()277 void ImapModelOpenConnectionTest::testCapabilityAfterLogin()
278 {
279 reinit(TlsRequired::Yes);
280
281 cEmpty();
282 cServer("* OK foo\r\n");
283 QVERIFY(completedSpy->isEmpty());
284 cClient(t.mk("CAPABILITY\r\n"));
285 cServer("* CAPABILITY imap4rev1 starttls\r\n"
286 + t.last("ok cap\r\n"));
287 QVERIFY(authSpy->isEmpty());
288 cClient(t.mk("STARTTLS\r\n"));
289 cServer(t.last("OK will establish secure layer immediately\r\n"));
290 QVERIFY(authSpy->isEmpty());
291 cClient("[*** STARTTLS ***]"
292 + t.mk("CAPABILITY\r\n"));
293 QVERIFY(completedSpy->isEmpty());
294 QVERIFY(authSpy->isEmpty());
295 cServer("* CAPABILITY IMAP4rev1\r\n"
296 + t.last("OK capability completed\r\n"));
297 cClient(t.mk("LOGIN luzr sikrit\r\n"));
298 QCOMPARE(authSpy->size(), 1);
299 cServer("* CAPABILITY imap4rev1\r\n" + t.last("OK logged in\r\n"));
300 cEmpty();
301 QCOMPARE(completedSpy->size(), 1);
302 QVERIFY(failedSpy->isEmpty());
303 QCOMPARE(authSpy->size(), 1);
304 QVERIFY(startTlsUpgradeSpy->isEmpty());
305 QCOMPARE(authErrorSpy->size(), 0);
306 QCOMPARE(model->imapAuthError(), QString());
307 }
308
309 /** @short Test conf-requested STARTTLS when the server doesn't support STARTTLS at all */
testOkStartTlsForbidden()310 void ImapModelOpenConnectionTest::testOkStartTlsForbidden()
311 {
312 reinit(TlsRequired::Yes);
313
314 cEmpty();
315 cServer("* OK foo\r\n");
316 QVERIFY(completedSpy->isEmpty());
317 cClient(t.mk("CAPABILITY\r\n"));
318 cServer("* CAPABILITY imap4rev1\r\n"
319 + t.last("ok cap\r\n"));
320 cClient(t.mk("LOGOUT\r\n"));
321 QVERIFY(authSpy->isEmpty());
322 QCOMPARE(failedSpy->size(), 1);
323 QVERIFY(authSpy->isEmpty());
324 QVERIFY(startTlsUpgradeSpy->isEmpty());
325 }
326
327 /** @short Test to re-request formerly embedded capabilities when launching STARTTLS */
testOkStartTlsDiscardCaps()328 void ImapModelOpenConnectionTest::testOkStartTlsDiscardCaps()
329 {
330 reinit(TlsRequired::Yes);
331
332 cEmpty();
333 cServer("* OK [Capability imap4rev1 starttls] foo\r\n");
334 QVERIFY(completedSpy->isEmpty());
335 QVERIFY(authSpy->isEmpty());
336 cClient(t.mk("STARTTLS\r\n"));
337 cServer(t.last("OK will establish secure layer immediately\r\n"));
338 QVERIFY(authSpy->isEmpty());
339 cClient("[*** STARTTLS ***]" + t.mk("CAPABILITY\r\n"));
340 QVERIFY(completedSpy->isEmpty());
341 QVERIFY(authSpy->isEmpty());
342 cServer("* CAPABILITY IMAP4rev1\r\n" + t.last("OK capability completed\r\n"));
343 cClient(t.mk("LOGIN luzr sikrit\r\n"));
344 QCOMPARE(authSpy->size(), 1);
345 cServer(t.last("OK [CAPABILITY IMAP4rev1] logged in\r\n"));
346 cEmpty();
347 QCOMPARE(completedSpy->size(), 1);
348 QVERIFY(failedSpy->isEmpty());
349 QCOMPARE(authSpy->size(), 1);
350 QVERIFY(startTlsUpgradeSpy->isEmpty());
351 QCOMPARE(authErrorSpy->size(), 0);
352 QCOMPARE(model->imapAuthError(), QString());
353 }
354
355 /** @short Test how COMPRESS=DEFLATE gets activated and its interaction with further tasks */
testCompressDeflateOk()356 void ImapModelOpenConnectionTest::testCompressDeflateOk()
357 {
358 cEmpty();
359 cServer("* OK [capability imap4rev1] hi there\r\n");
360 QVERIFY(completedSpy->isEmpty());
361 cClient(t.mk("LOGIN luzr sikrit\r\n"));
362 QCOMPARE(authSpy->size(), 1);
363 cServer(t.last("OK [CAPABILITY IMAP4rev1 compress=deflate id] logged in\r\n"));
364 #if TROJITA_COMPRESS_DEFLATE
365 cClient(t.mk("COMPRESS DEFLATE\r\n"));
366 cServer(t.last("OK compressing\r\n"));
367 cClient("[*** DEFLATE ***]" + t.mk("ID (\"name\" \"Trojita\")\r\n"));
368 cServer("* ID nil\r\n" + t.last("OK you courious peer\r\n"));
369 #else
370 cClient(t.mk("ID (\"name\" \"Trojita\")\r\n"));
371 cServer("* ID nil\r\n" + t.last("OK you courious peer\r\n"));
372 #endif
373 cEmpty();
374 QCOMPARE(completedSpy->size(), 1);
375 QVERIFY(failedSpy->isEmpty());
376 QCOMPARE(authSpy->size(), 1);
377 QVERIFY(startTlsUpgradeSpy->isEmpty());
378 QCOMPARE(authErrorSpy->size(), 0);
379 QCOMPARE(model->imapAuthError(), QString());
380 }
381
382 /** @short Test that denied COMPRESS=DEFLATE doesn't result in compression being active */
testCompressDeflateNo()383 void ImapModelOpenConnectionTest::testCompressDeflateNo()
384 {
385 cEmpty();
386 cServer("* OK [capability imap4rev1] hi there\r\n");
387 QVERIFY(completedSpy->isEmpty());
388 cClient(t.mk("LOGIN luzr sikrit\r\n"));
389 QCOMPARE(authSpy->size(), 1);
390 cServer(t.last("OK [CAPABILITY IMAP4rev1 compress=deflate id] logged in\r\n"));
391 #if TROJITA_COMPRESS_DEFLATE
392 cClient(t.mk("COMPRESS DEFLATE\r\n"));
393 cServer(t.last("NO I just don't want to\r\n"));
394 cClient(t.mk("ID (\"name\" \"Trojita\")\r\n"));
395 cServer("* ID nil\r\n"
396 + t.last("OK you courious peer\r\n"));
397 #else
398 cClient(t.mk("ID (\"name\" \"Trojita\")\r\n"));
399 cServer("* ID nil\r\n"
400 + t.last("OK you courious peer\r\n"));
401 #endif
402 cEmpty();
403 QCOMPARE(completedSpy->size(), 1);
404 QVERIFY(failedSpy->isEmpty());
405 QCOMPARE(authSpy->size(), 1);
406 QVERIFY(startTlsUpgradeSpy->isEmpty());
407 QCOMPARE(authErrorSpy->size(), 0);
408 QCOMPARE(model->imapAuthError(), QString());
409 }
410
411 /** @short Make sure that as long as the OpenConnectionTask has not finished its job, nothing else will get queued */
testOpenConnectionShallBlock()412 void ImapModelOpenConnectionTest::testOpenConnectionShallBlock()
413 {
414 model->rowCount(QModelIndex());
415 cEmpty();
416 cServer("* OK [capability imap4rev1] hi there\r\n");
417 QVERIFY(completedSpy->isEmpty());
418 cClient(t.mk("LOGIN luzr sikrit\r\n"));
419 QCOMPARE(authSpy->size(), 1);
420 cServer(t.last("OK [CAPABILITY IMAP4rev1 compress=deflate id] logged in\r\n"));
421 #if TROJITA_COMPRESS_DEFLATE
422 cClient(t.mk("COMPRESS DEFLATE\r\n"));
423 cServer(t.last("NO I just don't want to\r\n"));
424 QCoreApplication::processEvents();
425 QCoreApplication::processEvents();
426 QCoreApplication::processEvents();
427 #endif
428 auto c1 = t.mk("ID (\"name\" \"Trojita\")\r\n");
429 auto r1 = t.last("OK you courious peer\r\n");
430 auto c2 = t.mk("LIST \"\" \"%\"\r\n");
431 auto r2 = t.last("OK listed, nothing like that in there\r\n");
432 cClient(c1 + c2);
433 cServer("* ID nil\r\n" + r2 + r1);
434 cEmpty();
435 QCOMPARE(completedSpy->size(), 1);
436 QVERIFY(failedSpy->isEmpty());
437 QCOMPARE(authSpy->size(), 1);
438 QVERIFY(startTlsUpgradeSpy->isEmpty());
439 QCOMPARE(authErrorSpy->size(), 0);
440 QCOMPARE(model->imapAuthError(), QString());
441 }
442
443 /** @short Test that no tasks can skip over a task which is blocking for login */
testLoginDelaysOtherTasks()444 void ImapModelOpenConnectionTest::testLoginDelaysOtherTasks()
445 {
446 using namespace Imap::Mailbox;
447 MemoryCache *cache = dynamic_cast<MemoryCache *>(model->cache());
448 Q_ASSERT(cache);
449 cache->setChildMailboxes(QString(),
450 QList<MailboxMetadata>() << MailboxMetadata(QLatin1String("a"), QString(), QStringList())
451 << MailboxMetadata(QLatin1String("b"), QString(), QStringList())
452 );
453 m_enableAutoLogin = false;
454 // The cache is not queried synchronously
455 QCOMPARE(model->rowCount(QModelIndex()), 1);
456 QCoreApplication::processEvents();
457 // The cache should have been consulted by now
458 QCOMPARE(model->rowCount(QModelIndex()), 3);
459 cEmpty();
460 cServer("* OK [capability imap4rev1] hi there\r\n");
461 QVERIFY(completedSpy->isEmpty());
462 // The login must not be sent (as per m_enableAutoLogin being false)
463 cEmpty();
464 QModelIndex mailboxA = model->index(1, 0, QModelIndex());
465 QVERIFY(mailboxA.isValid());
466 QCOMPARE(mailboxA.data(RoleMailboxName).toString(), QString::fromUtf8("a"));
467 QModelIndex mailboxB = model->index(2, 0, QModelIndex());
468 QVERIFY(mailboxB.isValid());
469 QCOMPARE(mailboxB.data(RoleMailboxName).toString(), QString::fromUtf8("b"));
470 QModelIndex msgListA = mailboxA.child(0, 0);
471 Q_ASSERT(msgListA.isValid());
472 QModelIndex msgListB = mailboxB.child(0, 0);
473 Q_ASSERT(msgListB.isValid());
474
475 // Request syncing the mailboxes
476 QCOMPARE(model->rowCount(msgListA), 0);
477 QCOMPARE(model->rowCount(msgListB), 0);
478 cEmpty();
479
480 // Unblock the login
481 m_enableAutoLogin = true;
482 provideAuthDetails();
483 cClient(t.mk("LOGIN luzr sikrit\r\n"));
484 cServer(t.last("OK [CAPABILITY IMAP4rev1] logged in\r\n"));
485 // FIXME: selecting the "b" shall definitely wait until "a" is completed
486 auto c1 = t.mk("LIST \"\" \"%\"\r\n");
487 auto r1 = t.last("OK listed, nothing like that in there\r\n");
488 auto c2 = t.mk("SELECT a\r\n");
489 auto c3 = t.mk("SELECT b\r\n");
490 cClient(c1 + c2 + c3);
491 cServer(r1);
492 cEmpty();
493 QCOMPARE(completedSpy->size(), 1);
494 QVERIFY(failedSpy->isEmpty());
495 QCOMPARE(authSpy->size(), 1);
496 QVERIFY(startTlsUpgradeSpy->isEmpty());
497 QCOMPARE(authErrorSpy->size(), 0);
498 QCOMPARE(model->imapAuthError(), QString());
499 }
500
501 /** @short Test that we respect an initial BYE and don't proceed with login */
testInitialBye()502 void ImapModelOpenConnectionTest::testInitialBye()
503 {
504 model->rowCount(QModelIndex());
505 cEmpty();
506 cServer("* BYE Sorry, we're offline\r\n");
507 cClient(t.mk("LOGOUT\r\n"));
508 cEmpty();
509 QCOMPARE(failedSpy->size(), 1);
510 QVERIFY(completedSpy->isEmpty());
511 QVERIFY(connErrorSpy->isEmpty());
512 QVERIFY(startTlsUpgradeSpy->isEmpty());
513 }
514
515 /** @short Test how we react on some crazy garbage instead of a proper IMAP4 greeting */
testInitialGarbage()516 void ImapModelOpenConnectionTest::testInitialGarbage()
517 {
518 QFETCH(QByteArray, greetings);
519
520 model->rowCount(QModelIndex());
521 cEmpty();
522 {
523 ExpectSingleErrorHere blocker(this);
524 cServer(greetings);
525 }
526 //qDebug() << QList<QVariantList>(*connErrorSpy);
527 QCOMPARE(connErrorSpy->size(), 1);
528 QCOMPARE(failedSpy->size(), 1);
529 QVERIFY(completedSpy->isEmpty());
530 QVERIFY(startTlsUpgradeSpy->isEmpty());
531 }
532
testInitialGarbage_data()533 void ImapModelOpenConnectionTest::testInitialGarbage_data()
534 {
535 QTest::addColumn<QByteArray>("greetings");
536
537 QTest::newRow("utter-garbage")
538 << QByteArray("blesmrt trojita\r\n");
539
540 QTest::newRow("pop3")
541 << QByteArray("+OK InterMail POP3 server ready.\r\n");
542 }
543
testAuthFailure()544 void ImapModelOpenConnectionTest::testAuthFailure()
545 {
546 cEmpty();
547 cServer("* OK [capability imap4rev1] Serivce Ready\r\n");
548 QVERIFY(completedSpy->isEmpty());
549 QCOMPARE(authSpy->size(), 1);
550 cClient(t.mk("LOGIN luzr sikrit\r\n"));
551 QCOMPARE(authSpy->size(), 1);
552 cServer(t.last("NO [AUTHENTICATIONFAILED] foobar\r\n"));
553 QCOMPARE(completedSpy->size(), 0);
554 QCOMPARE(authSpy->size(), 2);
555 QCOMPARE(authErrorSpy->size(), 1);
556 QVERIFY(model->imapAuthError().contains("foobar"));
557 }
558
testAuthFailureNoRespCode()559 void ImapModelOpenConnectionTest::testAuthFailureNoRespCode()
560 {
561 cEmpty();
562 cServer("* OK [capability imap4rev1] Service Ready\r\n");
563 QVERIFY(completedSpy->isEmpty());
564 QCOMPARE(authSpy->size(), 1);
565 cClient(t.mk("LOGIN luzr sikrit\r\n"));
566 QCOMPARE(authSpy->size(), 1);
567 cServer(t.last("NO Derp\r\n"));
568 QCOMPARE(completedSpy->size(), 0);
569 QCOMPARE(authSpy->size(), 2);
570 QCOMPARE(authErrorSpy->size(), 1);
571 QVERIFY(model->imapAuthError().contains("Derp"));
572 }
573
574 /** @short Ensure that network reconnects do not lead to a huge number of password prompts
575
576 https://bugs.kde.org/show_bug.cgi?id=362477
577 */
testExcessivePasswordPrompts()578 void ImapModelOpenConnectionTest::testExcessivePasswordPrompts()
579 {
580 cEmpty();
581 m_enableAutoLogin = false;
582 cServer("* OK [capability imap4rev1] Service Ready\r\n");
583 cEmpty();
584 QCOMPARE(authSpy->size(), 1);
585 SOCK->fakeDisconnect(QStringLiteral("Fake network going down"));
586 for (int i = 0; i < 10; ++i) {
587 QCoreApplication::processEvents();
588 }
589 // SOCK is now unusable
590 LibMailboxSync::setModelNetworkPolicy(model, Imap::Mailbox::NETWORK_ONLINE);
591 model->rowCount(QModelIndex());
592 QCoreApplication::processEvents();
593 // SOCK is now back to a usable state
594 cServer("* OK [capability imap4rev1] Service Ready\r\n");
595 QCOMPARE(authSpy->size(), 1);
596 model->setImapUser(QStringLiteral("a"));
597 model->setImapPassword(QStringLiteral("b"));
598 cClient(t.mk("LOGIN a b\r\n"));
599 QCOMPARE(authSpy->size(), 1);
600 cServer(t.last("OK [CAPABILITY imap4rev1] logged in\r\n"));
601 cClient(t.mk("LIST \"\" \"%\"\r\n"));
602 cServer(t.last("OK listed\r\n"));
603 cEmpty();
604 }
605
606 // FIXME: verify how LOGINDISABLED even after STARTLS ends up
607
provideAuthDetails()608 void ImapModelOpenConnectionTest::provideAuthDetails()
609 {
610 if (m_enableAutoLogin) {
611 model->setImapUser(QStringLiteral("luzr"));
612 model->setImapPassword(QStringLiteral("sikrit"));
613 }
614 }
615
616
617 QTEST_GUILESS_MAIN( ImapModelOpenConnectionTest )
618