diff --git a/repeater/web/api_endpoints.py b/repeater/web/api_endpoints.py index fa6b480..59641a7 100644 --- a/repeater/web/api_endpoints.py +++ b/repeater/web/api_endpoints.py @@ -1983,8 +1983,18 @@ class APIEndpoints: if "repeater" not in self.config: self.config["repeater"] = {} self.config["repeater"]["mode"] = new_mode + + # The engine reads the mode live from the shared config dict; + # update_and_save writes it to disk so the choice survives a + # restart. + result = self.config_manager.update_and_save( + updates={}, live_update=True, live_update_sections=["repeater"] + ) + if not result.get("saved", False): + return self._error(result.get("error", "Failed to save configuration to file")) + logger.info(f"Mode changed to: {new_mode}") - return {"success": True, "mode": new_mode} + return {"success": True, "mode": new_mode, "persisted": True} except cherrypy.HTTPError: # Re-raise HTTP errors (like 405 Method Not Allowed) without logging raise diff --git a/tests/test_api_endpoints_core_coverage.py b/tests/test_api_endpoints_core_coverage.py index 9aed860..f84a267 100644 --- a/tests/test_api_endpoints_core_coverage.py +++ b/tests/test_api_endpoints_core_coverage.py @@ -1726,10 +1726,14 @@ def test_set_mode_and_set_duty_cycle_paths(cherrypy_ctx): invalid = api.set_mode() assert invalid["success"] is False + api.config_manager.update_and_save.return_value = {"saved": True} request.json = {"mode": "monitor"} ok = api.set_mode() - assert ok == {"success": True, "mode": "monitor"} + assert ok == {"success": True, "mode": "monitor", "persisted": True} assert api.config["repeater"]["mode"] == "monitor" + api.config_manager.update_and_save.assert_called_once_with( + updates={}, live_update=True, live_update_sections=["repeater"] + ) request.json = {"enabled": False} duty = api.set_duty_cycle() @@ -1737,6 +1741,21 @@ def test_set_mode_and_set_duty_cycle_paths(cherrypy_ctx): assert api.config["duty_cycle"]["enforcement_enabled"] is False +def test_set_mode_save_failure_reports_error(cherrypy_ctx): + request, _ = cherrypy_ctx + api = _make_api({"repeater": {}}) + api.config_manager.update_and_save.return_value = {"saved": False, "error": "disk full"} + + request.method = "POST" + request.json = {"mode": "no_tx"} + resp = api.set_mode() + assert resp["success"] is False + assert "disk full" in resp["error"] + # The live switch already happened on the shared dict; only the + # persistence failed, and the error says so instead of a silent loss. + assert api.config["repeater"]["mode"] == "no_tx" + + def test_update_duty_cycle_config_branches(cherrypy_ctx): request, _ = cherrypy_ctx api = _make_api({"duty_cycle": {}})