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: 91.5% 248 / 0 / 271
Functions: 94.1% 32 / 0 / 34
Branches: 55.2% 434 / 0 / 786

data/DavPushStore.cpp
Line Branch Exec Source
1 #include "DavPushStore.h"
2
3 #include "DatabaseSecurity.h"
4
5 #include <QLoggingCategory>
6 #include <QRandomGenerator>
7 #include <QSqlError>
8 #include <QSqlQuery>
9 #include <QUuid>
10
11
4/7
✓ Branch 2 → 3 taken 1 time.
✓ Branch 2 → 8 taken 1 time.
✓ Branch 4 → 5 taken 1 time.
✗ Branch 4 → 8 not taken.
✓ Branch 5 → 6 taken 1 time.
✗ Branch 10 → 11 not taken.
✗ Branch 10 → 12 not taken.
2 Q_LOGGING_CATEGORY(lcDavPushStore, "mailjd.davpush.store")
12
13 namespace {
14
15 1848 DavPushCollectionRecord collectionFrom(const QSqlQuery &q) {
16 1848 DavPushCollectionRecord r;
17
2/2
✓ Branch 2 → 3 taken 1848 times.
✓ Branch 3 → 4 taken 1848 times.
1848 r.accountId = q.value(0).toString();
18
2/2
✓ Branch 7 → 8 taken 1848 times.
✓ Branch 8 → 9 taken 1848 times.
1848 r.path = q.value(1).toString();
19
2/2
✓ Branch 12 → 13 taken 1848 times.
✓ Branch 13 → 14 taken 1848 times.
1848 r.offer.capability = davPushCapabilityFromName(q.value(2).toString());
20
2/2
✓ Branch 17 → 18 taken 1848 times.
✓ Branch 18 → 19 taken 1848 times.
1848 r.offer.profile = static_cast<DavPushProfile>(q.value(3).toInt());
21
2/2
✓ Branch 20 → 21 taken 1848 times.
✓ Branch 21 → 22 taken 1848 times.
1848 r.offer.topic = q.value(4).toString();
22
2/2
✓ Branch 25 → 26 taken 1848 times.
✓ Branch 26 → 27 taken 1848 times.
1848 r.offer.vapidPublicKey = q.value(5).toByteArray();
23
2/2
✓ Branch 30 → 31 taken 1848 times.
✓ Branch 31 → 32 taken 1848 times.
1848 r.offer.reason = q.value(6).toString();
24
2/2
✓ Branch 35 → 36 taken 1848 times.
✓ Branch 36 → 37 taken 1848 times.
1848 r.checkedAt = q.value(7).toLongLong();
25
2/2
✓ Branch 38 → 39 taken 1848 times.
✓ Branch 39 → 40 taken 1848 times.
1848 r.registrationUrl = q.value(8).toString();
26
2/2
✓ Branch 43 → 44 taken 1848 times.
✓ Branch 44 → 45 taken 1848 times.
1848 r.registrationReceiver = q.value(9).toString();
27
2/2
✓ Branch 48 → 49 taken 1848 times.
✓ Branch 49 → 50 taken 1848 times.
1848 r.expiresAt = q.value(10).toLongLong();
28
2/2
✓ Branch 51 → 52 taken 1848 times.
✓ Branch 52 → 53 taken 1848 times.
1848 r.registeredAt = q.value(11).toLongLong();
29
2/2
✓ Branch 54 → 55 taken 1848 times.
✓ Branch 55 → 56 taken 1848 times.
1848 r.renewedAt = q.value(12).toLongLong();
30
2/2
✓ Branch 57 → 58 taken 1848 times.
✓ Branch 58 → 59 taken 1848 times.
1848 r.lastError = q.value(13).toString();
31
2/2
✓ Branch 62 → 63 taken 1848 times.
✓ Branch 63 → 64 taken 1848 times.
1848 r.lastErrorAt = q.value(14).toLongLong();
32
2/2
✓ Branch 65 → 66 taken 1848 times.
✓ Branch 66 → 67 taken 1848 times.
1848 r.nextRetryAt = q.value(15).toLongLong();
33
2/2
✓ Branch 68 → 69 taken 1848 times.
✓ Branch 69 → 70 taken 1848 times.
1848 r.failures = q.value(16).toInt();
34
2/2
✓ Branch 71 → 72 taken 1848 times.
✓ Branch 72 → 73 taken 1848 times.
1848 r.lastPushAt = q.value(17).toLongLong();
35
2/2
✓ Branch 74 → 75 taken 1848 times.
✓ Branch 75 → 76 taken 1848 times.
1848 r.lastSyncAt = q.value(18).toLongLong();
36
2/2
✓ Branch 77 → 78 taken 1848 times.
✓ Branch 78 → 79 taken 1848 times.
1848 r.registrationEndpoint = q.value(19).toString();
37 1848 return r;
38 ✗ }
39
40 const char *kCollectionColumns =
41 "account_id, path, capability, profile, topic, vapid_key, reason, checked_at, registration_url, "
42 "registration_receiver, expires_at, registered_at, renewed_at, last_error, last_error_at, next_retry_at, "
43 "failures, last_push_at, last_sync_at, endpoint_hash";
44
45 344 qint64 hourOf(qint64 epochSeconds) { return epochSeconds - epochSeconds % 3600; }
46
47 } // namespace
48
49 868 DavPushStore::~DavPushStore() {
50
2/2
✓ Branch 3 → 4 taken 867 times.
✓ Branch 3 → 9 taken 1 time.
868 if (m_db.isValid()) {
51 867 m_db.close();
52 867 m_db = QSqlDatabase();
53 867 QSqlDatabase::removeDatabase(m_connectionName);
54 }
55 868 }
56
57 13653 bool DavPushStore::exec(const QString &sql, const QVariantList &values) const {
58
1/1
✓ Branch 2 → 3 taken 13653 times.
13653 QSqlQuery q(m_db);
59
1/1
✓ Branch 3 → 4 taken 13653 times.
13653 q.prepare(sql);
60
3/3
✓ Branch 8 → 9 taken 15356 times.
✓ Branch 11 → 6 taken 15356 times.
✓ Branch 11 → 12 taken 13653 times.
29009 for (const auto &value : values) q.addBindValue(value);
61
2/3
✓ Branch 12 → 13 taken 13653 times.
✗ Branch 13 → 14 not taken.
✓ Branch 13 → 34 taken 13653 times.
13653 if (!q.exec()) {
62 ✗ m_lastError = q.lastError().text();
63 ✗ qCWarning(lcDavPushStore) << "SQL failed:" << m_lastError << sql.left(80);
64 ✗ return false;
65 }
66 13653 return true;
67 13653 }
68
69 868 bool DavPushStore::open(const QString &path) {
70
3/3
✓ Branch 2 → 3 taken 868 times.
✓ Branch 3 → 4 taken 1 time.
✓ Branch 3 → 11 taken 867 times.
868 if (!DatabaseSecurity::preparePath(path)) {
71 1 m_lastError = QStringLiteral("cannot prepare a private database path");
72 1 return false;
73 }
74
4/4
✓ Branch 11 → 12 taken 867 times.
✓ Branch 12 → 13 taken 867 times.
✓ Branch 13 → 14 taken 867 times.
✓ Branch 17 → 18 taken 867 times.
1734 m_connectionName = QStringLiteral("davpush_") + QUuid::createUuid().toString(QUuid::WithoutBraces).left(8);
75
2/2
✓ Branch 27 → 28 taken 867 times.
✓ Branch 28 → 29 taken 867 times.
1734 m_db = QSqlDatabase::addDatabase(QStringLiteral("QSQLITE"), m_connectionName);
76
1/1
✓ Branch 32 → 33 taken 867 times.
867 m_db.setDatabaseName(path);
77
5/8
✓ Branch 33 → 34 taken 867 times.
✓ Branch 34 → 35 taken 867 times.
✗ Branch 34 → 37 not taken.
✓ Branch 35 → 36 taken 867 times.
✗ Branch 36 → 37 not taken.
✓ Branch 36 → 38 taken 867 times.
✗ Branch 39 → 40 not taken.
✓ Branch 39 → 58 taken 867 times.
867 if (!m_db.open() || !DatabaseSecurity::restrictExistingFile(path)) {
78 ✗ m_lastError = m_db.lastError().text();
79 ✗ qCWarning(lcDavPushStore) << "open failed:" << m_lastError;
80 ✗ m_db.close();
81 ✗ return false;
82 }
83
1/1
✓ Branch 58 → 59 taken 867 times.
867 QSqlQuery pragma(m_db);
84
1/1
✓ Branch 62 → 63 taken 867 times.
867 pragma.exec(QStringLiteral("PRAGMA journal_mode=WAL"));
85
1/1
✓ Branch 68 → 69 taken 867 times.
867 pragma.exec(QStringLiteral("PRAGMA busy_timeout=5000"));
86
3/3
✓ Branch 71 → 72 taken 867 times.
✓ Branch 72 → 73 taken 1 time.
✓ Branch 72 → 75 taken 866 times.
867 if (!createSchema()) {
87
1/1
✓ Branch 73 → 74 taken 1 time.
1 m_db.close();
88 1 return false;
89 }
90 866 return true;
91 867 }
92
93 867 bool DavPushStore::createSchema() {
94
1/1
✓ Branch 2 → 3 taken 867 times.
867 QSqlQuery version(m_db);
95
7/16
✓ Branch 6 → 7 taken 867 times.
✓ Branch 7 → 8 taken 867 times.
✗ Branch 7 → 10 not taken.
✓ Branch 8 → 9 taken 867 times.
✗ Branch 9 → 10 not taken.
✓ Branch 9 → 11 taken 867 times.
✓ Branch 12 → 13 taken 867 times.
✗ Branch 12 → 14 not taken.
✓ Branch 14 → 15 taken 867 times.
✗ Branch 14 → 16 not taken.
✗ Branch 16 → 17 not taken.
✓ Branch 16 → 18 taken 867 times.
✗ Branch 170 → 171 not taken.
✗ Branch 170 → 172 not taken.
✗ Branch 174 → 175 not taken.
✗ Branch 174 → 176 not taken.
1734 if (!version.exec(QStringLiteral("PRAGMA user_version")) || !version.next()) return false;
96
2/2
✓ Branch 18 → 19 taken 867 times.
✓ Branch 19 → 20 taken 867 times.
867 const int current = version.value(0).toInt();
97
2/2
✓ Branch 21 → 22 taken 1 time.
✓ Branch 21 → 42 taken 866 times.
867 if (current > SchemaVersion) {
98 // A newer MailJD wrote this file: push stays off instead of guessing.
99
1/1
✓ Branch 26 → 27 taken 1 time.
2 m_lastError = QStringLiteral("dav-push.sqlite has the newer schema %1").arg(current);
100
5/5
✓ Branch 31 → 32 taken 1 time.
✓ Branch 35 → 36 taken 1 time.
✓ Branch 36 → 37 taken 1 time.
✓ Branch 40 → 33 taken 1 time.
✓ Branch 40 → 41 taken 1 time.
2 qCWarning(lcDavPushStore) << m_lastError;
101 1 return false;
102 }
103
2/2
✓ Branch 42 → 43 taken 18 times.
✓ Branch 42 → 44 taken 848 times.
866 if (current == SchemaVersion) return true;
104
2/2
✓ Branch 44 → 45 taken 1 time.
✓ Branch 44 → 94 taken 847 times.
848 if (current == 1) {
105 // v1 -> v2: the registered endpoint's hash (unknown for old rows).
106
2/3
✓ Branch 45 → 46 taken 1 time.
✗ Branch 46 → 47 not taken.
✓ Branch 46 → 48 taken 1 time.
1 if (!m_db.transaction()) return false;
107
5/15
✓ Branch 52 → 53 taken 1 time.
✓ Branch 53 → 54 taken 1 time.
✗ Branch 53 → 62 not taken.
✓ Branch 74 → 75 taken 1 time.
✗ Branch 74 → 76 not taken.
✓ Branch 76 → 77 taken 1 time.
✗ Branch 76 → 78 not taken.
✗ Branch 78 → 79 not taken.
✓ Branch 78 → 81 taken 1 time.
✗ Branch 211 → 212 not taken.
✗ Branch 211 → 213 not taken.
✗ Branch 215 → 216 not taken.
✗ Branch 215 → 217 not taken.
✗ Branch 219 → 220 not taken.
✗ Branch 219 → 221 not taken.
5 if (!exec(QStringLiteral("ALTER TABLE collections ADD COLUMN endpoint_hash TEXT DEFAULT ''")) ||
108
8/22
✓ Branch 59 → 60 taken 1 time.
✓ Branch 60 → 61 taken 1 time.
✗ Branch 61 → 62 not taken.
✓ Branch 61 → 63 taken 1 time.
✓ Branch 64 → 65 taken 1 time.
✗ Branch 64 → 66 not taken.
✓ Branch 66 → 67 taken 1 time.
✗ Branch 66 → 68 not taken.
✓ Branch 68 → 69 taken 1 time.
✗ Branch 68 → 70 not taken.
✓ Branch 70 → 71 taken 1 time.
✗ Branch 70 → 72 not taken.
✓ Branch 72 → 73 taken 1 time.
✗ Branch 72 → 74 not taken.
✗ Branch 194 → 195 not taken.
✗ Branch 194 → 196 not taken.
✗ Branch 199 → 200 not taken.
✗ Branch 199 → 201 not taken.
✗ Branch 203 → 204 not taken.
✗ Branch 203 → 205 not taken.
✗ Branch 207 → 208 not taken.
✗ Branch 207 → 209 not taken.
3 !exec(QStringLiteral("PRAGMA user_version = %1").arg(SchemaVersion))) {
109 ✗ m_db.rollback();
110 ✗ return false;
111 }
112
6/6
✓ Branch 81 → 82 taken 1 time.
✓ Branch 85 → 86 taken 1 time.
✓ Branch 86 → 87 taken 1 time.
✓ Branch 87 → 88 taken 1 time.
✓ Branch 91 → 83 taken 1 time.
✓ Branch 91 → 92 taken 1 time.
2 qCInfo(lcDavPushStore) << "dav-push.sqlite migrated to schema" << SchemaVersion;
113
1/1
✓ Branch 92 → 93 taken 1 time.
1 return m_db.commit();
114 }
115 const QStringList statements{
116 ✗ QStringLiteral("CREATE TABLE IF NOT EXISTS meta (key TEXT PRIMARY KEY, value TEXT)"),
117 847 QStringLiteral("CREATE TABLE IF NOT EXISTS collections (account_id TEXT NOT NULL, path TEXT NOT NULL, "
118 "capability TEXT DEFAULT 'unknown', profile INTEGER DEFAULT 0, topic TEXT DEFAULT '', "
119 "vapid_key BLOB, reason TEXT DEFAULT '', checked_at INTEGER DEFAULT 0, "
120 "registration_url TEXT DEFAULT '', registration_receiver TEXT DEFAULT '', "
121 "expires_at INTEGER DEFAULT 0, registered_at INTEGER DEFAULT 0, renewed_at INTEGER DEFAULT 0, "
122 "last_error TEXT DEFAULT '', last_error_at INTEGER DEFAULT 0, next_retry_at INTEGER DEFAULT 0, "
123 "failures INTEGER DEFAULT 0, last_push_at INTEGER DEFAULT 0, last_sync_at INTEGER DEFAULT 0, "
124 "endpoint_hash TEXT DEFAULT '', PRIMARY KEY(account_id, path))"),
125 847 QStringLiteral("CREATE TABLE IF NOT EXISTS receivers (account_id TEXT PRIMARY KEY, receiver_id TEXT NOT NULL, "
126 "provider TEXT NOT NULL, server TEXT NOT NULL, created_at INTEGER DEFAULT 0, "
127 "generation INTEGER DEFAULT 0)"),
128 847 QStringLiteral("CREATE TABLE IF NOT EXISTS cleanup (id INTEGER PRIMARY KEY, account_id TEXT NOT NULL, "
129 "registration_url TEXT NOT NULL, created_at INTEGER DEFAULT 0, attempts INTEGER DEFAULT 0)"),
130 847 QStringLiteral("CREATE TABLE IF NOT EXISTS stats_counters (scope TEXT NOT NULL, name TEXT NOT NULL, "
131 "value INTEGER DEFAULT 0, PRIMARY KEY(scope, name))"),
132 847 QStringLiteral("CREATE TABLE IF NOT EXISTS stats_hourly (scope TEXT NOT NULL, hour INTEGER NOT NULL, "
133 "name TEXT NOT NULL, value INTEGER DEFAULT 0, PRIMARY KEY(scope, hour, name))"),
134 847 QStringLiteral("CREATE TABLE IF NOT EXISTS stats_latency (id INTEGER PRIMARY KEY, scope TEXT NOT NULL, "
135 "kind TEXT NOT NULL, millis INTEGER NOT NULL, at INTEGER NOT NULL)"),
136 847 QStringLiteral("CREATE TABLE IF NOT EXISTS stats_events (id INTEGER PRIMARY KEY, scope TEXT NOT NULL, "
137 "at INTEGER NOT NULL, kind TEXT NOT NULL, detail TEXT DEFAULT '')"),
138 847 QStringLiteral("CREATE TABLE IF NOT EXISTS stats_scopes (scope TEXT PRIMARY KEY, since INTEGER DEFAULT 0)"),
139 847 QStringLiteral("CREATE INDEX IF NOT EXISTS idx_latency_scope ON stats_latency(scope, kind, id)"),
140 847 QStringLiteral("CREATE INDEX IF NOT EXISTS idx_events_scope ON stats_events(scope, id)"),
141
2/4
✓ Branch 134 → 135 taken 10164 times.
✓ Branch 134 → 136 taken 847 times.
✗ Branch 229 → 230 not taken.
✗ Branch 229 → 231 not taken.
12705 QStringLiteral("PRAGMA user_version = %1").arg(SchemaVersion)};
142
2/3
✓ Branch 149 → 150 taken 847 times.
✗ Branch 150 → 151 not taken.
✓ Branch 150 → 152 taken 847 times.
847 if (!m_db.transaction()) return false;
143
2/2
✓ Branch 163 → 154 taken 10164 times.
✓ Branch 163 → 164 taken 847 times.
11011 for (const QString &statement : statements) {
144
2/3
✓ Branch 156 → 157 taken 10164 times.
✗ Branch 158 → 159 not taken.
✓ Branch 158 → 161 taken 10164 times.
10164 if (!exec(statement)) {
145 ✗ m_db.rollback();
146 ✗ return false;
147 }
148 }
149
1/1
✓ Branch 164 → 165 taken 847 times.
847 return m_db.commit();
150 12725 }
151
152 861 QString DavPushStore::installationId() {
153
1/1
✓ Branch 2 → 3 taken 861 times.
861 QSqlQuery q(m_db);
154
1/1
✓ Branch 6 → 7 taken 861 times.
861 q.prepare(QStringLiteral("SELECT value FROM meta WHERE key = 'installation_id'"));
155
16/22
✓ Branch 9 → 10 taken 861 times.
✓ Branch 10 → 11 taken 861 times.
✗ Branch 10 → 18 not taken.
✓ Branch 11 → 12 taken 861 times.
✓ Branch 12 → 13 taken 17 times.
✓ Branch 12 → 18 taken 844 times.
✓ Branch 13 → 14 taken 17 times.
✓ Branch 14 → 15 taken 17 times.
✓ Branch 16 → 17 taken 17 times.
✗ Branch 16 → 18 not taken.
✓ Branch 19 → 20 taken 17 times.
✓ Branch 19 → 21 taken 844 times.
✓ Branch 21 → 22 taken 17 times.
✓ Branch 21 → 23 taken 844 times.
✓ Branch 23 → 24 taken 17 times.
✓ Branch 23 → 29 taken 844 times.
✓ Branch 24 → 25 taken 17 times.
✓ Branch 25 → 26 taken 17 times.
✗ Branch 54 → 55 not taken.
✗ Branch 54 → 56 not taken.
✗ Branch 58 → 59 not taken.
✗ Branch 58 → 60 not taken.
861 if (q.exec() && q.next() && !q.value(0).toString().isEmpty()) return q.value(0).toString();
156
2/2
✓ Branch 29 → 30 taken 844 times.
✓ Branch 30 → 31 taken 844 times.
844 const QString id = QUuid::createUuid().toString(QUuid::WithoutBraces);
157
3/5
✓ Branch 36 → 37 taken 844 times.
✓ Branch 41 → 42 taken 844 times.
✓ Branch 41 → 43 taken 844 times.
✗ Branch 75 → 76 not taken.
✗ Branch 75 → 77 not taken.
1688 exec(QStringLiteral("INSERT OR REPLACE INTO meta (key, value) VALUES ('installation_id', ?)"), {id});
158 844 return id;
159 1705 }
160
161 1741 std::optional<DavPushCollectionRecord> DavPushStore::collection(const QString &accountId, const QString &path) const {
162
1/1
✓ Branch 2 → 3 taken 1741 times.
1741 QSqlQuery q(m_db);
163
1/1
✓ Branch 9 → 10 taken 1741 times.
5223 q.prepare(QStringLiteral("SELECT %1 FROM collections WHERE account_id = ? AND path = ?")
164
1/1
✓ Branch 8 → 9 taken 1741 times.
5223 .arg(QLatin1String(kCollectionColumns)));
165
1/1
✓ Branch 15 → 16 taken 1741 times.
1741 q.addBindValue(accountId);
166
1/1
✓ Branch 19 → 20 taken 1741 times.
1741 q.addBindValue(path);
167
7/8
✓ Branch 21 → 22 taken 1741 times.
✓ Branch 22 → 23 taken 1741 times.
✗ Branch 22 → 25 not taken.
✓ Branch 23 → 24 taken 1741 times.
✓ Branch 24 → 25 taken 378 times.
✓ Branch 24 → 26 taken 1363 times.
✓ Branch 27 → 28 taken 378 times.
✓ Branch 27 → 29 taken 1363 times.
1741 if (!q.exec() || !q.next()) return std::nullopt;
168
1/1
✓ Branch 29 → 30 taken 1363 times.
1363 return collectionFrom(q);
169 1741 }
170
171 289 QList<DavPushCollectionRecord> DavPushStore::collections(const QString &accountId) const {
172 289 QList<DavPushCollectionRecord> result;
173
1/1
✓ Branch 2 → 3 taken 289 times.
289 QSqlQuery q(m_db);
174
1/1
✓ Branch 9 → 10 taken 289 times.
867 q.prepare(QStringLiteral("SELECT %1 FROM collections WHERE ? = '' OR account_id = ? ORDER BY account_id, path")
175
1/1
✓ Branch 8 → 9 taken 289 times.
578 .arg(QLatin1String(kCollectionColumns)));
176 // A null QString binds as SQL NULL; "" means "all accounts".
177
4/4
✓ Branch 14 → 15 taken 2 times.
✓ Branch 14 → 18 taken 287 times.
✓ Branch 19 → 20 taken 2 times.
✓ Branch 19 → 21 taken 287 times.
291 const QString filter = accountId.isEmpty() ? QStringLiteral("") : accountId;
178
1/1
✓ Branch 23 → 24 taken 289 times.
289 q.addBindValue(filter);
179
1/1
✓ Branch 27 → 28 taken 289 times.
289 q.addBindValue(filter);
180
2/3
✓ Branch 29 → 30 taken 289 times.
✓ Branch 30 → 31 taken 289 times.
✗ Branch 30 → 38 not taken.
289 if (q.exec())
181
5/5
✓ Branch 32 → 33 taken 485 times.
✓ Branch 33 → 34 taken 485 times.
✓ Branch 36 → 37 taken 774 times.
✓ Branch 37 → 32 taken 485 times.
✓ Branch 37 → 38 taken 289 times.
774 while (q.next()) result.append(collectionFrom(q));
182 289 return result;
183 289 }
184
185 319 bool DavPushStore::saveCollection(const DavPushCollectionRecord &r) {
186
3/5
✓ Branch 30 → 31 taken 319 times.
✓ Branch 37 → 38 taken 6380 times.
✓ Branch 37 → 39 taken 319 times.
✗ Branch 56 → 57 not taken.
✗ Branch 56 → 58 not taken.
7656 return exec(QStringLiteral("INSERT OR REPLACE INTO collections (%1) VALUES "
187 "(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")
188
1/1
✓ Branch 29 → 30 taken 319 times.
957 .arg(QLatin1String(kCollectionColumns)),
189 319 {r.accountId, r.path, davPushCapabilityName(r.offer.capability), int(r.offer.profile), r.offer.topic,
190 1595 r.offer.vapidPublicKey, r.offer.reason, r.checkedAt, r.registrationUrl, r.registrationReceiver,
191 2233 r.expiresAt, r.registeredAt, r.renewedAt, r.lastError, r.lastErrorAt, r.nextRetryAt, r.failures,
192 638 r.lastPushAt, r.lastSyncAt, r.registrationEndpoint});
193 319 }
194
195 4 bool DavPushStore::removeCollection(const QString &accountId, const QString &path) {
196
3/5
✓ Branch 8 → 9 taken 4 times.
✓ Branch 14 → 15 taken 8 times.
✓ Branch 14 → 16 taken 4 times.
✗ Branch 27 → 28 not taken.
✗ Branch 27 → 29 not taken.
12 return exec(QStringLiteral("DELETE FROM collections WHERE account_id = ? AND path = ?"), {accountId, path});
197 4 }
198
199 103 std::optional<DavPushReceiverRecord> DavPushStore::receiver(const QString &accountId) const {
200
1/1
✓ Branch 2 → 3 taken 103 times.
103 QSqlQuery q(m_db);
201
1/1
✓ Branch 6 → 7 taken 103 times.
103 q.prepare(QStringLiteral("SELECT account_id, receiver_id, provider, server, created_at, generation "
202 "FROM receivers WHERE account_id = ?"));
203
1/1
✓ Branch 11 → 12 taken 103 times.
103 q.addBindValue(accountId);
204
7/8
✓ Branch 13 → 14 taken 103 times.
✓ Branch 14 → 15 taken 103 times.
✗ Branch 14 → 17 not taken.
✓ Branch 15 → 16 taken 103 times.
✓ Branch 16 → 17 taken 99 times.
✓ Branch 16 → 18 taken 4 times.
✓ Branch 19 → 20 taken 99 times.
✓ Branch 19 → 21 taken 4 times.
103 if (!q.exec() || !q.next()) return std::nullopt;
205
7/15
✓ Branch 21 → 22 taken 4 times.
✓ Branch 22 → 23 taken 4 times.
✓ Branch 23 → 24 taken 4 times.
✓ Branch 24 → 25 taken 4 times.
✓ Branch 25 → 26 taken 4 times.
✓ Branch 26 → 27 taken 4 times.
✓ Branch 28 → 29 taken 4 times.
✗ Branch 61 → 62 not taken.
✗ Branch 61 → 63 not taken.
✗ Branch 67 → 68 not taken.
✗ Branch 67 → 69 not taken.
✗ Branch 73 → 74 not taken.
✗ Branch 73 → 75 not taken.
✗ Branch 79 → 80 not taken.
✗ Branch 79 → 81 not taken.
16 return DavPushReceiverRecord{q.value(0).toString(), q.value(1).toString(), q.value(2).toString(),
206
5/5
✓ Branch 27 → 28 taken 4 times.
✓ Branch 29 → 30 taken 4 times.
✓ Branch 30 → 31 taken 4 times.
✓ Branch 31 → 32 taken 4 times.
✓ Branch 32 → 33 taken 4 times.
12 q.value(3).toString(), q.value(4).toLongLong(), q.value(5).toInt()};
207 103 }
208
209 34 bool DavPushStore::saveReceiver(const DavPushReceiverRecord &r) {
210
3/5
✓ Branch 12 → 13 taken 34 times.
✓ Branch 18 → 19 taken 204 times.
✓ Branch 18 → 20 taken 34 times.
✗ Branch 31 → 32 not taken.
✗ Branch 31 → 33 not taken.
272 return exec(QStringLiteral("INSERT OR REPLACE INTO receivers (account_id, receiver_id, provider, server, "
211 "created_at, generation) VALUES (?, ?, ?, ?, ?, ?)"),
212 68 {r.accountId, r.receiverId, r.provider, r.server, r.createdAt, r.generation});
213 34 }
214
215 1 bool DavPushStore::removeReceiver(const QString &accountId) {
216
3/5
✓ Branch 7 → 8 taken 1 time.
✓ Branch 13 → 14 taken 1 time.
✓ Branch 13 → 15 taken 1 time.
✗ Branch 26 → 27 not taken.
✗ Branch 26 → 28 not taken.
2 return exec(QStringLiteral("DELETE FROM receivers WHERE account_id = ?"), {accountId});
217 1 }
218
219 ✗ QStringList DavPushStore::receiverAccounts() const {
220 ✗ QStringList accounts;
221 ✗ QSqlQuery q(m_db);
222 ✗ if (q.exec(QStringLiteral("SELECT account_id FROM receivers ORDER BY account_id")))
223 ✗ while (q.next()) accounts << q.value(0).toString();
224 ✗ return accounts;
225 ✗ }
226
227 217 bool DavPushStore::queueCleanup(const QString &accountId, const QString &registrationUrl, qint64 now) {
228
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 217 times.
217 if (registrationUrl.isEmpty()) return true;
229
1/1
✓ Branch 5 → 6 taken 217 times.
217 QSqlQuery count(m_db);
230
7/14
✓ Branch 9 → 10 taken 217 times.
✓ Branch 10 → 11 taken 217 times.
✗ Branch 10 → 17 not taken.
✓ Branch 11 → 12 taken 217 times.
✓ Branch 12 → 13 taken 217 times.
✗ Branch 12 → 17 not taken.
✓ Branch 22 → 23 taken 217 times.
✗ Branch 22 → 24 not taken.
✓ Branch 24 → 25 taken 5 times.
✓ Branch 24 → 34 taken 212 times.
✗ Branch 56 → 57 not taken.
✗ Branch 56 → 58 not taken.
✗ Branch 60 → 61 not taken.
✗ Branch 60 → 62 not taken.
868 if (count.exec(QStringLiteral("SELECT COUNT(*) FROM cleanup")) && count.next() &&
231
6/10
✓ Branch 13 → 14 taken 217 times.
✓ Branch 14 → 15 taken 217 times.
✓ Branch 15 → 16 taken 5 times.
✓ Branch 15 → 17 taken 212 times.
✓ Branch 18 → 19 taken 217 times.
✗ Branch 18 → 20 not taken.
✓ Branch 20 → 21 taken 217 times.
✗ Branch 20 → 22 not taken.
✗ Branch 52 → 53 not taken.
✗ Branch 52 → 54 not taken.
434 count.value(0).toInt() >= kMaxCleanupEntries) {
232 // Bounded: the oldest entry goes; the server expiry ends it anyway.
233
1/1
✓ Branch 29 → 30 taken 5 times.
10 exec(QStringLiteral("DELETE FROM cleanup WHERE id = (SELECT MIN(id) FROM cleanup)"));
234 }
235
3/5
✓ Branch 41 → 42 taken 217 times.
✓ Branch 47 → 48 taken 651 times.
✓ Branch 47 → 49 taken 217 times.
✗ Branch 82 → 83 not taken.
✗ Branch 82 → 84 not taken.
1085 return exec(QStringLiteral("INSERT INTO cleanup (account_id, registration_url, created_at) VALUES (?, ?, ?)"),
236 217 {accountId, registrationUrl, now});
237 434 }
238
239 88 QList<DavPushCleanupEntry> DavPushStore::cleanupEntries(const QString &accountId) const {
240 88 QList<DavPushCleanupEntry> result;
241
1/1
✓ Branch 2 → 3 taken 88 times.
88 QSqlQuery q(m_db);
242
1/1
✓ Branch 6 → 7 taken 88 times.
88 q.prepare(QStringLiteral("SELECT id, account_id, registration_url, created_at, attempts FROM cleanup "
243 "WHERE ? = '' OR account_id = ? ORDER BY id"));
244 // A null QString binds as SQL NULL; "" means "all accounts".
245
4/4
✓ Branch 10 → 11 taken 5 times.
✓ Branch 10 → 14 taken 83 times.
✓ Branch 15 → 16 taken 5 times.
✓ Branch 15 → 17 taken 83 times.
93 const QString filter = accountId.isEmpty() ? QStringLiteral("") : accountId;
246
1/1
✓ Branch 19 → 20 taken 88 times.
88 q.addBindValue(filter);
247
1/1
✓ Branch 23 → 24 taken 88 times.
88 q.addBindValue(filter);
248
2/3
✓ Branch 25 → 26 taken 88 times.
✓ Branch 26 → 27 taken 88 times.
✗ Branch 26 → 48 not taken.
88 if (q.exec())
249
3/3
✓ Branch 46 → 47 taken 916 times.
✓ Branch 47 → 28 taken 828 times.
✓ Branch 47 → 48 taken 88 times.
916 while (q.next())
250
3/3
✓ Branch 28 → 29 taken 828 times.
✓ Branch 29 → 30 taken 828 times.
✓ Branch 35 → 36 taken 828 times.
3312 result.append({q.value(0).toLongLong(), q.value(1).toString(), q.value(2).toString(), q.value(3).toLongLong(),
251
1/1
✓ Branch 37 → 38 taken 828 times.
1656 q.value(4).toInt()});
252 88 return result;
253 2572 }
254
255 10 bool DavPushStore::updateCleanupAttempt(qint64 id) {
256 // S106-078 (INV-12): the attempts are bounded — after kMaxCleanupAttempts
257 // failed DELETEs the entry goes (the server's Expires ends the
258 // subscription anyway); it used to be retried at every converge forever.
259
7/21
✓ Branch 7 → 8 taken 10 times.
✓ Branch 8 → 9 taken 10 times.
✗ Branch 8 → 18 not taken.
✓ Branch 31 → 32 taken 10 times.
✗ Branch 31 → 33 not taken.
✓ Branch 33 → 34 taken 10 times.
✗ Branch 33 → 35 not taken.
✓ Branch 35 → 36 taken 10 times.
✗ Branch 35 → 39 not taken.
✓ Branch 37 → 38 taken 10 times.
✓ Branch 37 → 39 taken 10 times.
✗ Branch 65 → 66 not taken.
✗ Branch 65 → 67 not taken.
✗ Branch 69 → 70 not taken.
✗ Branch 69 → 71 not taken.
✗ Branch 73 → 74 not taken.
✗ Branch 73 → 75 not taken.
✗ Branch 77 → 78 not taken.
✗ Branch 77 → 81 not taken.
✗ Branch 79 → 80 not taken.
✗ Branch 79 → 81 not taken.
60 return exec(QStringLiteral("UPDATE cleanup SET attempts = attempts + 1 WHERE id = ?"), {id}) &&
260
9/25
✓ Branch 15 → 16 taken 10 times.
✓ Branch 16 → 17 taken 10 times.
✗ Branch 16 → 18 not taken.
✓ Branch 19 → 20 taken 10 times.
✗ Branch 19 → 21 not taken.
✓ Branch 21 → 22 taken 10 times.
✗ Branch 21 → 23 not taken.
✓ Branch 23 → 24 taken 10 times.
✗ Branch 23 → 25 not taken.
✓ Branch 25 → 26 taken 10 times.
✗ Branch 25 → 29 not taken.
✓ Branch 27 → 28 taken 20 times.
✓ Branch 27 → 29 taken 10 times.
✓ Branch 29 → 30 taken 10 times.
✗ Branch 29 → 31 not taken.
✗ Branch 41 → 42 not taken.
✗ Branch 41 → 43 not taken.
✗ Branch 45 → 46 not taken.
✗ Branch 45 → 47 not taken.
✗ Branch 49 → 50 not taken.
✗ Branch 49 → 51 not taken.
✗ Branch 53 → 54 not taken.
✗ Branch 53 → 57 not taken.
✗ Branch 55 → 56 not taken.
✗ Branch 55 → 57 not taken.
50 exec(QStringLiteral("DELETE FROM cleanup WHERE id = ? AND attempts >= ?"), {id, kMaxCleanupAttempts});
261 20 }
262
263 12 bool DavPushStore::removeCleanup(qint64 id) {
264
3/5
✓ Branch 7 → 8 taken 12 times.
✓ Branch 13 → 14 taken 12 times.
✓ Branch 13 → 15 taken 12 times.
✗ Branch 26 → 27 not taken.
✗ Branch 26 → 28 not taken.
24 return exec(QStringLiteral("DELETE FROM cleanup WHERE id = ?"), {id});
265 12 }
266
267 2 bool DavPushStore::removeCleanupForAccount(const QString &accountId) {
268
3/5
✓ Branch 7 → 8 taken 2 times.
✓ Branch 13 → 14 taken 2 times.
✓ Branch 13 → 15 taken 2 times.
✗ Branch 26 → 27 not taken.
✗ Branch 26 → 28 not taken.
4 return exec(QStringLiteral("DELETE FROM cleanup WHERE account_id = ?"), {accountId});
269 2 }
270
271 35 bool DavPushStore::beginTransaction() { return m_db.transaction(); }
272
273 35 bool DavPushStore::commitTransaction() { return m_db.commit(); }
274
275 ✗ void DavPushStore::rollbackTransaction() { m_db.rollback(); }
276
277 271 bool DavPushStore::addCounter(const QString &scope, const QString &name, qint64 delta, qint64 now) {
278
5/18
✓ Branch 54 → 55 taken 271 times.
✗ Branch 54 → 56 not taken.
✓ Branch 56 → 57 taken 271 times.
✗ Branch 56 → 58 not taken.
✓ Branch 58 → 59 taken 271 times.
✗ Branch 58 → 62 not taken.
✓ Branch 60 → 61 taken 542 times.
✓ Branch 60 → 62 taken 271 times.
✗ Branch 112 → 113 not taken.
✗ Branch 112 → 114 not taken.
✗ Branch 116 → 117 not taken.
✗ Branch 116 → 118 not taken.
✗ Branch 120 → 121 not taken.
✗ Branch 120 → 122 not taken.
✗ Branch 124 → 125 not taken.
✗ Branch 124 → 128 not taken.
✗ Branch 126 → 127 not taken.
✗ Branch 126 → 128 not taken.
1084 return exec(QStringLiteral("INSERT INTO stats_scopes (scope, since) VALUES (?, ?) ON CONFLICT(scope) DO NOTHING"),
279 271 {scope, now}) &&
280
8/23
✓ Branch 17 → 18 taken 271 times.
✓ Branch 18 → 19 taken 271 times.
✗ Branch 18 → 31 not taken.
✓ Branch 44 → 45 taken 271 times.
✗ Branch 44 → 46 not taken.
✓ Branch 46 → 47 taken 271 times.
✗ Branch 46 → 48 not taken.
✓ Branch 48 → 49 taken 271 times.
✗ Branch 48 → 52 not taken.
✓ Branch 50 → 51 taken 813 times.
✓ Branch 50 → 52 taken 271 times.
✓ Branch 52 → 53 taken 271 times.
✗ Branch 52 → 54 not taken.
✗ Branch 88 → 89 not taken.
✗ Branch 88 → 90 not taken.
✗ Branch 92 → 93 not taken.
✗ Branch 92 → 94 not taken.
✗ Branch 96 → 97 not taken.
✗ Branch 96 → 98 not taken.
✗ Branch 100 → 101 not taken.
✗ Branch 100 → 104 not taken.
✗ Branch 102 → 103 not taken.
✗ Branch 102 → 104 not taken.
1355 exec(QStringLiteral("INSERT INTO stats_counters (scope, name, value) VALUES (?, ?, ?) "
281 "ON CONFLICT(scope, name) DO UPDATE SET value = value + excluded.value"),
282
2/3
✓ Branch 8 → 9 taken 271 times.
✓ Branch 9 → 10 taken 271 times.
✗ Branch 9 → 31 not taken.
1084 {scope, name, delta}) &&
283
8/23
✓ Branch 28 → 29 taken 271 times.
✓ Branch 29 → 30 taken 271 times.
✗ Branch 29 → 31 not taken.
✓ Branch 34 → 35 taken 271 times.
✗ Branch 34 → 36 not taken.
✓ Branch 36 → 37 taken 271 times.
✗ Branch 36 → 38 not taken.
✓ Branch 38 → 39 taken 271 times.
✗ Branch 38 → 42 not taken.
✓ Branch 40 → 41 taken 1084 times.
✓ Branch 40 → 42 taken 271 times.
✓ Branch 42 → 43 taken 271 times.
✗ Branch 42 → 44 not taken.
✗ Branch 64 → 65 not taken.
✗ Branch 64 → 66 not taken.
✗ Branch 68 → 69 not taken.
✗ Branch 68 → 70 not taken.
✗ Branch 72 → 73 not taken.
✗ Branch 72 → 74 not taken.
✗ Branch 76 → 77 not taken.
✗ Branch 76 → 80 not taken.
✗ Branch 78 → 79 not taken.
✗ Branch 78 → 80 not taken.
1626 exec(QStringLiteral("INSERT INTO stats_hourly (scope, hour, name, value) VALUES (?, ?, ?, ?) "
284 "ON CONFLICT(scope, hour, name) DO UPDATE SET value = value + excluded.value"),
285
1/2
✓ Branch 32 → 33 taken 271 times.
✗ Branch 32 → 34 not taken.
542 {scope, hourOf(now), name, delta});
286 813 }
287
288 159 qint64 DavPushStore::counter(const QString &scope, const QString &name) const {
289
1/1
✓ Branch 2 → 3 taken 159 times.
159 QSqlQuery q(m_db);
290
1/1
✓ Branch 6 → 7 taken 159 times.
159 q.prepare(QStringLiteral("SELECT value FROM stats_counters WHERE scope = ? AND name = ?"));
291
1/1
✓ Branch 11 → 12 taken 159 times.
159 q.addBindValue(scope);
292
1/1
✓ Branch 15 → 16 taken 159 times.
159 q.addBindValue(name);
293
9/12
✓ Branch 17 → 18 taken 159 times.
✓ Branch 18 → 19 taken 159 times.
✗ Branch 18 → 24 not taken.
✓ Branch 19 → 20 taken 159 times.
✓ Branch 20 → 21 taken 6 times.
✓ Branch 20 → 24 taken 153 times.
✓ Branch 21 → 22 taken 6 times.
✓ Branch 22 → 23 taken 6 times.
✓ Branch 26 → 27 taken 6 times.
✓ Branch 26 → 28 taken 153 times.
✗ Branch 45 → 46 not taken.
✗ Branch 45 → 47 not taken.
318 return q.exec() && q.next() ? q.value(0).toLongLong() : 0;
294 159 }
295
296 37 qint64 DavPushStore::periodSum(const QString &scope, const QString &name, qint64 since) const {
297
1/1
✓ Branch 2 → 3 taken 37 times.
37 QSqlQuery q(m_db);
298
1/1
✓ Branch 6 → 7 taken 37 times.
37 q.prepare(QStringLiteral("SELECT COALESCE(SUM(value), 0) FROM stats_hourly WHERE scope = ? AND name = ? AND hour >= ?"));
299
1/1
✓ Branch 11 → 12 taken 37 times.
37 q.addBindValue(scope);
300
1/1
✓ Branch 15 → 16 taken 37 times.
37 q.addBindValue(name);
301
1/1
✓ Branch 20 → 21 taken 37 times.
37 q.addBindValue(hourOf(since));
302
7/12
✓ Branch 22 → 23 taken 37 times.
✓ Branch 23 → 24 taken 37 times.
✗ Branch 23 → 29 not taken.
✓ Branch 24 → 25 taken 37 times.
✓ Branch 25 → 26 taken 37 times.
✗ Branch 25 → 29 not taken.
✓ Branch 26 → 27 taken 37 times.
✓ Branch 27 → 28 taken 37 times.
✓ Branch 31 → 32 taken 37 times.
✗ Branch 31 → 33 not taken.
✗ Branch 54 → 55 not taken.
✗ Branch 54 → 56 not taken.
74 return q.exec() && q.next() ? q.value(0).toLongLong() : 0;
303 37 }
304
305 321 bool DavPushStore::addLatency(const QString &scope, const QString &kind, qint64 millis, qint64 now) {
306
5/18
✓ Branch 37 → 38 taken 321 times.
✗ Branch 37 → 39 not taken.
✓ Branch 39 → 40 taken 321 times.
✗ Branch 39 → 41 not taken.
✓ Branch 41 → 42 taken 321 times.
✗ Branch 41 → 45 not taken.
✓ Branch 43 → 44 taken 1284 times.
✓ Branch 43 → 45 taken 321 times.
✗ Branch 71 → 72 not taken.
✗ Branch 71 → 73 not taken.
✗ Branch 75 → 76 not taken.
✗ Branch 75 → 77 not taken.
✗ Branch 79 → 80 not taken.
✗ Branch 79 → 81 not taken.
✗ Branch 83 → 84 not taken.
✗ Branch 83 → 87 not taken.
✗ Branch 85 → 86 not taken.
✗ Branch 85 → 87 not taken.
1926 return exec(QStringLiteral("INSERT INTO stats_latency (scope, kind, millis, at) VALUES (?, ?, ?, ?)"),
307
2/3
✓ Branch 10 → 11 taken 321 times.
✓ Branch 11 → 12 taken 321 times.
✗ Branch 11 → 24 not taken.
1284 {scope, kind, millis, now}) &&
308
8/23
✓ Branch 21 → 22 taken 321 times.
✓ Branch 22 → 23 taken 321 times.
✗ Branch 22 → 24 not taken.
✓ Branch 27 → 28 taken 321 times.
✗ Branch 27 → 29 not taken.
✓ Branch 29 → 30 taken 321 times.
✗ Branch 29 → 31 not taken.
✓ Branch 31 → 32 taken 321 times.
✗ Branch 31 → 35 not taken.
✓ Branch 33 → 34 taken 1605 times.
✓ Branch 33 → 35 taken 321 times.
✓ Branch 35 → 36 taken 321 times.
✗ Branch 35 → 37 not taken.
✗ Branch 47 → 48 not taken.
✗ Branch 47 → 49 not taken.
✗ Branch 51 → 52 not taken.
✗ Branch 51 → 53 not taken.
✗ Branch 55 → 56 not taken.
✗ Branch 55 → 57 not taken.
✗ Branch 59 → 60 not taken.
✗ Branch 59 → 63 not taken.
✗ Branch 61 → 62 not taken.
✗ Branch 61 → 63 not taken.
2247 exec(QStringLiteral("DELETE FROM stats_latency WHERE scope = ? AND kind = ? AND id NOT IN "
309 "(SELECT id FROM stats_latency WHERE scope = ? AND kind = ? ORDER BY id DESC LIMIT ?)"),
310
1/2
✓ Branch 25 → 26 taken 321 times.
✗ Branch 25 → 27 not taken.
642 {scope, kind, scope, kind, kMaxLatencySamples});
311 642 }
312
313 11 QList<qint64> DavPushStore::latencies(const QString &scope, const QString &kind) const {
314 11 QList<qint64> values;
315
1/1
✓ Branch 2 → 3 taken 11 times.
11 QSqlQuery q(m_db);
316
1/1
✓ Branch 6 → 7 taken 11 times.
11 q.prepare(QStringLiteral("SELECT millis FROM stats_latency WHERE scope = ? AND kind = ? ORDER BY id"));
317
1/1
✓ Branch 11 → 12 taken 11 times.
11 q.addBindValue(scope);
318
1/1
✓ Branch 15 → 16 taken 11 times.
11 q.addBindValue(kind);
319
2/3
✓ Branch 17 → 18 taken 11 times.
✓ Branch 18 → 19 taken 11 times.
✗ Branch 18 → 27 not taken.
11 if (q.exec())
320
6/6
✓ Branch 20 → 21 taken 276 times.
✓ Branch 21 → 22 taken 276 times.
✓ Branch 22 → 23 taken 276 times.
✓ Branch 25 → 26 taken 287 times.
✓ Branch 26 → 20 taken 276 times.
✓ Branch 26 → 27 taken 11 times.
287 while (q.next()) values << q.value(0).toLongLong();
321 11 return values;
322 11 }
323
324 264 bool DavPushStore::addEvent(const QString &scope, const QString &kind, const QString &detail, qint64 now) {
325
5/18
✓ Branch 36 → 37 taken 264 times.
✗ Branch 36 → 38 not taken.
✓ Branch 38 → 39 taken 264 times.
✗ Branch 38 → 40 not taken.
✓ Branch 40 → 41 taken 264 times.
✗ Branch 40 → 44 not taken.
✓ Branch 42 → 43 taken 1056 times.
✓ Branch 42 → 44 taken 264 times.
✗ Branch 72 → 73 not taken.
✗ Branch 72 → 74 not taken.
✗ Branch 76 → 77 not taken.
✗ Branch 76 → 78 not taken.
✗ Branch 80 → 81 not taken.
✗ Branch 80 → 82 not taken.
✗ Branch 84 → 85 not taken.
✗ Branch 84 → 88 not taken.
✗ Branch 86 → 87 not taken.
✗ Branch 86 → 88 not taken.
1584 return exec(QStringLiteral("INSERT INTO stats_events (scope, at, kind, detail) VALUES (?, ?, ?, ?)"),
326
3/7
✓ Branch 11 → 12 taken 264 times.
✓ Branch 12 → 13 taken 264 times.
✗ Branch 12 → 23 not taken.
✓ Branch 44 → 45 taken 264 times.
✗ Branch 44 → 46 not taken.
✗ Branch 90 → 91 not taken.
✗ Branch 90 → 92 not taken.
1320 {scope, now, kind, detail.left(300)}) &&
327
8/23
✓ Branch 20 → 21 taken 264 times.
✓ Branch 21 → 22 taken 264 times.
✗ Branch 21 → 23 not taken.
✓ Branch 26 → 27 taken 264 times.
✗ Branch 26 → 28 not taken.
✓ Branch 28 → 29 taken 264 times.
✗ Branch 28 → 30 not taken.
✓ Branch 30 → 31 taken 264 times.
✗ Branch 30 → 34 not taken.
✓ Branch 32 → 33 taken 792 times.
✓ Branch 32 → 34 taken 264 times.
✓ Branch 34 → 35 taken 264 times.
✗ Branch 34 → 36 not taken.
✗ Branch 48 → 49 not taken.
✗ Branch 48 → 50 not taken.
✗ Branch 52 → 53 not taken.
✗ Branch 52 → 54 not taken.
✗ Branch 56 → 57 not taken.
✗ Branch 56 → 58 not taken.
✗ Branch 60 → 61 not taken.
✗ Branch 60 → 64 not taken.
✗ Branch 62 → 63 not taken.
✗ Branch 62 → 64 not taken.
1320 exec(QStringLiteral("DELETE FROM stats_events WHERE scope = ? AND id NOT IN "
328 "(SELECT id FROM stats_events WHERE scope = ? ORDER BY id DESC LIMIT ?)"),
329
1/2
✓ Branch 24 → 25 taken 264 times.
✗ Branch 24 → 26 not taken.
528 {scope, scope, kMaxEvents});
330 528 }
331
332 5 QList<DavPushStore::Event> DavPushStore::events(const QString &scope) const {
333 5 QList<Event> result;
334
1/1
✓ Branch 2 → 3 taken 5 times.
5 QSqlQuery q(m_db);
335
1/1
✓ Branch 6 → 7 taken 5 times.
5 q.prepare(QStringLiteral("SELECT at, kind, detail FROM stats_events WHERE scope = ? ORDER BY id DESC"));
336
1/1
✓ Branch 11 → 12 taken 5 times.
5 q.addBindValue(scope);
337
2/3
✓ Branch 13 → 14 taken 5 times.
✓ Branch 14 → 15 taken 5 times.
✗ Branch 14 → 30 not taken.
5 if (q.exec())
338
5/5
✓ Branch 16 → 17 taken 100 times.
✓ Branch 17 → 18 taken 100 times.
✓ Branch 28 → 29 taken 105 times.
✓ Branch 29 → 16 taken 100 times.
✓ Branch 29 → 30 taken 5 times.
105 while (q.next()) result.append({q.value(0).toLongLong(), q.value(1).toString(), q.value(2).toString()});
339 5 return result;
340 105 }
341
342 2 bool DavPushStore::resetStatistics(const QString &scope, qint64 now) {
343
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 2 times.
2 if (!m_db.transaction()) return false;
344
5/18
✓ Branch 85 → 86 taken 2 times.
✗ Branch 85 → 87 not taken.
✓ Branch 87 → 88 taken 2 times.
✗ Branch 87 → 89 not taken.
✓ Branch 89 → 90 taken 2 times.
✗ Branch 89 → 93 not taken.
✓ Branch 91 → 92 taken 2 times.
✓ Branch 91 → 93 taken 2 times.
✗ Branch 195 → 196 not taken.
✗ Branch 195 → 197 not taken.
✗ Branch 199 → 200 not taken.
✗ Branch 199 → 201 not taken.
✗ Branch 203 → 204 not taken.
✗ Branch 203 → 205 not taken.
✗ Branch 207 → 208 not taken.
✗ Branch 207 → 211 not taken.
✗ Branch 209 → 210 not taken.
✗ Branch 209 → 211 not taken.
8 const bool ok = exec(QStringLiteral("DELETE FROM stats_counters WHERE scope = ?"), {scope}) &&
345
8/23
✓ Branch 17 → 18 taken 2 times.
✓ Branch 18 → 19 taken 2 times.
✗ Branch 18 → 42 not taken.
✓ Branch 75 → 76 taken 2 times.
✗ Branch 75 → 77 not taken.
✓ Branch 77 → 78 taken 2 times.
✗ Branch 77 → 79 not taken.
✓ Branch 79 → 80 taken 2 times.
✗ Branch 79 → 83 not taken.
✓ Branch 81 → 82 taken 2 times.
✓ Branch 81 → 83 taken 2 times.
✓ Branch 83 → 84 taken 2 times.
✗ Branch 83 → 85 not taken.
✗ Branch 171 → 172 not taken.
✗ Branch 171 → 173 not taken.
✗ Branch 175 → 176 not taken.
✗ Branch 175 → 177 not taken.
✗ Branch 179 → 180 not taken.
✗ Branch 179 → 181 not taken.
✗ Branch 183 → 184 not taken.
✗ Branch 183 → 187 not taken.
✗ Branch 185 → 186 not taken.
✗ Branch 185 → 187 not taken.
8 exec(QStringLiteral("DELETE FROM stats_hourly WHERE scope = ?"), {scope}) &&
346
8/23
✓ Branch 24 → 25 taken 2 times.
✓ Branch 25 → 26 taken 2 times.
✗ Branch 25 → 42 not taken.
✓ Branch 65 → 66 taken 2 times.
✗ Branch 65 → 67 not taken.
✓ Branch 67 → 68 taken 2 times.
✗ Branch 67 → 69 not taken.
✓ Branch 69 → 70 taken 2 times.
✗ Branch 69 → 73 not taken.
✓ Branch 71 → 72 taken 2 times.
✓ Branch 71 → 73 taken 2 times.
✓ Branch 73 → 74 taken 2 times.
✗ Branch 73 → 75 not taken.
✗ Branch 147 → 148 not taken.
✗ Branch 147 → 149 not taken.
✗ Branch 151 → 152 not taken.
✗ Branch 151 → 153 not taken.
✗ Branch 155 → 156 not taken.
✗ Branch 155 → 157 not taken.
✗ Branch 159 → 160 not taken.
✗ Branch 159 → 163 not taken.
✗ Branch 161 → 162 not taken.
✗ Branch 161 → 163 not taken.
8 exec(QStringLiteral("DELETE FROM stats_latency WHERE scope = ?"), {scope}) &&
347
10/26
✓ Branch 10 → 11 taken 2 times.
✓ Branch 11 → 12 taken 2 times.
✗ Branch 11 → 42 not taken.
✓ Branch 31 → 32 taken 2 times.
✓ Branch 32 → 33 taken 2 times.
✗ Branch 32 → 42 not taken.
✓ Branch 55 → 56 taken 2 times.
✗ Branch 55 → 57 not taken.
✓ Branch 57 → 58 taken 2 times.
✗ Branch 57 → 59 not taken.
✓ Branch 59 → 60 taken 2 times.
✗ Branch 59 → 63 not taken.
✓ Branch 61 → 62 taken 2 times.
✓ Branch 61 → 63 taken 2 times.
✓ Branch 63 → 64 taken 2 times.
✗ Branch 63 → 65 not taken.
✗ Branch 123 → 124 not taken.
✗ Branch 123 → 125 not taken.
✗ Branch 127 → 128 not taken.
✗ Branch 127 → 129 not taken.
✗ Branch 131 → 132 not taken.
✗ Branch 131 → 133 not taken.
✗ Branch 135 → 136 not taken.
✗ Branch 135 → 139 not taken.
✗ Branch 137 → 138 not taken.
✗ Branch 137 → 139 not taken.
14 exec(QStringLiteral("DELETE FROM stats_events WHERE scope = ?"), {scope}) &&
348
9/25
✓ Branch 39 → 40 taken 2 times.
✓ Branch 40 → 41 taken 2 times.
✗ Branch 40 → 42 not taken.
✓ Branch 43 → 44 taken 2 times.
✗ Branch 43 → 45 not taken.
✓ Branch 45 → 46 taken 2 times.
✗ Branch 45 → 47 not taken.
✓ Branch 47 → 48 taken 2 times.
✗ Branch 47 → 49 not taken.
✓ Branch 49 → 50 taken 2 times.
✗ Branch 49 → 53 not taken.
✓ Branch 51 → 52 taken 4 times.
✓ Branch 51 → 53 taken 2 times.
✓ Branch 53 → 54 taken 2 times.
✗ Branch 53 → 55 not taken.
✗ Branch 99 → 100 not taken.
✗ Branch 99 → 101 not taken.
✗ Branch 103 → 104 not taken.
✗ Branch 103 → 105 not taken.
✗ Branch 107 → 108 not taken.
✗ Branch 107 → 109 not taken.
✗ Branch 111 → 112 not taken.
✗ Branch 111 → 115 not taken.
✗ Branch 113 → 114 not taken.
✗ Branch 113 → 115 not taken.
8 exec(QStringLiteral("INSERT OR REPLACE INTO stats_scopes (scope, since) VALUES (?, ?)"), {scope, now});
349
1/2
✗ Branch 93 → 94 not taken.
✓ Branch 93 → 96 taken 2 times.
2 if (!ok) {
350 ✗ m_db.rollback();
351 ✗ return false;
352 }
353 2 return m_db.commit();
354 10 }
355
356 4 qint64 DavPushStore::statisticsSince(const QString &scope) const {
357
1/1
✓ Branch 2 → 3 taken 4 times.
4 QSqlQuery q(m_db);
358
1/1
✓ Branch 6 → 7 taken 4 times.
4 q.prepare(QStringLiteral("SELECT since FROM stats_scopes WHERE scope = ?"));
359
1/1
✓ Branch 11 → 12 taken 4 times.
4 q.addBindValue(scope);
360
9/12
✓ Branch 13 → 14 taken 4 times.
✓ Branch 14 → 15 taken 4 times.
✗ Branch 14 → 20 not taken.
✓ Branch 15 → 16 taken 4 times.
✓ Branch 16 → 17 taken 2 times.
✓ Branch 16 → 20 taken 2 times.
✓ Branch 17 → 18 taken 2 times.
✓ Branch 18 → 19 taken 2 times.
✓ Branch 22 → 23 taken 2 times.
✓ Branch 22 → 24 taken 2 times.
✗ Branch 37 → 38 not taken.
✗ Branch 37 → 39 not taken.
8 return q.exec() && q.next() ? q.value(0).toLongLong() : 0;
361 4 }
362
363 36 bool DavPushStore::pruneHourly(qint64 now) {
364
3/5
✓ Branch 8 → 9 taken 36 times.
✓ Branch 14 → 15 taken 36 times.
✓ Branch 14 → 16 taken 36 times.
✗ Branch 27 → 28 not taken.
✗ Branch 27 → 29 not taken.
108 return exec(QStringLiteral("DELETE FROM stats_hourly WHERE hour < ?"),
365 72 {hourOf(now) - qint64(kHourlyRetentionHours) * 3600});
366 36 }
367