aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-hub/tests
diff options
context:
space:
mode:
Diffstat (limited to 'packages/meshbay-hub/tests')
-rw-r--r--packages/meshbay-hub/tests/test_connect_never_hangs.py4
-rw-r--r--packages/meshbay-hub/tests/test_desktop_shell.py81
-rw-r--r--packages/meshbay-hub/tests/test_indexing_dock.py4
3 files changed, 82 insertions, 7 deletions
diff --git a/packages/meshbay-hub/tests/test_connect_never_hangs.py b/packages/meshbay-hub/tests/test_connect_never_hangs.py
index 5680877..1fb4d3d 100644
--- a/packages/meshbay-hub/tests/test_connect_never_hangs.py
+++ b/packages/meshbay-hub/tests/test_connect_never_hangs.py
@@ -69,7 +69,7 @@ def test_a_timed_out_gathering_still_sends_the_offer():
@pytest.mark.skipif(not MAIN.exists(), reason="the desktop client is not present")
def test_every_hub_call_from_the_client_has_a_deadline():
source = MAIN.read_text(encoding="utf-8")
- block = source.split("ipcMain.handle('hub:fetch'", 1)[1].split("ipcMain.handle", 1)[0]
+ block = source.split("handle('hub:fetch'", 1)[1].split("\n handle(", 1)[0]
assert "AbortSignal.timeout" in block, (
"a hub that accepts the connection and says nothing holds this for "
"as long as the OS allows")
@@ -88,5 +88,5 @@ def test_the_deadline_outlasts_the_hubs_own_longest_call():
@pytest.mark.skipif(not MAIN.exists(), reason="the desktop client is not present")
def test_a_timeout_says_so_rather_than_saying_fetch_failed():
source = MAIN.read_text(encoding="utf-8")
- block = source.split("ipcMain.handle('hub:fetch'", 1)[1].split("ipcMain.handle", 1)[0]
+ block = source.split("handle('hub:fetch'", 1)[1].split("\n handle(", 1)[0]
assert "TimeoutError" in block and "did not answer" in block
diff --git a/packages/meshbay-hub/tests/test_desktop_shell.py b/packages/meshbay-hub/tests/test_desktop_shell.py
index b9b3319..732ba07 100644
--- a/packages/meshbay-hub/tests/test_desktop_shell.py
+++ b/packages/meshbay-hub/tests/test_desktop_shell.py
@@ -18,7 +18,7 @@ import re
from pathlib import Path
import pytest
-from spa_source import transport_files
+from spa_source import STATIC, transport_files
CLIENT = Path(__file__).resolve().parents[2] / "meshbay-client"
MAIN = CLIENT / "src" / "main.js"
@@ -378,6 +378,81 @@ def test_plain_http_is_refused_except_to_loopback():
assert "127\\." in source and "localhost" in source
+def test_every_channel_answers_only_the_packaged_page():
+ """
+ A channel registered straight on `ipcMain` would answer any frame that got
+ hold of a bridge; `handle()` checks the sender first. So no channel may be
+ registered any other way, and every one the preload names is registered.
+ """
+ source = _main()
+ wrapper = source.split("function handle(channel, fn) {", 1)[1].split("\n}\n", 1)[0]
+ assert "fromOurPage(event)" in wrapper
+ assert source.count("ipcMain.handle(") == 1, "a channel skips the sender check"
+ registered = set(re.findall(r"\n handle\('([\w:-]+)'", source))
+ invoked = set(re.findall(r"ipcRenderer\.invoke\('([\w:-]+)'", _preload()))
+ assert invoked <= registered, f"the preload names unregistered channels: {invoked - registered}"
+
+
+def test_the_page_cannot_read_or_replace_the_secret_store():
+ """It holds the device's hub key (§8.2), which the page is never handed."""
+ for channel in ("secrets:get", "secrets:set", "secrets:clear"):
+ assert channel not in _main() and channel not in _preload(), channel
+
+
+def _node_ops() -> dict[str, str]:
+ """Each operation of `NODE_OPS` in main.js, with its source."""
+ block = _main().split("const NODE_OPS = {", 1)[1].split("\n };\n", 1)[0]
+ parts = re.split(r"\n (\w+):", "\n" + block)
+ return dict(zip(parts[1::2], parts[2::2]))
+
+
+def test_the_page_names_node_operations_not_routes():
+ """
+ The page used to hand the main process a method and a path, which made the
+ whole loopback API the page's. Every operation the interface calls exists,
+ and none exists that it does not call — an unused one is surface.
+ """
+ assert "node:call" not in _main() and "node:call" not in _preload()
+ used: set[str] = set()
+ for path in STATIC.glob("*.js"):
+ used |= set(re.findall(r"(?:node\.op|nodeOp)\('(\w+)'", path.read_text(encoding="utf-8")))
+ defined = set(_node_ops())
+ assert used, "no node operation found in the interface"
+ assert used <= defined, f"called but not defined: {used - defined}"
+ assert defined <= used, f"defined but never called: {defined - used}"
+
+
+@pytest.mark.parametrize("op", ["attachGroup", "addRoot", "initGek", "clearDenylist"])
+def test_what_widens_the_node_is_confirmed_natively(op):
+ """Sharing a folder, hosting a group, replacing the key, re-admitting a
+ revoked subject: asked by a dialog the main process draws, which a script in
+ the page cannot answer. A folder chosen in the native picker is its own
+ confirmation."""
+ body = _node_ops()[op]
+ assert "confirmOrRefuse(" in body or "confirmFolder(" in body
+
+
+def test_the_native_dialogs_are_worded_in_every_language():
+ """The words come from the interface's catalogues; a key missing from one is
+ a dialog that shows its key."""
+ keys = set(re.findall(r"(?:confirmOrRefuse|nativeText)\('([\w.]+)'", _main()))
+ assert "native.declined" in keys and "dialog.ok" in keys
+ for catalogue in (STATIC / "locales").glob("*.js"):
+ text = catalogue.read_text(encoding="utf-8")
+ missing = [k for k in keys if f"'{k}':" not in text]
+ assert not missing, f"{catalogue.name} lacks {missing}"
+
+
+def test_the_node_is_never_pointed_at_a_hub_the_page_names():
+ """`node:start` writes the hub this application is signed in to, and a
+ username that cannot break out of a TOML string."""
+ source = _main()
+ assert "opts.hubUrl" not in source
+ provision = source.split("async function provisionFromRequest(opts) {", 1)[1]
+ provision = provision.split("\n }\n", 1)[0]
+ assert "config.hubBase" in provision and "USERNAME_RE.test(username)" in provision
+
+
# ── One interface, one source ───────────────────────────────────────────────
def test_the_interface_is_copied_not_forked():
@@ -420,7 +495,7 @@ def test_automatic_saving_never_opens_a_dialog_for_want_of_a_folder():
system Downloads folder is the answer when there is no other.
"""
source = _main()
- begin = source.split("ipcMain.handle('save:begin'", 1)[1].split("ipcMain.handle", 1)[0]
+ begin = source.split("handle('save:begin'", 1)[1].split("\n handle(", 1)[0]
assert "defaultDownloadDir()" in begin, (
"the automatic path has no destination when no folder was chosen")
assert "app.getPath('downloads')" in source
@@ -430,7 +505,7 @@ def test_a_chosen_folder_that_has_gone_is_not_silently_replaced():
"""Someone who picked an external drive should be told it is not there,
not find the film in their home directory a week later."""
source = _main()
- begin = source.split("ipcMain.handle('save:begin'", 1)[1].split("ipcMain.handle", 1)[0]
+ begin = source.split("handle('save:begin'", 1)[1].split("\n handle(", 1)[0]
assert "config.downloadDir && !chosen" in begin, (
"a chosen-but-missing folder falls through to the default instead of asking")
assert "showSaveDialog" in begin
diff --git a/packages/meshbay-hub/tests/test_indexing_dock.py b/packages/meshbay-hub/tests/test_indexing_dock.py
index 93803a0..e960950 100644
--- a/packages/meshbay-hub/tests/test_indexing_dock.py
+++ b/packages/meshbay-hub/tests/test_indexing_dock.py
@@ -190,5 +190,5 @@ def test_the_transport_passes_the_new_counters_through():
def test_the_dock_polls_the_node_wide_route_not_one_group():
source = DOCK.read_text(encoding="utf-8")
- assert "'/api/index-status'" in source
- assert "/index-status`" not in source
+ assert "node.op('indexStatus')" in source
+ assert "groupIndexStatus" not in source