summaryrefslogtreecommitdiffstats
path: root/packages/meshbay-node/tests
diff options
context:
space:
mode:
Diffstat (limited to 'packages/meshbay-node/tests')
-rw-r--r--packages/meshbay-node/tests/golden/cli.json9
-rw-r--r--packages/meshbay-node/tests/test_attach_from_the_hub.py99
-rw-r--r--packages/meshbay-node/tests/test_ops.py2
3 files changed, 105 insertions, 5 deletions
diff --git a/packages/meshbay-node/tests/golden/cli.json b/packages/meshbay-node/tests/golden/cli.json
index b397eeb..48d2f74 100644
--- a/packages/meshbay-node/tests/golden/cli.json
+++ b/packages/meshbay-node/tests/golden/cli.json
@@ -4,7 +4,7 @@
"asked": [],
"exit": 0,
"stderr": "",
- "stdout": "usage: meshbay-node [-h] [--hub-url HUB_URL] [--username USERNAME] [--dir DIR] [--yes]\n [--config CONFIG] [--group GROUP] [--link] [--writable] [--no-writable]\n [--removable] [--no-removable] [--name NAME]\n [--log-level {DEBUG,INFO,WARNING,ERROR}]\n [{init,reset,status,gek-init,gek,operator,member,group,root,file,video,chat,denylist,stun,transfers,reload,restart-daemon,autostart,service,calibrate-argon2}]\n [subcommand] [target] [value]\n\nMeshBay Node daemon\n\npositional arguments:\n {init,reset,status,gek-init,gek,operator,member,group,root,file,video,chat,denylist,stun,transfers,reload,restart-daemon,autostart,service,calibrate-argon2}\n init: provision config + keystore | reset: erase all node state | status:\n node state and keys | operator pair: pair a browser with this node |\n member list|invite|cancel|revoke|unpin | group list|add|remove | root\n list|add|remove|set|eject|plug | gek init|rotate | file list|rm | video\n rematch: re-resolve TMDB matches for a group's videos | chat\n status|rotate|encrypt-history|prune | denylist show|clear | stun\n list|add|remove|reset | transfers show|set|max-size|per-member: live\n transfer slots, the node-wide caps, the largest single upload, and how\n many one member may run at once in a group | reload: re-read node.toml\n (hot; systemd or the loopback API) | restart-daemon: restart the node\n (systemd unit, the Windows autostart launcher, or the service task,\n whichever applies) | autostart install|remove|start|stop|status (Windows:\n run meshbay-node at each sign-in, no admin) | service\n install|remove|start|stop|status (Windows: run at boot, before sign-in,\n needs admin once to install) | calibrate-argon2: benchmark\n subcommand 'pair' for operator; list|invite|revoke|unpin for member; list|add|remove\n for group; list|add|remove|set|eject|plug for root; init|rotate for gek;\n list|rm for file; rematch for video; show|clear for denylist;\n list|add|remove|reset for stun; show|set|max-size|per-member for\n transfers; install|remove|start|stop|status for autostart and for service\n target username for member invite|revoke|unpin (an optional e-mail label with\n --link, a link id for member cancel); group name for group add; file id\n for file rm; identifier for denylist clear; download cap for transfers\n set; size in GB for transfers max-size\n value the second value where a verb takes two: the upload cap for transfers set\n\noptions:\n -h, --help show this help message and exit\n --hub-url HUB_URL hub URL, for init (e.g. https://meshbay.org)\n --username USERNAME hub username, for init\n --dir DIR shared directory, for group add\n --yes skip the confirmation for destructive commands\n --config CONFIG Config file path\n --group GROUP group id (optional if only one is configured)\n --link member invite: an invitation link, for someone who may have no account yet\n (valid 7 days, single use)\n --writable root accepts member uploads (root add/set)\n --no-writable root is read-only (root add/set, group add)\n --removable mark root as removable (root set/add)\n --no-removable mark root as not removable (root set)\n --name NAME root name (root add; defaults to directory basename)\n --log-level {DEBUG,INFO,WARNING,ERROR}\n",
+ "stdout": "usage: meshbay-node [-h] [--hub-url HUB_URL] [--username USERNAME] [--dir DIR] [--yes]\n [--config CONFIG] [--group GROUP] [--link] [--writable] [--no-writable]\n [--removable] [--no-removable] [--open] [--name NAME]\n [--log-level {DEBUG,INFO,WARNING,ERROR}]\n [{init,reset,status,gek-init,gek,operator,member,group,root,file,video,chat,denylist,stun,transfers,reload,restart-daemon,autostart,service,calibrate-argon2}]\n [subcommand] [target] [value]\n\nMeshBay Node daemon\n\npositional arguments:\n {init,reset,status,gek-init,gek,operator,member,group,root,file,video,chat,denylist,stun,transfers,reload,restart-daemon,autostart,service,calibrate-argon2}\n init: provision config + keystore | reset: erase all node state | status:\n node state and keys | operator pair: pair a browser with this node |\n member list|invite|cancel|revoke|unpin | group list|add|remove | root\n list|add|remove|set|eject|plug | gek init|rotate | file list|rm | video\n rematch: re-resolve TMDB matches for a group's videos | chat\n status|rotate|encrypt-history|prune | denylist show|clear | stun\n list|add|remove|reset | transfers show|set|max-size|per-member: live\n transfer slots, the node-wide caps, the largest single upload, and how\n many one member may run at once in a group | reload: re-read node.toml\n (hot; systemd or the loopback API) | restart-daemon: restart the node\n (systemd unit, the Windows autostart launcher, or the service task,\n whichever applies) | autostart install|remove|start|stop|status (Windows:\n run meshbay-node at each sign-in, no admin) | service\n install|remove|start|stop|status (Windows: run at boot, before sign-in,\n needs admin once to install) | calibrate-argon2: benchmark\n subcommand 'pair' for operator; list|invite|revoke|unpin for member; list|add|remove\n for group; list|add|remove|set|eject|plug for root; init|rotate for gek;\n list|rm for file; rematch for video; show|clear for denylist;\n list|add|remove|reset for stun; show|set|max-size|per-member for\n transfers; install|remove|start|stop|status for autostart and for service\n target username for member invite|revoke|unpin (an optional e-mail label with\n --link, a link id for member cancel); group name for group add; file id\n for file rm; identifier for denylist clear; download cap for transfers\n set; size in GB for transfers max-size\n value the second value where a verb takes two: the upload cap for transfers set\n\noptions:\n -h, --help show this help message and exit\n --hub-url HUB_URL hub URL, for init (e.g. https://meshbay.org)\n --username USERNAME hub username, for init\n --dir DIR shared directory, for group add\n --yes skip the confirmation for destructive commands\n --config CONFIG Config file path\n --group GROUP group id (optional if only one is configured)\n --link member invite: an invitation link, for someone who may have no account yet\n (valid 7 days, single use)\n --writable root accepts member uploads (root add/set)\n --no-writable root is read-only (root add/set, group add)\n --removable mark root as removable (root set/add)\n --no-removable mark root as not removable (root set)\n --open group add: anyone the hub lists the group to may join (default: by\n invitation only)\n --name NAME root name (root add; defaults to directory basename)\n --log-level {DEBUG,INFO,WARNING,ERROR}\n",
"systemctl": []
},
"autostart no-such-sub": {
@@ -266,7 +266,7 @@
"asked": [],
"exit": 1,
"stderr": "",
- "stdout": "usage: meshbay-node group add <name> --dir <path> [--no-writable]\n\nThe group must already exist on the hub and be yours. This\nonly tells the node to host it, and picks its first\ndirectory, which accepts uploads unless --no-writable.\nAdd more with: meshbay-node root add <path> [--writable]\n",
+ "stdout": "usage: meshbay-node group add <name> --dir <path> [--no-writable] [--open]\n\nThe group must already exist on the hub and be yours. This\nonly tells the node to host it, and picks its first\ndirectory, which accepts uploads unless --no-writable.\nAdd more with: meshbay-node root add <path> [--writable]\n",
"systemctl": []
},
"group add g --dir /tmp/media --no-writable": {
@@ -275,6 +275,7 @@
"POST",
"/api/groups/attach",
{
+ "join_policy": "invite",
"name": "g",
"shared_dir": "/tmp/media",
"writable": false
@@ -284,7 +285,7 @@
"asked": [],
"exit": 0,
"stderr": "",
- "stdout": "g (g) added to <tmp>/node.toml\n shared_dir <tmp> (read-only)\n\nTell the daemon to re-read its config, then give the group a key:\n meshbay-node reload\n meshbay-node gek init --group g\n\nThe key is this group's own — members of your other groups cannot\nread it, and joining one says nothing about the other.\n",
+ "stdout": "g (g) added to <tmp>/node.toml\n shared_dir <tmp> (read-only)\n join_policy invite\n\nTell the daemon to re-read its config, then give the group a key:\n meshbay-node reload\n meshbay-node gek init --group g\n\nThe key is this group's own — members of your other groups cannot\nread it, and joining one says nothing about the other.\n",
"systemctl": []
},
"group list": {
@@ -423,7 +424,7 @@
"api": [],
"asked": [],
"exit": 2,
- "stderr": "usage: meshbay-node [-h] [--hub-url HUB_URL] [--username USERNAME] [--dir DIR] [--yes]\n [--config CONFIG] [--group GROUP] [--link] [--writable] [--no-writable]\n [--removable] [--no-removable] [--name NAME]\n [--log-level {DEBUG,INFO,WARNING,ERROR}]\n [{init,reset,status,gek-init,gek,operator,member,group,root,file,video,chat,denylist,stun,transfers,reload,restart-daemon,autostart,service,calibrate-argon2}]\n [subcommand] [target] [value]\nmeshbay-node: error: argument command: invalid choice: 'no-such-verb' (choose from init, reset, status, gek-init, gek, operator, member, group, root, file, video, chat, denylist, stun, transfers, reload, restart-daemon, autostart, service, calibrate-argon2)\n",
+ "stderr": "usage: meshbay-node [-h] [--hub-url HUB_URL] [--username USERNAME] [--dir DIR] [--yes]\n [--config CONFIG] [--group GROUP] [--link] [--writable] [--no-writable]\n [--removable] [--no-removable] [--open] [--name NAME]\n [--log-level {DEBUG,INFO,WARNING,ERROR}]\n [{init,reset,status,gek-init,gek,operator,member,group,root,file,video,chat,denylist,stun,transfers,reload,restart-daemon,autostart,service,calibrate-argon2}]\n [subcommand] [target] [value]\nmeshbay-node: error: argument command: invalid choice: 'no-such-verb' (choose from init, reset, status, gek-init, gek, operator, member, group, root, file, video, chat, denylist, stun, transfers, reload, restart-daemon, autostart, service, calibrate-argon2)\n",
"stdout": "",
"systemctl": []
},
diff --git a/packages/meshbay-node/tests/test_attach_from_the_hub.py b/packages/meshbay-node/tests/test_attach_from_the_hub.py
new file mode 100644
index 0000000..f4aaa25
--- /dev/null
+++ b/packages/meshbay-node/tests/test_attach_from_the_hub.py
@@ -0,0 +1,99 @@
+"""
+Hosting a group writes what the operator said, never what the hub says.
+
+`attach_group` looks the group up on the hub, because the node is the process
+signed in there. What it must not take from that answer is how people join: a
+hub able to declare a group open would be handed its key by anyone it sent
+(admission in `transport/webrtc/admission.py` admits a stranger to an open
+group). And every string it writes into node.toml is someone else's text — a
+group name chosen on the hub, a folder name — so none of it may end a TOML
+string and write lines of its own.
+"""
+
+import tomllib
+from pathlib import Path
+
+import pytest
+from meshbay_node import ops
+from meshbay_node.config import load_config
+from meshbay_node.ops.node_toml import toml_string
+
+GID = "0f8fad5b-d9cb-469f-a165-70867728950e"
+HOSTILE = 'Films"\n[node]\nui_port = 1\n# '
+
+
+class _Hub:
+ _session = object() # signed in
+
+ def __init__(self, group):
+ self._group = group
+
+ async def list_my_groups(self):
+ return [self._group]
+
+
+def _state(tmp_path: Path, group: dict) -> dict:
+ conf = tmp_path / "node.toml"
+ conf.write_text('[hub]\nurl = "https://hub.invalid"\nusername = "op"\n\n'
+ '[node]\nui_port = 18000\n', encoding="utf-8")
+ return {"config": load_config(conf), "config_path": str(conf), "hub": _Hub(group)}
+
+
+def _hosted(tmp_path: Path) -> dict:
+ parsed = tomllib.loads((tmp_path / "node.toml").read_text(encoding="utf-8"))
+ return parsed["groups"][0] | {"node": parsed["node"]}
+
+
+async def test_a_group_the_hub_calls_open_is_hosted_by_invitation(tmp_path):
+ state = _state(tmp_path, {"id": GID, "name": "Films", "visibility": "public",
+ "join_policy": "open"})
+ out = await ops.attach_group(state, "Films", str(tmp_path / "share"))
+
+ hosted = _hosted(tmp_path)
+ assert hosted["join_policy"] == "invite"
+ assert hosted["visibility"] == "private"
+ assert out["hub_join_policy"] == "open", "the caller is not told the two differ"
+
+
+async def test_the_operator_opens_it(tmp_path):
+ state = _state(tmp_path, {"id": GID, "name": "Films", "join_policy": "invite"})
+ await ops.attach_group(state, "Films", str(tmp_path / "share"), join_policy="open")
+
+ hosted = _hosted(tmp_path)
+ assert (hosted["join_policy"], hosted["visibility"]) == ("open", "public")
+
+
+async def test_an_unknown_policy_is_refused(tmp_path):
+ state = _state(tmp_path, {"id": GID, "name": "Films"})
+ with pytest.raises(ops.OpError):
+ await ops.attach_group(state, "Films", str(tmp_path / "share"),
+ join_policy="anyone")
+
+
+async def test_a_group_name_cannot_write_lines_into_node_toml(tmp_path):
+ state = _state(tmp_path, {"id": GID, "name": HOSTILE})
+ await ops.attach_group(state, HOSTILE, str(tmp_path / "share"))
+
+ hosted = _hosted(tmp_path)
+ assert hosted["name"] == HOSTILE
+ assert hosted["node"]["ui_port"] == 18000
+
+
+async def test_a_folder_name_cannot_either(tmp_path):
+ state = _state(tmp_path, {"id": GID, "name": "Films"})
+ await ops.attach_group(state, "Films", str(tmp_path / "share"))
+ state["config"] = load_config(tmp_path / "node.toml")
+ # Root names are refused with such characters already (roots.py); the
+ # path is not, and is the operator's own folder or a member's request.
+ weird = tmp_path / 'a "quoted"\\ folder\n[node]'
+ await ops.add_root(state, GID, str(weird), name="extra")
+
+ hosted = _hosted(tmp_path)
+ assert Path(hosted["roots"][-1]["path"]) == weird
+ assert hosted["node"]["ui_port"] == 18000
+
+
+@pytest.mark.parametrize("value", ["plain", 'q"uote', "back\\slash", "line\nbreak",
+ "tab\there", "del\x7f", "café 日本"])
+def test_every_string_reads_back_as_written(value):
+ assert tomllib.loads(f"v = {toml_string(value)}")["v"] == value
diff --git a/packages/meshbay-node/tests/test_ops.py b/packages/meshbay-node/tests/test_ops.py
index 9eb8339..2c5a15a 100644
--- a/packages/meshbay-node/tests/test_ops.py
+++ b/packages/meshbay-node/tests/test_ops.py
@@ -405,7 +405,7 @@ def test_every_path_written_into_node_toml_goes_through_as_posix():
import re
source = ops_source()
# Every f-string interpolation that lands on the right of a TOML `path =`.
- writes = re.findall(r'path\s*=\s*\\?"\{([^}]+)\}', source)
+ writes = re.findall(r'path\s*=\s*\\?"?\{(?:toml_string\()?([^}]+)\}', source)
assert writes, "no TOML path writer found — did the config writer move?"
for expr in writes:
assert "as_posix()" in expr, (