summaryrefslogtreecommitdiffstats
path: root/packages
diff options
context:
space:
mode:
authorChristophe Besson <cbesson@gmail.com>2026-09-25 11:09:12 +0200
committerChristophe Besson <cbesson@gmail.com>2026-09-25 12:07:49 +0200
commit6348698322e5bfcb80d0ef0dcd833ba4e6179640 (patch)
tree6d4f47a0a1745ef5ae912368d078cf681b9fcd19 /packages
parent71ea15a926d02437a65b8d788834c46f4e6df9a3 (diff)
downloadmeshbay-6348698322e5bfcb80d0ef0dcd833ba4e6179640.tar.gz
test(hub): read the transport wherever it is split
spa_source.transport_files() takes the classic transport*.js scripts from the hub's shell, in load order. Every test that read transport.js reads them all, the Node harnesses run them joined as one scope, the chat probe loads each, and the desktop shell must load them in order. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'packages')
-rw-r--r--packages/meshbay-hub/tests/harness/chat_send_probe.py10
-rw-r--r--packages/meshbay-hub/tests/harness/index_seal_probe.mjs6
-rw-r--r--packages/meshbay-hub/tests/harness/offer_retry_harness.mjs4
-rw-r--r--packages/meshbay-hub/tests/harness/upload_seal_probe.mjs5
-rw-r--r--packages/meshbay-hub/tests/spa_source.py31
-rw-r--r--packages/meshbay-hub/tests/test_account_pinning.py8
-rw-r--r--packages/meshbay-hub/tests/test_app_settings_plugin.py5
-rw-r--r--packages/meshbay-hub/tests/test_challenge_signature_client.py7
-rw-r--r--packages/meshbay-hub/tests/test_connect_never_hangs.py7
-rw-r--r--packages/meshbay-hub/tests/test_desktop_shell.py5
-rw-r--r--packages/meshbay-hub/tests/test_helloworld_proves_the_plugin_claim.py4
-rw-r--r--packages/meshbay-hub/tests/test_hub_address_seam.py4
-rw-r--r--packages/meshbay-hub/tests/test_index_seal_client.py3
-rw-r--r--packages/meshbay-hub/tests/test_indexing_dock.py3
-rw-r--r--packages/meshbay-hub/tests/test_invite_link_client.py3
-rw-r--r--packages/meshbay-hub/tests/test_offer_retry.py3
-rw-r--r--packages/meshbay-hub/tests/test_reconnect_refresh.py3
-rw-r--r--packages/meshbay-hub/tests/test_rewrap_fanout.py7
-rw-r--r--packages/meshbay-hub/tests/test_search_unlisted.py3
-rw-r--r--packages/meshbay-hub/tests/test_spa_ordering.py7
-rw-r--r--packages/meshbay-hub/tests/test_transfers.py3
-rw-r--r--packages/meshbay-hub/tests/test_transport_contracts.py4
-rw-r--r--packages/meshbay-hub/tests/test_upload_controls_hidden.py7
-rw-r--r--packages/meshbay-hub/tests/test_upload_seal_client.py3
-rw-r--r--packages/meshbay-hub/tests/test_video_audio_track.py3
-rw-r--r--packages/meshbay-hub/tests/test_video_seek.py3
-rw-r--r--packages/meshbay-hub/tests/test_video_subtitles.py3
27 files changed, 118 insertions, 36 deletions
diff --git a/packages/meshbay-hub/tests/harness/chat_send_probe.py b/packages/meshbay-hub/tests/harness/chat_send_probe.py
index d343c504..75696280 100644
--- a/packages/meshbay-hub/tests/harness/chat_send_probe.py
+++ b/packages/meshbay-hub/tests/harness/chat_send_probe.py
@@ -77,6 +77,9 @@ import threading
import time
from pathlib import Path
+sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
+from spa_source import transport_files # noqa: E402
+
STATIC = Path(__file__).resolve().parents[2] / "src" / "meshbay_hub" / "static"
PORT = 8755
RECORDS = []
@@ -104,7 +107,10 @@ def _page() -> str:
.replace("__GROUP_ID__", GROUP_ID)
.replace("__GEK_HEX__", GEK.hex())
.replace("__KEYS_NONCE_HEX__", sealed["nonce"].hex())
- .replace("__KEYS_CT_HEX__", sealed["ct"].hex()))
+ .replace("__KEYS_CT_HEX__", sealed["ct"].hex())
+ # transport.js and the parts split out of it, in the shell's order.
+ .replace("__TRANSPORT_SCRIPTS__", "\n".join(
+ f'<script src="/{p.name}"></script>' for p in transport_files())))
PAGE_TEMPLATE = r"""<!doctype html><html><head><meta charset=utf-8>
<link rel="stylesheet" href="/style.css"></head>
@@ -119,7 +125,7 @@ PAGE_TEMPLATE = r"""<!doctype html><html><head><meta charset=utf-8>
Without them a send fails with "cannot read properties of undefined". -->
<script src="/crypto.js"></script>
<script src="/keyderive.js"></script>
-<script src="/transport.js"></script>
+__TRANSPORT_SCRIPTS__
<script type="module">
import { html, render, useRef, useState, useEffect } from '/vendor/htm-preact.js';
import { ChatPanel } from '/chat-app.js';
diff --git a/packages/meshbay-hub/tests/harness/index_seal_probe.mjs b/packages/meshbay-hub/tests/harness/index_seal_probe.mjs
index 36302bbb..2493a055 100644
--- a/packages/meshbay-hub/tests/harness/index_seal_probe.mjs
+++ b/packages/meshbay-hub/tests/harness/index_seal_probe.mjs
@@ -21,6 +21,7 @@
* the outstanding request ended.
*/
import fs from 'fs';
+import { delimiter } from 'path';
const STATIC = process.argv[2];
const input = JSON.parse(fs.readFileSync(process.argv[3], 'utf8'));
@@ -41,7 +42,10 @@ globalThis.document = {
};
new Function(fs.readFileSync(`${STATIC}/crypto.js`, 'utf8'))();
-new Function(fs.readFileSync(`${STATIC}/transport.js`, 'utf8'))();
+// argv[4]: transport.js and the parts split out of it, joined as one scope.
+const transportSrc = process.argv[4].split(delimiter)
+ .map((p) => fs.readFileSync(p, 'utf8')).join('\n');
+new Function(transportSrc)();
const hex = (s) => Uint8Array.from(s.match(/../g).map((b) => parseInt(b, 16)));
diff --git a/packages/meshbay-hub/tests/harness/offer_retry_harness.mjs b/packages/meshbay-hub/tests/harness/offer_retry_harness.mjs
index 3a4b5687..3e8233bd 100644
--- a/packages/meshbay-hub/tests/harness/offer_retry_harness.mjs
+++ b/packages/meshbay-hub/tests/harness/offer_retry_harness.mjs
@@ -6,8 +6,10 @@
//
// Usage: node offer_retry_harness.mjs <path to transport.js> <json config>
import { readFileSync } from 'fs';
+import { delimiter } from 'path';
-const src = readFileSync(process.argv[2], 'utf8');
+// transport.js and the parts split out of it, joined.
+const src = process.argv[2].split(delimiter).map((p) => readFileSync(p, 'utf8')).join('\n');
const cfg = JSON.parse(process.argv[3] || '{}');
const {
diff --git a/packages/meshbay-hub/tests/harness/upload_seal_probe.mjs b/packages/meshbay-hub/tests/harness/upload_seal_probe.mjs
index 0fe7554c..60ec48a7 100644
--- a/packages/meshbay-hub/tests/harness/upload_seal_probe.mjs
+++ b/packages/meshbay-hub/tests/harness/upload_seal_probe.mjs
@@ -21,6 +21,7 @@
* seal, so the ciphertext stays exactly the one Python produced.
*/
import fs from 'fs';
+import { delimiter } from 'path';
const STATIC = process.argv[2];
const input = JSON.parse(fs.readFileSync(process.argv[3], 'utf8'));
@@ -38,7 +39,9 @@ globalThis.document = {
};
new Function(fs.readFileSync(`${STATIC}/crypto.js`, 'utf8'))();
-const transportSrc = fs.readFileSync(`${STATIC}/transport.js`, 'utf8');
+// argv[4]: transport.js and the parts split out of it, joined as one scope.
+const transportSrc = process.argv[4].split(delimiter)
+ .map((p) => fs.readFileSync(p, 'utf8')).join('\n');
new Function(transportSrc)();
// The msgpack codec is private to transport.js — pulled out the same way the
// groupbox parity harness pulls out sealGroup, so this probe encodes and
diff --git a/packages/meshbay-hub/tests/spa_source.py b/packages/meshbay-hub/tests/spa_source.py
index 7df98cac..cfeae60a 100644
--- a/packages/meshbay-hub/tests/spa_source.py
+++ b/packages/meshbay-hub/tests/spa_source.py
@@ -7,6 +7,7 @@ without looking at it. So the tests that read the search code take it from
here, the page and whatever it has been split into.
"""
+import re
from pathlib import Path
STATIC = Path(__file__).resolve().parents[1] / "src" / "meshbay_hub" / "static"
@@ -26,3 +27,33 @@ def search_argv() -> str:
"""The same files, as the one argument the Node harnesses take."""
import os
return os.pathsep.join(str(p) for p in search_files())
+
+
+# ── The transport: transport.js and the classic scripts split out of it ──────
+
+WEBAPP = STATIC.parent / "api" / "webapp.py"
+CLASSIC_TAG = re.compile(r'<script src="/a/\{v\}/([^"]+)"></script>')
+
+
+def classic_scripts() -> list[str]:
+ """The classic (non-module) scripts of the hub's shell, in load order."""
+ return CLASSIC_TAG.findall(WEBAPP.read_text(encoding="utf-8"))
+
+
+def transport_files() -> list[Path]:
+ """Every file of the transport, in the order the shell loads them: the
+ classic scripts named `transport*.js`. Derived from the shell, so a part
+ added there is read here without anybody having to list it."""
+ files = [STATIC / n for n in classic_scripts() if n.startswith("transport")]
+ assert files and files[0].name.startswith("transport"), files
+ return files
+
+
+def transport_source() -> str:
+ return "\n".join(p.read_text(encoding="utf-8") for p in transport_files())
+
+
+def transport_argv() -> str:
+ """The same files, as the one argument the Node harnesses take."""
+ import os
+ return os.pathsep.join(str(p) for p in transport_files())
diff --git a/packages/meshbay-hub/tests/test_account_pinning.py b/packages/meshbay-hub/tests/test_account_pinning.py
index a1419c39..529ebd59 100644
--- a/packages/meshbay-hub/tests/test_account_pinning.py
+++ b/packages/meshbay-hub/tests/test_account_pinning.py
@@ -24,6 +24,7 @@ import pytest
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
from meshbay_common.device import device_add_transcript
+from spa_source import transport_argv
STATIC = (Path(__file__).resolve().parents[1]
/ "src" / "meshbay_hub" / "static")
@@ -56,7 +57,10 @@ globalThis.localStorage = {
// crypto.js publishes onto window; transport.js reads it from there.
new Function(fs.readFileSync(process.argv[2], 'utf8'))();
const T = new Function(
- fs.readFileSync(process.argv[3], 'utf8') + '\nreturn { _verifyRoster };')();
+ // transport.js and the parts split out of it, joined as one scope.
+ process.argv[3].split(require('path').delimiter)
+ .map((p) => fs.readFileSync(p, 'utf8')).join('\n')
+ + '\nreturn { _verifyRoster };')();
(async () => {
const input = JSON.parse(fs.readFileSync(process.argv[4], 'utf8'));
@@ -104,7 +108,7 @@ def _verify(devices, node_pk=NODE_PK):
{"payload": {"devices": devices, "node_pk": node_pk},
"node_pk": node_pk}))
run = subprocess.run(
- ["node", str(h), str(CRYPTO), str(TRANSPORT), str(payload)],
+ ["node", str(h), str(CRYPTO), transport_argv(), str(payload)],
capture_output=True, timeout=60)
assert run.returncode == 0, run.stderr.decode()[-2000:]
return json.loads(run.stdout.decode())
diff --git a/packages/meshbay-hub/tests/test_app_settings_plugin.py b/packages/meshbay-hub/tests/test_app_settings_plugin.py
index a1ecec7f..a56d70f9 100644
--- a/packages/meshbay-hub/tests/test_app_settings_plugin.py
+++ b/packages/meshbay-hub/tests/test_app_settings_plugin.py
@@ -17,6 +17,7 @@ from pathlib import Path
import node_tree
import pytest
+from spa_source import transport_source
STATIC = Path(__file__).resolve().parents[1] / "src" / "meshbay_hub" / "static"
APPS = STATIC / "apps.js"
@@ -195,7 +196,7 @@ def test_the_directory_op_is_signed_and_names_its_app():
indistinguishable — so the app is in the signed subject, and both sides
build it the same way.
"""
- transport = TRANSPORT.read_text(encoding="utf-8")
+ transport = transport_source()
body = transport[transport.index("async setAppDirectories("):]
body = body[:body.index("\n async ", 1)]
assert "admin_challenge" in body and "_authorizeAdminOp" in body
@@ -378,7 +379,7 @@ def test_a_saved_setting_reaches_the_page_that_renders_the_pane():
the broadcast, and the one that asked went on showing an unsaved-looking
draft. Clicking Save again just re-sent it.
"""
- transport = TRANSPORT.read_text(encoding="utf-8")
+ transport = transport_source()
block = transport[transport.index("const BROADCAST_ACK_TYPES"):]
block = block[:block.index("]);") + 3]
for ack in ("chat_directory_ack", "chat_link_preview_ack",
diff --git a/packages/meshbay-hub/tests/test_challenge_signature_client.py b/packages/meshbay-hub/tests/test_challenge_signature_client.py
index 90aa5f22..e3c33ac5 100644
--- a/packages/meshbay-hub/tests/test_challenge_signature_client.py
+++ b/packages/meshbay-hub/tests/test_challenge_signature_client.py
@@ -20,6 +20,7 @@ import pytest
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
from meshbay_common.crypto import pk_to_b64
from meshbay_common.handshake import challenge_transcript, webrtc_binding
+from spa_source import transport_argv
STATIC = Path(__file__).resolve().parents[1] / "src" / "meshbay_hub" / "static"
@@ -39,7 +40,9 @@ globalThis.document = {
};
const STATIC = process.argv[2];
new Function(fs.readFileSync(`${STATIC}/crypto.js`, 'utf8'))();
-const src = fs.readFileSync(`${STATIC}/transport.js`, 'utf8');
+// argv[4]: transport.js and the parts split out of it, joined as one scope.
+const src = process.argv[4].split(require('path').delimiter)
+ .map((p) => fs.readFileSync(p, 'utf8')).join('\n');
const { _challengeProvesNodeKey } =
new Function(src + '\nreturn { _challengeProvesNodeKey };')();
@@ -93,7 +96,7 @@ def test_the_browser_holds_the_node_to_its_challenge(tmp_path):
harness.write_text(_HARNESS)
payload = tmp_path / "cases.json"
payload.write_text(json.dumps([c for c, _ in cases.values()]))
- proc = subprocess.run(["node", str(harness), str(STATIC), str(payload)],
+ proc = subprocess.run(["node", str(harness), str(STATIC), str(payload), transport_argv()],
capture_output=True, text=True, timeout=60)
assert proc.returncode == 0, proc.stderr
got = dict(zip(cases, json.loads(proc.stdout)))
diff --git a/packages/meshbay-hub/tests/test_connect_never_hangs.py b/packages/meshbay-hub/tests/test_connect_never_hangs.py
index d6f13697..5680877e 100644
--- a/packages/meshbay-hub/tests/test_connect_never_hangs.py
+++ b/packages/meshbay-hub/tests/test_connect_never_hangs.py
@@ -22,6 +22,7 @@ import re
from pathlib import Path
import pytest
+from spa_source import transport_source
STATIC = Path(__file__).resolve().parents[1] / "src" / "meshbay_hub" / "static"
TRANSPORT = STATIC / "transport.js"
@@ -33,7 +34,7 @@ pytestmark = pytest.mark.skipif(not TRANSPORT.exists(),
def _gathering_block() -> str:
"""The wait on ICE gathering, and only it."""
- source = TRANSPORT.read_text(encoding="utf-8")
+ source = transport_source()
start = source.index("iceGatheringState")
return source[max(0, start - 900):start + 700]
@@ -48,7 +49,7 @@ def test_ice_gathering_has_a_deadline():
def test_the_deadline_is_long_enough_for_a_stun_round_trip():
"""Cutting gathering off too early drops the reflexive candidate and breaks
every connection that is not on the same network."""
- source = TRANSPORT.read_text(encoding="utf-8")
+ source = transport_source()
match = re.search(r"const ICE_GATHER_TIMEOUT_MS = (\d+);", source)
assert match, "the constant is gone or was renamed"
assert 2000 <= int(match.group(1)) <= 10000
@@ -57,7 +58,7 @@ def test_the_deadline_is_long_enough_for_a_stun_round_trip():
def test_a_timed_out_gathering_still_sends_the_offer():
"""Host candidates are already gathered, which is enough on a LAN. Giving
up instead would turn a slow STUN server into a refusal to connect."""
- source = TRANSPORT.read_text(encoding="utf-8")
+ source = transport_source()
block = _gathering_block()
# The deadline resolves the promise; it does not reject it.
assert "reject" not in block.split("setTimeout", 1)[1][:300]
diff --git a/packages/meshbay-hub/tests/test_desktop_shell.py b/packages/meshbay-hub/tests/test_desktop_shell.py
index e6eefc2d..b9b33193 100644
--- a/packages/meshbay-hub/tests/test_desktop_shell.py
+++ b/packages/meshbay-hub/tests/test_desktop_shell.py
@@ -18,6 +18,7 @@ import re
from pathlib import Path
import pytest
+from spa_source import transport_files
CLIENT = Path(__file__).resolve().parents[2] / "meshbay-client"
MAIN = CLIENT / "src" / "main.js"
@@ -402,6 +403,10 @@ def test_the_packaged_page_loads_the_shared_modules():
for module in ("keyderive.js", "crypto.js", "transport.js", "app.js",
"style.css", "argon2.min.js"):
assert module in page, f"{module} is not loaded by the packaged page"
+ # Every part of the transport, in the order the hub's shell loads them.
+ parts = [p.name for p in transport_files()]
+ at = [page.index(f'src="./{name}"') for name in parts]
+ assert at == sorted(at), f"the packaged page loads the transport out of order: {parts}"
assert "/a/" not in page, "the packaged page points at the hub's asset prefix"
diff --git a/packages/meshbay-hub/tests/test_helloworld_proves_the_plugin_claim.py b/packages/meshbay-hub/tests/test_helloworld_proves_the_plugin_claim.py
index 71a0ea8f..95094834 100644
--- a/packages/meshbay-hub/tests/test_helloworld_proves_the_plugin_claim.py
+++ b/packages/meshbay-hub/tests/test_helloworld_proves_the_plugin_claim.py
@@ -33,6 +33,7 @@ from pathlib import Path
import node_tree
import pytest
+import spa_source
STATIC = Path(__file__).resolve().parents[1] / "src" / "meshbay_hub" / "static"
@@ -51,7 +52,8 @@ def _code_only(source: str) -> str:
# ── The claim ────────────────────────────────────────────────────────────────
@pytest.mark.parametrize("name", [
- "group-page.js", "group-settings.js", "files-app.js", "transport.js",
+ "group-page.js", "group-settings.js", "files-app.js",
+ *[p.name for p in spa_source.transport_files()],
"hub-client.js", "settings-ui.js", "folder-tree.js",
])
def test_no_shared_client_file_mentions_it(name):
diff --git a/packages/meshbay-hub/tests/test_hub_address_seam.py b/packages/meshbay-hub/tests/test_hub_address_seam.py
index ccdac7d1..64dcdc9c 100644
--- a/packages/meshbay-hub/tests/test_hub_address_seam.py
+++ b/packages/meshbay-hub/tests/test_hub_address_seam.py
@@ -18,6 +18,7 @@ import re
from pathlib import Path
import pytest
+from spa_source import transport_source
STATIC = Path(__file__).resolve().parents[1] / "src" / "meshbay_hub" / "static"
@@ -28,6 +29,9 @@ CALLERS = ["app.js", "keyderive.js", "transport.js", "crypto.js",
def _source(name: str) -> str:
+ # transport.js stands for every part of the transport (spa_source).
+ if name == "transport.js":
+ return transport_source()
return (STATIC / name).read_text(encoding="utf-8")
diff --git a/packages/meshbay-hub/tests/test_index_seal_client.py b/packages/meshbay-hub/tests/test_index_seal_client.py
index e1135daf..20f89d1f 100644
--- a/packages/meshbay-hub/tests/test_index_seal_client.py
+++ b/packages/meshbay-hub/tests/test_index_seal_client.py
@@ -22,6 +22,7 @@ import msgpack
import pytest
from meshbay_common.crypto import generate_gek
from meshbay_common.groupbox import PURPOSE_INDEX, seal
+from spa_source import transport_argv
STATIC = Path(__file__).resolve().parents[1] / "src" / "meshbay_hub" / "static"
PROBE = Path(__file__).resolve().parent / "harness" / "index_seal_probe.mjs"
@@ -70,7 +71,7 @@ def _run(frames: list[str], gek: bytes = GEK) -> dict:
vectors.write_text(json.dumps(
{"gek": gek.hex(), "group_id": GROUP, "frames": frames}))
proc = subprocess.run(
- ["node", str(PROBE), str(STATIC), str(vectors)],
+ ["node", str(PROBE), str(STATIC), str(vectors), transport_argv()],
capture_output=True, text=True, timeout=120)
if proc.returncode != 0:
pytest.fail(f"probe failed:\n{proc.stderr}")
diff --git a/packages/meshbay-hub/tests/test_indexing_dock.py b/packages/meshbay-hub/tests/test_indexing_dock.py
index ade41736..d69564d0 100644
--- a/packages/meshbay-hub/tests/test_indexing_dock.py
+++ b/packages/meshbay-hub/tests/test_indexing_dock.py
@@ -16,6 +16,7 @@ import subprocess
from pathlib import Path
import pytest
+from spa_source import transport_source
STATIC = Path(__file__).resolve().parents[1] / "src" / "meshbay_hub" / "static"
MODEL = STATIC / "index-dock-model.js"
@@ -180,7 +181,7 @@ def test_leaving_a_group_clears_its_row():
def test_the_transport_passes_the_new_counters_through():
- source = TRANSPORT.read_text(encoding="utf-8")
+ source = transport_source()
block = source[source.index("msg.type === 'index_progress'"):]
block = block[:block.index("return;")]
for field in ("files_done", "files_total", "kind", "root_pos", "queued"):
diff --git a/packages/meshbay-hub/tests/test_invite_link_client.py b/packages/meshbay-hub/tests/test_invite_link_client.py
index be9885ad..2223ad86 100644
--- a/packages/meshbay-hub/tests/test_invite_link_client.py
+++ b/packages/meshbay-hub/tests/test_invite_link_client.py
@@ -28,6 +28,7 @@ from pathlib import Path
import pytest
from meshbay_hub import mail as mail_mod
from meshbay_hub.api import invite_links
+from spa_source import transport_source
STATIC = Path(__file__).resolve().parents[1] / "src" / "meshbay_hub" / "static"
LINK_JS = STATIC / "invite-link.js"
@@ -144,7 +145,7 @@ def test_the_code_leaves_the_address_and_stays_in_the_tab(tmp_path):
def test_a_link_code_goes_to_the_node_the_link_names_and_no_other(tmp_path):
- src = (STATIC / "transport.js").read_text(encoding="utf-8")
+ src = transport_source()
fn = re.search(r"^function _linkJoinRefusal\(.*?^\}", src, re.M | re.S)
assert fn, "transport.js no longer has _linkJoinRefusal"
got = _run(tmp_path, fn.group(0) + """
diff --git a/packages/meshbay-hub/tests/test_offer_retry.py b/packages/meshbay-hub/tests/test_offer_retry.py
index 3c968db9..6389f8e2 100644
--- a/packages/meshbay-hub/tests/test_offer_retry.py
+++ b/packages/meshbay-hub/tests/test_offer_retry.py
@@ -19,6 +19,7 @@ import subprocess
from pathlib import Path
import pytest
+from spa_source import transport_argv
STATIC = Path(__file__).resolve().parents[1] / "src" / "meshbay_hub" / "static"
TRANSPORT = STATIC / "transport.js"
@@ -31,7 +32,7 @@ pytestmark = pytest.mark.skipif(
def _post(**cfg) -> dict:
proc = subprocess.run(
- ["node", str(HARNESS), str(TRANSPORT), json.dumps(cfg)],
+ ["node", str(HARNESS), transport_argv(), json.dumps(cfg)],
capture_output=True, text=True)
assert proc.returncode == 0, proc.stderr
return json.loads(proc.stdout)
diff --git a/packages/meshbay-hub/tests/test_reconnect_refresh.py b/packages/meshbay-hub/tests/test_reconnect_refresh.py
index c93e94db..ff6d7fb4 100644
--- a/packages/meshbay-hub/tests/test_reconnect_refresh.py
+++ b/packages/meshbay-hub/tests/test_reconnect_refresh.py
@@ -25,6 +25,7 @@ import re
from pathlib import Path
import pytest
+from spa_source import transport_source
STATIC = Path(__file__).resolve().parents[1] / "src" / "meshbay_hub" / "static"
TRANSPORT = STATIC / "transport.js"
@@ -37,7 +38,7 @@ pytestmark = pytest.mark.skipif(
@pytest.fixture(scope="module")
def transport():
- return TRANSPORT.read_text()
+ return transport_source()
@pytest.fixture(scope="module")
diff --git a/packages/meshbay-hub/tests/test_rewrap_fanout.py b/packages/meshbay-hub/tests/test_rewrap_fanout.py
index 54ef67af..7c0f272c 100644
--- a/packages/meshbay-hub/tests/test_rewrap_fanout.py
+++ b/packages/meshbay-hub/tests/test_rewrap_fanout.py
@@ -16,6 +16,7 @@ import subprocess
from pathlib import Path
import pytest
+from spa_source import transport_argv
STATIC = Path(__file__).resolve().parents[1] / "src" / "meshbay_hub" / "static"
TRANSPORT = STATIC / "transport.js"
@@ -40,7 +41,9 @@ global.localStorage = {
// connect() is stubbed on the prototype below, so no WebRTC shim is needed.
global.RTCPeerConnection = function () { throw new Error('connect() not stubbed'); };
-eval(fs.readFileSync(process.argv[2], 'utf8'));
+// transport.js and the parts split out of it, joined as one scope.
+eval(process.argv[2].split(require('path').delimiter)
+ .map((p) => fs.readFileSync(p, 'utf8')).join('\n'));
const T = window.MeshBayTransport;
let deriveEncCalls = 0;
@@ -153,7 +156,7 @@ def result(tmp_path_factory):
harness = d / "harness.cjs"
harness.write_text(_HARNESS)
proc = subprocess.run(
- ["node", str(harness), str(TRANSPORT)],
+ ["node", str(harness), transport_argv()],
capture_output=True, text=True, timeout=120,
)
if proc.returncode != 0:
diff --git a/packages/meshbay-hub/tests/test_search_unlisted.py b/packages/meshbay-hub/tests/test_search_unlisted.py
index 94f3aee2..430a2bbd 100644
--- a/packages/meshbay-hub/tests/test_search_unlisted.py
+++ b/packages/meshbay-hub/tests/test_search_unlisted.py
@@ -16,6 +16,7 @@ import re
from pathlib import Path
import pytest
+from spa_source import transport_source
STATIC = Path(__file__).resolve().parents[1] / "src" / "meshbay_hub" / "static"
SEARCH_PAGE = STATIC / "search-page.js"
@@ -76,7 +77,7 @@ def test_every_search_view_is_built_from_the_indexed_groups_only():
def test_the_operator_hears_back_from_their_own_change():
"""Same failure as Chat's directory: an admin ack swallowed by its request."""
- transport = TRANSPORT.read_text(encoding="utf-8")
+ transport = transport_source()
block = transport[transport.index("const BROADCAST_ACK_TYPES"):]
assert "'search_listed_ack'" in block[:block.index("]);")]
replay = transport[transport.index("function _replayBroadcast"):]
diff --git a/packages/meshbay-hub/tests/test_spa_ordering.py b/packages/meshbay-hub/tests/test_spa_ordering.py
index 1c36d09d..087298f7 100644
--- a/packages/meshbay-hub/tests/test_spa_ordering.py
+++ b/packages/meshbay-hub/tests/test_spa_ordering.py
@@ -20,6 +20,7 @@ that nothing reads a value assigned later — and then move the markers.
from pathlib import Path
import pytest
+from spa_source import transport_source
STATIC = (Path(__file__).resolve().parents[1]
/ "src" / "meshbay_hub" / "static")
@@ -30,7 +31,7 @@ pytestmark = pytest.mark.skipif(
def _positions(*needles: str) -> list[int]:
- source = TRANSPORT.read_text()
+ source = transport_source()
out = []
for needle in needles:
idx = source.find(needle)
@@ -96,7 +97,7 @@ def test_the_ack_still_verifies_the_announced_node_key():
the client compares the two. Losing that check would leave the announcement
trusted on its own.
"""
- source = TRANSPORT.read_text()
+ source = transport_source()
assert "Node identity changed during the handshake" in source, (
"the challenge's node_pk must be checked against the ack's")
assert "verifyNodeSignature" in source, (
@@ -214,7 +215,7 @@ def test_admin_page_does_not_borrow_the_group_settings_state():
# waiting for every ack, 3.47 MB/s with 32 chunks in flight.
def test_the_uploader_keeps_several_chunks_in_flight():
- src = TRANSPORT.read_text()
+ src = transport_source()
body = src[src.index("async uploadFile("):]
body = body[:body.index("\n async ", 1)]
assert "UPLOAD_WINDOW" in body, "the send window is gone — uploads are serial again"
diff --git a/packages/meshbay-hub/tests/test_transfers.py b/packages/meshbay-hub/tests/test_transfers.py
index 9b65ca5c..b1bac4a0 100644
--- a/packages/meshbay-hub/tests/test_transfers.py
+++ b/packages/meshbay-hub/tests/test_transfers.py
@@ -14,6 +14,7 @@ import subprocess
from pathlib import Path
import pytest
+from spa_source import transport_source
STATIC = Path(__file__).resolve().parents[1] / "src" / "meshbay_hub" / "static"
TRANSFERS = STATIC / "transfers.js"
@@ -381,7 +382,7 @@ def test_asking_for_a_slot_on_a_dead_channel_does_not_throw(tmp_path):
module = tmp_path / "transport_lease.mjs"
# The real Lease, lifted out as text — the class is not exported, and a
# second copy of it here would agree with whatever it was copied from.
- src = (STATIC / "transport.js").read_text()
+ src = transport_source()
# From the constant the class depends on, not from the class: lifting only
# the class left LEASE_WATCHDOG_MS undefined, which the class reads the
# first time it arms its watchdog.
diff --git a/packages/meshbay-hub/tests/test_transport_contracts.py b/packages/meshbay-hub/tests/test_transport_contracts.py
index 76e81235..978c2e5d 100644
--- a/packages/meshbay-hub/tests/test_transport_contracts.py
+++ b/packages/meshbay-hub/tests/test_transport_contracts.py
@@ -19,7 +19,7 @@ import re
from pathlib import Path
import pytest
-from spa_source import search_source
+from spa_source import search_source, transport_source
STATIC = Path(__file__).resolve().parents[1] / "src" / "meshbay_hub" / "static"
TRANSPORT = STATIC / "transport.js"
@@ -54,7 +54,7 @@ pytestmark = pytest.mark.skipif(
@pytest.fixture(scope="module")
def transport():
- return TRANSPORT.read_text()
+ return transport_source()
@pytest.fixture(scope="module")
diff --git a/packages/meshbay-hub/tests/test_upload_controls_hidden.py b/packages/meshbay-hub/tests/test_upload_controls_hidden.py
index a2f93526..6316e449 100644
--- a/packages/meshbay-hub/tests/test_upload_controls_hidden.py
+++ b/packages/meshbay-hub/tests/test_upload_controls_hidden.py
@@ -23,6 +23,7 @@ import re
from pathlib import Path
import pytest
+from spa_source import transport_source
STATIC = Path(__file__).resolve().parents[1] / "src" / "meshbay_hub" / "static"
APP = STATIC / "app.js"
@@ -202,7 +203,7 @@ def test_a_change_reaches_people_already_connected(app):
list somebody clicks.
"""
assert "transport.onRootsChanged" in app
- transport = TRANSPORT.read_text(encoding="utf-8")
+ transport = transport_source()
assert "root_eject_ack" in transport, "nothing routes the node's notice"
@@ -219,7 +220,7 @@ def test_the_notice_also_answers_the_operators_own_request():
again on Chat's directory — where it meant the pane went on showing an
unsaved-looking draft after a save that had worked.
"""
- transport = TRANSPORT.read_text(encoding="utf-8")
+ transport = transport_source()
block = transport[transport.index("msg.type.endsWith('_ack')"):]
block = block[:block.index("_uploaders")]
assert "BROADCAST_ACK_TYPES" in block and "_replayBroadcast" in block, (
@@ -229,7 +230,7 @@ def test_the_notice_also_answers_the_operators_own_request():
# ── Changing it ─────────────────────────────────────────────────────────────
def test_changing_a_root_is_signed():
- transport = TRANSPORT.read_text(encoding="utf-8")
+ transport = transport_source()
for method in ("updateRoot", "ejectRoot", "plugRoot"):
body = transport[transport.index(f"async {method}("):]
body = body[:body.index("\n async ", 1)]
diff --git a/packages/meshbay-hub/tests/test_upload_seal_client.py b/packages/meshbay-hub/tests/test_upload_seal_client.py
index 63be6e38..bcda14c3 100644
--- a/packages/meshbay-hub/tests/test_upload_seal_client.py
+++ b/packages/meshbay-hub/tests/test_upload_seal_client.py
@@ -30,6 +30,7 @@ from meshbay_common.protocol import MNP
from meshbay_node.indexer.group_index import GroupIndex
from meshbay_node.roots import RootSet
from meshbay_node.transport.webrtc_server import WebRTCPeerSession
+from spa_source import transport_argv
STATIC = Path(__file__).resolve().parents[1] / "src" / "meshbay_hub" / "static"
PROBE = Path(__file__).resolve().parent / "harness" / "upload_seal_probe.mjs"
@@ -49,7 +50,7 @@ def _run_probe(payload: dict) -> dict:
f = Path(d) / "input.json"
f.write_text(json.dumps(payload))
proc = subprocess.run(
- ["node", str(PROBE), str(STATIC), str(f)],
+ ["node", str(PROBE), str(STATIC), str(f), transport_argv()],
capture_output=True, text=True, timeout=60,
)
if proc.returncode != 0 or not proc.stdout:
diff --git a/packages/meshbay-hub/tests/test_video_audio_track.py b/packages/meshbay-hub/tests/test_video_audio_track.py
index 6eb98ba0..54beca25 100644
--- a/packages/meshbay-hub/tests/test_video_audio_track.py
+++ b/packages/meshbay-hub/tests/test_video_audio_track.py
@@ -29,6 +29,7 @@ import subprocess
from pathlib import Path
import pytest
+from spa_source import transport_source
STATIC = Path(__file__).resolve().parents[1] / "src" / "meshbay_hub" / "static"
APP = STATIC / "video-player.js"
@@ -182,7 +183,7 @@ def test_no_track_is_ever_sent_to_a_node_that_did_not_offer_one():
makes that reachable, so the field has to be absent unless the caller was
given a real one.
"""
- src = TRANSPORT.read_text()
+ src = transport_source()
fn = src[src.index("requestStream(fileId"):]
fn = fn[:fn.index("\n /** Room for")]
assert "Number.isInteger(audioTrack)" in fn, \
diff --git a/packages/meshbay-hub/tests/test_video_seek.py b/packages/meshbay-hub/tests/test_video_seek.py
index 665970d0..94360656 100644
--- a/packages/meshbay-hub/tests/test_video_seek.py
+++ b/packages/meshbay-hub/tests/test_video_seek.py
@@ -35,6 +35,7 @@ from pathlib import Path
import node_tree
import pytest
+from spa_source import transport_source
STATIC = Path(__file__).resolve().parents[1] / "src" / "meshbay_hub" / "static"
APP = STATIC / "video-player.js"
@@ -98,7 +99,7 @@ def test_seeking_past_the_end_is_pulled_back(stream_fn):
def test_the_request_carries_it():
- text = TRANSPORT.read_text()
+ text = transport_source()
fn = text[text.index("requestStream(fileId"):]
fn = fn[:fn.index("\n }")]
assert "start" in fn, "requestStream cannot express a seek"
diff --git a/packages/meshbay-hub/tests/test_video_subtitles.py b/packages/meshbay-hub/tests/test_video_subtitles.py
index 7fdc9cda..5a4b215a 100644
--- a/packages/meshbay-hub/tests/test_video_subtitles.py
+++ b/packages/meshbay-hub/tests/test_video_subtitles.py
@@ -34,6 +34,7 @@ import subprocess
from pathlib import Path
import pytest
+from spa_source import transport_source
STATIC = Path(__file__).resolve().parents[1] / "src" / "meshbay_hub" / "static"
APP = STATIC / "video-player.js"
@@ -51,7 +52,7 @@ def app():
@pytest.fixture(scope="module")
def transport():
- return TRANSPORT.read_text()
+ return transport_source()
def _lift(src: str, name: str) -> str: