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: 87.4% 188 / 0 / 215
Functions: 91.7% 22 / 0 / 24
Branches: 70.7% 265 / 0 / 375

app/core/CalendarAccountSync.cpp
Line Branch Exec Source
1 #include "CalendarAccountSync.h"
2
3 #include "CalendarSyncApply.h"
4 #include "CalendarSyncStatus.h"
5 #include "data/CalendarStore.h"
6 #include "service/CalDavClient.h"
7 #include "util/SecureUtil.h"
8
9 #include <QLoggingCategory>
10 #include <memory>
11
12 Q_DECLARE_LOGGING_CATEGORY(lcCalendarSync)
13
14 141 CalendarAccountSync::CalendarAccountSync(const CalendarSyncAccount &account, const CalendarSyncRequest &request,
15 141 Deps deps, QObject *parent)
16
1/1
✓ Branch 8 → 9 taken 141 times.
141 : QObject(parent), m_account(account), m_request(request), m_deps(std::move(deps)) {
17
1/1
✓ Branch 15 → 16 taken 141 times.
141 qRegisterMetaType<CalendarSyncResult>();
18 141 m_result.accountId = account.accountId;
19 141 m_result.reasons = request.reasons;
20
1/1
✓ Branch 17 → 18 taken 141 times.
141 m_timeout.setSingleShot(true);
21
1/1
✓ Branch 18 → 19 taken 141 times.
141 connect(&m_timeout, &QTimer::timeout, this, [this] {
22 ✗ fail(QStringLiteral("timeout"), QStringLiteral("No answer within 60 s"));
23 ✗ emit statusMessage(tr("Calendar sync timed out. Try again."));
24 ✗ finish(false);
25 ✗ });
26 141 }
27
28 880 bool CalendarAccountSync::current() const {
29
5/6
✓ Branch 2 → 3 taken 880 times.
✗ Branch 2 → 8 not taken.
✓ Branch 4 → 5 taken 876 times.
✓ Branch 4 → 7 taken 4 times.
✓ Branch 6 → 7 taken 874 times.
✓ Branch 6 → 8 taken 2 times.
880 return !m_finished && (!m_deps.configurationCurrent || m_deps.configurationCurrent());
30 }
31
32 141 void CalendarAccountSync::start() {
33 141 m_deps.status->started(m_account.accountId);
34 141 m_timeout.start(m_deps.timeoutMs);
35
2/2
✓ Branch 4 → 5 taken 1 time.
✓ Branch 4 → 7 taken 140 times.
141 if (m_account.requiresLocalAuthorization) {
36
2/2
✓ Branch 4 → 5 taken 1 time.
✓ Branch 5 → 6 taken 1 time.
2 QTimer::singleShot(0, this, [this] { onCredentials({}, {}); });
37 1 return;
38 }
39
1/1
✓ Branch 8 → 9 taken 140 times.
140 m_deps.resolveCredentials(this, m_account.accountId, m_account.serverUrl, m_account.username,
40 280 [this](QByteArray password, const QString &error) {
41
1/1
✓ Branch 4 → 5 taken 127 times.
127 onCredentials(std::move(password), error);
42 127 });
43 }
44
45 20 void CalendarAccountSync::cancel() {
46
1/2
✗ Branch 2 → 3 not taken.
✓ Branch 2 → 4 taken 20 times.
20 if (m_finished) return;
47 20 m_finished = true;
48 20 m_timeout.stop();
49
2/2
✓ Branch 6 → 7 taken 6 times.
✓ Branch 6 → 9 taken 14 times.
20 if (m_client) m_client->disconnect(this);
50 // S106-053: close the status run — the account stayed "running" for the
51 // rest of the session when sync was switched off mid-run.
52 20 m_deps.status->cancelled(m_account.accountId);
53 }
54
55 128 void CalendarAccountSync::onCredentials(QByteArray password, const QString &error) {
56
3/4
✓ Branch 2 → 3 taken 127 times.
✓ Branch 2 → 4 taken 1 time.
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 127 times.
128 if (m_finished || m_credentialsResolved) return;
57 127 m_credentialsResolved = true;
58
2/2
✓ Branch 6 → 7 taken 2 times.
✓ Branch 6 → 9 taken 125 times.
127 if (!current()) {
59 2 finish(false);
60 2 return;
61 }
62
5/6
✓ Branch 10 → 11 taken 123 times.
✓ Branch 10 → 12 taken 2 times.
✗ Branch 11 → 12 not taken.
✓ Branch 11 → 13 taken 123 times.
✓ Branch 14 → 15 taken 2 times.
✓ Branch 14 → 30 taken 123 times.
125 if (password.isEmpty() || m_account.requiresLocalAuthorization) {
63 // A missing keyring entry used to be reported as "authorize locally"
64 // in the status bar only; it now names the cause.
65 1 const QString kind = m_account.requiresLocalAuthorization ? QStringLiteral("authorization")
66
6/6
✓ Branch 15 → 16 taken 1 time.
✓ Branch 15 → 19 taken 1 time.
✓ Branch 22 → 23 taken 1 time.
✓ Branch 22 → 24 taken 1 time.
✓ Branch 24 → 25 taken 1 time.
✓ Branch 24 → 26 taken 1 time.
4 : QStringLiteral("password");
67
1/1
✓ Branch 26 → 27 taken 2 times.
2 fail(kind, error);
68
1/1
✓ Branch 27 → 28 taken 2 times.
2 finish(false);
69 2 return;
70 2 }
71
1/1
✓ Branch 32 → 33 taken 123 times.
123 run(std::move(password));
72 }
73
74 10 void CalendarAccountSync::fail(const QString &kind, const QString &detail) {
75
1/2
✓ Branch 3 → 4 taken 10 times.
✗ Branch 3 → 5 not taken.
10 if (m_result.errorKind.isEmpty()) m_result.errorKind = kind;
76 10 m_deps.status->failed(m_account.accountId, kind, detail);
77 // G16: the server's text stays in the log and the status details; the
78 // status line gets the plain-language message for the kind of failure.
79
2/2
✓ Branch 6 → 7 taken 10 times.
✓ Branch 7 → 8 taken 10 times.
10 emit statusMessage(CalendarSyncStatus::messageForKind(kind));
80 10 }
81
82 121 void CalendarAccountSync::finish(bool completed) {
83
1/2
✗ Branch 2 → 3 not taken.
✓ Branch 2 → 4 taken 121 times.
121 if (m_finished) return;
84 121 m_finished = true;
85 121 m_timeout.stop();
86
2/2
✓ Branch 6 → 7 taken 116 times.
✓ Branch 6 → 9 taken 5 times.
121 if (m_client) m_client->disconnect(this);
87 121 m_result.completed = completed;
88
2/2
✓ Branch 19 → 12 taken 169 times.
✓ Branch 19 → 20 taken 121 times.
290 for (const QString &path : std::as_const(m_requested))
89
3/3
✓ Branch 14 → 15 taken 7 times.
✓ Branch 14 → 17 taken 162 times.
✓ Branch 15 → 16 taken 7 times.
169 if (!m_result.syncedPaths.contains(path)) m_result.failedPaths.insert(path);
90 121 m_deps.status->finished(m_account.accountId);
91 121 emit finished(m_result);
92 }
93
94 123 void CalendarAccountSync::run(QByteArray password) {
95
2/2
✓ Branch 2 → 3 taken 123 times.
✓ Branch 3 → 4 taken 123 times.
123 auto *client = m_deps.makeClient(m_account.serverUrl, m_account.username, QString::fromUtf8(password));
96
1/1
✓ Branch 5 → 6 taken 123 times.
123 SecureUtil::zeroMemory(password);
97
2/2
✓ Branch 6 → 7 taken 1 time.
✓ Branch 6 → 9 taken 122 times.
123 if (!client) {
98
1/1
✓ Branch 7 → 8 taken 1 time.
1 finish(false);
99 83 return;
100 }
101
1/1
✓ Branch 9 → 10 taken 122 times.
122 m_client = client;
102
1/1
✓ Branch 10 → 11 taken 122 times.
122 client->setParent(this);
103
1/1
✓ Branch 11 → 12 taken 122 times.
122 connect(client, &CalDavClient::syncFailed, this, [this](const QString &error) {
104
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 5 times.
5 if (!current()) return;
105
7/7
✓ Branch 5 → 6 taken 5 times.
✓ Branch 9 → 10 taken 5 times.
✓ Branch 10 → 11 taken 5 times.
✓ Branch 11 → 12 taken 5 times.
✓ Branch 12 → 13 taken 5 times.
✓ Branch 16 → 7 taken 5 times.
✓ Branch 16 → 17 taken 5 times.
10 qCWarning(lcCalendarSync) << "Sync failed for account" << m_account.accountId << error;
106
2/2
✓ Branch 19 → 20 taken 5 times.
✓ Branch 20 → 21 taken 5 times.
5 fail(CalendarSyncStatus::kindForNetworkError(m_client->lastNetworkError()), error);
107 });
108
1/1
✓ Branch 13 → 14 taken 122 times.
122 connect(client, &CalDavClient::calendarsDiscovered, this, &CalendarAccountSync::onDiscovered);
109
1/1
✓ Branch 15 → 16 taken 122 times.
122 connect(client, &CalDavClient::collectionSynced, this, &CalendarAccountSync::onCollectionSynced);
110
1/1
✓ Branch 17 → 18 taken 122 times.
122 connect(client, &CalDavClient::collectionSyncRejected, this, &CalendarAccountSync::onCollectionRejected);
111 122 connect(client, &CalDavClient::eventsSynced, this,
112
1/1
✓ Branch 19 → 20 taken 122 times.
122 [this](const QString &path, const QList<CalendarEvent> &events, bool complete) {
113
3/6
✓ Branch 3 → 4 taken 5 times.
✗ Branch 3 → 6 not taken.
✗ Branch 5 → 6 not taken.
✓ Branch 5 → 7 taken 5 times.
✗ Branch 8 → 9 not taken.
✓ Branch 8 → 10 taken 5 times.
5 if (!current() || !selected(path)) return;
114 5 onQueryResult(path, true, m_deps.apply->applyQueryEvents(m_account.accountId, path, events, complete),
115 complete);
116 });
117 122 connect(client, &CalDavClient::tasksSynced, this,
118
1/1
✓ Branch 21 → 22 taken 122 times.
122 [this](const QString &path, const QList<CalendarTask> &tasks, bool complete) {
119
3/6
✓ Branch 3 → 4 taken 5 times.
✗ Branch 3 → 6 not taken.
✗ Branch 5 → 6 not taken.
✓ Branch 5 → 7 taken 5 times.
✗ Branch 8 → 9 not taken.
✓ Branch 8 → 10 taken 5 times.
5 if (!current() || !selected(path)) return;
120 5 onQueryResult(path, false, m_deps.apply->applyQueryTasks(m_account.accountId, path, tasks, complete),
121 complete);
122 });
123
1/1
✓ Branch 23 → 24 taken 122 times.
238 connect(client, &CalDavClient::allRequestsFinished, this, [this] { finish(true); });
124 // F-54 (SPRINT-104): the run's 60 s budget is an inactivity budget —
125 // every answered request restarts it (like the contact sync's timer), so
126 // a batched initial download of a large calendar survives the run.
127
1/1
✓ Branch 25 → 26 taken 122 times.
122 connect(client, &CalDavClient::requestAnswered, this, [this] {
128
1/2
✓ Branch 2 → 3 taken 268 times.
✗ Branch 2 → 4 not taken.
268 if (!m_finished) m_timeout.start(m_deps.timeoutMs);
129 268 });
130
131 // A targeted run needs every forced collection to be known locally (its
132 // components decide the query fallback); otherwise it discovers first.
133 122 QStringList targets;
134
2/2
✓ Branch 27 → 28 taken 40 times.
✓ Branch 27 → 63 taken 82 times.
122 if (!m_request.discover) {
135 40 QSet<QString> known;
136
5/5
✓ Branch 29 → 30 taken 40 times.
✓ Branch 30 → 31 taken 40 times.
✓ Branch 31 → 32 taken 40 times.
✓ Branch 40 → 33 taken 113 times.
✓ Branch 40 → 41 taken 40 times.
153 for (const auto &calendar : m_deps.store->allCalendars())
137
2/3
✓ Branch 35 → 36 taken 113 times.
✗ Branch 35 → 38 not taken.
✓ Branch 36 → 37 taken 113 times.
153 if (calendar.accountId == m_account.accountId) known.insert(calendar.path);
138
4/4
✓ Branch 42 → 43 taken 40 times.
✓ Branch 43 → 44 taken 40 times.
✓ Branch 56 → 45 taken 43 times.
✓ Branch 56 → 57 taken 40 times.
83 for (const QString &path : m_request.forcedPaths)
139
4/7
✓ Branch 47 → 48 taken 43 times.
✗ Branch 47 → 51 not taken.
✓ Branch 49 → 50 taken 43 times.
✗ Branch 49 → 51 not taken.
✓ Branch 52 → 53 taken 43 times.
✗ Branch 52 → 54 not taken.
✓ Branch 53 → 54 taken 43 times.
43 if (selected(path) && known.contains(path)) targets << path;
140
1/2
✗ Branch 59 → 60 not taken.
✓ Branch 59 → 61 taken 40 times.
40 if (targets.size() != m_request.forcedPaths.size()) m_request.discover = true;
141 40 }
142
2/2
✓ Branch 63 → 64 taken 82 times.
✓ Branch 63 → 66 taken 40 times.
122 if (m_request.discover) {
143
1/1
✓ Branch 64 → 65 taken 82 times.
82 client->discoverCalendars();
144 82 return;
145 }
146
2/2
✓ Branch 76 → 69 taken 43 times.
✓ Branch 76 → 77 taken 40 times.
83 for (const QString &path : std::as_const(targets)) {
147
1/1
✓ Branch 70 → 71 taken 43 times.
43 syncCollection(path);
148
2/3
✓ Branch 71 → 72 taken 43 times.
✗ Branch 72 → 73 not taken.
✓ Branch 72 → 74 taken 43 times.
43 if (!current()) return;
149 }
150 122 }
151
152 77 void CalendarAccountSync::onDiscovered(const QList<CalendarInfo> &calendars) {
153
2/3
✓ Branch 2 → 3 taken 77 times.
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 77 times.
77 if (!current()) return;
154
1/2
✗ Branch 8 → 9 not taken.
✓ Branch 8 → 40 taken 77 times.
77 if (const auto scheduling = m_client->serverScheduling()) {
155 ✗ CalendarSyncStatus::setServerScheduling(m_account.accountId, *scheduling);
156 // Sprint 108 S108.8.4: the outbox (free/busy), the addresses the server
157 // schedules for, and — once — whether it schedules by itself
158 // (calendar-auto-schedule in the DAV header of OPTIONS).
159 ✗ const QString outbox = m_client->scheduleOutboxUrl();
160 ✗ CalendarSyncStatus::setScheduling(m_account.accountId, outbox, m_client->calendarUserAddresses());
161 ✗ if (*scheduling && !outbox.isEmpty()) {
162 ✗ const QString accountId = m_account.accountId;
163 ✗ CalDavClient *client = m_client;
164 ✗ const quint64 request = client->options(outbox);
165 ✗ auto connection = std::make_shared<QMetaObject::Connection>();
166 ✗ *connection = connect(client, &CalDavClient::resourceResponse, client,
167 ✗ [connection, request, accountId](const DavResponse &response) {
168 ✗ if (response.request != request) return;
169 ✗ QObject::disconnect(*connection);
170 ✗ if (response.status < 200 || response.status >= 300) return;
171 ✗ const bool automatic = response.davHeader.contains("calendar-auto-schedule");
172 ✗ CalendarSyncStatus::setAutoSchedule(accountId, automatic);
173 ✗ qCInfo(lcCalendarSync) << "calendar-auto-schedule" << automatic;
174 ✗ });
175 ✗ }
176 ✗ }
177 77 QStringList unchanged;
178
2/2
✓ Branch 74 → 42 taken 157 times.
✓ Branch 74 → 75 taken 77 times.
234 for (const auto &cal : calendars) {
179
1/2
✗ Branch 44 → 45 not taken.
✓ Branch 44 → 46 taken 157 times.
157 if (!selected(cal.path)) continue;
180
1/1
✓ Branch 46 → 47 taken 157 times.
157 const QString known = m_deps.store->calendarCtag(m_account.accountId, cal.path);
181
7/7
✓ Branch 48 → 49 taken 129 times.
✓ Branch 48 → 52 taken 28 times.
✓ Branch 50 → 51 taken 33 times.
✓ Branch 50 → 52 taken 96 times.
✓ Branch 53 → 54 taken 33 times.
✓ Branch 53 → 55 taken 124 times.
✓ Branch 54 → 55 taken 33 times.
157 if (!cal.ctag.isEmpty() && cal.ctag == known) unchanged << cal.path;
182 157 CalendarInfo stored = cal;
183 157 stored.accountId = m_account.accountId;
184 157 stored.ctag = known; // committed after a complete sync only
185
4/4
✓ Branch 58 → 59 taken 156 times.
✓ Branch 58 → 61 taken 1 time.
✓ Branch 60 → 61 taken 1 time.
✓ Branch 60 → 62 taken 155 times.
157 stored.readOnly = stored.readOnly || m_account.readOnlyCalendars.contains(cal.path);
186
1/1
✓ Branch 63 → 64 taken 157 times.
157 m_deps.store->upsertCalendar(stored, m_account.accountId);
187
2/3
✓ Branch 65 → 66 taken 157 times.
✗ Branch 65 → 67 not taken.
✓ Branch 66 → 67 taken 157 times.
157 if (m_deps.adoptServerColor) m_deps.adoptServerColor(m_account.accountId, cal.path, cal.color);
188
1/1
✓ Branch 67 → 68 taken 157 times.
157 m_discoveredCtags.insert(cal.path, cal.ctag);
189 157 }
190
1/1
✓ Branch 75 → 76 taken 77 times.
77 QPointer<CalendarAccountSync> guard(this);
191
1/1
✓ Branch 76 → 77 taken 77 times.
77 emit collectionsDiscovered(m_account.accountId, calendars);
192
4/7
✓ Branch 78 → 79 taken 77 times.
✗ Branch 78 → 81 not taken.
✓ Branch 79 → 80 taken 77 times.
✗ Branch 80 → 81 not taken.
✓ Branch 80 → 82 taken 77 times.
✗ Branch 83 → 84 not taken.
✓ Branch 83 → 85 taken 77 times.
77 if (!guard || !current()) return;
193
1/1
✓ Branch 85 → 86 taken 77 times.
77 emit storeChanged();
194
4/7
✓ Branch 87 → 88 taken 77 times.
✗ Branch 87 → 90 not taken.
✓ Branch 88 → 89 taken 77 times.
✗ Branch 89 → 90 not taken.
✓ Branch 89 → 91 taken 77 times.
✗ Branch 92 → 93 not taken.
✓ Branch 92 → 94 taken 77 times.
77 if (!guard || !current()) return;
195
2/2
✓ Branch 135 → 96 taken 157 times.
✓ Branch 135 → 136 taken 77 times.
234 for (const auto &cal : calendars) {
196
1/2
✗ Branch 98 → 99 not taken.
✓ Branch 98 → 100 taken 157 times.
157 if (!selected(cal.path)) continue;
197 // An unfinished enumeration continues even when the CTag is unchanged;
198 // a forced collection (push, recovery) ignores the CTag.
199
4/4
✓ Branch 101 → 102 taken 33 times.
✓ Branch 101 → 107 taken 124 times.
✓ Branch 103 → 104 taken 28 times.
✓ Branch 103 → 107 taken 5 times.
185 if (unchanged.contains(cal.path) && !m_request.forcedPaths.contains(cal.path) &&
200
6/9
✓ Branch 104 → 105 taken 28 times.
✓ Branch 105 → 106 taken 28 times.
✗ Branch 105 → 107 not taken.
✓ Branch 108 → 109 taken 28 times.
✓ Branch 108 → 110 taken 129 times.
✓ Branch 110 → 111 taken 28 times.
✓ Branch 110 → 124 taken 129 times.
✗ Branch 173 → 174 not taken.
✗ Branch 173 → 175 not taken.
185 !m_deps.store->enumerationState(m_account.accountId, cal.path).active) {
201
6/6
✓ Branch 111 → 112 taken 28 times.
✓ Branch 115 → 116 taken 28 times.
✓ Branch 116 → 117 taken 28 times.
✓ Branch 117 → 118 taken 28 times.
✓ Branch 121 → 113 taken 28 times.
✓ Branch 121 → 122 taken 28 times.
56 qCInfo(lcCalendarSync) << "Collection unchanged (CTag), skipped:" << cal.path;
202
1/1
✓ Branch 122 → 123 taken 28 times.
28 m_result.syncedPaths.insert(cal.path);
203 28 continue;
204 28 }
205
1/1
✓ Branch 124 → 125 taken 129 times.
129 syncCollection(cal.path);
206
4/7
✓ Branch 126 → 127 taken 129 times.
✗ Branch 126 → 129 not taken.
✓ Branch 127 → 128 taken 129 times.
✗ Branch 128 → 129 not taken.
✓ Branch 128 → 130 taken 129 times.
✗ Branch 131 → 132 not taken.
✓ Branch 131 → 133 taken 129 times.
129 if (!guard || !current()) return;
207 }
208 77 }
209
210 172 void CalendarAccountSync::syncCollection(const QString &path) {
211
1/1
✓ Branch 2 → 3 taken 172 times.
172 m_requested.insert(path);
212
3/3
✓ Branch 3 → 4 taken 172 times.
✓ Branch 6 → 7 taken 3 times.
✓ Branch 6 → 8 taken 169 times.
172 if (m_deps.queryOnlyCollections->contains(calendarSelectionKey(m_account.accountId, path)))
213 3 queryCollection(path);
214 else
215
2/2
✓ Branch 8 → 9 taken 169 times.
✓ Branch 9 → 10 taken 169 times.
169 requestPage(path, m_deps.apply->nextRequestToken(m_account.accountId, path));
216 172 }
217
218 // The classic full download: one calendar-query for events, one for tasks.
219 5 void CalendarAccountSync::queryCollection(const QString &path) {
220 5 QStringList components;
221
5/5
✓ Branch 2 → 3 taken 5 times.
✓ Branch 3 → 4 taken 5 times.
✓ Branch 4 → 5 taken 5 times.
✓ Branch 17 → 6 taken 5 times.
✓ Branch 17 → 18 taken 5 times.
10 for (const auto &calendar : m_deps.store->allCalendars())
222
3/6
✓ Branch 8 → 9 taken 5 times.
✗ Branch 8 → 12 not taken.
✓ Branch 10 → 11 taken 5 times.
✗ Branch 10 → 12 not taken.
✓ Branch 13 → 14 taken 5 times.
✗ Branch 13 → 15 not taken.
10 if (calendar.accountId == m_account.accountId && calendar.path == path) components = calendar.supportedComponents;
223
3/8
✗ Branch 20 → 21 not taken.
✓ Branch 20 → 26 taken 5 times.
✗ Branch 25 → 26 not taken.
✗ Branch 25 → 27 not taken.
✗ Branch 28 → 29 not taken.
✓ Branch 28 → 30 taken 5 times.
✗ Branch 30 → 31 not taken.
✓ Branch 30 → 32 taken 5 times.
5 const bool events = components.isEmpty() || components.contains(QStringLiteral("VEVENT"));
224
3/8
✗ Branch 33 → 34 not taken.
✓ Branch 33 → 39 taken 5 times.
✗ Branch 38 → 39 not taken.
✗ Branch 38 → 40 not taken.
✗ Branch 41 → 42 not taken.
✓ Branch 41 → 43 taken 5 times.
✗ Branch 43 → 44 not taken.
✓ Branch 43 → 45 taken 5 times.
5 const bool tasks = components.isEmpty() || components.contains(QStringLiteral("VTODO"));
225
1/1
✓ Branch 47 → 48 taken 5 times.
5 m_queryPending.insert(path, {m_discoveredCtags.value(path), int(events) + int(tasks)});
226
2/3
✓ Branch 50 → 51 taken 5 times.
✗ Branch 50 → 53 not taken.
✓ Branch 52 → 53 taken 5 times.
5 if (events) m_client->syncCalendar(path);
227
2/3
✓ Branch 53 → 54 taken 5 times.
✗ Branch 53 → 56 not taken.
✓ Branch 55 → 56 taken 5 times.
5 if (tasks) m_client->syncTasks(path);
228 5 }
229
230 175 void CalendarAccountSync::requestPage(const QString &path, const QString &token) {
231
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 17 taken 175 times.
175 if (++m_pages[path] > kMaxSyncPagesPerRun) {
232 // The checkpoint is committed; the next run continues from it.
233 ✗ qCWarning(lcCalendarSync) << "Page limit reached, continuing next run:" << m_account.accountId << path;
234 ✗ return;
235 }
236 175 m_client->syncCollection(path, token);
237 }
238
239 166 void CalendarAccountSync::onCollectionSynced(const QString &path, const DavCollectionChanges &changes) {
240
4/7
✓ Branch 2 → 3 taken 166 times.
✓ Branch 3 → 4 taken 166 times.
✗ Branch 3 → 6 not taken.
✗ Branch 5 → 6 not taken.
✓ Branch 5 → 7 taken 166 times.
✗ Branch 8 → 9 not taken.
✓ Branch 8 → 10 taken 166 times.
166 if (!current() || !selected(path)) return;
241
1/1
✓ Branch 11 → 12 taken 166 times.
166 const auto outcome = m_deps.apply->applySyncPage(m_account.accountId, path, changes, m_discoveredCtags.value(path));
242
4/4
✓ Branch 13 → 14 taken 135 times.
✓ Branch 13 → 15 taken 31 times.
✓ Branch 14 → 15 taken 133 times.
✓ Branch 14 → 31 taken 2 times.
166 if (outcome.appliedResources > 0 || outcome.result != CalendarSyncApply::PageResult::Failed) {
243
1/1
✓ Branch 15 → 16 taken 164 times.
164 QPointer<CalendarAccountSync> guard(this);
244
1/1
✓ Branch 16 → 17 taken 164 times.
164 emit storeChanged();
245
4/7
✓ Branch 18 → 19 taken 164 times.
✗ Branch 18 → 21 not taken.
✓ Branch 19 → 20 taken 164 times.
✗ Branch 20 → 21 not taken.
✓ Branch 20 → 22 taken 164 times.
✗ Branch 23 → 24 not taken.
✓ Branch 23 → 25 taken 164 times.
164 if (!guard || !current()) return;
246 164 }
247
2/2
✓ Branch 31 → 32 taken 3 times.
✓ Branch 31 → 33 taken 163 times.
166 if (outcome.result == CalendarSyncApply::PageResult::Continue) {
248
1/1
✓ Branch 32 → 63 taken 3 times.
3 requestPage(path, outcome.continueToken);
249
2/2
✓ Branch 33 → 34 taken 160 times.
✓ Branch 33 → 36 taken 3 times.
163 } else if (outcome.result == CalendarSyncApply::PageResult::Committed) {
250
1/1
✓ Branch 34 → 35 taken 160 times.
160 m_result.syncedPaths.insert(path);
251 } else {
252
8/8
✓ Branch 36 → 37 taken 3 times.
✓ Branch 40 → 41 taken 3 times.
✓ Branch 41 → 42 taken 3 times.
✓ Branch 42 → 43 taken 3 times.
✓ Branch 43 → 44 taken 3 times.
✓ Branch 44 → 45 taken 3 times.
✓ Branch 48 → 38 taken 3 times.
✓ Branch 48 → 49 taken 3 times.
6 qCWarning(lcCalendarSync) << "Collection sync incomplete:" << m_account.accountId << path << outcome.error;
253 // A failed local write is no server error (user report 2026-10-13: the
254 // status blamed the server for "database is locked").
255
7/11
✓ Branch 49 → 50 taken 1 time.
✓ Branch 49 → 53 taken 2 times.
✓ Branch 56 → 57 taken 3 times.
✓ Branch 58 → 59 taken 2 times.
✓ Branch 58 → 60 taken 1 time.
✓ Branch 60 → 61 taken 1 time.
✓ Branch 60 → 62 taken 2 times.
✗ Branch 84 → 85 not taken.
✗ Branch 84 → 86 not taken.
✗ Branch 88 → 89 not taken.
✗ Branch 88 → 90 not taken.
6 fail(outcome.localFailure ? QStringLiteral("local") : QStringLiteral("server"), outcome.error);
256 }
257 166 }
258
259 5 void CalendarAccountSync::onCollectionRejected(const QString &path, bool invalidToken) {
260
3/6
✓ Branch 3 → 4 taken 5 times.
✗ Branch 3 → 6 not taken.
✗ Branch 5 → 6 not taken.
✓ Branch 5 → 7 taken 5 times.
✗ Branch 8 → 9 not taken.
✓ Branch 8 → 10 taken 5 times.
5 if (!current() || !selected(path)) return;
261
2/2
✓ Branch 10 → 11 taken 3 times.
✓ Branch 10 → 35 taken 2 times.
5 if (invalidToken) {
262 // RFC 6578 §3.2: start over with the whole collection — once per run.
263
3/6
✓ Branch 12 → 13 taken 3 times.
✗ Branch 12 → 15 not taken.
✗ Branch 14 → 15 not taken.
✓ Branch 14 → 16 taken 3 times.
✗ Branch 17 → 18 not taken.
✓ Branch 17 → 30 taken 3 times.
3 if (m_tokenResets.contains(path) || !m_deps.apply->resetCollectionToken(m_account.accountId, path)) {
264 ✗ fail(QStringLiteral("server"), QStringLiteral("sync token rejected repeatedly"));
265 ✗ return;
266 }
267
1/1
✓ Branch 30 → 31 taken 3 times.
3 m_tokenResets.insert(path);
268
1/1
✓ Branch 32 → 33 taken 3 times.
3 requestPage(path, QString());
269 3 return;
270 }
271 // This collection lacks sync-collection: calendar-query from now on.
272
7/7
✓ Branch 35 → 36 taken 2 times.
✓ Branch 39 → 40 taken 2 times.
✓ Branch 40 → 41 taken 2 times.
✓ Branch 41 → 42 taken 2 times.
✓ Branch 42 → 43 taken 2 times.
✓ Branch 46 → 37 taken 2 times.
✓ Branch 46 → 47 taken 2 times.
4 qCInfo(lcCalendarSync) << "sync-collection unsupported, using calendar-query for" << m_account.accountId << path;
273
2/2
✓ Branch 47 → 48 taken 2 times.
✓ Branch 48 → 49 taken 2 times.
2 m_deps.queryOnlyCollections->insert(calendarSelectionKey(m_account.accountId, path));
274 2 queryCollection(path);
275 }
276
277 10 void CalendarAccountSync::onQueryResult(const QString &path, bool events, bool stored, bool complete) {
278 Q_UNUSED(events);
279
1/1
✓ Branch 2 → 3 taken 10 times.
10 auto it = m_queryPending.find(path);
280
3/4
✓ Branch 3 → 4 taken 10 times.
✗ Branch 3 → 5 not taken.
✓ Branch 4 → 5 taken 1 time.
✓ Branch 4 → 14 taken 9 times.
10 if (!stored || !complete) {
281 // Incomplete or not stored: this run commits no CTag for the collection.
282
2/3
✓ Branch 7 → 8 taken 1 time.
✗ Branch 7 → 11 not taken.
✓ Branch 9 → 10 taken 1 time.
1 if (it != m_queryPending.end()) m_queryPending.erase(it);
283
2/3
✓ Branch 11 → 12 taken 1 time.
✗ Branch 11 → 13 not taken.
✓ Branch 12 → 13 taken 1 time.
1 if (stored) emit storeChanged();
284 1 return;
285 }
286
1/1
✓ Branch 14 → 15 taken 9 times.
9 QPointer<CalendarAccountSync> guard(this);
287
6/6
✓ Branch 17 → 18 taken 8 times.
✓ Branch 17 → 21 taken 1 time.
✓ Branch 19 → 20 taken 4 times.
✓ Branch 19 → 21 taken 4 times.
✓ Branch 22 → 23 taken 4 times.
✓ Branch 22 → 32 taken 5 times.
9 if (it != m_queryPending.end() && --it->second == 0) {
288
1/3
✗ Branch 25 → 26 not taken.
✓ Branch 25 → 28 taken 4 times.
✗ Branch 27 → 28 not taken.
4 if (!it->first.isEmpty()) m_deps.store->setCalendarCtag(m_account.accountId, path, it->first);
289
1/1
✓ Branch 29 → 30 taken 4 times.
4 m_queryPending.erase(it);
290
1/1
✓ Branch 30 → 31 taken 4 times.
4 m_result.syncedPaths.insert(path);
291 }
292
1/1
✓ Branch 32 → 33 taken 9 times.
9 emit storeChanged();
293 9 }
294