aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-node
diff options
context:
space:
mode:
Diffstat (limited to 'packages/meshbay-node')
-rw-r--r--packages/meshbay-node/tests/golden/cli.json4
-rw-r--r--packages/meshbay-node/tests/test_added_root_does_not_wait_for_its_scan.py2
-rw-r--r--packages/meshbay-node/tests/test_audio_meta_cache.py6
-rw-r--r--packages/meshbay-node/tests/test_cli_dispatch.py2
-rw-r--r--packages/meshbay-node/tests/test_cli_golden.py22
-rw-r--r--packages/meshbay-node/tests/test_disk_io_off_loop.py15
-rw-r--r--packages/meshbay-node/tests/test_hot_reload_survives_client_close.py8
-rw-r--r--packages/meshbay-node/tests/test_keystore.py2
-rw-r--r--packages/meshbay-node/tests/test_leaseless_reads.py2
-rw-r--r--packages/meshbay-node/tests/test_node_status.py20
-rw-r--r--packages/meshbay-node/tests/test_ops.py2
-rw-r--r--packages/meshbay-node/tests/test_quic_enabled.py2
-rw-r--r--packages/meshbay-node/tests/test_root_ops_reach_the_live_set.py12
-rw-r--r--packages/meshbay-node/tests/test_root_paths_are_operator_only.py4
-rw-r--r--packages/meshbay-node/tests/test_root_work_outlives_the_session.py2
-rw-r--r--packages/meshbay-node/tests/test_roots.py6
-rw-r--r--packages/meshbay-node/tests/test_roster_pairing.py6
-rw-r--r--packages/meshbay-node/tests/test_security_regressions.py2
-rw-r--r--packages/meshbay-node/tests/test_stream_capacity_config.py2
-rw-r--r--packages/meshbay-node/tests/test_upload_size_cap.py2
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)