fix: review fixes for the OTA slot

- The field is hasOtaSlot, like hasEthernet and the other "has" bools.
- The web UI shows the device's reason for any failed upload, not only
  for /restore, so it shows "no OTA slot" from a page that missed the
  header. The update routes' 500 replies give Update.errorString()
  instead of an empty reason.
- Native: the writer leaves hasOtaSlot out when true and writes "os":0.
- e2e: header() next to uptime(); the test skips on firmware without
  the field instead of failing, and matches the reason loosely. The
  README says what the test sends.
- PROTOCOL.md and the comments name only the builds that exist.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
cjkas 2026-10-28 22:19:27 +01:00
parent 5721d34a34
commit 657b345eaf
13 changed files with 41 additions and 38 deletions

View file

@ -12,7 +12,7 @@ import urllib.request
import pytest
from proto_client import ProtoClient, run_sync
from test_settings import uptime
from test_settings import header, uptime
# Every route that needs the token under Secured, sent without a body or
# parameters, so a route that let one through would still change nothing.
@ -173,18 +173,16 @@ def test_a_socket_from_the_page_the_device_served_is_accepted(host, token):
def test_a_firmware_upload_without_an_ota_slot_is_refused(host, token, connect, schema):
"""Builds with one app slot (C5, C6, debug) say so in the header, and
refuse the upload without stopping the radio or rebooting."""
c = connect(schema=schema.hash)
_, frames, _ = run_sync(c)
key = schema.keys["header"]
header = next(schema.expand(f["d"][key]) for f in frames if key in f["d"])
if header.get("otaSlot", schema.default("otaSlot")):
"""Builds with one app slot (C6, debug) say so in the header, and refuse
the upload without stopping the radio or rebooting."""
_, frames, _ = run_sync(connect(schema=schema.hash))
# Left out at its default, true, as is a field older firmware doesn't have.
if header(schema, frames).get("hasOtaSlot", True):
pytest.skip("the device has an OTA slot: an upload would start an update")
uptime_before = uptime(schema, frames)
status, _, body = upload(host, "/updateFirmware", auth(token))
assert status == 400, body
assert json.loads(body)["desc"] == "This device has no OTA slot, so its firmware is flashed over USB."
assert "no OTA slot" in json.loads(body)["desc"]
# The old reply rebooted half a second later.
time.sleep(2)
_, frames, _ = run_sync(connect(schema=schema.hash))