From 7601991ccb1d75637c055062c38b1852eeef9700 Mon Sep 17 00:00:00 2001 From: Christophe Besson Date: Sat, 5 Sep 2026 09:28:03 +0200 Subject: feat(client): a Node-page toggle to switch into/out of service mode The installer's own mode question is effectively one-shot: customInstall skips it entirely once the firewall rules already exist, for any reason -- and per-user mode sets those up on its own, with no Scheduled Task involved. So declining once (or the rules existing from something unrelated, as happened on a dev machine this session) was a dead end: no reinstall, repair, or uninstall/reinstall cycle could ever bring the question back, since uninstall defaults to leaving both alone. Add the other door in (and out): a checkbox on the Node page, next to the existing per-user autostart toggle, wired main.js -> preload.js -> platform.js -> node-page.js. It runs packaging/win/service-mode.ps1 -- the exact script installer.nsh already runs -- via one Start-Process -Verb RunAs elevation, so the two paths can never disagree about what service mode means. The elevation helper writes a tiny param()-based .ps1 to %TEMP% so the target script path and its arguments bind through real PowerShell parameters instead of nested string-quoting. Also fixes a real pre-existing gap found while checking this: 8 of the 10 locale catalogues (all but en/fr) were missing the autostart/service-mode keys added in an earlier commit this session (b782886) -- test_locales.py's key-set-parity check uses a for-loop with an inline assert, so it stopped at the first mismatch (fr) and never actually reached the other eight. Backfilled all five keys (three pre-existing, two new) in de/es/it/ja/nl/pl/pt-BR/zh-CN. Co-Authored-By: Claude Sonnet 5 --- packages/meshbay-node/tests/test_packaging_win.py | 51 +++++++++++++++++++++++ 1 file changed, 51 insertions(+) (limited to 'packages/meshbay-node') diff --git a/packages/meshbay-node/tests/test_packaging_win.py b/packages/meshbay-node/tests/test_packaging_win.py index 31816d2..21aaf29 100644 --- a/packages/meshbay-node/tests/test_packaging_win.py +++ b/packages/meshbay-node/tests/test_packaging_win.py @@ -316,6 +316,57 @@ def test_service_status_reports_state_without_admin(): "calls status/run/end directly, unelevated") +HUB_STATIC = ROOT / "packages" / "meshbay-hub" / "src" / "meshbay_hub" / "static" + + +def test_service_mode_toggle_elevates_the_same_script_the_installer_runs(): + """ + The installer's own mode question is effectively one-shot (it skips + itself the moment the firewall rules exist for any reason, and per-user + mode sets those up on its own with no Scheduled Task involved) -- so + declining once, or the rules existing for any other reason, is a dead + end through setup alone. The Node page's toggle is the other door in + (and out), and it must drive service-mode.ps1 -- the exact script + installer.nsh runs -- so the two paths can never disagree about what + "service mode" means. One elevation (-Verb RunAs), no stored password. + """ + main_js = (CLIENT / "src" / "main.js").read_text(encoding="utf-8") + assert "winElevateServiceMode" in main_js + fn = main_js.split("function winElevateServiceMode", 1)[1] + fn = fn[:fn.index("\n }\n")] + assert "service-mode.ps1" in fn + assert "-Verb RunAs" in fn or "'-Verb', 'RunAs'" in fn or "-Verb', 'RunAs'" in fn + assert "Get-Credential" not in fn + + handler = main_js.split("ipcMain.handle('node:service-mode'", 1)[1] + handler = handler[:handler.index("ipcMain.handle(")] + assert "winElevateServiceMode" in handler + assert "'install'" in handler or '"install"' in handler + assert "'remove'" in handler or '"remove"' in handler + + +def test_node_page_service_mode_toggle_is_wired_end_to_end(): + """preload.js -> platform.js -> node-page.js, the same three-layer shape + the existing autostart toggle uses. A break anywhere in this chain means + the checkbox renders but does nothing, or never renders at all.""" + preload = (CLIENT / "src" / "preload.js").read_text(encoding="utf-8") + assert "serviceMode:" in preload + assert "'node:service-mode'" in preload + + platform_js = (HUB_STATIC / "platform.js").read_text(encoding="utf-8") + assert "serviceMode:" in platform_js + assert "bridge.node.serviceMode('install')" in platform_js + assert "bridge.node.serviceMode('remove')" in platform_js + + node_page = (HUB_STATIC / "node-page.js").read_text(encoding="utf-8") + assert "platform.node.serviceMode.available" in node_page + assert "platform.node.serviceMode.install()" in node_page + assert "platform.node.serviceMode.remove()" in node_page + # Checked state must reflect the CURRENT mode, not a separate flag -- + # otherwise the toggle and the status panel above it could disagree. + assert "info.mode === 'service'" in node_page + + def test_the_help_smoke_test_joins_multiline_output_before_matching(): """ `& exe --help 2>&1` is an ARRAY once the output wraps past one line, which -- cgit v1.2.3