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% 124 / 0 / 128
Functions: 100.0% 9 / 0 / 9
Branches: 76.3% 225 / 0 / 295

service/CardDavXml.cpp
Line Branch Exec Source
1 #include "CardDavXml.h"
2
3 #include "DavXmlHelper.h"
4
5 #include <QDomDocument>
6 #include <QUrl>
7
8 using namespace DavXmlHelper;
9
10 namespace CardDavXml {
11
12 namespace {
13
14 22134 int effectivePort(const QUrl &url) {
15 22134 const int port = url.port();
16
2/2
✓ Branch 3 → 4 taken 136 times.
✓ Branch 3 → 5 taken 21998 times.
22134 if (port >= 0)
17 136 return port;
18
3/3
✓ Branch 5 → 6 taken 21998 times.
✓ Branch 10 → 11 taken 21996 times.
✓ Branch 10 → 12 taken 2 times.
43996 return url.scheme().compare(QStringLiteral("https"), Qt::CaseInsensitive) == 0 ? 443 : 80;
19 }
20
21 30 bool hasChildNamed(const QDomElement &parent, const QString &localName) {
22
2/2
✓ Branch 2 → 3 taken 30 times.
✓ Branch 3 → 4 taken 30 times.
30 return !findFirstElementByLocalName(parent, localName).isNull();
23 }
24
25 // Response-level or propstat-level status line of a multistatus response.
26 5302 QString statusOf(const QDomElement &response) {
27
6/6
✓ Branch 4 → 5 taken 5302 times.
✓ Branch 17 → 18 taken 10604 times.
✓ Branch 18 → 19 taken 10604 times.
✓ Branch 23 → 24 taken 15906 times.
✓ Branch 24 → 8 taken 10604 times.
✓ Branch 24 → 25 taken 5302 times.
15906 for (QDomElement child = response.firstChildElement(); !child.isNull(); child = child.nextSiblingElement())
28
2/3
✓ Branch 9 → 10 taken 10604 times.
✗ Branch 12 → 13 not taken.
✓ Branch 12 → 15 taken 10604 times.
10604 if (localNameOf(child) == QLatin1String("status"))
29
1/3
✗ Branch 13 → 14 not taken.
✓ Branch 27 → 28 taken 5302 times.
✗ Branch 27 → 30 not taken.
5302 return child.text();
30 5302 return {};
31 }
32
33 } // namespace
34
35 11069 QString resolveHref(const QString &serverUrl, const QString &href) {
36
1/1
✓ Branch 2 → 3 taken 11069 times.
11069 const QUrl server(serverUrl);
37 11069 QUrl base = server;
38
3/4
✓ Branch 5 → 6 taken 11069 times.
✓ Branch 8 → 9 taken 11069 times.
✓ Branch 10 → 11 taken 11069 times.
✗ Branch 10 → 20 not taken.
11069 if (!base.path().endsWith(QLatin1Char('/')))
39
3/3
✓ Branch 14 → 15 taken 11069 times.
✓ Branch 15 → 16 taken 11069 times.
✓ Branch 16 → 17 taken 11069 times.
22138 base.setPath(base.path() + QLatin1Char('/'));
40
1/1
✓ Branch 20 → 21 taken 11069 times.
11069 const QUrl candidate(href);
41
4/4
✓ Branch 21 → 22 taken 11069 times.
✓ Branch 22 → 23 taken 10892 times.
✓ Branch 22 → 24 taken 177 times.
✓ Branch 23 → 25 taken 10892 times.
11069 const QUrl resolved = candidate.isRelative() ? base.resolved(candidate) : candidate;
42
8/14
✓ Branch 27 → 28 taken 11069 times.
✓ Branch 28 → 29 taken 11069 times.
✓ Branch 30 → 31 taken 11069 times.
✗ Branch 30 → 48 not taken.
✓ Branch 36 → 37 taken 11067 times.
✓ Branch 36 → 48 taken 2 times.
✓ Branch 60 → 61 taken 11069 times.
✗ Branch 60 → 62 not taken.
✓ Branch 62 → 63 taken 3 times.
✓ Branch 62 → 64 taken 11066 times.
✗ Branch 108 → 109 not taken.
✗ Branch 108 → 110 not taken.
✗ Branch 112 → 113 not taken.
✗ Branch 112 → 114 not taken.
44276 if (!resolved.isValid() || resolved.scheme().compare(server.scheme(), Qt::CaseInsensitive) != 0 ||
43
4/10
✓ Branch 32 → 33 taken 11069 times.
✓ Branch 34 → 35 taken 11069 times.
✓ Branch 56 → 57 taken 11069 times.
✗ Branch 56 → 58 not taken.
✓ Branch 58 → 59 taken 11069 times.
✗ Branch 58 → 60 not taken.
✗ Branch 98 → 99 not taken.
✗ Branch 98 → 100 not taken.
✗ Branch 103 → 104 not taken.
✗ Branch 103 → 105 not taken.
33207 resolved.host().compare(server.host(), Qt::CaseInsensitive) != 0 ||
44
9/14
✓ Branch 25 → 26 taken 11069 times.
✓ Branch 26 → 27 taken 11069 times.
✗ Branch 26 → 48 not taken.
✓ Branch 37 → 38 taken 11067 times.
✓ Branch 38 → 39 taken 11067 times.
✓ Branch 39 → 40 taken 11067 times.
✗ Branch 39 → 48 not taken.
✓ Branch 41 → 42 taken 11067 times.
✓ Branch 43 → 44 taken 11066 times.
✓ Branch 43 → 48 taken 1 time.
✓ Branch 54 → 55 taken 11069 times.
✗ Branch 54 → 56 not taken.
✗ Branch 93 → 94 not taken.
✗ Branch 93 → 95 not taken.
55340 effectivePort(resolved) != effectivePort(server) || !resolved.userName().isEmpty() ||
45
6/9
✓ Branch 45 → 46 taken 11066 times.
✗ Branch 47 → 48 not taken.
✓ Branch 47 → 49 taken 11066 times.
✓ Branch 50 → 51 taken 11066 times.
✓ Branch 50 → 52 taken 3 times.
✓ Branch 52 → 53 taken 11067 times.
✓ Branch 52 → 54 taken 2 times.
✗ Branch 88 → 89 not taken.
✗ Branch 88 → 90 not taken.
22135 !resolved.password().isEmpty())
46 3 return {};
47
1/1
✓ Branch 65 → 66 taken 11066 times.
11066 return resolved.toString();
48 11069 }
49
50 96 AddressBookInfo book(const QDomElement &response) {
51 96 AddressBookInfo book;
52
3/3
✓ Branch 6 → 7 taken 96 times.
✓ Branch 7 → 8 taken 96 times.
✓ Branch 8 → 9 taken 96 times.
96 book.path = findFirstElementByLocalName(response, QStringLiteral("href")).text().trimmed();
53
3/3
✓ Branch 18 → 19 taken 96 times.
✓ Branch 19 → 20 taken 96 times.
✓ Branch 20 → 21 taken 96 times.
96 book.displayName = findFirstElementByLocalName(response, QStringLiteral("displayname")).text().trimmed();
54
3/3
✓ Branch 30 → 31 taken 96 times.
✓ Branch 31 → 32 taken 96 times.
✓ Branch 32 → 33 taken 96 times.
96 book.ctag = findFirstElementByLocalName(response, QStringLiteral("getctag")).text().trimmed();
55
3/3
✓ Branch 42 → 43 taken 96 times.
✓ Branch 43 → 44 taken 96 times.
✓ Branch 44 → 45 taken 96 times.
96 const QString token = findFirstElementByLocalName(response, QStringLiteral("sync-token")).text().trimmed();
56
1/1
✓ Branch 52 → 53 taken 96 times.
96 const QDomElement reports = findFirstElementByLocalName(response, QStringLiteral("supported-report-set"));
57
6/16
✓ Branch 56 → 57 taken 22 times.
✓ Branch 56 → 64 taken 74 times.
✓ Branch 57 → 58 taken 22 times.
✗ Branch 58 → 59 not taken.
✓ Branch 58 → 65 taken 22 times.
✗ Branch 62 → 63 not taken.
✗ Branch 63 → 64 not taken.
✗ Branch 63 → 65 not taken.
✗ Branch 66 → 67 not taken.
✓ Branch 66 → 68 taken 96 times.
✗ Branch 68 → 69 not taken.
✓ Branch 68 → 70 taken 96 times.
✗ Branch 202 → 203 not taken.
✗ Branch 202 → 204 not taken.
✗ Branch 206 → 207 not taken.
✗ Branch 206 → 208 not taken.
96 book.supportsSyncCollection = !token.isEmpty() || (!reports.isNull() && hasChildNamed(reports, QStringLiteral("sync-collection")));
58 // The server's current token, compared with the stored one by the caller.
59 96 book.syncToken = token;
60
1/1
✓ Branch 71 → 72 taken 96 times.
96 book.readOnly = readOnlyFromPrivileges(response);
61
1/1
✓ Branch 75 → 76 taken 96 times.
96 const QDomElement data = findFirstElementByLocalName(response, QStringLiteral("supported-address-data"));
62
6/6
✓ Branch 80 → 81 taken 96 times.
✓ Branch 103 → 104 taken 22 times.
✓ Branch 104 → 105 taken 22 times.
✓ Branch 109 → 110 taken 118 times.
✓ Branch 110 → 84 taken 22 times.
✓ Branch 110 → 111 taken 96 times.
118 for (QDomElement type = data.firstChildElement(); !type.isNull(); type = type.nextSiblingElement()) {
63
1/1
✓ Branch 88 → 89 taken 22 times.
44 const QString version = type.attribute(QStringLiteral("version"));
64
3/6
✓ Branch 93 → 94 taken 22 times.
✗ Branch 93 → 97 not taken.
✓ Branch 95 → 96 taken 22 times.
✗ Branch 95 → 97 not taken.
✓ Branch 98 → 99 taken 22 times.
✗ Branch 98 → 100 not taken.
22 if (!version.isEmpty() && !book.supportedVersions.contains(version))
65
1/1
✓ Branch 99 → 100 taken 22 times.
22 book.supportedVersions << version;
66 118 }
67
4/4
✓ Branch 115 → 116 taken 96 times.
✓ Branch 116 → 117 taken 96 times.
✓ Branch 117 → 118 taken 96 times.
✓ Branch 118 → 119 taken 96 times.
96 book.maxResourceSize = findFirstElementByLocalName(response, QStringLiteral("max-resource-size")).text().trimmed().toLongLong();
68
2/2
✓ Branch 125 → 126 taken 1 time.
✓ Branch 125 → 140 taken 95 times.
96 if (book.displayName.isEmpty()) {
69 1 QString name = book.path;
70
2/3
✓ Branch 129 → 130 taken 1 time.
✓ Branch 130 → 131 taken 1 time.
✗ Branch 130 → 132 not taken.
1 if (name.endsWith(QLatin1Char('/')))
71
1/1
✓ Branch 131 → 132 taken 1 time.
1 name.chop(1);
72
1/1
✓ Branch 135 → 136 taken 1 time.
1 book.displayName = name.mid(name.lastIndexOf(QLatin1Char('/')) + 1);
73 1 }
74 96 return book;
75 96 }
76
77 20 QList<AddressBookInfo> parseBooks(const QByteArray &xml) {
78 20 QList<AddressBookInfo> books;
79
1/1
✓ Branch 2 → 3 taken 20 times.
20 QDomDocument doc;
80
2/3
✓ Branch 3 → 4 taken 20 times.
✗ Branch 4 → 5 not taken.
✓ Branch 4 → 6 taken 20 times.
20 if (!DavXmlHelper::parseInto(doc, xml))
81 ✗ return books;
82
1/1
✓ Branch 9 → 10 taken 20 times.
20 const auto responses = findElementsByLocalNameDoc(doc, QStringLiteral("response"));
83
2/2
✓ Branch 54 → 13 taken 23 times.
✓ Branch 54 → 55 taken 20 times.
43 for (int i = 0; i < responses.count(); ++i) {
84
2/2
✓ Branch 13 → 14 taken 23 times.
✓ Branch 14 → 15 taken 23 times.
23 const QDomElement response = responses.at(i).toElement();
85
1/1
✓ Branch 19 → 20 taken 23 times.
23 const QDomElement type = findFirstElementByLocalName(response, QStringLiteral("resourcetype"));
86
9/16
✓ Branch 22 → 23 taken 23 times.
✓ Branch 23 → 24 taken 23 times.
✗ Branch 23 → 29 not taken.
✓ Branch 27 → 28 taken 23 times.
✓ Branch 28 → 29 taken 2 times.
✓ Branch 28 → 30 taken 21 times.
✓ Branch 31 → 32 taken 23 times.
✗ Branch 31 → 33 not taken.
✓ Branch 33 → 34 taken 23 times.
✗ Branch 33 → 35 not taken.
✓ Branch 35 → 36 taken 2 times.
✓ Branch 35 → 37 taken 21 times.
✗ Branch 75 → 76 not taken.
✗ Branch 75 → 77 not taken.
✗ Branch 79 → 80 not taken.
✗ Branch 79 → 81 not taken.
46 if (type.isNull() || !hasChildNamed(type, QStringLiteral("addressbook")))
87 2 continue;
88
1/1
✓ Branch 37 → 38 taken 21 times.
21 const AddressBookInfo found = book(response);
89
1/2
✓ Branch 39 → 40 taken 21 times.
✗ Branch 39 → 41 not taken.
21 if (!found.path.isEmpty())
90
1/1
✓ Branch 40 → 41 taken 21 times.
21 books.append(found);
91 25 }
92 20 return books;
93 20 }
94
95 51 bool parseChanges(const QByteArray &xml, const Resolve &resolve, DavCardChanges *changes) {
96 51 QStringList removed;
97
1/1
✓ Branch 2 → 3 taken 51 times.
51 QDomDocument doc;
98
5/8
✓ Branch 3 → 4 taken 51 times.
✓ Branch 4 → 5 taken 51 times.
✗ Branch 4 → 7 not taken.
✓ Branch 5 → 6 taken 51 times.
✗ Branch 6 → 7 not taken.
✓ Branch 6 → 8 taken 51 times.
✗ Branch 9 → 10 not taken.
✓ Branch 9 → 11 taken 51 times.
51 if (!parseSyncCollectionStatus(xml, &removed, &changes->syncToken) || !DavXmlHelper::parseInto(doc, xml))
99 ✗ return false;
100
4/4
✓ Branch 11 → 12 taken 51 times.
✓ Branch 12 → 13 taken 51 times.
✓ Branch 22 → 14 taken 6 times.
✓ Branch 22 → 23 taken 51 times.
57 for (const QString &href : removed) {
101
1/1
✓ Branch 15 → 16 taken 6 times.
6 const QString resolved = resolve(href);
102
1/2
✓ Branch 17 → 18 taken 6 times.
✗ Branch 17 → 19 not taken.
6 if (!resolved.isEmpty())
103
1/1
✓ Branch 18 → 19 taken 6 times.
6 changes->removed << resolved;
104 6 }
105
1/1
✓ Branch 26 → 27 taken 51 times.
51 const auto responses = findElementsByLocalNameDoc(doc, QStringLiteral("response"));
106
2/2
✓ Branch 110 → 30 taken 5319 times.
✓ Branch 110 → 111 taken 51 times.
5370 for (int i = 0; i < responses.count(); ++i) {
107
2/2
✓ Branch 30 → 31 taken 5319 times.
✓ Branch 31 → 32 taken 5319 times.
5319 const QDomElement response = responses.at(i).toElement();
108 // F-35 (SPRINT-104): a collection-level 507 marks the answer as
109 // truncated (RFC 6578 §3.6) — the client must continue from the
110 // returned token instead of treating the page as the whole book.
111
1/1
✓ Branch 36 → 37 taken 5319 times.
5319 const auto statuses = findElementsByLocalName(response, QStringLiteral("status"));
112
2/2
✓ Branch 52 → 40 taken 5318 times.
✓ Branch 52 → 53 taken 5319 times.
10637 for (int s = 0; s < statuses.count(); ++s)
113
6/6
✓ Branch 40 → 41 taken 5318 times.
✓ Branch 41 → 42 taken 5318 times.
✓ Branch 42 → 43 taken 5318 times.
✓ Branch 44 → 45 taken 5318 times.
✓ Branch 48 → 49 taken 4 times.
✓ Branch 48 → 50 taken 5314 times.
5318 if (statuses.at(s).toElement().text().contains(QLatin1String(" 507")))
114 4 changes->truncated = true;
115
4/4
✓ Branch 56 → 57 taken 5319 times.
✓ Branch 57 → 58 taken 5319 times.
✓ Branch 58 → 59 taken 5319 times.
✓ Branch 59 → 60 taken 5319 times.
5319 const QString href = resolve(findFirstElementByLocalName(response, QStringLiteral("href")).text().trimmed());
116
3/3
✓ Branch 68 → 69 taken 5319 times.
✓ Branch 69 → 70 taken 5319 times.
✓ Branch 70 → 71 taken 5319 times.
5319 const QString etag = findFirstElementByLocalName(response, QStringLiteral("getetag")).text().trimmed();
117
9/11
✓ Branch 76 → 77 taken 5319 times.
✗ Branch 76 → 85 not taken.
✓ Branch 78 → 79 taken 5309 times.
✓ Branch 78 → 85 taken 10 times.
✓ Branch 81 → 82 taken 5309 times.
✓ Branch 82 → 83 taken 5308 times.
✓ Branch 82 → 85 taken 1 time.
✗ Branch 84 → 85 not taken.
✓ Branch 84 → 86 taken 5308 times.
✓ Branch 87 → 88 taken 11 times.
✓ Branch 87 → 89 taken 5308 times.
5319 if (href.isEmpty() || etag.isEmpty() || href.endsWith(QLatin1Char('/')) || changes->removed.contains(href))
118 11 continue;
119
1/1
✓ Branch 89 → 90 taken 5308 times.
5308 changes->changed.insert(href, etag);
120 5352 }
121 51 return true;
122 51 }
123
124 3 bool parseEtags(const QByteArray &xml, const Resolve &resolve, QHash<QString, QString> *etags) {
125
1/1
✓ Branch 2 → 3 taken 3 times.
3 QDomDocument doc;
126
2/3
✓ Branch 3 → 4 taken 3 times.
✗ Branch 4 → 5 not taken.
✓ Branch 4 → 6 taken 3 times.
3 if (!DavXmlHelper::parseInto(doc, xml))
127 ✗ return false;
128
1/1
✓ Branch 9 → 10 taken 3 times.
3 const auto responses = findElementsByLocalNameDoc(doc, QStringLiteral("response"));
129
2/2
✓ Branch 85 → 13 taken 7 times.
✓ Branch 85 → 86 taken 3 times.
10 for (int i = 0; i < responses.count(); ++i) {
130
2/2
✓ Branch 13 → 14 taken 7 times.
✓ Branch 14 → 15 taken 7 times.
7 const QDomElement response = responses.at(i).toElement();
131
1/1
✓ Branch 19 → 20 taken 7 times.
7 const QDomElement type = findFirstElementByLocalName(response, QStringLiteral("resourcetype"));
132
9/16
✓ Branch 22 → 23 taken 7 times.
✓ Branch 23 → 24 taken 7 times.
✗ Branch 23 → 30 not taken.
✓ Branch 27 → 28 taken 7 times.
✓ Branch 28 → 29 taken 3 times.
✓ Branch 28 → 30 taken 4 times.
✓ Branch 31 → 32 taken 7 times.
✗ Branch 31 → 33 not taken.
✓ Branch 33 → 34 taken 7 times.
✗ Branch 33 → 35 not taken.
✓ Branch 35 → 36 taken 3 times.
✓ Branch 35 → 37 taken 4 times.
✗ Branch 106 → 107 not taken.
✗ Branch 106 → 108 not taken.
✗ Branch 110 → 111 not taken.
✗ Branch 110 → 112 not taken.
14 if (!type.isNull() && hasChildNamed(type, QStringLiteral("collection")))
133 3 continue;
134
4/4
✓ Branch 40 → 41 taken 4 times.
✓ Branch 41 → 42 taken 4 times.
✓ Branch 42 → 43 taken 4 times.
✓ Branch 43 → 44 taken 4 times.
4 const QString href = resolve(findFirstElementByLocalName(response, QStringLiteral("href")).text().trimmed());
135
3/3
✓ Branch 52 → 53 taken 4 times.
✓ Branch 53 → 54 taken 4 times.
✓ Branch 54 → 55 taken 4 times.
4 const QString etag = findFirstElementByLocalName(response, QStringLiteral("getetag")).text().trimmed();
136
5/9
✓ Branch 60 → 61 taken 4 times.
✗ Branch 60 → 68 not taken.
✓ Branch 62 → 63 taken 4 times.
✗ Branch 62 → 68 not taken.
✓ Branch 65 → 66 taken 4 times.
✓ Branch 66 → 67 taken 4 times.
✗ Branch 66 → 68 not taken.
✓ Branch 69 → 70 taken 4 times.
✗ Branch 69 → 71 not taken.
4 if (!href.isEmpty() && !etag.isEmpty() && !href.endsWith(QLatin1Char('/')))
137
1/1
✓ Branch 70 → 71 taken 4 times.
4 etags->insert(href, etag);
138 10 }
139 3 return true;
140 3 }
141
142 90 bool parseCards(const QByteArray &xml, const Resolve &resolve, QList<DavCardResource> *cards,
143 QSet<QString> *answeredPaths) {
144
1/1
✓ Branch 2 → 3 taken 90 times.
90 QDomDocument doc;
145
2/3
✓ Branch 3 → 4 taken 90 times.
✗ Branch 4 → 5 not taken.
✓ Branch 4 → 6 taken 90 times.
90 if (!DavXmlHelper::parseInto(doc, xml))
146 ✗ return false;
147
1/1
✓ Branch 9 → 10 taken 90 times.
90 const auto responses = findElementsByLocalNameDoc(doc, QStringLiteral("response"));
148
2/2
✓ Branch 85 → 13 taken 5303 times.
✓ Branch 85 → 86 taken 90 times.
5393 for (int i = 0; i < responses.count(); ++i) {
149
2/2
✓ Branch 13 → 14 taken 5303 times.
✓ Branch 14 → 15 taken 5303 times.
5303 const QDomElement response = responses.at(i).toElement();
150 5303 DavCardResource card;
151
4/4
✓ Branch 19 → 20 taken 5303 times.
✓ Branch 20 → 21 taken 5303 times.
✓ Branch 21 → 22 taken 5303 times.
✓ Branch 22 → 23 taken 5303 times.
5303 card.href = resolve(findFirstElementByLocalName(response, QStringLiteral("href")).text().trimmed());
152
3/3
✓ Branch 33 → 34 taken 5303 times.
✓ Branch 34 → 35 taken 5303 times.
✓ Branch 35 → 36 taken 5303 times.
5303 card.etag = findFirstElementByLocalName(response, QStringLiteral("getetag")).text().trimmed();
153
2/2
✓ Branch 45 → 46 taken 5303 times.
✓ Branch 46 → 47 taken 5303 times.
5303 card.raw = findFirstElementByLocalName(response, QStringLiteral("address-data")).text();
154
10/14
✓ Branch 53 → 54 taken 5303 times.
✗ Branch 53 → 60 not taken.
✓ Branch 55 → 56 taken 5302 times.
✓ Branch 55 → 60 taken 1 time.
✓ Branch 56 → 57 taken 5302 times.
✓ Branch 58 → 59 taken 5302 times.
✗ Branch 59 → 60 not taken.
✓ Branch 59 → 61 taken 5302 times.
✓ Branch 62 → 63 taken 5302 times.
✓ Branch 62 → 64 taken 1 time.
✓ Branch 64 → 65 taken 1 time.
✓ Branch 64 → 66 taken 5302 times.
✗ Branch 140 → 141 not taken.
✗ Branch 140 → 142 not taken.
5303 if (card.href.isEmpty() || card.raw.isEmpty() || statusOf(response).contains(QLatin1String(" 404")))
155 1 continue;
156 // Decoded paths: servers re-encode hrefs differently.
157
3/3
✓ Branch 66 → 67 taken 5302 times.
✓ Branch 68 → 69 taken 5302 times.
✓ Branch 69 → 70 taken 5302 times.
10604 answeredPaths->insert(QUrl(card.href).path());
158
1/1
✓ Branch 72 → 73 taken 5302 times.
5302 cards->append(card);
159 5304 }
160 90 return true;
161 90 }
162
163 } // namespace CardDavXml
164