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: 100.0% 3 / 0 / 3
Functions: 100.0% 3 / 0 / 3
Branches: 57.1% 4 / 0 / 7

service/CardDavClient.h
Line Branch Exec Source
1 #pragma once
2
3 #include <QHash>
4 #include <QList>
5 #include <QNetworkReply>
6 #include <QObject>
7 #include <functional>
8
9 #include "data/ContactModels.h"
10 #include "service/CardDavXml.h"
11 #include "service/DavResponse.h"
12
13 class QNetworkAccessManager;
14 class QDomElement;
15
16
17 // CardDAV client (Sprint 97 S97.1.3 CONTACT-DISCOVERY, S97.1.5
18 // CONTACT-SYNC-INCR, write requests for the ContactOutbox).
19 //
20 // Discovery like CalDAV (RFC 6352 §7.1, RFC 6764): the configured URL
21 // itself, then /.well-known/carddav → current-user-principal →
22 // addressbook-home-set → the books in the home (PROPFIND Depth 1 with
23 // display name, CTag, sync-token, privileges, supported-address-data and
24 // max-resource-size); the Nextcloud path /remote.php/dav/addressbooks/
25 // users/<user>/ stays as the last fallback.
26 //
27 // Sync never downloads a whole book in one answer: fetchBook() reads the
28 // book's properties (one request; an unchanged sync-token or CTag ends the
29 // run there), listChanges() asks sync-collection for ETags only (or
30 // listEtags() PROPFINDs them on servers without RFC 6578), and multiget()
31 // fetches changed cards in batches. Every answer stays within
32 // DavNetworkLimits; a batch that is too large is reported as such so the
33 // caller can split it.
34 class CardDavClient : public QObject {
35 Q_OBJECT
36
37 public:
38 CardDavClient(const QString &serverUrl, const QString &username, const QString &password,
39 QObject *parent = nullptr);
40
41 void discoverAddressBooks();
42 void fetchBook(const QString &bookPath);
43 void listChanges(const QString &bookPath, const QString &syncToken);
44 void listEtags(const QString &bookPath);
45 void multiget(const QString &bookPath, const QStringList &hrefs);
46
47 // Raw resource requests; each returns a request id echoed in
48 // resourceResponse(). An invalid If-Match tag is refused locally (status 0).
49 quint64 putResource(const QString &url, const QByteArray &body, const QString &ifMatch, bool createOnly);
50 quint64 deleteResource(const QString &url, const QString &ifMatch);
51 quint64 getResource(const QString &url);
52 // <book>/<name>.vcf for a new card; `name` is a fresh UUID, never a
53 // foreign UID (S97.1.4).
54 QString newResourceUrl(const QString &bookPath, const QString &name) const;
55 QString resolveDavUrl(const QString &href) const;
56
57 void setNetworkAccessManager(QNetworkAccessManager *nam);
58 // Sprint 96: the own push registration of the book the next PUT/DELETE
59 // writes to ("" = none). Sent as Push-Dont-Notify, never "*".
60 13 void setPushDontNotify(const QString &registrationUrl) { m_pushDontNotify = registrationUrl; }
61 1 QNetworkReply::NetworkError lastNetworkError() const { return m_lastNetworkError; }
62
63 signals:
64 void addressBooksDiscovered(const QList<AddressBookInfo> &books);
65 void bookFetched(const QString &bookPath, const AddressBookInfo &book);
66 void changesListed(const QString &bookPath, const DavCardChanges &changes);
67 // The server has no sync-collection (invalidToken = false) or no longer
68 // accepts the token (true).
69 void collectionSyncRejected(const QString &bookPath, bool invalidToken);
70 void etagsListed(const QString &bookPath, const QHash<QString, QString> &etags);
71 void cardsFetched(const QString &bookPath, const QList<DavCardResource> &cards, const QStringList &missing);
72 // tooLarge: the answer hit DavNetworkLimits — split the batch.
73 void multigetFailed(const QString &bookPath, const QStringList &hrefs, const QString &error, bool tooLarge);
74 void syncFailed(const QString &error);
75 void resourceResponse(const DavResponse &response);
76 #ifdef MAILJD_E2E_TESTING
77 // Metadata only: operation class and an opaque per-client request token.
78 void e2eTransportPhase(const QString &phase, quint64 requestToken);
79 #endif
80
81 private:
82 enum class DiscoveryStep { ConfiguredCollection, WellKnown, Principal, Home, NextcloudFallback };
83
84 QNetworkReply *send(const QByteArray &verb, const QString &url, const QByteArray &depth, const QByteArray &body,
85 const QString &operation);
86 void requestDiscovery(const QString &url, DiscoveryStep step);
87 void advanceDiscovery(const QString &reason);
88 void onDiscoverReply(QNetworkReply *reply);
89 QList<AddressBookInfo> booksIn(const QByteArray &xml) const;
90 AddressBookInfo bookFrom(const QDomElement &response) const;
91 // Common failure handling; false when the reply failed (signal emitted).
92 bool replyOk(QNetworkReply *reply, const QString &what);
93 quint64 sendResourceRequest(const QByteArray &method, const QString &url, const QByteArray &body,
94 const QList<QPair<QByteArray, QByteArray>> &headers);
95 QByteArray authHeader() const;
96 #ifdef MAILJD_E2E_TESTING
97 void reportE2eTransportPhase(const QString &operation);
98 #endif
99
100 QNetworkAccessManager *m_nam = nullptr;
101 QString m_serverUrl;
102 QString m_username;
103 QString m_password;
104 DiscoveryStep m_discoveryStep = DiscoveryStep::ConfiguredCollection;
105 int m_discoveryRedirects = 0;
106 QString m_discoveryError;
107 QNetworkReply::NetworkError m_lastNetworkError = QNetworkReply::NoError;
108 quint64 m_nextResourceRequest = 0;
109 bool m_pushPropertiesRejected = false; // retry fetchBook without push properties
110 QString m_pushDontNotify;
111 #ifdef MAILJD_E2E_TESTING
112 quint64 m_e2eRequestToken = 0;
113 #endif
114 };
115
116
4/7
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 1 time.
✓ Branch 8 → 9 taken 1 time.
✗ Branch 8 → 14 not taken.
✓ Branch 9 → 10 taken 1 time.
✓ Branch 10 → 11 taken 1 time.
✗ Branch 14 → 15 not taken.
1 Q_DECLARE_METATYPE(DavCardChanges)
117