"""Exercise regulator JSON, authenticated batch reboot and shipped browser UI.""" import json from pathlib import Path import unittest from test_replay_reset_integration import extract_braced import test_webconfig_lost_reply as batch_fixture import test_webconfig_recovery_auth as auth_fixture import test_webconfig_ui_runtime as browser_fixture ROOT = Path(__file__).resolve().parents[1] class WebConfigRegulatorTest(unittest.TestCase): compile_run = auth_fixture.WebConfigRecoveryAuthTest.compile_run setup_values = browser_fixture.WebConfigUiRuntimeTest.setup_values run_page = browser_fixture.WebConfigUiRuntimeTest.run_page def test_actual_snapshot_and_authenticated_reboot_batch(self): # Reuse the actual HTTP/config parser fixture, rather than a duplicate # batch model. The unrelated Lost-reply test main is replaced below. source = batch_fixture.LostReplyWebConfigTest.fixture(self) source = source[:source.index("String writeBody(")] server = (ROOT / "src/helpers/esp32/WebConfigServer.cpp").read_text() snapshot = extract_braced(server, "static void webConfigRegulatorsSnapshot(") self.compile_run(source + r''' #include namespace mesh { namespace power { uint8_t modes[2] = {0, 3}; RegulatorMode requested[2] = {RegulatorMode::Default, RegulatorMode::Dcdc}; RegulatorMode active[2] = {RegulatorMode::Default, RegulatorMode::Ldo}; bool fallback[2] = {false, true}, recovery = true; const char* regulatorModeName(RegulatorMode mode) { return mode == RegulatorMode::Dcdc ? "dcdc" : mode == RegulatorMode::Ldo ? "ldo" : "auto"; } uint8_t configuredRegulatorModes(bool mcu) { return modes[mcu ? 0 : 1]; } RegulatorMode requestedRegulatorMode(bool mcu) { return requested[mcu ? 0 : 1]; } RegulatorMode activeRegulatorMode(bool mcu) { return active[mcu ? 0 : 1]; } const char* activeRegulatorModeName(bool mcu) { auto mode = activeRegulatorMode(mcu); return mode == RegulatorMode::Default ? "unknown" : regulatorModeName(mode); } bool regulatorFallbackActive(bool mcu) { return fallback[mcu ? 0 : 1]; } bool regulatorRecoveryAvailable() { return recovery; } }} ''' + snapshot + r''' int main() { using namespace mesh::power; JsonDocument doc; webConfigRegulatorsSnapshot(doc.to()); assert(doc["recovery"].as()); assert(doc["mcu"]["modes"].as() == 0); assert(strcmp(doc["mcu"]["active"].as(), "unknown") == 0); assert(doc["radio"]["modes"].as() == 3); assert(strcmp(doc["radio"]["requested"].as(), "dcdc") == 0); assert(strcmp(doc["radio"]["active"].as(), "ldo") == 0); assert(doc["radio"]["fallback"].as()); modes[0] = 3; modes[1] = 2; recovery = false; requested[0] = active[0] = RegulatorMode::Dcdc; webConfigRegulatorsSnapshot(doc.to()); assert(!doc["recovery"].as() && doc["mcu"]["modes"].as() == 3); assert(doc["radio"]["modes"].as() == 2); assert(strcmp(doc["mcu"]["active"].as(), "dcdc") == 0); assert(mesh::esp32::managementPassword::publish("password")); for (const char* key : {"mcu.regulator", "radio.regulator"}) { assert(wcIsAllowedSetKey(key) && wcSetKeyRequiresReboot(key)); WebConfigServer server; String body = String("{\"reqid\":\"0123456789abcdef\",\"reboot\":false,\"set\":{\"") + key + "\":\"ldo\"}}"; AsyncWebServerRequest unauthenticated(body); unauthenticated.header.cookies.clear(); server.handleConfigPost(&unauthenticated); assert(unauthenticated.status == 401 && server._batch_count == 0); AsyncWebServerRequest save(body); server.handleConfigPost(&save); assert(save.status == 202 && server._batch_count == 1 && server._batch_reboot); assert(String(server._batch[0].cmd) == String("set ") + key + " ldo"); assert(String(server._batch[0].key) == key); AsyncWebServerRequest replay(body); server.handleConfigPost(&replay); assert(replay.status == 202 && server._batch_count == 1 && server._batch_reboot); } for (const char* key : {"mcu.regulator.extra", "radio.regulator.extra", "mcu.regulator\nreboot", "MCU.regulator", "radio.regulator ", "regulator"}) { assert(!wcIsAllowedSetKey(key) && !wcSetKeyRequiresReboot(key)); } assert(!wcSetKeyRequiresReboot(nullptr)); } ''') def test_ldo_only_radio_hides_dc_dc_and_unsupported_processor(self): status, config = self.setup_values() status["mode"] = "lan" config["regulators"] = { "recovery": False, "mcu": {"modes": 0}, "radio": {"modes": 2, "requested": "ldo", "active": "ldo", "fallback": False}, } prelude = r'''''' % (json.dumps(status), json.dumps(config)) dom = self.run_page(prelude) for check in ("regulator-visible", "mcu-hidden", "mcu-disabled", "dcdc-unavailable", "recovery-unavailable"): self.assertIn(f'data-test-{check}="true"', dom) self.assertIn('data-test-radio-value="ldo"', dom) self.assertIn('data-test-radio-status="Active: LDO. Automatic fallback: off."', dom) def test_clear_fallback_and_mode_change_use_existing_save_and_reboot_flow(self): status, config = self.setup_values() status["mode"] = "lan" config["regulators"] = { "recovery": True, "mcu": {"modes": 3, "requested": "dcdc", "active": "dcdc", "fallback": False}, "radio": {"modes": 3, "requested": "auto", "active": "ldo", "fallback": True}, } prelude = r'''''' % (json.dumps(status), json.dumps(config)) dom = self.run_page(prelude) for key, value in (("mcu-loaded", "dcdc"), ("clear-visible", "true"), ("unchanged-mode-resubmitted", "auto"), ("posts-before-save", "0"), ("posts", "1"), ("post-mcu", "ldo"), ("post-radio", "auto"), ("post-reboot", "true"), ("reboot-visible", "true"), ("regulator-reboot-message", "true")): self.assertIn(f'data-test-{key}="{value}"', dom) self.assertIn('data-test-fallback-status="Active: LDO. Automatic fallback: on (LDO)."', dom) def test_older_firmware_without_regulator_metadata_hides_the_card(self): status, config = self.setup_values() status["mode"] = "lan" config.pop("regulators", None) prelude = r'''''' % (json.dumps(status), json.dumps(config)) dom = self.run_page(prelude) self.assertIn('data-test-regulator-hidden="true"', dom) self.assertIn('data-test-radio-disabled="true"', dom) if __name__ == "__main__": unittest.main()