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: 95.6% 131 / 0 / 137
Functions: 100.0% 9 / 0 / 9
Branches: 95.1% 212 / 20 / 243

data/ExternalContentAllowlist.cpp
Line Branch Exec Source
1 #include "data/ExternalContentAllowlist.h"
2
3 #include "data/MailCacheSql.h"
4 #include <QLoggingCategory>
5 #include <QSqlError>
6 #include <QSqlQuery>
7 #include <QVariant>
8
9 // Same category as MailCache (defined there), see docs/LOGGING.md.
10 Q_DECLARE_LOGGING_CATEGORY(lcCache)
11
12 using MailCacheSql::beginWriteTransaction;
13
14 5035 ExternalContentAllowlist::ExternalContentAllowlist(QSqlDatabase &db, QString &lastError,
15 5035 std::function<void()> whitelistChanged)
16 5035 : m_db(db), m_lastError(lastError), m_whitelistChanged(std::move(whitelistChanged)) {}
17
18 42 bool ExternalContentAllowlist::addWhitelistEntry(const QString &type, const QString &value) {
19 // S106-044: the same validation as replaceWhitelistEntries — the header
20 // contract promised it, the code inserted any type/value verbatim.
21
12/14
✓ Branch 6 → 7 taken 21 times.
✓ Branch 6 → 13 taken 21 times.
✓ Branch 11 → 12 taken 2 times.
✓ Branch 11 → 13 taken 19 times.
✓ Branch 14 → 15 taken 21 times.
✓ Branch 14 → 16 taken 21 times.
✓ Branch 16 → 17 taken 21 times.
✓ Branch 16 → 18 taken 21 times.
✓ Branch 18 → 19 taken 42 times.
✗ Branch 18 → 20 not taken.
✓ Branch 20 → 21 taken 42 times.
✗ Branch 20 → 22 not taken.
✓ Branch 22 → 23 taken 2 times.
✓ Branch 22 → 43 taken 40 times.
105 if (type != QStringLiteral("sender") && type != QStringLiteral("domain")) {
22
1/1
✓ Branch 27 → 28 taken 2 times.
4 m_lastError = QStringLiteral("Invalid whitelist entry type: %1").arg(type);
23
5/5
✓ Branch 32 → 33 taken 2 times.
✓ Branch 36 → 37 taken 2 times.
✓ Branch 37 → 38 taken 2 times.
✓ Branch 41 → 34 taken 2 times.
✓ Branch 41 → 42 taken 2 times.
4 qCWarning(lcCache) << m_lastError;
24 2 return false;
25 }
26
3/3
✓ Branch 43 → 44 taken 40 times.
✓ Branch 46 → 47 taken 1 time.
✓ Branch 46 → 64 taken 39 times.
40 if (value.trimmed().isEmpty()) {
27 1 m_lastError = QStringLiteral("Invalid empty whitelist entry value");
28
5/5
✓ Branch 53 → 54 taken 1 time.
✓ Branch 57 → 58 taken 1 time.
✓ Branch 58 → 59 taken 1 time.
✓ Branch 62 → 55 taken 1 time.
✓ Branch 62 → 63 taken 1 time.
2 qCWarning(lcCache) << m_lastError;
29 1 return false;
30 }
31
1/1
✓ Branch 64 → 65 taken 39 times.
39 QSqlQuery q(m_db);
32
1/1
✓ Branch 68 → 69 taken 39 times.
39 q.prepare(QStringLiteral(
33 "INSERT OR IGNORE INTO external_content_whitelist (type, value) "
34 "VALUES (:type, :value)"));
35
1/1
✓ Branch 76 → 77 taken 39 times.
78 q.bindValue(QStringLiteral(":type"), type);
36
2/2
✓ Branch 81 → 82 taken 39 times.
✓ Branch 86 → 87 taken 39 times.
78 q.bindValue(QStringLiteral(":value"), value.toLower());
37
38
3/3
✓ Branch 91 → 92 taken 39 times.
✓ Branch 92 → 93 taken 4 times.
✓ Branch 92 → 110 taken 35 times.
39 if (!q.exec()) {
39
2/2
✓ Branch 93 → 94 taken 4 times.
✓ Branch 94 → 95 taken 4 times.
4 m_lastError = q.lastError().text();
40
6/6
✓ Branch 98 → 99 taken 4 times.
✓ Branch 102 → 103 taken 4 times.
✓ Branch 103 → 104 taken 4 times.
✓ Branch 104 → 105 taken 4 times.
✓ Branch 108 → 100 taken 4 times.
✓ Branch 108 → 109 taken 4 times.
8 qCWarning(lcCache) << "Failed to add whitelist entry:" << m_lastError;
41 4 return false;
42 }
43
4/4
✓ Branch 110 → 111 taken 35 times.
✓ Branch 111 → 112 taken 31 times.
✓ Branch 111 → 113 taken 4 times.
✓ Branch 112 → 113 taken 31 times.
35 if (q.numRowsAffected() > 0) m_whitelistChanged();
44
1/1
✓ Branch 113 → 114 taken 35 times.
35 return q.numRowsAffected() > 0;
45 39 }
46
47 13 bool ExternalContentAllowlist::removeWhitelistEntry(qint64 id) {
48
1/1
✓ Branch 2 → 3 taken 13 times.
13 QSqlQuery q(m_db);
49
1/1
✓ Branch 6 → 7 taken 13 times.
13 q.prepare(QStringLiteral(
50 "DELETE FROM external_content_whitelist WHERE id = :id"));
51
1/1
✓ Branch 14 → 15 taken 13 times.
26 q.bindValue(QStringLiteral(":id"), id);
52
53
3/3
✓ Branch 18 → 19 taken 13 times.
✓ Branch 19 → 20 taken 4 times.
✓ Branch 19 → 37 taken 9 times.
13 if (!q.exec()) {
54
2/2
✓ Branch 20 → 21 taken 4 times.
✓ Branch 21 → 22 taken 4 times.
4 m_lastError = q.lastError().text();
55
6/6
✓ Branch 25 → 26 taken 4 times.
✓ Branch 29 → 30 taken 4 times.
✓ Branch 30 → 31 taken 4 times.
✓ Branch 31 → 32 taken 4 times.
✓ Branch 35 → 27 taken 4 times.
✓ Branch 35 → 36 taken 4 times.
8 qCWarning(lcCache) << "Failed to remove whitelist entry:" << m_lastError;
56 4 return false;
57 }
58
4/4
✓ Branch 37 → 38 taken 9 times.
✓ Branch 38 → 39 taken 7 times.
✓ Branch 38 → 40 taken 2 times.
✓ Branch 39 → 40 taken 7 times.
9 if (q.numRowsAffected() > 0) m_whitelistChanged();
59
1/1
✓ Branch 40 → 41 taken 9 times.
9 return q.numRowsAffected() > 0;
60 13 }
61
62 // T-313: Remove all whitelist entries (for settings sync apply)
63 7 void ExternalContentAllowlist::clearWhitelist() {
64
1/1
✓ Branch 2 → 3 taken 7 times.
7 QSqlQuery q(m_db);
65
3/3
✓ Branch 6 → 7 taken 7 times.
✓ Branch 9 → 10 taken 4 times.
✓ Branch 9 → 27 taken 3 times.
7 if (!q.exec(QStringLiteral(
66 "DELETE FROM external_content_whitelist"))) {
67
2/2
✓ Branch 10 → 11 taken 4 times.
✓ Branch 11 → 12 taken 4 times.
4 m_lastError = q.lastError().text();
68
6/6
✓ Branch 15 → 16 taken 4 times.
✓ Branch 19 → 20 taken 4 times.
✓ Branch 20 → 21 taken 4 times.
✓ Branch 21 → 22 taken 4 times.
✓ Branch 25 → 17 taken 4 times.
✓ Branch 25 → 26 taken 4 times.
8 qCWarning(lcCache) << "Failed to clear whitelist:" << m_lastError;
69 } else {
70
4/4
✓ Branch 27 → 28 taken 3 times.
✓ Branch 28 → 29 taken 2 times.
✓ Branch 28 → 30 taken 1 time.
✓ Branch 29 → 30 taken 2 times.
3 if (q.numRowsAffected() > 0) m_whitelistChanged();
71
7/7
✓ Branch 30 → 31 taken 3 times.
✓ Branch 34 → 35 taken 3 times.
✓ Branch 35 → 36 taken 3 times.
✓ Branch 36 → 37 taken 3 times.
✓ Branch 37 → 38 taken 3 times.
✓ Branch 42 → 32 taken 3 times.
✓ Branch 42 → 43 taken 3 times.
6 qCInfo(lcCache) << "Cleared whitelist (" << q.numRowsAffected()
72
1/1
✓ Branch 38 → 39 taken 3 times.
3 << "entries)";
73 }
74 7 }
75
76 14 bool ExternalContentAllowlist::replaceWhitelistEntries(
77 const QList<QPair<QString, QString>> &entries) {
78
2/2
✓ Branch 75 → 4 taken 17 times.
✓ Branch 75 → 76 taken 11 times.
28 for (const auto &entry : entries) {
79 17 const QString type = entry.first;
80
12/14
✓ Branch 10 → 11 taken 7 times.
✓ Branch 10 → 17 taken 10 times.
✓ Branch 15 → 16 taken 2 times.
✓ Branch 15 → 17 taken 5 times.
✓ Branch 18 → 19 taken 7 times.
✓ Branch 18 → 20 taken 10 times.
✓ Branch 20 → 21 taken 7 times.
✓ Branch 20 → 22 taken 10 times.
✓ Branch 22 → 23 taken 17 times.
✗ Branch 22 → 24 not taken.
✓ Branch 24 → 25 taken 17 times.
✗ Branch 24 → 26 not taken.
✓ Branch 26 → 27 taken 2 times.
✓ Branch 26 → 47 taken 15 times.
41 if (type != QStringLiteral("sender") && type != QStringLiteral("domain")) {
81
1/1
✓ Branch 31 → 32 taken 2 times.
4 m_lastError = QStringLiteral("Invalid whitelist entry type: %1").arg(type);
82 4 qCWarning(lcCache) << m_lastError; // GCOVR_EXCL_BR_LINE — Qt logging-macro cleanup only; validation failure remains measured.
83 2 return false;
84 }
85
3/3
✓ Branch 47 → 48 taken 15 times.
✓ Branch 50 → 51 taken 1 time.
✓ Branch 50 → 68 taken 14 times.
15 if (entry.second.trimmed().isEmpty()) {
86 1 m_lastError = QStringLiteral("Invalid empty whitelist entry value");
87
5/5
✓ Branch 57 → 58 taken 1 time.
✓ Branch 61 → 62 taken 1 time.
✓ Branch 62 → 63 taken 1 time.
✓ Branch 66 → 59 taken 1 time.
✓ Branch 66 → 67 taken 1 time.
2 qCWarning(lcCache) << m_lastError;
88 1 return false;
89 }
90 17 }
91
92
3/3
✓ Branch 76 → 77 taken 11 times.
✓ Branch 77 → 78 taken 3 times.
✓ Branch 77 → 95 taken 8 times.
11 if (!beginWriteTransaction(m_db)) {
93
2/2
✓ Branch 78 → 79 taken 3 times.
✓ Branch 79 → 80 taken 3 times.
3 m_lastError = m_db.lastError().text();
94 6 qCWarning(lcCache) << "Failed to start whitelist transaction:" // GCOVR_EXCL_BR_LINE — Qt logging-macro cleanup only; transaction result remains measured.
95
1/1
✓ Branch 89 → 90 taken 3 times.
3 << m_lastError;
96 3 return false;
97 }
98
99
1/1
✓ Branch 95 → 96 taken 8 times.
8 QSqlQuery clear(m_db);
100
3/3
✓ Branch 99 → 100 taken 8 times.
✓ Branch 102 → 103 taken 2 times.
✓ Branch 102 → 121 taken 6 times.
8 if (!clear.exec(QStringLiteral("DELETE FROM external_content_whitelist"))) {
101
2/2
✓ Branch 103 → 104 taken 2 times.
✓ Branch 104 → 105 taken 2 times.
2 m_lastError = clear.lastError().text();
102
1/1
✓ Branch 108 → 109 taken 2 times.
2 m_db.rollback();
103
5/5
✓ Branch 109 → 110 taken 2 times.
✓ Branch 113 → 114 taken 2 times.
✓ Branch 114 → 115 taken 2 times.
✓ Branch 119 → 111 taken 2 times.
✓ Branch 119 → 120 taken 2 times.
4 qCWarning(lcCache) << "Failed to clear whitelist in transaction:"
104
1/1
✓ Branch 115 → 116 taken 2 times.
2 << m_lastError;
105 2 return false;
106 }
107
108
1/1
✓ Branch 121 → 122 taken 6 times.
6 QSqlQuery insert(m_db);
109
1/1
✓ Branch 125 → 126 taken 6 times.
6 insert.prepare(QStringLiteral(
110 "INSERT OR IGNORE INTO external_content_whitelist (type, value) "
111 "VALUES (:type, :value)"));
112
2/2
✓ Branch 175 → 130 taken 8 times.
✓ Branch 175 → 176 taken 5 times.
13 for (const auto &entry : entries) {
113
1/1
✓ Branch 136 → 137 taken 8 times.
16 insert.bindValue(QStringLiteral(":type"), entry.first);
114
3/3
✓ Branch 141 → 142 taken 8 times.
✓ Branch 142 → 143 taken 8 times.
✓ Branch 147 → 148 taken 8 times.
16 insert.bindValue(QStringLiteral(":value"), entry.second.trimmed().toLower());
115
3/3
✓ Branch 153 → 154 taken 8 times.
✓ Branch 154 → 155 taken 1 time.
✓ Branch 154 → 173 taken 7 times.
8 if (!insert.exec()) {
116
2/2
✓ Branch 155 → 156 taken 1 time.
✓ Branch 156 → 157 taken 1 time.
1 m_lastError = insert.lastError().text();
117
1/1
✓ Branch 160 → 161 taken 1 time.
1 m_db.rollback();
118 2 qCWarning(lcCache) << "Failed to insert whitelist entry in transaction:" // GCOVR_EXCL_BR_LINE — Qt logging-macro cleanup only; insert result remains measured.
119
1/1
✓ Branch 167 → 168 taken 1 time.
1 << m_lastError;
120 1 return false;
121 }
122 }
123
124
2/3
✓ Branch 176 → 177 taken 5 times.
✗ Branch 177 → 178 not taken.
✓ Branch 177 → 196 taken 5 times.
5 if (!m_db.commit()) {
125 ✗ m_lastError = m_db.lastError().text();
126 ✗ m_db.rollback();
127 ✗ qCWarning(lcCache) << "Failed to commit whitelist transaction:" // GCOVR_EXCL_BR_LINE — Qt logging-macro cleanup only; commit result remains measured.
128 ✗ << m_lastError;
129 ✗ return false;
130 }
131
132
7/7
✓ Branch 196 → 197 taken 5 times.
✓ Branch 200 → 201 taken 5 times.
✓ Branch 201 → 202 taken 5 times.
✓ Branch 203 → 204 taken 5 times.
✓ Branch 204 → 205 taken 5 times.
✓ Branch 208 → 198 taken 5 times.
✓ Branch 208 → 209 taken 5 times.
10 qCInfo(lcCache) << "Replaced whitelist with" << entries.size() << "entries";
133
1/1
✓ Branch 209 → 210 taken 5 times.
5 m_whitelistChanged();
134 5 return true;
135 8 }
136
137 834 QList<WhitelistEntry> ExternalContentAllowlist::whitelistEntries() const {
138 834 QList<WhitelistEntry> result;
139
1/1
✓ Branch 2 → 3 taken 834 times.
834 QSqlQuery q(m_db);
140
1/1
✓ Branch 6 → 7 taken 834 times.
834 q.exec(QStringLiteral(
141 "SELECT id, type, value, created_at "
142 "FROM external_content_whitelist ORDER BY created_at DESC"));
143
144
3/3
✓ Branch 31 → 32 taken 874 times.
✓ Branch 32 → 10 taken 40 times.
✓ Branch 32 → 33 taken 834 times.
874 while (q.next()) {
145 40 WhitelistEntry e;
146
2/2
✓ Branch 10 → 11 taken 40 times.
✓ Branch 11 → 12 taken 40 times.
40 e.id = q.value(0).toLongLong();
147
2/2
✓ Branch 13 → 14 taken 40 times.
✓ Branch 14 → 15 taken 40 times.
40 e.type = q.value(1).toString();
148
2/2
✓ Branch 18 → 19 taken 40 times.
✓ Branch 19 → 20 taken 40 times.
40 e.value = q.value(2).toString();
149
2/2
✓ Branch 23 → 24 taken 40 times.
✓ Branch 24 → 25 taken 40 times.
40 e.createdAt = q.value(3).toString();
150
1/1
✓ Branch 28 → 29 taken 40 times.
40 result.append(e);
151 40 }
152 834 return result;
153 834 }
154
155 15 bool ExternalContentAllowlist::isWhitelisted(const QString &senderEmail) const {
156
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 15 times.
15 if (senderEmail.isEmpty())
157 ✗ return false;
158
159
1/1
✓ Branch 5 → 6 taken 15 times.
15 QString email = senderEmail.toLower();
160
161 // Check sender match
162
1/1
✓ Branch 6 → 7 taken 15 times.
15 QSqlQuery q(m_db);
163
1/1
✓ Branch 10 → 11 taken 15 times.
15 q.prepare(QStringLiteral(
164 "SELECT 1 FROM external_content_whitelist "
165 "WHERE type = 'sender' AND value = :email"));
166
1/1
✓ Branch 18 → 19 taken 15 times.
30 q.bindValue(QStringLiteral(":email"), email);
167
8/8
✓ Branch 22 → 23 taken 15 times.
✓ Branch 23 → 24 taken 13 times.
✓ Branch 23 → 27 taken 2 times.
✓ Branch 24 → 25 taken 13 times.
✓ Branch 25 → 26 taken 5 times.
✓ Branch 25 → 27 taken 8 times.
✓ Branch 28 → 29 taken 5 times.
✓ Branch 28 → 30 taken 10 times.
15 if (q.exec() && q.next())
168 5 return true;
169
170 // Check domain match
171 10 int atPos = email.indexOf('@');
172
1/2
✓ Branch 32 → 33 taken 10 times.
✗ Branch 32 → 63 not taken.
10 if (atPos >= 0) {
173
1/1
✓ Branch 33 → 34 taken 10 times.
10 QString domain = email.mid(atPos + 1);
174
1/1
✓ Branch 37 → 38 taken 10 times.
10 q.prepare(QStringLiteral(
175 "SELECT 1 FROM external_content_whitelist "
176 "WHERE type = 'domain' AND value = :domain"));
177
1/1
✓ Branch 45 → 46 taken 10 times.
20 q.bindValue(QStringLiteral(":domain"), domain);
178
8/8
✓ Branch 49 → 50 taken 10 times.
✓ Branch 50 → 51 taken 8 times.
✓ Branch 50 → 54 taken 2 times.
✓ Branch 51 → 52 taken 8 times.
✓ Branch 52 → 53 taken 3 times.
✓ Branch 52 → 54 taken 5 times.
✓ Branch 55 → 56 taken 3 times.
✓ Branch 55 → 57 taken 7 times.
10 if (q.exec() && q.next())
179 3 return true;
180 10 }
181 7 return false;
182 15 }
183
184 8 QStringList ExternalContentAllowlist::whitelistedDomains() const {
185 8 QStringList result;
186
1/1
✓ Branch 2 → 3 taken 8 times.
8 QSqlQuery q(m_db);
187
1/1
✓ Branch 6 → 7 taken 8 times.
8 q.exec(QStringLiteral(
188 "SELECT value FROM external_content_whitelist WHERE type = 'domain'"));
189
3/3
✓ Branch 16 → 17 taken 13 times.
✓ Branch 17 → 10 taken 5 times.
✓ Branch 17 → 18 taken 8 times.
13 while (q.next())
190
3/3
✓ Branch 10 → 11 taken 5 times.
✓ Branch 11 → 12 taken 5 times.
✓ Branch 12 → 13 taken 5 times.
5 result.append(q.value(0).toString());
191 8 return result;
192 8 }
193
194 5 QStringList ExternalContentAllowlist::whitelistedSenders() const {
195 5 QStringList result;
196
1/1
✓ Branch 2 → 3 taken 5 times.
5 QSqlQuery q(m_db);
197
1/1
✓ Branch 6 → 7 taken 5 times.
5 q.exec(QStringLiteral(
198 "SELECT value FROM external_content_whitelist WHERE type = 'sender'"));
199
3/3
✓ Branch 16 → 17 taken 9 times.
✓ Branch 17 → 10 taken 4 times.
✓ Branch 17 → 18 taken 5 times.
9 while (q.next())
200
3/3
✓ Branch 10 → 11 taken 4 times.
✓ Branch 11 → 12 taken 4 times.
✓ Branch 12 → 13 taken 4 times.
4 result.append(q.value(0).toString());
201 5 return result;
202 5 }
203