aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-hub/tests/test_mnp_1_0_node_compat.py
diff options
context:
space:
mode:
authorChristophe Besson <cbesson@gmail.com>2026-09-10 17:29:50 +0200
committerChristophe Besson <cbesson@gmail.com>2026-09-10 17:29:50 +0200
commit4753c67816c774323e3ab4efc76d3259e8ded40d (patch)
tree0ac0b8a3013aa483111ea249e3aa37ff49988891 /packages/meshbay-hub/tests/test_mnp_1_0_node_compat.py
parent1dcedc77083b908b7b3b431bad679a4813884355 (diff)
downloadmeshbay-4753c67816c774323e3ab4efc76d3259e8ded40d.tar.gz
refactor(spa): stop asking a node what version it is
`MNP_MIN_SUPPORTED` is the version this build speaks, so `check_version` refuses everything below it at the handshake. Every capability the client was gating on the node's version is therefore true of every peer it can reach: * `supportsSealedUpload` — an upload is sealed or it is not sent; * `supportsAppOps` — one `app_directories` op, and no `setVideoRoot` / `setAudioRoot` / `setPhotoRoots` wrappers behind it; * `supportsTransferSlots` and `Lease._skip()` — a lease is always real, so there is no branch where a transfer runs without one; * `legacyNode`, the read-only shared-directories table, and the two hints telling an operator their node is too old to configure an app. The version the node declares is still recorded, for diagnostics. Nothing branches on it, and the comment says so, because a field kept "just in case" is how the branches came back last time. `test_mnp_1_0_node_compat.py` goes with them: it existed to hold the fallbacks in place, and holding a fallback that cannot execute is how a suite starts lying. The two locale strings for those hints are removed from all ten catalogues. Hub suite 872 passed (test_sticky_header deselected — failing before this). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AsoWC3GmhNdwVFomW3QjH3
Diffstat (limited to 'packages/meshbay-hub/tests/test_mnp_1_0_node_compat.py')
-rw-r--r--packages/meshbay-hub/tests/test_mnp_1_0_node_compat.py144
1 files changed, 0 insertions, 144 deletions
diff --git a/packages/meshbay-hub/tests/test_mnp_1_0_node_compat.py b/packages/meshbay-hub/tests/test_mnp_1_0_node_compat.py
deleted file mode 100644
index b15018b..0000000
--- a/packages/meshbay-hub/tests/test_mnp_1_0_node_compat.py
+++ /dev/null
@@ -1,144 +0,0 @@
-"""
-The page ships before the nodes do.
-
-The SPA is served by the hub, so deploying the hub puts this version of the
-client in front of *every* node, including the ones still running MNP 1.0. That
-window is not a corner case — it is the normal state for as long as it takes an
-operator to update, and for a node someone else runs it may be indefinite.
-
-The failure mode is specific and quiet: a node logs an unknown message type and
-sends **nothing back**, so a control that speaks MNP 1.1 to it produces a
-thirty-second wait ending in a timeout, with nothing on screen to say the node
-simply cannot do this. Three of them were like that before these tests:
-
-* The shared-directories toggles, eject and plug have no older equivalent at
- all.
-* The per-app folder pickers spoke `app_directories`, where a 1.0 node
- understands `video_root` / `audio_root` / `photo_roots`.
-
-Source-reading, like the other SPA guards. What it cannot check is that the
-degraded path is pleasant; what it does check is that each of the three exists.
-"""
-
-import re
-from pathlib import Path
-
-import pytest
-
-STATIC = Path(__file__).resolve().parents[1] / "src" / "meshbay_hub" / "static"
-TRANSPORT = STATIC / "transport.js"
-FILES_APP = STATIC / "files-app.js"
-GROUP_PAGE = STATIC / "group-page.js"
-GROUP_SETTINGS = STATIC / "group-settings.js"
-
-pytestmark = pytest.mark.skipif(not TRANSPORT.exists(),
- reason="SPA sources unavailable")
-
-
-def _component(source: str, name: str) -> str:
- start = source.index(f"\nfunction {name}(")
- end = source.find("\nfunction ", start + 1)
- return source[start:end if end != -1 else len(source)]
-
-
-# ── Knowing which node you are talking to ───────────────────────────────────
-
-def test_the_client_keeps_the_version_it_checked():
- """
- `_checkNodeVersion` parsed the node's version and threw it away, so nothing
- downstream could ask. Refusing to connect is not the only thing a version
- is good for.
- """
- source = TRANSPORT.read_text(encoding="utf-8")
- assert "this._nodeVersion = String(reply.v" in source
- assert "get supportsAppOps()" in source
-
-
-def test_the_capability_reads_the_version_rather_than_guessing():
- """
- Inferring it from whether some field happens to be present is how two
- unrelated things end up coupled — the flag would flip because a payload
- changed shape for another reason entirely.
- """
- source = TRANSPORT.read_text(encoding="utf-8")
- getter = source[source.index("get supportsAppOps()"):]
- getter = getter[:getter.index("\n }") + 4]
- assert "_nodeVersion" in getter
- assert "1" in getter, "no version comparison in the capability check"
-
-
-# ── The three degraded paths ────────────────────────────────────────────────
-
-def test_app_directories_fall_back_to_the_three_older_messages():
- """
- Videos, Music and Photos each had their own message before the generic op,
- and those still work — so an operator on an un-upgraded node keeps the
- ability they had rather than being handed a control that times out.
- """
- source = TRANSPORT.read_text(encoding="utf-8")
- legacy = source[source.index("async setAppDirectoriesLegacy("):]
- legacy = legacy[:legacy.index("\n async ", 1)]
- for call in ("setPhotoRoots", "setVideoRoot", "setAudioRoot"):
- assert call in legacy, f"{call} is not reachable on the older path"
-
- panel = _component(GROUP_SETTINGS.read_text(encoding="utf-8"),
- "GroupSettingsPanel")
- assert "transport.supportsAppOps" in panel, (
- "the settings page sends the 1.1 message unconditionally")
-
-
-def test_the_older_path_refuses_what_it_cannot_carry():
- """
- `video_root` and `audio_root` hold one folder. Sending several would store
- the first and drop the rest silently, which is worse than refusing — the
- operator would see a saved setting that is not what they chose.
- """
- source = TRANSPORT.read_text(encoding="utf-8")
- legacy = source[source.index("async setAppDirectoriesLegacy("):]
- legacy = legacy[:legacy.index("\n async ", 1)]
- assert "clean.length > 1" in legacy
- assert "throw new Error" in legacy
-
-
-def test_root_management_is_read_only_against_an_older_node():
- """
- Unlike the app directories, `writable`, `removable`, eject and plug have no
- older equivalent to route to. The controls are shown without being
- offered, with the reason, rather than accepting a click that goes nowhere.
- """
- panel = _component(GROUP_SETTINGS.read_text(encoding="utf-8"),
- "GroupSettingsPanel")
- table_call = panel[panel.index("<${SharedDirectoriesTable}"):]
- table_call = table_call[:table_call.index("/>")]
- assert "readOnly=" in table_call
- assert "nodeSupportsAppOps" in table_call
- assert "settings_node.roots_node_too_old" in panel, (
- "nothing says why the controls are inert")
-
-
-def test_chat_settings_are_hidden_rather_than_routed():
- """
- Chat's directory and link-preview switch are new in 1.1 with nothing
- before them, so there is no older message to fall back to.
- """
- panel = _component(GROUP_SETTINGS.read_text(encoding="utf-8"),
- "GroupSettingsPanel")
- assert "settings_node.app_node_too_old" in panel
-
-
-# ── Reading an older node's handshake ───────────────────────────────────────
-
-def test_the_ack_is_read_in_both_shapes(app=None):
- """
- A 1.0 ack has `video_root` and no `video_directories`, and no
- `chat_link_preview` at all. Reading a missing plural as "nothing
- configured" empties a working Videos tab; reading a missing switch as off
- silently changes what a group's chat does.
- """
- page = GROUP_PAGE.read_text(encoding="utf-8")
- block = page[page.index("setAppDirectories({"):]
- block = block[:block.index("setNodeSupportsAppOps")]
- for legacy in ("ack.video_root", "ack.audio_root", "ack.photo_roots"):
- assert legacy in block, f"{legacy} is not read as a fallback"
- assert "ack.chat_link_preview !== false" in page, (
- "an absent link-preview switch must read as on, not off")