diff options
Diffstat (limited to 'packages/meshbay-node')
| -rw-r--r-- | packages/meshbay-node/tests/test_cli_dispatch.py | 34 | ||||
| -rw-r--r-- | packages/meshbay-node/tests/test_packaging_win.py | 27 |
2 files changed, 61 insertions, 0 deletions
diff --git a/packages/meshbay-node/tests/test_cli_dispatch.py b/packages/meshbay-node/tests/test_cli_dispatch.py index caf672d..f58c020 100644 --- a/packages/meshbay-node/tests/test_cli_dispatch.py +++ b/packages/meshbay-node/tests/test_cli_dispatch.py @@ -190,3 +190,37 @@ def test_lifecycle_commands_report_systemctl_failure( daemon_mod.main() assert exc.value.code == 1 assert "not loaded" in capsys.readouterr().out + + +def test_a_bare_invocation_with_no_config_yet_exits_cleanly(monkeypatch, tmp_path, capsys): + """ + A bare `meshbay-node` — no subcommand, nothing dispatches, falls through to + "start the daemon" at the bottom of main() — is exactly what the Windows W3 + Startup .vbs and the service-mode Scheduled Task both run unattended. On a + fresh install neither has ever provisioned anything, so this is the first + thing that runs on the very first boot, quite possibly before the user has + ever opened the client. + + This must fail closed, fast, and without a trace: no exception (nothing + catches one — the .vbs and the Scheduled Task are both headless, so an + uncaught crash is invisible and, run on every boot, an inert but permanent + fixture in the Task Scheduler history and Windows' own Application log). + Confirmed by hand first: measured under a second and zero bytes written + to disk against a real, empty %LOCALAPPDATA%. + + load_config() already returns an empty Config when the path does not + exist rather than raising — this pins that main() then notices the empty + hub.username and exits before ever reaching NodeDaemon()/asyncio.run(), + so nothing here has to mock the daemon startup at all. + """ + missing_config = tmp_path / "does-not-exist" / "node.toml" + monkeypatch.setattr(daemon_mod, "DEFAULT_CONFIG_PATH", missing_config) + monkeypatch.setattr(sys, "argv", ["meshbay-node"]) + + with pytest.raises(SystemExit) as exc: + daemon_mod.main() + + assert exc.value.code == 1 + assert "meshbay-node init" in capsys.readouterr().out + assert not missing_config.parent.exists(), ( + "a fresh, unprovisioned start must not create anything on disk") diff --git a/packages/meshbay-node/tests/test_packaging_win.py b/packages/meshbay-node/tests/test_packaging_win.py index 6471446..22ce44b 100644 --- a/packages/meshbay-node/tests/test_packaging_win.py +++ b/packages/meshbay-node/tests/test_packaging_win.py @@ -327,6 +327,33 @@ def test_main_js_drives_the_service_task_for_all_three_actions(): assert "winServiceTaskStatus" in body, f"{handler} never checks for the service task" +def test_node_start_provisions_before_it_ever_touches_the_service_task(): + """ + On a fresh install with service mode chosen, the Scheduled Task exists + before anything is provisioned (node.toml is written by the wizard, not + by the installer). node:start must call provisionNode() first and only + then ask about the service task / spawn -- reversed, the very first + "start" from the wizard would run (or query) an unconfigured daemon + instead of the one it just told the caller to expect. + + A bare unprovisioned run is independently proven harmless + (test_a_bare_invocation_with_no_config_yet_exits_cleanly in + test_cli_dispatch.py), so this is about correctness of *this* call + actually starting the daemon the wizard just configured, not about + safety if the order were reversed. + """ + main_js = (CLIENT / "src" / "main.js").read_text(encoding="utf-8") + body = main_js.split("ipcMain.handle('node:start'", 1)[1] + body = body[:body.index("ipcMain.handle(")] + + provision_at = body.index("provisionNode(") + service_check_at = body.index("winServiceTaskStatus") + assert provision_at < service_check_at, ( + "node:start checks the service task before provisioning — a fresh " + "install's first Start would run/query the daemon before node.toml " + "exists for it to read") + + def test_firewall_ps1_targets_both_executables_and_is_idempotent(): """One script, both rules — so installer.nsh only ever has to name it once on the way in and once on the way out.""" |