""" When the operator closes uploading, the controls go — both of them. There are two ways to put a file into a group and they are in different components: the Upload button in the Files toolbar, and the paperclip in the chat composer. Hiding one and forgetting the other is the obvious mistake, and the second one is the easier to forget because it does not look like an upload. Nothing here is a security property. **The node refuses the upload** — that is `test_member_upload_policy.py` in the node package. This is about not offering somebody a button whose only outcome is an error message. """ import re from pathlib import Path import pytest STATIC = Path(__file__).resolve().parents[1] / "src" / "meshbay_hub" / "static" APP = STATIC / "app.js" TRANSPORT = STATIC / "transport.js" pytestmark = pytest.mark.skipif(not APP.exists(), reason="SPA sources unavailable") @pytest.fixture(scope="module") def app() -> str: return APP.read_text(encoding="utf-8") def _component(app: str, name: str) -> str: start = app.index(f"\nfunction {name}(") end = app.find("\nfunction ", start + 1) return app[start:end if end != -1 else len(app)] # ── Both controls ─────────────────────────────────────────────────────────── def test_the_files_toolbar_hides_its_upload_button(app): page = _component(app, "GroupPage") toolbar = page[page.index("file-toolbar"):] toolbar = toolbar[:toolbar.index("group.mkdir")] assert "mayUpload &&" in toolbar, "the Upload button is offered regardless" def test_the_chat_composer_hides_its_paperclip(app): chat = _component(app, "ChatPanel") composer = chat[chat.index("chat-input-row"):] assert "mayUpload &&" in composer, ( "the chat attachment is the second way in and is still offered") def test_both_read_the_same_answer(app): """Two derivations would eventually disagree, and the disagreement would be one of them offering an upload the node refuses.""" page = _component(app, "GroupPage") assert re.search(r"const mayUpload = memberUpload \|\| isNodeAdmin;", page), ( "mayUpload is no longer derived in one place") assert "mayUpload=${mayUpload}" in page, "the chat panel is told separately" def test_the_operator_keeps_their_own_controls(app): page = _component(app, "GroupPage") assert "memberUpload || isNodeAdmin" in page, ( "turning uploads off would hide the operator's own upload button") # ── Learning the answer ───────────────────────────────────────────────────── def test_the_answer_comes_from_the_node(app): """Not from the hub, which has no say in what may be written to someone else's disk, and no way to be believed about it.""" page = _component(app, "GroupPage") assert "ack.member_upload !== false" in page, ( "the handshake ack is what carries this") assert "hubFetch" not in page[page.index("ack.member_upload") - 400: page.index("ack.member_upload")] def test_an_older_node_is_treated_as_permissive(app): """A node that predates the setting sends no such field. Reading a missing field as "off" would close every group on the older half of the network.""" page = _component(app, "GroupPage") assert "!== false" in page[page.index("ack.member_upload"): page.index("ack.member_upload") + 60] def test_a_change_reaches_people_already_connected(app): """The operator may be someone else entirely, changing it while you have the group open. A button that survives until the next reconnection is a button somebody presses.""" page = _component(app, "GroupPage") assert "transport.onUploadPolicy" in page transport = TRANSPORT.read_text(encoding="utf-8") assert "member_upload_ack" in transport, "nothing routes the node's notice" def test_the_notice_still_answers_the_operators_own_request(app): """The same message is both a broadcast and the reply to the request that caused it — returning early on it would leave that request hanging until it timed out.""" transport = TRANSPORT.read_text(encoding="utf-8") block = transport[transport.index("member_upload_ack"):] block = block[:block.index("index_sync")] assert "return" not in block # ── Changing it ───────────────────────────────────────────────────────────── def test_changing_it_is_signed(app): transport = TRANSPORT.read_text(encoding="utf-8") method = transport[transport.index("async setMemberUpload("):] method = method[:method.index("\n async ", 1)] assert "admin_challenge" in method and "_authorizeAdminOp" in method, ( "an unsigned instruction would let any member turn uploads back on") def test_only_the_operator_is_offered_the_setting(app): panel = _component(app, "GroupSettingsPanel") section = panel[panel.index("members.uploads_title") - 400: panel.index("members.uploads_title")] assert "isNodeAdmin && connected" in section