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: 50.6% 39 / 0 / 77
Functions: 40.0% 6 / 0 / 15
Branches: 41.5% 44 / 0 / 106

presentation/MailPermanentDelete.cpp
Line Branch Exec Source
1 #include "MailPermanentDelete.h"
2 #include "QuickMailBackend.h"
3 #include "app/core/MailSessionCore.h"
4 #include "ui/MailListModel.h"
5
6 #include "data/MailCache.h"
7 #include "service/ImapService.h"
8
9 #include <QLoggingCategory>
10 #include <QPointer>
11
12 Q_DECLARE_LOGGING_CATEGORY(lcQuickBackend)
13
14 2 MailPermanentDelete::MailPermanentDelete(std::function<ImapService *()> imap,
15 std::function<MailCache *()> cache,
16 2 Deps deps, QObject *parent)
17 2 : QObject(parent), m_imap(std::move(imap)), m_cache(std::move(cache)),
18 4 m_deps(std::move(deps)) {}
19
20 ✗ bool MailPermanentDelete::armOrDelete(const MailId &selected, const QList<qint64> &uids) {
21 ✗ if (!m_armed.isValid() || m_armed.elapsed() > 5000) {
22 ✗ m_armed.start();
23 ✗ return false; // armed: the caller shows the confirmation text
24 }
25 ✗ m_armed.invalidate();
26 ✗ run(selected, uids);
27 ✗ return true;
28 }
29
30 3 void MailPermanentDelete::deleteNow(const MailId &selected, const QList<qint64> &uids) {
31 3 m_armed.invalidate();
32 3 run(selected, uids);
33 3 }
34
35 3 void MailPermanentDelete::run(const MailId &selected, const QList<qint64> &uids) {
36
1/1
✓ Branch 2 → 3 taken 3 times.
3 auto *imap = m_imap();
37
1/1
✓ Branch 3 → 4 taken 3 times.
3 auto *cache = m_cache();
38
5/10
✓ Branch 4 → 5 taken 3 times.
✗ Branch 4 → 9 not taken.
✓ Branch 5 → 6 taken 3 times.
✗ Branch 5 → 9 not taken.
✓ Branch 7 → 8 taken 3 times.
✗ Branch 7 → 9 not taken.
✗ Branch 8 → 9 not taken.
✓ Branch 8 → 10 taken 3 times.
✗ Branch 11 → 12 not taken.
✓ Branch 11 → 16 taken 3 times.
3 if (!imap || !cache || uids.isEmpty() || selected.folderId <= 0) {
39 ✗ m_deps.setStatus(QObject::tr("Nothing selected."));
40 ✗ return;
41 }
42
4/8
✓ Branch 17 → 18 taken 3 times.
✗ Branch 17 → 21 not taken.
✗ Branch 20 → 21 not taken.
✓ Branch 20 → 22 taken 3 times.
✓ Branch 23 → 24 taken 3 times.
✗ Branch 23 → 25 not taken.
✗ Branch 25 → 26 not taken.
✓ Branch 25 → 30 taken 3 times.
3 if (imap->state() != ImapService::State::Selected || imap->selectedFolder().isEmpty()) {
43 ✗ m_deps.setStatus(QObject::tr("The folder must be open to delete for good."));
44 ✗ return;
45 }
46 3 const QString folder = imap->selectedFolder();
47
1/1
✓ Branch 31 → 32 taken 3 times.
3 const quint32 epoch = cache->uidValidity(selected.folderId);
48 // The flags ride the mutation journal's ordinary path; the expunge below
49 // is what makes the deletion permanent once the marks are stored.
50
2/2
✓ Branch 43 → 34 taken 3 times.
✓ Branch 43 → 44 taken 3 times.
6 for (qint64 uid : uids)
51
1/1
✓ Branch 38 → 39 taken 3 times.
6 imap->storeFlag(uid, QStringLiteral("\\Deleted"), true);
52
1/1
✓ Branch 44 → 45 taken 3 times.
3 wire(imap);
53 3 m_pending.enqueue({selected.folderId, uids, epoch});
54
1/1
✓ Branch 48 → 49 taken 3 times.
3 imap->expungeUids(uids, folder, epoch);
55 6 }
56
57 // The expunge signals are shared by every deletion on the connection;
58 // they are connected once (S106-240: a single-shot connection per run let
59 // the first EXPUNGE's answer settle every deletion waiting at that time).
60 3 void MailPermanentDelete::wire(ImapService *imap) {
61
2/2
✓ Branch 3 → 4 taken 1 time.
✓ Branch 3 → 5 taken 2 times.
3 if (m_wired == imap) return;
62
1/2
✗ Branch 6 → 7 not taken.
✓ Branch 6 → 9 taken 2 times.
2 if (m_wired) disconnect(m_wired, nullptr, this, nullptr);
63 2 m_pending.clear();
64 2 m_wired = imap;
65
2/2
✓ Branch 3 → 4 taken 4 times.
✓ Branch 11 → 12 taken 2 times.
6 connect(imap, &ImapService::expungeComplete, this, [this] { settle(true, QString()); });
66
1/1
✓ Branch 13 → 14 taken 2 times.
2 connect(imap, &ImapService::expungeFailed, this, [this](const QString &error) { settle(false, error); });
67
1/1
✓ Branch 15 → 16 taken 2 times.
2 connect(imap, &ImapService::stateChanged, this, [this](ImapService::State state) {
68 ✗ if (state != ImapService::State::Disconnected && state != ImapService::State::Error) return;
69 ✗ while (!m_pending.isEmpty()) settle(false, QObject::tr("Connection lost"));
70 });
71 }
72
73 4 void MailPermanentDelete::settle(bool ok, const QString &error) {
74
2/2
✓ Branch 3 → 4 taken 1 time.
✓ Branch 3 → 5 taken 3 times.
4 if (m_pending.isEmpty()) return;
75
1/1
✓ Branch 5 → 6 taken 3 times.
3 const Pending pending = m_pending.dequeue();
76
1/2
✗ Branch 6 → 7 not taken.
✓ Branch 6 → 14 taken 3 times.
3 if (!ok) {
77 ✗ m_deps.setStatus(QObject::tr("Could not delete for good: %1").arg(error));
78 ✗ return;
79 }
80 // S106-241: after a mailbox reset the UIDs name other messages; the next
81 // sync reconciles the folder instead of a blind local removal.
82
1/1
✓ Branch 14 → 15 taken 3 times.
3 MailCache *cache = m_cache();
83
6/7
✓ Branch 15 → 16 taken 3 times.
✗ Branch 15 → 19 not taken.
✓ Branch 16 → 17 taken 3 times.
✓ Branch 17 → 18 taken 2 times.
✓ Branch 17 → 19 taken 1 time.
✓ Branch 20 → 21 taken 2 times.
✓ Branch 20 → 22 taken 1 time.
3 if (cache && cache->uidValidity(pending.folderId) == pending.epoch)
84
1/1
✓ Branch 21 → 33 taken 2 times.
2 m_deps.removeLocally(pending.folderId, pending.uids);
85 else
86
5/5
✓ Branch 22 → 23 taken 1 time.
✓ Branch 26 → 27 taken 1 time.
✓ Branch 27 → 28 taken 1 time.
✓ Branch 31 → 24 taken 1 time.
✓ Branch 31 → 32 taken 1 time.
2 qCWarning(lcQuickBackend) << "Delete forever: mailbox epoch changed; local rows left to the next sync";
87
3/3
✓ Branch 33 → 34 taken 3 times.
✓ Branch 36 → 37 taken 3 times.
✓ Branch 37 → 38 taken 3 times.
6 m_deps.setStatus(QObject::tr("Deleted %1 message(s) for good.").arg(pending.uids.size()));
88 3 }
89
90 // ─── QuickMailBackend's delete-forever seam (F-09) ────────────────
91
92 // F-09 (SPRINT-104): the open folder's visible selected UIDs (or the
93 // current mail) for the MailPermanentDelete unit.
94 ✗ QList<qint64> QuickMailBackend::deleteForeverUids(const MailId &selected) const {
95 ✗ QList<qint64> uids;
96 ✗ for (int row = 0, rows = activeRowCount(); row < rows; ++row) {
97 ✗ const auto key = activeKeyAt(row);
98 ✗ if (key.folderId == selected.folderId && m_selection.contains(key.uid))
99 ✗ uids.append(key.uid);
100 ✗ }
101 ✗ if (uids.isEmpty() && selected.uid > 0) uids.append(selected.uid);
102 ✗ return uids;
103 ✗ }
104
105
106 // F-09: the unit is created on first use (the backend owns it).
107 ✗ void QuickMailBackend::ensureDeleteForever() {
108 ✗ if (m_deleteForever) return;
109 ✗ m_deleteForever = new MailPermanentDelete(
110 ✗ [this] { return m_session.imap(); }, [this] { return m_session.cache(); },
111 ✗ {[this](const QString &status) { setStatus(status); },
112 ✗ [this](qint64 folderId, const QList<qint64> &uids) {
113 ✗ for (qint64 uid : uids) m_session.cache()->removeHeader(folderId, uid);
114 ✗ m_session.headers()->removeByUids(uids, folderId);
115 ✗ }},
116 ✗ this);
117 ✗ }
118
119