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