Files
ESP32_Serial_Swiss_Army_Knife/tests/web_firmware_update
Commander1024 31a22eba06 Add admin firmware upload support
Implement authenticated HTTPS OTA uploads with bounded streaming, image
validation, reboot coordination, and lifecycle exclusion. Add the admin
UI,
regression tests, and Phase 10 acceptance documentation.
2026-09-18 22:22:11 +02:00
..
2026-09-18 22:22:11 +02:00
2026-09-18 22:22:11 +02:00
2026-09-18 22:22:11 +02:00
2026-09-18 22:22:11 +02:00

Firmware upload backend host tests

Run from the repository root:

python3 tests/web_firmware_update/run.py
python3 tests/web_cookie_auth/run.py

The first compiles the unchanged production upload source, production auth admission helpers/parser, and production server upload reservation/typed reboot functions with -Wall -Wextra -Werror and trapping undefined-behavior checks. It uses installed ESP-IDF image-layout headers (IDF_PATH, otherwise the normal PlatformIO framework directory). It performs no firmware build or device action. HTTP IO, private header-adapter admission, database, OTA/flash and FreeRTOS scheduling are doubles. The existing cookie-auth suite separately exercises production private-adapter framing/duplicate-header handling and session storage.

The 61 cases cover:

  • Cookie, Origin, CSRF, admin, query/method and readiness rejection before reads.
  • Raw content type, nonempty body, actual partition capacity and inactive OTA app selection; bytewise partial image prefix and bounded/exact streaming writes.
  • Wrong magic/chip/segment/descriptor/hash-header rejection before OTA erase.
  • Service/identity busy, allocation/task creation and OTA begin/write/end/metadata/ boot-selection failures; handle consumption versus abort cleanup.
  • Parsed SDK image length mismatch, final session revocation/currentness failure.
  • EOF before/after OTA begin, stalled receive and total slow-drip deadlines.
  • Reservation rejection of competing upload/typed reboot, including during receive and boot selection; response failure at every response stage.
  • Successful commit followed by delayed reboot-owner action; failed response retains selection but does not reboot and releases reservations/resources.

These are deterministic failure-injection tests, not real SDK image-integrity, flash, concurrent scheduler, TLS, network, power-loss or hardware test evidence. Stop/start/rotation guard use is source-checked, not dynamically executed here.

Parent integration contract

POST /api/firmware, raw application/octet-stream, known Content-Length, existing cookie + same-origin Origin + X-CSRF-Token, admin only. Use the existing browser CORS-mode/same-origin-credentials request pattern. No multipart, chunked transfer, Expect, query parameters, signature/version policy or filename validation. Standard ESP32-S3 application image with appended SDK SHA-256 digest is required. A filename alone is never trusted.

200 {"ok":true,"rebooting":true} means SDK validation and boot selection succeeded. After synchronous send success, the preallocated owner task waits 500 ms and calls esp_restart() without stopping HTTPD or retaining a request or socket. It keeps service/identity reservations until reset. Send success is not peer receipt. Send failure after selection schedules no reboot, releases reservations, and leaves the accepted image selected for a later reset. A lost acknowledgement is therefore uncertain; never automatically retry.

All errors use {"error":"code"}:

HTTP Codes
400 invalid_request, invalid_firmware, firmware_incomplete
401 authentication_required
403 origin, csrf, admin_required
408 firmware_timeout
413 firmware_too_large
415 firmware_content_type
500 firmware_write_failed, firmware_commit_failed
503 unavailable, busy, firmware_unavailable, firmware_resources

Rejections with unread bodies close rather than asking HTTPD to discard the remaining upload. Responses are JSON, no-store, nosniff and no-referrer. Errors contain no SDK diagnostic text or secret material. A boot-selection API failure is not retried or followed by reboot; flash/power failures at metadata commit cannot offer transactional certainty beyond SDK guarantees.

The synchronous handler may block other HTTPD work for the upload duration. A 4 KiB heap buffer and a 2 KiB reboot-owner stack are prepared before flashing; no whole-image allocation. Receive checks enforce 10 seconds without progress and 120 seconds total, using the server's existing one-second receive timeout. These checks do not preempt SDK flash/validation calls or scheduler delays. Only the inactive app and SDK OTA metadata are written, not NVS/data partitions.

Integration work outside backend ownership

  • Server URI capacity is now 40; server_lifecycle.py currently fails its hardcoded 36-initializer extraction check (now 37). Its old 39-handler capacity, required-registration positions 116 and optional positions 1718 also need adjustment to 40, 117 and 1819, respectively, plus a firmware-handler fake. That existing fixture is intentionally not edited here.
  • HTTPS stop/start/rotation and typed reboot share the upload reservation; direct identity mutation is separately reserved. Direct esp_restart() callers in system_console.c, ssh_transport.c, web_admin_transport.c, and local_status_ui.c do not share this fence. Those files were outside the backend's allowed ownership. The parent must coordinate those reboot paths for global software-reboot exclusion; do not claim that property yet.