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

service/ManageSieveClient.h
Line Branch Exec Source
1 #pragma once
2
3 #include <QElapsedTimer>
4 #include <QList>
5 #include <QObject>
6 #include <QPointer>
7 #include <QSslCertificate>
8 #include <QSslSocket>
9 #include <QTimer>
10
11 #include <functional>
12
13 #include "service/ManageSieveProtocol.h"
14
15 // Connection settings for one ManageSieve server.
16 struct SieveServerConfig {
17 QString host;
18 quint16 port = 4190;
19 QString security = QStringLiteral("starttls"); // "starttls" or "ssl"
20 QString username;
21 QByteArray password; // zeroed right after AUTHENTICATE is written
22 };
23
24 // ManageSieve client (Sprint 95 S95.1.1, SIEVE-PROTO), modelled on
25 // SmtpService: a QSslSocket state machine with a command queue.
26 //
27 // Flow: connect → greeting (= capabilities) → STARTTLS (mandatory unless the
28 // account uses implicit TLS) → the pre-TLS capabilities are discarded and the
29 // re-issued set is read → AUTHENTICATE "PLAIN" (initial response) or "LOGIN"
30 // → queued commands, one at a time.
31 //
32 // Invariants:
33 // - No credential is ever written to an unencrypted socket (SR-07); a server
34 // without STARTTLS fails with ErrorKind::NoStartTls.
35 // - TLS errors are never ignored; tests supply their CA instead.
36 // - Every queued callback is invoked exactly once (success, failure, cancel
37 // or destruction); results of an older socket generation are dropped.
38 // - The password is zeroed after it was written; a reconnect needs a new
39 // setServer() call with credentials.
40 // - The connection is opened on demand and closed with LOGOUT after the
41 // idle timeout; commands queued during LOGOUT reconnect. A REFERRAL is
42 // reported, never followed.
43 // - capabilities() describe the current connection: they are reset on
44 // every connect and on a server change.
45 // - Logs carry command names, sizes, response codes and timings only, never
46 // script text or credentials (SPRINT-95 rule 4).
47 class ManageSieveClient : public QObject {
48 Q_OBJECT
49
50 public:
51 enum class ErrorKind {
52 None,
53 Network,
54 Timeout,
55 Certificate,
56 NoStartTls,
57 NoMechanism,
58 NoCredentials,
59 AuthFailed,
60 Server, // NO response to a command
61 Bye, // server closed the session (TRYLATER, …)
62 Referral, // REFERRAL response code; host in Result::referral
63 Protocol,
64 Cancelled,
65 };
66
67 struct Result {
68 bool ok = false;
69 ErrorKind error = ErrorKind::None;
70 ManageSieve::Response response;
71 QList<ManageSieve::ScriptEntry> scripts; // LISTSCRIPTS
72 QByteArray script; // GETSCRIPT
73 QString message; // technical detail (log/"Details")
74 QString referral;
75 qint64 elapsedMs = 0;
76 };
77 using Callback = std::function<void(const Result &)>;
78
79 struct Timeouts {
80 int connectMs = 15000;
81 int commandMs = 30000;
82 int uploadMs = 60000;
83 int idleMs = 60000;
84 };
85
86 explicit ManageSieveClient(QObject *parent = nullptr);
87 ~ManageSieveClient() override;
88
89 void setServer(const SieveServerConfig &config);
90 void setCaCertificates(const QList<QSslCertificate> &certificates);
91 105 void setTimeouts(const Timeouts &timeouts) { m_timeouts = timeouts; }
92
93 // Capabilities after TLS, without logging in (discovery).
94 void probe(Callback callback);
95 // Logs in and reports the post-login capabilities.
96 void login(Callback callback);
97 void listScripts(Callback callback);
98 void getScript(const QString &name, Callback callback);
99 void checkScript(const QByteArray &script, Callback callback);
100 void putScript(const QString &name, const QByteArray &script, Callback callback);
101 void setActive(const QString &name, Callback callback);
102 void renameScript(const QString &from, const QString &to, Callback callback);
103 void deleteScript(const QString &name, Callback callback);
104 void haveSpace(const QString &name, qint64 size, Callback callback);
105 void noop(Callback callback);
106
107 // Sends LOGOUT when idle-connected; fails nothing that is still queued.
108 void logout();
109 // Fails every queued command with ErrorKind::Cancelled and closes.
110 void cancelAll();
111
112 1861 const ManageSieve::Capabilities &capabilities() const { return m_capabilities; }
113 bool isAuthenticated() const { return m_phase == Phase::Ready || m_phase == Phase::Busy; }
114 int queuedCommands() const { return m_queue.size(); }
115
116 #ifdef MAILJD_UNIT_TEST
117 4 bool holdsCredentialsForTest() const {
118
3/4
✓ Branch 3 → 4 taken 3 times.
✓ Branch 3 → 6 taken 1 time.
✗ Branch 5 → 6 not taken.
✓ Branch 5 → 7 taken 3 times.
4 return !m_config.password.isEmpty() || !m_loginUser.isEmpty();
119 }
120 #endif
121
122 signals:
123 void connectionClosed();
124
125 private:
126 enum class Phase {
127 Disconnected,
128 Connecting,
129 Greeting,
130 StartTls,
131 TlsHandshake,
132 TlsCapabilities,
133 Authenticating,
134 Ready,
135 Busy,
136 LoggingOut,
137 };
138
139 enum class Kind {
140 Probe,
141 Login,
142 Capability,
143 ListScripts,
144 GetScript,
145 CheckScript,
146 PutScript,
147 SetActive,
148 RenameScript,
149 DeleteScript,
150 HaveSpace,
151 Noop,
152 };
153
154 struct Pending {
155 Kind kind = Kind::Noop;
156 QByteArray wire; // full command incl. CRLF; empty for Probe/Login
157 QByteArray name; // command name for logs
158 Callback callback;
159 bool upload = false;
160 Result result;
161 QElapsedTimer timer;
162 };
163
164 void enqueue(Kind kind, const QByteArray &name, const QByteArray &wire, Callback callback,
165 bool upload = false);
166 void ensureConnected();
167 void createSocket();
168 void onConnected();
169 void onEncrypted();
170 void onReadyRead();
171 void onSocketError(QAbstractSocket::SocketError error);
172 void onTimeout();
173 void handleLine(const QList<ManageSieve::Token> &tokens);
174 void handleCapabilitiesDone(const ManageSieve::Response &response);
175 void handleAuthLine(const QList<ManageSieve::Token> &tokens);
176 void handleCommandLine(const QList<ManageSieve::Token> &tokens);
177 void startAuthentication();
178 void completeLeadingProbes();
179 void dispatchNext();
180 void finishCurrent(Result result);
181 void failAll(ErrorKind error, const QString &message, const ManageSieve::Response &response = {});
182 void closeSocket();
183 void finishLogout();
184 void writeRaw(const QByteArray &bytes);
185 void armTimeout(int ms);
186 void clearCredentials();
187 static bool needsAuthentication(Kind kind);
188
189 QPointer<QSslSocket> m_socket;
190 SieveServerConfig m_config;
191 QList<QSslCertificate> m_caCertificates;
192 Timeouts m_timeouts;
193 Phase m_phase = Phase::Disconnected;
194 ManageSieve::Capabilities m_capabilities;
195 QList<Pending> m_queue; // front = current command while Busy
196 QByteArray m_buffer;
197 qint64 m_listBytes = 0; // name bytes of the running LISTSCRIPTS
198 QByteArray m_loginUser; // pending LOGIN mechanism user name
199 int m_loginStep = 0;
200 quint64 m_generation = 0;
201 QTimer m_timeout;
202 QTimer m_idle;
203 };
204