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

app/core/BackgroundAccountSession.h
Line Branch Exec Source
1 #pragma once
2
3 #include <QHash>
4 #include <QObject>
5 #include <QPointer>
6 #include <QStringList>
7 #include <QTimer>
8
9 #include <functional>
10 #include <vector>
11
12 #include "data/AccountConfig.h"
13 #include "service/ImapService.h"
14
15 class ConnectionHealthMonitor;
16 class MailCache;
17 struct StatusResult;
18
19 // F-50 (SPRINT-104, D-11): every configured account stays online, not only
20 // the one selected in the account selector. One connection per non-selected
21 // account watches its folders with a slow STATUS round and publishes the
22 // unread badges into the shared cache, so background arrivals can notify
23 // (the account-scoped arrival summaries of F-36) and the counts are fresh
24 // when the user switches.
25 //
26 // v1 scope (SPRINT-104-F50-BACKGROUND-ACCOUNTS.md): badges and arrival
27 // signals only — no background header sync and no replay of queued journal
28 // actions; those stay with the selected account. Watching uses STATUS
29 // polling (RFC 3501): RFC 5461 NOTIFY requires a selected mailbox in this
30 // client's push session, which a background connection never has; the
31 // deviation from the package's "NOTIFY where available" is recorded there.
32 //
33 // Deliberately defensive about startup: the first connect happens seconds
34 // after the session loaded (never on the critical path) and accounts
35 // stagger, so a profile with many accounts cannot produce a connection
36 // burst against one server.
37 class BackgroundAccountSession final : public QObject {
38 Q_OBJECT
39 public:
40 struct Deps {
41 MailCache *cache = nullptr;
42 // Non-secret account configurations (passwords empty).
43 std::function<std::vector<AccountConfig>()> configurations;
44 std::function<QString()> selectedAccountId;
45 std::function<bool()> networkEnabled;
46 // Resolves the passwords of the given accounts asynchronously; tests
47 // inject a synchronous filler.
48 std::function<void(std::vector<AccountConfig> &, std::function<void()>)> resolveCredentials;
49 int startDelayMs = 8000; // after startup — off the critical path
50 int staggerMs = 3000; // between accounts
51 int pollIntervalMs = 600000; // one STATUS round every 10 minutes
52 int maxFolders = 50; // bounded rounds for huge folder trees
53 bool connectToServer = true; // test seam: no socket without it
54 };
55
56 explicit BackgroundAccountSession(Deps deps, QObject *parent = nullptr);
57 ~BackgroundAccountSession() override;
58
59 // Production wiring: AccountSession::accountsLoaded / configurationChanged
60 // / configurationRemoved. reconcile() starts the missing watches (staggered)
61 // and stops the removed and the now-selected ones.
62 void onAccountsLoaded();
63 void onConfigurationChanged(const QString &id);
64 void onConfigurationRemoved(const QString &id);
65 void setNetworkEnabled(bool enabled);
66
67 // Production handlers for the per-account service signals; also the test
68 // seams — state transitions and STATUS answers are injected directly.
69 void onStateChanged(const QString &accountId, ImapService::State state);
70 void onFolderStatus(const QString &accountId, const StatusResult &result);
71 // One STATUS round of one account (timer target; tests call it directly).
72 void pollNow(const QString &accountId);
73
74 64 bool isWatching(const QString &accountId) const { return m_watches.contains(accountId); }
75 3 bool networkEnabled() const { return m_networkEnabled; }
76 bool isReady(const QString &accountId) const; // authenticated once
77 1 QStringList watchedAccountIds() const { return m_watches.keys(); }
78 // Test seam: the account's service (the watch owns it).
79 ImapService *serviceForTests(const QString &accountId) const;
80
81 signals:
82 // One folder's unread count changed while its account was not selected.
83 void backgroundUnreadChanged(const QString &accountId, const QString &accountName,
84 const QString &folderPath, int unread);
85 // A watch could not start (no password) or its account vanished.
86 void watchFailed(const QString &accountId, const QString &reason);
87
88 private:
89 struct Watch {
90 QString name;
91 ImapConfig config; // password resolved before connecting
92 ImapService *service = nullptr;
93 ConnectionHealthMonitor *monitor = nullptr;
94 QStringList folders;
95 bool ready = false;
96 };
97
98 void reconcile();
99 void startWatch(const QString &accountId, const AccountConfig &config, int delayMs);
100 void stopWatch(const QString &accountId);
101 void pollWatch(const QString &accountId, Watch &watch);
102
103 Deps m_deps;
104 QHash<QString, Watch> m_watches;
105 QTimer m_pollTimer;
106 quint64 m_epoch = 0; // invalidates scheduled starts after a reload
107 bool m_networkEnabled = false;
108 };
109