diff options
Diffstat (limited to 'packages/meshbay-node/tests/test_packaging_win.py')
| -rw-r--r-- | packages/meshbay-node/tests/test_packaging_win.py | 114 |
1 files changed, 114 insertions, 0 deletions
diff --git a/packages/meshbay-node/tests/test_packaging_win.py b/packages/meshbay-node/tests/test_packaging_win.py index 2cc8f12..6f7573b 100644 --- a/packages/meshbay-node/tests/test_packaging_win.py +++ b/packages/meshbay-node/tests/test_packaging_win.py @@ -1102,3 +1102,117 @@ def test_build_win_msix_points_electron_builder_at_the_system_sdk(): # already pin that winCanElevateServiceMode() checks for service-mode.ps1's # presence generically, which is exactly what makes it work for a third # packaged target without being told about it. + + +# ------------------------------------------------------------------------ +# Three gaps a real sideload install found (2026-09-12) that reading the +# manifest and launching the app once had missed: no install-time hook means +# no equivalent of installer.nsh's PATH write either, an immediate daemon +# crash was silently discarded instead of surfacing to the user, and nobody +# told a first-time MSIX user the startup-mode choice existed at all. +# Confirmed live: process.resourcesPath/spawn work correctly under the +# installed AppX path, PATH actually gained the entry on next launch, and a +# real port-18000 collision now rejects in ~2s with the daemon's own stderr +# instead of a 60s generic timeout. +# ------------------------------------------------------------------------ + +ENSURE_NODE_PATH_PS1 = WIN / "ensure-node-path.ps1" + + +def test_ensure_node_path_script_is_idempotent_and_unelevated(): + """ + No admin verb, no elevation helper -- a per-user HKCU write never needed + elevation in the first place (installer.nsh's customInstall already did + this one unelevated); what MSIX lacks is an install-time hook to run + anything from, not the right to make this specific change. + """ + assert ENSURE_NODE_PATH_PS1.exists(), f"{ENSURE_NODE_PATH_PS1} is missing" + src = ENSURE_NODE_PATH_PS1.read_text(encoding="utf-8") + assert "HKEY_CURRENT_USER\\Environment" in src + assert "already present" in src, "must be a no-op when the entry already exists" + assert "RunAs" not in src and "Verb" not in src + assert "WM_SETTINGCHANGE" in src or "SendMessageTimeout" in src, ( + "must broadcast the change so already-open shells notice, same as " + "installer.nsh's own SendMessage") + + +def test_ensure_node_path_shipped_to_full_and_msix_not_light(): + pkg = _pkg() + full_yml = json.dumps(pkg["build"]) + assert "ensure-node-path.ps1" in full_yml + + msix_yml = MSIX_YML.read_text(encoding="utf-8") + assert "ensure-node-path.ps1" in msix_yml + + light_yml = LIGHT_YML.read_text(encoding="utf-8") + assert "ensure-node-path.ps1" not in light_yml, ( + "Light has no bundled node-runtime to add to PATH") + + +def test_main_js_calls_ensure_node_path_on_every_launch(): + src = MAIN_JS.read_text(encoding="utf-8") + assert "function winEnsureNodeOnPath()" in src + body = src.split("function winEnsureNodeOnPath()", 1)[1].split("\n }", 1)[0] + assert "hasBundledNode()" in body, "must not run at all for a Light install" + assert "ensure-node-path.ps1" in body + # Actually invoked, not just defined -- registerBridge() calls it once, + # unconditionally, on every launch (idempotent, so Full's already-set + # PATH is just a fast no-op query each time). + assert src.count("winEnsureNodeOnPath()") >= 2, ( + "must be both defined and called") + + +def test_node_start_surfaces_an_immediate_daemon_crash_instead_of_a_60s_timeout(): + """ + Reproduced live: a daemon that exits within ~1s (a port already bound, + reproduced with a second instance colliding on 127.0.0.1:18000) used to + be indistinguishable from one that simply never started -- spawn()'s + stdio was 'ignore', discarding the exact stderr line that named the real + problem, and waitForNode()'s 60s generic timeout was the only failure + path left. spawnNodeDetachedWatched watches for an early exit and + rejects with the daemon's own tail of stderr instead. + """ + src = MAIN_JS.read_text(encoding="utf-8") + assert "function spawnNodeDetachedWatched(" in src + body = src.split("function spawnNodeDetachedWatched(", 1)[1].split("\n }", 1)[0] + assert "stdio: ['ignore', 'pipe', 'pipe']" in body + assert "exited immediately" in body + assert "NODE_CRASH_WATCH_MS" in body + # The tail must be bounded by length, not by a line count -- a real + # capture had the actual OSError line pushed out by two uvicorn/asyncio + # tracebacks that followed it, which a short "last N lines" cut before + # this was fixed to bound by characters instead. + assert "split(/\\r?\\n/).slice(" not in body, ( + "a line-count tail can cut the one line that names the real error " + "-- bound by characters instead (reproduced live, see the comment " + "above this constant)") + assert "4000" in body + + async_fn = src.split("async function spawnNodeDetached()", 1)[1].split("\n }", 1)[0] + assert "spawnNodeDetachedWatched" in async_fn, ( + "spawnNodeDetached must actually use the watched spawn, not the old " + "fire-and-forget one") + + +def test_setup_welcome_hints_at_the_node_startup_choice(): + """ + build/installer.nsh's radio page was the only place this choice was ever + offered, and an AppX/MSIX install has no install-time page at all to + replace it with -- a first-time user of a build with a bundled node + otherwise has no reason to ever find the Node page's startup-mode + control. Shown only while neither mode is configured yet (so it + disappears on its own once one is, or never appears for Light/non- + Windows/browser, where platform.node.service.available is false). + """ + src = (HUB_STATIC / "app.js").read_text(encoding="utf-8") + fn = src.split("function SetupWelcome(", 1)[1].split("\nfunction ", 1)[0] + assert "platform.node.bundled()" in fn + assert "platform.node.service.status()" in fn + assert "mode === 'service'" in fn and "autostart" in fn + assert "setup.node_startup_hint" in fn + + +def test_node_startup_hint_key_exists_in_all_ten_locales(): + for name in ("en", "fr", "es", "pt-BR", "zh-CN", "ja", "de", "it", "nl", "pl"): + cat = (HUB_STATIC / "locales" / f"{name}.js").read_text(encoding="utf-8") + assert "'setup.node_startup_hint':" in cat, f"{name}.js is missing the key" |