mirror of
https://github.com/agessaman/MeshCore.git
synced 2026-08-28 02:54:05 +00:00
test(mqtt): extract + test remaining inline MQTT decision points (Phase 6)
Close the host-testable gaps named in Phase 6 of STABILITY_TESTABILITY_HANDOFF.md
by moving the last inline decision logic into the pure, host-tested policy seams:
- WiFi STA reconnect backoff: extract the inline ladder + wrap-safe timing from
handleWiFiConnection() into MQTTConnectionPolicy::{wifiReconnectBackoffMs,
wifiReconnectDue,nextWifiBackoffAttempt}. Behavior-preserving (elapsedMs is the
wrap-safe form of the old ULONG_MAX branch); ladder/clamp/attempt-cap unchanged.
- Publication outcome pairing: name the (packet, raw) -> delivered contract as
MQTTPacketQueuePolicy::queuedPacketPublished() and wire both queue-drain sites;
partial success = completed, not retried.
- Freeze MQTTPublicationType enum values in a test (the bridge-side MQTTMessageType
alignment is already enforced by a compile-time static_assert).
Adds host tests for all three (exact boundaries + millis() rollover). Native suite
green (13 dirs); non-PSRAM observer firmware smoke build compiles.
WebConfig batch/reboot/stop state-machine extraction and queue-orchestration
coverage remain open (tracked in the Phase 6 status).
This commit is contained in:
@@ -152,6 +152,49 @@ TEST(MQTTConnectionPolicy, JwtClockNeedsNtpOrAReasonableWallClock) {
|
||||
EXPECT_TRUE(Policy::jwtClockAvailable(true, 0U));
|
||||
}
|
||||
|
||||
TEST(MQTTConnectionPolicy, WifiBackoffLadderStartsAtFifteenSecondsAndSaturates) {
|
||||
EXPECT_EQ(15000U, Policy::wifiReconnectBackoffMs(0));
|
||||
EXPECT_EQ(30000U, Policy::wifiReconnectBackoffMs(1));
|
||||
EXPECT_EQ(60000U, Policy::wifiReconnectBackoffMs(2));
|
||||
EXPECT_EQ(120000U, Policy::wifiReconnectBackoffMs(3));
|
||||
EXPECT_EQ(300000U, Policy::wifiReconnectBackoffMs(4));
|
||||
// Clamps at the 300 s rung for the saturated attempt count and beyond.
|
||||
EXPECT_EQ(300000U, Policy::wifiReconnectBackoffMs(5));
|
||||
EXPECT_EQ(300000U, Policy::wifiReconnectBackoffMs(200));
|
||||
}
|
||||
|
||||
TEST(MQTTConnectionPolicy, WifiBackoffAttemptClimbsThenSaturatesAtFive) {
|
||||
uint8_t attempt = 0;
|
||||
for (uint8_t expected = 1; expected <= 5; ++expected) {
|
||||
attempt = Policy::nextWifiBackoffAttempt(attempt);
|
||||
EXPECT_EQ(expected, attempt);
|
||||
}
|
||||
// Saturated: never advances past 5 (index stays clamped at the 300 s rung).
|
||||
EXPECT_EQ(5U, Policy::nextWifiBackoffAttempt(attempt));
|
||||
EXPECT_EQ(5U, Policy::nextWifiBackoffAttempt(5));
|
||||
}
|
||||
|
||||
TEST(MQTTConnectionPolicy, WifiReconnectRequiresBothDownAndSinceAttemptToClearRung) {
|
||||
const uint32_t down_since = 1000U;
|
||||
const uint32_t last_attempt = 1000U;
|
||||
const uint8_t attempt = 0; // 15 s rung
|
||||
// Neither interval has elapsed yet.
|
||||
EXPECT_FALSE(Policy::wifiReconnectDue(1000U + 14999U, down_since, last_attempt, attempt));
|
||||
// Down long enough, but an attempt was made only 5 s ago (since-attempt short).
|
||||
EXPECT_FALSE(Policy::wifiReconnectDue(1000U + 15000U, down_since, 1000U + 10000U, attempt));
|
||||
// Both cleared at the exact boundary: due.
|
||||
EXPECT_TRUE(Policy::wifiReconnectDue(1000U + 15000U, down_since, last_attempt, attempt));
|
||||
}
|
||||
|
||||
TEST(MQTTConnectionPolicy, WifiReconnectDueSurvivesMillisRollover) {
|
||||
const uint32_t down_since = std::numeric_limits<uint32_t>::max() - 100U;
|
||||
const uint32_t last_attempt = down_since;
|
||||
const uint8_t attempt = 0; // 15 s rung
|
||||
const uint32_t now = down_since + 15000U; // wraps past zero
|
||||
EXPECT_TRUE(Policy::wifiReconnectDue(now, down_since, last_attempt, attempt));
|
||||
EXPECT_FALSE(Policy::wifiReconnectDue(down_since + 14999U, down_since, last_attempt, attempt));
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
|
||||
@@ -151,6 +151,29 @@ TEST(MQTTPacketQueuePolicy, RetrySchedulingDeadlineMayWrapToZero) {
|
||||
decision.retry_attempts));
|
||||
}
|
||||
|
||||
TEST(MQTTPacketQueuePolicy, PartialPublishCountsAsDeliveredEitherWay) {
|
||||
EXPECT_TRUE(QueuePolicy::queuedPacketPublished(true, true));
|
||||
EXPECT_TRUE(QueuePolicy::queuedPacketPublished(true, false)); // packet ok, raw failed
|
||||
EXPECT_TRUE(QueuePolicy::queuedPacketPublished(false, true)); // raw ok, packet failed
|
||||
EXPECT_FALSE(QueuePolicy::queuedPacketPublished(false, false)); // neither reached a slot
|
||||
}
|
||||
|
||||
TEST(MQTTPacketQueuePolicy, PublishOutcomePairingDrivesRetryDecision) {
|
||||
// packet succeeds / raw fails -> completed, no retry.
|
||||
QueuePolicy::RetryDecision d =
|
||||
QueuePolicy::retryDecision(QueuePolicy::queuedPacketPublished(true, false), 0, 1234U);
|
||||
EXPECT_EQ(QueuePolicy::RetryAction::Complete, d.action);
|
||||
|
||||
// raw succeeds / packet fails -> also completed.
|
||||
d = QueuePolicy::retryDecision(QueuePolicy::queuedPacketPublished(false, true), 0, 1234U);
|
||||
EXPECT_EQ(QueuePolicy::RetryAction::Complete, d.action);
|
||||
|
||||
// both fail on a fresh packet -> scheduled for a bounded retry.
|
||||
d = QueuePolicy::retryDecision(QueuePolicy::queuedPacketPublished(false, false), 0, 1234U);
|
||||
EXPECT_EQ(QueuePolicy::RetryAction::Schedule, d.action);
|
||||
EXPECT_EQ(1U, d.retry_attempts);
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
|
||||
@@ -171,6 +171,18 @@ TEST(MQTTTopicRouter, RejectsInvalidStyleTypeSlotAndOutput) {
|
||||
EXPECT_FALSE(mqttTopicSlotIndexValid(0, 0));
|
||||
}
|
||||
|
||||
TEST(MQTTTopicRouter, PublicationTypeEnumValuesAreFrozen) {
|
||||
// The bridge passes MQTTBridge::MQTTMessageType to mqttBuildPublicationTopic
|
||||
// as an int; a compile-time static_assert in the bridge ties the two enums
|
||||
// together. Freeze the router side here so its values can't drift on their own.
|
||||
EXPECT_EQ(0, MQTT_PUBLICATION_STATUS);
|
||||
EXPECT_EQ(1, MQTT_PUBLICATION_PACKETS);
|
||||
EXPECT_EQ(2, MQTT_PUBLICATION_RAW);
|
||||
EXPECT_STREQ("status", mqttPublicationTypeName(MQTT_PUBLICATION_STATUS));
|
||||
EXPECT_STREQ("packets", mqttPublicationTypeName(MQTT_PUBLICATION_PACKETS));
|
||||
EXPECT_STREQ("raw", mqttPublicationTypeName(MQTT_PUBLICATION_RAW));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
Reference in New Issue
Block a user