aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-hub/tests
diff options
context:
space:
mode:
authorChristophe Besson <cbesson@gmail.com>2026-09-07 00:48:55 +0200
committerChristophe Besson <cbesson@gmail.com>2026-09-07 00:48:55 +0200
commita36080742287bad8f657f688d7fec0022dd696a1 (patch)
tree3da5c9eedb9c0d2f22967ebf61bd777e32c7bb5e /packages/meshbay-hub/tests
parent6828e64a256caea3c4e51829ae1aa09dfa725324 (diff)
downloadmeshbay-a36080742287bad8f657f688d7fec0022dd696a1.tar.gz
feat(client): HelloWorld, the reference application
Every other test of the plugin architecture reads source for the *absence* of app names. That proves nobody wrote a special case for Videos; it cannot prove a genuinely new application works, because there was no new application. This is one. It stores directories, appears as a tab, has a settings pane and lists files, and the node has never heard its name outside a single allow-list entry. Two files and one registry line, which is the claim `docs/refactor-groups.md` §4.1 makes. It ships hidden behind `?dev=1` (`dev: true` in the registry, the same opt-in shape as transport.js's `?trace=1`). Registering it normally would put a toy app in every operator's group; not registering it would prove nothing, since registration is exactly what is claimed to be sufficient. **Adding it found two places where the claim was nearly true rather than true, and both are fixed by making the code less app-specific:** `group-settings.js` fell back to the whole registry when a group had no `enabled_apps` yet — which would have turned a hidden app on for everyone. It asks `availableApps()` now. `group-page.js` wrote out `videoDirectories` / `musicDirectories` / `photoDirectories` by hand, so a fifth app would have needed that file edited. It derives `<key>Directories` from the registry. Neither was found by reading; both were found by adding the app, which is the whole reason it exists. Verified in a real Electron window as well as by the tests: hidden by default, present with the flag, offered its own settings section, and listing exactly the files under its configured folder and its subfolders — not the ones beside it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011pvMdvLBG92jyhvD5pD6us
Diffstat (limited to 'packages/meshbay-hub/tests')
-rw-r--r--packages/meshbay-hub/tests/test_helloworld_proves_the_plugin_claim.py159
-rw-r--r--packages/meshbay-hub/tests/test_hook_ordering.py1
-rw-r--r--packages/meshbay-hub/tests/test_transport_contracts.py2
3 files changed, 162 insertions, 0 deletions
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
new file mode 100644
index 0000000..a1baf92
--- /dev/null
+++ b/packages/meshbay-hub/tests/test_helloworld_proves_the_plugin_claim.py
@@ -0,0 +1,159 @@
+"""
+The reference application, and what it is for.
+
+`docs/refactor-groups.md` claims that adding an application costs a registry
+entry and the app's own files — no op, no MNP message, no route, no edit to the
+pages that render it. Every other test of that claim reads source for the
+*absence* of app names, which proves nobody wrote a special case for Videos. It
+cannot prove that a genuinely new app works, because there was no new app.
+
+HelloWorld is one. It stores directories, appears as a tab, has a settings pane
+and lists files, and the node has never heard its name outside one allow-list
+entry. The assertions below are the claim, stated as things that must stay true
+of a file that was not written for it.
+
+It ships hidden behind `?dev=1` (apps.js's `dev: true`). Registering it
+normally would put a toy app in every operator's group; not registering it
+would prove nothing, since registration is exactly the thing being claimed as
+sufficient.
+
+**Two honest exceptions**, both found *by* adding it and both fixed by making
+the code less app-specific rather than more:
+
+* `group-settings.js` fell back to the whole registry when a group had no
+ `enabled_apps` yet, which would have turned a hidden app on for everyone. It
+ asks `availableApps()` now.
+* `group-page.js` wrote out `videoDirectories` / `musicDirectories` /
+ `photoDirectories` by hand. It derives `<key>Directories` from the registry
+ now, which is what made the claim true rather than nearly true.
+"""
+
+import re
+from pathlib import Path
+
+import pytest
+
+STATIC = Path(__file__).resolve().parents[1] / "src" / "meshbay_hub" / "static"
+NODE_SRC = (Path(__file__).resolve().parents[2] / "meshbay-node" / "src"
+ / "meshbay_node")
+
+APP = STATIC / "helloworld-app.js"
+SETTINGS = STATIC / "helloworld-app-settings.js"
+
+pytestmark = pytest.mark.skipif(not APP.exists(),
+ reason="the reference app is not in this checkout")
+
+
+def _code_only(source: str) -> str:
+ source = re.sub(r"/\*.*?\*/", "", source, flags=re.S)
+ return re.sub(r"^\s*//.*$", "", source, flags=re.M)
+
+
+# ── The claim ────────────────────────────────────────────────────────────────
+
+@pytest.mark.parametrize("name", [
+ "group-page.js", "group-settings.js", "files-app.js", "transport.js",
+ "hub-client.js", "settings-ui.js", "folder-tree.js",
+])
+def test_no_shared_client_file_mentions_it(name):
+ """
+ The registry is where an app is named, and nowhere else. A branch on
+ `'helloworld'` in any of these would mean the architecture works for four
+ apps somebody wrote plumbing for.
+ """
+ source = _code_only((STATIC / name).read_text(encoding="utf-8"))
+ assert "helloworld" not in source.lower(), (
+ f"{name} names the reference app — adding an application is supposed "
+ f"to touch nothing here")
+
+
+@pytest.mark.parametrize("name", [
+ "ops.py", "roster.py", "config.py", "roots.py",
+])
+def test_no_shared_node_module_mentions_it(name):
+ """
+ Its directories are stored by `ops.set_app_directories`, which keys the row
+ by whatever the app is called. Nothing on the node knows what it is.
+ """
+ source = (NODE_SRC / name).read_text(encoding="utf-8")
+ assert "helloworld" not in source.lower(), (
+ f"{name} names the reference app; the generic path was supposed to "
+ f"cover it")
+
+
+def test_the_node_names_it_once_and_only_in_the_allow_list():
+ """
+ `ALLOWED_APPS` is server-side enforcement — a client naming an app this
+ node does not know is refused — so an app absent from it could not
+ demonstrate anything. That entry plus the client's registry line is the
+ whole cost.
+ """
+ source = (NODE_SRC / "transport" / "webrtc_server.py").read_text(
+ encoding="utf-8")
+ code = re.sub(r"^\s*#.*$", "", source, flags=re.M)
+ hits = [ln for ln in code.splitlines() if "helloworld" in ln.lower()]
+ assert len(hits) == 1, f"expected one mention, got: {hits}"
+ assert "ALLOWED_APPS" in hits[0] or "helloworld" in hits[0]
+ assert "ALLOWED_APPS" in code[:code.index("helloworld") + 200]
+
+
+def test_the_registry_entry_is_ordinary():
+ source = (STATIC / "apps.js").read_text(encoding="utf-8")
+ entry = source[source.index("key: 'helloworld'"):]
+ entry = entry[:entry.index("},") + 2]
+ for field in ("icon:", "labelKey:", "Component:", "Settings:"):
+ assert field in entry, f"the entry has no {field}"
+ assert "dev: true" in entry, "it would ship to every operator"
+
+
+# ── And it is held to the same contract as the rest ──────────────────────────
+
+def test_its_settings_pane_takes_the_shared_props_and_no_others():
+ m = re.search(r"function \w+Settings\(\{([^}]*)\}\)",
+ SETTINGS.read_text(encoding="utf-8"))
+ assert m
+ props = {p.strip() for p in m.group(1).split(",") if p.strip()}
+ assert props <= {"roots", "dirs", "settings", "saveDirectories",
+ "transport", "signFn"}
+
+
+def test_it_reads_its_directories_under_its_own_key():
+ """
+ `<key>Directories` — the shape `group-page.js` derives for every registered
+ app. An app reading a name spelled anywhere else would need that place
+ edited too.
+ """
+ for path in (APP, SETTINGS):
+ assert "helloworldDirectories" in path.read_text(encoding="utf-8")
+
+
+def test_it_does_not_reach_for_the_transport():
+ """
+ It has no third-party service and no setting of its own, so it needs
+ neither — which is the case an app author most often starts from, and the
+ one the architecture has to make free.
+ """
+ source = _code_only(SETTINGS.read_text(encoding="utf-8"))
+ assert "transport." not in source
+ assert "saveDirectories" in source
+
+
+# ── Hidden, but genuinely registered ────────────────────────────────────────
+
+def test_a_dev_app_is_filtered_out_by_default():
+ source = (STATIC / "apps.js").read_text(encoding="utf-8")
+ assert "function availableApps()" in source
+ body = source[source.index("function availableApps()"):]
+ body = body[:body.index("\n}") + 2]
+ assert "devAppsShown()" in body and "a.dev" in body
+
+
+def test_nothing_falls_back_to_the_unfiltered_registry():
+ """
+ A fallback of "every app in the registry" would enable a hidden one for the
+ whole group. This is the exception the reference app found.
+ """
+ source = _code_only((STATIC / "group-settings.js").read_text(encoding="utf-8"))
+ assert "APPS.map(" not in source and "APPS.filter(" not in source, (
+ "group-settings.js reads the raw registry; it should ask "
+ "availableApps()")
diff --git a/packages/meshbay-hub/tests/test_hook_ordering.py b/packages/meshbay-hub/tests/test_hook_ordering.py
index 5719be5..01516bd 100644
--- a/packages/meshbay-hub/tests/test_hook_ordering.py
+++ b/packages/meshbay-hub/tests/test_hook_ordering.py
@@ -43,6 +43,7 @@ STATIC_FILES = [
"settings-ui.js", "folder-tree.js",
"chat-app-settings.js", "video-app-settings.js",
"music-app-settings.js", "photos-app-settings.js",
+ "helloworld-app.js", "helloworld-app-settings.js",
"auth-page.js", "explore-page.js", "create-group-page.js",
]
diff --git a/packages/meshbay-hub/tests/test_transport_contracts.py b/packages/meshbay-hub/tests/test_transport_contracts.py
index 3c6d022..b462242 100644
--- a/packages/meshbay-hub/tests/test_transport_contracts.py
+++ b/packages/meshbay-hub/tests/test_transport_contracts.py
@@ -41,6 +41,8 @@ SPLIT_FILES = [APP, GROUP_PAGE, CHAT_APP, STATIC / "files-app.js",
STATIC / "video-app-settings.js",
STATIC / "music-app-settings.js",
STATIC / "photos-app-settings.js",
+ STATIC / "helloworld-app.js",
+ STATIC / "helloworld-app-settings.js",
STATIC / "auth-page.js", STATIC / "explore-page.js",
CREATE_GROUP]