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% 6 / 0 / 6
Functions: 100.0% 6 / 0 / 6
Branches: -% 0 / 0 / 0

app/core/DavPushAccount.h
Line Branch Exec Source
1 #pragma once
2
3 #include <QElapsedTimer>
4 #include <QHash>
5 #include <QObject>
6 #include <QPointer>
7 #include <QSet>
8 #include <QTimer>
9 #include <functional>
10 #include <optional>
11
12 #include "CalendarAccountSync.h"
13 #include "DavPushSecrets.h"
14 #include "data/DavPushSettings.h"
15 #include "data/DavPushStore.h"
16 #include "service/DavPushClient.h"
17 #include "service/PushReceiver.h"
18
19 class DavPushStatistics;
20
21 // Sprint 96 S96.2.4: DAV-Push of one DAV account — its receiver (one push
22 // channel per account and installation), its key material and the
23 // registrations of its collections. The coordinator computes the desired
24 // state; apply() converges towards it.
25 //
26 // Lifecycle contract (SPRINT-96.md §3.3):
27 // - Endpoint and keys are prepared durably (keyring) before any POST; a
28 // lost local commit after a confirmed POST re-registers with the same
29 // endpoint (the server updates instead of duplicating).
30 // - After a successful registration a catch-up sync of that collection is
31 // requested (closes the gap since the last sync).
32 // - Renewal at 75 % of the confirmed lifetime with earlier-only jitter;
33 // failures back off (1 min doubling to 1 h; Retry-After honoured).
34 // - A replaced receiver (server change, lost secret) registers again and
35 // then removes the old registration; foreign registrations are never
36 // touched (only own Location URLs are ever deleted).
37 // - Every async callback is bound to the generation it was started in;
38 // stale answers overwrite neither state nor secrets.
39 class DavPushAccount final : public QObject {
40 1 Q_OBJECT
41 public:
42 struct Context {
43 DavPushStore *store = nullptr;
44 DavPushStatistics *stats = nullptr;
45 DavPushSecretStore *secrets = nullptr;
46 QString installationId;
47 // provider: DavPushSettings::Receiver::provider ("ntfy", "unifiedpush").
48 std::function<PushReceiver *(const QString &provider, QObject *parent)> makeReceiver;
49 // Ends the provider-side registration of a replaced receiver (UnifiedPush
50 // distributors keep it otherwise); no-op for ntfy.
51 std::function<void(const QString &provider, const QString &server, const QString &channel)> releaseReceiver;
52 std::function<DavPushClient *(const QString &serverUrl, const QString &username, const QString &password)> makeClient;
53 CalendarAccountSync::CredentialResolver resolveCredentials;
54 std::function<qint64()> now; // epoch seconds
55 // Read requests (account-wide when paths are empty).
56 std::function<void(const QString &accountId, const QStringList &paths, unsigned reasons)> requestSync;
57 // Committed RFC 6578 token of a collection (token no-op check).
58 std::function<QString(const QString &accountId, const QString &path)> storedToken;
59 // Known push failure (true) / healthy again (false) for the fallback.
60 std::function<void(const QString &accountId, bool failing)> pushHealth;
61 };
62
63 struct Target {
64 CalendarSyncAccount account;
65 DavPushSettings::Receiver receiver;
66 QSet<QString> knownPaths; // selected collections of every kind (topic mapping)
67 QSet<QString> desiredPaths; // supported + automatic + selected
68 bool active = false; // push allowed now (online, enabled, …)
69 };
70
71 DavPushAccount(const QString &accountId, Context context, QObject *parent = nullptr);
72 ~DavPushAccount() override;
73
74 void apply(const Target &target);
75 // Pause (offline, suspend, quit): stop the receiver, keep registrations.
76 void pause();
77 // Account deleted: forget receiver, secrets and local registrations.
78 void forget();
79 // "Reconnect push for this account": new stream, renew registrations.
80 void reconnect();
81 // The provider token changed: reconnect the stream with the new token.
82 void reloadToken();
83
84 QString accountId() const { return m_accountId; }
85 2679 const PushReceiver *receiver() const { return m_receiver; }
86 463 bool registering(const QString &path) const { return m_inFlight.contains(path); }
87 419 bool unregistering(const QString &path) const { return m_unregistering.contains(path); }
88 57 QString receiverProblem() const { return m_receiverProblem; } // keyring/crypto
89 qint64 nextRenewalAt() const { return m_nextRenewalAt; }
90 // Monotonic ms of the last accepted push per collection (latency).
91 std::optional<qint64> pendingPushSince(const QString &path) const;
92 1 void clearPendingPush(const QString &path) { m_pushSince.remove(path); }
93
94 signals:
95 void changed();
96
97 private:
98 void ensureReceiver();
99 void createReceiver();
100 void startReceiver();
101 void stopReceiver();
102 void converge();
103 void withClient(std::function<void(DavPushClient *client)> work);
104 void registerPath(DavPushClient *client, const QString &path);
105 void onRegistered(const DavPushClient::Result &result, quint64 generation);
106 void unregisterUrl(DavPushClient *client, const QString &path, const QString &url, qint64 cleanupId);
107 void onMessage(const QByteArray &payload, const QString &messageId);
108 void onReceiverState();
109 void scheduleRenewal();
110 bool needsRegistration(const DavPushCollectionRecord &record) const;
111 // Hash of the receiver's current push endpoint ("" while unknown).
112 QString endpointHash() const;
113 void releaseCurrentReceiver();
114 void recordError(DavPushCollectionRecord record, const QString &error, int retrySeconds);
115 QString collectionScope(const QString &path) const;
116
117 QString m_accountId;
118 Context m_ctx;
119 Target m_target;
120 quint64 m_generation = 0;
121 std::optional<DavPushReceiverRecord> m_receiverRecord;
122 std::optional<DavPushReceiverSecret> m_secret;
123 QByteArray m_providerToken;
124 bool m_streamSeen = false; // S106-082: the first stream of this process was seen
125 QSet<QString> m_caughtUpThisProcess; // registered (and caught up) in this process
126 bool m_secretLoading = false;
127 bool m_tokenLoaded = false;
128 QString m_receiverProblem;
129 QPointer<PushReceiver> m_receiver;
130 QSet<QString> m_inFlight;
131 QSet<QString> m_unregistering;
132 QSet<qint64> m_cleanupInFlight;
133 QHash<QString, QString> m_replaced; // path -> old registration URL to delete after success
134 QHash<QString, QString> m_registeringEndpoint; // path -> endpoint hash of the POST in flight
135 QTimer m_renewal;
136 qint64 m_nextRenewalAt = 0;
137 bool m_reportedFailure = false;
138 bool m_clientPending = false;
139 QList<std::function<void(DavPushClient *)>> m_clientWork;
140 QHash<QString, qint64> m_pushSince;
141 QElapsedTimer m_monotonic;
142 };
143