From 57ba3454b42f092b871b1bc30b4b285f144b8b02 Mon Sep 17 00:00:00 2001 From: ScottW514 Date: Thu, 10 Sep 2026 10:26:25 -0400 Subject: [PATCH] Clear a setting the only way the daemon accepts, everywhere it is cleared An empty form field never reaches the request, so POST /settings with an empty value in the body carries no key at all and the daemon answers 400, "no known setting in request". Only the query-string form gets an empty value through. Measured on the bench reference: POST /settings -d "lid_policy=" -> 400, the value unchanged POST /settings?lid_policy= -> 200, the key cleared Three places in the suite already knew this and say so in a comment; two did not. motion.lid-policy-hold restored with the form body, so on a machine whose lid_policy has never been set the restore was refused and the test failed its own check. It now uses the query-string form for the empty value, as the others do. This is the same test as the commit before: that one stopped it from writing the word "cancel" where the machine had nothing, and this one lets it write the nothing. cloud.mode-switch had the older shape of the same fault: it restored homing_mode to the literal "none" where the machine had it unset. On the bench reference the restore branch never ran, because that machine homes through the cloud, so it has not fired yet; on a machine with homing_mode unset it would have. It now restores exactly what it found. The rest of the suite was audited for this and is clean: camera.key-read, cooling.critical-tier, cooling.tec-drive, cooling.crash-watch-plumbing, cooling.fire-watch-tiers, cooling.floor-and-warm-up, cloud.verdict-hold, forgectrl.settings, logs.levels and motion.xy-microsteps all either guard the empty value or go through the shared Restore helper, which has always done it correctly. commission.cloud-header-capture uses that helper too. --- forgetest/forgetest/suite/cloud.py | 8 +++++++- forgetest/forgetest/suite/motion.py | 6 +++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/forgetest/forgetest/suite/cloud.py b/forgetest/forgetest/suite/cloud.py index 2cd4140..58abd87 100644 --- a/forgetest/forgetest/suite/cloud.py +++ b/forgetest/forgetest/suite/cloud.py @@ -372,7 +372,13 @@ def mode_switch(ctx): gfhome_homing(ctx, ev, g) finally: if ev["homing_mode"] != "gfcloud": - st, body = fc.post("/settings", data={"homing_mode": ev["homing_mode"] or "none"}) + # Back to exactly what the machine had: unset is the empty + # string, and clearing a key needs the query-string form. + # "none" is a value, and writing it where the machine had + # nothing is a leftover the hand-back reports. + st, body = (fc.post("/settings", params={"homing_mode": ""}) + if not ev["homing_mode"] + else fc.post("/settings", data={"homing_mode": ev["homing_mode"]})) ctx.log("restore homing_mode=%r -> %s", ev["homing_mode"], st) ctx.log("PASS: grbl -> cloud (session, hunt with the lid open, lens homed, airflow unjudged) -> " "grbl (port open, %s), then $H homed in %.1f s", ev["grbl_state"], ev["homing_s"]) diff --git a/forgetest/forgetest/suite/motion.py b/forgetest/forgetest/suite/motion.py index 01eece9..dded12c 100644 --- a/forgetest/forgetest/suite/motion.py +++ b/forgetest/forgetest/suite/motion.py @@ -1337,7 +1337,11 @@ def lid_policy_hold(ctx): g.command("G90") machine_idle(ctx) finally: - st, _b = fc.post("/settings", data={"lid_policy": was}) + # An empty value clears the key, and only the query-string form + # carries one: an empty form field never reaches the request, and + # the write is refused with "no known setting in request". + st, _b = (fc.post("/settings", params={"lid_policy": ""}) if not was + else fc.post("/settings", data={"lid_policy": was})) ev["lid_policy_restored"] = (fc.settings() or {}).get("lid_policy", "") ctx.log("lid_policy restored to %s", ev["lid_policy_restored"]) ctx.check(ev["lid_policy_restored"] == was, "lid_policy was not restored to %r", was)