From 328b01a2dd545d70a078db8df1e91b02d65bfc9c Mon Sep 17 00:00:00 2001 From: Christophe Besson Date: Wed, 23 Sep 2026 22:25:28 +0200 Subject: test: read the WebRTC transport's source as a set of files Source-reading tests take their text from node_source (node) and node_tree (hub): webrtc_server.py plus anything under transport/webrtc/, so a check for something's absence keeps reading the code it guards if that code moves. test_node_source_scope holds the boundary. Co-Authored-By: Claude Opus 5.5 --- packages/meshbay-node/tests/node_source.py | 52 ++++++++++++++++++++++ .../meshbay-node/tests/test_app_directories.py | 7 +-- .../meshbay-node/tests/test_chat_encryption.py | 4 +- .../meshbay-node/tests/test_disk_io_off_loop.py | 31 +++++++------ packages/meshbay-node/tests/test_hwaccel.py | 5 +-- .../meshbay-node/tests/test_node_source_scope.py | 35 +++++++++++++++ packages/meshbay-node/tests/test_roster_pairing.py | 9 ++-- .../tests/test_security_regressions.py | 25 +++++------ packages/meshbay-node/tests/test_task_lifetime.py | 51 +++++++-------------- .../tests/test_transport_wire_parity.py | 30 +++++++------ .../meshbay-node/tests/test_upload_size_cap.py | 4 +- 11 files changed, 160 insertions(+), 93 deletions(-) create mode 100644 packages/meshbay-node/tests/node_source.py create mode 100644 packages/meshbay-node/tests/test_node_source_scope.py (limited to 'packages/meshbay-node') diff --git a/packages/meshbay-node/tests/node_source.py b/packages/meshbay-node/tests/node_source.py new file mode 100644 index 0000000..df63773 --- /dev/null +++ b/packages/meshbay-node/tests/node_source.py @@ -0,0 +1,52 @@ +""" +The node's source text, for the tests that have to read it. + +A test that reads source to prove something is *absent* — no bare +`ensure_future`, no exception text sent to a peer, no GEK unwrapped over MNP — +goes on passing when the code it guards moves to another file: it simply stops +looking at that code. So every such test takes its text from here, and the +file sets below are derived from the tree rather than listed, so that a module +added later is read without anybody having to remember it. +""" + +import inspect +import textwrap +from pathlib import Path + +SRC = Path(__file__).resolve().parents[1] / "src" / "meshbay_node" +TRANSPORT = SRC / "transport" + +# The WebRTC transport is `webrtc_server.py` and whatever has been split out of +# it into `transport/webrtc/`. The other modules beside them are not part of it +# and are not held to its rules by these tests: QUIC is a transport of its own +# and not at parity (docs/MESHBAY_DESIGN.md §15.3), and `wire.py`, `tls_cert.py` +# and the ICE helpers serve both or neither. +WEBRTC_PACKAGE = TRANSPORT / "webrtc" + + +def webrtc_files() -> list[Path]: + """Every module of the WebRTC transport, however it is split up.""" + files = [TRANSPORT / "webrtc_server.py"] + if WEBRTC_PACKAGE.is_dir(): + files += sorted(p for p in WEBRTC_PACKAGE.rglob("*.py") + if "__pycache__" not in p.parts) + return files + + +def webrtc_source() -> str: + return "\n".join(p.read_text(encoding="utf-8") for p in webrtc_files()) + + +def session_source() -> str: + """The body of WebRTCPeerSession, including every class it is built from.""" + from meshbay_node.transport.webrtc_server import WebRTCPeerSession + + return "\n".join(inspect.getsource(k) for k in WebRTCPeerSession.__mro__ + if k.__module__.startswith("meshbay_node.")) + + +def session_method(name: str) -> str: + """One method of WebRTCPeerSession, wherever it is defined.""" + from meshbay_node.transport.webrtc_server import WebRTCPeerSession + + return textwrap.dedent(inspect.getsource(getattr(WebRTCPeerSession, name))) diff --git a/packages/meshbay-node/tests/test_app_directories.py b/packages/meshbay-node/tests/test_app_directories.py index 955a769..1b8d1e0 100644 --- a/packages/meshbay-node/tests/test_app_directories.py +++ b/packages/meshbay-node/tests/test_app_directories.py @@ -32,6 +32,7 @@ from meshbay_node.indexer.group_index import GroupIndex from meshbay_node.roots import RootSet from meshbay_node.roster import Roster from meshbay_node.transport.webrtc_server import WebRTCPeerSession +from node_source import session_method from conftest import one_root @@ -423,11 +424,7 @@ async def test_the_ack_has_no_list_of_applications_of_its_own(): """ from meshbay_node.daemon import NodeDaemon - source = Path(WebRTCPeerSession.__module__.replace(".", "/")) - text = (Path(__file__).resolve().parents[2] / "meshbay-node" / "src" - / f"{source}.py").read_text(encoding="utf-8") - body = text[text.index("def _app_directories_ack"):] - body = body[:body.index("\n def ", 1)] + body = session_method("_app_directories_ack") for app in NodeDaemon.APP_DIR_KEYS: assert app not in body, ( f"the ack names {app!r}; it must read the context's own keys") diff --git a/packages/meshbay-node/tests/test_chat_encryption.py b/packages/meshbay-node/tests/test_chat_encryption.py index e286306..da8f5dd 100644 --- a/packages/meshbay-node/tests/test_chat_encryption.py +++ b/packages/meshbay-node/tests/test_chat_encryption.py @@ -30,6 +30,7 @@ from meshbay_node.chat import FORMAT_SEALED_V1, ChatStore from meshbay_node.indexer.group_index import GroupIndex from meshbay_node.roster import open_roster from meshbay_node.transport.webrtc_server import WebRTCPeerSession +from node_source import webrtc_source from conftest import one_root @@ -258,8 +259,7 @@ async def test_there_is_no_setting_that_re_enables_plaintext(node): assert session.sent[-1]["type"] == "error" assert await node["chat"].message_count() == 0 - source = (Path(__file__).parent.parent / "src" / "meshbay_node" - / "transport" / "webrtc_server.py").read_text(encoding="utf-8") + source = webrtc_source() assert 'get("chat_encrypted"' not in source, ( "nothing may read a chat_encrypted setting — there is no switch") diff --git a/packages/meshbay-node/tests/test_disk_io_off_loop.py b/packages/meshbay-node/tests/test_disk_io_off_loop.py index ceaf565..f98eceb 100644 --- a/packages/meshbay-node/tests/test_disk_io_off_loop.py +++ b/packages/meshbay-node/tests/test_disk_io_off_loop.py @@ -36,6 +36,7 @@ from meshbay_node.roots import Root, RootSet from meshbay_node.transfers import LeaselessReads from meshbay_node.transport import webrtc_server from meshbay_node.transport.webrtc_server import WebRTCPeerSession +from node_source import webrtc_files from conftest import one_root, sealed_upload @@ -238,9 +239,10 @@ def test_no_handler_touches_the_disk_on_the_loop(): The whole class, not the calls that were fixed. Every measured test above exercises a handler that exists today; a new one - that stats a root inline would pass all of them. So this walks the module's - syntax tree instead and fails on any filesystem call outside the few - functions written to be run through `off_disk`. + that stats a root inline would pass all of them. So this walks the syntax + tree of every module of the WebRTC transport instead and fails on any + filesystem call outside the few functions written to be run through + `off_disk`. `entry_abs_path` and `safe_subdir` are in the list because both are `Path.resolve()` underneath, and a resolve is syscalls whatever it is @@ -274,14 +276,17 @@ def test_no_handler_touches_the_disk_on_the_loop(): found.append(f"{owner} calls {name}() at line {child.lineno}") visit(child, owner) - tree = ast.parse(Path(webrtc_server.__file__).read_text()) - for node in tree.body: - if isinstance(node, ast.ClassDef): - for member in node.body: - if isinstance(member, (ast.FunctionDef, ast.AsyncFunctionDef)): - visit(member, member.name) - elif isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)): - visit(node, node.name) + for path in webrtc_files(): + tree = ast.parse(path.read_text(encoding="utf-8")) + before = len(found) + for node in tree.body: + if isinstance(node, ast.ClassDef): + for member in node.body: + if isinstance(member, (ast.FunctionDef, ast.AsyncFunctionDef)): + visit(member, member.name) + elif isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)): + visit(node, node.name) + found[before:] = [f"{path.name}: {f}" for f in found[before:]] assert not found, ( "filesystem calls made from the event loop:\n " @@ -362,8 +367,8 @@ def test_the_scratch_read_is_only_ever_reached_on_a_thread(): handler. Passed to `asyncio.to_thread` it appears in the syntax tree as a name; called inline it appears as a call, which is what this refuses. """ - tree = ast.parse(Path(webrtc_server.__file__).read_text()) - direct = [n.lineno for n in ast.walk(tree) + direct = [f"{path.name}:{n.lineno}" for path in webrtc_files() + for n in ast.walk(ast.parse(path.read_text(encoding="utf-8"))) if isinstance(n, ast.Call) and isinstance(n.func, ast.Name) and n.func.id == "_read_scratch_capped"] diff --git a/packages/meshbay-node/tests/test_hwaccel.py b/packages/meshbay-node/tests/test_hwaccel.py index a79d784..0e6c6ab 100644 --- a/packages/meshbay-node/tests/test_hwaccel.py +++ b/packages/meshbay-node/tests/test_hwaccel.py @@ -15,11 +15,10 @@ real file. import re import sys -from pathlib import Path import pytest from meshbay_node import hwaccel -from meshbay_node.transport import webrtc_server +from node_source import webrtc_source from conftest import needs_subprocess @@ -94,7 +93,7 @@ def test_every_encoder_produces_what_the_node_announces(): and every mode is checked against it. Changing the arguments of one encoder without the other, or changing either without the announced string, fails. """ - source = Path(webrtc_server.__file__).read_text(encoding="utf-8") + source = webrtc_source() announced = set(re.findall(r"avc1\.([0-9a-f]{6})", source)) assert announced == {"640029"}, ( "the streaming path announces a codec string this test does not know " diff --git a/packages/meshbay-node/tests/test_node_source_scope.py b/packages/meshbay-node/tests/test_node_source_scope.py new file mode 100644 index 0000000..e5a86ff --- /dev/null +++ b/packages/meshbay-node/tests/test_node_source_scope.py @@ -0,0 +1,35 @@ +""" +The file set `node_source` hands to the source-reading tests has to contain +the code those tests are about. + +It is derived from the tree — `webrtc_server.py` and everything under +`transport/webrtc/` — so that a module split out later is read without anybody +adding it to a list. That only holds while the code actually lives there: a +class the session is built from, put anywhere else, would leave every +"nothing here does X" test in this suite silently not reading it. +""" + +import inspect +from pathlib import Path + +from meshbay_node.transport.webrtc_server import WebRTCPeerSession, WebRTCTransport +from node_source import TRANSPORT, webrtc_files + + +def test_every_class_the_transport_is_built_from_is_in_scope(): + scope = {p.resolve() for p in webrtc_files()} + for cls in (WebRTCPeerSession, WebRTCTransport): + for klass in cls.__mro__: + if not klass.__module__.startswith("meshbay_node."): + continue + where = Path(inspect.getsourcefile(klass)).resolve() + assert where in scope, ( + f"{klass.__qualname__} ({cls.__name__}) is defined in " + f"{where.relative_to(TRANSPORT.parent)}, which the source-reading " + f"tests do not read — move it under transport/webrtc/") + + +def test_the_scope_is_not_empty(): + files = webrtc_files() + assert TRANSPORT / "webrtc_server.py" in files + assert all(p.exists() for p in files) diff --git a/packages/meshbay-node/tests/test_roster_pairing.py b/packages/meshbay-node/tests/test_roster_pairing.py index 91191c1..038f12c 100644 --- a/packages/meshbay-node/tests/test_roster_pairing.py +++ b/packages/meshbay-node/tests/test_roster_pairing.py @@ -23,6 +23,7 @@ from meshbay_common.join import ROLE_MEMBER, ROLE_OPERATOR, join_transcript from meshbay_node.indexer.group_index import GroupIndex from meshbay_node.roster import Roster, hash_code, normalize_code from meshbay_node.transport.webrtc_server import WebRTCPeerSession +from node_source import session_method, webrtc_source from conftest import one_root @@ -568,9 +569,7 @@ def test_challenge_carries_node_pk_in_source(): Belt and braces for the above: the field must be in the message the node builds, whatever the surrounding handshake does. """ - source = (Path(__file__).parent.parent - / "src" / "meshbay_node" / "transport" - / "webrtc_server.py").read_text(encoding="utf-8") + source = webrtc_source() challenge = source[source.find("MNP.HANDSHAKE_CHALLENGE,"):] challenge = challenge[:challenge.find("})")] assert "node_pk" in challenge, ( @@ -899,9 +898,7 @@ def test_admin_authority_is_never_fetched_from_the_hub(): """ src = Path(__file__).parent.parent / "src" / "meshbay_node" - verifier = (src / "transport" / "webrtc_server.py").read_text(encoding="utf-8") - body = verifier[verifier.index("async def _verify_admin_sig"):] - body = body[:body.index("\n def ", 1)] + body = session_method("_verify_admin_sig") assert "operator_pks" in body, "the roster is where authority comes from" # Past the docstring: it names what was removed on purpose, so a reader knows # not to put it back. What must not reappear is code. diff --git a/packages/meshbay-node/tests/test_security_regressions.py b/packages/meshbay-node/tests/test_security_regressions.py index 7010523..7ace21b 100644 --- a/packages/meshbay-node/tests/test_security_regressions.py +++ b/packages/meshbay-node/tests/test_security_regressions.py @@ -18,6 +18,7 @@ from meshbay_common.crypto import generate_gek from meshbay_node.indexer.group_index import GroupIndex from meshbay_node.roots import RootSet from meshbay_node.transport.webrtc_server import WebRTCPeerSession +from node_source import TRANSPORT, webrtc_files, webrtc_source from conftest import one_root, opened_ack, sealed_upload @@ -461,9 +462,7 @@ def test_no_member_can_hand_the_node_key_material(tmp_path): "key material over MNP (C5b)" ) - source = (Path(__file__).parent.parent - / "src" / "meshbay_node" / "transport" - / "webrtc_server.py").read_text(encoding="utf-8") + source = webrtc_source() assert "_do_gek_bundle_store" not in source assert "_admin_exec_bundle_store" not in source @@ -496,8 +495,7 @@ def test_gek_auto_activation_is_gone(): Since the operator's X25519 public key is public, any member could hand the node a GEK of their choosing. Nothing arriving over MNP may set a live GEK. """ - source = (Path(__file__).parent.parent / "src" / "meshbay_node" - / "transport" / "webrtc_server.py").read_text(encoding="utf-8") + source = webrtc_source() assert "_try_activate_gek" not in source assert 'unwrap_gek_aes' not in source, ( "the MNP path must not unwrap a GEK — activation is local-admin only" @@ -709,8 +707,7 @@ def test_peer_errors_do_not_leak_internals(): client needs to know why it was refused, and those strings are authored for that purpose. The check targets the generic `except Exception as e` path. """ - source = (Path(__file__).parent.parent / "src" / "meshbay_node" - / "transport" / "webrtc_server.py").read_text(encoding="utf-8") + source = webrtc_source() assert '"detail": str(e)' not in source, ( "generic exception text relayed to peer — use a fixed message" ) @@ -759,9 +756,10 @@ def test_no_transport_ships_media_outside_the_aead(): "the constant outliving the handlers is how a deleted endpoint keeps " "looking like part of the wire contract") - root = Path(__file__).parent.parent / "src" / "meshbay_node" / "transport" - for name in ("webrtc_server.py", "quic_server.py", "quic_client.py"): - source = (root / name).read_text(encoding="utf-8") + quic = [TRANSPORT / "quic_server.py", TRANSPORT / "quic_client.py"] + for path in webrtc_files() + quic: + name = path.name + source = path.read_text(encoding="utf-8") # Word boundaries: `_stream_segments` and `STREAM_SEGMENT_SIZE` belong # to the live `stream_data` path, which is encrypted and stays. assert not re.search(r"\bstream_seg\b", source), ( @@ -780,11 +778,10 @@ def test_ffmpeg_never_blocks_the_event_loop(): """ import ast - source = (Path(__file__).parent.parent / "src" / "meshbay_node" - / "transport" / "webrtc_server.py").read_text(encoding="utf-8") - tree = ast.parse(source) + source = webrtc_source() blocking = [ - node for node in ast.walk(tree) + node for path in webrtc_files() + for node in ast.walk(ast.parse(path.read_text(encoding="utf-8"))) if isinstance(node, ast.Call) and isinstance(node.func, ast.Attribute) and node.func.attr == "run" diff --git a/packages/meshbay-node/tests/test_task_lifetime.py b/packages/meshbay-node/tests/test_task_lifetime.py index 133c030..a8445fa 100644 --- a/packages/meshbay-node/tests/test_task_lifetime.py +++ b/packages/meshbay-node/tests/test_task_lifetime.py @@ -25,33 +25,28 @@ file. """ import re -from pathlib import Path import pytest - -SERVER = (Path(__file__).resolve().parents[1] / "src" / "meshbay_node" - / "transport" / "webrtc_server.py") +from node_source import TRANSPORT, session_method, session_source, webrtc_source pytestmark = pytest.mark.skipif( - not SERVER.exists(), reason="the node sources are not available") + not TRANSPORT.exists(), reason="the node sources are not available") @pytest.fixture(scope="module") def source(): - return SERVER.read_text(encoding="utf-8") + return webrtc_source() @pytest.fixture(scope="module") -def session(source): - """The body of WebRTCPeerSession.""" - i = source.index("class WebRTCPeerSession") - return source[i:source.index("\nclass WebRTCTransport")] +def session(): + """The body of WebRTCPeerSession, and of every class it is built from.""" + return session_source() def test_the_session_keeps_a_reference_to_what_it_starts(session): assert "self._tasks: set[asyncio.Task] = set()" in session - spawn = session[session.index("def _spawn("):] - spawn = spawn[:spawn.index("\n def ", 1)] + spawn = session_method("_spawn") assert "self._tasks.add(task)" in spawn, "the reference is what keeps it alive" assert "self._tasks.discard(" in spawn and "add_done_callback(" in spawn, ( "without this the set grows for the life of the session") @@ -72,8 +67,7 @@ def test_nothing_in_the_session_is_fired_and_forgotten(session): def test_the_stream_still_takes_a_slot_for_its_whole_life(session): """The leak is only interesting because the slot is held this way.""" - body = session[session.index("async def _stream_video(self"):] - body = body[:body.index("\n async def ", 1)] + body = session_method("_stream_video") assert "async with sem:" in body @@ -83,13 +77,11 @@ def test_closing_a_session_releases_its_tasks(session): The cancelling itself lives in shutdown_tasks, which the state handler also uses; close() is the variant that additionally shuts the peer connection. """ - close = session[session.index("async def close(self)"):] - close = close[:close.index("\n\n")] if "\n\n" in close else close + close = session_method("close") assert "shutdown_tasks()" in close assert "self._pc.close()" in close - fn = session[session.index("async def shutdown_tasks(self)"):] - fn = fn[:fn.index("\n async def ", 1)] + fn = session_method("shutdown_tasks") assert "_stop_stream()" in fn assert "task.cancel()" in fn assert "gather" in fn, "cancelling without awaiting does not run the exits" @@ -129,8 +121,7 @@ def test_a_new_request_retires_the_previous_stream(session): assert "self._spawn(self._replace_stream(msg))" in session, ( "the stream request must go through the path that retires the old one") - fn = session[session.index("async def _replace_stream(self"):] - fn = fn[:fn.index("\n async def ", 1)] + fn = session_method("_replace_stream") assert "self._stop_stream()" in fn assert "await asyncio.wait_for(asyncio.shield(prev)" in fn, ( "the slot comes back when the old task exits its `async with sem` — " @@ -173,8 +164,7 @@ def test_losing_the_peer_stops_its_work(source): def test_shutdown_is_separate_from_closing_the_connection(session): """The state handler runs while aiortc is already tearing pc down.""" - fn = session[session.index("async def shutdown_tasks(self)"):] - fn = fn[:fn.index("\n async def ", 1)] + fn = session_method("shutdown_tasks") assert "self._pc.close()" not in fn, ( "calling pc.close() from the state handler re-enters the teardown") assert "task.cancel()" in fn and "gather" in fn @@ -186,8 +176,7 @@ def test_a_dead_channel_is_noticed_while_waiting_not_after(source): Waiting the whole budget on a channel that is already shut is the slot being held for nothing, which is what the log above shows. """ - fn = source[source.index("async def _await_stream_credit"):] - fn = fn[:fn.index("\n async def ", 1)] + fn = session_method("_await_stream_credit") before = fn[:fn.index("wait_for")] assert 'readyState != "open"' in before, ( "the channel must be checked before the wait, not only after it") @@ -221,8 +210,7 @@ def test_the_pipes_are_drained_before_waiting_on_ffmpeg(source): Which is the reported failure exactly: one video fine, the next hanging, the one after refused. """ - fn = source[source.index("async def _stream_video_inner"):] - fn = fn[:fn.index("\n def _send(")] + fn = session_method("_stream_video_inner") finally_block = fn[fn.index("finally:"):] assert "proc.kill()" in finally_block @@ -234,8 +222,7 @@ def test_the_pipes_are_drained_before_waiting_on_ffmpeg(source): def test_releasing_the_slot_does_not_depend_on_ffmpeg_behaving(source): """SIGKILL has already been sent; the OS will reap it either way.""" - fn = source[source.index("async def _stream_video_inner"):] - fn = fn[:fn.index("\n def _send(")] + fn = session_method("_stream_video_inner") tail = fn[fn.index("wait_for(proc.wait()"):] assert "except Exception" in tail, ( "a timeout waiting for ffmpeg must not stop the slot coming back") @@ -253,13 +240,7 @@ def test_chunks_wait_for_room_on_the_channel(session): work through the rest — which is what "stuck at 1 MB" looks like, one chunk being exactly one megabyte. """ - start = session.index("async def _do_file_request") - # Up to whatever the next member is. This used to end at - # "\n def _do_stream_segment" — a neighbour removed in MNP 2.0 — and an - # `index()` on a name that no longer exists fails the test for a reason - # that has nothing to do with what it is about. - nxt = re.search(r"\n (?:@|(?:async )?def )", session[start:]) - fn = session[start:start + nxt.start()] if nxt else session[start:] + fn = session_method("_do_file_request") assert "DOWNLOAD_BUFFER_HIGH" in fn, "the send buffer has to be watched" assert "await asyncio.sleep" in fn, "waiting for room is the point" assert 'readyState != "open"' in fn, ( diff --git a/packages/meshbay-node/tests/test_transport_wire_parity.py b/packages/meshbay-node/tests/test_transport_wire_parity.py index 6e74b28..cd31fb4 100644 --- a/packages/meshbay-node/tests/test_transport_wire_parity.py +++ b/packages/meshbay-node/tests/test_transport_wire_parity.py @@ -23,6 +23,7 @@ from meshbay_common.protocol import MNP, file_chunk_plaintext, file_chunk_wire from meshbay_node.indexer import DirectoryIndexer from meshbay_node.transport import quic_server, webrtc_server from meshbay_node.transport.wire import index_sync_message +from node_source import webrtc_source from conftest import one_root @@ -41,23 +42,27 @@ def shared_dir(tmp_path): return d +def _transports(): + """Each transport's whole source: WebRTC is several modules, QUIC is one.""" + return [("the WebRTC transport", webrtc_source()), + (quic_server.__name__, inspect.getsource(quic_server))] + + def test_both_transports_use_the_one_chunk_encoder(): """Neither server may encrypt a chunk itself.""" - for module in (webrtc_server, quic_server): - source = inspect.getsource(module) - assert "file_chunk_wire" in source, f"{module.__name__} bypasses the shared encoder" + for name, source in _transports(): + assert "file_chunk_wire" in source, f"{name} bypasses the shared encoder" assert "encrypt_chunk_aes(" not in source, ( - f"{module.__name__} encrypts a chunk on its own — that is how the two " + f"{name} encrypts a chunk on its own — that is how the two " f"copies diverged the first time") assert "chunk_key_aes(" not in source, ( - f"{module.__name__} derives a chunk key on its own") + f"{name} derives a chunk key on its own") def test_both_transports_use_the_one_index_builder(): - for module in (webrtc_server, quic_server): - source = inspect.getsource(module) + for name, source in _transports(): assert "index_sync_message" in source, ( - f"{module.__name__} builds index_sync itself") + f"{name} builds index_sync itself") assert "index_b64" not in inspect.getsource(quic_server), ( "QUIC is serializing the index again — that was the fork") @@ -68,13 +73,12 @@ def test_neither_transport_seals_by_hand(): A server reaching for AESGCM or HKDF directly is a second envelope waiting to disagree with the first about a nonce length, an info string or an AAD. """ - for module in (webrtc_server, quic_server): - source = inspect.getsource(module) - assert "seal(" in source, f"{module.__name__} sends an unsealed ack" + for name, source in _transports(): + assert "seal(" in source, f"{name} sends an unsealed ack" assert "AESGCM(" not in source, ( - f"{module.__name__} builds its own AEAD instead of using groupbox") + f"{name} builds its own AEAD instead of using groupbox") assert "HKDF(" not in source, ( - f"{module.__name__} derives its own subkey instead of using groupbox") + f"{name} derives its own subkey instead of using groupbox") def test_the_daemon_does_not_build_an_index_message_itself(): diff --git a/packages/meshbay-node/tests/test_upload_size_cap.py b/packages/meshbay-node/tests/test_upload_size_cap.py index cc04eba..dbb14d9 100644 --- a/packages/meshbay-node/tests/test_upload_size_cap.py +++ b/packages/meshbay-node/tests/test_upload_size_cap.py @@ -25,6 +25,7 @@ from meshbay_node.transport.webrtc_server import ( WebRTCPeerSession, WebRTCTransport, ) +from node_source import webrtc_source def _cfg(tmp_path: Path, body: str): @@ -115,8 +116,7 @@ def test_the_handler_reads_it_rather_than_the_constant(): is already in flight — which it does only if the handler asks the context each time instead of closing over the constant. """ - src = (Path(__file__).resolve().parents[1] / "src" / "meshbay_node" - / "transport" / "webrtc_server.py").read_text(encoding="utf-8") + src = webrtc_source() i = src.index("Upload exceeds size limit") check = src[src.rindex("if state.bytes", 0, i):i] assert "_max_upload_bytes()" in check, ( -- cgit v1.2.3