1 /* Copyright (C) 2014 - 2015 Stephan Platz <trojita@paalsteek.de>
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 <QTest>
24 #include <QNetworkRequest>
25 #include <QNetworkReply>
26 #include <QStandardItemModel>
27 
28 #include "test_Cryptography_MessageModel.h"
29 #include "configure.cmake.h"
30 #include "Cryptography/MessageModel.h"
31 #include "Cryptography/MessagePart.h"
32 #ifdef TROJITA_HAVE_MIMETIC
33 #include "Cryptography/LocalMimeParser.h"
34 #endif
35 #include "Imap/data.h"
36 #include "Imap/Model/ItemRoles.h"
37 #include "Imap/Model/MailboxTree.h"
38 #include "Streams/FakeSocket.h"
39 
40 /** @short Verify passthrough of existing MIME parts without modifications */
testImapMessageParts()41 void CryptographyMessageModelTest::testImapMessageParts()
42 {
43     QFETCH(QByteArray, bodystructure);
44     QFETCH(QByteArray, partId);
45     QFETCH(pathList, path);
46     QFETCH(QByteArray, pathToPart);
47     QFETCH(QByteArray, data);
48 
49     // By default, there's a 50ms delay between the time we request a part download and the time it actually happens.
50     // That's too long for a unit test.
51     model->setProperty("trojita-imap-delayed-fetch-part", 0);
52 
53     helperSyncBNoMessages();
54     cServer("* 1 EXISTS\r\n");
55     cClient(t.mk("UID FETCH 1:* (FLAGS)\r\n"));
56     cServer("* 1 FETCH (UID 333 FLAGS ())\r\n" + t.last("OK fetched\r\n"));
57 
58     QCOMPARE(model->rowCount(msgListB), 1);
59     QModelIndex msg = msgListB.child(0, 0);
60     QVERIFY(msg.isValid());
61     QCOMPARE(model->rowCount(msg), 0);
62     Cryptography::MessageModel msgModel(0, msg);
63     cClient(t.mk("UID FETCH 333 (" FETCH_METADATA_ITEMS ")\r\n"));
64     cServer("* 1 FETCH (UID 333 BODYSTRUCTURE (" + bodystructure + "))\r\n" + t.last("OK fetched\r\n"));
65     cEmpty();
66     QVERIFY(model->rowCount(msg) > 0);
67     QModelIndex mappedMsg = msgModel.index(0,0);
68     QVERIFY(mappedMsg.isValid());
69     QVERIFY(msgModel.rowCount(mappedMsg) > 0);
70 
71     QModelIndex mappedPart = mappedMsg;
72     for (QList<QPair<int,int> >::const_iterator it = path.constBegin(), end = path.constEnd(); it != end; ++it) {
73         mappedPart = mappedPart.child(it->first, it->second);
74         QVERIFY(mappedPart.isValid());
75     }
76     QCOMPARE(mappedPart.data(Imap::Mailbox::RolePartData).toString(), QString());
77     cClient(t.mk(QByteArray("UID FETCH 333 (BODY.PEEK[" + partId + "])\r\n")));
78     cServer("* 1 FETCH (UID 333 BODY[" + partId + "] " + asLiteral(data) + ")\r\n" + t.last("OK fetched\r\n"));
79 
80     QCOMPARE(mappedPart.data(Imap::Mailbox::RolePartData).toByteArray(), data);
81     QCOMPARE(mappedPart.data(Imap::Mailbox::RolePartPathToPart).toByteArray(), pathToPart);
82 
83     cEmpty();
84     QVERIFY(errorSpy->isEmpty());
85 }
86 
testImapMessageParts_data()87 void CryptographyMessageModelTest::testImapMessageParts_data()
88 {
89     QTest::addColumn<QByteArray>("bodystructure");
90     QTest::addColumn<QByteArray>("partId");
91     QTest::addColumn<QList<QPair<int,int> > >("path");
92     QTest::addColumn<QByteArray>("pathToPart");
93     QTest::addColumn<QByteArray>("data");
94 
95     QTest::newRow("plaintext-root")
96             << bsPlaintext
97             << QByteArray("1")
98             << (pathList() << qMakePair(0,0))
99             << QByteArray("/0")
100             << QByteArray("blesmrt");
101 
102     QTest::newRow("torture-plaintext")
103             << bsTortureTest
104             << QByteArray("1")
105             << (pathList() << qMakePair(0,0) << qMakePair(0,0))
106             << QByteArray("/0/0")
107             << QByteArray("plaintext");
108 
109     QTest::newRow("torture-richtext")
110             << bsTortureTest
111             << QByteArray("2.2.1")
112             << (pathList() << qMakePair(0,0) << qMakePair(1,0)
113                 << qMakePair(0,0) << qMakePair(1,0) << qMakePair(0,0))
114             << QByteArray("/0/1/0/1/0")
115             << QByteArray("text richtext");
116 }
117 
118 /** @short Verify building and retrieving of a custom MIME tree structure */
testCustomMessageParts()119 void CryptographyMessageModelTest::testCustomMessageParts()
120 {
121     // Initialize model with a root item
122     QStandardItemModel *minimal = new QStandardItemModel();
123     QStandardItem *dummy_root = new QStandardItem();
124     QStandardItem *root_mime = new QStandardItem(QStringLiteral("multipart/mixed"));
125     dummy_root->appendRow(root_mime);
126     minimal->invisibleRootItem()->appendRow(dummy_root);
127 
128     // Make sure we didn't mess up until here
129     QVERIFY(minimal->index(0,0).child(0,0).isValid());
130     QCOMPARE(minimal->index(0,0).child(0,0).data(), root_mime->data(Qt::DisplayRole));
131 
132     Cryptography::MessageModel msgModel(0, minimal->index(0,0));
133 
134     QModelIndex rootPartIndex = msgModel.index(0,0).child(0,0);
135 
136     QCOMPARE(rootPartIndex.data(), root_mime->data(Qt::DisplayRole));
137 
138     Cryptography::LocalMessagePart *localRoot = new Cryptography::LocalMessagePart(nullptr, 0, QByteArrayLiteral("multipart/mixed"));
139     Cryptography::LocalMessagePart *localText = new Cryptography::LocalMessagePart(localRoot, 0, QByteArrayLiteral("text/plain"));
140     localText->setData(QByteArrayLiteral("foobar"));
141     localRoot->setChild(0, Cryptography::MessagePart::Ptr(localText));
142     Cryptography::LocalMessagePart *localHtml = new Cryptography::LocalMessagePart(localRoot, 1, QByteArrayLiteral("text/html"));
143     localHtml->setData(QByteArrayLiteral("<html>foobar</html>"));
144     localRoot->setChild(1, Cryptography::MessagePart::Ptr(localHtml));
145 
146     msgModel.insertSubtree(rootPartIndex, Cryptography::MessagePart::Ptr(localRoot));
147 
148     QVERIFY(msgModel.rowCount(rootPartIndex) > 0);
149 
150     QModelIndex localRootIndex = rootPartIndex.child(0, 0);
151     QVERIFY(localRootIndex.isValid());
152     QCOMPARE(localRootIndex.data(Imap::Mailbox::RolePartMimeType), localRoot->data(Imap::Mailbox::RolePartMimeType));
153     QModelIndex localTextIndex = localRootIndex.child(0, 0);
154     QVERIFY(localTextIndex.isValid());
155     QCOMPARE(localTextIndex.data(Imap::Mailbox::RolePartMimeType), localText->data(Imap::Mailbox::RolePartMimeType));
156     QModelIndex localHtmlIndex = localRootIndex.child(1, 0);
157     QVERIFY(localHtmlIndex.isValid());
158     QCOMPARE(localHtmlIndex.data(Imap::Mailbox::RolePartMimeType), localHtml->data(Imap::Mailbox::RolePartMimeType));
159 }
160 
161 /** @short Check adding a custom MIME tree structure to an existing message
162  *
163  *  This test fetches the structure of an IMAP message and adds some custom
164  *  structure to that message. Adding random data as child of a text/plain
165  *  MIME part does not make sense semantically but that's not what we want to
166  *  test here.
167  */
testMixedMessageParts()168 void CryptographyMessageModelTest::testMixedMessageParts()
169 {
170 
171     // By default, there's a 50ms delay between the time we request a part download and the time it actually happens.
172     // That's too long for a unit test.
173     model->setProperty("trojita-imap-delayed-fetch-part", 0);
174 
175     helperSyncBNoMessages();
176     cServer("* 1 EXISTS\r\n");
177     cClient(t.mk("UID FETCH 1:* (FLAGS)\r\n"));
178     cServer("* 1 FETCH (UID 333 FLAGS ())\r\n" + t.last("OK fetched\r\n"));
179 
180     QCOMPARE(model->rowCount(msgListB), 1);
181     QModelIndex msg = msgListB.child(0, 0);
182     QVERIFY(msg.isValid());
183     QCOMPARE(model->rowCount(msg), 0);
184     cClient(t.mk("UID FETCH 333 (" FETCH_METADATA_ITEMS ")\r\n"));
185     const QByteArray bsEncrypted("\"encrypted\" (\"protocol\" \"application/pgp-encrypted\" \"boundary\" \"trojita=_7cf0b2b6-64c6-41ad-b381-853caf492c54\") NIL NIL NIL");
186     cServer("* 1 FETCH (UID 333 BODYSTRUCTURE (" + bsEncrypted + "))\r\n" + t.last("OK fetched\r\n"));
187     cEmpty();
188     QVERIFY(model->rowCount(msg) > 0);
189     Cryptography::MessageModel msgModel(0, msg);
190     QModelIndex mappedMsg = msgModel.index(0,0);
191     QVERIFY(mappedMsg.isValid());
192     QVERIFY(msgModel.rowCount(mappedMsg) > 0);
193     QCOMPARE(msgModel.parent(mappedMsg), QModelIndex());
194 
195     QModelIndex mappedPart = mappedMsg.child(0, 0);
196     QVERIFY(mappedPart.isValid());
197     QCOMPARE(mappedPart.data(Imap::Mailbox::RolePartPathToPart).toByteArray(), QByteArrayLiteral("/0"));
198 
199     cEmpty();
200     QVERIFY(errorSpy->isEmpty());
201 
202     // Add some custom structure to the given IMAP message
203     Cryptography::LocalMessagePart *localRoot = new Cryptography::LocalMessagePart(nullptr, 0, QByteArrayLiteral("multipart/mixed"));
204     Cryptography::LocalMessagePart *localText = new Cryptography::LocalMessagePart(localRoot, 0, QByteArrayLiteral("text/plain"));
205     localText->setData(QByteArrayLiteral("foobar"));
206     localRoot->setChild(0, Cryptography::MessagePart::Ptr(localText));
207     Cryptography::LocalMessagePart *localHtml = new Cryptography::LocalMessagePart(localRoot, 1, QByteArrayLiteral("text/html"));
208     localHtml->setData(QByteArrayLiteral("<html>foobar</html>"));
209     localRoot->setChild(1, Cryptography::MessagePart::Ptr(localHtml));
210 
211     msgModel.insertSubtree(mappedPart, Cryptography::MessagePart::Ptr(localRoot));
212 
213     QVERIFY(msgModel.rowCount(mappedPart) > 0);
214 
215     QModelIndex localRootIndex = mappedPart.child(0, 0);
216     QVERIFY(localRootIndex.isValid());
217     QCOMPARE(localRootIndex.data(Imap::Mailbox::RolePartMimeType), localRoot->data(Imap::Mailbox::RolePartMimeType));
218     QModelIndex localTextIndex = localRootIndex.child(0, 0);
219     QVERIFY(localTextIndex.isValid());
220     QCOMPARE(localTextIndex.data(Imap::Mailbox::RolePartMimeType), localText->data(Imap::Mailbox::RolePartMimeType));
221     QModelIndex localHtmlIndex = localRootIndex.child(1, 0);
222     QVERIFY(localHtmlIndex.isValid());
223     QCOMPARE(localHtmlIndex.data(Imap::Mailbox::RolePartMimeType), localHtml->data(Imap::Mailbox::RolePartMimeType));
224 }
225 
226 /** @short Verify that we can handle data from Mimetic and use them locally */
testLocalMimeParsing()227 void CryptographyMessageModelTest::testLocalMimeParsing()
228 {
229 #ifdef TROJITA_HAVE_MIMETIC
230     model->setProperty("trojita-imap-delayed-fetch-part", 0);
231     helperSyncBNoMessages();
232     cServer("* 1 EXISTS\r\n");
233     cClient(t.mk("UID FETCH 1:* (FLAGS)\r\n"));
234     cServer("* 1 FETCH (UID 333 FLAGS ())\r\n" + t.last("OK fetched\r\n"));
235     QCOMPARE(model->rowCount(msgListB), 1);
236     QModelIndex msg = msgListB.child(0, 0);
237     QVERIFY(msg.isValid());
238     QCOMPARE(model->rowCount(msg), 0);
239     cClient(t.mk("UID FETCH 333 (" FETCH_METADATA_ITEMS ")\r\n"));
240 
241     Cryptography::MessageModel msgModel(0, msg);
242     msgModel.registerPartHandler(std::make_shared<Cryptography::LocalMimeMessageParser>());
243 
244     const QByteArray bsTopLevelRfc822Message = QByteArrayLiteral(
245                 "\"messaGe\" \"rFc822\" NIL NIL NIL \"7bit\" 1511 (\"Thu, 8 Aug 2013 09:02:50 +0200\" "
246                 "\"Re: Your GSoC status\" ((\"Pali\" NIL \"pali.rohar\" \"gmail.com\")) "
247                 "((\"Pali\" NIL \"pali.rohar\" \"gmail.com\")) "
248                 "((\"Pali\" NIL \"pali.rohar\" \"gmail.com\")) ((\"Jan\" NIL \"jkt\" \"flaska.net\")) "
249                 "NIL NIL NIL \"<201308080902.51071@pali>\") "
250                 "((\"Text\" \"Plain\" (\"ChaRset\" \"uTf-8\") NIL NIL \"qUoted-printable\" 632 20 NIL NIL NIL NIL)"
251                 "(\"applicatioN\" \"pGp-signature\" (\"Name\" \"signature.asc\") NIL "
252                 "\"This is a digitally signed message part.\" \"7bit\" 205 NIL NIL NIL NIL) \"signed\" "
253                 "(\"boundary\" \"nextPart2106994.VznBGuL01i\" \"protocol\" \"application/pgp-signature\" \"micalg\" \"pgp-sha1\") "
254                 "NIL NIL NIL) 51 NIL NIL NIL NIL");
255 
256     cServer("* 1 FETCH (UID 333 BODYSTRUCTURE (" + bsTopLevelRfc822Message + "))\r\n" + t.last("OK fetched\r\n"));
257     cEmpty();
258     QVERIFY(model->rowCount(msg) > 0);
259     auto mappedMsg = msgModel.index(0,0);
260     QVERIFY(mappedMsg.isValid());
261     QVERIFY(msgModel.rowCount(mappedMsg) > 0);
262 
263     QPersistentModelIndex msgRoot = mappedMsg.child(0, 0);
264     QModelIndex formerMsgRoot = msgRoot;
265     QVERIFY(msgRoot.isValid());
266     QCOMPARE(msgRoot.data(Imap::Mailbox::RolePartPathToPart).toByteArray(),
267              QByteArrayLiteral("/0"));
268 
269     QCOMPARE(msgRoot.data(Imap::Mailbox::RolePartMimeType).toByteArray(), QByteArrayLiteral("message/rfc822"));
270     QCOMPARE(msgRoot.internalPointer(), formerMsgRoot.internalPointer());
271     QCOMPARE(msgModel.rowCount(msgRoot), 0);
272     cClientRegExp(t.mk("UID FETCH 333 \\(BODY\\.PEEK\\[1\\.(TEXT|HEADER)\\] BODY\\.PEEK\\[1\\.(TEXT|HEADER)\\]\\)"));
273     QByteArray myHeader = QByteArrayLiteral("Content-Type: mULTIpart/miXed; boundary=sep\r\n"
274                                             "MIME-Version: 1.0\r\n"
275                                             "Subject: =?ISO-8859-2?B?7Lno+L794e3p?=\r\n"
276                                             "From: =?utf-8?B?xJs=?= <1@example.org>\r\n"
277                                             "Sender: =?utf-8?B?xJq=?= <0@example.org>\r\n"
278                                             "To: =?utf-8?B?xJs=?= <2@example.org>, =?iso-8859-1?Q?=E1?= <3@example.org>\r\n"
279                                             "Cc: =?utf-8?B?xJz=?= <4@example.org>\r\n"
280                                             "reply-to: =?utf-8?B?xJm=?= <r@example.org>\r\n"
281                                             "BCC: =?utf-8?B?xJr=?= <5@example.org>\r\n\r\n");
282     QByteArray myBinaryBody = QByteArrayLiteral("This is the actual message body.\r\n");
283     QString myUnicode = QStringLiteral("Λέσβος");
284     QByteArray myBody = QByteArrayLiteral("preamble of a MIME message\r\n--sep\r\n"
285                                           "Content-Type: text/plain; charset=\"utf-8\"\r\nContent-Transfer-Encoding: base64\r\n\r\n")
286             + myUnicode.toUtf8().toBase64()
287             + QByteArrayLiteral("\r\n--sep\r\nContent-Type: pWned/NOw\r\n\r\n")
288             + myBinaryBody
289             + QByteArrayLiteral("\r\n--sep--\r\n");
290     cServer("* 1 FETCH (UID 333 BODY[1.TEXT] " + asLiteral(myBody) + " BODY[1.HEADER] " + asLiteral(myHeader) + ")\r\n"
291             + t.last("OK fetched\r\n"));
292 
293     // the part got replaced, so our QModelIndex should be invalid now
294     QVERIFY(msgRoot.internalPointer() != formerMsgRoot.internalPointer());
295 
296     QCOMPARE(msgModel.rowCount(msgRoot), 1);
297     QCOMPARE(msgRoot.data(Imap::Mailbox::RolePartMimeType).toByteArray(), QByteArrayLiteral("message/rfc822"));
298     QCOMPARE(msgRoot.data(Imap::Mailbox::RoleMessageSubject).toString(), QStringLiteral("ěščřžýáíé"));
299     QCOMPARE(msgRoot.data(Imap::Mailbox::RoleMessageSender),
300              QVariant(QVariantList() <<
301                       (QStringList() << QStringLiteral("Ě") << QString() << QStringLiteral("0") << QStringLiteral("example.org"))));
302     QCOMPARE(msgRoot.data(Imap::Mailbox::RoleMessageFrom),
303              QVariant(QVariantList() <<
304                       (QStringList() << QStringLiteral("ě") << QString() << QStringLiteral("1") << QStringLiteral("example.org"))));
305     QCOMPARE(msgRoot.data(Imap::Mailbox::RoleMessageTo),
306              QVariant(QVariantList() <<
307                       (QStringList() << QStringLiteral("ě") << QString() << QStringLiteral("2") << QStringLiteral("example.org")) <<
308                       (QStringList() << QStringLiteral("á") << QString() << QStringLiteral("3") << QStringLiteral("example.org"))));
309     QCOMPARE(msgRoot.data(Imap::Mailbox::RoleMessageCc),
310              QVariant(QVariantList() <<
311                       (QStringList() << QStringLiteral("Ĝ") << QString() << QStringLiteral("4") << QStringLiteral("example.org"))));
312     QCOMPARE(msgRoot.data(Imap::Mailbox::RoleMessageBcc),
313              QVariant(QVariantList() <<
314                       (QStringList() << QStringLiteral("Ě") << QString() << QStringLiteral("5") << QStringLiteral("example.org"))));
315     QCOMPARE(msgRoot.data(Imap::Mailbox::RoleMessageReplyTo),
316              QVariant(QVariantList() <<
317                       (QStringList() << QStringLiteral("ę") << QString() << QStringLiteral("r") << QStringLiteral("example.org"))));
318 
319     // NOTE: the OFFSET_MIME parts are not implemented; that's more or less on purpose because they aren't being used through
320     // the rest of the code so far.
321 
322     auto mHeader = msgRoot.child(0, Imap::Mailbox::TreeItem::OFFSET_HEADER);
323     QVERIFY(mHeader.isValid());
324     auto mText = msgRoot.child(0, Imap::Mailbox::TreeItem::OFFSET_TEXT);
325     QVERIFY(mText.isValid());
326     auto mMime = msgRoot.child(0, Imap::Mailbox::TreeItem::OFFSET_MIME);
327     QVERIFY(!mMime.isValid());
328     auto mRaw = msgRoot.child(0, Imap::Mailbox::TreeItem::OFFSET_RAW_CONTENTS);
329     QVERIFY(mRaw.isValid());
330     // We cannot compare them for an exact byte-equality because Mimetic apparently mangles the data a bit,
331     // for example there's an extra space after the comma in the To field in this case :(
332     QCOMPARE(mHeader.data(Imap::Mailbox::RolePartData).toByteArray().left(30), myHeader.left(30));
333     QCOMPARE(mHeader.data(Imap::Mailbox::RolePartData).toByteArray().right(4), QByteArrayLiteral("\r\n\r\n"));
334     QCOMPARE(mText.data(Imap::Mailbox::RolePartData).toByteArray(), myBody);
335 
336     // still that new C++11 toy, oh yeah :)
337     using bodyFldParam_t = std::result_of<decltype(&Imap::Mailbox::TreeItemPart::bodyFldParam)(Imap::Mailbox::TreeItemPart)>::type;
338     bodyFldParam_t expectedBodyFldParam;
339     QCOMPARE(msgRoot.data(Imap::Mailbox::RolePartBodyFldParam).value<bodyFldParam_t>(), expectedBodyFldParam);
340 
341     auto multipartIdx = msgRoot.child(0, 0);
342     QVERIFY(multipartIdx.isValid());
343     QCOMPARE(multipartIdx.data(Imap::Mailbox::RolePartMimeType).toByteArray(), QByteArrayLiteral("multipart/mixed"));
344     QCOMPARE(msgModel.rowCount(multipartIdx), 2);
345     expectedBodyFldParam.clear();
346     expectedBodyFldParam["BOUNDARY"] = "sep";
347     QCOMPARE(multipartIdx.data(Imap::Mailbox::RolePartBodyFldParam).value<bodyFldParam_t>(), expectedBodyFldParam);
348 
349     auto c1 = multipartIdx.child(0, 0);
350     QVERIFY(c1.isValid());
351     QCOMPARE(msgModel.rowCount(c1), 0);
352     QCOMPARE(c1.data(Imap::Mailbox::RolePartMimeType).toByteArray(), QByteArrayLiteral("text/plain"));
353     QCOMPARE(c1.data(Imap::Mailbox::RolePartData).toString(), myUnicode);
354     expectedBodyFldParam.clear();
355     expectedBodyFldParam["CHARSET"] = "utf-8";
356     QCOMPARE(c1.data(Imap::Mailbox::RolePartBodyFldParam).value<bodyFldParam_t>(), expectedBodyFldParam);
357 
358     auto c1raw = c1.child(0, Imap::Mailbox::TreeItem::OFFSET_RAW_CONTENTS);
359     QVERIFY(c1raw.isValid());
360     QCOMPARE(c1raw.data(Imap::Mailbox::RolePartData).toByteArray(), myUnicode.toUtf8().toBase64());
361     QVERIFY(!c1.child(0, Imap::Mailbox::TreeItem::OFFSET_HEADER).isValid());
362     QVERIFY(!c1.child(0, Imap::Mailbox::TreeItem::OFFSET_TEXT).isValid());
363     QVERIFY(!c1.child(0, Imap::Mailbox::TreeItem::OFFSET_MIME).isValid());
364 
365     auto c2 = multipartIdx.child(1, 0);
366     QVERIFY(c2.isValid());
367     QCOMPARE(msgModel.rowCount(c2), 0);
368     QCOMPARE(c2.data(Imap::Mailbox::RolePartMimeType).toByteArray(), QByteArrayLiteral("pwned/now"));
369     QCOMPARE(c2.data(Imap::Mailbox::RolePartData).toByteArray(), myBinaryBody);
370 
371     auto c2raw = c2.child(0, Imap::Mailbox::TreeItem::OFFSET_RAW_CONTENTS);
372     QVERIFY(c2raw.isValid());
373     QCOMPARE(c2raw.data(Imap::Mailbox::RolePartData).toByteArray(), myBinaryBody);
374     QVERIFY(!c2.child(0, Imap::Mailbox::TreeItem::OFFSET_HEADER).isValid());
375     QVERIFY(!c2.child(0, Imap::Mailbox::TreeItem::OFFSET_TEXT).isValid());
376     QVERIFY(!c2.child(0, Imap::Mailbox::TreeItem::OFFSET_MIME).isValid());
377 
378     cEmpty();
379     QVERIFY(errorSpy->isEmpty());
380 #else
381     QSKIP("Mimetic not available, cannot test LocalMimeMessageParser");
382 #endif
383 }
384 
testDelayedLoading()385 void CryptographyMessageModelTest::testDelayedLoading()
386 {
387     model->setProperty("trojita-imap-delayed-fetch-part", 0);
388     helperSyncBNoMessages();
389     cServer("* 1 EXISTS\r\n");
390     cClient(t.mk("UID FETCH 1:* (FLAGS)\r\n"));
391     cServer("* 1 FETCH (UID 333 FLAGS ())\r\n" + t.last("OK fetched\r\n"));
392     QCOMPARE(model->rowCount(msgListB), 1);
393     QModelIndex msg = msgListB.child(0, 0);
394     QVERIFY(msg.isValid());
395 
396     Cryptography::MessageModel msgModel(0, msg);
397     msgModel.setObjectName("msgModel");
398 
399     cEmpty();
400     QCOMPARE(msgModel.rowCount(QModelIndex()), 0);
401     cClient(t.mk("UID FETCH 333 (" FETCH_METADATA_ITEMS ")\r\n"));
402     cServer("* 1 FETCH (UID 333 BODYSTRUCTURE (" + bsPlaintext + "))\r\n" + t.last("OK fetched\r\n"));
403     cEmpty();
404     QCOMPARE(model->rowCount(msg), 1);
405     QCOMPARE(msgModel.rowCount(QModelIndex()), 1);
406     auto mappedMsg = msgModel.index(0,0);
407     QVERIFY(mappedMsg.isValid());
408 
409     QPersistentModelIndex msgRoot = mappedMsg.child(0, 0);
410     QVERIFY(msgRoot.isValid());
411     QCOMPARE(msgRoot.data(Imap::Mailbox::RolePartPathToPart).toByteArray(),
412              QByteArrayLiteral("/0"));
413 }
414 
415 QTEST_GUILESS_MAIN(CryptographyMessageModelTest)
416