aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-cli/tests/test_cli_actions.py
diff options
context:
space:
mode:
authorChristophe Besson <cbesson@gmail.com>2026-10-15 12:32:24 +0200
committerChristophe Besson <cbesson@gmail.com>2026-10-15 12:32:24 +0200
commit988a0eecd522431980174910db28e45a63ff2710 (patch)
tree80b16f8cfe907f084fac931fa35326c7f2892a9d /packages/meshbay-cli/tests/test_cli_actions.py
parente0a1036e0fee15b3c6c83f3e935d50f472a2e776 (diff)
downloadmeshbay-988a0eecd522431980174910db28e45a63ff2710.tar.gz
feat(cli): account, rm -r and mkdir, Tab completion
account shows the account and does what the sign-up form and the Profile page do: register (the passphrase floor and the hub's proof of work checked here, the recovery key printed once), verify, change the passphrase, reset a forgotten one, change the e-mail address, sign out everywhere, delete. A new passphrase carries the copy of the account's keys each node keeps for browsers first — opened under the old M, the same keys sealed under the new one — and only then tells the hub; a refusal sets the copies back. A reset restores them from the recovery copy. The connections that do it never make keys nor ask for a device approval. This machine keeps the M it changed away from, so a node out of reach is carried at its next connection; a copy under an M it does not know is no longer rewritten, which locked the browsers out after a passphrase changed elsewhere. rm -r removes a folder and all it holds, from the node's operator key: the files, then the folders deepest first, one confirmation that counts them. mkdir makes folders, --parents the missing ones (not -p, the passphrase). completion bash|powershell prints the script that makes Tab complete actions, verbs, options and the paths of the groups this machine is signed in to, cached two minutes; the deb and rpm packages install the bash one. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'packages/meshbay-cli/tests/test_cli_actions.py')
-rw-r--r--packages/meshbay-cli/tests/test_cli_actions.py78
1 files changed, 78 insertions, 0 deletions
diff --git a/packages/meshbay-cli/tests/test_cli_actions.py b/packages/meshbay-cli/tests/test_cli_actions.py
index bcbe0c4..006c183 100644
--- a/packages/meshbay-cli/tests/test_cli_actions.py
+++ b/packages/meshbay-cli/tests/test_cli_actions.py
@@ -205,6 +205,25 @@ def test_a_usage_error_is_exit_1_not_the_sign_in_code(capsys):
+def test_account_shows_the_account(hub, capsys):
+ assert main("--hub", "https://hub.test", "-u", USERNAME, "--password", PASSPHRASE,
+ "login") == 0
+ capsys.readouterr()
+ assert main("account") == 0
+ out = capsys.readouterr().out
+ assert f"Account: {USERNAME}" in out and "https://hub.test" in out
+ assert main("--json", "account", "show") == 0
+ assert json.loads(capsys.readouterr().out)["username"] == USERNAME
+
+
+def test_completion_prints_each_shells_script(capsys):
+ assert main("completion", "bash") == 0
+ assert "complete -F _meshbay_cli meshbay-cli" in capsys.readouterr().out
+ assert main("completion", "powershell") == 0
+ assert "Register-ArgumentCompleter -Native" in capsys.readouterr().out
+ assert main("completion", "fish") == 1
+
+
def test_help_prints_what_help_prints(capsys):
assert cli.main(["--help"]) == 0
whole = capsys.readouterr().out
@@ -472,6 +491,65 @@ def test_rm_deletes_only_what_this_account_may_and_asks_first(node, monkeypatch,
assert not node.files
+def test_rm_r_removes_a_folder_and_all_it_holds_from_the_operator_key(node, capsys):
+ from fake_hub import USER_ID
+ node.add("share/trip/day 1", "a.jpg", b"A" * 2000, uploader_id=USER_ID)
+ node.add("share/trip/day 2", "b.jpg", b"B" * 1000, uploader_id="someone-else")
+ node.add("share/trip", "notes.txt", b"n", uploader_id=USER_ID)
+ node.dirs |= {"share/trip/empty", "share/kept"}
+ kept = node.add("share/kept", "c.txt", b"c", uploader_id=USER_ID)
+
+ # Not the operator: refused whole, nothing deleted, not even its own files.
+ assert main("rm", "-r", "--yes", "Family/share/trip") == 8
+ assert "operator" in capsys.readouterr().err
+ assert len(node.files) == 4
+ # A root is never removed.
+ node.role = "operator"
+ assert main("rm", "-r", "--yes", "Family/share") == 8
+ assert "root" in capsys.readouterr().err
+
+ assert main("--json", "rm", "-r", "--yes", "Family/share/trip") == 0
+ out = json.loads(capsys.readouterr().out)
+ assert len(out["deleted"]) == 3 and out["failed"] == []
+ assert out["folders_removed"][-1] == "Family@alice-test/share/trip" # deepest first
+ assert {"Family@alice-test/share/trip/empty", "Family@alice-test/share/trip/day 1",
+ "Family@alice-test/share/trip/day 2"} <= set(out["folders_removed"])
+ assert set(node.files) == {kept["id"]}
+ assert not any(d == "share/trip" or d.startswith("share/trip/") for d in node.dirs)
+
+
+def test_rm_r_says_what_it_will_delete_before_it_asks(node, monkeypatch, capsys):
+ node.role = "operator"
+ for i in range(12):
+ node.add("share/big", f"f{i}.bin", b"x" * 1024)
+ monkeypatch.setattr("sys.stdin", _Terminal("n\n"))
+ assert main("rm", "-r", "Family/share/big") == 1
+ asked = capsys.readouterr().err
+ assert "Delete 12 files (12.0 KB) and 1 folder" in asked
+ assert "Family@alice-test/share/big/" in asked
+ assert len(node.files) == 12
+
+
+def test_mkdir_makes_folders_where_the_root_accepts_them(node, capsys):
+ assert main("mkdir", "Family/share/2026") == 0
+ assert "share/2026" in node.dirs
+ assert main("mkdir", "Family/share/2026") == 8 # already there
+ assert main("mkdir", "Family/share/a/b/c") == 3 # no parent
+ assert "--parents makes it too" in capsys.readouterr().err
+ assert main("--json", "mkdir", "--parents", "Family/share/a/b/c", "Family/share/2026") == 0
+ assert json.loads(capsys.readouterr().out)["made"] == [
+ "Family@alice-test/share/a", "Family@alice-test/share/a/b",
+ "Family@alice-test/share/a/b/c"]
+ node.add("share", "Notes.txt", b"n")
+ assert main("mkdir", "Family/share/notes.TXT") == 8 # a file, without case
+ assert "(a file)" in capsys.readouterr().err
+ assert main("mkdir", "Family/share/bad\x01name") == 8
+ assert main("mkdir", "Family/share") == 1 # a root is not made here
+ node.roots[0]["writable"] = False
+ assert main("mkdir", "Family/share/x") == 8
+ assert "read-only" in capsys.readouterr().err
+
+
def test_search_finds_across_groups_once_per_file(hub, monkeypatch, capsys):
from fake_node import FakeNode
from meshbay_cli.client.session import DeviceLinkPending