Gui: Fix icon name
[trojita.git] / tests / Composer / test_Composer_Submission.cpp
blob89722d38e50cd0e66dd415dd93de4808faa3cfdc
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/>.
23 #include <QtTest>
24 #include "test_Composer_Submission.h"
25 #include "Utils/FakeCapabilitiesInjector.h"
26 #include "Composer/MessageComposer.h"
27 #include "Imap/data.h"
28 #include "Imap/Model/DragAndDrop.h"
29 #include "Imap/Model/ItemRoles.h"
30 #include "Imap/Network/MsgPartNetAccessManager.h"
31 #include "Streams/FakeSocket.h"
33 ComposerSubmissionTest::ComposerSubmissionTest():
34 m_submission(0), sendingSpy(0), sentSpy(0), requestedSendingSpy(0), requestedBurlSendingSpy(0),
35 submissionSucceededSpy(0), submissionFailedSpy(0)
39 void ComposerSubmissionTest::init()
41 LibMailboxSync::init();
43 // There's a default delay of 50ms which made the cEmpty() and justKeepTask() ignore these pending actions...
44 model->setProperty("trojita-imap-delayed-fetch-part", QVariant(0u));
46 existsA = 5;
47 uidValidityA = 333666;
48 uidMapA << 10 << 11 << 12 << 13 << 14;
49 uidNextA = 15;
50 helperSyncAWithMessagesEmptyState();
52 QCOMPARE(msgListA.child(0, 0).data(Imap::Mailbox::RoleMessageSubject), QVariant());
53 cClient(t.mk("UID FETCH 10:14 (" FETCH_METADATA_ITEMS ")\r\n"));
54 cServer("* 1 FETCH (BODYSTRUCTURE "
55 "(\"text\" \"plain\" (\"charset\" \"UTF-8\" \"format\" \"flowed\") NIL NIL \"8bit\" 362 15 NIL NIL NIL)"
56 " ENVELOPE (NIL \"subj\" NIL NIL NIL NIL NIL NIL NIL \"<msgid>\")"
57 ")\r\n" +
58 t.last("OK fetched\r\n"));
60 m_msaFactory = new MSA::FakeFactory();
61 QString accountId = QStringLiteral("fake_account");
62 m_submission = new Composer::Submission(this, model, m_msaFactory, accountId);
64 sendingSpy = new QSignalSpy(m_msaFactory, SIGNAL(sending()));
65 sentSpy = new QSignalSpy(m_msaFactory, SIGNAL(sent()));
66 requestedSendingSpy = new QSignalSpy(m_msaFactory, SIGNAL(requestedSending(QByteArray,QList<QByteArray>,QByteArray)));
67 requestedBurlSendingSpy = new QSignalSpy(m_msaFactory, SIGNAL(requestedBurlSending(QByteArray,QList<QByteArray>,QByteArray)));
69 submissionSucceededSpy = new QSignalSpy(m_submission, SIGNAL(succeeded()));
70 submissionFailedSpy = new QSignalSpy(m_submission, SIGNAL(failed(QString)));
73 void ComposerSubmissionTest::cleanup()
75 LibMailboxSync::cleanup();
77 delete m_submission;
78 m_submission = 0;
79 delete m_msaFactory;
80 m_msaFactory = 0;
81 delete sendingSpy;
82 sendingSpy = 0;
83 delete sentSpy;
84 sentSpy = 0;
85 delete requestedSendingSpy;
86 requestedSendingSpy = 0;
87 delete requestedBurlSendingSpy;
88 requestedBurlSendingSpy = 0;
89 delete submissionSucceededSpy;
90 submissionSucceededSpy = 0;
91 delete submissionFailedSpy;
92 submissionFailedSpy = 0;
95 /** @short Test that we can send a very simple mail */
96 void ComposerSubmissionTest::testEmptySubmission()
98 // Try sending an empty mail
99 m_submission->send();
101 QVERIFY(sendingSpy->isEmpty());
102 QVERIFY(sentSpy->isEmpty());
103 QCOMPARE(requestedSendingSpy->size(), 1);
105 // This is a fake MSA implementation, so we have to confirm that sending has begun
106 m_msaFactory->doEmitSending();
107 QCOMPARE(sendingSpy->size(), 1);
108 QCOMPARE(sentSpy->size(), 0);
110 m_msaFactory->doEmitSent();
112 QCOMPARE(sendingSpy->size(), 1);
113 QCOMPARE(sentSpy->size(), 1);
115 QCOMPARE(submissionSucceededSpy->size(), 1);
116 QCOMPARE(submissionFailedSpy->size(), 0);
117 cEmpty();
120 void ComposerSubmissionTest::testSimpleSubmission()
122 m_submission->composer()->setFrom(
123 Imap::Message::MailAddress(QStringLiteral("Foo Bar"), QString(),
124 QStringLiteral("foo.bar"), QStringLiteral("example.org")));
125 m_submission->composer()->setSubject(QStringLiteral("testing"));
126 m_submission->composer()->setText(QStringLiteral("Sample message"));
128 m_submission->send();
129 QCOMPARE(requestedSendingSpy->size(), 1);
130 m_msaFactory->doEmitSending();
131 QCOMPARE(sendingSpy->size(), 1);
132 m_msaFactory->doEmitSent();
133 QCOMPARE(sentSpy->size(), 1);
135 QCOMPARE(submissionSucceededSpy->size(), 1);
136 QCOMPARE(submissionFailedSpy->size(), 0);
138 QVERIFY(requestedSendingSpy->size() == 1 &&
139 requestedSendingSpy->at(0).size() == 3 &&
140 requestedSendingSpy->at(0)[2].toByteArray().contains("Sample message"));
141 cEmpty();
143 //qDebug() << requestedSendingSpy->front();
146 void ComposerSubmissionTest::testSimpleSubmissionWithAppendFailed()
148 helperTestSimpleAppend(false, false, false, false);
152 void ComposerSubmissionTest::testSimpleSubmissionWithAppendNoAppenduid()
154 helperTestSimpleAppend(true, false, false, false);
157 void ComposerSubmissionTest::testSimpleSubmissionWithAppendAppenduid()
159 helperTestSimpleAppend(true, true, false, false);
162 void ComposerSubmissionTest::testSimpleSubmissionReplyingToOk()
164 helperTestSimpleAppend(true, true, true, true);
167 void ComposerSubmissionTest::testSimpleSubmissionReplyingToFailedFlags()
169 helperTestSimpleAppend(true, true, true, false);
172 void ComposerSubmissionTest::helperSetupProperHeaders()
174 m_submission->composer()->setFrom(
175 Imap::Message::MailAddress(QStringLiteral("Foo Bar"), QString(),
176 QStringLiteral("foo.bar"), QStringLiteral("example.org")));
177 m_submission->composer()->setSubject(QStringLiteral("testing"));
178 m_submission->composer()->setText(QStringLiteral("Sample message"));
179 m_submission->setImapOptions(true, QStringLiteral("outgoing"), QStringLiteral("somehost"), QStringLiteral("userfred"), false);
182 void ComposerSubmissionTest::helperTestSimpleAppend(bool appendOk, bool appendUid,
183 bool shallUpdateReplyingTo, bool replyingToUpdateOk)
185 // Don't bother with literal processing
186 FakeCapabilitiesInjector injector(model);
187 injector.injectCapability(QStringLiteral("LITERAL+"));
189 helperSetupProperHeaders();
191 if (shallUpdateReplyingTo) {
192 QModelIndex msgA10 = model->index(0, 0, msgListA);
193 QVERIFY(msgA10.isValid());
194 QCOMPARE(msgA10.data(Imap::Mailbox::RoleMessageUid).toUInt(), uidMapA[0]);
195 m_submission->composer()->setReplyingToMessage(msgA10);
197 m_submission->send();
199 // We are waiting for APPEND to finish here
200 QCOMPARE(requestedSendingSpy->size(), 0);
202 for (int i=0; i<5; ++i)
203 QCoreApplication::processEvents();
204 QString sentSoFar = QString::fromUtf8(SOCK->writtenStuff());
205 QString expected = t.mk("APPEND outgoing (\\Seen) ");
206 QCOMPARE(sentSoFar.left(expected.size()), expected);
207 cEmpty();
208 QCOMPARE(requestedSendingSpy->size(), 0);
210 if (!appendOk) {
211 cServer(t.last("NO append failed\r\n"));
212 cEmpty();
214 QCOMPARE(submissionSucceededSpy->size(), 0);
215 QCOMPARE(submissionFailedSpy->size(), 1);
216 QCOMPARE(requestedSendingSpy->size(), 0);
217 justKeepTask();
218 return;
221 // Assume the APPEND has suceeded
222 if (appendUid) {
223 cServer(t.last("OK [APPENDUID 666333666 123] append done\r\n"));
224 } else {
225 cServer(t.last("OK append done\r\n"));
227 cEmpty();
229 QCOMPARE(requestedSendingSpy->size(), 1);
230 m_msaFactory->doEmitSending();
231 QCOMPARE(sendingSpy->size(), 1);
232 m_msaFactory->doEmitSent();
233 QCOMPARE(sentSpy->size(), 1);
235 QCOMPARE(submissionFailedSpy->size(), 0);
236 if (shallUpdateReplyingTo) {
237 QCOMPARE(submissionSucceededSpy->size(), 0);
238 cClient(t.mk("UID STORE ") + QByteArray::number(uidMapA[0]) + " +FLAGS (\\Answered)\r\n");
239 if (replyingToUpdateOk) {
240 cServer(t.last("OK flags updated\r\n"));
241 } else {
242 cServer(t.last("NO you aren't going to update flags that easily\r\n"));
244 for (int i=0; i<5; ++i)
245 QCoreApplication::processEvents();
247 QCOMPARE(submissionSucceededSpy->size(), 1);
248 QCOMPARE(submissionFailedSpy->size(), 0);
250 QVERIFY(requestedSendingSpy->size() == 1 &&
251 requestedSendingSpy->at(0).size() == 3 &&
252 requestedSendingSpy->at(0)[2].toByteArray().contains("Sample message"));
253 cEmpty();
255 //qDebug() << requestedSendingSpy->front();
258 /** @short Check that a missing file attachment prevents submission */
259 void ComposerSubmissionTest::testMissingFileAttachmentSmtpSave()
261 helperMissingAttachment(true, false, false, true);
264 /** @short Check that a missing file attachment prevents submission */
265 void ComposerSubmissionTest::testMissingFileAttachmentSmtpNoSave()
267 helperMissingAttachment(false, false, false, true);
270 /** @short Check that a missing file attachment prevents submission */
271 void ComposerSubmissionTest::testMissingFileAttachmentBurlSave()
273 helperMissingAttachment(true, true, false, true);
276 /** @short Check that a missing file attachment prevents submission */
277 void ComposerSubmissionTest::testMissingFileAttachmentBurlNoSave()
279 helperMissingAttachment(false, true, false, true);
282 /** @short Check that a missing file attachment prevents submission */
283 void ComposerSubmissionTest::testMissingFileAttachmentImap()
285 helperMissingAttachment(true, false, true, true);
288 /** @short Check that a missing file attachment prevents submission */
289 void ComposerSubmissionTest::testMissingImapAttachmentSmtpSave()
291 helperMissingAttachment(true, false, false, false);
294 /** @short Check that a missing file attachment prevents submission */
295 void ComposerSubmissionTest::testMissingImapAttachmentSmtpNoSave()
297 helperMissingAttachment(false, false, false, false);
300 /** @short Check that a missing file attachment prevents submission */
301 void ComposerSubmissionTest::testMissingImapAttachmentBurlSave()
303 helperMissingAttachment(true, true, false, false);
306 /** @short Check that a missing file attachment prevents submission */
307 void ComposerSubmissionTest::testMissingImapAttachmentBurlNoSave()
309 helperMissingAttachment(false, true, false, false);
312 /** @short Check that a missing file attachment prevents submission */
313 void ComposerSubmissionTest::testMissingImapAttachmentImap()
315 helperMissingAttachment(true, false, true, false);
318 void ComposerSubmissionTest::helperMissingAttachment(bool save, bool burl, bool imap, bool attachingFile)
320 #ifdef Q_OS_OS2
321 QSKIP("Looks like QTemporaryFile is broken on OS/2");
322 #endif
323 helperSetupProperHeaders();
325 if (imap) {
326 Q_ASSERT(save);
329 QPersistentModelIndex msgA10 = model->index(0, 0, msgListA);
330 QVERIFY(msgA10.isValid());
331 QCOMPARE(msgA10.data(Imap::Mailbox::RoleMessageUid).toUInt(), uidMapA[0]);
333 m_submission->setImapOptions(save, QStringLiteral("meh"), QStringLiteral("pwn"), QStringLiteral("bob"), imap);
334 m_submission->setSmtpOptions(burl, QStringLiteral("pwn"));
335 m_submission->composer()->setReplyingToMessage(msgA10);
336 m_msaFactory->setBurlSupport(burl);
337 m_msaFactory->setImapSupport(imap);
339 if (attachingFile) {
340 // needs a special block for proper RAII-based removal
341 QTemporaryFile tempFile;
342 tempFile.open();
343 tempFile.write("Sample attachment for Trojita's ComposerSubmissionTest\r\n");
344 QCOMPARE(m_submission->composer()->addFileAttachment(tempFile.fileName()), true);
345 // The file gets deleted as soon as we leave this scope
346 } else {
347 // Attaching something which lives on the IMAP server
349 // Make sure the IMAP bits are ready
350 QCOMPARE(model->rowCount(msgA10), 1);
351 QPersistentModelIndex partData = model->index(0, 0, msgA10);
352 QVERIFY(partData.isValid());
354 helperAttachImapPart(0, "/0");
355 cClient(t.mk("UID FETCH ") + QByteArray::number(uidMapA[0]) + " (BODY.PEEK[1])\r\n");
358 m_submission->send();
360 if (!attachingFile) {
361 // Deliver the queued data to make the generic test prologue happy
362 cServer("* 1 FETCH (BODY[1] \"contents\")\r\n");
363 cServer(t.last("OK fetched\r\n"));
365 QCOMPARE(requestedSendingSpy->size(), 0);
366 QCOMPARE(submissionSucceededSpy->size(), 0);
367 QCOMPARE(submissionFailedSpy->size(), 1);
368 cEmpty();
369 justKeepTask();
372 void ComposerSubmissionTest::helperAttachImapPart(const int row, const QByteArray mimePart)
374 QScopedPointer<QMimeData> mimeData(new QMimeData());
375 QByteArray encodedData;
376 QDataStream stream(&encodedData, QIODevice::WriteOnly);
377 stream.setVersion(QDataStream::Qt_4_6);
378 stream << QStringLiteral("a") << uidValidityA << uidMapA[row] << mimePart;
379 mimeData->setData(Imap::MimeTypes::xTrojitaImapPart, encodedData);
380 auto partIndex = Imap::Network::MsgPartNetAccessManager::pathToPart(msgListA.child(row, 0), mimePart);
381 QVERIFY(partIndex.isValid());
382 QCOMPARE(m_submission->composer()->dropMimeData(mimeData.data(), Qt::CopyAction,
383 partIndex.row(), partIndex.column(), partIndex),
384 true);
387 void ComposerSubmissionTest::helperAttachImapMessage(const uint uid)
389 QScopedPointer<QMimeData> mimeData(new QMimeData());
390 QByteArray encodedData;
391 QDataStream stream(&encodedData, QIODevice::WriteOnly);
392 stream.setVersion(QDataStream::Qt_4_6);
393 stream << QStringLiteral("a") << uidValidityA << (QList<uint>() << uid);
394 mimeData->setData(Imap::MimeTypes::xTrojitaMessageList, encodedData);
395 QCOMPARE(m_submission->composer()->dropMimeData(mimeData.data(), Qt::CopyAction, 0, 0, QModelIndex()), true);
398 #define EXTRACT_TARILING_NUMBER(NUM) \
400 QCOMPARE(sentSoFar.left(expected.size()), expected); \
401 bool numberOk = false; \
402 QString numericPart = sentSoFar.mid(expected.size()); \
403 QCOMPARE(numericPart.right(3), QString::fromUtf8("}\r\n")); \
404 int num = numericPart.leftRef(numericPart.size() - 3).toInt(&numberOk); \
405 QVERIFY(numberOk); \
406 NUM = num; \
409 #define EAT_OCTETS(NUM, OUT) \
411 for (int i=0; i<5; ++i) \
412 QCoreApplication::processEvents(); \
413 QString buf = QString::fromUtf8(SOCK->writtenStuff()); \
414 QVERIFY(buf.size() > NUM); \
415 OUT = buf.mid(NUM); \
418 #define EXTRACT_OCTETS(NUM, PRIOR, REST) \
420 for (int i=0; i<5; ++i) \
421 QCoreApplication::processEvents(); \
422 QString buf = QString::fromUtf8(SOCK->writtenStuff()); \
423 QVERIFY(buf.size() > NUM); \
424 PRIOR = buf.left(NUM); \
425 REST = buf.mid(NUM); \
429 void ComposerSubmissionTest::testBurlSubmission()
431 FakeCapabilitiesInjector injector(model);
432 injector.injectCapability(QStringLiteral("CATENATE"));
433 injector.injectCapability(QStringLiteral("URLAUTH"));
434 helperSetupProperHeaders();
435 m_submission->setImapOptions(true, QStringLiteral("meh"), QStringLiteral("host"), QStringLiteral("usr"), false);
436 m_submission->setSmtpOptions(true, QStringLiteral("smtpUser"));
437 m_msaFactory->setBurlSupport(true);
439 helperAttachImapPart(0, "/0");
440 m_submission->send();
442 for (int i=0; i<5; ++i)
443 QCoreApplication::processEvents();
444 QString sentSoFar = QString::fromUtf8(SOCK->writtenStuff());
445 QString expected = t.mk("APPEND meh (\\Seen) CATENATE (TEXT {");
446 int octets;
447 EXTRACT_TARILING_NUMBER(octets);
448 cServer("+ carry on\r\n");
449 EAT_OCTETS(octets, sentSoFar);
450 expected = QStringLiteral(" URL \"/a;UIDVALIDITY=333666/;UID=10/;SECTION=1\" TEXT {");
451 EXTRACT_TARILING_NUMBER(octets);
452 cServer("+ carry on\r\n");
453 EAT_OCTETS(octets, sentSoFar);
454 QCOMPARE(sentSoFar, QString::fromUtf8(")\r\n"));
455 cServer(t.last("OK [APPENDUID 666333666 123] append done\r\n"));
456 cClient(t.mk("GENURLAUTH \"imap://usr@host/meh;UIDVALIDITY=666333666/;UID=123;urlauth=submit+smtpUser\" INTERNAL\r\n"));
457 // This is an actual example from RFC 4467
458 QString genAuthUrl = QStringLiteral("imap://joe@example.com/INBOX/;uid=20/;section=1.2;urlauth=submit+fred:internal:91354a473744909de610943775f92038");
459 cServer("* GENURLAUTH \"" + genAuthUrl.toUtf8() + "\"\r\n");
460 cServer(t.last("OK genurlauth\r\n"));
461 cEmpty();
462 QCOMPARE(requestedSendingSpy->size(), 0);
463 QCOMPARE(requestedBurlSendingSpy->size(), 1);
464 m_msaFactory->doEmitSending();
465 QCOMPARE(sendingSpy->size(), 1);
466 m_msaFactory->doEmitSent();
467 QCOMPARE(sentSpy->size(), 1);
469 QCOMPARE(submissionSucceededSpy->size(), 1);
470 QCOMPARE(submissionFailedSpy->size(), 0);
472 QCOMPARE(requestedBurlSendingSpy->size(), 1);
473 QCOMPARE(requestedBurlSendingSpy->at(0).size(), 3);
474 QCOMPARE(requestedBurlSendingSpy->at(0)[2].toString(), genAuthUrl);
475 cEmpty();
476 justKeepTask();
479 void ComposerSubmissionTest::testBurlSubmissionAttachedWholeMessage()
481 FakeCapabilitiesInjector injector(model);
482 injector.injectCapability(QStringLiteral("CATENATE"));
483 injector.injectCapability(QStringLiteral("URLAUTH"));
484 helperSetupProperHeaders();
485 m_submission->setImapOptions(true, QStringLiteral("meh"), QStringLiteral("host"), QStringLiteral("usr"), false);
486 m_submission->setSmtpOptions(true, QStringLiteral("smtpUser"));
487 m_msaFactory->setBurlSupport(true);
489 helperAttachImapMessage(uidMapA[1]);
490 m_submission->send();
492 for (int i=0; i<5; ++i)
493 QCoreApplication::processEvents();
494 QString sentSoFar = QString::fromUtf8(SOCK->writtenStuff());
495 QString expected = t.mk("APPEND meh (\\Seen) CATENATE (TEXT {");
496 int octets;
497 EXTRACT_TARILING_NUMBER(octets);
498 cServer("+ carry on\r\n");
499 EAT_OCTETS(octets, sentSoFar);
500 expected = QStringLiteral(" URL \"/a;UIDVALIDITY=333666/;UID=11\" TEXT {");
501 EXTRACT_TARILING_NUMBER(octets);
502 cServer("+ carry on\r\n");
503 EAT_OCTETS(octets, sentSoFar);
504 QCOMPARE(sentSoFar, QString::fromUtf8(")\r\n"));
505 cServer(t.last("OK [APPENDUID 666333666 123] append done\r\n"));
506 cClient(t.mk("GENURLAUTH \"imap://usr@host/meh;UIDVALIDITY=666333666/;UID=123;urlauth=submit+smtpUser\" INTERNAL\r\n"));
507 // This is an actual example from RFC 4467
508 QString genAuthUrl = QStringLiteral("imap://joe@example.com/INBOX/;uid=20;urlauth=submit+fred:internal:91354a473744909de610943775f92038");
509 cServer("* GENURLAUTH \"" + genAuthUrl.toUtf8() + "\"\r\n");
510 cServer(t.last("OK genurlauth\r\n"));
511 cEmpty();
512 QCOMPARE(requestedSendingSpy->size(), 0);
513 QCOMPARE(requestedBurlSendingSpy->size(), 1);
514 m_msaFactory->doEmitSending();
515 QCOMPARE(sendingSpy->size(), 1);
516 m_msaFactory->doEmitSent();
517 QCOMPARE(sentSpy->size(), 1);
519 QCOMPARE(submissionSucceededSpy->size(), 1);
520 QCOMPARE(submissionFailedSpy->size(), 0);
522 QCOMPARE(requestedBurlSendingSpy->size(), 1);
523 QCOMPARE(requestedBurlSendingSpy->at(0).size(), 3);
524 QCOMPARE(requestedBurlSendingSpy->at(0)[2].toString(), genAuthUrl);
525 cEmpty();
526 justKeepTask();
530 /** @short Chech that CATENATE is used and BURL disabled when the IMAP server cannot do URLAUTH */
531 void ComposerSubmissionTest::testCatenateBurlWithoutUrlauth()
533 FakeCapabilitiesInjector injector(model);
534 injector.injectCapability(QStringLiteral("CATENATE"));
535 // The URLAUTH is, however, not advertised by the IMAP server
536 helperSetupProperHeaders();
537 m_submission->setImapOptions(true, QStringLiteral("meh"), QStringLiteral("host"), QStringLiteral("usr"), false);
538 m_submission->setSmtpOptions(true, QStringLiteral("smtpUser"));
539 m_msaFactory->setBurlSupport(true);
541 QFETCH(QByteArray, bodystructure);
542 QFETCH(QByteArray, mimePart);
543 QFETCH(QByteArray, mimePartIndex);
544 QFETCH(QByteArray, cte);
545 QFETCH(QByteArray, rawDataFetch);
546 QFETCH(QByteArray, rawDataSMTP);
548 cServer("* 2 FETCH (UID " + QByteArray::number(uidMapA[1]) + " BODYSTRUCTURE (" + bodystructure + "))\r\n");
550 helperAttachImapPart(1, mimePartIndex);
551 cClient(t.mk("UID FETCH ") + QByteArray::number(uidMapA[1]) + " (BODY.PEEK[" + mimePart + "])\r\n");
552 cServer("* 2 FETCH (BODY[" + mimePart + "] {" + QByteArray::number(rawDataFetch.size()) + "}\r\n" + rawDataFetch + ")\r\n");
553 cServer(t.last("OK fetched\r\n"));
555 m_submission->send();
557 for (int i=0; i<5; ++i)
558 QCoreApplication::processEvents();
559 QString sentSoFar = QString::fromUtf8(SOCK->writtenStuff());
560 QString expected = t.mk("APPEND meh (\\Seen) CATENATE (TEXT {");
561 int octets;
562 EXTRACT_TARILING_NUMBER(octets);
563 cServer("+ carry on\r\n");
564 QString preambleViaImap;
565 EXTRACT_OCTETS(octets, preambleViaImap, sentSoFar);
566 expected = QStringLiteral(" URL \"/a;UIDVALIDITY=333666/;UID=") + QByteArray::number(uidMapA[1]) + "/;SECTION=" + QString::fromUtf8(mimePart) + "\" TEXT {";
567 EXTRACT_TARILING_NUMBER(octets);
568 cServer("+ carry on\r\n");
569 EAT_OCTETS(octets, sentSoFar);
570 QCOMPARE(sentSoFar, QString::fromUtf8(")\r\n"));
571 cServer(t.last("OK [APPENDUID 666333666 123] append done\r\n"));
572 cEmpty();
573 QCOMPARE(requestedSendingSpy->size(), 1);
574 QCOMPARE(requestedBurlSendingSpy->size(), 0);
575 m_msaFactory->doEmitSending();
576 QCOMPARE(sendingSpy->size(), 1);
577 m_msaFactory->doEmitSent();
578 QCOMPARE(sentSpy->size(), 1);
580 QCOMPARE(submissionSucceededSpy->size(), 1);
581 QCOMPARE(submissionFailedSpy->size(), 0);
583 QCOMPARE(requestedBurlSendingSpy->size(), 0);
584 QCOMPARE(requestedSendingSpy->size(), 1);
585 QCOMPARE(requestedSendingSpy->at(0).size(), 3);
586 QByteArray outgoingMessage = requestedSendingSpy->at(0)[2].toByteArray();
587 QVERIFY(outgoingMessage.contains("Subject: testing\r\n"));
588 QVERIFY(outgoingMessage.contains("Sample message\r\n"));
589 if (!outgoingMessage.contains(rawDataSMTP)) {
590 qDebug() << outgoingMessage;
591 qDebug() << rawDataSMTP;
593 QVERIFY(outgoingMessage.contains(rawDataSMTP));
594 auto preambleViaSmtp = QString::fromUtf8(outgoingMessage).left(preambleViaImap.size());
595 QCOMPARE(preambleViaSmtp, preambleViaImap);
596 cEmpty();
597 justKeepTask();
600 void ComposerSubmissionTest::testCatenateBurlWithoutUrlauth_data()
602 QTest::addColumn<QByteArray>("bodystructure");
603 QTest::addColumn<QByteArray>("mimePart");
604 QTest::addColumn<QByteArray>("mimePartIndex");
605 QTest::addColumn<QByteArray>("cte");
606 QTest::addColumn<QByteArray>("rawDataFetch");
607 QTest::addColumn<QByteArray>("rawDataSMTP");
609 QTest::newRow("plaintext-8bit")
610 << bsPlaintext
611 << QByteArray("1")
612 << QByteArray("/0")
613 << QByteArray("8bit")
614 << QByteArray("contents fetched over IMAP")
615 << QByteArray("\r\n"
616 "Content-Type: text/plain\r\n"
617 "Content-Disposition: attachment;\r\n"
618 "\tfilename=attachment\r\n"
619 "Content-Transfer-Encoding: 7bit\r\n"
620 "\r\n"
621 "contents fetched over IMAP");
623 QTest::newRow("plaintext-quoted-printable")
624 << bsMultipartSignedTextPlain
625 << QByteArray("1")
626 << QByteArray("/0/0")
627 << QByteArray("quoted-printable")
628 << QByteArray("pwn=20zor are usually found on very, very long lines indeed, which are looooong anyway.")
629 << QByteArray("\r\n"
630 "Content-Type: text/plain\r\n"
631 "Content-Disposition: attachment;\r\n"
632 "\tfilename=attachment\r\n"
633 "Content-Transfer-Encoding: quoted-printable\r\n"
634 "\r\n"
635 "pwn zor are usually found on very, very long lines indeed, which are looooong=\r\n"
636 " anyway.\r\n"
637 "--trojita=");
639 QTest::newRow("base64-part")
640 << bsManyPlaintexts
641 << QByteArray("2")
642 << QByteArray("/0/1")
643 << QByteArray("base64")
644 << QByteArray("meh wtf").toBase64()
645 << QByteArray("\r\n"
646 "Content-Type: plain/plain\r\n"
647 "Content-Disposition: attachment;\r\n"
648 "\tfilename=attachment\r\n"
649 "Content-Transfer-Encoding: base64\r\n"
650 "\r\n"
651 "bWVoIHd0Zg==\r\n\r\n"
652 "--trojita=");
655 /** @short Make sure that failed mail delivery prevents marking the original as answered, etc */
656 void ComposerSubmissionTest::testFailedMsa()
658 FakeCapabilitiesInjector injector(model);
659 injector.injectCapability(QStringLiteral("LITERAL+"));
661 helperSetupProperHeaders();
663 QModelIndex msgA10 = model->index(0, 0, msgListA);
664 QVERIFY(msgA10.isValid());
665 QCOMPARE(msgA10.data(Imap::Mailbox::RoleMessageUid).toUInt(), uidMapA[0]);
666 m_submission->composer()->setReplyingToMessage(msgA10);
667 m_submission->send();
669 // We are waiting for APPEND to finish here
670 QCOMPARE(requestedSendingSpy->size(), 0);
672 for (int i=0; i<5; ++i)
673 QCoreApplication::processEvents();
674 QString sentSoFar = QString::fromUtf8(SOCK->writtenStuff());
675 QString expected = t.mk("APPEND outgoing (\\Seen) ");
676 QCOMPARE(sentSoFar.left(expected.size()), expected);
677 cEmpty();
678 QCOMPARE(requestedSendingSpy->size(), 0);
680 // Assume the APPEND has suceeded
681 cServer(t.last("OK [APPENDUID 666333666 123] append done\r\n"));
682 cEmpty();
684 QCOMPARE(requestedSendingSpy->size(), 1);
685 m_msaFactory->doEmitSending();
686 QCOMPARE(sendingSpy->size(), 1);
687 m_msaFactory->doEmitError(QStringLiteral("error"));
688 QCOMPARE(sentSpy->size(), 0);
690 QCOMPARE(submissionFailedSpy->size(), 1);
691 QCOMPARE(submissionSucceededSpy->size(), 0);
693 QVERIFY(requestedSendingSpy->size() == 1 &&
694 requestedSendingSpy->at(0).size() == 3 &&
695 requestedSendingSpy->at(0)[2].toByteArray().contains("Sample message"));
696 cEmpty();
700 /** @short Test the Parser that a missing continuation request effectively cancels out the processing of the current command
702 We're testing it on this level because it is easy to trigger sending a literal this way.
704 void ComposerSubmissionTest::testNoImapContinuation()
706 helperSetupProperHeaders();
707 m_submission->send();
708 QCOMPARE(requestedSendingSpy->size(), 0);
710 // Also queue a couple of another IMAP commands
711 model->switchToMailbox(idxB);
712 model->switchToMailbox(idxC);
714 // A command sending the literal string will be rejected.
715 // The number of octets is platform-dependent, so we cannot just use cClient() here.
716 for (int i=0; i<5; ++i)
717 QCoreApplication::processEvents();
718 QString sentSoFar = QString::fromUtf8(SOCK->writtenStuff());
719 QString expected = t.mk("APPEND outgoing (\\Seen) {");
720 int octets;
721 EXTRACT_TARILING_NUMBER(octets);
722 Q_UNUSED(octets);
723 cEmpty();
724 cServer(t.last("NO rejected\r\n"));
725 // The Parser shall detect this and proceed towards sending other IMAP commands
726 cClient(t.mk("SELECT b\r\n"));
727 cServer("* 0 exists\r\n" + t.last("OK selected\r\n"));
728 cClient(t.mk("SELECT c\r\n"));
729 cServer("* 0 exists\r\n" + t.last("OK selected\r\n"));
730 cEmpty();
731 QCOMPARE(submissionFailedSpy->size(), 1);
732 QCOMPARE(submissionSucceededSpy->size(), 0);
733 justKeepTask();
736 /** @short Make sure that the original message is marked as replied to when replying */
737 void ComposerSubmissionTest::testReplyingNormal()
739 helperSetupProperHeaders();
740 m_submission->setImapOptions(false, QString(), QString(), QString(), false);
741 QModelIndex origMessage = msgListA.child(0, 0);
742 QVERIFY(origMessage.isValid());
743 QCOMPARE(origMessage.data(Imap::Mailbox::RoleMessageUid).toInt(), 10);
744 m_submission->composer()->setReplyingToMessage(origMessage);
746 m_submission->send();
747 cEmpty();
749 QCOMPARE(requestedSendingSpy->size(), 1);
750 m_msaFactory->doEmitSending();
751 QCOMPARE(sendingSpy->size(), 1);
752 m_msaFactory->doEmitSent();
753 QCOMPARE(sentSpy->size(), 1);
754 cClient(t.mk("UID STORE 10 +FLAGS (\\Answered)\r\n"));
755 cServer("* 1 FETCH (UID 10 FLAGS (\\Answered))\r\n"
756 + t.last("OK stored\r\n"));
757 cEmpty();
759 QCOMPARE(submissionSucceededSpy->size(), 1);
760 QCOMPARE(submissionFailedSpy->size(), 0);
762 QVERIFY(requestedSendingSpy->size() == 1 &&
763 requestedSendingSpy->at(0).size() == 3 &&
764 requestedSendingSpy->at(0)[2].toByteArray().contains("Sample message"));
765 cEmpty();
768 /** @short Make sure that the original message is marked as $Forwarded when forwarding */
769 void ComposerSubmissionTest::testForwardingNormal()
771 helperSetupProperHeaders();
772 m_submission->setImapOptions(false, QString(), QString(), QString(), false);
773 QModelIndex origMessage = msgListA.child(0, 0);
774 QVERIFY(origMessage.isValid());
775 QCOMPARE(origMessage.data(Imap::Mailbox::RoleMessageUid).toInt(), 10);
776 m_submission->composer()->prepareForwarding(origMessage, Composer::ForwardMode::FORWARD_AS_ATTACHMENT);
778 cClientRegExp(t.mk("UID FETCH 10 \\(BODY\\.PEEK\\["
780 "TEXT\\] BODY\\.PEEK\\[HEADER"
782 "HEADER\\] BODY\\.PEEK\\[TEXT"
784 "\\]\\)"));
785 cServer("* 1 FETCH (BODY[TEXT] \"contents\" BODY[HEADER] \"headers\")\r\n");
786 cServer(t.last("OK fetched\r\n"));
787 cEmpty();
789 m_submission->send();
790 cEmpty();
792 QCOMPARE(requestedSendingSpy->size(), 1);
793 m_msaFactory->doEmitSending();
794 QCOMPARE(sendingSpy->size(), 1);
795 m_msaFactory->doEmitSent();
796 QCOMPARE(sentSpy->size(), 1);
797 cClient(t.mk("UID STORE 10 +FLAGS ($Forwarded)\r\n"));
798 cServer("* 1 FETCH (UID 10 FLAGS ($Forwarded))\r\n"
799 + t.last("OK stored\r\n"));
800 cEmpty();
801 QCOMPARE(submissionSucceededSpy->size(), 1);
802 QCOMPARE(submissionFailedSpy->size(), 0);
804 QVERIFY(requestedSendingSpy->size() == 1 &&
805 requestedSendingSpy->at(0).size() == 3 &&
806 requestedSendingSpy->at(0)[2].toByteArray().contains("Sample message"));
807 cEmpty();
810 /** @short Check that we don't crash if a message being forwarded disappears while we're waiting for it */
811 void ComposerSubmissionTest::testForwardingDeletedWhileFetching()
813 helperSetupProperHeaders();
814 m_submission->setImapOptions(false, QString(), QString(), QString(), false);
815 QModelIndex origMessage = msgListA.child(0, 0);
816 QVERIFY(origMessage.isValid());
817 QCOMPARE(origMessage.data(Imap::Mailbox::RoleMessageUid).toInt(), 10);
818 m_submission->composer()->prepareForwarding(origMessage, Composer::ForwardMode::FORWARD_AS_ATTACHMENT);
820 cClientRegExp(t.mk("UID FETCH 10 \\(BODY\\.PEEK\\["
822 "TEXT\\] BODY\\.PEEK\\[HEADER"
824 "HEADER\\] BODY\\.PEEK\\[TEXT"
826 "\\]\\)"));
827 cServer("* 1 EXPUNGE\r\n")
828 cServer(t.last("NO not fetched, it's gone now\r\n"));
829 cEmpty();
831 m_submission->send();
832 cEmpty();
834 QCOMPARE(requestedSendingSpy->size(), 0);
835 QCOMPARE(submissionSucceededSpy->size(), 0);
836 QCOMPARE(submissionFailedSpy->size(), 1);
837 cEmpty();
840 /** @short Make sure that replying to a removed message works reasonably well */
841 void ComposerSubmissionTest::testReplyingToRemoved()
843 helperSetupProperHeaders();
844 m_submission->setImapOptions(false, QString(), QString(), QString(), false);
845 QModelIndex origMessage = msgListA.child(0, 0);
846 QVERIFY(origMessage.isValid());
847 QCOMPARE(origMessage.data(Imap::Mailbox::RoleMessageUid).toInt(), 10);
848 m_submission->composer()->setReplyingToMessage(origMessage);
849 cServer("* 1 EXPUNGE\r\n");
850 QVERIFY(!m_submission->composer()->replyingToMessage().isValid());
852 m_submission->send();
853 cEmpty();
855 QCOMPARE(requestedSendingSpy->size(), 1);
856 m_msaFactory->doEmitSending();
857 QCOMPARE(sendingSpy->size(), 1);
858 m_msaFactory->doEmitSent();
859 QCOMPARE(sentSpy->size(), 1);
860 cEmpty();
862 QCOMPARE(submissionSucceededSpy->size(), 1);
863 QCOMPARE(submissionFailedSpy->size(), 0);
865 QVERIFY(requestedSendingSpy->size() == 1 &&
866 requestedSendingSpy->at(0).size() == 3 &&
867 requestedSendingSpy->at(0)[2].toByteArray().contains("Sample message"));
868 cEmpty();
871 QTEST_GUILESS_MAIN(ComposerSubmissionTest)