1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
|
"""The node's life on a Windows desktop: set up at sign-in, stopped for real.
Two defects from the first Windows beta tester, each found again live on a
clean install (2026-10-07):
- After a service-mode install the node was never set up for the account that
signed in, the Node section stayed out of the sidebar, and the way out was
`meshbay-node init` in a terminal plus the key pasted on the profile page.
- A Stop left meshbay-node.exe running while the Node page said "Stopped": the
control API closes before the process exits, and the page asked nothing else.
What can run for real here does (processes, the CLI's stop, the exit timer);
the desktop application's side is pinned by reading main.js and app.js, the only
evidence available without launching it -- tests/e2e_windows_app.py launches it.
"""
import inspect
import os
import subprocess
import sys
import textwrap
import time
from pathlib import Path
import pytest
from meshbay_node import platform as plat
from meshbay_node.cli import lifecycle
ROOT = Path(__file__).resolve().parents[3]
MAIN_JS = ROOT / "packages" / "meshbay-client" / "src" / "main.js"
STATIC = ROOT / "packages" / "meshbay-hub" / "src" / "meshbay_hub" / "static"
def _js(path: Path) -> str:
return path.read_text(encoding="utf-8")
def _block(src: str, start: str, end: str = "\n });\n") -> str:
return src.split(start, 1)[1].split(end, 1)[0]
# ── node.toml ────────────────────────────────────────────────────────────────
def test_a_byte_order_mark_in_node_toml_is_read(tmp_path):
from meshbay_node.config import load_config
cfg_file = tmp_path / "node.toml"
cfg_file.write_bytes(b"\xef\xbb\xbf"
+ b'[hub]\nurl = "http://127.0.0.1:1"\nusername = "someone"\n')
assert load_config(cfg_file).hub.username == "someone"
def test_a_node_toml_it_cannot_read_is_said_not_swallowed(tmp_path):
"""A service node has no console: the reason it will not start goes to
the log, where it used to die after "Logging to ..." in silence."""
cfg_file = tmp_path / "node.toml"
cfg_file.write_text("[hub\nusername = ", encoding="utf-8")
env = {**os.environ, "LOCALAPPDATA": str(tmp_path), "APPDATA": str(tmp_path),
"HOME": str(tmp_path), "USERPROFILE": str(tmp_path)}
r = subprocess.run([sys.executable, "-c", "from meshbay_node.daemon import main; main()",
"--config", str(cfg_file)],
capture_output=True, text=True, timeout=60, env=env)
assert r.returncode == 1, r.stdout + r.stderr
assert "cannot read" in r.stdout
if sys.platform == "win32":
log = tmp_path / "meshbay" / "state" / "node.log"
assert "cannot read" in log.read_text(encoding="utf-8")
# ── the daemon leaves when it has stopped ────────────────────────────────────
def test_a_stopped_node_does_not_wait_for_a_busy_thread():
"""A worker still walking a large tree kept the process alive after
_shutdown(), with its control API already closed."""
script = textwrap.dedent("""
import threading, time
from meshbay_node.daemon import exit_after
threading.Thread(target=time.sleep, args=(120,), name="walker").start()
exit_after(0.5, "the node has stopped")
""")
t0 = time.monotonic()
r = subprocess.run([sys.executable, "-c", script], capture_output=True, text=True,
timeout=60)
assert r.returncode == 0, r.stderr
assert time.monotonic() - t0 < 30
assert "walker" in r.stderr, "it names what it did not wait for"
def test_only_the_real_daemon_ends_its_own_process():
"""Tests run daemons inside pytest: the timer must be armed by main()
alone, both before _shutdown() (a hung shutdown) and after it."""
from meshbay_node import daemon
assert daemon.NodeDaemon.exit_process_when_stopped is False
assert "daemon.exit_process_when_stopped = True" in inspect.getsource(daemon.main)
shutdown = inspect.getsource(daemon.NodeDaemon._shutdown)
assert shutdown.count("if self.exit_process_when_stopped:") == 2
assert shutdown.index("SHUTDOWN_DEADLINE_SECS") < shutdown.index('log.info("Node stopped")') \
< shutdown.index("EXIT_GRACE_SECS")
# ── the CLI's stop: by pid, then checked ─────────────────────────────────────
class _Stub:
def __init__(self, monkeypatch, *, answering=None, graceful=False, pids=(),
kill_works=True):
self.killed: list[int] = []
self.pids = list(pids)
monkeypatch.setattr(lifecycle, "_running_status", lambda cfg: answering)
monkeypatch.setattr(plat, "request_graceful_stop", lambda *a, **k: graceful)
monkeypatch.setattr(plat, "service_status", lambda: {"installed": False, "state": ""})
monkeypatch.setattr(plat, "_pid_alive", lambda pid: pid in self.pids)
monkeypatch.setattr(plat, "autostart_end", lambda: None)
monkeypatch.setattr(plat, "node_pids", lambda: list(self.pids))
def kill(pid):
self.killed.append(pid)
if kill_works:
self.pids.remove(pid)
monkeypatch.setattr(plat, "kill_pid", kill)
clock = iter(range(0, 10_000))
monkeypatch.setattr(time, "monotonic", lambda: next(clock))
monkeypatch.setattr(time, "sleep", lambda s: None)
class _Cfg:
data_dir = Path(".")
class node:
ui_port = 1
def test_a_stop_the_node_did_not_finish_kills_it_by_pid(monkeypatch):
stub = _Stub(monkeypatch, answering={"pid": 4242}, graceful=False, pids=[4242])
assert lifecycle._stop_node(_Cfg) == "stopped (forced)"
assert stub.killed == [4242]
def test_a_node_process_left_behind_is_never_called_stopped(monkeypatch):
_Stub(monkeypatch, answering=None, graceful=False, pids=[5151], kill_works=False)
with pytest.raises(lifecycle.NodeStillRunning, match="5151"):
lifecycle._stop_node(_Cfg)
def test_a_node_answering_nothing_is_still_a_node(monkeypatch):
"""Its API closed, its process there: not "not running"."""
stub = _Stub(monkeypatch, answering=None, graceful=False, pids=[6161])
monkeypatch.setattr(plat, "autostart_end", lambda: stub.pids.clear())
assert lifecycle._stop_node(_Cfg) == "stopped (forced)"
def test_a_stop_that_fails_exits_non_zero_with_the_reason(monkeypatch, capsys):
def refuse(cfg):
raise lifecycle.NodeStillRunning("meshbay-node is still running (pid 7)")
monkeypatch.setattr(lifecycle, "_stop_node", refuse)
monkeypatch.setattr(lifecycle, "load_config", lambda p: _Cfg)
class Args:
config = None
subcommand = "stop"
monkeypatch.setattr(plat, "service_status", lambda: {"installed": False, "state": ""})
monkeypatch.setattr(sys, "platform", "win32")
with pytest.raises(SystemExit) as e:
lifecycle.autostart(Args)
assert e.value.code == 1
assert "pid 7" in capsys.readouterr().out
@pytest.mark.skipif(sys.platform != "win32", reason="lists and kills real processes")
def test_node_pids_finds_every_copy_but_its_caller(tmp_path, monkeypatch):
"""Under an image name of this test's own: the real one would find, and
kill, the developer's node."""
import shutil
exe = tmp_path / "mbpidtest.exe"
shutil.copy(r"C:\Windows\System32\PING.EXE", exe)
proc = subprocess.Popen([str(exe), "-n", "300", "127.0.0.1"],
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
monkeypatch.setattr(plat, "NODE_IMAGES", ("mbpidtest.exe",))
try:
assert plat.node_pids() == [proc.pid]
plat.kill_pid(proc.pid)
assert proc.wait(timeout=10) is not None
assert plat.node_pids() == []
finally:
if proc.poll() is None:
proc.kill()
@pytest.mark.skipif(sys.platform != "win32", reason="lists and kills real processes")
def test_node_pids_counts_the_windowless_daemon_too(tmp_path, monkeypatch):
"""The Store package's startup task runs meshbay-nodew.exe: a Stop that
looked for meshbay-node.exe alone would call that node gone."""
import shutil
procs = []
for name in ("mbpidtest.exe", "mbpidtestw.exe"):
shutil.copy(r"C:\Windows\System32\PING.EXE", tmp_path / name)
procs.append(subprocess.Popen([str(tmp_path / name), "-n", "300", "127.0.0.1"],
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL))
monkeypatch.setattr(plat, "NODE_IMAGES", ("mbpidtest.exe", "mbpidtestw.exe"))
try:
assert sorted(plat.node_pids()) == sorted(p.pid for p in procs)
finally:
for p in procs:
p.kill()
assert set(plat.NODE_IMAGES) == {"mbpidtest.exe", "mbpidtestw.exe"}
monkeypatch.undo()
assert plat.NODE_IMAGES == ("meshbay-node.exe", "meshbay-nodew.exe")
def _current_package_family() -> str:
import ctypes
length = ctypes.c_uint32(0)
k32 = ctypes.windll.kernel32
if k32.GetCurrentPackageFamilyName(ctypes.byref(length), None) != 122:
return ""
buf = ctypes.create_unicode_buffer(length.value)
k32.GetCurrentPackageFamilyName(ctypes.byref(length), buf)
return buf.value
@pytest.mark.skipif(sys.platform != "win32", reason="package identity is Windows'")
def test_only_meshbays_own_package_counts_as_the_store_install(monkeypatch):
"""A terminal inside another packaged application (an IDE or an agent
from the Store) gives its processes that application's identity, and the
test suite itself may run in one: that is not MeshBay's package."""
assert plat.in_store_package() is False
family = _current_package_family()
if family:
monkeypatch.setattr(plat, "STORE_PACKAGE_FAMILY_PREFIX", family.split("_")[0] + "_")
assert plat.in_store_package() is True
def test_the_store_package_switches_its_startup_task_not_a_launcher(monkeypatch, capsys):
"""There a Startup-folder launcher or a boot task would name the versioned
WindowsApps path each update deletes (and right after sign-in Windows
refused the launcher that path): `autostart` switches the package's
startup task, and says so on a first line the desktop app reads."""
monkeypatch.setattr(sys, "platform", "win32")
monkeypatch.setattr(plat, "in_store_package", lambda: True)
monkeypatch.setattr(plat, "autostart_install", lambda *a: pytest.fail("wrote a launcher"))
monkeypatch.setattr(plat, "autostart_remove", lambda *a: pytest.fail("removed a launcher"))
monkeypatch.setattr(plat, "service_install", lambda *a: pytest.fail("made a boot task"))
monkeypatch.setattr(plat, "service_status", lambda: {"installed": False, "state": ""})
asked = []
answers = {"enable": "Enabled", "disable": "Disabled", "query": "Disabled"}
monkeypatch.setattr(plat, "startup_task", lambda a: asked.append(a) or answers[a])
class Args:
config = None
subcommand = "install"
for sub, action, state in (("install", "enable", "Enabled"), ("remove", "disable", "Disabled"),
("status", "query", "Disabled")):
Args.subcommand = sub
lifecycle.autostart(Args)
assert asked[-1] == action
assert capsys.readouterr().out.splitlines()[0] == f"startup task {state}"
# Switched off by the user in Windows Settings: theirs to switch back on.
answers["enable"] = "DisabledByUser"
Args.subcommand = "install"
with pytest.raises(SystemExit) as e:
lifecycle.autostart(Args)
assert e.value.code == 1
assert "Settings > Apps > Startup" in capsys.readouterr().out
# At boot is the .exe installer's.
with pytest.raises(SystemExit) as e:
lifecycle.service(Args)
assert e.value.code == 1
assert ".exe" in capsys.readouterr().out
def test_a_refused_action_stays_on_the_node_page_until_the_next_action():
"""The status poll (every five seconds) cleared the one error state there
was, so a refusal vanished before it was read."""
page = _js(STATIC / "node-page.js")
panel = page.split("function NodeServicePanel(", 1)[1].split("\nfunction ", 1)[0]
refresh = panel.split("const refresh = useCallback(", 1)[1].split("}, []);", 1)[0]
act = panel.split("const act = useCallback(", 1)[1].split("}, [", 1)[0]
assert "setActErr" not in refresh and "setPollErr('')" in refresh
assert "setActErr(platform.bridgeMessage(e))" in act
assert "const err = actErr || pollErr;" in panel
def test_the_startup_task_is_refused_outside_the_store_package(monkeypatch):
monkeypatch.setattr(plat, "in_store_package", lambda: False)
with pytest.raises(RuntimeError, match="Microsoft Store package"):
plat.startup_task("query")
with pytest.raises(ValueError):
plat.startup_task("toggle")
# ── the desktop application: what it says, and when it sets the node up ──────
def test_the_node_page_asks_the_node_not_only_the_task():
"""A node started from a terminal runs while the service task reads
"Ready"; one on its way out answers nothing for a while. The page said
"Stopped" about both."""
main_js = _js(MAIN_JS)
status = main_js.split("async function nodeServiceStatus()", 1)[1].split("\n }\n", 1)[0]
assert "/running/i.test(svc.state)" not in status
assert status.count("winNodeActivity()") == 2
activity = main_js.split("async function winNodeActivity()", 1)[1].split("\n }\n", 1)[0]
assert "probeNode()" in activity and "winNodePids()" in activity
assert "'unknown'" in activity and "'inactive'" in activity
page = _js(STATIC / "node-page.js")
assert "const stoppable = info.activeState !== 'inactive';" in page
assert "disabled=${!!busy || !stoppable}" in page
def test_signing_in_sets_up_this_machines_node():
main_js = _js(MAIN_JS)
assert "handle('node:ensure'" in main_js
ensure = main_js.split("async function ensureNode(opts)", 1)[1].split("\n }\n", 1)[0]
# Only the node this build ships, on Windows: Linux is unchanged.
assert "process.platform !== 'win32' || !hasBundledNode()" in ensure
# A node set up for someone else is left as it is...
assert ensure.index("return { state: 'other_account' }") < ensure.index("provisionNode(")
# ...and so is an account already linked to another node.
assert "if (linked && linked !== p.pk_node_ed25519) return { state: 'other_node' };" in ensure
assert ensure.index("state: 'other_node'") < ensure.index("linkNodeKeyAndAwaitRunning(")
# Started however this machine is set up, through the CLI like every start.
assert "winNodeStartVia(['autostart', 'start'])" in ensure
# One start at a time.
start = main_js.split("handle('node:start'", 1)[1][:400]
assert "if (nodeEnsuring) await nodeEnsuring" in start
preload = _js(ROOT / "packages" / "meshbay-client" / "src" / "preload.js")
assert "ensure: (opts) => ipcRenderer.invoke('node:ensure', opts)" in preload
assert "bridge.node.ensure(opts)" in _js(STATIC / "platform.js")
def test_the_node_section_shows_for_a_node_not_yet_linked():
"""It waited for the hub to hold a node key, which nothing gave it."""
app = _js(STATIC / "app.js")
assert "showNode=${hasNodeKey || localNode}" in app
assert "platform.capabilities.nodeAdmin && (hasNodeKey || localNode)" in app
effect = app.split("const signedInAs = user ? user.username : null;", 1)[1]
effect = effect.split("}, [signedInAs]);", 1)[0]
assert "platform.node.ensure(" in effect and "platform.node.installed()" in effect
# The token rotates; a renewal is not a sign-in.
assert "}, [signedInAs]);" in app
|