MailJDTourDownloadDocsQualityTestsCoverage

Which source lines and branches the tests execute. Lines 91.5 %, branches 75.0 % (minimum 85 % and 72 %). main @ 399ffe3d · 21 October 2026 · pipeline 7238.

GCC Code Coverage Report


Directory: src/
Coverage: low: ≥ 0% medium: ≥ 75.0% high: ≥ 90.0%
Coverage Exec / Excl / Total
Lines: 96.9% 63 / 0 / 65
Functions: 100.0% 6 / 0 / 6
Branches: 62.0% 119 / 0 / 192

presentation/QuickInvitationMail.cpp
Line Branch Exec Source
1 #include "QuickInvitation.h"
2 #include "ComposeRecipients.h"
3 #include "app/core/MailSessionCore.h"
4 #include "data/ICalendar.h"
5 #include "data/ImipInvitation.h"
6 #include "data/MailCache.h"
7 #include "service/MailPayloadExportService.h"
8 #include "service/MimeParser.h"
9 #include <QLoggingCategory>
10
11 Q_DECLARE_LOGGING_CATEGORY(lcInvitation)
12
13 namespace {
14 // iMIP parts are small; a bound keeps a hostile attachment out of memory.
15 constexpr qint64 kMaxCalendarBytes = 1024 * 1024;
16
17 1461 QString singleSender(const QString &header) {
18
1/1
✓ Branch 2 → 3 taken 1461 times.
1461 const auto parsed = ComposeRecipients::parse(header);
19
4/5
✓ Branch 4 → 5 taken 1461 times.
✗ Branch 4 → 12 not taken.
✓ Branch 7 → 8 taken 1036 times.
✓ Branch 7 → 12 taken 425 times.
✓ Branch 10 → 11 taken 1036 times.
2922 return parsed && parsed->envelope.size() == 1 ? Imip::addressOf(parsed->envelope.first()) : QString();
20 1461 }
21
22 25 bool isCalendarAttachment(const QVariantMap &attachment) {
23
3/3
✓ Branch 6 → 7 taken 25 times.
✓ Branch 7 → 8 taken 25 times.
✓ Branch 8 → 9 taken 25 times.
50 const QString type = attachment.value(QStringLiteral("contentType")).toString().toLower();
24
3/3
✓ Branch 18 → 19 taken 25 times.
✓ Branch 19 → 20 taken 25 times.
✓ Branch 20 → 21 taken 25 times.
50 const QString name = attachment.value(QStringLiteral("name")).toString().toLower();
25
4/5
✓ Branch 27 → 28 taken 25 times.
✓ Branch 28 → 29 taken 21 times.
✓ Branch 28 → 35 taken 4 times.
✓ Branch 31 → 32 taken 21 times.
✗ Branch 31 → 35 not taken.
46 return type.startsWith(QLatin1String("text/calendar")) || type == QLatin1String("application/ics") ||
26
2/3
✓ Branch 33 → 34 taken 21 times.
✗ Branch 34 → 35 not taken.
✓ Branch 34 → 36 taken 21 times.
46 name.endsWith(QLatin1String(".ics"));
27 25 }
28
29 } // namespace
30
31 6851 void QuickInvitation::setMail(const MailId &mail, const QVariantList &attachments, const QString &from) {
32 6851 qint64 attachmentId = 0;
33
2/2
✓ Branch 25 → 4 taken 25 times.
✓ Branch 25 → 26 taken 6847 times.
6872 for (const QVariant &value : attachments) {
34
1/1
✓ Branch 5 → 6 taken 25 times.
25 const QVariantMap attachment = value.toMap();
35
3/3
✓ Branch 6 → 7 taken 25 times.
✓ Branch 7 → 8 taken 21 times.
✓ Branch 7 → 9 taken 4 times.
25 if (!isCalendarAttachment(attachment)) continue;
36
2/2
✓ Branch 13 → 14 taken 4 times.
✓ Branch 14 → 15 taken 4 times.
8 attachmentId = attachment.value(QStringLiteral("id")).toLongLong();
37 4 break;
38 25 }
39
8/8
✓ Branch 27 → 28 taken 5452 times.
✓ Branch 27 → 32 taken 1399 times.
✓ Branch 28 → 29 taken 5449 times.
✓ Branch 28 → 32 taken 3 times.
✓ Branch 30 → 31 taken 5391 times.
✓ Branch 30 → 32 taken 58 times.
✓ Branch 33 → 34 taken 5391 times.
✓ Branch 33 → 35 taken 1460 times.
8308 if (mail == m_mail && attachmentId == m_attachmentId && from == m_mailFrom) return;
40 1460 m_mail = mail;
41 1460 m_mailFrom = from;
42
1/1
✓ Branch 37 → 38 taken 1460 times.
1460 m_mailSender = singleSender(from);
43 1460 m_attachmentId = attachmentId;
44 1460 const quint64 generation = ++m_readGeneration;
45
2/2
✓ Branch 40 → 41 taken 1460 times.
✓ Branch 41 → 42 taken 1460 times.
1460 MailPayloadExportService::shared()->cancel(this);
46
3/3
✓ Branch 43 → 44 taken 4 times.
✓ Branch 43 → 45 taken 1456 times.
✓ Branch 44 → 45 taken 4 times.
1460 if (m_invitation) clear();
47 1460 auto *cache = m_session.cache();
48
7/8
✓ Branch 47 → 48 taken 1226 times.
✓ Branch 47 → 50 taken 234 times.
✓ Branch 48 → 49 taken 3 times.
✓ Branch 48 → 50 taken 1223 times.
✗ Branch 49 → 50 not taken.
✓ Branch 49 → 51 taken 3 times.
✓ Branch 52 → 53 taken 1457 times.
✓ Branch 52 → 54 taken 3 times.
1460 if (!mail.isValid() || attachmentId <= 0 || !cache) return;
49 9 MailPayloadRequest request{cache->databasePath(), mail, cache->uidValidity(mail.folderId),
50
1/5
✓ Branch 56 → 57 taken 3 times.
✗ Branch 90 → 91 not taken.
✗ Branch 90 → 92 not taken.
✗ Branch 93 → 94 not taken.
✗ Branch 93 → 95 not taken.
3 MailPayloadRequest::Kind::Attachment, attachmentId, 0, kMaxCalendarBytes};
51 // S106-343: the header block of a real invitation can pass 16 KiB (long
52 // Received chains, DKIM/ARC sets); a cut block made the sender "unknown".
53 3 constexpr qint64 kHeaderWindowBytes = 256 * 1024;
54 9 const MailPayloadRequest headers{cache->databasePath(), mail, cache->uidValidity(mail.folderId),
55
1/5
✓ Branch 59 → 60 taken 3 times.
✗ Branch 96 → 97 not taken.
✗ Branch 96 → 98 not taken.
✗ Branch 99 → 100 not taken.
✗ Branch 99 → 101 not taken.
3 MailPayloadRequest::Kind::RawSource, 0, 0, kHeaderWindowBytes};
56
3/3
✓ Branch 60 → 61 taken 3 times.
✓ Branch 62 → 63 taken 3 times.
✓ Branch 64 → 65 taken 3 times.
6 MailPayloadExportService::shared()->readWindow(this, headers,
57 6 [this, generation, request](QByteArray prefix, MailPayloadReadResult headerResult) {
58
1/2
✗ Branch 2 → 3 not taken.
✓ Branch 2 → 4 taken 3 times.
3 if (generation != m_readGeneration) return;
59
3/4
✓ Branch 6 → 7 taken 2 times.
✓ Branch 6 → 10 taken 1 time.
✗ Branch 9 → 10 not taken.
✓ Branch 9 → 11 taken 2 times.
3 const bool completeHeader = prefix.contains("\r\n\r\n") || prefix.contains("\n\n");
60 3 m_mailSender.clear();
61
3/4
✓ Branch 13 → 14 taken 1 time.
✓ Branch 13 → 48 taken 2 times.
✓ Branch 14 → 15 taken 1 time.
✗ Branch 14 → 48 not taken.
3 if (headerResult.completed && completeHeader) {
62
1/1
✓ Branch 15 → 16 taken 1 time.
1 const auto fields = MimeParser::messageHeaders(prefix);
63
2/2
✓ Branch 19 → 20 taken 1 time.
✓ Branch 30 → 31 taken 1 time.
4 m_mailSender = singleSender(fields.contains(QStringLiteral("sender"))
64
7/24
✓ Branch 20 → 21 taken 1 time.
✗ Branch 20 → 26 not taken.
✓ Branch 25 → 30 taken 1 time.
✗ Branch 29 → 30 not taken.
✗ Branch 34 → 35 not taken.
✓ Branch 34 → 36 taken 1 time.
✗ Branch 36 → 37 not taken.
✓ Branch 36 → 38 taken 1 time.
✓ Branch 38 → 39 taken 1 time.
✗ Branch 38 → 40 not taken.
✓ Branch 40 → 41 taken 1 time.
✗ Branch 40 → 42 not taken.
✓ Branch 42 → 43 taken 1 time.
✗ Branch 42 → 44 not taken.
✗ Branch 58 → 59 not taken.
✗ Branch 58 → 60 not taken.
✗ Branch 62 → 63 not taken.
✗ Branch 62 → 64 not taken.
✗ Branch 66 → 67 not taken.
✗ Branch 66 → 68 not taken.
✗ Branch 70 → 71 not taken.
✗ Branch 70 → 72 not taken.
✗ Branch 74 → 75 not taken.
✗ Branch 74 → 76 not taken.
4 ? fields.value(QStringLiteral("sender")) : fields.value(QStringLiteral("from"), m_mailFrom));
65 1 }
66
1/1
✓ Branch 51 → 52 taken 3 times.
6 MailPayloadExportService::shared()->readWindow(this, request,
67 6 [this, generation](QByteArray bytes, MailPayloadReadResult result) {
68
1/2
✗ Branch 2 → 3 not taken.
✓ Branch 2 → 4 taken 3 times.
3 if (generation != m_readGeneration) return;
69
1/2
✗ Branch 4 → 5 not taken.
✓ Branch 4 → 17 taken 3 times.
3 if (!result.completed) {
70 ✗ qCWarning(lcInvitation) << "Calendar attachment unreadable:" << result.error;
71 ✗ return;
72 }
73
2/2
✓ Branch 17 → 18 taken 3 times.
✓ Branch 18 → 19 taken 3 times.
3 showCalendarText(QString::fromUtf8(bytes));
74 });
75 });
76 3 }
77
78 // S108.8.7 (D-24, client mode): an answer to a meeting the user organizes
79 // updates the attendee's state without a click — only when the stored
80 // event is organized by one of the user's addresses, the mail's sender is
81 // the replier (trust "ok"), the answer is not older than the stored
82 // SEQUENCE and MailJD (not the server) delivers the meeting. With server
83 // scheduling the server already recorded it (the card says so).
84 3 void QuickInvitation::applyReplyAutomatically() {
85
1/1
✓ Branch 2 → 3 taken 3 times.
3 const auto stored = storedCopy();
86
11/26
✓ Branch 4 → 5 taken 3 times.
✗ Branch 4 → 21 not taken.
✓ Branch 10 → 11 taken 3 times.
✓ Branch 11 → 12 taken 3 times.
✓ Branch 13 → 14 taken 2 times.
✓ Branch 13 → 21 taken 1 time.
✓ Branch 33 → 34 taken 3 times.
✗ Branch 33 → 35 not taken.
✓ Branch 35 → 36 taken 3 times.
✗ Branch 35 → 37 not taken.
✓ Branch 37 → 38 taken 3 times.
✗ Branch 37 → 39 not taken.
✓ Branch 39 → 40 taken 3 times.
✗ Branch 39 → 41 not taken.
✓ Branch 41 → 42 taken 1 time.
✓ Branch 41 → 43 taken 2 times.
✗ Branch 121 → 122 not taken.
✗ Branch 121 → 123 not taken.
✗ Branch 125 → 126 not taken.
✗ Branch 125 → 127 not taken.
✗ Branch 129 → 130 not taken.
✗ Branch 129 → 131 not taken.
✗ Branch 133 → 134 not taken.
✗ Branch 133 → 135 not taken.
✗ Branch 137 → 138 not taken.
✗ Branch 137 → 139 not taken.
11 if (!stored || m_card.value(QStringLiteral("trust")).toString() != QLatin1String("ok") ||
87
12/22
✓ Branch 18 → 19 taken 2 times.
✓ Branch 19 → 20 taken 2 times.
✗ Branch 20 → 21 not taken.
✓ Branch 20 → 22 taken 2 times.
✓ Branch 23 → 24 taken 2 times.
✓ Branch 23 → 25 taken 1 time.
✓ Branch 25 → 26 taken 2 times.
✓ Branch 25 → 27 taken 1 time.
✓ Branch 27 → 28 taken 2 times.
✓ Branch 27 → 29 taken 1 time.
✓ Branch 29 → 30 taken 2 times.
✓ Branch 29 → 31 taken 1 time.
✓ Branch 31 → 32 taken 3 times.
✗ Branch 31 → 33 not taken.
✗ Branch 105 → 106 not taken.
✗ Branch 105 → 107 not taken.
✗ Branch 109 → 110 not taken.
✗ Branch 109 → 111 not taken.
✗ Branch 113 → 114 not taken.
✗ Branch 113 → 115 not taken.
✗ Branch 117 → 118 not taken.
✗ Branch 117 → 119 not taken.
7 m_card.value(QStringLiteral("replyApplied")).toBool())
88 1 return;
89 const QString organizer =
90
3/3
✓ Branch 47 → 48 taken 2 times.
✓ Branch 51 → 52 taken 2 times.
✓ Branch 52 → 53 taken 2 times.
4 Imip::addressOf(ICal::parseFirst(stored->master.icalRaw, QStringLiteral("VEVENT")).value(QStringLiteral("ORGANIZER")));
91
5/11
✓ Branch 60 → 61 taken 2 times.
✗ Branch 60 → 64 not taken.
✓ Branch 61 → 62 taken 2 times.
✗ Branch 63 → 64 not taken.
✓ Branch 63 → 65 taken 2 times.
✓ Branch 66 → 67 taken 2 times.
✗ Branch 66 → 68 not taken.
✗ Branch 68 → 69 not taken.
✓ Branch 68 → 70 taken 2 times.
✗ Branch 160 → 161 not taken.
✗ Branch 160 → 162 not taken.
2 if (organizer.isEmpty() || !ownAddresses().contains(organizer)) return;
92
2/3
✓ Branch 71 → 72 taken 2 times.
✗ Branch 72 → 73 not taken.
✓ Branch 72 → 74 taken 2 times.
2 if (serverSends(stored->accountId)) return;
93
3/3
✓ Branch 76 → 77 taken 2 times.
✓ Branch 77 → 78 taken 1 time.
✓ Branch 77 → 91 taken 1 time.
2 if (m_invitation->sequence < Imip::sequenceOf(stored->master.icalRaw)) {
94
6/6
✓ Branch 78 → 79 taken 1 time.
✓ Branch 82 → 83 taken 1 time.
✓ Branch 83 → 84 taken 1 time.
✓ Branch 85 → 86 taken 1 time.
✓ Branch 89 → 80 taken 1 time.
✓ Branch 89 → 90 taken 1 time.
2 qCInfo(lcInvitation) << "Reply older than the stored meeting; not applied" << m_invitation->uid;
95 1 return;
96 }
97
1/1
✓ Branch 91 → 92 taken 1 time.
1 m_replyAutoApplied = applyReply(false);
98
2/3
✓ Branch 92 → 93 taken 1 time.
✗ Branch 92 → 94 not taken.
✓ Branch 93 → 94 taken 1 time.
1 if (m_replyAutoApplied) rebuildCard();
99 4 }
100