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

service/NtfyPushReceiver.h
Line Branch Exec Source
1 #pragma once
2
3 #include <QPointer>
4 #include <QStringList>
5 #include <QTimer>
6 #include <functional>
7
8 #include "PushReceiver.h"
9
10 class QNetworkAccessManager;
11 class QNetworkReply;
12
13 // Sprint 96 S96.2.1 / ADR-96-1: ntfy receive channel over the long-lived
14 // JSON stream GET <server>/<channel>/json (NDJSON). Nextcloud publishes to
15 // <server>/<channel>?up=1, so binary Web Push bodies arrive base64-encoded.
16 //
17 // Contract:
18 // - Bounded framing: at most kMaxFrameBytes per line and kMaxBufferBytes
19 // buffered; an overflow drops the stream (gap → owner catches up).
20 // - "open"/"keepalive" update the receive state only; "message" carries a
21 // payload; "poll_request" and unknown events are ignored.
22 // - No message within the heartbeat deadline (ntfy sends keepalives every
23 // 45 s by default) renews the stream; the stream has no total timeout.
24 // - Reconnect backoff 2, 4, 8 … s up to 5 min with earlier-only jitter,
25 // reset after a stable connection; 401/403 and TLS failures wait for the
26 // long backoff instead of retrying aggressively; 429 honours Retry-After.
27 // - TLS is always verified; the DAV credentials never reach the provider.
28 class NtfyPushReceiver final : public PushReceiver {
29 Q_OBJECT
30 public:
31 static constexpr int kMaxFrameBytes = 64 * 1024;
32 static constexpr int kMaxBufferBytes = 256 * 1024;
33
34 struct Timing {
35 int connectTimeoutMs = 30000;
36 int heartbeatTimeoutMs = 90000;
37 int stableAfterMs = 60000; // a connection this old resets the backoff
38 int backoffBaseMs = 2000;
39 int backoffMaxMs = 300000;
40 int failureBackoffMs = 300000; // auth/TLS: no aggressive retries
41 };
42
43 explicit NtfyPushReceiver(QObject *parent = nullptr);
44 ~NtfyPushReceiver() override;
45
46 void start(const Config &config) override;
47 void stop() override;
48 QString endpoint() const override;
49
50 // Test seams: injected network access manager (ownership taken), timing
51 // and a jitter source returning a fraction in [0, 1).
52 void setNetworkAccessManager(QNetworkAccessManager *nam);
53 11 void setTiming(const Timing &timing) { m_timing = timing; }
54 12 void setJitter(std::function<double()> jitter) { m_jitter = std::move(jitter); }
55 int attempt() const { return m_attempt; }
56
57 private:
58 void connectStream();
59 void onReadyRead();
60 void onFinished();
61 void processLine(const QByteArray &line);
62 void scheduleReconnect(Failure failure, const QString &detail, int minimumDelayMs = 0,
63 bool needsUser = false);
64 void dropStream();
65 int backoffDelayMs() const;
66
67 QNetworkAccessManager *m_nam = nullptr;
68 QPointer<QNetworkReply> m_reply;
69 Config m_config;
70 Timing m_timing;
71 std::function<double()> m_jitter;
72 QByteArray m_buffer;
73 QTimer m_reconnectTimer;
74 QTimer m_heartbeatTimer;
75 QTimer m_connectTimer;
76 int m_attempt = 0;
77 bool m_running = false;
78 bool m_opened = false;
79 bool m_hadConnection = false;
80 QStringList m_recentIds; // bounded duplicate filter
81 };
82