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

presentation/MailOutboxSender.h
Line Branch Exec Source
1 #pragma once
2
3 #include "data/AccountConfig.h"
4 #include "data/MailOutboxStore.h"
5
6 #include <QObject>
7 #include <QSet>
8 #include <QTimer>
9 #include <functional>
10 #include <optional>
11
12 // Sprint 108 S108.7.3 (D-20): the only way a composed message leaves MailJD.
13 // Entries of the outbox (MailOutboxStore) are sent when due through the SMTP
14 // account of their identity; the result is classified:
15 //
16 // network no connection, timeout, 4xx → retry with backoff
17 // account authentication refused, TLS failure → waits until the account
18 // changes, then retries
19 // recipients some RCPT refused permanently → sent to the others; the
20 // entry names the refused
21 // message 552/554 and other permanent refusals → failed; back to a draft
22 //
23 // The Date header is set when the message actually leaves. An entry found
24 // in "sending" at start (MailJD stopped after handing it over) is looked up
25 // in the Sent folder by Message-ID before anything is sent again.
26 class MailOutboxSender : public QObject {
27 25 Q_OBJECT
28 public:
29 struct TransmitResult {
30 bool ok = false;
31 int code = 0; // the server's reply code (0: local failure)
32 QString error; // the server's words
33 QStringList refused; // partly refused recipients
34 QString refusalReply;
35 };
36 using TransmitDone = std::function<void(const TransmitResult &)>;
37 struct Deps {
38 // The account with its SMTP password resolved (asynchronous).
39 std::function<void(const QString &accountId, std::function<void(std::optional<AccountConfig>)>)> resolveAccount;
40 // Sends bytes through SMTP (production: one SmtpService per message
41 // with partial recipients).
42 std::function<void(const AccountConfig &, const QString &from, const QStringList &recipients,
43 const QByteArray &message, TransmitDone)> transmit;
44 // Stores the private Sent copy; false = not now (another account), retried later.
45 std::function<bool(const OutboxEntry &, const QByteArray &sentCopy)> storeSentCopy;
46 // Flags the original (\Answered / $Forwarded) after the transmission.
47 std::function<void(const OutboxEntry &)> flagOriginal;
48 // After a transmission (D-23: an invitation writes its event now).
49 std::function<void(const OutboxEntry &, const QByteArray &sentBytes)> afterTransmit;
50 // Whether a Message-ID is already in the account's Sent folder (restart).
51 std::function<void(const OutboxEntry &, std::function<void(bool found)>)> sentContains;
52 // S108.8.4 (D-24): an invitation the calendar server delivers — no
53 // SMTP and no Sent copy; deliverByServer writes the event (the server
54 // then sends the invitations) and answers whether that worked.
55 std::function<bool(const OutboxEntry &)> serverDelivers;
56 std::function<bool(const OutboxEntry &)> deliverByServer;
57 std::function<bool()> online;
58 std::function<void(const QString &title, const QString &body)> notify;
59 std::function<QDateTime()> now; // tests drive time
60 };
61
62 MailOutboxSender(MailOutboxStore &store, Deps deps, QObject *parent = nullptr);
63
64 // Startup: recovers entries left in "sending" and schedules the next run.
65 void start();
66 qint64 enqueue(const OutboxEntry &entry);
67 // *Undo* within the undo window: the entry leaves the outbox; returns it
68 // (its draft session reopens). Empty when it is already on its way.
69 std::optional<OutboxEntry> takeBack(qint64 id);
70 bool sendNow(qint64 id);
71 bool reschedule(qint64 id, const QDateTime &due);
72 // D-20 "Send again only to these": a new attempt for the refused recipients.
73 bool resendToRefused(qint64 id);
74 // The account's settings changed: waiting entries try again.
75 void accountChanged(const QString &accountId);
76 // Network came back.
77 void networkAvailable();
78 // Quit: messages in their undo window go now (the user pressed *Send*);
79 // waits up to `timeoutMs` for them.
80 void flushUndoWindow(int timeoutMs = 15000);
81 // Runs every due entry once (the timer calls it; tests too).
82 void processDue();
83 bool busy() const { return !m_inFlight.isEmpty(); }
84 // RFC 5322 Date header replaced by the transmission time.
85 static QByteArray withDate(const QByteArray &message, const QDateTime &when);
86 // The error class of a failed transmission.
87 static QString classify(const TransmitResult &result);
88 static QDateTime nextRetry(const QDateTime &now, int attempts);
89
90 signals:
91 void changed();
92 // A message left (to the accepted recipients); `late` when its scheduled
93 // time passed while MailJD was not running.
94 void transmitted(qint64 id, const QString &summaryTo, bool late);
95 void failedEntry(qint64 id, const QString &errorClass, const QString &error);
96
97 private:
98 MailOutboxStore &m_store;
99 Deps m_deps;
100 QTimer m_timer;
101 QSet<qint64> m_inFlight;
102 QDateTime now() const;
103 void scheduleNext();
104 void transmit(const OutboxEntry &entry);
105 void deliverByServer(const OutboxEntry &entry);
106 void finish(const OutboxEntry &entry, const TransmitResult &result, const QByteArray &sentBytes);
107 };
108