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

app/core/SieveSession.h
Line Branch Exec Source
1 #pragma once
2
3 #include <QDateTime>
4 #include <QList>
5 #include <QObject>
6 #include <QPointer>
7
8 #include <functional>
9 #include <memory>
10
11 #include "data/SieveHistory.h"
12 #include "data/SieveMerge.h"
13 #include "data/SieveRuleCodec.h"
14 #include "service/ManageSieveClient.h"
15
16 // Rule session of one account (Sprint 95 S95.1.9, SIEVE-SESSION).
17 //
18 // Loads the active script (LISTSCRIPTS + GETSCRIPT, parse off the UI thread
19 // above 64 KiB), keeps the base version the user's edits start from, and
20 // uploads through one pipeline:
21 // CHECKSCRIPT (or temp-name PUTSCRIPT on pre-1.0 servers)
22 // → GETSCRIPT + SHA-256 compare with the base (D-7: merge or conflict)
23 // → HAVESPACE → PUTSCRIPT → SETACTIVE (new script only)
24 // → history entry of the replaced version (D-8)
25 // → GETSCRIPT again to detect a lost race.
26 //
27 // Invariants:
28 // - No PUTSCRIPT/SETACTIVE without a preceding successful check of the
29 // exact bytes uploaded (rule 3, gate G5); a rejected script is never
30 // activated.
31 // - A version changed elsewhere is never overwritten silently: disjoint
32 // changes merge, overlapping ones end in SaveOutcome::Conflict unless the
33 // caller passes `keepMine` (gate G6).
34 // - One upload at a time; later saves queue and re-run the check. Uploads
35 // and scripts-panel changes (activate/rename/delete) never overlap.
36 // - Capabilities are read after login(), from the upload's connection.
37 // - Uploads are never retried automatically; a transport failure keeps
38 // the caller's draft ("Not saved to the server").
39 // - A script that does not parse is published read-only and is only
40 // replaced by an explicit script-editor save.
41 // - Logs carry sizes, hashes, codes and timings, never script text.
42 class SieveSession : public QObject {
43 Q_OBJECT
44
45 public:
46 enum class Status { Idle, Loading, Ready, Saving, Offline, Error };
47
48 struct Config {
49 QString accountId;
50 SieveServerConfig server; // password is supplied by the provider
51 QString storageDirectory; // ~/.config/mailjd/sieve/<account-id>
52 };
53
54 using PasswordCallback = std::function<void(bool ok, const QByteArray &password)>;
55 using CredentialProvider = std::function<void(PasswordCallback)>;
56
57 struct SaveOptions {
58 bool keepMine = false; // conflict resolution "Keep mine"
59 QString reason; // for logs: "rule", "script", "rename", …
60 // S95.3.10: stop before uploading a merge result (Kind::MergeReview)
61 // so the caller can show it; the reviewed upload passes the server
62 // version it was merged with as `expectedServer`.
63 bool reviewMerge = false;
64 QByteArray expectedServer;
65 // S106-319: the script the bytes were rendered from (null: the session's
66 // base when the upload runs). An edit rendered while an earlier save was
67 // in flight has the older base; it is merged onto the server version
68 // like a change made elsewhere instead of overwriting the earlier save.
69 QByteArray renderedFrom;
70 };
71
72 struct SaveOutcome {
73 enum class Kind { Saved, Merged, Conflict, Rejected, Quota, Failed, NotLoaded, MergeReview };
74 Kind kind = Kind::Failed;
75 QString message; // technical detail ("Details")
76 QList<ManageSieve::ServerError> errors; // CHECKSCRIPT/PUTSCRIPT positions
77 Sieve::MergeResult merge; // Merged / Conflict
78 QByteArray theirs; // server version on Conflict
79 QByteArray uploaded; // bytes now on the server
80 bool raceDetected = false; // changed again during the upload
81 ManageSieveClient::ErrorKind transportError = ManageSieveClient::ErrorKind::None;
82
83
4/4
✓ Branch 2 → 3 taken 75 times.
✓ Branch 2 → 4 taken 542 times.
✓ Branch 3 → 4 taken 7 times.
✓ Branch 3 → 5 taken 68 times.
617 bool ok() const { return kind == Kind::Saved || kind == Kind::Merged; }
84 };
85 using SaveCallback = std::function<void(const SaveOutcome &)>;
86 using DoneCallback = std::function<void(bool ok, const QString &message)>;
87
88 explicit SieveSession(const Config &config, QObject *parent = nullptr);
89 ~SieveSession() override;
90
91 235 void setCredentialProvider(CredentialProvider provider) { m_credentials = std::move(provider); }
92 40 void setServer(const SieveServerConfig &server) { m_config.server = server; }
93 234 ManageSieveClient *client() { return m_client; }
94 41 SieveHistory &history() { return m_history; }
95
96 void load();
97 void save(const QByteArray &script, const SaveOptions &options, SaveCallback callback);
98 // CHECKSCRIPT only (script editor "Check"); Saved = valid. Servers
99 // without CHECKSCRIPT report Saved with the message "unchecked".
100 void check(const QByteArray &script, SaveCallback callback);
101
102 // Scripts panel (D-2): every call refreshes the script list afterwards.
103 void activateScript(const QString &name, DoneCallback callback);
104 void renameScript(const QString &from, const QString &to, DoneCallback callback);
105 void deleteScript(const QString &name, DoneCallback callback);
106
107 11570 Status status() const { return m_status; }
108 68 QString statusMessage() const { return m_statusMessage; }
109 3 ManageSieveClient::ErrorKind lastError() const { return m_lastError; }
110 4620 const Sieve::Document &document() const { return m_document; }
111 111 QByteArray baseScript() const { return m_base; }
112 QString baseHash() const;
113 213 QString activeScript() const { return m_activeScript; }
114 QString editedScriptName() const; // active, or "mailjd" for a first script
115 7 QList<ManageSieve::ScriptEntry> scripts() const { return m_scripts; }
116 1259 const ManageSieve::Capabilities &capabilities() const { return m_client->capabilities(); }
117 bool isOfflineCopy() const { return m_status == Status::Offline; }
118 1 QDateTime loadedAt() const { return m_loadedAt; }
119 72 QDateTime savedAt() const { return m_savedAt; }
120 // A save, a load or a scripts-panel change runs; the panel's buttons
121 // are disabled meanwhile (S106-101).
122
6/6
✓ Branch 2 → 3 taken 15 times.
✓ Branch 2 → 5 taken 5 times.
✓ Branch 3 → 4 taken 14 times.
✓ Branch 3 → 5 taken 1 time.
✓ Branch 4 → 5 taken 1 time.
✓ Branch 4 → 6 taken 13 times.
20 bool busy() const { return m_saving || m_panelBusy || m_status == Status::Loading; }
123 // Test seam: runs on the worker thread of every off-thread decode.
124 2 void setDecodeProbe(std::function<void()> probe) { m_decodeProbe = std::move(probe); }
125
126 static constexpr int kOffThreadParseBytes = 64 * 1024;
127 290 static QString newScriptName() { return QStringLiteral("mailjd"); }
128
129 signals:
130 void statusChanged();
131 void documentChanged();
132 void scriptsChanged();
133 void saved(const QString &reason);
134
135 private:
136 struct SaveJob {
137 QByteArray script;
138 SaveOptions options;
139 SaveCallback callback;
140 };
141 struct Upload; // pipeline state of the running save
142
143 void withCredentials(std::function<void(bool)> next);
144 void loadActive(const QList<ManageSieve::ScriptEntry> &scripts);
145 void publish(const QByteArray &script, const QString &scriptName, bool offline);
146 void applyDocument(const QByteArray &script, Sieve::Document document);
147 void decodeScript(const QByteArray &script, std::function<void(Sieve::Document)> done);
148 bool beginPanelChange(const DoneCallback &callback);
149 DoneCallback endPanelChange(DoneCallback callback);
150 void loadFailed(const ManageSieveClient::Result &result);
151 void setStatus(Status status, const QString &message = {});
152 void refreshScripts(DoneCallback callback, bool ok, const QString &message);
153
154 void startNextSave();
155 void checkStep(const std::shared_ptr<Upload> &upload);
156 void concurrencyStep(const std::shared_ptr<Upload> &upload);
157 void spaceStep(const std::shared_ptr<Upload> &upload);
158 void tempValidationStep(const std::shared_ptr<Upload> &upload);
159 void putStep(const std::shared_ptr<Upload> &upload);
160 void activateStep(const std::shared_ptr<Upload> &upload);
161 void verifyStep(const std::shared_ptr<Upload> &upload);
162 void finishSave(const std::shared_ptr<Upload> &upload, SaveOutcome outcome);
163 SaveOutcome failure(const ManageSieveClient::Result &result) const;
164
165 Config m_config;
166 ManageSieveClient *m_client = nullptr;
167 SieveHistory m_history;
168 CredentialProvider m_credentials;
169 Status m_status = Status::Idle;
170 QString m_statusMessage;
171 ManageSieveClient::ErrorKind m_lastError = ManageSieveClient::ErrorKind::None;
172 QList<ManageSieve::ScriptEntry> m_scripts;
173 QString m_activeScript;
174 QByteArray m_base;
175 Sieve::Document m_document;
176 QDateTime m_loadedAt;
177 QDateTime m_savedAt;
178 QList<SaveJob> m_saveQueue;
179 bool m_saving = false;
180 bool m_panelBusy = false;
181 quint64 m_loadGeneration = 0;
182 std::function<void()> m_decodeProbe;
183 };
184