From a45e77df1b0024707914a446aa89d33baa223787 Mon Sep 17 00:00:00 2001 From: Christophe Besson Date: Sun, 27 Sep 2026 22:21:26 +0200 Subject: test: make both suites pass on Windows MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Most of these failed on Windows for reasons that had nothing to do with the code under test, which is how real Windows defects hid among them: - Read and write files as UTF-8, and talk to Node in UTF-8. read_text(), write_text() and subprocess text=True use the locale codepage, cp1252 on Windows: "é", "—" and "→" arrived as "?" or crashed, some sixty tests. Calls to PowerShell and schtasks are left alone -- they answer in the console codepage. - Import ESM harness modules by file URL (as_uri): a raw "C:\..." path is not a module specifier. - test_cli_golden: mask the tmp path in its JSON-escaped form, spell it the POSIX way, record on Linux, mask the protocol version (the recording had failed everywhere since the MNP 4.0 bump) and argparse's version-dependent quoting; point USERPROFILE at the tmp home, or `member invite` and `operator pair` wrote their codes into the developer's profile. - test_disk_io_off_loop: expect what a free loop can reach on the platform's timer, 15.6 ms on Windows, not an assumed 5 ms. - test_root_paths_are_operator_only: expect the OS's spelling of the path. - test_audio_meta_cache: find ffprobe with shutil.which. Node suite on Windows: 1489 passed, none failed. Hub suite: 3 failures left, all older than this change (two SQLite concurrency tests, one transfer resume). Co-Authored-By: Claude Opus 5.5 --- packages/meshbay-hub/tests/harness/layout_probe.py | 2 +- packages/meshbay-hub/tests/harness/scroll_probe.py | 2 +- packages/meshbay-hub/tests/test_account_pinning.py | 4 +- packages/meshbay-hub/tests/test_argon2_off_loop.py | 2 +- .../meshbay-hub/tests/test_asset_versioning.py | 2 +- .../tests/test_availability_between_members.py | 2 +- .../meshbay-hub/tests/test_browser_idle_signout.py | 2 +- .../meshbay-hub/tests/test_bundle_kdf_parity.py | 8 ++-- .../meshbay-hub/tests/test_captcha_host_check.py | 9 ++-- packages/meshbay-hub/tests/test_cast_subtitles.py | 6 +-- .../tests/test_challenge_signature_client.py | 6 +-- .../meshbay-hub/tests/test_chat_scroll_bottom.py | 2 +- .../meshbay-hub/tests/test_client_version_gate.py | 8 ++-- packages/meshbay-hub/tests/test_downloads.py | 54 +++++++++++----------- .../meshbay-hub/tests/test_files_drop_upload.py | 2 +- packages/meshbay-hub/tests/test_files_sorting.py | 2 +- packages/meshbay-hub/tests/test_hook_ordering.py | 6 +-- .../meshbay-hub/tests/test_index_seal_client.py | 4 +- packages/meshbay-hub/tests/test_indexing_dock.py | 8 ++-- .../meshbay-hub/tests/test_invite_link_client.py | 4 +- packages/meshbay-hub/tests/test_layout_measured.py | 4 +- .../meshbay-hub/tests/test_layout_responsive.py | 4 +- packages/meshbay-hub/tests/test_locales.py | 11 +++-- packages/meshbay-hub/tests/test_media_pager.py | 4 +- packages/meshbay-hub/tests/test_member_removal.py | 2 +- packages/meshbay-hub/tests/test_memory_ceiling.py | 8 ++-- packages/meshbay-hub/tests/test_music_queue.py | 2 +- .../tests/test_node_page_width_measured.py | 2 +- .../tests/test_notification_dismissal.py | 4 +- packages/meshbay-hub/tests/test_offer_retry.py | 2 +- packages/meshbay-hub/tests/test_playlist_crypto.py | 6 +-- packages/meshbay-hub/tests/test_playlist_key.py | 6 +-- packages/meshbay-hub/tests/test_playlist_merge.py | 6 +-- packages/meshbay-hub/tests/test_queue_ops.py | 6 +-- .../meshbay-hub/tests/test_reconnect_refresh.py | 12 ++--- packages/meshbay-hub/tests/test_recovery_key.py | 4 +- packages/meshbay-hub/tests/test_resume_position.py | 2 +- packages/meshbay-hub/tests/test_rewrap_fanout.py | 4 +- .../tests/test_search_connect_deadline.py | 2 +- packages/meshbay-hub/tests/test_search_fanout.py | 2 +- .../tests/test_search_files_unmerged.py | 2 +- .../meshbay-hub/tests/test_search_media_merge.py | 10 ++-- packages/meshbay-hub/tests/test_search_pool.py | 2 +- .../meshbay-hub/tests/test_search_source_merge.py | 6 +-- .../tests/test_season_panel_placement.py | 10 ++-- packages/meshbay-hub/tests/test_session_renewal.py | 14 +++--- packages/meshbay-hub/tests/test_site_basics.py | 2 +- packages/meshbay-hub/tests/test_spa_ordering.py | 12 ++--- packages/meshbay-hub/tests/test_spa_syntax.py | 6 +-- .../tests/test_streamed_download_reliability.py | 14 +++--- .../meshbay-hub/tests/test_table_rows_measured.py | 2 +- packages/meshbay-hub/tests/test_transfers.py | 34 +++++++------- .../meshbay-hub/tests/test_transport_contracts.py | 12 ++--- .../meshbay-hub/tests/test_upload_seal_client.py | 4 +- packages/meshbay-hub/tests/test_versions_agree.py | 6 +-- .../meshbay-hub/tests/test_video_audio_track.py | 6 +-- .../meshbay-hub/tests/test_video_buffer_ceiling.py | 6 +-- .../meshbay-hub/tests/test_video_default_season.py | 6 +-- .../tests/test_video_detail_measured.py | 4 +- packages/meshbay-hub/tests/test_video_reconnect.py | 4 +- packages/meshbay-hub/tests/test_video_seek.py | 12 ++--- .../meshbay-hub/tests/test_video_stream_switch.py | 6 +-- packages/meshbay-hub/tests/test_video_subtitles.py | 6 +-- packages/meshbay-hub/tests/test_zip_size_limit.py | 6 +-- packages/meshbay-hub/tests/test_zipstream.py | 16 +++---- packages/meshbay-node/tests/golden/cli.json | 4 +- .../test_added_root_does_not_wait_for_its_scan.py | 2 +- .../meshbay-node/tests/test_audio_meta_cache.py | 6 ++- packages/meshbay-node/tests/test_cli_dispatch.py | 2 +- packages/meshbay-node/tests/test_cli_golden.py | 22 ++++++++- .../meshbay-node/tests/test_disk_io_off_loop.py | 15 ++++-- .../tests/test_hot_reload_survives_client_close.py | 8 ++-- packages/meshbay-node/tests/test_keystore.py | 2 +- .../meshbay-node/tests/test_leaseless_reads.py | 2 +- packages/meshbay-node/tests/test_node_status.py | 20 ++++---- packages/meshbay-node/tests/test_ops.py | 2 +- packages/meshbay-node/tests/test_quic_enabled.py | 2 +- .../tests/test_root_ops_reach_the_live_set.py | 12 ++--- .../tests/test_root_paths_are_operator_only.py | 4 +- .../tests/test_root_work_outlives_the_session.py | 2 +- packages/meshbay-node/tests/test_roots.py | 6 +-- packages/meshbay-node/tests/test_roster_pairing.py | 6 +-- .../tests/test_security_regressions.py | 2 +- .../tests/test_stream_capacity_config.py | 2 +- .../meshbay-node/tests/test_upload_size_cap.py | 2 +- 85 files changed, 293 insertions(+), 266 deletions(-) diff --git a/packages/meshbay-hub/tests/harness/layout_probe.py b/packages/meshbay-hub/tests/harness/layout_probe.py index 65b3083..0abbda6 100644 --- a/packages/meshbay-hub/tests/harness/layout_probe.py +++ b/packages/meshbay-hub/tests/harness/layout_probe.py @@ -81,7 +81,7 @@ RECORDS = [] def main() -> int: widths = [int(w) for w in sys.argv[1].split(",")] - fragment = Path(sys.argv[2]).read_text() + fragment = Path(sys.argv[2]).read_text(encoding="utf-8") selectors = sys.argv[3:] class H(http.server.BaseHTTPRequestHandler): diff --git a/packages/meshbay-hub/tests/harness/scroll_probe.py b/packages/meshbay-hub/tests/harness/scroll_probe.py index ae407b8..55d5b2b 100644 --- a/packages/meshbay-hub/tests/harness/scroll_probe.py +++ b/packages/meshbay-hub/tests/harness/scroll_probe.py @@ -32,7 +32,7 @@ STATIC = Path(__file__).resolve().parents[2] / "src" / "meshbay_hub" / "static" # group-page refactor. APP = STATIC / "chat-app.js" PORT = 8736 -FRAG = Path(sys.argv[1]).read_text() +FRAG = Path(sys.argv[1]).read_text(encoding="utf-8") HEIGHTS = ([int(h) for h in sys.argv[2].split(",")] if len(sys.argv) > 2 else [700, 900, 1200]) diff --git a/packages/meshbay-hub/tests/test_account_pinning.py b/packages/meshbay-hub/tests/test_account_pinning.py index 529ebd5..ebd29bd 100644 --- a/packages/meshbay-hub/tests/test_account_pinning.py +++ b/packages/meshbay-hub/tests/test_account_pinning.py @@ -102,11 +102,11 @@ def _entry(user, pk_ed, pk_x="cGtY", *, added_by="", sk_signer=None, def _verify(devices, node_pk=NODE_PK): with tempfile.TemporaryDirectory() as tmp: h = Path(tmp) / "h.js" - h.write_text(_HARNESS) + h.write_text(_HARNESS, encoding="utf-8") payload = Path(tmp) / "in.json" payload.write_text(json.dumps( {"payload": {"devices": devices, "node_pk": node_pk}, - "node_pk": node_pk})) + "node_pk": node_pk}), encoding="utf-8") run = subprocess.run( ["node", str(h), str(CRYPTO), transport_argv(), str(payload)], capture_output=True, timeout=60) diff --git a/packages/meshbay-hub/tests/test_argon2_off_loop.py b/packages/meshbay-hub/tests/test_argon2_off_loop.py index 5c25a8e..ae6cc08 100644 --- a/packages/meshbay-hub/tests/test_argon2_off_loop.py +++ b/packages/meshbay-hub/tests/test_argon2_off_loop.py @@ -28,7 +28,7 @@ def test_nothing_derives_argon2_on_the_event_loop(): for path in SRC.rglob("*.py"): if path.name == "auth.py": continue - for n, line in enumerate(path.read_text().splitlines(), 1): + for n, line in enumerate(path.read_text(encoding="utf-8").splitlines(), 1): if direct.search(line): offenders.append(f"{path.relative_to(SRC)}:{n}: {line.strip()}") assert not offenders, ( diff --git a/packages/meshbay-hub/tests/test_asset_versioning.py b/packages/meshbay-hub/tests/test_asset_versioning.py index aa2dc6d..7057311 100644 --- a/packages/meshbay-hub/tests/test_asset_versioning.py +++ b/packages/meshbay-hub/tests/test_asset_versioning.py @@ -135,7 +135,7 @@ def test_a_new_file_moves_the_fingerprint(): before = _asset_version() extra = STATIC_DIR / "locales" / "zz-test-only.js" try: - extra.write_text("export default {};\n") + extra.write_text("export default {};\n", encoding="utf-8") assert _asset_version() != before finally: extra.unlink() diff --git a/packages/meshbay-hub/tests/test_availability_between_members.py b/packages/meshbay-hub/tests/test_availability_between_members.py index cd10046..24d85a0 100644 --- a/packages/meshbay-hub/tests/test_availability_between_members.py +++ b/packages/meshbay-hub/tests/test_availability_between_members.py @@ -537,7 +537,7 @@ def test_no_mail_is_sent_from_the_event_loop(): for path in root.rglob("*.py"): if path.name == "mail.py": continue - for n, line in enumerate(path.read_text().splitlines(), 1): + for n, line in enumerate(path.read_text(encoding="utf-8").splitlines(), 1): if direct_call.search(line): offenders.append(f"{path.name}:{n}: {line.strip()}") assert not offenders, ( diff --git a/packages/meshbay-hub/tests/test_browser_idle_signout.py b/packages/meshbay-hub/tests/test_browser_idle_signout.py index 50f7f04..454874a 100644 --- a/packages/meshbay-hub/tests/test_browser_idle_signout.py +++ b/packages/meshbay-hub/tests/test_browser_idle_signout.py @@ -84,7 +84,7 @@ def outcome(): pytest.skip("node is not available") proc = subprocess.run( [NODE, "--input-type=module", "--eval", HARNESS, IDLE.as_uri()], - capture_output=True, text=True, timeout=30) + capture_output=True, text=True, encoding="utf-8", timeout=30) assert proc.returncode == 0, proc.stderr return json.loads(proc.stdout.strip().splitlines()[-1]) diff --git a/packages/meshbay-hub/tests/test_bundle_kdf_parity.py b/packages/meshbay-hub/tests/test_bundle_kdf_parity.py index 27e10d4..c62aace 100644 --- a/packages/meshbay-hub/tests/test_bundle_kdf_parity.py +++ b/packages/meshbay-hub/tests/test_bundle_kdf_parity.py @@ -77,19 +77,19 @@ const argon2 = require(process.argv[3]); def js_hashes(tmp_path_factory): d = tmp_path_factory.mktemp("kdf") harness = d / "harness.cjs" - harness.write_text(_HARNESS) + harness.write_text(_HARNESS, encoding="utf-8") vectors = [ {"username": u, "password": p, "mem": MEM_KIB, "time": TIME_COST, "lanes": LANES} for u in CASES for p in PASSWORDS ] payload = d / "vectors.json" - payload.write_text(json.dumps(vectors)) + payload.write_text(json.dumps(vectors), encoding="utf-8") proc = subprocess.run( ["node", str(harness), str(VENDOR / "argon2.wasm"), str(VENDOR / "argon2.min.js"), str(payload)], - capture_output=True, text=True, timeout=300, + capture_output=True, text=True, encoding="utf-8", timeout=300, ) if proc.returncode != 0: pytest.fail(f"node harness failed:\n{proc.stderr[-2000:]}") @@ -124,7 +124,7 @@ def test_parameters_still_match_the_client(): The numbers live in keyderive.js; this test is the second copy. Tuning one without the other orphans every bundle already written, so make it fail. """ - source = (STATIC / "keyderive.js").read_text() + source = (STATIC / "keyderive.js").read_text(encoding="utf-8") assert f"ARGON2_MEM_KIB = {MEM_KIB}" in source assert f"ARGON2_TIME = {TIME_COST}" in source assert f"ARGON2_LANES = {LANES}" in source diff --git a/packages/meshbay-hub/tests/test_captcha_host_check.py b/packages/meshbay-hub/tests/test_captcha_host_check.py index 778009f..a0ff509 100644 --- a/packages/meshbay-hub/tests/test_captcha_host_check.py +++ b/packages/meshbay-hub/tests/test_captcha_host_check.py @@ -208,7 +208,7 @@ def test_hub_toml_carries_the_allowed_hosts(tmp_path): '[captcha]\n' 'site_key = "6Lsite"\n' 'secret_key = "6Lsecret"\n' - 'allowed_hosts = ["meshbay.org", " meshbay ", "", "localhost"]\n') + 'allowed_hosts = ["meshbay.org", " meshbay ", "", "localhost"]\n', encoding="utf-8") cfg = load_config(cfg_file) @@ -222,7 +222,8 @@ def test_a_hub_toml_without_the_key_checks_nothing(tmp_path): """The upgrade path. A hub that never heard of this setting keeps the behaviour it has, with reCAPTCHA doing the origin check.""" cfg_file = tmp_path / "hub.toml" - cfg_file.write_text('[captcha]\nsite_key = "6Lsite"\nsecret_key = "6Lsecret"\n') + cfg_file.write_text('[captcha]\nsite_key = "6Lsite"\nsecret_key = "6Lsecret"\n', + encoding="utf-8") assert load_config(cfg_file).captcha.host_check is None @@ -231,10 +232,10 @@ def test_the_unattributed_flag_comes_from_the_config(tmp_path, monkeypatch): cfg_file = tmp_path / "hub.toml" cfg_file.write_text( '[captcha]\nsite_key = "k"\nsecret_key = "s"\n' - 'allowed_hosts = ["meshbay.org"]\nallow_unattributed_host = true\n') + 'allowed_hosts = ["meshbay.org"]\nallow_unattributed_host = true\n', encoding="utf-8") assert load_config(cfg_file).captcha.allow_unattributed_host is True - cfg_file.write_text('[captcha]\nsite_key = "k"\nsecret_key = "s"\n') + cfg_file.write_text('[captcha]\nsite_key = "k"\nsecret_key = "s"\n', encoding="utf-8") assert load_config(cfg_file).captcha.allow_unattributed_host is False, ( "the default has to stay off — it is the looser of the two") diff --git a/packages/meshbay-hub/tests/test_cast_subtitles.py b/packages/meshbay-hub/tests/test_cast_subtitles.py index fdf7fcc..bdc279b 100644 --- a/packages/meshbay-hub/tests/test_cast_subtitles.py +++ b/packages/meshbay-hub/tests/test_cast_subtitles.py @@ -66,7 +66,7 @@ def _run(tmp_path, body: str, *args: str): + "\n" + body, encoding="utf-8") proc = subprocess.run( ["node", str(script), *args], - capture_output=True, text=True, timeout=30) + capture_output=True, text=True, encoding="utf-8", timeout=30) assert proc.returncode == 0, proc.stderr return json.loads(proc.stdout) @@ -260,7 +260,7 @@ def served(tmp_path_factory): script.write_text(RELAY_SCRIPT, encoding="utf-8") proc = subprocess.run( ["node", str(script), str(RELAY)], - capture_output=True, text=True, timeout=60) + capture_output=True, text=True, encoding="utf-8", timeout=60) assert proc.returncode == 0, proc.stderr return json.loads(proc.stdout.strip().splitlines()[-1]) @@ -363,7 +363,7 @@ def loaded(tmp_path_factory): script.write_text(CHROMECAST_SCRIPT, encoding="utf-8") proc = subprocess.run( ["node", str(script), str(CHROMECAST)], - capture_output=True, text=True, timeout=60) + capture_output=True, text=True, encoding="utf-8", timeout=60) if proc.returncode != 0: pytest.skip(f"cast-chromecast.js is not loadable here: {proc.stderr}") return json.loads(proc.stdout) diff --git a/packages/meshbay-hub/tests/test_challenge_signature_client.py b/packages/meshbay-hub/tests/test_challenge_signature_client.py index e3c33ac..b904698 100644 --- a/packages/meshbay-hub/tests/test_challenge_signature_client.py +++ b/packages/meshbay-hub/tests/test_challenge_signature_client.py @@ -93,11 +93,11 @@ def test_the_browser_holds_the_node_to_its_challenge(tmp_path): "garbage for a signature": (case(sig=b"\x00" * 64), "refused"), } harness = tmp_path / "harness.js" - harness.write_text(_HARNESS) + harness.write_text(_HARNESS, encoding="utf-8") payload = tmp_path / "cases.json" - payload.write_text(json.dumps([c for c, _ in cases.values()])) + payload.write_text(json.dumps([c for c, _ in cases.values()]), encoding="utf-8") proc = subprocess.run(["node", str(harness), str(STATIC), str(payload), transport_argv()], - capture_output=True, text=True, timeout=60) + capture_output=True, text=True, encoding="utf-8", timeout=60) assert proc.returncode == 0, proc.stderr got = dict(zip(cases, json.loads(proc.stdout))) assert got == {name: want for name, (_, want) in cases.items()} diff --git a/packages/meshbay-hub/tests/test_chat_scroll_bottom.py b/packages/meshbay-hub/tests/test_chat_scroll_bottom.py index 7b66c00..42b1055 100644 --- a/packages/meshbay-hub/tests/test_chat_scroll_bottom.py +++ b/packages/meshbay-hub/tests/test_chat_scroll_bottom.py @@ -42,7 +42,7 @@ pytestmark = pytest.mark.skipif(not CHAT.exists(), reason="SPA sources not prese def _chat_panel_source() -> str: - src = CHAT.read_text() + src = CHAT.read_text(encoding="utf-8") start = src.index("\nfunction ChatPanel(") end = src.find("\nfunction ", start + 1) return src[start:end if end != -1 else len(src)] diff --git a/packages/meshbay-hub/tests/test_client_version_gate.py b/packages/meshbay-hub/tests/test_client_version_gate.py index 4dc9b98..91cb99a 100644 --- a/packages/meshbay-hub/tests/test_client_version_gate.py +++ b/packages/meshbay-hub/tests/test_client_version_gate.py @@ -32,7 +32,7 @@ pytestmark = pytest.mark.skipif( def _lift(name: str) -> str: - src = MAIN.read_text() + src = MAIN.read_text(encoding="utf-8") cut = src[src.index(name):] return cut[:cut.index("\n}\n") + 2] @@ -69,8 +69,8 @@ out.compare = [ compareVersions('nonsense', '1.1.0'), ]; console.log(JSON.stringify(out)); -""") - proc = subprocess.run(["node", str(script)], capture_output=True, text=True) +""", encoding="utf-8") + proc = subprocess.run(["node", str(script)], capture_output=True, text=True, encoding="utf-8") assert proc.returncode == 0, proc.stderr return json.loads(proc.stdout) @@ -133,7 +133,7 @@ def test_a_first_run_with_no_hub_yet_is_not_stopped(tmp_path): def test_the_gate_runs_before_the_window_is_built(): """A window that opens and then cannot connect is the failure this replaces, so the order is the whole point.""" - src = MAIN.read_text() + src = MAIN.read_text(encoding="utf-8") ready = src[src.index("app.whenReady().then("):] ready = ready[:ready.index("createWindow();")] assert "await refuseIfTooOld()" in ready, ( diff --git a/packages/meshbay-hub/tests/test_downloads.py b/packages/meshbay-hub/tests/test_downloads.py index 395053b..c8c68a9 100644 --- a/packages/meshbay-hub/tests/test_downloads.py +++ b/packages/meshbay-hub/tests/test_downloads.py @@ -26,7 +26,7 @@ pytestmark = pytest.mark.skipif( def _run(body, tmp_path): module = tmp_path / "downloads.mjs" - module.write_text(DOWNLOADS.read_text()) + module.write_text(DOWNLOADS.read_text(encoding="utf-8"), encoding="utf-8") script = tmp_path / "case.mjs" script.write_text( # A localStorage good enough for a preference, so the module can be @@ -36,12 +36,12 @@ def _run(body, tmp_path): " getItem: k => (store.has(k) ? store.get(k) : null),\n" " setItem: (k, v) => store.set(k, String(v)),\n" "};\n" - f"const M = await import('{module.as_posix()}');\n" + f"const M = await import('{module.as_uri()}');\n" "const out = [];\n" "const say = (...a) => out.push(...a);\n" f"{body}\n" - "console.log(JSON.stringify(out));\n") - proc = subprocess.run(["node", str(script)], capture_output=True, text=True) + "console.log(JSON.stringify(out));\n", encoding="utf-8") + proc = subprocess.run(["node", str(script)], capture_output=True, text=True, encoding="utf-8") assert proc.returncode == 0, proc.stderr return json.loads(proc.stdout) @@ -104,7 +104,7 @@ def test_the_open_action_reads_the_file_back(tmp_path): manager either. It is only offered for a file written into a granted folder, since that is the one a page can read back. """ - src = DOWNLOADS.read_text() + src = DOWNLOADS.read_text(encoding="utf-8") target = src[src.index("export async function openTarget"):] assert "getFile()" in target and "window.open(" in target assert "revokeObjectURL" in target, "the blob URL must not be leaked" @@ -122,7 +122,7 @@ def test_the_worker_only_answers_its_own_urls(): service worker that answers more than it should is a cache bug waiting to happen. """ - src = SW.read_text() + src = SW.read_text(encoding="utf-8") assert "startsWith(PREFIX)" in src assert "self.location.origin" in src, "cross-origin requests must fall through" # The API, not the word: the file explains in prose that it caches nothing. @@ -131,7 +131,7 @@ def test_the_worker_only_answers_its_own_urls(): def test_the_download_is_announced_as_an_attachment(): - src = SW.read_text() + src = SW.read_text(encoding="utf-8") assert "Content-Disposition" in src and "attachment" in src assert "filename*=UTF-8''" in src, "a name with accents would be mangled" assert "Content-Length" in src @@ -142,7 +142,7 @@ def test_a_length_is_only_promised_when_it_is_known(tmp_path): An archive is assembled as it goes and is larger than the files in it. Announcing the sum of their sizes would truncate the download at that mark. """ - src = SW.read_text() + src = SW.read_text(encoding="utf-8") assert "if (entry.size > 0)" in src # The zip-directory download started in files-app.js (group-page refactor) @@ -153,7 +153,7 @@ def test_a_length_is_only_promised_when_it_is_known(tmp_path): # became a bare `target = ...` inside a try when _openDownloadTarget gained # the ability to refuse an oversized download (test_memory_ceiling.py). # What this test is about -- the `0` -- did not move. - app = (STATIC / "file-utils.js").read_text() + app = (STATIC / "file-utils.js").read_text(encoding="utf-8") # Anchored on the argument list, not on the function name: the call became # `_openTargetInTurn(suggested, …)` when target openings were serialised. # The `0` this test is about did not move. @@ -169,7 +169,7 @@ def test_backpressure_is_real(tmp_path): is transferred gives `writer.write()` something to wait on; posting chunks to a port would queue them in memory and look identical from here. """ - src = DOWNLOADS.read_text() + src = DOWNLOADS.read_text(encoding="utf-8") fn = src[src.index("export async function openStreamedDownload"):] assert "new TransformStream()" in fn # The transfer list may carry more than the stream — a reply port rides @@ -210,13 +210,13 @@ def test_the_streamed_path_gives_up_rather_than_blocking_for_ever(): So the worker confirms that it actually answered, and this path reports failure instead of returning a sink nobody drains. """ - src = DOWNLOADS.read_text() + src = DOWNLOADS.read_text(encoding="utf-8") fn = src[src.index("export async function openStreamedDownload"):] assert "mbdl-serving" in fn, "the worker has to confirm it served the request" assert "Promise.race" in fn, "the confirmation needs a deadline" assert "writable.abort" in fn, "give up cleanly so the caller can fall back" - sw = (DOWNLOADS.parent / "sw.js").read_text() + sw = (DOWNLOADS.parent / "sw.js").read_text(encoding="utf-8") assert "mbdl-serving" in sw, "and the worker has to send that confirmation" @@ -227,7 +227,7 @@ def test_an_uncontrolled_page_is_not_treated_as_ready(): # became a parameter, and `serviceWorker()` no longer contains the words. # The behaviour itself is executed in test_streamed_download_reliability.py; # this stays as the cheap guard on the module's shape. - src = DOWNLOADS.read_text() + src = DOWNLOADS.read_text(encoding="utf-8") section = src[src.index("// ── Streaming to disk"):] assert "navigator.serviceWorker.controller" in section assert "controllerchange" in section, ( @@ -251,7 +251,7 @@ def test_an_apostrophe_in_a_name_does_not_lose_the_name(tmp_path): The real function is lifted out of sw.js and run — a second copy here would have the same blind spot as the first. """ - src = SW.read_text() + src = SW.read_text(encoding="utf-8") fn = src[src.index("function contentDisposition"):] fn = fn[:fn.index("\n}") + 2] @@ -263,8 +263,8 @@ for (const name of ["S03E02. Queen's Landing.mp4", 'Caf\\u00e9 (2019).mkv', out[name] = contentDisposition(name); } console.log(JSON.stringify(out)); -""") - proc = subprocess.run(["node", str(script)], capture_output=True, text=True) +""", encoding="utf-8") + proc = subprocess.run(["node", str(script)], capture_output=True, text=True, encoding="utf-8") assert proc.returncode == 0, proc.stderr out = json.loads(proc.stdout) @@ -304,7 +304,7 @@ def test_a_sink_that_stops_consuming_fails_instead_of_hanging(tmp_path): wrong. Bounding it does not fix whatever stopped the sink — it turns an unexplainable freeze into a failed transfer that names itself. """ - src = (STATIC / "file-utils.js").read_text() + src = (STATIC / "file-utils.js").read_text(encoding="utf-8") fn = src[src.index("async function _writeOrStall"):] fn = fn[:fn.index("\n}\n") + 2] @@ -328,8 +328,8 @@ const live = { write: async () => {} }; await _writeOrStall(live, new Uint8Array(4), 0); out.liveOk = true; console.log(JSON.stringify(out)); -""") - proc = subprocess.run(["node", str(script)], capture_output=True, text=True) +""", encoding="utf-8") + proc = subprocess.run(["node", str(script)], capture_output=True, text=True, encoding="utf-8") assert proc.returncode == 0, proc.stderr out = json.loads(proc.stdout) assert out["threw"], "a dead sink hung for ever instead of failing" @@ -358,8 +358,8 @@ def test_the_worker_is_kept_alive_while_it_streams(): clock. What it protects is that the ping exists at all, is cleared on both exits, and is answered by the worker. """ - dl = DOWNLOADS.read_text() - sw = SW.read_text() + dl = DOWNLOADS.read_text(encoding="utf-8") + sw = SW.read_text(encoding="utf-8") assert "SW_KEEPALIVE_MS" in dl and "mbdl-ping" in dl, ( "nothing keeps the worker alive; downloads longer than ~30 s will " @@ -389,7 +389,7 @@ def _turn_harness(tmp_path, name, body, *, picker=True, budget_ms=90000): the budget is supplied here, so a case about the budget need not wait a minute and a half for it. """ - src = (STATIC / "file-utils.js").read_text() + src = (STATIC / "file-utils.js").read_text(encoding="utf-8") def lift(decl): cut = src[src.index(decl):] @@ -422,8 +422,8 @@ const TARGET_QUEUE_BUDGET_MS = {budget_ms}; """ + lift("function _openTargetInTurn") + lift("function _waitBriefly") + f""" {body} console.log(JSON.stringify(out)); -""") - proc = subprocess.run(["node", str(script)], capture_output=True, text=True) +""", encoding="utf-8") + proc = subprocess.run(["node", str(script)], capture_output=True, text=True, encoding="utf-8") assert proc.returncode == 0, proc.stderr return json.loads(proc.stdout) @@ -518,7 +518,7 @@ def test_a_pause_falls_between_chunks_and_resumes_at_one(tmp_path): rule this repo follows for the video player: model the environment, never the code under test. """ - src = (STATIC / "file-utils.js").read_text() + src = (STATIC / "file-utils.js").read_text(encoding="utf-8") fn = src[src.index("async function pipelinedDownload"):] fn = fn[:fn.index("\n}\n") + 2] @@ -558,8 +558,8 @@ await pipelinedDownload({}, 'k', 'file', 10, () => {}, {}, signal, '', out.resumeFrom); out.writtenAfterResume = written.slice(); console.log(JSON.stringify(out)); -""") - proc = subprocess.run(["node", str(script)], capture_output=True, text=True) +""", encoding="utf-8") + proc = subprocess.run(["node", str(script)], capture_output=True, text=True, encoding="utf-8") assert proc.returncode == 0, proc.stderr out = json.loads(proc.stdout) diff --git a/packages/meshbay-hub/tests/test_files_drop_upload.py b/packages/meshbay-hub/tests/test_files_drop_upload.py index fc15c47..aa39f30 100644 --- a/packages/meshbay-hub/tests/test_files_drop_upload.py +++ b/packages/meshbay-hub/tests/test_files_drop_upload.py @@ -41,7 +41,7 @@ def source(): def _run(tmp_path, source, expr): script = tmp_path / "case.js" script.write_text(f"{source}\nconsole.log(JSON.stringify({expr}));", encoding="utf-8") - out = subprocess.run(["node", str(script)], capture_output=True, text=True, check=True) + out = subprocess.run(["node", str(script)], capture_output=True, encoding="utf-8", check=True) return json.loads(out.stdout) diff --git a/packages/meshbay-hub/tests/test_files_sorting.py b/packages/meshbay-hub/tests/test_files_sorting.py index 730d1e8..f3aaf34 100644 --- a/packages/meshbay-hub/tests/test_files_sorting.py +++ b/packages/meshbay-hub/tests/test_files_sorting.py @@ -38,7 +38,7 @@ def _names(tmp_path, source, rows, key, asc): f"{source}\nconsole.log(JSON.stringify(" f"sortRows({json.dumps(rows)}, {json.dumps(key)}, {json.dumps(asc)}).map((r) => r.name)));", encoding="utf-8") - out = subprocess.run(["node", str(script)], capture_output=True, text=True, check=True) + out = subprocess.run(["node", str(script)], capture_output=True, encoding="utf-8", check=True) return json.loads(out.stdout) diff --git a/packages/meshbay-hub/tests/test_hook_ordering.py b/packages/meshbay-hub/tests/test_hook_ordering.py index d5ffcfe..0172127 100644 --- a/packages/meshbay-hub/tests/test_hook_ordering.py +++ b/packages/meshbay-hub/tests/test_hook_ordering.py @@ -67,7 +67,7 @@ DEPS = re.compile(r"^ \}, \[([^\]]*)\]\);", re.M) @pytest.fixture(scope="module") def app(): - return APP.read_text() + return APP.read_text(encoding="utf-8") def _components(src: str): @@ -84,7 +84,7 @@ def _all_components(): path = STATIC / name if not path.exists(): continue - for cname, body in _components(path.read_text()): + for cname, body in _components(path.read_text(encoding="utf-8")): yield f"{name}:{cname}", body @@ -147,7 +147,7 @@ def test_the_mse_harness_reads_functions_in_source_order(): running it, which is the one class of defect it would otherwise be well placed to catch. """ - harness = (Path(__file__).parent / "harness" / "mse_harness.mjs").read_text() + harness = (Path(__file__).parent / "harness" / "mse_harness.mjs").read_text(encoding="utf-8") assert "sort" in harness and "indexOf" in harness, ( "the harness still extracts the player functions in a hardcoded order, " "so it cannot see one declared before its own dependency") diff --git a/packages/meshbay-hub/tests/test_index_seal_client.py b/packages/meshbay-hub/tests/test_index_seal_client.py index 20f89d1..f12d791 100644 --- a/packages/meshbay-hub/tests/test_index_seal_client.py +++ b/packages/meshbay-hub/tests/test_index_seal_client.py @@ -69,10 +69,10 @@ def _run(frames: list[str], gek: bytes = GEK) -> dict: with tempfile.TemporaryDirectory() as tmp: vectors = Path(tmp) / "vectors.json" vectors.write_text(json.dumps( - {"gek": gek.hex(), "group_id": GROUP, "frames": frames})) + {"gek": gek.hex(), "group_id": GROUP, "frames": frames}), encoding="utf-8") proc = subprocess.run( ["node", str(PROBE), str(STATIC), str(vectors), transport_argv()], - capture_output=True, text=True, timeout=120) + capture_output=True, text=True, encoding="utf-8", timeout=120) if proc.returncode != 0: pytest.fail(f"probe failed:\n{proc.stderr}") return json.loads(proc.stdout) diff --git a/packages/meshbay-hub/tests/test_indexing_dock.py b/packages/meshbay-hub/tests/test_indexing_dock.py index d69564d..93803a0 100644 --- a/packages/meshbay-hub/tests/test_indexing_dock.py +++ b/packages/meshbay-hub/tests/test_indexing_dock.py @@ -33,11 +33,11 @@ needs_node = pytest.mark.skipif(shutil.which("node") is None, reason="node is no def _run(tmp_path, body: str): - (tmp_path / "package.json").write_text('{"type":"module"}') - (tmp_path / "model.js").write_text(MODEL.read_text(encoding="utf-8")) + (tmp_path / "package.json").write_text('{"type":"module"}', encoding="utf-8") + (tmp_path / "model.js").write_text(MODEL.read_text(encoding="utf-8"), encoding="utf-8") script = tmp_path / "case.js" - script.write_text("import * as m from './model.js';\n" + body) - proc = subprocess.run(["node", str(script)], capture_output=True, text=True, + script.write_text("import * as m from './model.js';\n" + body, encoding="utf-8") + proc = subprocess.run(["node", str(script)], capture_output=True, text=True, encoding="utf-8", cwd=str(tmp_path)) assert proc.returncode == 0, proc.stderr return json.loads(proc.stdout) diff --git a/packages/meshbay-hub/tests/test_invite_link_client.py b/packages/meshbay-hub/tests/test_invite_link_client.py index 313e224..aa8c194 100644 --- a/packages/meshbay-hub/tests/test_invite_link_client.py +++ b/packages/meshbay-hub/tests/test_invite_link_client.py @@ -54,8 +54,8 @@ def _module_body() -> str: def _run(tmp_path, script: str): harness = tmp_path / "h.js" - harness.write_text(script) - out = subprocess.run(["node", str(harness)], capture_output=True, text=True, timeout=60) + harness.write_text(script, encoding="utf-8") + out = subprocess.run(["node", str(harness)], capture_output=True, encoding="utf-8", timeout=60) assert out.returncode == 0, out.stderr return json.loads(out.stdout) diff --git a/packages/meshbay-hub/tests/test_layout_measured.py b/packages/meshbay-hub/tests/test_layout_measured.py index 9bf1bde..c5d6280 100644 --- a/packages/meshbay-hub/tests/test_layout_measured.py +++ b/packages/meshbay-hub/tests/test_layout_measured.py @@ -78,7 +78,7 @@ def measured(tmp_path_factory): """One browser for every width, because launching one apiece cost the suite three minutes.""" fragment = tmp_path_factory.mktemp("layout") / "fragment.html" - fragment.write_text(NAV) + fragment.write_text(NAV, encoding="utf-8") proc = subprocess.run( ["python3", str(HARNESS), ",".join(str(w) for w in WIDTHS), str(fragment), *SELECTORS], @@ -211,7 +211,7 @@ GROUPED_SELECTORS = [".transfer-panel", ".transfer-head", ".transfer-head-summar @pytest.fixture(scope="module") def grouped(tmp_path_factory): fragment = tmp_path_factory.mktemp("grouped") / "fragment.html" - fragment.write_text(GROUPED) + fragment.write_text(GROUPED, encoding="utf-8") proc = subprocess.run( ["python3", str(HARNESS), ",".join(str(w) for w in WIDTHS), str(fragment), *GROUPED_SELECTORS], diff --git a/packages/meshbay-hub/tests/test_layout_responsive.py b/packages/meshbay-hub/tests/test_layout_responsive.py index 4ee07d0..bb73557 100644 --- a/packages/meshbay-hub/tests/test_layout_responsive.py +++ b/packages/meshbay-hub/tests/test_layout_responsive.py @@ -38,7 +38,7 @@ pytestmark = pytest.mark.skipif( @pytest.fixture(scope="module") def css(): - return CSS.read_text() + return CSS.read_text(encoding="utf-8") def _rule(css: str, selector: str) -> str: @@ -119,7 +119,7 @@ APP = STATIC / "chat-app.js" @pytest.fixture(scope="module") def app(): - return APP.read_text() + return APP.read_text(encoding="utf-8") def test_the_chat_panel_is_measured_not_guessed(app): diff --git a/packages/meshbay-hub/tests/test_locales.py b/packages/meshbay-hub/tests/test_locales.py index 602d161..05e1115 100644 --- a/packages/meshbay-hub/tests/test_locales.py +++ b/packages/meshbay-hub/tests/test_locales.py @@ -35,19 +35,20 @@ EXPECTED = ["en", "fr", "es", "pt-BR", "zh-CN", "ja", "de", "it", "nl", "pl"] def _sandbox(tmp_path): """A directory node will treat as ESM, holding the real sources.""" - (tmp_path / "package.json").write_text('{"type":"module"}') + (tmp_path / "package.json").write_text('{"type":"module"}', encoding="utf-8") (tmp_path / "locales").mkdir(exist_ok=True) for src in LOCALES.glob("*.js"): - (tmp_path / "locales" / src.name).write_text(src.read_text()) - (tmp_path / "i18n.js").write_text(I18N.read_text()) + (tmp_path / "locales" / src.name).write_text(src.read_text(encoding="utf-8"), + encoding="utf-8") + (tmp_path / "i18n.js").write_text(I18N.read_text(encoding="utf-8"), encoding="utf-8") return tmp_path def _node(tmp_path, body): script = tmp_path / "case.js" - script.write_text(body) + script.write_text(body, encoding="utf-8") proc = subprocess.run( - ["node", str(script)], capture_output=True, text=True, cwd=str(tmp_path)) + ["node", str(script)], capture_output=True, text=True, encoding="utf-8", cwd=str(tmp_path)) assert proc.returncode == 0, proc.stderr return json.loads(proc.stdout) diff --git a/packages/meshbay-hub/tests/test_media_pager.py b/packages/meshbay-hub/tests/test_media_pager.py index a8a0569..835ed6f 100644 --- a/packages/meshbay-hub/tests/test_media_pager.py +++ b/packages/meshbay-hub/tests/test_media_pager.py @@ -36,8 +36,8 @@ def source(): def _run(tmp_path, source, expr): script = tmp_path / "case.js" - script.write_text(f"{source}\nconsole.log(JSON.stringify({expr}));") - out = subprocess.run(["node", str(script)], capture_output=True, text=True, check=True) + script.write_text(f"{source}\nconsole.log(JSON.stringify({expr}));", encoding="utf-8") + out = subprocess.run(["node", str(script)], capture_output=True, encoding="utf-8", check=True) return json.loads(out.stdout) diff --git a/packages/meshbay-hub/tests/test_member_removal.py b/packages/meshbay-hub/tests/test_member_removal.py index 7af73a0..5073873 100644 --- a/packages/meshbay-hub/tests/test_member_removal.py +++ b/packages/meshbay-hub/tests/test_member_removal.py @@ -27,7 +27,7 @@ pytestmark = pytest.mark.skipif( def _remove_member_body() -> str: - source = SETTINGS.read_text() + source = SETTINGS.read_text(encoding="utf-8") start = source.find("const removeMember = useCallback(") assert start != -1, "removeMember is gone from group-settings.js" end = source.find("\n }, [", start) diff --git a/packages/meshbay-hub/tests/test_memory_ceiling.py b/packages/meshbay-hub/tests/test_memory_ceiling.py index 5984292..9ea6ad3 100644 --- a/packages/meshbay-hub/tests/test_memory_ceiling.py +++ b/packages/meshbay-hub/tests/test_memory_ceiling.py @@ -51,7 +51,7 @@ def _lift(name, source): @pytest.fixture(scope="module") def target_fn(): """The ceiling, its error and the real function — read, never re-typed.""" - src = FILE_UTILS.read_text() + src = FILE_UTILS.read_text(encoding="utf-8") ceiling = re.search(r"^const MEMORY_CEILING = .*?;$", src, re.M) assert ceiling, "MEMORY_CEILING is gone from file-utils.js" # The test's own CEILING constant must agree with the source's, or every @@ -123,8 +123,8 @@ try {{ outcome = {{ kind: 'refused', name: err.name, message: err.message }}; }} console.log(JSON.stringify(outcome)); -""") - proc = subprocess.run(["node", str(script)], capture_output=True, text=True) +""", encoding="utf-8") + proc = subprocess.run(["node", str(script)], capture_output=True, text=True, encoding="utf-8") assert proc.returncode == 0, proc.stderr return json.loads(proc.stdout) @@ -259,7 +259,7 @@ def test_no_unguarded_memory_floor(target_fn): def test_the_guard_is_what_the_preview_uses_too(target_fn): """`FilePreview` decrypts a whole entry with no writable at all, so it needs the same ceiling — and must import it rather than keep a second number.""" - files_app = (STATIC / "files-app.js").read_text() + files_app = (STATIC / "files-app.js").read_text(encoding="utf-8") assert "MEMORY_CEILING" in files_app assert re.search(r"entry\.size\s*>\s*MEMORY_CEILING", files_app), ( "the preview modal must refuse an oversized entry before fetching it") diff --git a/packages/meshbay-hub/tests/test_music_queue.py b/packages/meshbay-hub/tests/test_music_queue.py index 5bf9e97..8f3f9bd 100644 --- a/packages/meshbay-hub/tests/test_music_queue.py +++ b/packages/meshbay-hub/tests/test_music_queue.py @@ -123,7 +123,7 @@ def test_an_unreachable_group_is_skipped_whole_rather_than_one_track_at_a_time(s def test_the_music_wrapper_names_the_op_it_forwards(name): """A wrapper that takes two arguments forwards two, and the third is lost without a word. Both of these did exactly that.""" - src = (STATIC / name).read_text() + src = (STATIC / name).read_text(encoding="utf-8") marker = ("const onPlayQueue = useCallback((tracks, startIndex, op)" if name == "group-page.js" else "const handleMusicPlay = useCallback((tracks, startIndex, op)") diff --git a/packages/meshbay-hub/tests/test_node_page_width_measured.py b/packages/meshbay-hub/tests/test_node_page_width_measured.py index bd6a231..ae49641 100644 --- a/packages/meshbay-hub/tests/test_node_page_width_measured.py +++ b/packages/meshbay-hub/tests/test_node_page_width_measured.py @@ -122,7 +122,7 @@ SELECTORS = ["#node.node-page", "#settings", "#node .node-table-scroll", def measured(tmp_path_factory): """One browser for every width — launching one apiece cost three minutes.""" fragment = tmp_path_factory.mktemp("nodewidth") / "fragment.html" - fragment.write_text(FRAGMENT) + fragment.write_text(FRAGMENT, encoding="utf-8") proc = subprocess.run( ["python3", str(HARNESS), ",".join(str(w) for w in WIDTHS), str(fragment), *SELECTORS], diff --git a/packages/meshbay-hub/tests/test_notification_dismissal.py b/packages/meshbay-hub/tests/test_notification_dismissal.py index d498b4d..0198dee 100644 --- a/packages/meshbay-hub/tests/test_notification_dismissal.py +++ b/packages/meshbay-hub/tests/test_notification_dismissal.py @@ -149,7 +149,7 @@ def test_the_spa_asks_for_unread_only(): exercising it, and here it is the only thing that catches the defect that actually happened. """ - src = APP.read_text() + src = APP.read_text(encoding="utf-8") fetches = re.findall(r"hubFetch\('(/v1/notifications\?[^']*)'", src) assert fetches, "the notification list fetch is no longer where this reads it" for url in fetches: @@ -167,7 +167,7 @@ def test_the_feed_does_not_style_a_state_it_can_no_longer_show(): was dead code that described behaviour the application had abandoned — and reading it is what made the two halves' disagreement visible. """ - src = APP.read_text() + src = APP.read_text(encoding="utf-8") assert "n.read ?" not in src, ( "the feed branches on `read` again; either it is dead code or the " "dismissal contract has changed and this file should say how") diff --git a/packages/meshbay-hub/tests/test_offer_retry.py b/packages/meshbay-hub/tests/test_offer_retry.py index 6389f8e..1239ae0 100644 --- a/packages/meshbay-hub/tests/test_offer_retry.py +++ b/packages/meshbay-hub/tests/test_offer_retry.py @@ -33,7 +33,7 @@ pytestmark = pytest.mark.skipif( def _post(**cfg) -> dict: proc = subprocess.run( ["node", str(HARNESS), transport_argv(), json.dumps(cfg)], - capture_output=True, text=True) + capture_output=True, text=True, encoding="utf-8") assert proc.returncode == 0, proc.stderr return json.loads(proc.stdout) diff --git a/packages/meshbay-hub/tests/test_playlist_crypto.py b/packages/meshbay-hub/tests/test_playlist_crypto.py index 4ff3080..4dcfcb6 100644 --- a/packages/meshbay-hub/tests/test_playlist_crypto.py +++ b/packages/meshbay-hub/tests/test_playlist_crypto.py @@ -69,11 +69,11 @@ const bigBody = (n) => ({ def _run(tmp_path, body): - src = SRC.read_text().replace("export {", "const _unused_export = {") + src = SRC.read_text(encoding="utf-8").replace("export {", "const _unused_export = {") script = tmp_path / "case.mjs" - script.write_text(f"{src}\n{PRELUDE}\n{body}\n") + script.write_text(f"{src}\n{PRELUDE}\n{body}\n", encoding="utf-8") out = subprocess.run(["node", str(script)], - capture_output=True, text=True, timeout=60) + capture_output=True, text=True, encoding="utf-8", timeout=60) assert out.returncode == 0, out.stderr return json.loads(out.stdout) diff --git a/packages/meshbay-hub/tests/test_playlist_key.py b/packages/meshbay-hub/tests/test_playlist_key.py index dbed8ae..261cc32 100644 --- a/packages/meshbay-hub/tests/test_playlist_key.py +++ b/packages/meshbay-hub/tests/test_playlist_key.py @@ -58,11 +58,11 @@ globalThis.argon2 = { def _run(tmp_path, body): - src = KEYDERIVE.read_text() + src = KEYDERIVE.read_text(encoding="utf-8") script = tmp_path / "case.mjs" - script.write_text(f"{PRELUDE}\n{src}\n{body}\n") + script.write_text(f"{PRELUDE}\n{src}\n{body}\n", encoding="utf-8") out = subprocess.run(["node", str(script)], - capture_output=True, text=True, timeout=60) + capture_output=True, text=True, encoding="utf-8", timeout=60) assert out.returncode == 0, out.stderr return json.loads(out.stdout) diff --git a/packages/meshbay-hub/tests/test_playlist_merge.py b/packages/meshbay-hub/tests/test_playlist_merge.py index 2c7b612..dd089e8 100644 --- a/packages/meshbay-hub/tests/test_playlist_merge.py +++ b/packages/meshbay-hub/tests/test_playlist_merge.py @@ -39,7 +39,7 @@ EXPORT = re.compile(r"^export \{[^}]*\};?\s*$", re.M | re.S) @pytest.fixture(scope="module") def module_source(): - text = SRC.read_text() + text = SRC.read_text(encoding="utf-8") assert not IMPORT.search(text), ( "playlist-merge.js has gained an import. It is executed standalone " "here, and the merge is untested from the moment it cannot be — keep " @@ -53,9 +53,9 @@ def module_source(): def _run(tmp_path, module_source, body): script = tmp_path / "case.js" - script.write_text(f"{module_source}\n{body}\n") + script.write_text(f"{module_source}\n{body}\n", encoding="utf-8") out = subprocess.run( - ["node", str(script)], capture_output=True, text=True, timeout=30) + ["node", str(script)], capture_output=True, text=True, encoding="utf-8", timeout=30) assert out.returncode == 0, out.stderr return json.loads(out.stdout) diff --git a/packages/meshbay-hub/tests/test_queue_ops.py b/packages/meshbay-hub/tests/test_queue_ops.py index 64b5ca1..e3853f2 100644 --- a/packages/meshbay-hub/tests/test_queue_ops.py +++ b/packages/meshbay-hub/tests/test_queue_ops.py @@ -38,7 +38,7 @@ EXPORT = re.compile(r"^export \{[^}]*\};?\s*$", re.M) @pytest.fixture(scope="module") def module_source(): - text = SRC.read_text() + text = SRC.read_text(encoding="utf-8") assert not IMPORT.search(text), ( "queue-ops.js has gained an import. It is executed standalone here, " "and the queue is untested from the moment it cannot be — keep the " @@ -52,9 +52,9 @@ def module_source(): def _run(tmp_path, module_source, body): script = tmp_path / "case.js" - script.write_text(f"{module_source}\n{body}\n") + script.write_text(f"{module_source}\n{body}\n", encoding="utf-8") out = subprocess.run( - ["node", str(script)], capture_output=True, text=True, timeout=30) + ["node", str(script)], capture_output=True, text=True, encoding="utf-8", timeout=30) assert out.returncode == 0, out.stderr return json.loads(out.stdout) diff --git a/packages/meshbay-hub/tests/test_reconnect_refresh.py b/packages/meshbay-hub/tests/test_reconnect_refresh.py index ff6d7fb..cd702d3 100644 --- a/packages/meshbay-hub/tests/test_reconnect_refresh.py +++ b/packages/meshbay-hub/tests/test_reconnect_refresh.py @@ -43,12 +43,12 @@ def transport(): @pytest.fixture(scope="module") def group_page(): - return GROUP_PAGE.read_text() + return GROUP_PAGE.read_text(encoding="utf-8") @pytest.fixture(scope="module") def video_player(): - return VIDEO_PLAYER.read_text() + return VIDEO_PLAYER.read_text(encoding="utf-8") def _reconnect_loop(transport: str) -> str: @@ -93,7 +93,7 @@ def test_one_listener_throwing_does_not_rob_the_next(transport): def test_nothing_assigns_the_old_setter(): """`grep onReconnected =` is what this is, spelled so it cannot rot.""" offenders = [p.name for p in STATIC.glob("*.js") - if re.search(r"\.onReconnected\s*=", p.read_text())] + if re.search(r"\.onReconnected\s*=", p.read_text(encoding="utf-8"))] assert offenders == [], ( f"{offenders} still assign a slot that no longer exists") @@ -159,10 +159,10 @@ def test_every_harness_that_renders_the_group_page_stubs_the_subscription(): """ harness = Path(__file__).parent / "harness" stubs = [p for p in harness.glob("*.py") - if "window.MeshBayTransport" in p.read_text() - and "GroupPage" in p.read_text()] + if "window.MeshBayTransport" in p.read_text(encoding="utf-8") + and "GroupPage" in p.read_text(encoding="utf-8")] assert stubs, "no harness stubs the transport any more — has this moved?" missing = [p.name for p in stubs - if "addReconnectListener" not in p.read_text()] + if "addReconnectListener" not in p.read_text(encoding="utf-8")] assert missing == [], ( f"{missing} render GroupPage against a node that cannot be subscribed to") diff --git a/packages/meshbay-hub/tests/test_recovery_key.py b/packages/meshbay-hub/tests/test_recovery_key.py index 54415f6..e43e6de 100644 --- a/packages/meshbay-hub/tests/test_recovery_key.py +++ b/packages/meshbay-hub/tests/test_recovery_key.py @@ -102,10 +102,10 @@ const fp = async (key) => hex(await webcrypto.subtle.encrypt( def result(tmp_path_factory): d = tmp_path_factory.mktemp("recovery") harness = d / "harness.cjs" - harness.write_text(_HARNESS) + harness.write_text(_HARNESS, encoding="utf-8") proc = subprocess.run( ["node", str(harness), str(KEYDERIVE)], - capture_output=True, text=True, timeout=120, + capture_output=True, text=True, encoding="utf-8", timeout=120, ) if proc.returncode != 0: pytest.fail(f"node harness failed:\n{proc.stderr[-2000:]}") diff --git a/packages/meshbay-hub/tests/test_resume_position.py b/packages/meshbay-hub/tests/test_resume_position.py index 07ac23a..67eb786 100644 --- a/packages/meshbay-hub/tests/test_resume_position.py +++ b/packages/meshbay-hub/tests/test_resume_position.py @@ -85,7 +85,7 @@ def _run(body: str, tmp_path: Path): f"{body}\n" "console.log(JSON.stringify(out));\n", encoding="utf-8") - proc = subprocess.run(["node", str(script)], capture_output=True, text=True) + proc = subprocess.run(["node", str(script)], capture_output=True, text=True, encoding="utf-8") assert proc.returncode == 0, proc.stderr return json.loads(proc.stdout) diff --git a/packages/meshbay-hub/tests/test_rewrap_fanout.py b/packages/meshbay-hub/tests/test_rewrap_fanout.py index 7c0f272..b278d0c 100644 --- a/packages/meshbay-hub/tests/test_rewrap_fanout.py +++ b/packages/meshbay-hub/tests/test_rewrap_fanout.py @@ -154,10 +154,10 @@ const names = (a) => a.map((x) => x.name).sort(); def result(tmp_path_factory): d = tmp_path_factory.mktemp("rewrap") harness = d / "harness.cjs" - harness.write_text(_HARNESS) + harness.write_text(_HARNESS, encoding="utf-8") proc = subprocess.run( ["node", str(harness), transport_argv()], - capture_output=True, text=True, timeout=120, + capture_output=True, text=True, encoding="utf-8", timeout=120, ) if proc.returncode != 0: pytest.fail(f"node harness failed:\n{proc.stderr[-2000:]}") diff --git a/packages/meshbay-hub/tests/test_search_connect_deadline.py b/packages/meshbay-hub/tests/test_search_connect_deadline.py index 2d3db06..953f027 100644 --- a/packages/meshbay-hub/tests/test_search_connect_deadline.py +++ b/packages/meshbay-hub/tests/test_search_connect_deadline.py @@ -49,7 +49,7 @@ CAP_MS = 30000 def _attempt(**cfg) -> dict: proc = subprocess.run( ["node", str(HARNESS), search_argv(), json.dumps(cfg)], - capture_output=True, text=True) + capture_output=True, text=True, encoding="utf-8") assert proc.returncode == 0, proc.stderr return json.loads(proc.stdout) diff --git a/packages/meshbay-hub/tests/test_search_fanout.py b/packages/meshbay-hub/tests/test_search_fanout.py index fca879a..23a8b67 100644 --- a/packages/meshbay-hub/tests/test_search_fanout.py +++ b/packages/meshbay-hub/tests/test_search_fanout.py @@ -67,7 +67,7 @@ DEAD_MS = 10000 # a node that does not, to the connection deadline def _sweep(**cfg) -> dict: proc = subprocess.run( ["node", str(HARNESS), search_argv(), json.dumps(cfg)], - capture_output=True, text=True) + capture_output=True, text=True, encoding="utf-8") assert proc.returncode == 0, proc.stderr return json.loads(proc.stdout) diff --git a/packages/meshbay-hub/tests/test_search_files_unmerged.py b/packages/meshbay-hub/tests/test_search_files_unmerged.py index b84b25d..b6621e5 100644 --- a/packages/meshbay-hub/tests/test_search_files_unmerged.py +++ b/packages/meshbay-hub/tests/test_search_files_unmerged.py @@ -38,7 +38,7 @@ MERGE_CALL = "mergeUnitEntries" def _memo(name): """The body of `const = useMemo(() => { ... }, [...]);`.""" - src = SEARCH_PAGE.read_text() + src = SEARCH_PAGE.read_text(encoding="utf-8") m = re.search( r"^ const " + re.escape(name) + r" = useMemo\(\(\) => \{.*?^ \}, \[.*?\]\);", src, re.M | re.S) diff --git a/packages/meshbay-hub/tests/test_search_media_merge.py b/packages/meshbay-hub/tests/test_search_media_merge.py index 3464902..082de7e 100644 --- a/packages/meshbay-hub/tests/test_search_media_merge.py +++ b/packages/meshbay-hub/tests/test_search_media_merge.py @@ -50,7 +50,7 @@ EXPORT = re.compile(r"^export \{[^}]*\};?\s*$", re.M) def _block(path, header): """One top-level `function name(...) {` ... `}` read out of a module.""" - src = path.read_text() + src = path.read_text(encoding="utf-8") m = re.search(r"^" + re.escape(header) + r".*?^\}", src, re.M | re.S) assert m, ( f"{header} is no longer where this test reads it from in {path.name} — " @@ -60,7 +60,7 @@ def _block(path, header): def _const(name): m = re.search(r"^const " + re.escape(name) + r" = .*?;$", - SEARCH_PAGE.read_text(), re.M) + SEARCH_PAGE.read_text(encoding="utf-8"), re.M) assert m, f"{name} moved — the search-page units cannot be lifted" return m.group(0) @@ -70,7 +70,7 @@ def pipeline(): """Everything the three views need, in one script.""" return "\n".join([ "const t = (k) => k;", - EXPORT.sub("", MERGE.read_text()), + EXPORT.sub("", MERGE.read_text(encoding="utf-8")), _block(VIDEO_APP, "function underVideoRoot(entry, directories) {"), _block(VIDEO_APP, "function buildSeasons(episodes) {"), _block(VIDEO_APP, "function groupVideoEntries(entries, videoDirectories) {"), @@ -90,9 +90,9 @@ def pipeline(): def _node(tmp_path, pipeline, body): script = tmp_path / "case.js" - script.write_text(f"{pipeline}\n{body}\n") + script.write_text(f"{pipeline}\n{body}\n", encoding="utf-8") out = subprocess.run( - ["node", str(script)], capture_output=True, text=True, timeout=30) + ["node", str(script)], capture_output=True, text=True, encoding="utf-8", timeout=30) assert out.returncode == 0, out.stderr return json.loads(out.stdout) diff --git a/packages/meshbay-hub/tests/test_search_pool.py b/packages/meshbay-hub/tests/test_search_pool.py index cf9eeb8..89b46cd 100644 --- a/packages/meshbay-hub/tests/test_search_pool.py +++ b/packages/meshbay-hub/tests/test_search_pool.py @@ -36,7 +36,7 @@ pytestmark = pytest.mark.skipif( def _run(**cfg) -> dict: proc = subprocess.run( ["node", str(HARNESS), search_argv(), json.dumps(cfg)], - capture_output=True, text=True) + capture_output=True, text=True, encoding="utf-8") assert proc.returncode == 0, proc.stderr return json.loads(proc.stdout) diff --git a/packages/meshbay-hub/tests/test_search_source_merge.py b/packages/meshbay-hub/tests/test_search_source_merge.py index b471891..10d10fc 100644 --- a/packages/meshbay-hub/tests/test_search_source_merge.py +++ b/packages/meshbay-hub/tests/test_search_source_merge.py @@ -44,7 +44,7 @@ EXPORT = re.compile(r"^export \{[^}]*\};?\s*$", re.M) @pytest.fixture(scope="module") def module_source(): - text = SRC.read_text() + text = SRC.read_text(encoding="utf-8") assert not IMPORT.search(text), ( "source-merge.js has gained an import. It is executed standalone here, " "and the merge is untested from the moment it cannot be — keep the " @@ -58,9 +58,9 @@ def module_source(): def _run(tmp_path, module_source, body): script = tmp_path / "case.js" - script.write_text(f"{module_source}\n{body}\n") + script.write_text(f"{module_source}\n{body}\n", encoding="utf-8") out = subprocess.run( - ["node", str(script)], capture_output=True, text=True, timeout=30) + ["node", str(script)], capture_output=True, text=True, encoding="utf-8", timeout=30) assert out.returncode == 0, out.stderr return json.loads(out.stdout) diff --git a/packages/meshbay-hub/tests/test_season_panel_placement.py b/packages/meshbay-hub/tests/test_season_panel_placement.py index 8c04ea7..6e5f78b 100644 --- a/packages/meshbay-hub/tests/test_season_panel_placement.py +++ b/packages/meshbay-hub/tests/test_season_panel_placement.py @@ -43,7 +43,7 @@ BLOCK = re.compile( @pytest.fixture(scope="module") def source(): - m = BLOCK.search(APP.read_text()) + m = BLOCK.search(APP.read_text(encoding="utf-8")) assert m, ("placeSeasonPanel is no longer where this test reads it from — " "the season menu's placement is untested until this is fixed") return m.group(0) @@ -58,8 +58,8 @@ def _place(tmp_path, source, *, top, bottom, left=40, width=300, inner_height=74 const el = {{ getBoundingClientRect: () => ({{ top: {top}, bottom: {bottom}, left: {left}, width: {width} }}) }}; console.log(JSON.stringify(placeSeasonPanel(el))); - """) - proc = subprocess.run(["node", str(script)], capture_output=True, text=True) + """, encoding="utf-8") + proc = subprocess.run(["node", str(script)], capture_output=True, text=True, encoding="utf-8") assert proc.returncode == 0, proc.stderr return json.loads(proc.stdout) @@ -157,7 +157,7 @@ def test_no_element_means_no_position(tmp_path, source): globalThis.window = {{ innerHeight: 740 }}; {source} console.log(JSON.stringify(placeSeasonPanel(null))); - """) - proc = subprocess.run(["node", str(script)], capture_output=True, text=True) + """, encoding="utf-8") + proc = subprocess.run(["node", str(script)], capture_output=True, text=True, encoding="utf-8") assert proc.returncode == 0, proc.stderr assert json.loads(proc.stdout) is None diff --git a/packages/meshbay-hub/tests/test_session_renewal.py b/packages/meshbay-hub/tests/test_session_renewal.py index 7b8c108..ecf9ac5 100644 --- a/packages/meshbay-hub/tests/test_session_renewal.py +++ b/packages/meshbay-hub/tests/test_session_renewal.py @@ -50,7 +50,7 @@ pytestmark = pytest.mark.skipif( def _run(scenario: str, app: Path = APP) -> dict: proc = subprocess.run( ["node", str(HARNESS), str(app), json.dumps({"scenario": scenario})], - capture_output=True, text=True, timeout=60) + capture_output=True, text=True, encoding="utf-8", timeout=60) assert proc.returncode == 0, f"{proc.stdout}\n{proc.stderr}" return json.loads(proc.stdout.strip().splitlines()[-1]) @@ -59,14 +59,14 @@ def _run(scenario: str, app: Path = APP) -> dict: def broken(tmp_path_factory): """The client as it shipped: the rotated refresh token dropped.""" out = tmp_path_factory.mktemp("session") / "broken.js" - src = APP.read_text() + src = APP.read_text(encoding="utf-8") replaced = src.replace( " refreshToken: data.refresh_token || _auth.refreshToken,", " refreshToken: _auth.refreshToken,") assert replaced != src, ( "could not reconstruct the defect — the line it hinged on has moved, " "and the A/B below would be comparing the fix against itself") - out.write_text(replaced) + out.write_text(replaced, encoding="utf-8") return out @@ -186,13 +186,13 @@ def test_the_session_is_much_longer_than_the_token(): def test_renewal_happens_before_expiry_not_after(): """A margin, so the first click after a long film does not pay for a 401.""" - src = APP.read_text() + src = APP.read_text(encoding="utf-8") import re margin = int(re.search(r"const TOKEN_RENEW_MARGIN_S = (\d+)", src).group(1)) assert margin >= 300, ( f"{margin} s of margin against a one-hour token is thin: a backgrounded " "tab has its timers throttled and may not check for minutes") - assert "visibilitychange" in APP_JS.read_text(), ( + assert "visibilitychange" in APP_JS.read_text(encoding="utf-8"), ( "nothing re-checks when the tab comes back, which is exactly when the " "token is most likely to have aged out unnoticed") @@ -213,7 +213,7 @@ def test_renewing_does_not_tear_down_the_webrtc_connection(): Signing in or out must still re-run it, so the dependency is whether there is a token, not which one. """ - src = GROUP_PAGE.read_text() + src = GROUP_PAGE.read_text(encoding="utf-8") i = src.index("means tearing down the WebRTC connection") deps = src[i:src.index(");", i)] assert "Boolean(token)" in deps, ( @@ -228,7 +228,7 @@ def test_the_connection_signs_its_offer_with_a_live_token(): It signs the offer relayed through the hub, where an expired one is a 401 and no connection at all. """ - src = GROUP_PAGE.read_text() + src = GROUP_PAGE.read_text(encoding="utf-8") connect = src[src.index("const connect = async () => {"):] connect = connect[:connect.index("\n };")] assert "await ensureFreshToken()" in connect, ( diff --git a/packages/meshbay-hub/tests/test_site_basics.py b/packages/meshbay-hub/tests/test_site_basics.py index a3da39a..78cad83 100644 --- a/packages/meshbay-hub/tests/test_site_basics.py +++ b/packages/meshbay-hub/tests/test_site_basics.py @@ -47,7 +47,7 @@ def test_the_icons_are_served_from_the_root(client, path, magic): def test_meshbay_org_sends_them_to_the_hub(path): """The public site owns only the paths its matcher names; a file claimed there would be looked for in /srv/meshbay/site and 404.""" - m = re.search(r"^\s*@site path (.+)$", CADDYFILE.read_text(), re.M) + m = re.search(r"^\s*@site path (.+)$", CADDYFILE.read_text(encoding="utf-8"), re.M) assert m, "the @site matcher is gone" assert path not in m.group(1).split() diff --git a/packages/meshbay-hub/tests/test_spa_ordering.py b/packages/meshbay-hub/tests/test_spa_ordering.py index 087298f..fdedaf8 100644 --- a/packages/meshbay-hub/tests/test_spa_ordering.py +++ b/packages/meshbay-hub/tests/test_spa_ordering.py @@ -132,7 +132,7 @@ COMPONENT_FILES = { def _component(name: str) -> str: """The source of one top-level `function Name(...)`, up to the next one.""" - source = COMPONENT_FILES.get(name, APP).read_text() + source = COMPONENT_FILES.get(name, APP).read_text(encoding="utf-8") start = source.find(f"\nfunction {name}(") if start == -1: start = source.find(f"\nexport function {name}(") @@ -225,7 +225,7 @@ def test_the_uploader_keeps_several_chunks_in_flight(): def test_no_caller_waits_for_one_chunk_at_a_time(): - app = APP.read_text() + app = APP.read_text(encoding="utf-8") assert "uploadChunk(" not in app, ( "a per-chunk await is back in the SPA; use transport.uploadFile()") @@ -248,7 +248,7 @@ def test_leaving_a_group_hands_the_transport_over_rather_than_closing_it(): def test_signing_out_stops_them(): - app = APP.read_text() + app = APP.read_text(encoding="utf-8") logout = app[app.index(" logout: () => {"):] logout = logout[:logout.index("navigate('/login')")] assert "transfers.reset()" in logout, ( @@ -257,7 +257,7 @@ def test_signing_out_stops_them(): def test_the_files_panel_no_longer_carries_its_own_progress_bars(): """They moved next to the bell, where they stay visible across the app.""" - app = APP.read_text() + app = APP.read_text(encoding="utf-8") for gone in ("setUlState", "setDlState", "dl-bar"): assert gone not in app, f"{gone} survived the move to the transfer widget" @@ -270,7 +270,7 @@ def test_a_multi_file_download_waits_for_each_picker(): meant the first opened a dialog and the rest were rejected — two files selected, one file downloaded. """ - app = STATIC.joinpath("files-app.js").read_text() + app = STATIC.joinpath("files-app.js").read_text(encoding="utf-8") # Anchored on the loop rather than on the markup around it: the toolbar # moved from a dropdown to icon buttons and took the old wrapper with it, # while the property under test — one picker at a time — did not change. @@ -289,7 +289,7 @@ def test_links_in_chat_are_built_as_elements_not_markup(): never HTML, and only for http(s) — otherwise javascript: would be one message away from running here. """ - app = STATIC.joinpath("chat-app.js").read_text() + app = STATIC.joinpath("chat-app.js").read_text(encoding="utf-8") fn = app[app.index("function linkify("):] fn = fn[:fn.index("\nfunction ", 1)] assert "innerHTML" not in fn and "dangerouslySetInnerHTML" not in fn diff --git a/packages/meshbay-hub/tests/test_spa_syntax.py b/packages/meshbay-hub/tests/test_spa_syntax.py index 352f84b..aac4010 100644 --- a/packages/meshbay-hub/tests/test_spa_syntax.py +++ b/packages/meshbay-hub/tests/test_spa_syntax.py @@ -47,7 +47,7 @@ def test_every_module_parses(tmp_path): copy = tmp_path / (path.stem + ".mjs") copy.write_text(path.read_text(encoding="utf-8"), encoding="utf-8") proc = subprocess.run(["node", "--check", str(copy)], - capture_output=True, text=True) + capture_output=True, text=True, encoding="utf-8") if proc.returncode != 0: first = (proc.stderr or "").strip().splitlines() detail = next((ln for ln in first if "Error" in ln), first[:1] and first[0] or "") @@ -67,12 +67,12 @@ def test_the_check_would_notice_a_broken_file(tmp_path): as_js = tmp_path / "sample.js" as_js.write_text(bad, encoding="utf-8") lenient = subprocess.run(["node", "--check", str(as_js)], - capture_output=True, text=True) + capture_output=True, text=True, encoding="utf-8") as_mjs = tmp_path / "sample.mjs" as_mjs.write_text(bad, encoding="utf-8") strict = subprocess.run(["node", "--check", str(as_mjs)], - capture_output=True, text=True) + capture_output=True, text=True, encoding="utf-8") assert strict.returncode != 0, ( "the .mjs check no longer reports a module syntax error — this whole " diff --git a/packages/meshbay-hub/tests/test_streamed_download_reliability.py b/packages/meshbay-hub/tests/test_streamed_download_reliability.py index e1b3800..e60e833 100644 --- a/packages/meshbay-hub/tests/test_streamed_download_reliability.py +++ b/packages/meshbay-hub/tests/test_streamed_download_reliability.py @@ -195,8 +195,8 @@ def _run(tmp_path, body, *, control_after_ms=0, active=True, controlled_at_load=False, registered_at_load=False, worker_asleep=False): module = tmp_path / "downloads.mjs" - module.write_text(DOWNLOADS.read_text()) - (tmp_path / "package.json").write_text('{"type":"module"}') + module.write_text(DOWNLOADS.read_text(encoding="utf-8"), encoding="utf-8") + (tmp_path / "package.json").write_text('{"type":"module"}', encoding="utf-8") plan = { "controlAfterMs": control_after_ms, "active": active, @@ -211,12 +211,12 @@ def _run(tmp_path, body, *, control_after_ms=0, active=True, } script = tmp_path / "case.mjs" script.write_text( - (PRELUDE % {"plan": json.dumps(plan), "module": module.as_posix(), + (PRELUDE % {"plan": json.dumps(plan), "module": module.as_uri(), "control": control_budget_ms, "controlled": json.dumps(controlled_at_load)}) + body - + "\nout.log = log;\nconsole.log(JSON.stringify(out));\n") - proc = subprocess.run(["node", str(script)], capture_output=True, text=True, + + "\nout.log = log;\nconsole.log(JSON.stringify(out));\n", encoding="utf-8") + proc = subprocess.run(["node", str(script)], capture_output=True, text=True, encoding="utf-8", timeout=120) assert proc.returncode == 0, proc.stderr return json.loads(proc.stdout) @@ -338,7 +338,7 @@ def test_the_worker_is_primed_at_boot_not_at_the_first_click(tmp_path): from a module that actually imports it — `node --check` would not notice a missing import, which is a mistake this repo has already shipped once. """ - app = (STATIC / "app.js").read_text() + app = (STATIC / "app.js").read_text(encoding="utf-8") assert "downloads.primeServiceWorker()" in app, "nothing primes the worker" assert "import * as downloads from './downloads.js'" in app, ( "app.js calls downloads.primeServiceWorker() without importing downloads") @@ -350,7 +350,7 @@ def test_the_worker_is_primed_at_boot_not_at_the_first_click(tmp_path): def test_the_worker_answers_a_re_claim(tmp_path): """The page's last resort before declaring the path unavailable only works if sw.js implements the other half.""" - sw = (STATIC / "sw.js").read_text() + sw = (STATIC / "sw.js").read_text(encoding="utf-8") assert "mbdl-claim" in sw and "clients.claim()" in sw diff --git a/packages/meshbay-hub/tests/test_table_rows_measured.py b/packages/meshbay-hub/tests/test_table_rows_measured.py index f203624..b1f36ea 100644 --- a/packages/meshbay-hub/tests/test_table_rows_measured.py +++ b/packages/meshbay-hub/tests/test_table_rows_measured.py @@ -61,7 +61,7 @@ SELECTORS = [f"tbody tr:nth-child({r}) td:nth-child({c})" @pytest.fixture(scope="module") def measured(tmp_path_factory): fragment = tmp_path_factory.mktemp("table") / "fragment.html" - fragment.write_text(TABLE) + fragment.write_text(TABLE, encoding="utf-8") proc = subprocess.run( ["python3", str(HARNESS), ",".join(str(w) for w in WIDTHS), str(fragment), *SELECTORS], diff --git a/packages/meshbay-hub/tests/test_transfers.py b/packages/meshbay-hub/tests/test_transfers.py index b1bac4a..1d0c0f0 100644 --- a/packages/meshbay-hub/tests/test_transfers.py +++ b/packages/meshbay-hub/tests/test_transfers.py @@ -26,15 +26,15 @@ pytestmark = pytest.mark.skipif( def _run(body, tmp_path): module = tmp_path / "transfers.mjs" - module.write_text(TRANSFERS.read_text()) + module.write_text(TRANSFERS.read_text(encoding="utf-8"), encoding="utf-8") script = tmp_path / "case.mjs" script.write_text( - f"import {{ TransferStore, formatSpeed }} from '{module.as_posix()}';\n" + f"import {{ TransferStore, formatSpeed }} from '{module.as_uri()}';\n" "const out = [];\n" "const say = (...a) => out.push(...a);\n" f"{body}\n" - "console.log(JSON.stringify(out));\n") - proc = subprocess.run(["node", str(script)], capture_output=True, text=True) + "console.log(JSON.stringify(out));\n", encoding="utf-8") + proc = subprocess.run(["node", str(script)], capture_output=True, text=True, encoding="utf-8") assert proc.returncode == 0, proc.stderr return json.loads(proc.stdout) @@ -388,11 +388,11 @@ def test_asking_for_a_slot_on_a_dead_channel_does_not_throw(tmp_path): # first time it arms its watchdog. start = src.index("const LEASE_WATCHDOG_MS") end = src.index("\nclass MeshBayTransport") - module.write_text(src[start:end] + "\nexport { Lease };\n") + module.write_text(src[start:end] + "\nexport { Lease };\n", encoding="utf-8") script = tmp_path / "case.mjs" script.write_text(f""" -import {{ Lease }} from '{module.as_posix()}'; +import {{ Lease }} from '{module.as_uri()}'; const out = []; const transport = {{ supportsTransferSlots: true, @@ -409,8 +409,8 @@ try {{ lease.release('cancelled'); out.push('release ok'); }} catch (e) {{ out.push('release threw: ' + e.message); }} clearTimeout(lease._watchdog); console.log(JSON.stringify(out)); -""") - proc = subprocess.run(["node", str(script)], capture_output=True, text=True) +""", encoding="utf-8") + proc = subprocess.run(["node", str(script)], capture_output=True, text=True, encoding="utf-8") assert proc.returncode == 0, proc.stderr out = json.loads(proc.stdout) assert out[0] is None, f"asking for a slot threw: {out[0]}" @@ -433,7 +433,7 @@ def test_the_slot_is_asked_for_after_there_is_somewhere_to_write(): Source-reading, because the ordering is the whole property and it has no behaviour of its own to drive: what matters is which call comes first. """ - src = (STATIC / "file-utils.js").read_text() + src = (STATIC / "file-utils.js").read_text(encoding="utf-8") fn = src[src.index("async function downloadEntry"):] fn = fn[:fn.index("\n}\n")] # `_openTargetInTurn` since target openings were serialised — same call, @@ -832,7 +832,7 @@ def test_a_paused_transfer_is_not_filed_under_finished(tmp_path): here: a copy of them in this file would agree with a broken version by construction. """ - src = (STATIC / "app.js").read_text() + src = (STATIC / "app.js").read_text(encoding="utf-8") start = src.index(" const running = items.filter(") block = src[start:src.index("const active =", start)] @@ -851,8 +851,8 @@ const items = [ const seen = { running, waiting, paused, finished }; console.log(JSON.stringify(Object.fromEntries( Object.entries(seen).map(([k, v]) => [k, v.map(i => i.id)])))); -""") - proc = subprocess.run(["node", str(script)], capture_output=True, text=True) +""", encoding="utf-8") + proc = subprocess.run(["node", str(script)], capture_output=True, text=True, encoding="utf-8") assert proc.returncode == 0, proc.stderr groups = json.loads(proc.stdout) @@ -870,7 +870,7 @@ def test_a_paused_transfer_still_counts_as_active(tmp_path): """The badge says how much is going on. A paused transfer is not over — the person means to come back to it — so counting it as nothing would be a panel that says "0" over work that is still there.""" - src = (STATIC / "app.js").read_text() + src = (STATIC / "app.js").read_text(encoding="utf-8") start = src.index(" const running = items.filter(") block = src[start:src.index("\n\n", src.index("const active =", start))] @@ -879,8 +879,8 @@ def test_a_paused_transfer_still_counts_as_active(tmp_path): const items = [{ id: 1, status: 'paused' }, { id: 2, status: 'done' }]; """ + block + """ console.log(JSON.stringify({ active })); -""") - proc = subprocess.run(["node", str(script)], capture_output=True, text=True) +""", encoding="utf-8") + proc = subprocess.run(["node", str(script)], capture_output=True, text=True, encoding="utf-8") assert proc.returncode == 0, proc.stderr assert json.loads(proc.stdout)["active"] == 1 @@ -897,7 +897,7 @@ def test_a_row_that_cannot_pause_says_so_where_the_button_would_be(): Shown only where a folder can actually be chosen: Firefox and Safari have none to choose, and "choose a folder" would be advice that cannot be taken. """ - src = (STATIC / "app.js").read_text() + src = (STATIC / "app.js").read_text(encoding="utf-8") row = src[src.index("function TransferRow"):] row = row[:row.index("\n}\n")] @@ -917,4 +917,4 @@ def test_the_reason_is_translated_everywhere(): """`t()` falls back to the key, so a missing catalogue entry shows `transfers.not_pausable` in a tooltip rather than a sentence.""" for path in sorted((STATIC / "locales").glob("*.js")): - assert "'transfers.not_pausable'" in path.read_text(), path.name + assert "'transfers.not_pausable'" in path.read_text(encoding="utf-8"), path.name diff --git a/packages/meshbay-hub/tests/test_transport_contracts.py b/packages/meshbay-hub/tests/test_transport_contracts.py index 978c2e5..c1ca36b 100644 --- a/packages/meshbay-hub/tests/test_transport_contracts.py +++ b/packages/meshbay-hub/tests/test_transport_contracts.py @@ -59,22 +59,22 @@ def transport(): @pytest.fixture(scope="module") def app(): - return APP.read_text() + return APP.read_text(encoding="utf-8") @pytest.fixture(scope="module") def chat(): - return CHAT_APP.read_text() + return CHAT_APP.read_text(encoding="utf-8") @pytest.fixture(scope="module") def group_page(): - return GROUP_PAGE.read_text() + return GROUP_PAGE.read_text(encoding="utf-8") @pytest.fixture(scope="module") def create_group(): - return CREATE_GROUP.read_text() + return CREATE_GROUP.read_text(encoding="utf-8") def test_chat_history_pages_backwards(transport): @@ -187,7 +187,7 @@ def test_messages_are_keyed_by_id_not_index(chat): def test_presence_has_three_states_and_a_label_for_each(app): for state in ("online", "offline", "unknown"): - assert f"presence-{state}" in (STATIC / "style.css").read_text() + assert f"presence-{state}" in (STATIC / "style.css").read_text(encoding="utf-8") assert "t('presence.' + state)" in app, ( "red and green are the pair colour-blind readers cannot separate, so " "the dot needs a title and an aria-label, not just a colour") @@ -278,7 +278,7 @@ def test_no_setter_survives_the_state_it_belonged_to(): """ import re for path in SPLIT_FILES: - app = path.read_text() + app = path.read_text(encoding="utf-8") declared = set(re.findall(r"const \[\s*\w+\s*,\s*(set\w+)\s*\]\s*=\s*useState", app)) # Names brought in from another module are defined, just not here. imported = set() diff --git a/packages/meshbay-hub/tests/test_upload_seal_client.py b/packages/meshbay-hub/tests/test_upload_seal_client.py index bcda14c..f62aa68 100644 --- a/packages/meshbay-hub/tests/test_upload_seal_client.py +++ b/packages/meshbay-hub/tests/test_upload_seal_client.py @@ -48,10 +48,10 @@ BODY = bytes(range(256)) * 3 # 768 bytes → 24 chunks of 32 def _run_probe(payload: dict) -> dict: with tempfile.TemporaryDirectory() as d: f = Path(d) / "input.json" - f.write_text(json.dumps(payload)) + f.write_text(json.dumps(payload), encoding="utf-8") proc = subprocess.run( ["node", str(PROBE), str(STATIC), str(f), transport_argv()], - capture_output=True, text=True, timeout=60, + capture_output=True, text=True, encoding="utf-8", timeout=60, ) if proc.returncode != 0 or not proc.stdout: pytest.fail(f"upload probe failed:\n{proc.stderr}") diff --git a/packages/meshbay-hub/tests/test_versions_agree.py b/packages/meshbay-hub/tests/test_versions_agree.py index 4466c93..46887c0 100644 --- a/packages/meshbay-hub/tests/test_versions_agree.py +++ b/packages/meshbay-hub/tests/test_versions_agree.py @@ -27,11 +27,11 @@ PACKAGES = ROOT / "packages" def _python_versions() -> dict[str, str]: found = {} for pyproject in sorted(PACKAGES.glob("*/pyproject.toml")): - m = re.search(r'^version = "([^"]+)"', pyproject.read_text(), re.M) + m = re.search(r'^version = "([^"]+)"', pyproject.read_text(encoding="utf-8"), re.M) if m: found[f"{pyproject.parent.name}/pyproject.toml"] = m.group(1) for init in sorted(PACKAGES.glob("*/src/*/__init__.py")): - m = re.search(r'^__version__ = "([^"]+)"', init.read_text(), re.M) + m = re.search(r'^__version__ = "([^"]+)"', init.read_text(encoding="utf-8"), re.M) if m: found[f"{init.parent.name}/__init__.py"] = m.group(1) return found @@ -41,7 +41,7 @@ def _client_version() -> str | None: pkg = PACKAGES / "meshbay-client" / "package.json" if not pkg.exists(): return None - return json.loads(pkg.read_text()).get("version") + return json.loads(pkg.read_text(encoding="utf-8")).get("version") @pytest.mark.skipif(not PACKAGES.is_dir(), reason="package layout not present") diff --git a/packages/meshbay-hub/tests/test_video_audio_track.py b/packages/meshbay-hub/tests/test_video_audio_track.py index 54beca2..82d6e18 100644 --- a/packages/meshbay-hub/tests/test_video_audio_track.py +++ b/packages/meshbay-hub/tests/test_video_audio_track.py @@ -42,7 +42,7 @@ pytestmark = pytest.mark.skipif( @pytest.fixture(scope="module") def app(): - return APP.read_text() + return APP.read_text(encoding="utf-8") def _player(app: str) -> str: @@ -88,10 +88,10 @@ def _label_cases(tmp_path, app, cases, locale="en"): "const t = (k, p) => `${k}:${p.n}`;\n" + src + "\nconst out = JSON.parse(process.argv[2]).map(audioTrackLabel);\n" - "console.log(JSON.stringify(out));\n") + "console.log(JSON.stringify(out));\n", encoding="utf-8") proc = subprocess.run( ["node", str(script), json.dumps(cases)], - capture_output=True, text=True) + capture_output=True, text=True, encoding="utf-8") assert proc.returncode == 0, proc.stderr return json.loads(proc.stdout) diff --git a/packages/meshbay-hub/tests/test_video_buffer_ceiling.py b/packages/meshbay-hub/tests/test_video_buffer_ceiling.py index a488976..da9766e 100644 --- a/packages/meshbay-hub/tests/test_video_buffer_ceiling.py +++ b/packages/meshbay-hub/tests/test_video_buffer_ceiling.py @@ -67,7 +67,7 @@ pytestmark = pytest.mark.skipif( @pytest.fixture(scope="module") def app(): - return APP.read_text() + return APP.read_text(encoding="utf-8") def _player(app: str) -> str: @@ -95,7 +95,7 @@ HARNESS = Path(__file__).parent / "harness" / "mse_harness.mjs" def _harness(**cfg) -> dict: proc = subprocess.run( ["node", str(HARNESS), str(APP), json.dumps(cfg)], - capture_output=True, text=True) + capture_output=True, text=True, encoding="utf-8") assert proc.returncode == 0, proc.stderr return json.loads(proc.stdout) @@ -437,7 +437,7 @@ def test_credit_is_a_window_and_not_a_debt(app): pump = pump[:pump.index("\n }, [")] assert "STREAM_WINDOW - outstandingRef.current" in pump, ( "pump() no longer tops a window up to what is allowed in flight") - src = APP.read_text() + src = APP.read_text(encoding="utf-8") window = int(re.search(r"const STREAM_WINDOW = (\d+)", src).group(1)) assert 2 <= window <= 16, ( f"a window of {window} segments is either too small to keep the pipe " diff --git a/packages/meshbay-hub/tests/test_video_default_season.py b/packages/meshbay-hub/tests/test_video_default_season.py index 898d3f5..ab1d7c1 100644 --- a/packages/meshbay-hub/tests/test_video_default_season.py +++ b/packages/meshbay-hub/tests/test_video_default_season.py @@ -43,7 +43,7 @@ BLOCK = re.compile(r"^function defaultSeason\(show\) \{.*?^\}", re.M | re.S) @pytest.fixture(scope="module") def source(): - m = BLOCK.search(APP.read_text()) + m = BLOCK.search(APP.read_text(encoding="utf-8")) assert m, ("defaultSeason is no longer where this test reads it from — the " "season a show opens on is untested until this is fixed") return m.group(0) @@ -57,8 +57,8 @@ def _default(tmp_path, source, seasons): script.write_text(f""" {source} console.log(JSON.stringify(defaultSeason({json.dumps(show)}))); - """) - proc = subprocess.run(["node", str(script)], capture_output=True, text=True) + """, encoding="utf-8") + proc = subprocess.run(["node", str(script)], capture_output=True, text=True, encoding="utf-8") assert proc.returncode == 0, proc.stderr return json.loads(proc.stdout) diff --git a/packages/meshbay-hub/tests/test_video_detail_measured.py b/packages/meshbay-hub/tests/test_video_detail_measured.py index 1ac8586..4cd5969 100644 --- a/packages/meshbay-hub/tests/test_video_detail_measured.py +++ b/packages/meshbay-hub/tests/test_video_detail_measured.py @@ -182,7 +182,7 @@ SELECTORS = [ def measured(tmp_path_factory): """One browser for every width — launching one apiece cost three minutes.""" fragment = tmp_path_factory.mktemp("videodetail") / "fragment.html" - fragment.write_text(FRAGMENT) + fragment.write_text(FRAGMENT, encoding="utf-8") proc = subprocess.run( ["python3", str(HARNESS), ",".join(str(w) for w in WIDTHS), str(fragment), *SELECTORS], @@ -292,7 +292,7 @@ def test_the_episode_list_reserves_its_scrollbar(): rectangle — `test_layout_responsive.py` says so at length — but it is worth more than a test that cannot fail. """ - css = (STATIC / "style.css").read_text() + css = (STATIC / "style.css").read_text(encoding="utf-8") rule = re.search( r"\.video-detail\.video-detail-steady \.video-season-list \{([^}]*)\}", css) assert rule, "the steady episode-list rule is gone" diff --git a/packages/meshbay-hub/tests/test_video_reconnect.py b/packages/meshbay-hub/tests/test_video_reconnect.py index beeeace..91aa87b 100644 --- a/packages/meshbay-hub/tests/test_video_reconnect.py +++ b/packages/meshbay-hub/tests/test_video_reconnect.py @@ -53,7 +53,7 @@ pytestmark = pytest.mark.skipif( @pytest.fixture(scope="module") def app(): - return APP.read_text() + return APP.read_text(encoding="utf-8") def _player(app: str) -> str: @@ -87,7 +87,7 @@ def _plan(app: str, cases: list[dict]) -> list[dict]: "(c) => reconnectPlan(c.playhead, c.range, c.ended, c.cast))));" ) proc = subprocess.run(["node", "--input-type=module", "-e", script], - capture_output=True, text=True) + capture_output=True, text=True, encoding="utf-8") assert proc.returncode == 0, proc.stderr return json.loads(proc.stdout) diff --git a/packages/meshbay-hub/tests/test_video_seek.py b/packages/meshbay-hub/tests/test_video_seek.py index 9436065..3289eda 100644 --- a/packages/meshbay-hub/tests/test_video_seek.py +++ b/packages/meshbay-hub/tests/test_video_seek.py @@ -46,7 +46,7 @@ pytestmark = pytest.mark.skipif(not APP.exists(), reason="SPA sources unavailabl @pytest.fixture(scope="module") def app(): - return APP.read_text() + return APP.read_text(encoding="utf-8") def _player(app: str) -> str: @@ -164,7 +164,7 @@ def test_the_leak_deadlocks_the_window_and_the_fix_clears_it(): proc = subprocess.run( ["node", str(harness), str(APP), json.dumps({"decrementFirst": decrement_first})], - capture_output=True, text=True) + capture_output=True, text=True, encoding="utf-8") assert proc.returncode == 0, proc.stderr return json.loads(proc.stdout) @@ -269,7 +269,7 @@ def test_seeks_are_debounced(app): """Dragging fires `seeking` continuously; each one we act on costs a spawn.""" player = _player(app) assert "SEEK_DEBOUNCE_MS" in player, "every intermediate drag position seeks" - ms = int(re.search(r"const SEEK_DEBOUNCE_MS = (\d+)", APP.read_text()).group(1)) + ms = int(re.search(r"const SEEK_DEBOUNCE_MS = (\d+)", APP.read_text(encoding="utf-8")).group(1)) assert 150 <= ms <= 1000, ( f"{ms} ms is either short enough to still storm the node or long " "enough to feel broken") @@ -289,7 +289,7 @@ def test_a_seek_inside_the_buffer_does_not_reach_the_node(app): def test_the_position_is_kept_in_this_browser(app): """localStorage: no protocol, no storage for anyone else to keep, and nothing new learns what you watch.""" - src = APP.read_text() + src = APP.read_text(encoding="utf-8") assert "mb:pos:" in src, "no position is stored" read = src[src.index("function readResumePosition"):] read = read[:read.index("\n}")] @@ -299,7 +299,7 @@ def test_the_position_is_kept_in_this_browser(app): def test_a_finished_film_does_not_offer_to_resume(app): - src = APP.read_text() + src = APP.read_text(encoding="utf-8") write = src[src.index("function writeResumePosition"):] write = write[:write.index("\n}")] assert "RESUME_MAX_FRACTION" in write and "removeItem" in write, ( @@ -317,6 +317,6 @@ def test_the_viewer_can_refuse_the_resume(app): @pytest.mark.parametrize("locale", ["en", "fr", "es", "pt-BR", "zh-CN", "ja", "de", "it", "nl", "pl"]) def test_the_resume_strings_exist_everywhere(locale): - text = (STATIC / "locales" / f"{locale}.js").read_text() + text = (STATIC / "locales" / f"{locale}.js").read_text(encoding="utf-8") for key in ("video.resumed_at", "video.from_start"): assert key in text, f"{locale} is missing {key}" diff --git a/packages/meshbay-hub/tests/test_video_stream_switch.py b/packages/meshbay-hub/tests/test_video_stream_switch.py index 7859057..6c67430 100644 --- a/packages/meshbay-hub/tests/test_video_stream_switch.py +++ b/packages/meshbay-hub/tests/test_video_stream_switch.py @@ -45,7 +45,7 @@ pytestmark = pytest.mark.skipif( @pytest.fixture(scope="module") def app(): - return APP.read_text() + return APP.read_text(encoding="utf-8") def _player(app: str) -> str: @@ -125,8 +125,8 @@ def test_the_stall_is_reproduced_and_the_reset_clears_it(tmp_path): out[name] = p.st.appended - afterFirst; } console.log(JSON.stringify(out)); - """) - proc = subprocess.run(["node", str(script)], capture_output=True, text=True) + """, encoding="utf-8") + proc = subprocess.run(["node", str(script)], capture_output=True, text=True, encoding="utf-8") assert proc.returncode == 0, proc.stderr got = json.loads(proc.stdout) diff --git a/packages/meshbay-hub/tests/test_video_subtitles.py b/packages/meshbay-hub/tests/test_video_subtitles.py index 5a4b215..68058cd 100644 --- a/packages/meshbay-hub/tests/test_video_subtitles.py +++ b/packages/meshbay-hub/tests/test_video_subtitles.py @@ -47,7 +47,7 @@ pytestmark = pytest.mark.skipif( @pytest.fixture(scope="module") def app(): - return APP.read_text() + return APP.read_text(encoding="utf-8") @pytest.fixture(scope="module") @@ -117,10 +117,10 @@ def _label_cases(tmp_path, app, cases, locale="en"): "const t = (k, p) => (p ? `${k}:${p.n}` : k);\n" + src + "\nconst out = JSON.parse(process.argv[2]).map(subtitleTrackLabel);\n" - "console.log(JSON.stringify(out));\n") + "console.log(JSON.stringify(out));\n", encoding="utf-8") proc = subprocess.run( ["node", str(script), json.dumps(cases)], - capture_output=True, text=True) + capture_output=True, text=True, encoding="utf-8") assert proc.returncode == 0, proc.stderr return json.loads(proc.stdout) diff --git a/packages/meshbay-hub/tests/test_zip_size_limit.py b/packages/meshbay-hub/tests/test_zip_size_limit.py index 9243fd1..6ef0989 100644 --- a/packages/meshbay-hub/tests/test_zip_size_limit.py +++ b/packages/meshbay-hub/tests/test_zip_size_limit.py @@ -46,7 +46,7 @@ def _run(total_bytes, tmp_path, picker=False): for src in STATIC.glob("*.js"): (sandbox / src.name).write_text(src.read_text(encoding="utf-8"), encoding="utf-8") - (tmp_path / "package.json").write_text('{"type":"module"}') + (tmp_path / "package.json").write_text('{"type":"module"}', encoding="utf-8") # ask.js draws a dialog in the DOM, which Node has none of; the question is # answered here instead, exactly where `confirm` used to be stubbed. (sandbox / "ask.js").write_text( @@ -87,7 +87,7 @@ if ({picker_js}) {{ }}); }} -const M = await import('{(sandbox / "file-utils.js").as_posix()}'); +const M = await import('{(sandbox / "file-utils.js").as_uri()}'); // Faithful enough to the real store: it runs `prepare` and honours what it // returns. The target is opened there now — the row exists from the click and @@ -126,7 +126,7 @@ out.limit = M.ZIP_MAX_BYTES; console.log(JSON.stringify(out)); """, encoding="utf-8") - proc = subprocess.run(["node", str(script)], capture_output=True, text=True) + proc = subprocess.run(["node", str(script)], capture_output=True, text=True, encoding="utf-8") assert proc.returncode == 0, proc.stderr return json.loads(proc.stdout) diff --git a/packages/meshbay-hub/tests/test_zipstream.py b/packages/meshbay-hub/tests/test_zipstream.py index 51a42d0..55a4d97 100644 --- a/packages/meshbay-hub/tests/test_zipstream.py +++ b/packages/meshbay-hub/tests/test_zipstream.py @@ -32,12 +32,12 @@ def _build(files, force_zip64=False, tmp_path=None): #