diff options
Diffstat (limited to 'packages/meshbay-node')
20 files changed, 74 insertions, 49 deletions
diff --git a/packages/meshbay-node/tests/golden/cli.json b/packages/meshbay-node/tests/golden/cli.json index 8db5abd..71fa336 100644 --- a/packages/meshbay-node/tests/golden/cli.json +++ b/packages/meshbay-node/tests/golden/cli.json @@ -102,7 +102,7 @@ "asked": [], "exit": 0, "stderr": "", - "stdout": "encryption always on (MNP 3.4)\nepoch 1\nmessages 0 encrypted, 0 in the clear\n", + "stdout": "encryption always on (MNP <version>)\nepoch 1\nmessages 0 encrypted, 0 in the clear\n", "systemctl": [] }, "denylist clear": { @@ -423,7 +423,7 @@ "api": [], "asked": [], "exit": 2, - "stderr": "usage: meshbay-node [-h] [--hub-url HUB_URL] [--username USERNAME] [--dir DIR] [--yes]\n [--config CONFIG] [--group GROUP] [--link] [--writable] [--no-writable]\n [--removable] [--no-removable] [--name NAME]\n [--log-level {DEBUG,INFO,WARNING,ERROR}]\n [{init,reset,status,gek-init,gek,operator,member,group,root,file,video,chat,denylist,stun,transfers,reload,restart-daemon,autostart,service,calibrate-argon2}]\n [subcommand] [target] [value]\nmeshbay-node: error: argument command: invalid choice: 'no-such-verb' (choose from 'init', 'reset', 'status', 'gek-init', 'gek', 'operator', 'member', 'group', 'root', 'file', 'video', 'chat', 'denylist', 'stun', 'transfers', 'reload', 'restart-daemon', 'autostart', 'service', 'calibrate-argon2')\n", + "stderr": "usage: meshbay-node [-h] [--hub-url HUB_URL] [--username USERNAME] [--dir DIR] [--yes]\n [--config CONFIG] [--group GROUP] [--link] [--writable] [--no-writable]\n [--removable] [--no-removable] [--name NAME]\n [--log-level {DEBUG,INFO,WARNING,ERROR}]\n [{init,reset,status,gek-init,gek,operator,member,group,root,file,video,chat,denylist,stun,transfers,reload,restart-daemon,autostart,service,calibrate-argon2}]\n [subcommand] [target] [value]\nmeshbay-node: error: argument command: invalid choice: 'no-such-verb' (choose from init, reset, status, gek-init, gek, operator, member, group, root, file, video, chat, denylist, stun, transfers, reload, restart-daemon, autostart, service, calibrate-argon2)\n", "stdout": "", "systemctl": [] }, diff --git a/packages/meshbay-node/tests/test_added_root_does_not_wait_for_its_scan.py b/packages/meshbay-node/tests/test_added_root_does_not_wait_for_its_scan.py index 4d4c4e8..0a9762d 100644 --- a/packages/meshbay-node/tests/test_added_root_does_not_wait_for_its_scan.py +++ b/packages/meshbay-node/tests/test_added_root_does_not_wait_for_its_scan.py @@ -110,7 +110,7 @@ async def test_the_daemon_serves_the_new_set_and_releases_its_lock(tmp_path): f'data_dir = "{(tmp_path / "data").as_posix()}"\n\n' f'[[groups]]\nid = "{GROUP}"\nname = "plop"\n\n' f' [[groups.roots]]\n path = "{one.as_posix()}"\n name = "one"\n\n' - f' [[groups.roots]]\n path = "{two.as_posix()}"\n name = "two"\n') + f' [[groups.roots]]\n path = "{two.as_posix()}"\n name = "two"\n', encoding="utf-8") idx = await _indexer(one) ctx = {"roots": idx.roots} diff --git a/packages/meshbay-node/tests/test_audio_meta_cache.py b/packages/meshbay-node/tests/test_audio_meta_cache.py index e6ee315..835d3f6 100644 --- a/packages/meshbay-node/tests/test_audio_meta_cache.py +++ b/packages/meshbay-node/tests/test_audio_meta_cache.py @@ -14,6 +14,7 @@ old name's answer, which is the fault `_reenrich_renamed_audio_entries` exists to prevent. """ import asyncio +import shutil import subprocess from pathlib import Path @@ -23,8 +24,9 @@ from meshbay_node.indexer import enrich_audio from meshbay_node.indexer.enrich_audio import AudioEnricher from meshbay_node.media_cache import MediaCache -_HAVE_FFMPEG = ( - subprocess.run(["which", "ffmpeg"], capture_output=True).returncode == 0) +# shutil.which, not `which`: there is no `which` outside Git Bash on Windows, +# and the suite stopped at collection when run from PowerShell. +_HAVE_FFMPEG = shutil.which("ffmpeg") is not None pytestmark = pytest.mark.skipif(not _HAVE_FFMPEG, reason="ffmpeg not installed") diff --git a/packages/meshbay-node/tests/test_cli_dispatch.py b/packages/meshbay-node/tests/test_cli_dispatch.py index 5b16fbe..8ef18c2 100644 --- a/packages/meshbay-node/tests/test_cli_dispatch.py +++ b/packages/meshbay-node/tests/test_cli_dispatch.py @@ -113,7 +113,7 @@ def stub_daemon(monkeypatch, tmp_path): patch_cli(monkeypatch, "_resolve_group", lambda cfg, g: "g" * 32) conf = tmp_path / "node.toml" - conf.write_text('[hub]\nurl = "https://example.invalid"\n') + conf.write_text('[hub]\nurl = "https://example.invalid"\n', encoding="utf-8") patch_cli(monkeypatch, "DEFAULT_CONFIG_PATH", conf) # And a home of its own: the data directory (where `member invite` and # `operator pair` write their code, where `status` finds the run token of diff --git a/packages/meshbay-node/tests/test_cli_golden.py b/packages/meshbay-node/tests/test_cli_golden.py index 3d7b0c0..5fe3f0b 100644 --- a/packages/meshbay-node/tests/test_cli_golden.py +++ b/packages/meshbay-node/tests/test_cli_golden.py @@ -18,11 +18,13 @@ import builtins import getpass import json import os +import re import subprocess import sys from pathlib import Path import pytest +from meshbay_common import MNP_VERSION from meshbay_node import config as config_mod from meshbay_node import daemon as daemon_mod from test_cli_dispatch import VERBS @@ -77,7 +79,7 @@ def _run(argv: list[str], tmp_path: Path, capsys) -> dict: return subprocess.CompletedProcess(args, 0, stdout="", stderr="") conf = tmp_path / "node.toml" - conf.write_text('[hub]\nurl = "https://example.invalid"\n') + conf.write_text('[hub]\nurl = "https://example.invalid"\n', encoding="utf-8") code = 0 with pytest.MonkeyPatch.context() as mp: patch_cli(mp, "_daemon_api", fake_api) @@ -86,6 +88,9 @@ def _run(argv: list[str], tmp_path: Path, capsys) -> dict: # The data directory and the keystore's default path come from these; # left real, `status` would record the developer's own node. mp.setenv("HOME", str(tmp_path / "home")) + # Path.home() on Windows: without it `member invite` and `operator + # pair` wrote their codes into the developer's own profile. + mp.setenv("USERPROFILE", str(tmp_path / "home")) mp.setattr(config_mod, "DEFAULT_CONFIG_PATH", conf) mp.setattr(builtins, "input", fake_input) mp.setattr(getpass, "getpass", lambda *a, **kw: "test-password") @@ -93,6 +98,9 @@ def _run(argv: list[str], tmp_path: Path, capsys) -> dict: # argparse wraps the help to the terminal's width. mp.setenv("COLUMNS", "100") mp.setattr(sys, "argv", ["meshbay-node", *argv]) + # Recorded on Linux: `autostart`/`service` are Windows-only there, and + # their Windows side is test_platform.py's and test_packaging_win.py's. + mp.setattr(sys, "platform", "linux") try: daemon_mod.main() except SystemExit as e: @@ -100,7 +108,17 @@ def _run(argv: list[str], tmp_path: Path, capsys) -> dict: out = capsys.readouterr() def mask(text: str) -> str: - return text.replace(str(tmp_path), "<tmp>") + # Masked in the JSON text, where a Windows path's backslashes are + # doubled -- the unescaped form never matched there -- and spelled the + # POSIX way after it. The protocol version is not the CLI's shape: it + # left this recording failing everywhere from the MNP 4.0 bump on. + text = text.replace(json.dumps(str(tmp_path))[1:-1], "<tmp>") + text = re.sub(r'<tmp>(?:\\\\[^\\\s"]+)+', + lambda m: m.group(0).replace("\\\\", "/"), text) + # argparse quotes its choices on some Python versions and not others. + text = re.sub(r"\(choose from [^)]*\)", + lambda m: m.group(0).replace("'", ""), text) + return text.replace(f"MNP {MNP_VERSION}", "MNP <version>") return json.loads(mask(json.dumps({ "exit": code, "stdout": out.out, "stderr": out.err, diff --git a/packages/meshbay-node/tests/test_disk_io_off_loop.py b/packages/meshbay-node/tests/test_disk_io_off_loop.py index a94789a..44864ba 100644 --- a/packages/meshbay-node/tests/test_disk_io_off_loop.py +++ b/packages/meshbay-node/tests/test_disk_io_off_loop.py @@ -47,6 +47,11 @@ GROUP = "g" * 32 # gets ~40 wake-ups inside one of these and a blocked one gets none. SLOW_S = 0.2 TICK_S = 0.005 +# What a free loop actually achieves: Windows' asyncio timers run on the 15.6 ms +# system tick, so a 5 ms sleep takes 15.6 and a free loop gets ~13 wake-ups, +# never 40 -- every test here failed there against a threshold of 20. Still +# unmistakable: a blocked loop gets none. +FREE_TICKS = SLOW_S / max(TICK_S, time.get_clock_info("monotonic").resolution) CONTENT = b"a file worth waking a disk for" * 400 @@ -139,7 +144,7 @@ async def test_a_slow_chunk_read_does_not_stop_the_loop(tmp_path, monkeypatch): # The read really did take its time, and the loop really did keep running: # both halves matter, because a wrapper that never ran would also leave the # ticker free. - assert ticker.ticks > SLOW_S / TICK_S / 2, ( + assert ticker.ticks > FREE_TICKS / 2, ( f"the loop was blocked: {ticker.ticks} wake-ups during a {SLOW_S}s read") chunk = next(m for m in session.sent if m.get("type") == MNP.FILE_CHUNK) @@ -161,7 +166,7 @@ async def test_a_slow_stat_does_not_stop_the_loop(tmp_path, monkeypatch): await session._do_file_request( {"type": MNP.FILE_REQUEST, "file_id": entry.id, "chunk_index": 0}) - assert ticker.ticks > SLOW_S / TICK_S / 2, ( + assert ticker.ticks > FREE_TICKS / 2, ( f"the loop was blocked: {ticker.ticks} wake-ups during a {SLOW_S}s stat") assert any(m.get("type") == MNP.FILE_CHUNK for m in session.sent) @@ -230,7 +235,7 @@ async def test_the_availability_poll_does_not_stop_the_loop(tmp_path, monkeypatc await idx.stop() roots.close_io() - assert ticker.ticks > SLOW_S / TICK_S / 2, ( + assert ticker.ticks > FREE_TICKS / 2, ( f"the loop was blocked: {ticker.ticks} wake-ups during a {SLOW_S}s poll") @@ -322,7 +327,7 @@ async def test_a_slow_upload_write_does_not_stop_the_loop(tmp_path, monkeypatch) await session._do_file_upload(sealed_upload( session, filename="clip.bin", data=CONTENT)) - assert ticker.ticks > SLOW_S / TICK_S / 2, ( + assert ticker.ticks > FREE_TICKS / 2, ( f"the loop was blocked: {ticker.ticks} wake-ups during a {SLOW_S}s write") assert (shared / "clip.bin").read_bytes() == CONTENT @@ -405,5 +410,5 @@ async def test_a_slow_scratch_read_does_not_stop_the_loop(tmp_path, monkeypatch) media_tools._read_scratch_capped, scratch, 1 << 20, "subtitle track") assert blob == CONTENT - assert ticker.ticks > SLOW_S / TICK_S / 2, ( + assert ticker.ticks > FREE_TICKS / 2, ( f"the loop was blocked: {ticker.ticks} wake-ups during a {SLOW_S}s read") diff --git a/packages/meshbay-node/tests/test_hot_reload_survives_client_close.py b/packages/meshbay-node/tests/test_hot_reload_survives_client_close.py index fb9eade..97c14d2 100644 --- a/packages/meshbay-node/tests/test_hot_reload_survives_client_close.py +++ b/packages/meshbay-node/tests/test_hot_reload_survives_client_close.py @@ -101,7 +101,7 @@ async def test_hot_loaded_group_finishes_scanning_without_anyone_awaiting_the_re data_dir = tmp_path / "data" config_path = tmp_path / "node.toml" - config_path.write_text(_toml(data_dir, first_dir)) + config_path.write_text(_toml(data_dir, first_dir), encoding="utf-8") from meshbay_node.config import load_config daemon = NodeDaemon(load_config(config_path), config_path=config_path) @@ -163,7 +163,7 @@ async def test_hot_loaded_group_finishes_scanning_without_anyone_awaiting_the_re # attach + /api/reload would leave it, then fire the reload exactly # as ui/app.py does: scheduled, NOT awaited. config_path.write_text(_toml(data_dir, first_dir, - second_group_id, second_dir)) + second_group_id, second_dir), encoding="utf-8") reload_task = asyncio.ensure_future(daemon._reload_config()) # Stand in for "the browser tab is gone": do something completely @@ -225,7 +225,7 @@ async def test_group_scoped_ops_404_until_listed_then_succeed(tmp_path): data_dir = tmp_path / "data2" config_path = tmp_path / "node2.toml" - config_path.write_text(_toml(data_dir, first_dir)) + config_path.write_text(_toml(data_dir, first_dir), encoding="utf-8") from meshbay_node.config import load_config daemon = NodeDaemon(load_config(config_path), config_path=config_path) @@ -282,7 +282,7 @@ async def test_group_scoped_ops_404_until_listed_then_succeed(tmp_path): # The same file write ops.attach_group does (a raw text append), # then the same fire-and-forget reload /api/reload now does. config_path.write_text(_toml(data_dir, first_dir, - second_group_id, second_dir)) + second_group_id, second_dir), encoding="utf-8") reload_task = asyncio.ensure_future(ops.start_reload(daemon._state)) await asyncio.sleep(0.01) diff --git a/packages/meshbay-node/tests/test_keystore.py b/packages/meshbay-node/tests/test_keystore.py index b9dd3a4..5cef9f9 100644 --- a/packages/meshbay-node/tests/test_keystore.py +++ b/packages/meshbay-node/tests/test_keystore.py @@ -94,7 +94,7 @@ def test_env_var_unlock(tmp_path, monkeypatch): def test_unlock_file(tmp_path): path = tmp_path / "keystore.enc" kf_path = tmp_path / "unlock.key" - kf_path.write_text("filepass42") + kf_path.write_text("filepass42", encoding="utf-8") kf_path.chmod(0o600) create_keystore(path=path, password="filepass42") diff --git a/packages/meshbay-node/tests/test_leaseless_reads.py b/packages/meshbay-node/tests/test_leaseless_reads.py index 2c4c6cd..3c4018f 100644 --- a/packages/meshbay-node/tests/test_leaseless_reads.py +++ b/packages/meshbay-node/tests/test_leaseless_reads.py @@ -108,7 +108,7 @@ def test_the_bound_covers_what_the_music_player_actually_reads_ahead(): player = SPA / "music-player.js" if not player.exists(): pytest.skip("the SPA sources are not present next to the node package") - body = player.read_text() + body = player.read_text(encoding="utf-8") fn = body[body.index("function prefetchDepth()"):] fn = fn[:fn.index("\n}\n")] depths = [int(n) for n in re.findall(r"return (\d+);", fn)] diff --git a/packages/meshbay-node/tests/test_node_status.py b/packages/meshbay-node/tests/test_node_status.py index 8bb2f39..bd63f8e 100644 --- a/packages/meshbay-node/tests/test_node_status.py +++ b/packages/meshbay-node/tests/test_node_status.py @@ -266,7 +266,7 @@ async def test_add_root_creates_directory_and_returns_info(tmp_path): conf = tmp_path / "node.toml" conf.write_text(f'[[groups]]\nid = "{GROUP}"\nname = "test"\n\n' - f' [[groups.roots]]\n path = "{shared}"\n') + f' [[groups.roots]]\n path = "{shared}"\n', encoding="utf-8") node_cfg = NodeConfig.__new__(NodeConfig) node_cfg.groups = [cfg] @@ -308,7 +308,7 @@ async def test_remove_root_requires_at_least_one_remaining(tmp_path): conf = tmp_path / "node.toml" conf.write_text(f'[[groups]]\nid = "{GROUP}"\nname = "test"\n\n' - f' [[groups.roots]]\n path = "{shared}"\n') + f' [[groups.roots]]\n path = "{shared}"\n', encoding="utf-8") index = GroupIndex(group_id=GROUP, sk_node=Ed25519PrivateKey.generate()) roots = one_root(shared) state = { @@ -344,7 +344,7 @@ async def test_removing_a_writable_root_is_allowed(tmp_path): conf.write_text( f'[[groups]]\nid = "{GROUP}"\nname = "test"\n\n' f' [[groups.roots]]\n path = "{d1}"\n name = "incoming"\n writable = true\n\n' - f' [[groups.roots]]\n path = "{d2}"\n name = "shared"\n') + f' [[groups.roots]]\n path = "{d2}"\n name = "shared"\n', encoding="utf-8") roots = RootSet.build([asdict(r) for r in cfg.roots]) index = GroupIndex(group_id=GROUP, sk_node=Ed25519PrivateKey.generate()) state = { @@ -355,7 +355,7 @@ async def test_removing_a_writable_root_is_allowed(tmp_path): result = await ops.remove_root(state, GROUP, "incoming") assert result["status"] == "removed" assert [r["name"] for r in result["roots"]] == ["shared"] - assert conf.read_text().count("[[groups.roots]]") == 1 + assert conf.read_text(encoding="utf-8").count("[[groups.roots]]") == 1 async def test_update_root_rewrites_the_flags_in_node_toml(tmp_path): @@ -380,7 +380,7 @@ async def test_update_root_rewrites_the_flags_in_node_toml(tmp_path): f'[[groups]]\nid = "{GROUP}"\nname = "test"\n\n' f' [[groups.roots]]\n' f' # the operator explained this one to themselves\n' - f' path = "{d1}"\n name = "media"\n') + f' path = "{d1}"\n name = "media"\n', encoding="utf-8") roots = RootSet.build([asdict(r) for r in cfg.roots]) index = GroupIndex(group_id=GROUP, sk_node=Ed25519PrivateKey.generate()) state = { @@ -392,7 +392,7 @@ async def test_update_root_rewrites_the_flags_in_node_toml(tmp_path): result = await ops.update_root(state, GROUP, "media", writable=True, removable=True) assert result["status"] == "updated" - text = conf.read_text() + text = conf.read_text(encoding="utf-8") assert "writable = true" in text assert "removable = true" in text assert "the operator explained this one to themselves" in text, ( @@ -406,7 +406,7 @@ async def test_update_root_rewrites_the_flags_in_node_toml(tmp_path): # A second call that changes nothing must not append a duplicate line. await ops.update_root(state, GROUP, "media", writable=True, removable=True) - assert conf.read_text().count("writable =") == 1 + assert conf.read_text(encoding="utf-8").count("writable =") == 1 async def test_update_root_replaces_a_legacy_upload_line(tmp_path): @@ -430,7 +430,7 @@ async def test_update_root_replaces_a_legacy_upload_line(tmp_path): conf.write_text( f'[[groups]]\nid = "{GROUP}"\nname = "test"\n\n' f' [[groups.roots]]\n path = "{d1}"\n name = "media"\n' - f' upload = true\n') + f' upload = true\n', encoding="utf-8") roots = RootSet.build([asdict(r) for r in cfg.roots]) index = GroupIndex(group_id=GROUP, sk_node=Ed25519PrivateKey.generate()) state = { @@ -440,7 +440,7 @@ async def test_update_root_replaces_a_legacy_upload_line(tmp_path): } await ops.update_root(state, GROUP, "media", writable=False) - text = conf.read_text() + text = conf.read_text(encoding="utf-8") assert "upload = true" not in text assert "writable = false" in text @@ -463,7 +463,7 @@ async def test_remove_root_succeeds_with_two_roots(tmp_path): conf.write_text( f'[[groups]]\nid = "{GROUP}"\nname = "test"\n\n' f' [[groups.roots]]\n path = "{d1}"\n name = "dir1"\n\n' - f' [[groups.roots]]\n path = "{d2}"\n name = "dir2"\n') + f' [[groups.roots]]\n path = "{d2}"\n name = "dir2"\n', encoding="utf-8") roots = RootSet.build([asdict(r) for r in cfg.roots]) index = GroupIndex(group_id=GROUP, sk_node=Ed25519PrivateKey.generate()) state = { diff --git a/packages/meshbay-node/tests/test_ops.py b/packages/meshbay-node/tests/test_ops.py index d49d3fa..b011802 100644 --- a/packages/meshbay-node/tests/test_ops.py +++ b/packages/meshbay-node/tests/test_ops.py @@ -145,7 +145,7 @@ async def test_deleting_a_file_removes_it_from_disk_and_index(tmp_path): state = _state(tmp_path) ctx = state["groups_ctx"]["g" * 32] target = ctx["roots"].roots[0].path / "gone.txt" - target.write_text("x") + target.write_text("x", encoding="utf-8") from meshbay_common.protocol import IndexEntry ctx["index"].add_entry(IndexEntry(id="a" * 64, name="gone.txt", path="shared", size=1, type="other", added_at=0)) diff --git a/packages/meshbay-node/tests/test_quic_enabled.py b/packages/meshbay-node/tests/test_quic_enabled.py index c0c1568..b3abd8e 100644 --- a/packages/meshbay-node/tests/test_quic_enabled.py +++ b/packages/meshbay-node/tests/test_quic_enabled.py @@ -15,7 +15,7 @@ from meshbay_node.config import load_config def _cfg(tmp_path: Path, body: str): p = tmp_path / "node.toml" - p.write_text(textwrap.dedent(body)) + p.write_text(textwrap.dedent(body), encoding="utf-8") return load_config(p) diff --git a/packages/meshbay-node/tests/test_root_ops_reach_the_live_set.py b/packages/meshbay-node/tests/test_root_ops_reach_the_live_set.py index e90fdfe..b2b3650 100644 --- a/packages/meshbay-node/tests/test_root_ops_reach_the_live_set.py +++ b/packages/meshbay-node/tests/test_root_ops_reach_the_live_set.py @@ -58,7 +58,7 @@ async def _state(tmp_path: Path) -> tuple[dict, Roster]: f' [[groups.roots]]\n path = "{(tmp_path / "one").as_posix()}"\n' f' name = "one"\n\n' f' [[groups.roots]]\n path = "{(tmp_path / "two").as_posix()}"\n' - f' name = "two"\n') + f' name = "two"\n', encoding="utf-8") live = RootSet.build([asdict(r) for r in cfg.roots]) index = GroupIndex(group_id=GROUP, sk_node=Ed25519PrivateKey.generate()) @@ -101,7 +101,7 @@ async def test_adding_a_root_reaches_node_toml_and_the_ack(tmp_path): assert [r["name"] for r in result["roots"]] == ["one", "two", "uploads"] assert [r.name for r in state["config"].groups[0].roots] == [ "one", "two", "uploads"] - assert "uploads" in Path(state["config_path"]).read_text() + assert "uploads" in Path(state["config_path"]).read_text(encoding="utf-8") finally: await roster.close() @@ -111,7 +111,7 @@ async def test_removing_a_root_reaches_node_toml_and_the_ack(tmp_path): try: result = await ops.remove_root(state, GROUP, "two") assert [r["name"] for r in result["roots"]] == ["one"] - assert Path(state["config_path"]).read_text().count( + assert Path(state["config_path"]).read_text(encoding="utf-8").count( "[[groups.roots]]") == 1 finally: await roster.close() @@ -229,7 +229,7 @@ async def test_adding_the_same_directory_twice_is_still_refused(tmp_path): await ops.add_root(state, GROUP, str(tmp_path / "uploads")) assert len(state["config"].groups[0].roots) == 3, ( "the refused add left something behind") - assert Path(state["config_path"]).read_text().count( + assert Path(state["config_path"]).read_text(encoding="utf-8").count( "[[groups.roots]]") == 3 finally: await roster.close() @@ -283,12 +283,12 @@ async def test_the_file_on_disk_and_the_config_in_memory_agree(tmp_path): await ops.remove_root(state, GROUP, "one") import tomllib - on_disk = tomllib.loads(Path(state["config_path"]).read_text()) + on_disk = tomllib.loads(Path(state["config_path"]).read_text(encoding="utf-8")) disk_paths = [str(r["path"]) for r in on_disk["groups"][0]["roots"]] memory_paths = [Path(r.path).as_posix() for r in state["config"].groups[0].roots] assert disk_paths == memory_paths - assert Path(state["config_path"]).read_text().count( + assert Path(state["config_path"]).read_text(encoding="utf-8").count( "[[groups.roots]]") == 2 finally: await roster.close() diff --git a/packages/meshbay-node/tests/test_root_paths_are_operator_only.py b/packages/meshbay-node/tests/test_root_paths_are_operator_only.py index e00f1cf..646c7b4 100644 --- a/packages/meshbay-node/tests/test_root_paths_are_operator_only.py +++ b/packages/meshbay-node/tests/test_root_paths_are_operator_only.py @@ -122,7 +122,7 @@ def test_the_cli_only_reads_fields_the_payload_carries(monkeypatch, tmp_path, ca "groups": [{"id": gid, "roots": [_Recording(offered)]}]}) patch_cli(monkeypatch, "_resolve_group", lambda cfg, g: gid) conf = tmp_path / "node.toml" - conf.write_text('[hub]\nurl = "https://example.invalid"\n') + conf.write_text('[hub]\nurl = "https://example.invalid"\n', encoding="utf-8") patch_cli(monkeypatch, "DEFAULT_CONFIG_PATH", conf) # A stub missed would otherwise reach the node running on this machine. monkeypatch.setenv("HOME", str(tmp_path / "home")) @@ -131,7 +131,7 @@ def test_the_cli_only_reads_fields_the_payload_carries(monkeypatch, tmp_path, ca daemon_mod.main() assert read, "the root CLI no longer reads the payload this way" - assert "/tmp/x" in capsys.readouterr().out + assert str(Path("/tmp/x")) in capsys.readouterr().out assert read <= set(offered), ( f"the `root` CLI reads keys the loopback payload does not carry: " f"{sorted(read - set(offered))}") diff --git a/packages/meshbay-node/tests/test_root_work_outlives_the_session.py b/packages/meshbay-node/tests/test_root_work_outlives_the_session.py index 1e31c31..92bea4d 100644 --- a/packages/meshbay-node/tests/test_root_work_outlives_the_session.py +++ b/packages/meshbay-node/tests/test_root_work_outlives_the_session.py @@ -60,7 +60,7 @@ async def _daemon(tmp_path: Path, one: Path, two: Path): f'data_dir = "{(tmp_path / "data").as_posix()}"\n\n' f'[[groups]]\nid = "{GROUP}"\nname = "plop"\n\n' f' [[groups.roots]]\n path = "{one.as_posix()}"\n name = "one"\n\n' - f' [[groups.roots]]\n path = "{two.as_posix()}"\n name = "two"\n') + f' [[groups.roots]]\n path = "{two.as_posix()}"\n name = "two"\n', encoding="utf-8") idx = DirectoryIndexer(roots=_set(one), group_id=GROUP, sk_node=Ed25519PrivateKey.generate(), gek=None) diff --git a/packages/meshbay-node/tests/test_roots.py b/packages/meshbay-node/tests/test_roots.py index 004f004..9a403ff 100644 --- a/packages/meshbay-node/tests/test_roots.py +++ b/packages/meshbay-node/tests/test_roots.py @@ -320,13 +320,13 @@ def test_free_name_returns_original_when_not_taken(tmp_path): def test_free_name_appends_counter_on_collision(tmp_path): - (tmp_path / "photo.jpg").write_text("x") + (tmp_path / "photo.jpg").write_text("x", encoding="utf-8") assert _free_name(tmp_path, "photo.jpg") == "photo (2).jpg" def test_free_name_increments_past_multiple_collisions(tmp_path): - (tmp_path / "photo.jpg").write_text("x") - (tmp_path / "photo (2).jpg").write_text("x") + (tmp_path / "photo.jpg").write_text("x", encoding="utf-8") + (tmp_path / "photo (2).jpg").write_text("x", encoding="utf-8") assert _free_name(tmp_path, "photo.jpg") == "photo (3).jpg" diff --git a/packages/meshbay-node/tests/test_roster_pairing.py b/packages/meshbay-node/tests/test_roster_pairing.py index a2d0f8b..5c94855 100644 --- a/packages/meshbay-node/tests/test_roster_pairing.py +++ b/packages/meshbay-node/tests/test_roster_pairing.py @@ -634,7 +634,7 @@ def test_code_lifetimes_are_configurable(tmp_path): path.write_text( '[hub]\nurl = "https://example.org"\nusername = "grenet"\n' "[node]\ninvite_ttl_hours = 72\npair_ttl_hours = 2\n" - ) + , encoding="utf-8") cfg = load_config(path) assert cfg.node.invite_ttl_hours == 72 assert cfg.node.pair_ttl_hours == 2 @@ -924,7 +924,7 @@ async def test_group_add_appends_without_rewriting_the_file(tmp_path): conf = tmp_path / "node.toml" conf.write_text( '# keep me\n[hub]\nurl = "https://meshbay.org"\nusername = "grenet"\n\n' - '[[groups]]\nid = "aaaa"\nname = "first"\nshared_dir = "/tmp/a"\n') + '[[groups]]\nid = "aaaa"\nname = "first"\nshared_dir = "/tmp/a"\n', encoding="utf-8") block = ('\n[[groups]]\n' 'id = "bbbb"\n' @@ -974,7 +974,7 @@ async def test_a_directory_with_anything_in_it_is_refused(tmp_path, roster): session = await _dir_session(tmp_path, roster) full = tmp_path / "shared" / "full" full.mkdir() - (full / "keep.txt").write_text("still here") + (full / "keep.txt").write_text("still here", encoding="utf-8") await session._do_dir_delete({"dir": "shared/full"}) diff --git a/packages/meshbay-node/tests/test_security_regressions.py b/packages/meshbay-node/tests/test_security_regressions.py index 8e90332..c0c2c2c 100644 --- a/packages/meshbay-node/tests/test_security_regressions.py +++ b/packages/meshbay-node/tests/test_security_regressions.py @@ -682,7 +682,7 @@ def test_legacy_keystore_still_opens(tmp_path): "iv_b64": _b64.b64encode(iv).decode(), "tag_b64": _b64.b64encode(tag).decode(), "ciphertext_b64": _b64.b64encode(ct).decode(), - })) + }), encoding="utf-8") keys = load_keystore(path=path, password="legacy-pass") assert keys.pk_ed25519_b64 == pk_to_b64(sk_ed.public_key()) diff --git a/packages/meshbay-node/tests/test_stream_capacity_config.py b/packages/meshbay-node/tests/test_stream_capacity_config.py index a40cbc0..9350b4c 100644 --- a/packages/meshbay-node/tests/test_stream_capacity_config.py +++ b/packages/meshbay-node/tests/test_stream_capacity_config.py @@ -32,7 +32,7 @@ from node_source import daemon_call def _cfg(tmp_path: Path, body: str): p = tmp_path / "node.toml" - p.write_text(textwrap.dedent(body)) + p.write_text(textwrap.dedent(body), encoding="utf-8") return load_config(p) diff --git a/packages/meshbay-node/tests/test_upload_size_cap.py b/packages/meshbay-node/tests/test_upload_size_cap.py index d2deb03..677edde 100644 --- a/packages/meshbay-node/tests/test_upload_size_cap.py +++ b/packages/meshbay-node/tests/test_upload_size_cap.py @@ -26,7 +26,7 @@ from node_source import daemon_call, webrtc_source def _cfg(tmp_path: Path, body: str): p = tmp_path / "node.toml" - p.write_text(textwrap.dedent(body)) + p.write_text(textwrap.dedent(body), encoding="utf-8") return load_config(p) |