mirror of
https://github.com/mikecarper/MeshCore.git
synced 2026-09-26 19:57:55 +00:00
Cleanup before the commit phase ignored whether the temp was actually removed. On a fresh install a complete, byte-verified temp that failed schema verification (or whose read-back failed in finish()) could survive a failed remove() with no primary to outrank it, and boot recovery then promoted the very value the CLI had just reported as rolled back. Both cleanups now return a disposition: success means the temp is gone or an existing primary is authoritative. A false disposition maps to the new CleanupIndeterminate, which latches the same indeterminate reply as a commit that could not be rolled back. A short write is excluded, since it leaves structurally incomplete JSON that recovery classifies as invalid. Recovery also stops spending transaction state on an opaque backup: an uncertain temp beside a FutureUsable or uncertain backup now holds both names and runs defaults instead of promoting the candidate into the authoritative name, where the "any primary owns the name" rule would keep it even after a later boot proved it corrupt.
Host unit tests
Fast, hardware-free unit tests for the fork's pure logic, run on the host with
GoogleTest via PlatformIO's native environment. They cover the extractable
observer/WebConfig logic (validation, preset table, topic templates, key
parsing) — the parts that don't depend on the ESP32, radio, or network stack.
Integration behavior (AsyncTCP transport, WiFi/MQTT, SoftAP) is exercised
separately; see "Local testing without hardware" in MQTT_IMPLEMENTATION.md.
Running
pio test -e native # all suites
pio test -e native -f test_webconfig_keys # a single suite
A green [PASSED] per suite means GoogleTest returned 0 (all assertions
passed). PlatformIO's "0 test cases" line is just its Unity-style counter and
does not reflect the GoogleTest count — run the built binary directly
(.pio/build/native/program) to see the per-assertion breakdown.
Suites
| Suite | Source under test | Covers |
|---|---|---|
test_mqtt_presets |
src/helpers/MQTTPresets.h |
preset lookup; table integrity (unique names, non-empty URLs, JWT-audience invariant, names fit the slot buffer); mqttPresetNeedsSlotCredentials; slot-count constants |
test_observer_validation |
src/helpers/MQTTObserverValidation.h |
IATA (exactly 3 alphanumerics), owner key (64 hex), NTP hostname, and the buffer-fit check behind the #17 length validation — including boundaries and nulls |
test_webconfig_keys |
src/helpers/WebConfigKeys.h |
POST-key allowlist, secret detection, admin-password classification/validation, slot-index bounds, and the short-key out-of-bounds guard (attacker-supplied keys) |
test_topic_template |
src/helpers/MQTTTopicTemplate.h |
{iata}/{device}/{token}/{type} expansion, overflow/NUL-termination, and a buffer-size fuzz |
test_mqtt_topic_router |
src/helpers/MQTTTopicRouter.h |
complete preset/custom topic-routing contract; MeshRank all types except raw; required identifiers; invalid inputs/slots; exact buffer boundaries |
test_mqtt_connection_policy |
src/helpers/MQTTConnectionPolicy.h |
reconnect guard/backoff/stagger and breaker transitions; stable reset; JWT lifetime/renewal policy; exact timing boundaries and 32-bit millis() rollover |
test_mqtt_packet_queue_policy |
src/helpers/MQTTPacketQueuePolicy.h |
queue-full eviction; stale-disconnect flush; adaptive drain limits; bounded QoS0 retries; exact timing boundaries and 32-bit millis() rollover |
test_mqtt_packet_filter |
src/helpers/MQTTPacketFilter.h |
per-slot 0-15 allowlist parsing/formatting, numeric and named spellings; exact bounds; membership; candidate/eligible split and retry-completion policy; pre-queue union gate; default-mask detection |
test_mqtt_runtime_buffer_lifecycle |
src/helpers/MQTTRuntimeBufferLifecycle.h |
idempotent allocation/release; partial-allocation degradation; retry of only missing buffers |
test_mqtt_prefs_codec |
src/helpers/MQTTPrefsStorage.h, src/helpers/MQTTPrefsCodec.h |
binary pre-slot/3-slot/6-slot migration fixtures; v1 header integrity; downgrade preservation; shortest-payload write policy (default filters stay downgrade-readable) |
test_mqtt_prefs_serializer |
src/helpers/MQTTPrefsSerializer.h, src/helpers/ConfigSerializer.* |
semantic nested /mqtt.json round trips; numeric slot keys; required/future version handling; strict length/overflow/duplicate rejection; safe semantic repair; scratch-before-live loading |
test_mqtt_prefs_atomic_store |
src/helpers/MQTTPrefsAtomicStore.h, src/helpers/MQTTPrefsRecovery.h |
production JSON begin/write/checksum-finish/schema-verify/commit orchestration; first-migration and rename-boundary recovery; legacy /node_prefs handoff; failure cleanup and original-file preservation |
test_mqtt_payload_builder |
src/helpers/MQTTPayloadBuilder.cpp |
status/packet/raw JSON contracts; optional fields; escaping; RX metrics and path; score handling; exact buffer bounds; maximum representative payloads |
test_utils |
src/Utils.cpp |
Utils::toHex (upstream) |
Conventions (and how to add a suite)
- Each
test/test_<name>/directory builds into its own GoogleTest program and must define its ownmain()(::testing::InitGoogleTest+RUN_ALL_TESTS). - Tests are host-only: include only pure headers. Arduino/crypto stubs live
in
test/mocks/(on the include path via-I test/mocks). - Firmware headers are included from
src(via-I src, e.g.#include "helpers/MQTTPresets.h"). Some are guarded or ESP-flavored, so a suite may need shims before the include — e.g.test_mqtt_presetsdoes#define WITH_MQTT_BRIDGE 1(the preset table is behind that flag) and#define PROGMEM(the embedded CA-cert strings are PROGMEM-qualified). - To add a suite: create
test/test_<name>/test_<name>.cppwith amain(), and add any host-only source it links to thenativeenv'sbuild_src_filterinplatformio.ini(header-only code needs no source entry). No other wiring. - Keep logic testable by extracting pure functions into headers (as
MQTTObserverValidation.h/WebConfigKeys.h/MQTTTopicTemplate.hdo) and having the firmware call the same functions.